본문으로 건너뛰기

프로그래머의 왕도는 없다

개발팀 회의에서 팀원들에게 전하려고 적었던 메모를 다듬은 1인칭 에세이. 지름길은 없지만, 반복 가능한 길은 있다는 이야기.

유클리드에게 기하학을 쉽게 배우는 지름길이 없느냐고 물은 왕에게, 그는 "기하학에는 왕도(royal road)가 없다"고 답했다는 일화가 전해진다. 이 일화의 역사적 정확성은 차치하고, 나는 이 문장을 프로그래밍에 그대로 빌려 오고 싶다. 프로그래머의 왕도는 없다. 이 글은 내가 동료를 가르치고 스스로를 단련하는 방식에 대한 기록이다.

이 글을 읽는 기준

이 글에서 **"내 경험이다"**라고 표시한 문장은 나에게만 검증된 개인적 휴리스틱이다. 그렇게 표시하지 않은 일반론 역시 널리 통용되는 관찰이지, 만인에게 증명된 법칙은 아니다. 둘을 구분해서 읽되, 휴리스틱은 자기 환경에서 실험해 보고 맞으면 가져가고 틀리면 폐기하면 된다.

절차가 아니라 방향과 불변 조건을 가르친다

내가 팀원에게 방향은 알려 주되 그 과정을 정해 주지 않는 이유는 단순하다. 실무에서 마주치는 문제는 "해답은 하나"라는 선택지가 주어진 적이 거의 없기 때문이다. 정해진 절차를 외운 사람은 그 절차가 통하는 문제까지만 갈 수 있다. 절차가 막히는 순간, 절차 뒤에 있던 이유를 모르는 사람은 멈춰 서고, 이유를 아는 사람은 절차를 다시 설계한다.

그래서 나는 순서를 외우게 하는 대신 몇 개의 불변 조건(invariant)을 새기게 한다. "외부 입력은 경계에서 검증한다", "상태를 바꾸는 지점은 한곳으로 모은다", "실패를 삼키지 않고 표면에 올린다" 같은 문장들이다. 언어와 프레임워크가 바뀌어도 성립하는 문장이기에, 도구가 교체된 후에도 판단 기준으로 남는다. 문제에 대한 해결 방안을 스스로 모색해 보고, 그 과정에서 부딪히고, 그 부딪힘을 통해 성장하는 것 — 이 순서를 대신해 줄 수 있는 정답지는 없다.

한 가지 가치관도 덧붙인다. 이것은 보편적 사실이 아니라 나의 신념이다. 단순한 구현을 반복하는 것은 코더가 되는 길이고, 더 좋은 방법이 없는지를 계속 찾아가는 것이 프로그래머가 갖춰야 할 덕목이라고 나는 믿는다. 배운 스승이나 사고방식에 스스로를 고정시키지 않는 것 — 배움의 출처를 존중하되 그 안에 갇히지 않는 것 — 이 신념의 다른 표현이다.

에러와 직접 씨름한 시간이 가장 오래 남는다

에러는 직접 해결해 볼 것. 이것이 내 경험의 핵심이다. 남이 고쳐 준 에러는 기억에 남지 않는다. 누군가 내 자리에 앉아 삼십 분 만에 고쳐 준 문제는 일주일 뒤에 똑같은 얼굴로 다시 나타난다. 반면 한밤중까지 로그와 스택 트레이스를 뒤지며 직접 잡아낸 에러는 몇 년이 지나도 비슷한 징후만으로 알아챌 수 있게 해 준다. 이것은 어디까지나 내 경험이며, 모든 상황에서 혼자 씨름하는 것이 최선이라는 뜻은 아니다.

프로덕션 장애에서는 배움이 복구를 미루지 않는다

원래 메모에는 "회사에서 프로젝트를 하는 중이라면, 에러 난 순간을 그대로 저장해 두고 남는 시간에 해결하라"고 적혀 있었다. 취지는 지금도 옳지만, 운영 환경에서는 순서를 더 엄격하게 잡아야 한다. 복구가 학습보다 먼저다. 서비스가 멈춰 있는 동안 원인 분석에 몰입하는 것은 학습이 아니라 피해 확산이다. 롤백, 재시작, 핫픽스 등 가장 빠르고 검증된 복구 경로를 먼저 택하고, 학습은 그 뒤로 미룬다.

