브라이언 커니한의 ‘좋은 취향’ 조언은 읽기 쉬운 코드가 시간을 절약하고 버그를 줄이며 팀이 영리한 요령보다 더 빠르게 움직이게 하는 이유를 보여준다.

브라이언 커니한의 이름은 많은 개발자가 무심코 사용하는 곳곳에 등장합니다: 고전적인 유닉스 도구들, C 생태계, 그리고 사람들에게 프로그램을 명확하게 설명하는 법을 가르친 수십 년의 글과 강연들. The C Programming Language(데니스 리치와 공저), The Unix Programming Environment, 혹은 그의 에세이와 강연을 떠올리든 공통된 맥락은 간단한 아이디어를 깔끔하게 표현하려는 집요함입니다.
커니한의 최고의 조언은 C 문법이나 유닉스 관습에 의존하지 않습니다. 그것은 인간이 읽는 방식에 관한 것입니다: 우리는 구조를 훑고, 이름에 의존하며, 의도를 추론하고, 코드가 요령으로 의미를 숨기면 혼란스러워합니다. 그 때문에 가독성에 대한 “취향”은 TypeScript, Python, Go, Java, Rust 등 어떤 언어를 쓰든 여전히 중요합니다.
언어는 바뀌고 도구는 좋아집니다. 팀은 여전히 시간 압박 아래 기능을 배포하고, 대부분의 코드는 원래 작성자가 아닌 다른 사람이 유지보수합니다(종종 미래의 당신). 가독성은 그 모든 것을 버티게 해주는 승수입니다.
이 글은 ‘영웅적 코딩’을 찬양하거나 구식 규칙을 외우라는 주장이 아닙니다. 일상 코드를 더 다루기 쉽게 만드는 습관에 대한 실용적인 안내입니다:
커니한의 영향력은 단순하고 팀 친화적인 목표를 가리키기 때문에 중요합니다: 코드는 소통해야 합니다. 코드가 명확한 설명처럼 읽히면 해독하는 데 쓸 시간이 줄어들고 개선하는 데 더 많은 시간을 쓸 수 있습니다.
가독성에서의 “좋은 취향”은 개인적 스타일이나 멋진 패턴, 혹은 해결책을 최소 라인에 압축하는 것이 아닙니다. 그것은 의도를 신뢰성 있게 전달하는 가장 간단하고 명확한 선택을 하는 습관입니다.
좋은 취향의 솔루션은 다음 독자에게 기본적인 질문에 답합니다: 이 코드는 무엇을 하려는가, 그리고 왜 이렇게 했는가? 그 대답을 위해 정신적 체조나 숨은 가정, 요령을 풀어야 한다면 그 코드는 팀에게 시간을 빼앗고 있는 것입니다.
대부분의 코드는 작성될 때보다 훨씬 자주 읽힙니다. “좋은 취향”은 읽기를 주된 활동으로 간주합니다:
그래서 가독성은 단지 미적인 문제가 아닙니다(들여쓰기, 줄 너비, snake_case 선호 여부 등). 그것들은 도움이 되지만 “좋은 취향”은 주로 추론을 쉽게 만드는 것—명확한 이름, 분명한 제어 흐름, 예측 가능한 구조—에 관한 것입니다.
흔한 실수는 간결함을 위해 최적화하는 것입니다. 때로는 가장 명확한 코드는 단계를 명시하기 때문에 약간 더 길어집니다.
예를 들어, 다음 두 접근을 비교해보세요:
validate → normalize → compute → return처럼 설명하는 몇 개의 명명된 중간 변수를 둔 방식.두 번째 버전은 줄이 늘어날 수 있지만, 정확성을 검증하는 데 드는 인지 부하를 줄입니다. 또한 버그를 격리하기 쉽고 변경을 안전하게 적용할 수 있게 합니다.
좋은 취향은 요령으로 계속 ‘개선’하는 것을 멈출 줄 아는 능력입니다. 동료가 당신의 설명 없이도 코드를 이해할 수 있다면 잘 선택한 것입니다.
영리한 코드는 순간적으로는 승리처럼 느껴집니다: 줄 수가 줄고, 깔끔한 요령이 있고, diff에서 ‘와’ 하는 요소가 있죠. 그러나 실제 팀에서는 그 영리함이 반복 청구서로 바뀝니다—온보딩 시간, 리뷰 시간, 누군가 코드를 다시 만질 때마다 망설임으로 지불되는 비용입니다.
온보딩이 지연된다. 새 팀원은 제품을 배우는 것 외에 당신만의 단축어를 배워야 합니다. 함수 이해가 영리한 연산자나 암묵적 관습을 해독해야 한다면, 사람들은 그 코드를 변경하지 않거나 두려움으로 변경합니다.
리뷰가 길어지고 신뢰성이 떨어진다. 리뷰어는 트릭이 올바른지 증명하느라 에너지를 쏟고, 행동이 의도와 일치하는지 평가하기보다 그 트릭의 정당성을 검증하느라 시간을 씁니다. 더 나쁜 것은, 영리한 코드는 정신적으로 시뮬레이션하기 어렵기 때문에 리뷰어가 단순 버전에서 잡아낼 수 있었을 엣지 케이스를 놓친다는 점입니다.
영리함은 다음 상황에서 누적됩니다:
자주 문제를 일으키는 예:
17, 0.618, -1)—기억할 수 없는 규칙을 암호화합니다.\u0026\u0026 / || 트릭 등)—독자가 미묘한 평가 규칙을 알고 있어야 합니다.커니한의 “취향”에 대한 관점은 여기서 드러납니다: 가독성은 더 많이 쓰는 것이 아니라 의도를 명확히 하는 것입니다. “스마트한” 버전이 오늘 20초를 절약해주지만 향후 읽는 사람에게 매번 20분을 소비하게 한다면, 그것은 현명한 것이 아니라 비싼 선택입니다.
커니한의 “취향”은 작은 반복 가능한 결정들에서 자주 드러납니다. 파일을 훑거나 동작을 검색하거나 시간 압박 속에서 버그를 고칠 때, 작은 가독성 개선이 쌓이면 큰 차이를 만듭니다.
좋은 이름은 주석의 필요를 줄이고 실수를 숨기기 어렵게 만듭니다.
의도 드러내는 이름을 목표로 하세요:
sum보다 invoiceTotalCents를 선호하세요.이름을 해독해야 한다면 그것은 본래 역할을 하지 못하는 것입니다.
대부분의 읽기는 훑기입니다. 일관된 공백과 구조는 눈이 중요한 것을 찾도록 돕습니다: 함수 경계, 조건문, 그리고 ‘해피 패스’.
실용적인 습관 몇 가지:
논리가 복잡해질 때는 결정을 명시적으로 만드는 것이 가독성을 향상시키는 경우가 많습니다.
다음 두 스타일을 비교해보세요:
// Harder to scan
if (user \u0026\u0026 user.active \u0026\u0026 !user.isBanned \u0026\u0026 (role === 'admin' || role === 'owner')) {
allow();
}
// Clearer
if (!user) return deny('missing user');
if (!user.active) return deny('inactive');
if (user.isBanned) return deny('banned');
if (role !== 'admin' \u0026\u0026 role !== 'owner') return deny('insufficient role');
allow();
두 번째 버전은 길지만 체크리스트처럼 읽히며 확장해도 깨지기 쉽지 않습니다.
이 작은 선택들이 유지보수 가능한 코드의 일상적 공예입니다: 정직한 이름, 독자를 안내하는 포맷, 정신적 체조를 요구하지 않는 제어 흐름.
커니한의 명료함 스타일은 작업을 어떻게 함수와 모듈로 나누느냐에서 가장 잘 드러납니다. 독자는 구조를 훑어보며 각 부분이 무엇을 하는지 대략 맞춰볼 수 있어야 합니다.
하나의 줌 레벨에서 정확히 한 가지 일을 하는 함수를 목표로 하세요. 검증, 비즈니스 규칙, 포맷팅, I/O를 섞어 쓰면 독자는 여러 실마리를 동시에 유지해야 합니다.
간단한 테스트: 함수 내부에 "// 이제 X를 한다" 같은 주석을 쓰고 있다면, X는 종종 별도의 명명된 함수로 추출할 좋은 후보입니다.
긴 파라미터 리스트는 숨은 복잡성의 세금입니다: 각 호출 지점이 미니 설정 파일처럼 됩니다.
여러 파라미터가 항상 함께 움직이면 적절히 그룹화하세요. 옵션 객체나 작은 데이터 구조는 호출 지점을 자기 설명적으로 만들 수 있습니다—단, 그룹이 일관성을 유지하고 ‘잡동사니’ 가방이 되지 않도록 하세요.
또한 도메인 개념을 원시값보다 전달하는 것을 선호하세요. UserId는 string보다, DateRange는 (start, end)보다 규칙을 담기 쉽습니다.
모듈은 약속입니다: “이 개념에 필요한 것은 여기 있고, 나머지는 다른 곳에 있다.” 모듈을 작게 유지해 목적을 머릿속에 담을 수 있게 하고 부작용을 최소화하는 경계를 설계하세요.
도움이 되는 실용적 습관들:
공유 상태가 필요하다면 정직하게 이름 짓고 불변식을 문서화하세요. 가독성은 복잡성을 회피하는 것이 아니라 독자가 기대하는 곳에 복잡성을 두는 것입니다. 이러한 경계를 유지하면서 변경하는 방법에 대해서는 /blog/refactoring-as-a-habit를 참조하세요.
커니한의 “취향”은 주석을 다는 방식에도 드러납니다: 목표는 모든 줄을 주석 처리하는 것이 아니라 미래의 혼란을 줄이는 것입니다. 가장 좋은 주석은 코드가 맞지만 놀라운 부분에서 잘못된 가정을 막아주는 주석입니다.
코드를 그대로 다시 말하는 주석(“i를 증가”)은 잡음을 추가하고 주석을 무시하게 만듭니다. 유용한 주석은 의도, 트레이드오프, 혹은 구문만으로는 드러나지 않는 제약을 설명합니다.
# Bad: says what the code already says
retry_count += 1
# Good: explains why the retry is bounded
retry_count += 1 # Avoids throttling bans on repeated failures
‘무엇’을 주석으로 쓰고 싶은 유혹을 느낀다면, 그 신호는 보통 코드가 더 명확해져야 한다는 뜻입니다(더 나은 이름, 작은 함수, 더 단순한 제어 흐름). 사실은 코드는 사실을 담고, 주석은 이유를 담게 하세요.
오래된 주석보다 신뢰를 더 빨리 깎아 먹는 것은 없습니다. 선택적 주석은 시간이 지나며 흐트러지고, 잘못된 주석은 능동적인 버그 원인이 됩니다.
실용적 습관: 주석 업데이트를 변경의 일부로 취급하세요. 리뷰에서 다음을 묻는 것이 합당합니다: 이 주석이 여전히 동작과 일치하는가? 아니라면 업데이트하거나 제거하세요. “주석 없음”이 “틀린 주석”보다 낫습니다.
인라인 주석은 지역적 놀라움에 대해 씁니다. 더 넓은 안내는 도큐스트링, README, 개발자 노트에 두세요—특히 다음과 같은 경우:
좋은 도큐스트링은 사용법과 기대 가능한 오류를 알려주되 구현을 서술하지 않습니다. 짧은 /docs나 /README 노트는 ‘왜 이렇게 했는가’의 이야기를 담아 리팩터링을 견디게 합니다.
작은 승리: 주석 수는 줄지만 각 주석은 존재할 이유가 있어야 합니다.
대부분의 코드는 해피 패스에서 ‘괜찮아’ 보입니다. 취향의 진짜 시험은 입력이 빠졌거나 서비스가 타임아웃되고 사용자가 예상치 못한 행동을 할 때 무슨 일이 일어나는가입니다. 스트레스 상황에서 영리한 코드는 진실을 숨기기 쉬우며, 명확한 코드는 실패를 드러내고 복구 가능하게 합니다.
오류 메시지는 제품과 디버깅 워크플로의 일부입니다. 다음을 생각하며 작성하세요:
로깅이 있다면 구조화된 컨텍스트(예: requestId, userId, invoiceId)를 추가해 메시지가 관련 없는 데이터를 뒤질 필요 없이 액션 가능하도록 만드세요.
모든 것을 ‘하나의 영리한 표현’으로 처리하려는 유혹이 있습니다. 좋은 취향은 중요하게 여겨지는 몇 가지 엣지 케이스를 선택해 보이게 만드는 것입니다.
예를 들어, ‘빈 입력’이나 ‘찾을 수 없음’에 대해 명시적 분기를 두는 것은 중간에 어딘가에서 암묵적으로 null이 발생하는 연쇄보다 읽기 쉬운 경우가 많습니다. 중요한 특수 케이스라면 이름 붙여 앞에서 처리하세요.
가끔 객체, 문자열, false를 섞어 반환하면 독자는 정신적 의사결정 트리를 유지해야 합니다. 일관된 패턴을 선호하세요:
명확한 실패 처리로 놀라움이 줄고, 놀라움이 버그와 야간 호출의 온상입니다.
가독성은 당신이 의도한 것만이 아닙니다. 다음 사람이 오후 4시 55분에 파일을 열 때 기대하는 것이기도 합니다. 일관성은 ‘코드 읽기’를 패턴 인식으로 바꿉니다—놀라움이 줄고 오해가 적어지며 매 스프린트마다 반복되는 논쟁도 줄어듭니다.
좋은 팀 스타일 가이드는 짧고 구체적이며 실용적입니다. 모든 선호를 인코딩하려 들지 말고 반복되는 질문을 정리하세요: 네이밍 규칙, 파일 구조, 에러 처리 패턴, 테스트의 ‘완료’ 기준 등.
진짜 가치는 사회적 합의에 있습니다: 문서로 정해지면 리뷰는 ‘내가 X를 선호한다’가 아니라 ‘우리는 X로 합의했다(이유는 여기에)’로 바뀝니다. 가이드는 살아 있는 문서로 유지하고 쉽게 찾을 수 있게 하세요—많은 팀이 리포지토리에 고정합니다(예: /docs/style-guide.md).
측정 가능하고 지루한 것은 포매터와 린터를 사용하세요:
이렇게 하면 사람은 의미(네이밍, API 형태, 엣지 케이스, 의도와의 일치)에 집중할 수 있습니다. 수동 규칙은 여전히 설계 선택을 설명할 때 중요합니다—예: “중첩을 줄이기 위해 early return을 선호” 같은 것들은 도구가 판단하기 어렵습니다.
때로는 복잡성이 정당화됩니다: 성능 제약, 임베디드 제약, 까다로운 동시성 또는 플랫폼 특정 동작. 합의는 이렇습니다: 예외는 허용되지만 명시적이어야 합니다.
간단한 기준: 트레이드오프를 짧은 주석으로 문서화하고, 성능이 이유라면 마이크로벤치마크나 측정치를 첨부하며, 복잡한 코드를 명확한 인터페이스 뒤로 격리해 코드베이스의 대다수는 읽기 쉽게 유지하세요.
좋은 코드 리뷰는 검사라기보다 ‘좋은 취향’에 대한 짧고 집중된 수업처럼 느껴져야 합니다. 커니한의 요지는 영리한 코드가 악이라는 게 아니라, 다른 사람이 함께 살아가야 할 때 영리함이 비용이 된다는 점입니다. 리뷰는 그 선택을 가시화하고 의도적으로 가독성을 선택할 기회입니다.
먼저 물어보세요: “동료가 한 번 훑어보고 이해할 수 있는가?” 보통 네이밍, 구조, 테스트, 행동을 보고 미세 최적화로 들어가야 합니다.
코드가 올바르지만 읽기 어렵다면 가독성을 실제 결함으로 취급하세요. 변수 이름 바꾸기, 긴 함수 분리, 제어 흐름 단순화, 예상 동작을 보여주는 작은 테스트 추가를 제안하세요. “작동하지만 왜인지 모르겠다”는 리뷰는 향후 몇 주의 혼란을 막습니다.
실용적인 검토 순서:
피드백이 점수 매기기로 흐르면 리뷰는 망가집니다. “왜 이렇게 했나?” 보다는 다음과 같이 말해보세요:
질문은 협업을 유도하고 보이지 않던 제약을 드러냅니다. 제안은 방향을 제시하되 무능력을 암시하지 않습니다. 이런 톤이 팀에 ‘취향’을 전파합니다.
일관된 가독성을 원한다면 리뷰어 기분에 의존하지 마세요. 리뷰 템플릿과 정의된 완료 조건에 몇 가지 ‘가독성 체크’를 추가하세요. 짧고 구체적으로 유지하세요:
시간이 지나면 리뷰는 스타일을 단속하는 것이 아니라 판단을 가르치는 과정이 됩니다—커니한이 주장한 바로 그 일상적 규율입니다.
LLM 도구는 동작하는 코드를 빠르게 생성할 수 있지만, 커니한이 말한 기준은 ‘동작한다’가 아니라 ‘의사소통한다’입니다. 팀이 챗 기반으로 생성된 코드를 반복하는 흐름을 쓴다면 가독성을 1등 시민 수용 기준으로 다루세요.
플랫폼 예시로 Koder.ai처럼 챗 프롬프트로 React 프론트엔드, Go 백엔드, Flutter 모바일 앱을 생성하고 소스 코드를 내보낼 수 있는 곳에서는 다음을 요청하세요:
속도는 사람이 검토하고 유지보수하고 확장하기 쉬울 때 가장 가치가 있습니다.
가독성은 한 번 ‘달성’하는 것이 아닙니다. 요구사항이 변하면 코드를 꾸준히 평범한 말로 되돌리는 노력이 필요합니다. 커니한의 감수성은 여기서 잘 맞습니다: 영웅적 재작성이나 오늘을 빛낼 ‘스마트’ 원라이너보다 꾸준하고 이해하기 쉬운 개선을 선호하세요.
가장 안전한 리팩터링은 지루합니다: 동작을 바꾸지 않는 작은 변화들. 각 단계마다 테스트를 실행하세요. 테스트가 없다면 그 영역을 잠시 보호할 몇 가지 포커스된 검사(임시 가드레일)를 추가하세요—구조를 개선하면서 두려움 없이 작업할 수 있게 하는 것입니다.
실용적 리듬:
작은 커밋은 리뷰를 쉽게 만듭니다: 동료가 의도를 판단하기 쉽고 부작용을 찾기 수월합니다.
모든 ‘영리한’ 구조를 한 번에 제거할 필요는 없습니다. 기능이나 버그 수정을 위해 코드를 만질 때마다 영리한 요령을 이해하기 쉬운 등가물로 교체하세요:
실제 팀에서 가독성이 이기는 방식은 이렇게 사람들이 이미 작업하고 있는 지점에서 하나씩 개선하는 것입니다.
모든 정리는 긴급하지 않습니다. 유용한 규칙: 코드가 활발히 변경되고 있거나 자주 오해되거나 버그를 일으킬 가능성이 높다면 지금 리팩터하세요. 안정적이고 격리된 부분이라면 나중에 일정 잡으세요.
리팩터링 부채를 가시화하세요: 짧은 TODO와 맥락을 남기거나 문제를 설명하는 티켓(예: ‘새 결제 수단 추가가 어렵다; 함수가 5가지 일을 함’)을 추가하세요. 그러면 혼란스러운 코드가 팀의 영구 세금으로 조용히 남는 대신 의도적으로 판단할 수 있습니다.
“좋은 취향”을 일관되게 적용하고 싶다면 실행하기 쉽게 만드세요. 기억하기 짧고 실행하기 구체적인 체크리스트를 제안합니다.
이전: process(data)가 검증, 파싱, 저장, 로깅을 한 곳에서 처리한다.
이후: validateInput, parseOrder, saveOrder, logResult로 분리. 메인 함수는 읽기 쉬운 개요가 된다.
이전: if not valid then return false가 다섯 번 반복된다.
이후: 한 번의 선행 가드(또는 하나의 validation 함수)로 문제 목록을 명확히 반환한다.
이전: x, tmp, flag2, doThing() 같은 이름들.
이후: retryCount, draftInvoice, isEligibleForRefund, sendReminderEmail()처럼 의도를 드러내는 이름.
이전: 세 가지 특수 케이스가 반복문 중간에 숨겨져 있다.
이후: 특수 케이스를 먼저 처리(또는 헬퍼로 추출)하고 단순한 루프를 실행한다.
이번 주 하나만 개선 목표로 정하세요: “새 약어 금지”, “해피 패스 먼저”, “PR마다 헬퍼 하나 추출”, 또는 “모든 에러 메시지에 다음 단계 포함”처럼. 7일간 추적한 뒤 실제로 읽기 쉬워지게 만든 규칙만 계속하세요.
커니한의 영향력은 C 언어나 Unix 자체보다 더 근본적인 원칙에서 옵니다: 코드는 의사소통 수단이라는 점입니다.
언어와 프레임워크는 바뀌지만, 팀은 여전히 코드가 빠르게 훑어보고, 사고하고, 리뷰하고, 디버그할 수 있기를 바란다는 점은 변하지 않습니다—특히 몇 달 뒤이거나 시간 압박 아래일 때는 더더욱 그렇습니다.
“좋은 취향”이란 의도를 전달하는 가장 간단하고 명확한 선택을 꾸준히 고르는 습관을 말합니다.
실용적인 테스트는 이겁니다: 동료가 ‘이 코드는 무엇을 하고, 왜 이렇게 했는가?’를 별도의 해석 없이 답할 수 있는가? 숨은 가정이나 요령이 필요하면 가독성이 떨어진 것입니다.
대부분의 코드는 작성될 때보다 읽힐 때가 훨씬 많습니다.
읽는 사람을 먼저 고려하면 온보딩 시간, 리뷰 마찰, 잘못된 변경의 위험을 줄일 수 있습니다—특히 유지보수자가 문맥이 부족한 ‘미래의 나’일 때 효과적입니다.
“영리함의 세금”은 다음과 같이 나타납니다:
오늘 몇 초를 절약하더라도 수정할 때마다 몇 분을 더 쓰게 만든다면 순이익은 음수입니다.
자주 문제를 일으키는 패턴들:
이런 패턴은 중간 상태를 숨기고 리뷰에서 놓치기 쉬운 엣지 케이스를 만들기 쉽습니다.
짧은 코드가 항상 더 낫지는 않습니다.
명명된 중간 변수로 단계를 명시하는(예: validate → normalize → compute) 방식은 몇 줄을 더 쓰더라도 올바름을 검증하기 쉽고 디버깅과 향후 변경이 더 안전해집니다.
다음 습관을 목표로 하세요:
invoiceTotalCents)이름을 해독해야 한다면 그 이름은 제 역할을 못 하고 있는 것입니다.
단순하고 명시적인 분기를 선호하고 ‘해피 패스’를 가시적으로 유지하세요.
유용한 전술:
‘무엇’을 설명하는 주석은 잡음을 늘립니다. ‘왜’를 설명하세요.
유용한 주석은 의도, 트레이드오프, 비가시적 불변식 등을 담습니다. 명확한 코드가 가능하다면 코드를 먼저 개선하고, 주석은 그럼에도 불가피한 설명을 위해 남기세요. 주석은 변경의 일부로 업데이트해야 합니다—부정확한 주석은 없는 것보다 해롭습니다.
자동화 도구는 형식과 단순 규칙을 처리하게 하고, 사람은 의미에 집중하게 하세요.
경량 스타일 가이드는 반복되는 결정을 정리해 불필요한 논쟁을 줄입니다.
성능이나 제약으로 예외를 둘 때는 그 선택을 문서화하고 복잡한 코드를 명확한 인터페이스 뒤로 숨기세요.