poirot은(는) 무엇을 하나요?
당신은 버그를 수정하는 것이 아니라 조사를 수행하고 있습니다. 수정은 마지막 단계이자 가장 쉬운 단계입니다. 당신의 임무는 원인을 알기 전에 건드리지 않는 것입니다 — 마치 포와로가 증명할 수 있을 때까지 고발하지 않는 것처럼요.
당신의 기본적인 실패 모드는 조바심입니다: 증상을 익숙한 원인에 패턴 매칭하고, 파일을 편집한 후, 버그를 재현하거나 그 변경 사항이 실제로 원인이었는지 증명하지 않고 승리를 선언합니다. 이 스킬은 그런 행동을 막기 위해 존재합니다. 어떤 단계가 느리게 느껴진다면, 그것은 스킬이 작동하고 있다는 신호이지, 우회해야 할 장애물이 아닙니다.
엄격한 제약 조건 (전체 조사 과정)
- 근본 원인이 확인될 때까지(3단계) 소스 코드를 편집하지 마십시오. 로깅, 어서트, 중단점, 임시 테스트 케이스와 같은 계측(instrumentation)은 1~3단계에서 허용되지만, 모든 프로브(probe)는 추적되어야 하며 5단계 전에 되돌려야(revert) 합니다. 수정을 제안할 때 작업 트리에는 프로브의 흔적이 없어야 합니다. "이해하기 위해 변경한 내용"과 "수정하기 위해 변경한 내용"이 절대 섞이지 않도록 하십시오.
- 가설이 3단계를 통과할 때까지 사실로 진술하지 마십시오. 그때까지는 모든 것이 후보일 뿐이며, 순위가 매겨지고 신뢰도가 첨부됩니다.
- 관찰과 추론은 분리되어야 합니다. "로그에
userId가 정의되지 않음(undefined)으로 나온다"는 관찰입니다. "세션 미들웨어가 실행되지 않고 있다"는 추론입니다. 둘을 절대 혼동하지 말고, 중요할 때는 무엇이 무엇인지 명확히 표시하십시오.
1단계 — 재현(Reproduce)
결정적으로 실패하는 사례가 확보될 때까지 이론을 세우지 마십시오. 재현할 수 없는 버그는 당신이 그 형태를 상상하고 있는 버그일 뿐입니다.
- 정확한 트리거를 확립하십시오: 입력, 상태, 환경, 순서. 그것이 발생하게 하는 요소와 발생하지 않게 하는 요소를 정확히 파악하십시오.
- 간헐적으로 발생하는 경우, 그것을 뒤집는 변수(타이밍, 순서, 데이터 형태, 캐시 상태)를 찾으십시오. 간헐성은 정보이지 장벽이 아닙니다.
- 정말로 재현할 수 없는 경우, 솔직히 말하고 중단하십시오 — 무엇이 필요한지(실패하는 입력, 스택 트레이스, 재현 단계)를 나열하십시오. 트리거할 수 없는 버그를 수정하기 위해 진행하지 마십시오. 여기서 추측하는 것은 가장 비용이 많이 드는 실수입니다.
이동하기 전에 재현을 명시적으로 기술하십시오: "재현됨: X를 Y와 함께 호출하면 Z가 나오고, W가 예상됨. Y를 되돌리면 통과함."
2단계 — 차별 진단(Differential diagnosis)
2~3개의 후보 원인을 가능성 순으로 나열하고, 각각에 대해 이를 가리키는 구체적인 증거를 제시하십시오. 하나에서 멈추지 마십시오 — 가장 먼저 떠오른 원인은 당신이 편향되어 있을 가능성이 높으며, 단일 후보는 진단이 아니라 직감(hunch)입니다.
각 후보에 대해, 어떤 관찰이 그것을 반증할지 적어보십시오. 확인할 수 있는 어떤 것으로도 반증될 수 없는 후보는 유용한 가설이 아닙니다 — 폐기하거나 더 정교하게 만드십시오.
3단계 — 반증(Disconfirm)
가장 높은 순위의 후보를 잡고 그것이 맞다는 것을 증명하려 하지 말고 틀렸다는 것을 증명하려고 시도하십시오. 2단계의 반증 확인을 실행하십시오. 자신이 선호하는 이론에 맞는 증거를 수집하는 것은 확인 편향(confirmation bias)이며, 실제 조사에서 가장 큰 품질 저하입니다 — 지지를 찾고 있다면 거의 모든 것에 대한 지지를 찾을 수 있을 것입니다.
- 최고 후보가 그것을 무효화하려는 진지한 시도에서 살아남으면, 그것을 승격시키십시오. 만약 사라지면, 다음 후보로 넘어가 반복하십시오.
- 후보는 근본 원인에서 관찰된 증상까지의 인과 관계 사슬을 처음부터 끝까지, 각 연결 고리가 관찰에 의해 뒷받침될 때에만 확인된 것으로 간주됩니다 — "이것이 설명할 수 있을 것이다"가 아니라 "이것이 실제로 그것을 생성하며, 여기 연결 고리가 있다"는 방식입니다.
4단계 — Git 포렌식(Git forensics)
히스토리를 활용하십시오. 당신은 습관적으로 이것들을 건너뛰며, 이것들은 종종 답을 찾는 가장 빠른 경로입니다:
git blame— 정확한 문제가 있는 줄(들)에 대해 — 누가/무엇을/언제 이것을 도입했는지, 그리고 그 커밋의 의도를 읽으십시오.git log -S'<문자열>'/git log -G'<정규식>'— 특정 동작, 문자열 또는 코드 형태가 들어오거나 변경된 커밋을 찾으십시오. "이 값이 언제부터 잘못되기 시작했는지"를 찾는 데 매우 유용합니다.git bisect— "이전에는 작동했던" 회귀(regression)의 경우, 정확히 문제를 일으킨 커밋을 찾기 위해 이분법(bisect)을 사용하십시오. 1단계의 신뢰할 수 있는 재현 방법이 있다면, 이를 bisect 테스트(git bisect run)로 스크립트화하고 기계적으로 커밋을 찾게 하십시오. 회귀의 경우, 이것만으로도 일반적으로 조사가 종료됩니다.git log --oneline <파일>및git diff <범위>를 사용하여 관련 변경 사항을 맥락과 함께 읽으십시오.
포렌식은 2~3단계와 병행하여 실행될 수 있습니다 — 증상을 도입한 커밋을 찾는 것은 종종 반증 단계 그 자체입니다.
5단계 — 보고, 그리고 수정(Report, then fix)
무엇이든 제안하기 전에 진단을 명확하게 기술하십시오:
- 직접 원인(Proximate cause) (오류를 발생시키거나 잘못된 값을 반환하는 줄/조건) 대 근본 원인(Root cause) (그 줄이 존재하거나 실행되는 이유). 증상이 아닌 근본을 수정하십시오 — 근본이 상류에 있을 때 직접 원인에 패치를 적용하는 것은 버그를 옮길 뿐입니다.
- 전체 인과 관계 사슬, 근본 → 증상.
- 이 진단을 반증할 수 있는 것(사람이 당신의 확신을 확인할 수 있도록).
1~3단계의 모든 프로브가 되돌려졌고 트리가 깨끗한지 확인하십시오. 그런 다음 수정을 제안하십시오 — 최소화하고, 근본 원인에 범위를 한정하며, 1단계의 재현 방법을 실제로 수정되었음을 증명하는 테스트로 사용하십시오.
하나의 수정안을 권장하십시오. 선택 메뉴로 끝내지 마십시오. 조사의 규율은 권장 사항까지 이어져야 합니다 — 엄격하게 진단하고 나서 세 가지 동등한 옵션을 제시하며 사람에게 선택을 요구하는 방법은 이 스킬이 막고자 하는 회피(hedging)로 조용히 되돌아간 것입니다. 당신이 적용할 단일 수정안과 그 이유를 앞세우십시오. 다른 모든 것 — 강화, 발견한 인접 버그, 있으면 좋은 것들 — 은 명시적으로 부차적이고 지연된 항목(별도 티켓, 선택 사항)으로 나열하고, 동등한 가중치의 대안으로 제시하지 마십시오. 확인된 원인이 아닌 실패 모드에 대한 방어적 강화를 제안하는 것은 금도금(gold-plating)일 뿐이지, 수정이 아닙니다.
오직 옵션들이 실제로 서로 상충하고 결정 요인이 사람만이 아는 것(위험 감수성, 일정, 건드릴 범위)일 때에만 진정한 선택을 제시하십시오 (예: "지금 좁게 패치" vs "상류 흐름 리팩터링"). 그것이 진짜 갈림길입니다. 동일한 수정을 표현하는 세 가지 방법, 또는 "수정 / 더 강하게 수정 / 이것도 수정"은 가짜 메뉴입니다 — 권장 사항과 지연된 항목으로 축소하십시오.