다만 복구 전에 가능한 범위에서 증거를 보존한다. 에러 로그, 스택 트레이스, 배포 버전과 재현 경로를 남기되 요청 전체를 그대로 복사하지 않는다. 미리 정한 allowlist의 필드만 수집하고 인증 토큰·세션 ID·비밀번호·개인정보는 마스킹하거나 제외한다. 증거 저장소에도 최소 권한, 접근 감사와 짧은 보존 기간을 적용한다. 안전하게 남길 수 없는 값은 수집하지 않는다. 서비스가 안정된 뒤에 — 그제야 — 이 스냅샷을 열어 아래의 학습 루프를 돌린다. 배움은 미뤄지는 것이지 포기되는 것이 아니다.

남의 코드를 많이 읽어라

세 번째 원칙은 남의 코드를 많이 읽는 것이다. 글을 많이 읽은 사람이 글을 잘 쓰고 어휘가 풍부해지는 것과 같은 이치다. 내 코드만 보고 있으면 사고방식이 고정된다 — 이것도 내 경험이다. 내가 아는 패턴이 세상의 전부인 것처럼 느껴지기 시작하는 순간이 위험 신호다. 팀원의 코드, 매일 쓰는 라이브러리의 내부 구현, 언어의 표준 라이브러리까지 가리지 않고 읽되, "왜 이렇게 했을까"를 함께 묻는다. 코드 리뷰에서도 오픈소스를 읽을 때도, 나는 같은 질문 목록을 사용한다.

  • 이 코드가 해결하려는 문제를 한 문장으로 말할 수 있는가
  • 입력과 출력, 그리고 불변 조건이 어디서 보장되는가
  • 실패 경로가 성공 경로만큼 명시적인가 — 에러를 삼키는 곳은 없는가
  • 이름이 의도를 드러내는가, 아니면 주석이 이름을 대신 변명하고 있는가
  • 이 패턴이 이 맥락에서 선택된 이유를 추측할 수 있는가 — 왜 다른 대안이 아니라 이것인가
  • 테스트가 코드의 내부가 아니라 기대하는 동작을 설명하고 있는가
  • 이 코드에서 한 가지를 가져와 내 코드에 쓴다면 무엇이겠는가

항상 "왜?"를 묻고, 대안을 비교하라

마지막 원칙은 비판적이 되라는 것이다. 모든 결정에 "왜?"가 붙는 것이 듣는 사람에게는 고통일 수 있다는 것을 안다. 하지만 이 질문의 목적은 상대를 공격하는 것이 아니다. 스스로 한 번 더 생각하고, 그게 정말 맞는지, 다른 대안은 정녕 없는지를 확인하는 것이다. 애자일 적용 0x0002에서 기획자가 Why와 What을 설명하고 개발자가 How와 When으로 답했던 구조도, 같은 질문을 조직 차원에서 제도화한 예다. 내가 논문과 발표를 리뷰할 때 쓰는 노트 역시 모든 구성 요소에 "왜 이것을 사용했는가"를 붙이고, 강점과 약점을 나란히 정리하는 읽기법이다.

비판이 실용으로 이어지게 하려면 형식이 필요하다. 나는 설계 결정을 내릴 때 최소 두 개의 대안을 나란히 놓고, 선택하지 않은 쪽의 기각 사유를 함께 남긴다. 대안 비교 없는 선택은 선택이 아니라 관성이다.

나의 학습 루프

위의 원칙들이 실천으로 굳는 형태가 있다. 에러든, 낯선 코드든, 장애든, 만나는 문제 전부를 이 여섯 단계로 통과시킨다.

  1. 문제 정의 — 에러 메시지를 옮겨 적는 것에서 끝내지 않고, "기대한 동작"과 "실제 동작"의 차이를 한 문장으로 쓴다. 문제가 문장으로 잡히지 않으면 아직 모르는 것이다.
  2. 가설 — "~인 것 같다"가 아니라 "~라면 ~이 관찰되어야 한다"의 형태로 쓴다. 반증 가능한 문장만이 가설이다.
  3. 가장 작은 실험 — 그 가설을 죽일 수 있는 최소의 행동 하나를 고른다. 로그 한 줄 추가, 재현 케이스 하나, 절반씩 좁혀 가는 주석 처리. 한 번에 두 가지를 바꾸지 않는다.
  4. 증거 — 관찰한 것을 기대가 아니라 있는 그대로 기록한다. 기대와 어긋난 관찰일수록 지우지 않는다. 그것이 다음 가설의 씨앗이다.
  5. 설명 — 왜 그런 일이 일어났는지를 인과로 말할 수 있어야 끝난 것이다. "고쳐졌다"와 "이해했다"는 다르다.
  6. 재사용 가능한 노트 — 다음에 같은 문제를 만날 나, 혹은 전혀 모르는 동료가 검색 한 번으로 찾아 쓸 수 있는 형태로 남긴다. 노트의 형식 기준은 이 사이트의 블로그 편집 가이드에 맞춘다.

