본문으로 건너뛰기

클린 코드: 읽기 좋은 코드를 작성하는 실용적인 원칙들

Development12분 읽기본문 5,797

핵심 요약

이 글에서 확인할 내용

"좋은 코드는 그 자체로 최고의 문서다." 라고 많은 개발자들이 말합니다. 그러나 좋은 코드란 무엇일까요? 이 글에서는 실무에서 바로 적용할 수 있는 클린 코드 원칙들을 구체적인 예시와 함께 살펴봅니다. 1. 의미 있는 이름 짓기 1.1 의도를 드러내는 이름 변수, 함수, 클래스의 이름은 그것이 무엇을 하는지, 왜 존재하는지 명확히 드러내야 합니다.

  • 1. 의미 있는 이름 짓기
  • 2. 함수 설계 원칙
  • 3. 주석에 대한 생각

클린 코드: 읽기 좋은 코드를 작성하는 실용적인 원칙들 흐름을 한눈에 정리한 그림

"좋은 코드는 그 자체로 최고의 문서다." 라고 많은 개발자들이 말합니다. 그러나 좋은 코드란 무엇일까요? 이 글에서는 실무에서 바로 적용할 수 있는 클린 코드 원칙들을 구체적인 예시와 함께 살펴봅니다.

1. 의미 있는 이름 짓기

1.1 의도를 드러내는 이름

변수, 함수, 클래스의 이름은 그것이 무엇을 하는지, 왜 존재하는지 명확히 드러내야 합니다. d라는 변수보다 elapsedTimeInDays가 훨씬 이해하기 쉽습니다. 이름을 보고 주석을 읽지 않아도 의미를 파악할 수 있어야 합니다.

1.2 검색 가능한 이름

숫자 7이나 문자열 "admin"을 코드 곳곳에 사용하면 나중에 변경해야 할 때 찾기 어렵습니다. DAYS_IN_WEEK이나 ADMIN_ROLE처럼 상수로 정의하면 IDE 검색으로 쉽게 찾을 수 있고, 한 곳만 수정하면 됩니다.

1.3 일관된 명명 규칙

팀 전체가 같은 명명 규칙을 따라야 합니다. fetch, get, retrieve를 혼용하면 혼란스럽습니다. 동일한 개념에는 동일한 단어를 사용하세요. 또한 도메인 용어와 기술 용어를 구분해야 합니다. 비즈니스 용어는 도메인 전문가와 소통할 때 같은 언어를 사용하기 위해 그대로 쓰는 것이 좋습니다.

2. 함수 설계 원칙

2.1 작게 만들기

함수는 한 가지 일만 해야 합니다. "한 가지"의 기준은 추상화 수준입니다. 함수 내의 모든 문장이 같은 추상화 수준이어야 합니다. 저수준의 구현 세부사항과 고수준의 비즈니스 로직이 섞여 있으면 안 됩니다.

함수 길이는 20줄을 넘기지 않는 것이 좋습니다. 길어지면 별도의 함수로 추출할 기회를 찾으세요. 추출된 함수의 이름이 의도를 드러내면 코드가 마치 글처럼 읽히게 됩니다.

2.2 인자 개수 줄이기

함수의 인자는 적을수록 좋습니다. 이상적으로는 0개, 최대 3개를 넘지 않도록 합니다. 인자가 많으면 객체로 묶어서 전달하세요. 연관된 데이터를 하나의 개념으로 묶으면 함수 시그니처도 간결해지고, 인자 순서를 기억할 필요도 없어집니다.

2.3 부수 효과를 피하라

함수가 예상치 못한 일을 하면 안 됩니다. checkPassword라는 함수가 비밀번호 검증뿐 아니라 세션을 초기화한다면 호출하는 쪽에서 예상하지 못한 버그가 발생합니다. 함수 이름이 하는 일을 정확히 설명해야 합니다.

