Waylog Blog

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

Programming

Clean Code

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

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(); // 핵심 로직이 들여쓰기 없이 맨 아래에 위치
}

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

6. 오류 처리

5.1 예외 사용하기

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

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

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

5.3 null을 반환하지 마라

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

7. 경계

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

결론

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