이 루프는 새로운 발명이 아니다. 애자일 적용 0x0001에서 정리한 "측정 → 수정 → 측정"의 반복, 그리고 과학 방법의 가설-검증 절차를 일상 규모로 축소한 것이다. 중요한 것은 루프의 존재가 아니라 반복 가능성이다. 어제의 루프와 오늘의 루프가 다른 모양이면, 그것은 루프가 아니라 즉흥이다.

자주 빠지는 함정 세 가지

원칙을 실천할수록 자주 보게 되는, 나 자신을 포함해 누구나 빠지기 쉬운 함정 셋을 적는다.

  1. 카고 컬트 (cargo cult). 유명 코드베이스나 기술 블로그의 패턴을, 그 패턴이 해결했던 문제의 맥락 없이 통째로 복사하는 것이다. 패턴은 그것이 죽인 문제와 함께 가져와야 한다. 문제가 없는 자리에 놓인 패턴은 해결책이 아니라 장식이고, 장식은 다음 독자의 이해 비용이 된다.
  2. 끝없는 혼자만의 씨름 (endless solo struggle). "에러는 직접 해결하라"는 원칙의 가장 흔한 왜곡이다. 씨름의 시간이 배움의 시간인 것은 맞지만, 진전 없는 씨름에는 상한이 필요하다. 내 기준으로는 — 보편적 기준이 아니라 내 휴리스틱이다 — 몇 시간 동안 가설이 줄지 않으면, 그때까지 관찰한 증거와 시도한 실험을 정리해서 질문으로 바꾼다. 좋은 질문으로 다듬는 과정 자체가 학습이며, 무작정 버티는 것보다 훨씬 빠르다.
  3. 성과 과시형 비판 (performative criticism). "왜?"를 검증의 도구가 아니라 존재감의 무기로 쓰는 것이다. 대안 없는 비판은 비판이 아니다. 비판할 때는 대안 하나와, 그 대안이 치러야 할 비용까지 함께 가져온다. 이 규칙은 남에게도 나 자신에게도 적용한다.

주간 실천 체크리스트

원칙은 주기적으로 점검하지 않으면 구호가 된다. 매주 스스로에게 묻는 다섯 가지다. 회고라는 활동 자체의 가치는 애자일 적용 0x0002의 retrospective 기록에도 잘 정리되어 있다.

  • 이번 주에 에러 하나를 끝까지 직접 해결하고, 그 과정을 학습 루프의 노트로 남겼는가
  • 내가 쓰지 않은 코드를 읽고, 가져올 패턴 하나와 버릴 패턴 하나를 기록했는가
  • 내린 설계·구현 결정 하나에 "왜?"를 묻고, 대안 하나와 나란히 비교했는가
  • 누군가에게 정해진 절차 대신 방향과 불변 조건을 설명한 적이 있는가
  • 이번 주 커밋 중에 무언가를 더 단순하게 만든 것이 하나 이상 있는가

마치며

왕도는 없다. 하지만 길이 아예 없는 것도 아니다. 방향을 알려 주되 걸음은 스스로 걷게 할 것, 에러와 씨름하게 할 것, 남의 코드를 읽게 할 것, "왜?"를 멈추지 않게 할 것 — 이 네 가지가 내가 아는, 지름길은 아니지만 꺾이지 않는 길의 전부다. 이 문장들 역시 내 경험에서 온 휴리스틱이다. 읽는 사람이 자기 환경에서 실험해 보고, 맞는 것은 가져가고 틀린 것은 폐기하면 그만이다. 그 폐기의 과정마저 이 글이 말한 루프의 일부가 될 것이다.