명령(Command)과 조회(Query)를 분리하세요. 상태를 변경하는 함수는 값을 반환하지 않고, 값을 반환하는 함수는 상태를 변경하지 않습니다. 이를 CQS(Command-Query Separation) 원칙이라고 합니다.

3. 주석에 대한 생각

3.1 코드로 의도를 표현하라

주석은 필요악입니다. 주석이 필요하다면 코드가 의도를 제대로 표현하지 못하는 것은 아닌지 먼저 고민하세요. 주석 대신 함수나 변수를 추출하여 이름으로 의도를 드러낼 수 있는 경우가 많습니다.

3.2 좋은 주석

법적 고지, 정보 제공(복잡한 정규표현식 설명 등), TODO, 경고, 방어적 주석(왜 이런 이상한 코드가 필요한지 설명) 등은 가치 있는 주석입니다. 단, 주석도 유지보수 대상입니다. 코드가 변경될 때 주석도 함께 업데이트해야 합니다.

3.3 나쁜 주석

코드를 그대로 반복하는 주석, 오래되어 틀린 정보를 담은 주석, 이력을 기록하는 주석(버전 관리 시스템을 사용하세요), 주석 처리된 코드(삭제하세요) 등은 피해야 합니다.

4. 형식 맞추기

4.1 세로 형식

관련 있는 코드는 가까이, 관련 없는 코드는 멀리 배치합니다. 변수는 사용하는 위치에 최대한 가깝게 선언합니다. 인스턴스 변수는 클래스 시작 부분에 모아둡니다.

4.2 가로 형식

줄 길이는 80-120자를 넘지 않는 것이 좋습니다. 너무 긴 줄은 읽기 어렵습니다. 들여쓰기로 계층 구조를 명확히 하고, 연관된 부분은 공백 없이, 다른 개념은 공백으로 분리합니다.

4.3 팀 규칙

팀원 모두가 동의한 코딩 컨벤션을 따르세요. ESLint, Prettier 같은 도구를 설정하여 자동으로 형식을 맞추면 코드 리뷰에서 스타일 논쟁에 시간을 낭비하지 않습니다.

5. Early Return (Guard Clauses)

중첩된 if문(Arrow Code)은 가독성의 적입니다. 예외 상황을 먼저 처리하고 반환(Return)해버리는 'Early Return' 패턴을 사용하세요.

Before:

function processUser(user) {
  if (user != null) {
    if (user.isActive) {
      if (user.hasPermission) {
        saveData();
      }
    }
  }
}

After:

function processUser(user) {
  if (!user) return;
  if (!user.isActive) return;
  if (!user.hasPermission) return;

  saveData(); // 핵심 로직이 들여쓰기 없이 맨 아래에 위치
}

핵심은 "정상적인 흐름"을 들여쓰기 없이 최상위 레벨에 유지하는 것입니다.

본문 중간 핵심 흐름을 시각화한 Development 일러스트

6. 오류 처리

6.1 예외 사용하기

오류 코드를 반환하는 대신 예외를 던지세요. 호출하는 코드가 더 깔끔해집니다. 오류 처리 코드와 정상 로직이 분리되어 가독성이 높아집니다.

6.2 예외에 의미 있는 정보 담기

예외를 잡았을 때 문제를 진단할 수 있는 충분한 정보를 포함하세요. 무엇을 시도했는지, 왜 실패했는지, 어떤 값들이 관련되었는지 등이 담겨야 합니다.

6.3 null을 반환하지 마라

null을 반환하면 호출하는 모든 곳에서 null 체크를 해야 합니다. 대신 빈 리스트, Optional, 기본 객체(Null Object Pattern) 등을 사용하세요. null 체크 코드가 사라지면 핵심 로직이 더 잘 보입니다.

7. 경계

