좋은 코드 리뷰는 코드를 훑어보기만 하는 것이 아니라 어떤 이해 불가능한 천재적인 요소를 더해야 하는 것이 아닙니다. 시스템의 각 측면을 반복적으로 고려하고 코드의 정신적 모델을 구축한 다음 그 과정을 사용하여 어리석은 버그, 고전적인 버그 및 심층적인 버그를 찾는 것입니다.
좋은 코드 리뷰는 다양한 관점에서 작동하며 높은 수준에서 가장 작은 세부 사항까지 반복적으로 전환합니다.
나는 지금까지 스마트 계약 보안에 중점을 두고 세 해 동안 일해왔고, 코드 리뷰는 그 작업의 중요한 부분입니다. 이것은 내가 그들로부터 최대한의 효과를 얻기 위한 나의 시스템입니다.
내부 코드 리뷰는 버그를 찾는 가장 효율적인 방법입니다. 이상적으로 당신의 단위 테스트와 포크 테스트가 코드가 "작동"하는 일을 확인하는 데 쉬운 작업을 수행할 수 있어야 합니다.
계약 보안은 그라디언트이며 부울이 아닙니다. 퍼즈 테스트, 불변 테스트 및 형식 증명은 모두 코드가 올바른지 확인하는 데 도움이 되는 환상적인 레이어입니다. 그러나 보안 그라디언트에서 가장 큰 이동은 좋은 코드 리뷰에서 나옵니다.
스마트 계약에서 정확성은 굉장히 중요합니다.
대부분의 다른 산업은 수십 또는 수백만 달러가 즉시 전송 가능한 버그를 찾아 악용하는 사람에게 전환되지 않습니다.
비행기를 탈 때: 네, 세상은 무작위이고, 네, 비행기가 많고, 네, 하드웨어 고장은 문제입니다. 그러나 파리, 동유럽, 북한, 샌프란시스코 및 캐나다 야생 지역에 있는 1만 명의 사람들이 코드를 읽고 비행 제어 시스템에 케이블을 연결하고 날씨, 시계, 공항 레이아웃을 공격적으로 변경하며 비행기에 방사선을 쏘아 넣어 코드가 실수를 범할 수 있는 특정 상태로 시스템을 놓는 것처럼 아닙니다. 스마트 계약은 본질적으로 적대적인 환경에서 살아남습니다.
그리고 악한 인간 뿐만 아니라 어둠 속 숲에 숨어있는 모든 자동 시스템이 있습니다. 이 코드를 지속적으로 뚫고 무료 자금의 징표에서 습격할 준비가 되어 있습니다.
코드가 작동하는 것만으로는 충분하지 않습니다. 그것이 무작위에도 작동하는 것만으로도 충분하지 않습니다. 코드는 주변 환경을 변경하여 이용하려는 적대적인 적에게 모든 상황에서 완벽하게 올바르게 작동해야 합니다. 완벽한 코드는 인간이 처음에 바로 올바르게 작성하기 어렵습니다.
우리가 코드 리뷰를 하는 두 번째 이유는 코드를 간단하게 유지하고 싶기 때문입니다.
장기적으로 간단함은 버그를 방지하고 개발 속도를 높일 것입니다. 시스템에 남아 있는 복잡성은 우리가 미래 소프트웨어 개발을 할 때마다 시간 세금이며, 복잡성은 미래에 구축하는 모든 것에 위험을 추가합니다.
단순함은 무료로 얻을 수 있는 것이 아닙니다. 쉬운 것도 아닙니다. 단순함을 만들기 위해서는 시간이 필요하며, 작업이 필요하며, 창의성이 필요합니다. 단순함은 다차원적인 문제입니다. 때로는 단순함에 대한 다양한 접근 사이에서 트레이드오프를 해야 할 수도 있습니다.
단순함이 어려우므로 코드에 대한 팀의 여러 사람의 의견, 피드백, 완벽함을 원합니다. 단순함은 종종 협업 프로세스의 결과입니다.
이것이 웹2 회사였다면 행동이 변경되지 않는 방식으로 코드의 가장 작은 라인을 지적하는 것은 반사회적인 행동이 될 것입니다. 여기서 우리의 솔리디티 코드에서는 우리 각자가 가져올 수 있는 완벽성을 추구하고 싶습니다.
코드 리뷰를 하는 세 번째 이유는 팀 내에서 지식을 공유하고자 하기 때문입니다.
우리는 4명의 스마트 계약 개발자 팀이 있습니다. 코드를 작성하는 사람이 심층적인 리뷰를 진행하고 두 명의 추가적인 사람이 또한 심층적인 리뷰를 하면 팀의 3/4가 새로운 코드를 심층적으로 이해하게 됩니다. 팀이 코드베이스를 심층적으로 이해할 때 팀 다이내믹스와 나머지 시스템과 호환되는 코드를 작성할 수 있는 능력에 중요한 차이가 있습니다.
이러한 코드 리뷰는 팀 간에 코딩 기술 및 보안 관련 문제를 공유합니다. 학습은 양방향입니다. 코드 리뷰에서 읽은 내용에서 배울 수 있고, 내 코드에 대한 코드 리뷰에서 다른 사람이 발견한 내용에서도 배울 수 있습니다.
예를 들어, 지난 주에 코드를 리뷰하면서 개발자가 상속 계층 구조에서 상위 수준에서 정보를 하위 기본 코드로 가져오는 필요를 어떻게 처리했는지를 보았습니다. 그 주 후반에는 동일한 기본 코드를 공유하는 다양한 종류의 Curve 풀을 처리하는 방법을 살펴볼 때 배운 것을 활용했습니다.
코드 리뷰는 코드를 한 번 훑어보고 보이는 대로 의견을 달고 그것을 승인하는 것이 아닙니다.
우리가 무엇을 시도하고 있는지를 다시 한 번 돌아보고 보겠습니다. 우리는 모든 버그를 찾으려고 노력하고 있습니다. 나는 버그를 세 가지 범주로 생각하는 것을 좋아합니다.
어리석은 버그 (예: 함수에 대한 까먹은 인증) 클래식한 버그 (재진입, 오라클 조작과 같은) 매우 어려운 버그 (시스템이 예상보다 복잡한 경우) 핵심 문제는 어떻게 깊고 세련된 깊은 버그를 찾을 수 있는가입니다. 이것은 종종 코드를 작성한 사람보다 머릿속에서 더 나은 코드의 정신적 모델을 만들어야 하는 것을 필요로 합니다. 특히 출발 시점에서 어떻게 그렇게 할 것인가?
해결책은 코드를 반복적으로 훑어보는 것입니다. 각각 다른 문제를 찾기 위해 코드를 여러 번 반복하는 것이며, 매번 더 나은 사진, 더 풍부한 코드의 정신적 모델을 만들어가는 것입니다. 정말로 정신적 표현에서 어려운 버그를 찾을 수 있습니다.
그래서 목표는 다음과 같습니다:
첫 번째로 하는 일은 첫 패스 읽기입니다. 이것은 코드의 전체적인 구조에 대한 느낌을 얻어 나중에 효율적으로 탐색할 수 있도록 하는 것입니다.
지금 나는 리뷰에 종이를 좋아합니다. 그래서 종이에 코드를 출력하고 종종 첫 패스 읽기를 위해 코드 주석을 제거합니다.
그런 다음 내 종이에 내가 가진 모든 질문, 나쁜 것처럼 보이거나 추측한 코드가 어떻게 깨질 수 있는지에 대한 방법을 기록합니다. 나는 이런 일에 대해 종종 틀립니다 - 나는 것들이 깨져 있는 것에 대해 낙관적인 경향이 있습니다. (첫 패스 읽기를 마치면 내 주석을 통해 다시 작업하고 실제로 문제인지 확인합니다. 여기서 시스템에 대한 내 지식을 쌓는 데 또 다른 단계입니다.)
나머지 과정은 고려/확인해야 할 항목들을 담은 체크리스트 문서를 기반으로 진행됩니다. 이 큰 장점은 시스템을 여러 가지 관점에서 생각하도록 강요한다는 것입니다. 코드를 읽을 때는 존재하지 않는 것들을 찾는 것을 잊기 쉽습니다. 체크리스트는 이러한 부재의 문제를 확인하는 데 도움이 됩니다.
우리의 체크리스트는 우리 고유의 코드베이스와 원하는 코드 스타일에 특화되어 있습니다.
체크리스트를 통과하면 시스템에 대한 이해가 깊어지며 어리석은 버그와 클래식한 버그가 드러납니다.
우리 체크리스트의 몇 가지 항목은 실제로 코드가 리뷰를 통과하기 위해 반드시 참이어야 하는 것은 아닙니다. 그것들은 참이 아니라면 매우 주의 깊게 조사해야 하며 위험에 주의해야 하는 것들입니다. 예를 들어 "원시 이더리움 사용 안 함"이라는 체크리스트 항목이 있고 거의 대부분의 계약이 이를 준수합니다. 그러나 원시 이더리움을 사용해야 할 경우 가능한 위험에 특히 주의를 기울여 문제가 발생할 수 있는 상황을 심각하게 고려해야 합니다.
체크리스트는 코드에서 일반적인 버그를 찾는 가장 효과적인 방법입니다.
다음 접근 방식은 시스템의 불변성에 대해 생각하는 것입니다.
불변성은 시스템이 항상 올바르기 위해 항상 참이어야 하는 것들입니다. 시스템을 생각하는 효과적인 방법 중 하나이며 실제로 시스템이 항상 우리가 기대하는 대로 작동하는지 확인하는 데 유용합니다.
이를 "이 코드가 실행되기 전에 참이어야 하는 것들", "이 코드가 실행된 후에 참이어야 하는 것들", 그리고 "다른 상태 변수들 간의 관계에 대한 규칙"으로 분해할 수 있습니다.
따라서 한 번 이러한 불변성을 기록하면 코드를 다시 검토하고 이 불변성이 실제로 유지되는지 확인합니다.
상태 불변성은 특히 유용합니다. 시스템이 올바르기 위해 어떤 상태는 항상 다른 상태와 특정한 관계를 가져야 합니다. 예를 들어 각 계정의 잔액을 더하면 시스템의 총 잔액과 동일해야 한다고 가정해 봅시다. 상태 불변성을 구축하는 한 가지 좋은 방법은 계약의 구성이 아닌 변수 목록을 따라가면서 각각에 대한 관계를 생각하는 것입니다.
불변성 기반 사고는 아마도 코드 리뷰 및 버그 찾기의 가장 과소평가된 비밀 무기일 것입니다.
배포 코드, 구성, 필수 모니터링, 그리고 지배 조치는 코드만큼 최종 시스템의 일부입니다. 여기에 모든 것이 올바른지 확인하십시오. 모든 주소가 올바른지 수동으로 확인하는 것을 포함하여 모든 것이 정확한지 확인하십시오.
마지막으로 몇 분의 분별력 있는 포크 테스트에는 큰 가치가 있습니다. 시스템을 사용하면서 발견되는 것이 얼마나 놀라운지 알 수 있습니다. 코드는 테스트를 통과하도록 작성되었지만 테스트와 다른 숫자 또는 작업 순서를 사용하면 잘못된 동작을 할 수 있습니다. 이는 배포 및 지배 조치가 작동하는 시스템으로 결과를 보장합니다.
보통 여기서 수행하는 포크 테스트를 기록하고 약간 정리한 후 코드가 실제로 배포된 후 나중에 다시 사용할 수 있도록 저장합니다.
코드의 초안이 작성되면 나는 일반적으로 이 전체 프로세스와는 전혀 달라 보이는 비공식적인 첫 번째 패스 리뷰를 수행하고 싶어합니다. 이렇게 하면 테스트 스위트를 작성하기 전에 모든 설계 수정을 수행하고 시스템에 진짜 품질을 추가할 수 있습니다.
그래서 실제 내부 리뷰는 코드가 작성되고이 코드 세트의 소유자가 코드와 테스트에 만족할 때 발생합니다. 먼저 소유자는 자체 리뷰를 수행하고 나서 나머지 두 팀 멤버를 리뷰하도록 태그합니다.
코드 소유자는 언제나 첫 번째 리뷰를 수행해야 합니다. 현재 소유자는 전체 우주에서 이 코드를 가장 잘 이해하는 사람이므로 새로운 관점을 강요함으로써 버그를 찾을 가능성이 꽤 높습니다. 두 번째로, 이것은 소유자가 자신의 리뷰가 얼마나 좋은지 즉각적인 피드백을 얻을 수 있게 합니다. 소유자가 버그를 잡지 못하면 희망적으로 나머지 두 리뷰어가 찾아낼 것입니다.
코드를 감사자에게 보내기 전에 완전한 내부 리뷰 프로세스를 수행하려고 합니다. 이렇게 하면 외부 감사가 내부 프로세스를 확인하는 역할을 합니다. 감사자가 중요한 버그를 찾으면 내부 리뷰 프로세스를 어떻게 변경해야 하는지 다시 고민해야 합니다.
가장 어려운 버그를 찾는 비결은 시스템을 여러 다양한 관점과 여러 다양한 수준에서 바라보고 간단한 버그에 대한 체크리스트를 사용하는 것입니다!