에러 우선 사고방식: 오늘 깨질 부분을 기준으로 코딩 세션 설계하기
기능 중심에서 에러 중심 코딩으로 전환해 보세요. 매 코딩 세션을 ‘어디가 가장 잘 깨질지’를 기준으로 설계하는 방법을, 리스크 평가·타깃 테스트·가벼운 실천법·탄탄한 설계 원칙과 함께 정리했습니다.
에러 우선 사고방식: 오늘 깨질 부분을 기준으로 코딩 세션 설계하기
대부분의 개발자는 코딩을 시작할 때 이렇게 묻습니다.
“오늘 뭐를 만들어야 하지?”
조용히 더 높은 성과를 내는 개발자들은 이렇게 묻습니다.
“오늘 뭐가 가장 잘 깨질까?”
이 작은 전환, 즉 **기능 우선(feature-first)**에서 **에러 우선(error-first)**으로의 전환은 계획, 코딩, 테스트 방식 전체를 바꿉니다. 버그를 짜증 나는 방해 요소로 보는 대신, 핵심 설계 제약 조건이자 값진 데이터로 다루게 됩니다.
이 글에서는 “무엇을 출시할지”가 아니라 “무엇이 어떻게 깨질지”를 기준으로 코딩 세션을 설계하는 방법을 단계별로 살펴봅니다.
1. 목표를 바꾸기: 기능 배송에서 리스크 관리로
대부분의 팀은 기능 단위로 일정을 잡습니다.
- 엔드포인트 X 구현하기
- UI 컴포넌트 Y 만들기
- 서비스 Z 연동하기
에러 우선 사고방식은 세션을 이렇게 다시 정의합니다.
세션 목표: 지금 만들려는 것 중, 가장 위험한 부분을 찾아내고, 스트레스 테스트하고, 영향 범위를 제한한다.
기능 자체는 여전히 중요하지만, 이제 기능은 리스크 관리를 위한 맥락이 되고 더 이상 유일한 목표가 아닙니다.
키보드를 치기 전에 스스로에게 물어보세요.
- 어디가 가장 실패할 가능성이 큰가?
- 어디가 가장 불확실한가?
- 어디가 잘못되면 피해가 가장 클까?
이제 당신의 코딩 세션은 어디서, 어떻게 깨질지를 당신의 조건 안에서 실험하는 통제된 실험이 됩니다.
2. 코딩 전에 리스크를 체계적으로 스캔하기
바로 구현을 시작하는 대신, 5–10분 정도만 투자해서 리스크를 훑어보세요. 크게 세 가지 범주에 집중합니다.
2.1 복잡한 영역
본능적으로 “이건 좀 까다롭겠다” 싶은 로직이나 아키텍처를 찾으세요.
- 동시성, 비동기 흐름, 레이스 컨디션
- 상태 전이가 많은 상태 기반 시스템
- 중첩된 조건문, 분기 로직, 기능 플래그(feature flag)
- 성능에 민감한 경로
화이트보드나 종이에 30초 안에 플로우를 설명하지 못한다면, 거기는 리스크 핫스팟입니다.
2.2 외부 의존성
내가 완전히 통제할 수 없는 것은 모두 리스크를 증폭시킵니다.
- 서드파티 API 또는 SDK
- 메시지 큐, 브로커
- 스키마나 마이그레이션이 불분명한 데이터베이스
- 문서가 부족한 레거시 서비스
스스로에게 물어보세요.
이 의존성이 느리거나, 불안정하거나, 예상치 못한 데이터를 돌려주면 어떻게 될까?
2.3 불명확하거나 자주 바뀌는 요구사항
버그는 코드에서만 나오지 않습니다. 상당수는 의도에 대한 오해에서 시작됩니다.
- “X처럼 동작” 또는 “충분히 빠르게” 같은 애매한 수용 기준
- 서로 다른 멘탈 모델을 가진 여러 이해관계자
- 타임아웃, 재시도, 부분 실패 같은 엣지 케이스가 명시되지 않은 경우
“일단 대충 구현해 보고 나중에 맞추지 뭐”라는 생각이 든다면, 이미 하나의 리스크를 발견한 겁니다.
이 리스크들을 짧게 메모로 남기세요. 이것이 곧 이번 코딩 세션 플랜의 뼈대가 됩니다.
3. 구현보다 먼저 테스트와 실험을 설계하기
깨질 가능성이 높은 부분을 파악했다면, 바로 구현부터 하지 말고 테스트와 실험부터 설계하세요.
3.1 가장 위험한 부분을 먼저 때려 보기
각 고위험 영역마다 작은 테스트나 실험을 만듭니다.
- 복잡한 로직: 경계값, 이상 상태를 때리는 유닛 테스트 작성
- 외부 서비스: 느린 응답, 타임아웃, 잘못된 페이로드를 시뮬레이션하는 통합 테스트 작성
- 불명확한 요구사항: “예시 시나리오”를 만들어 팀이나 PO와 확인
단순히 정답 여부를 검증하는 것이 아니라, 의도적으로 내 설계를 부수려는 시도입니다.
3.2 실패를 싸고, 일찍, 통제된 상태에서 만들기
알려지지 않은 요소들을 통제된 실험으로 바꿉니다.
- 가장 위험한 플로우의 최소 구현(스파이크)을 만들어 보기
- 간단한 프로토타입에 부하를 걸어 어디서 비틀리는지 보기
- 기능 토글(feature toggle)을 사용해 위험한 로직을 다크 런(dark)으로 배포하고 관찰하기
목표는 빨리, 안전하게, 배울 수 있게 망가뜨리는 것입니다. 리스크가 넓게 퍼지기 전에 말이죠.
4. 일상적인 코딩에 리스크 관리 프레임워크 녹여 넣기
리스크 관리는 항공우주나 금융만의 전유물이 아닙니다. 그들이 쓰는 도구를 가볍게 변형해, 일상 개발에도 충분히 적용할 수 있습니다.
4.1 간단한 리스크 매트릭스
머릿속이나 메모에 2×2 그리드를 그립니다.
- Impact(영향도): Low vs. High (깨졌을 때 얼마나 큰 피해인가?)
- Likelihood(발생 가능성): Low vs. High (깨질 가능성이 얼마나 되는가?)
앞서 찾은 각 리스크를 이 그리드에 배치합니다.
- High Impact / High Likelihood: 가장 먼저 대응 (핵심 로직, 크리티컬 경로)
- High Impact / Low Likelihood: 가드, 모니터링, 폴백으로 완충
- Low Impact / High Likelihood: 어느 정도 실패는 허용하되, 폭발 범위를 제한
- Low Impact / Low Likelihood: 나중에 처리하거나 수용
이렇게 하면, 실제로는 별 영향도 없는 희귀 엣지 케이스에 집착하면서 정작 위험하고 가능성 높은 실패를 방치하는 일을 피할 수 있습니다.
4.2 Failure Modes and Effects Analysis(경량 FMEA)
위험한 컴포넌트에 대해, 아주 간단하게 다음을 훑어봅니다.
- Function(기능): 이 컴포넌트는 무엇을 해야 하는가?
- Failure modes(실패 모드): 어떤 식으로 실패할 수 있는가? (잘못된 데이터, 데이터 없음, 지연된 데이터, 깨진 상태 등)
- Effects(영향): 실패하면 다운스트림에 어떤 일이 벌어지는가? 누가, 무엇이 피해를 보는가?
- Detection(탐지): 이런 실패를 우리는 어떻게 알아챌 수 있는가?
- Mitigation(완화): 발생 가능성이나 피해를 어떻게 줄일 수 있는가?
5–10분만 투자해도, 지금 바로 넣어둘 수 있는 누락된 체크, 로그, 세이프가드를 여럿 발견하게 됩니다.
5. 버그를 문제로만 보지 말고 ‘데이터’로 다루기
에러 우선 사고방식에서는 버그를 시스템과 프로세스에 대한 피드백 신호로 봅니다.
버그를 마주쳤을 때, 고치고 넘어가기만 하지 말고 이렇게 물어보세요.
- 이건 어떤 카테고리의 실패인가? (로직, 연동, 요구사항, 환경)
- 이걸 잡아줬어야 할 테스트가 있었나? 없다면 지금 추가한다.
- 로그나 모니터링에 신호가 있었는데 무시했거나, 애초에 내지 않았나?
- 어떤 가정이 틀렸던 건가?
그리고 자신의 멘탈 모델과 습관을 업데이트합니다.
- 레이스 컨디션이 자주 터진다면, 동시성 리스크가 크다는 뜻 → 공유 상태를 줄이는 설계로 전환하고, 더 결정적인(비 nondeterministic) 테스트를 늘립니다.
- 연동(통합) 버그가 많다면, 더 이른 시점에 목(mock) 환경과 계약 테스트(contract test)를 구축합니다.
- 요구사항 불일치가 반복된다면, 이해관계자와 시나리오를 더 강하게 검증하는 프로세스를 도입합니다.
시간이 지날수록, 에러 우선 접근은 자기 교정(self-correcting) 시스템이 됩니다. 각각의 버그가 다음 리스크 추정의 정확도를 높여 줍니다.
6. 에러를 중심에 둔, 가벼운 시스템과 습관 만들기
거창한 프로세스가 꼭 필요한 건 아닙니다. 대신, 에러 다루기를 일상화해 주는 작고 반복 가능한 실천법 몇 가지만 추가하면 됩니다.
6.1 체크리스트
코딩 전 또는 머지 전 체크리스트를 짧게 만듭니다. 예를 들면:
- 이번 변경에서 가장 위험한 3가지를 명확히 적어두었는가?
- 각 고위험 영역마다 최소 한 개 이상의 스트레스 테스트가 있는가?
- 이 코드가 남길 로그와 에러 메시지만 보고도, 미래의 누군가가 원인을 파악할 수 있는가?
- 의존성이 느리거나, 내려가 있거나, 쓰레기 데이터를 돌려줄 때 무슨 일이 일어나는가?
체크리스트는 “대충 괜찮겠지” 같은 기억과 감정에 의존하지 않게 해 주고, 압박 속에서도 일관성을 유지시켜 줍니다.
6.2 프리모텀(Pre-mortem)
기능을 구현하기 전에 짧게 프리모텀을 해봅니다.
“이 기능이 프로덕션에서 최악의 방식으로 실패했다고 가정해 보자. 뭐가 가장 그럴듯하게 잘못됐을까?”
가능한 실패 시나리오를 리스트업한 뒤:
- 그 시나리오를 겨냥한 테스트, 모니터링, 폴백을 추가합니다.
- 실패 모드가 너무 파괴적이라면 설계 자체를 손봅니다.
6.3 로깅과 가시성(Observability) 기준 세우기
깨졌을 때, 상황을 쉽게 보고 이해할 수 있어야 합니다.
- 에러 메시지를 표준화하고, 핵심 컨텍스트(사용자 ID, 요청 ID, 작업 타입 등)를 포함합니다.
- 로그 레벨(info/debug/warn/error)을 적절히 사용해 진짜 이슈가 눈에 띄게 합니다.
- 위험한 연산마다 추적 가능한 로그와 메트릭을 확보합니다.
목표는, 무언가 깨졌을 때 추측 없이, 빠르게 근본 원인까지 도달할 수 있게 하는 것입니다.
7. ‘탄탄함(Resilience)’을 1급 설계 목표로 만들기
에러 우선 코딩의 궁극적인 결과는 탄탄한(resilient) 시스템입니다. 아무 일도 안 일어나길 바라지 않고, 반드시 실패할 것이라는 전제 아래 설계합니다.
7.1 완벽이 아니라 실패를 전제로 계획하기
각 컴포넌트마다 이렇게 질문해 보세요.
- 이게 실패한다면, 가장 우아한(그래도 괜찮은) 실패 방식은 무엇인가?
- 완전히 죽지 않고 성능 저하(degrade) 수준에서 버틸 수 있는가? (기능 축소, 캐시 데이터 사용, 폴백 UI 등)
- 어떻게 회복하고, 가능한 자동으로 셀프 힐(self-heal)할 수 있을까?
예를 들면:
- 실시간 API가 다운되면, 캐시되거나 부분적인 데이터라도 보여주기
- DB가 잠시 내려가 있으면, 쓰기 요청을 큐잉해 두었다가 나중에 반영하기
- 서킷 브레이커(circuit breaker)를 사용해 연쇄 실패를 막기
7.2 복구 경로를 명시적으로 설계하기
다음 내용을 문서화하고 구현합니다.
- 안전하게 재시도하는 방법 (멱등(idempotent) 연산, 유니크 요청 ID 등)
- 잘못된 배포가 났을 때 롤백 또는 롤 포워드하는 절차
- 운영자가 명확한 런북(runbook)을 가지고 개입할 수 있는 방법
탄탄함은 단순히 업타임이 아니라, 압박 상황에서도 예측 가능하고 통제 가능한 동작을 의미합니다.
결론: 실패가 기본값이라고 가정하고 코딩하기
“깨질 것을 중심으로 코딩 세션을 설계한다”는 건 비관주의가 아니라, 프로다운 현실 감각입니다.
다음을 통해:
- 시작 단계에서 리스크를 체계적으로 평가하고,
- 가장 위험한 경로를 겨냥한 테스트와 실험을 먼저 설계하고,
- 간단한 리스크 프레임워크로 우선순위를 정하고,
- 모든 버그를 피드백으로 처리하고,
- 에러에 초점을 둔 가벼운 습관과 기준을 만들고,
- 탄탄함을 핵심 설계 목표로 삼으면,
당신은 실패에 반응만 하는 사람이 아니라, 실패의 모양과 범위를 미리 설계하는 사람이 됩니다.
시스템이 깨질 때 놀라는 대신, 이런 말을 할 수 있게 될 겁니다.
“네, 이런 유형의 문제가 생길 걸 어느 정도 예상했어요. 그래서 대비도 해 두었죠.”
다음 코딩 세션을 시작할 때 이렇게 물어보세요.
*“오늘 뭐 만들어야 하지?”*가 아니라,
“오늘 어디가 가장 잘 깨질까? 그리고 그걸 위해 내가 지금 뭘 할 수 있을까?”