외부 라이브러리나 API를 사용할 때는 경계를 명확히 하세요. 래퍼(Wrapper) 클래스나 어댑터(Adapter) 패턴으로 외부 코드를 캡슐화하면, 외부 변경의 영향을 최소화할 수 있고, 테스트도 쉬워집니다.

8. 특성화 테스트와 점진적 리팩터링: 레거시 코드를 안전하게 개선하는 전략

클린 코드를 논할 때 신규 코드 작성에 집중하는 글은 많지만, 현실의 개발자가 마주하는 코드 대부분은 레거시입니다. 테스트도 없고, 의도도 불명확하며, 누가 왜 이렇게 작성했는지 알 수 없는 코드를 앞에 두고 "리팩터링하라"는 말은 공허하게 들립니다. 건드리면 무엇이 깨질지 모르는 코드를 안전하게 개선하는 체계적인 방법이 필요합니다.

마이클 페더스(Michael Feathers)는 저서 Working Effectively with Legacy Code에서 "테스트 없는 코드는 레거시 코드다"라고 정의했습니다. 이 기준에서 출발하면 리팩터링의 첫 번째 과제는 코드를 깨끗하게 만드는 것이 아니라, 현재 동작을 안전하게 고정하는 것입니다.

특성화 테스트(Characterization Test): 현재 동작의 고정

특성화 테스트는 코드가 "올바르게" 동작하는지 검증하는 것이 아닙니다. 코드가 지금 실제로 어떻게 동작하는지를 그대로 기록하는 테스트입니다. 버그가 있더라도 현재 동작이 그대로 고정됩니다. 이렇게 하는 이유는 하나입니다. 리팩터링 도중 의도치 않은 동작 변경이 발생하면 즉시 알아챌 수 있도록 하기 위해서입니다.

작성 순서는 간단합니다. 첫째, 대상 함수나 모듈을 호출하고 실제 반환값을 기록합니다. 둘째, 그 반환값을 그대로 기댓값으로 삼는 테스트를 작성합니다. 셋째, 테스트가 통과하는지 확인합니다. 이 세 단계가 전부입니다. 내용을 이해하거나 버그를 고치려 하지 않습니다. 특성화 테스트는 안전망이지 교정 도구가 아닙니다.

예를 들어 복잡한 할인율 계산 함수가 있다고 가정합니다. 입력값 집합에 대해 실제로 반환되는 숫자를 그대로 기댓값으로 고정합니다. 나중에 리팩터링 후 동일 입력에 다른 숫자가 나오면 테스트가 실패하고 변경 사실을 즉시 알 수 있습니다. 설령 원래 숫자가 버그였더라도, 그 버그를 의도적으로 고칠 것인지 아닌지는 별도의 의사결정이 됩니다.

점진적 개선의 4단계 사이클

특성화 테스트로 안전망이 깔리면 다음 사이클로 리팩터링을 진행합니다.

1단계 - 봉합부(seam) 찾기: 코드에서 테스트 가능한 연결 지점을 찾습니다. 직접 의존하는 외부 호출(DB, 파일 I/O, 외부 API)이 있는 함수는 그 의존성을 인터페이스로 추출하여 테스트용 페이크(fake)로 교체할 수 있는 구조로 먼저 바꿉니다. 이 단계에서 로직을 변경하지 않습니다. 오직 의존성 주입이 가능한 구조로만 바꿉니다.

2단계 - 커버리지 확장: 봉합부가 생기면 다양한 입력 시나리오에 대한 특성화 테스트를 추가로 작성합니다. 행복한 경로(happy path)뿐 아니라 빈 값, 경계값, 이미 알고 있는 오류 케이스도 포함합니다. 커버리지가 충분히 확보되면 리팩터링을 시작할 신호입니다.

3단계 - 최소 단위 추출: 한 번에 전체를 바꾸려 하지 않습니다. 가장 이해하기 쉬운 조각 하나를 추출하여 이름을 부여하고, 테스트를 실행합니다. 통과하면 커밋합니다. 이 원칙을 지키면 리팩터링 도중 컨텍스트 전환이 일어나거나 중단되어도 코드베이스가 더 나빠지는 일이 없습니다.

