시니어 엔지니어의 7가지 코딩 패턴 (7 Coding Patterns of Senior Engineers)
한 줄 정의
소프트웨어 복잡성을 격리하고 동료 개발자의 불필요한 놀라움(surprise)을 방지하기 위해, 시니어 엔지니어들이 코드 작성 및 아키텍처 판단 단계에서 실천하는 7가지 핵심 코딩 패턴.
핵심 요지
- 코드는 쓰는 것보다 읽는 비용이 더 비싸다: 시니어 엔지니어는 자신의 지식을 뽐내기 위한 화려한 추상화 대신, 급박한 장애 상황에서 동료가 역공학하지 않고도 직관적으로 의도를 이해할 수 있는 지루하고 명확한 코드를 선호한다.
- 의사결정의 패턴: 프레임워크 종속적인 기술에 매몰되기보다 버그를 신속히 탐지하고, 비즈니스 요구사항 변경에 유연하게 대응하며, 시스템을 오해하기 어렵게 설계하는 데 집중한다.
- 7가지 패턴의 본질: 코드 들여쓰기를 최소화하고(Return Early), 구현이 아닌 비즈니스 의미를 강조하며(Business Naming), 외부 시스템의 오염을 차단하고(Boundary), 무효한 데이터 상태를 원천 차단하며(Prevent Invalid State), 판단과 실행을 분리하고(Separation), 추적 가능한 에러를 제공하고(Friendly Error), Git Diff를 깔끔하게 관리(Diff-Driven)하는 실질적 룰이다.
상세
1. 조건문 미로 대신 조기 반환(Return Early) 사용
- 개념: 함수가 깊숙이 들여써지는 터널 구조가 되지 않도록 오류가 날 만한 예외 경로(Guard clauses)를 함수 상단에서 먼저 쳐내는 패턴이다.
- 장점: 개발자가 장애 상황에서 흐름을 읽는 방식(“이 함수를 멈추는 요인은 무엇인가?“)대로 작성되므로 가독성이 높고 디버깅이 쉬워진다. 정상 경로(happy path)가 중첩 블록 밑바닥에 파묻히는 것을 방지한다.
2. 기술적 구현이 아닌 비즈니스적 의미를 이름으로 지정
- 개념:
data,result,payload,temp같은 단순 기술 표현 대신subscription,isBillable,usersEligibleForReactivation처럼 비즈니스 도메인의 상세 규칙이 투영된 이름을 짓는다. - 장점: 구현 세부 사항이 바뀌어도 비즈니스의 본질적인 뜻이 유지되며, 잘못된 코드 사용(예: 삭제되지 않고 정지된 사용자가
activeUsers에 들어가는 등)으로 인한 버그를 줄인다.
3. 외부의 혼돈을 막는 명확한 완충 경계(Boundary) 설정
- 개념: 서드파티 API(Stripe, Slack, GitHub 등)의 응답 데이터나 데이터베이스의 로우(Raw) 데이터, 프레임워크에 종속적인 요청(Request) 객체가 핵심 도메인 로직에 그대로 노출되지 않도록 완충 어댑터(Mapper/Adapter)를 두는 방식이다.
- 장점: 외부 API 필드가 임의로 변경되어도 전체 내부 로직이 깨지지 않고 어댑터의 맵핑 코드만 수정하면 되므로 외부의 혼돈이 누수(leakage)되는 것을 완벽히 방어한다.
4. 올바르지 않은 상태가 발생하는 것 자체를 아예 차단 (Prevent Invalid State)
- 개념: 모든 필드를 옵셔널(
?)로 설정하여 컴파일러의 경고만 우회하는 느슨한 설계를 피하고, TypeScript의 식별 가능한 유니온(Discriminated Union) 등을 사용해 상호 배제적이고 정밀한 상태를 표현한다. - 예시: 가입 중인 유저(
DraftUser)와 영구 저장된 유저(SavedUser)를 결합하지 않고 분리함으로써, 이메일이 채워져 있는지, ID가 있는지 등의 불필요한 방어 코드 도배를 막고 잘못된 비즈니스 요청을 컴파일 타임에 즉시 잡아낸다.
5. 의사결정과 실제 실행의 명확한 분리 (Separation of Decision and Execution)
- 개념: “판단하는 비즈니스 규칙(Pure Function)“과 “외부에 부수 효과를 일으키는 실행 로직(Side Effect - DB 쓰기, 결제, 이메일 전송 등)“을 분리한다.
- 장점: 비즈니스 판단 규칙만 따로 떼어내서 테스트할 때 모킹(Mocking) 지옥에 빠지지 않고 독립적으로 완벽하게 검증(Unit Test)할 수 있다.
6. 다음 동료에게 이정표가 되는 명확한 에러 설계
- 개념: 내부 인프라나 보안 취약점을 노출하지 않는 안전한 한편, 백엔드/프론트엔드 및 온콜(On-call) 담당자가 역추적할 수 있도록 기계적 식별 코드(
code), 구체적 사유, 요청 ID(requestId)를 포함한 구조화된 에러를 제공한다. - 주의: 프론트엔드가 에러 메시지 텍스트를 구문 분석(
error.message.includes("exists"))하게 방치하면 텍스트 수정 시 오작동하는 폭탄이 된다. 에러는 사람이 읽고, 에러 코드는 시스템이 식별하도록 구조화한다.
7. 로컬 동작 시연이 아니라 Git Diff를 배려하여 코딩
- 개념: 로컬에서 겨우 동작만 완료하고 수백 줄의 리팩토링, 로직 수정, 마이그레이션이 한데 뒤엉킨 PR을 제출하는 대신, 코드 리뷰어가 안전하게 검토하고 필요시 부분 롤백할 수 있도록 Git 변경 이력(Diff)을 배려해 작업을 조각낸다.
- 작업 조각 예시:
- PR 1: 기능 수정 없이 결제 필드 이름 변경
- PR 2: 환불 자격 확인 헬퍼 함수 및 테스트 추가
- PR 3: 환불 자격 여부를 결제 흐름에 연동
[Aviation Rule] 10,000피트 규칙 (Sterile Cockpit Rule)
- 비행 규칙의 기원: 1981년 미국 민간항공국(FAA)은 지상 활주(taxi), 이륙, 착륙을 포함하여 10,000피트 이하의 고도에서 비행하는 중요한 단계 동안 운항승무원이 안전 운항에 필요한 필수 업무 외에 사담, 서류 작업 등 주의를 분산시키는 일체의 활동을 금지하는 규칙(규칙 121.542, ‘운항승무원의 의무’)을 제정하였다. 이는 1974년 착륙 도중 조종석의 주의가 분산되어 72명의 목숨을 앗아간 휴먼 에러 사고의 뼈아픈 교훈에서 탄생한 통제다.
- 주의력 관리와 0-1 모드: 10,000피트 이하의 고도에서는 타협이나 회색지대가 없는 선명한 이진법적 기준(0 또는 1)인 0-1 모드를 가동한다. 이는 정신적 자원을 핵심 문제 해결에 100% 쏟아붓도록 가이드라인을 세우고, 타협(예: “이번 한 번만”)에 의한 의지력 낭비와 의사결정 피로를 완전히 배제하는 효과가 있다. 시니어들의 코딩 패턴 또한 기발함보다 안전한 작동 경계를 설정하여 시스템 오용이나 주의 분산의 위험(Surprise)을 제어하는 본질을 공유한다.
예시
// [Separation of Decision & Execution 적용 예시]
// 의사결정: Pure Function (Mocking 필요 없음)
function getRefundEligibility(invoice: Invoice): RefundEligibility {
if (invoice.status !== "paid") {
return { allowed: false, reason: "Invoice is not paid" };
}
if (invoice.refundedAt) {
return { allowed: false, reason: "Invoice is already refunded" };
}
return { allowed: true };
}
// 실제 실행: Impure Function (부수 효과 유발)
async function refundInvoice(invoiceId: string) {
const invoice = await getInvoice(invoiceId);
const eligibility = getRefundEligibility(invoice);
if (!eligibility.allowed) {
throw new ValidationError(eligibility.reason);
}
await paymentProvider.refund(invoice.paymentId);
await markInvoiceRefunded(invoice.id);
}충돌
- 엄격한 경계 vs 단순 구조의 생산성: 소규모 프로토타입 앱에서 모든 외부 엔터티에 매핑 레이어(어댑터)를 추가하는 것은 오버엔지니어링일 수 있다. 그러나 데이터가 여러 레이어와 기능에 걸쳐 쓰이기 시작한다면 완충지대가 반드시 필요하다.