4단계 - 의도 드러내기: 추출된 작은 함수들의 이름을 비즈니스 도메인 언어로 다시 명명합니다. 이 단계에서 처음으로 "읽기 좋은 코드"가 만들어집니다. 4단계는 1~3단계가 충분히 진행된 후에야 의미가 있습니다. 안전망 없이 이름만 바꾸는 것은 클린 코드가 아니라 착각입니다.

이 전략은 마이클 페더스의 Working Effectively with Legacy Code와 켄트 벡의 리팩터링 사이클을 결합한 접근입니다. 핵심은 "테스트가 먼저, 개선이 나중" 이라는 순서를 절대 바꾸지 않는 것입니다. 이 순서를 지키면 레거시 코드도 조금씩 클린해질 수 있고, 지키지 않으면 리팩터링이 새로운 버그의 온상이 됩니다.

9. 클린 아키텍처와 객체 지향의 본질

클린 코드는 변수 이름 길이를 제한하거나 함수 단위 길이를 줄이는 것에서 끝나지 않습니다. 클린 아키텍처(Clean Architecture)의 본질은 "무엇이 무엇을 아는가", 즉 코드의 의존성 방향(Dependency Direction)을 통제하는 데 있습니다.

9.1 의존성 역전 원칙(Dependency Inversion)과 포트(Port)

프론트엔드나 백엔드를 막론하고 가장 다루기 어려운 코드는, 순수한 비즈니스 로직(도메인 모델)이 네트워크 통신 모듈(Axios)이나 UI 렌더링 라이브러리(React)에 직접 의존하는 형태입니다.
비즈니스 로직은 어떤 외부 도구가 연결되든 독자적으로 테스트 가능해야 합니다. 이를 위해 인터페이스(Port)를 정의하고, 외부 라이브러리 계층이 그 인터페이스를 구현(Adapter)하도록 의존성 방향을 역전시키는 것이 SOLID의 DIP(의존성 역전 원칙)입니다.

9.2 순수 함수와 부수 효과(Side Effect)의 격리

클린 코드를 달성하는 가장 실천하기 쉬운 방법은 시스템 전체에서 순수 함수(Pure Function)가 차지하는 비중을 늘리는 것입니다. 상태를 변경하거나 외부 인프라(Network, DB, File I/O)를 건드리는 코드를 핵심 로직 밖으로 밀어내면, 남는 코어는 입력값을 받아 검증하고 결과값을 반환하는 순수 로직만 남습니다. 이 상태는 테스트하기 쉬울 뿐 아니라, 나중에 코드를 수정할 때의 심리적 부담도 크게 줄여줍니다. 예쁜 코드 그 자체보다, 변경에 대한 내성(Resilience)을 확보하는 것이 클린 아키텍처가 궁극적으로 추구하는 가치입니다.

운영 관점 정리

클린 코드는 하루아침에 작성되지 않습니다. 꾸준한 연습과 리팩토링이 필요합니다. "보이스카우트 규칙"을 기억하세요. 코드를 체크인할 때 체크아웃할 때보다 더 깨끗하게 만드세요. 작은 개선이 쌓여 큰 변화를 만듭니다.

이 규칙을 실천하는 가장 현실적인 방법은 코드 리뷰에서 대표적인 코드 냄새(Code Smell)를 놓치지 않는 것입니다. 20줄을 넘는 긴 메서드, 3개를 넘는 파라미터, 의미 없는 매직 넘버, 그리고 두 곳 이상에 중복된 코드를 발견하면 그 자리에서 바로 추출하고 정리하세요. 동료와 미래의 자신을 위해 읽기 좋은 코드를 작성하는 것, 그것이 클린 코드의 전부입니다.


이 주제와 연결되는 글