문서 읽는 데 40분 · A1

A-1: 의미 있는 이름·주석·포매팅 — 코드를 읽는 사람을 위한 첫 배려

목차 27
전체 11강 중 1강 · 클린코드
난이도 · 중급

ℹ️읽기 좋고 고치기 쉬운 코드를 쓰는 법 — 리팩토링·설계 원칙. 코드를 충분히 써본 뒤에 빛을 발하는 주제예요.

안녕하세요, 여러분의 개발 안목을 길러 드릴 홍순구 튜터입니다. 오늘부터 우리는 클린 코드라는 새로운 여정을 시작합니다. 지금까지 언어 기초에서 "문법을 읽고 쓰는 손"을 만들었다면, 이 과목은 그 손으로 "오래 살아남는 코드를 쓰는 안목" 을 기릅니다.

동작하는 코드를 짜는 것과, 6개월 뒤 다른 사람이(혹은 내가) 열어 봐도 이해되고 마음 놓고 고칠 수 있는 코드를 짜는 것은 전혀 다른 일이에요. 실무 코드는 한 번 쓰고 버리는 게 아니라 몇 년을 두고 수십 명이 고쳐 가며 삽니다. 회사가 주니어와 미들을 가르는 가장 큰 잣대도 바로 "이 사람 코드를 다른 사람이 이어받을 수 있는가"입니다. 처음엔 지저분한 코드가 더 빨라 보이지만, 기능이 쌓일수록 고치는 비용이 폭발해요. 클린 코드는 그 폭발을 막으려고 미리 조금 더 들이는 투자입니다.

너무 부담 갖지 마세요. 객체지향(클래스·상속·인터페이스)을 한 번 떼었으면 따라올 수 있게 준비했습니다. 거창한 설계 이전에, 오늘은 가장 작은 단위인 변수 하나의 이름, 숫자 하나, 주석 한 줄, 줄 간격부터 손봅니다.

코드 리뷰를 받아 본 분이라면 이런 말 한 번쯤 들어 보셨죠?

  • "변수명만 봐선 이게 뭔지 모르겠어요."
  • "이 주석, 코드랑 따로 노는데요?"
  • "여기 10이 대체 무슨 숫자예요?"

오늘 수업이 끝나면, 여러분은 이 세 마디를 듣지 않는 코드를 쓰게 됩니다. 자, 출발해 볼까요?

텍스트
 A-1 의 여정 — 한 줄 한 줄을 읽기 좋게

 이름         의도를 드러내고 · 발음되고 · 검색되는 이름
 매직 넘버     10, 0.1 같은 숫자에 이름을 달아 준다
 주석         거짓말하는 주석을 걷어내고, 코드로 말하게 한다
 포매팅        읽는 리듬을 만들고, 팀의 약속에 맡긴다

💡 오늘 수업의 핵심 — "코드는 컴퓨터가 아니라 사람이 읽는다"

같은 동작을 하는 코드라도, 이름과 형식에 따라 읽는 사람의 시간은 10배까지 차이 납니다. 오늘은 그 차이를 만드는 네 가지 — 이름, 매직 넘버, 주석, 포매팅 — 을 지저분한 Before와 깨끗한 After로 나란히 보면서 익힙니다.

🎯 학습 목표

  • 의도를 드러내고, 발음·검색이 되는 이름을 짓고, 매직 넘버를 이름 있는 상수로 바꾼다.
  • 거짓말하는 주석을 걷어내고, 설명은 주석이 아니라 코드(이름)로 옮기되 꼭 필요한 좋은 주석은 남긴다.
  • 코드 포매팅이 읽는 리듬임을 알고, 개인 취향이 아니라 팀의 약속과 자동 포매터에 맡긴다.

Step 1: "이름 하나에 6개월이 걸린다"

코드 리뷰에서 가장 자주 듣는 말이 "변수명만 봐선 뭔지 모르겠어요"입니다. 회원 목록에서 활성 회원만 골라내는 코드를 한번 볼게요.

❌ Before

Java
public List<Member> f(List<Member> l) {
    List<Member> r = new ArrayList<>();
    for (Member e : l) {
        if (e.isActive()) {
            r.add(e);
        }
    }
    return r;
}

코드는 멀쩡히 돌아갑니다. 그런데 f는 무슨 일을 할까요? l, r, e는 각각 뭘까요? 메서드 이름이 아무것도 알려 주지 않아서, 안을 한 줄 한 줄 따라 읽어야만 "아, 활성 회원을 거르는구나"를 겨우 압니다. 게다가 r 같은 한 글자 이름은 검색이 안 돼요. 나중에 이 변수만 찾으려고 r을 검색하면 코드 전체의 수천 군데가 걸립니다.

✅ After

Java
public List<Member> findActiveMembers(List<Member> members) {
    List<Member> activeMembers = new ArrayList<>();
    for (Member member : members) {
        if (member.isActive()) {
            activeMembers.add(member);
        }
    }
    return activeMembers;
}

메서드 이름만 읽어도 "활성 회원을 찾는다"가 바로 들어옵니다. 주석 한 줄 없이 이름이 의도를 설명하죠. 여기서 좋은 이름의 세 규칙을 뽑아 볼게요.

  • 의도를 드러내는 이름: findActiveMembers는 "무엇을 하는지"를 말한다. f는 아무것도 말하지 않는다.
  • 검색 가능한 이름: activeMembers는 검색하면 딱 그 변수만 걸린다. r은 못 찾는다.
  • 의미 있는 구분: list1, list2처럼 숫자만 붙여 구분하지 말 것. 무엇이 다른지가 이름에 담겨야 한다.

이름만 바꿨을 뿐 거르는 결과는 한 군데도 안 변합니다. 동작은 똑같은데 읽는 속도만 빨라지는 거예요.

⚠️ 언제 깨나 — 짧은 이름이 무조건 나쁜 건 아닙니다. 세 줄짜리 반복문의 i, 람다의 짧은 인자처럼 스코프가 아주 좁고 의미가 자명하면 한 글자도 괜찮아요. 핵심은 "길수록 좋다"가 아니라 "스코프에 맞는 이름"입니다. 멀리까지 살아남는 변수일수록 이름이 또렷해야 합니다.

💡 한 줄 정리

이름은 곧 문서다 — 의도를 드러내고, 검색되고, 의미로 구분되는 이름을 지어라.

🙋 학생 질문 — "한 글자 변수는 무조건 쓰면 안 되나요?"

아니에요. 기준은 글자 수가 아니라 스코프(변수가 살아 있는 범위) 입니다. 두세 줄 안에서 끝나는 반복문 인덱스 i, 좌표 x/y, 람다 인자 같은 건 짧아도 의미가 바로 보여서 괜찮습니다. 반대로 메서드 전체나 클래스 필드처럼 멀리까지 쓰이는 변수에 r, tmp 같은 이름을 붙이면, 읽는 사람이 매번 "이게 뭐였더라" 하고 위로 거슬러 올라가야 해요. "짧게 살면 짧은 이름, 길게 살면 긴 이름"으로 기억하면 편합니다.


Step 2: "발음할 수 있나요? — 헝가리안 표기법의 몰락"

옛날 코드에는 변수 이름 앞에 타입을 붙이는 관습이 있었습니다. 문자열이면 str, 정수면 i, 이런 식으로요. 이걸 헝가리안 표기법이라고 부릅니다. 태그 목록에 #을 붙이는 코드로 보겠습니다.

❌ Before

Java
public List<String> buildLabels(List<String> strList) {
    List<String> strResultList = new ArrayList<>();
    for (String strItem : strList) {
        strResultList.add("#" + strItem);
    }
    return strResultList;
}

strList, strResultList, strItem — 앞에 붙은 str이 "이건 문자열이야"를 알려 줍니다. 그런데 문제가 둘 있어요. 첫째, 요즘 IDE는 변수에 마우스만 올려도 타입을 바로 보여 줍니다. 타입 접두사는 이미 아는 정보를 또 적는 노이즈예요. 둘째, 나중에 이 변수의 타입이 바뀌면(문자열에서 리스트로) 이름은 str 그대로 남아 거짓말을 하게 됩니다. 발음도 안 돼요. "스트리절트리스트"라고 읽히죠.

✅ After

Java
public List<String> buildLabels(List<String> tags) {
    List<String> labels = new ArrayList<>();
    for (String tag : tags) {
        labels.add("#" + tag);
    }
    return labels;
}

tags, labels, tag — 타입이 아니라 무엇을 담는지(의미) 를 말합니다. 타입은 IDE와 컴파일러에게 맡기고, 이름은 의미에 집중하는 거예요.

헝가리안 표기법은 1980~90년대, 에디터가 빈약하고 타입 검사가 약하던 시절에 생긴 관습입니다. 그땐 이름에 타입을 적어 두는 게 도움이 됐어요. 하지만 현대 IDE가 타입을 즉시 보여 주고 안전하게 이름을 바꿔 주면서, 접두사는 거추장스러운 군더더기가 됐습니다. 그래서 지금은 거의 사라졌어요.

💡 같은 원리, 다른 표기: Java는 buildLabels처럼 단어를 붙여 쓰는 camelCase를, Python은 build_labels처럼 밑줄로 잇는 snake_case(PEP 8)를 씁니다. 표기 관습은 언어마다 다르지만, "타입 접두사(strTag) 없이 의미를 드러낸다"는 원리는 똑같습니다.

⚠️ 언제 깨나 — 살아남은 인코딩도 있습니다. 일부 팀은 멤버 변수에 _ 접두사를, 인터페이스에 I 접두사(주로 C#)를 약속해 씁니다. 팀이 합의해 일관되게 쓴다면 괜찮아요. 규칙의 본질은 "타입을 절대 붙이지 마라"가 아니라 "이름이 거짓말하게 두지 마라" 입니다.

💡 한 줄 정리

이름엔 타입이 아니라 의미를 담아라 — 타입을 표시하는 접두사는 IDE 시대에 거짓말의 원천이 된다.

🙋 학생 질문 — "그럼 m_ 같은 멤버 변수 접두사도 쓰면 안 되나요?"

상황에 따라 다릅니다. m_count, _count처럼 필드를 구분하려는 접두사는 헝가리안 표기법과 결이 조금 달라요. 타입이 아니라 "이건 지역 변수가 아니라 필드다"라는 역할을 표시하니까요. 그래도 요즘 Java에서는 this.count로 구분하거나 IDE가 필드를 다른 색으로 보여 주기 때문에 거의 안 씁니다. 핵심은 같습니다 — 팀이 합의했고 일관되면 쓰되, 안 쓰는 쪽이 요즘 기본값이에요.


Step 3: "10이 대체 무슨 숫자예요?"

리뷰에서 숫자를 보면 꼭 나오는 질문이 "이 10이 무슨 숫자예요?"입니다. 구매 횟수에 따라 할인해 주는 코드를 볼게요.

❌ Before

Java
public int finalPrice(int price, int purchaseCount) {
    if (purchaseCount >= 10) {
        return price - (int) (price * 0.2);
    } else if (purchaseCount >= 3) {
        return price - (int) (price * 0.1);
    }
    return price;
}

10, 0.2, 3, 0.1 — 코드만 봐선 정체를 알 수 없습니다. 이렇게 의미 설명 없이 코드에 그대로 적힌 숫자를 매직 넘버라고 불러요. 더 큰 문제는, 같은 10이 여러 곳에 흩어져 있을 때입니다. 단골 기준이 10에서 15로 바뀌면, 흩어진 10을 하나라도 놓치는 순간 버그가 됩니다.

✅ After

Java
private static final int LOYAL_CUSTOMER_THRESHOLD = 10;
private static final int REPEAT_CUSTOMER_THRESHOLD = 3;
private static final double LOYAL_DISCOUNT_RATE = 0.2;
private static final double REPEAT_DISCOUNT_RATE = 0.1;

public int finalPrice(int price, int purchaseCount) {
    if (purchaseCount >= LOYAL_CUSTOMER_THRESHOLD) {
        return price - (int) (price * LOYAL_DISCOUNT_RATE);
    } else if (purchaseCount >= REPEAT_CUSTOMER_THRESHOLD) {
        return price - (int) (price * REPEAT_DISCOUNT_RATE);
    }
    return price;
}

이제 10은 "단골 기준", 0.2는 "단골 할인율"이라고 이름이 설명합니다. 정책이 바뀌면 맨 위 상수 한 곳만 고치면 되고, 흩어진 숫자를 찾아 헤맬 일도 없어요. 숫자에 이름만 달았을 뿐, 어느 구매 구간에서도 계산 결과는 똑같습니다.

⚠️ 언제 깨나 — 모든 숫자를 상수로 빼면 오히려 과합니다. 첫 인덱스 0, i + 11, 반으로 나누는 / 2처럼 의미가 자명한 숫자는 그대로가 더 잘 읽혀요. ZERO = 0 같은 상수는 코드를 어지럽힐 뿐입니다. 기준은 딱 하나 — "이 숫자가 왜 하필 이 값인지 설명이 필요한가?" 입니다.

💡 한 줄 정리

설명이 필요한 숫자엔 이름을 달아라 — 단, 자명한 0·1·2까지 상수로 빼면 과하다.

🙋 학생 질문 — "상수 이름이 너무 길어지는데, 짧게 줄이면 안 되나요?"

이름이 길어지는 건 보통 그 상수가 그만큼 많은 맥락을 담고 있다는 신호예요. LOYAL_DISCOUNT_RATE가 길다고 LDR로 줄이면, 다시 Step 1의 "검색 안 되고 의미 없는 이름" 문제로 돌아갑니다. 길이가 부담스러우면, 이름을 줄이는 대신 관련 상수들을 한 곳(예: 할인 정책 클래스)에 모으는 방법을 씁니다. 그러면 맥락이 위치로 드러나서 이름을 조금 짧게 가져갈 여지가 생겨요. 다만 그건 클래스 설계 이야기라, 다음 모듈들에서 더 다룹니다.


Step 4: "주석은 대개 실패의 신호다"

리뷰 단골 지적 두 번째, "이 주석 코드랑 따로 노는데요?"입니다. 주석을 무조건 나쁘다고 보는 건 아니지만, 솔직히 말하면 대다수의 주석은 "코드로 설명하지 못한 것의 변명"이에요. 그중에서도 가장 위험한 건 거짓말하는 주석입니다.

❌ Before

Java
// 무료배송 기준: 3만원 이상 구매 시
public boolean isFreeShipping(int totalPrice) {
    return totalPrice >= 50_000;
}

주석은 "3만원"이라는데, 코드는 50_000, 즉 5만원입니다. 누군가 배송 정책을 바꾸면서 코드는 고치고 주석은 안 고친 거예요. 주석은 컴파일러가 검사하지 않아서, 시간이 지나면 이렇게 코드와 따로 놀게 됩니다. 그리고 이 주석을 믿은 사람은 틀린 정보로 일하게 돼요. 실제로 30_000원을 넣어 보면 무료배송이 안 됩니다. 주석이 거짓말을 하고 있는 거죠.

✅ After

Java
private static final int FREE_SHIPPING_THRESHOLD = 50_000;

public boolean isFreeShipping(int totalPrice) {
    return totalPrice >= FREE_SHIPPING_THRESHOLD;
}

주석을 지우고, 상수 이름이 같은 말을 하게 했습니다. 이름은 코드의 일부라서 거짓말을 하면 컴파일러나 사용처에서 바로 들통나요. 주석과 달리 코드와 따로 놀 수가 없습니다.

그렇다면 좋은 주석은 뭘까요? 코드로 표현할 수 없는 것만 남기면 됩니다.

Java
// 이벤트 시작 0시에 트래픽이 몰리므로, 이 시간대만 캐시 TTL을 짧게 둔다. (의도/배경)
static final int EVENT_CACHE_TTL_SECONDS = 5;

// ⚠️ 이 순서를 뒤집으면 외래 키 제약으로 삭제가 실패한다. 순서를 바꾸지 말 것. (경고)
// TODO: 배치 분리 후 이 메서드는 스케줄러로 옮긴다. (JIRA-1234)

"왜 이렇게 했는지(의도·배경)", "건드리면 안 되는 이유(경고)", "나중에 할 일(TODO)", 그리고 공개 API의 사용법 문서나 법적 고지 — 이런 건 코드만으로는 못 담아서 주석이 제값을 합니다.

⚠️ 언제 깨나 — "주석은 무조건 나쁘다"는 말은 맹목적인 규칙 추종입니다. 좋은 주석(의도·경고·법적 고지·공개 API 문서)은 살려야 해요. 나쁜 건 두 종류 — 코드를 그대로 옮긴 주석(i++; // i를 1 증가)과, 낡아서 거짓말하는 주석입니다. 지울 주석과 남길 주석을 가리는 안목이 실력이에요.

💡 한 줄 정리

거짓말하는 주석은 없느니만 못하다 — 코드와 따로 노는 주석을 걷어내되, 의도·경고 같은 좋은 주석은 남겨라.

🙋 학생 질문 — "그래도 한국어 주석으로 설명해 두면 후임이 편하지 않나요?"

마음은 알지만, 그 친절이 거꾸로 독이 되기 쉬워요. 주석은 코드가 바뀌어도 자동으로 따라 바뀌지 않거든요. 처음엔 정확했던 설명도 몇 번의 수정을 거치면 코드와 어긋나고, 후임은 그 낡은 설명을 믿고 잘못 이해합니다. 정말 후임을 위한다면, 설명을 주석 대신 이름에 담으세요. 이름은 코드가 바뀔 때 같이 바뀌니까요. 그게 바로 다음 Step의 주제입니다.


Step 5: "설명은 주석이 아니라 코드로"

Step 4가 "거짓말하는 주석을 걷어내라"였다면, 이번엔 한 걸음 더 나아갑니다. 주석으로 설명하려던 내용을 아예 이름으로 옮기는 거예요. 회원이 탈퇴할 수 있는지 판단하는 코드를 볼게요.

❌ Before

Java
public boolean canWithdraw(Member member) {
    // 가입한 지 7일이 지났고, 정지 상태가 아니며, 보유 포인트가 0인 회원만 탈퇴할 수 있다.
    return member.daysSinceJoined() >= 7
            && !member.isSuspended()
            && member.point() == 0;
}

조건 세 개가 한 줄에 뭉쳐 있어서, 위에 달린 주석이 없으면 무슨 판단인지 한눈에 안 들어옵니다. 주석이 있어야만 읽히는 코드는, 사실 코드가 스스로 설명하는 데 실패한 거예요.

✅ After

Java
private static final int WITHDRAW_COOLDOWN_DAYS = 7;

public boolean canWithdraw(Member member) {
    boolean passedCooldown = member.daysSinceJoined() >= WITHDRAW_COOLDOWN_DAYS;
    boolean notSuspended = !member.isSuspended();
    boolean hasNoPoint = member.point() == 0;

    return passedCooldown && notSuspended && hasNoPoint;
}

주석이 하던 설명을 이제 변수 이름이 합니다. passedCooldown(쿨다운 지남), notSuspended(정지 아님), hasNoPoint(포인트 없음) — 이렇게 의미를 담은 변수를 설명 변수라고 불러요. 마지막 return은 "쿨다운 지났고, 정지 아니고, 포인트 없으면 탈퇴 가능"으로 영어 문장처럼 읽힙니다. 7도 상수로 빼서 의미를 달았죠. 주석을 지웠는데 오히려 더 잘 읽히게 됐습니다.

이걸 더 밀고 가면, 이 세 조건을 아예 isEligibleToWithdraw() 같은 작은 함수로 뽑아낼 수도 있어요. 다만 그 "함수로 쪼개기"는 다음 시간(함수 설계)에서 본격적으로 다룹니다. 오늘은 이름으로 의도를 드러내는 데까지입니다.

⚠️ 언제 깨나 — 설명 변수도 과하면 독입니다. 딱 한 번 쓰고 의미가 이미 자명한 식까지 변수로 빼면, 줄만 늘고 읽는 흐름이 끊겨요. 설명 변수는 "주석을 달고 싶을 만큼 복잡하거나 의도가 안 보이는 식"에만 씁니다.

💡 한 줄 정리

주석으로 변명할 코드라면, 그 설명을 이름에 담아라 — 이름은 코드와 함께 늙는다.

🙋 학생 질문 — "설명 변수를 만들면 연산이 더 생겨서 느려지지 않나요?"

거의 그렇지 않습니다. 요즘 컴파일러와 런타임은 이런 단순한 지역 변수를 최적화로 걷어내서, 실제 성능 차이는 사실상 없다고 봐도 됩니다. 설령 아주 미세한 차이가 있더라도, 그보다 "사람이 읽고 고치는 시간"을 줄이는 이득이 훨씬 큽니다. 성능이 진짜 중요한 극히 일부 구간은 따로 측정해서 판단하면 되고, 그 트레이드오프 이야기는 이 과목 마지막에 제대로 다룹니다.


Step 6: "코드도 읽는 리듬이 있다"

포매팅은 그냥 취향 문제 같지만, 실제로는 읽는 속도를 바꿉니다. 합계와 최댓값을 구하는 같은 코드를, 형식만 다르게 두 번 보겠습니다.

❌ Before

Java
public int sum(int[] numbers){int total=0;
    for(int number:numbers){
    total+=number;}
    return total;}
public int max(int[] numbers){
    int maxValue=numbers[0];for(int number:numbers){if(number>maxValue)maxValue=number;}
    return maxValue;}

연산자 양옆에 공백이 없고, 줄바꿈이 제멋대로고, 두 메서드 사이가 딱 붙어 있습니다. 눈이 어디서 끊어 읽어야 할지 몰라 한참을 헤매게 돼요.

✅ After

Java
public int sum(int[] numbers) {
    int total = 0;
    for (int number : numbers) {
        total += number;
    }
    return total;
}

public int max(int[] numbers) {
    int maxValue = numbers[0];
    for (int number : numbers) {
        if (number > maxValue) {
            maxValue = number;
        }
    }
    return maxValue;
}

형식만 정리했을 뿐 계산 결과는 그대로인데, 읽는 부담이 확 줄었죠. 여기엔 두 가지 원리가 깔려 있습니다.

  • 수직 밀집도: 서로 관련 있는 코드(변수 선언과 그 사용)는 가까이 붙인다.
  • 개념적 거리: 관련 없는 것(summax라는 별개의 메서드)은 빈 줄로 띄워 구분한다.

가로로는 연산자 양옆에 공백을 두고(total += number), 들여쓰기를 일관되게 맞춥니다. 가까운 건 모으고 먼 건 띄우는 것 — 글의 문단 나누기와 똑같아요.

⚠️ 언제 깨나 — 포매팅엔 절대적인 정답이 없습니다. 중괄호를 같은 줄에 둘지 다음 줄에 둘지, 들여쓰기를 4칸으로 할지 2칸으로 할지는 옳고 그름이 아니라 합의의 문제예요. 정말 중요한 건 일관성 하나입니다. 한 파일 안에서, 한 팀 안에서 같기만 하면 됩니다. 그 "합의"를 어떻게 지키느냐가 바로 다음 Step입니다.

💡 한 줄 정리

가까운 건 붙이고 먼 건 띄워라 — 포매팅은 코드를 읽는 리듬이고, 핵심은 일관성이다.

🙋 학생 질문 — "들여쓰기는 탭이 맞나요 스페이스가 맞나요?"

둘 다 맞고, 둘 다 틀립니다. 탭은 화면에서 폭을 조절할 수 있어 좋고, 스페이스는 어디서 봐도 똑같이 보여서 좋아요. 양쪽 다 합리적인 이유가 있어서 끝없이 싸우게 되는 주제입니다. 그래서 정답은 "탭이냐 스페이스냐"가 아니라 "팀이 하나로 정하고 모두가 따른다" 예요. 그리고 그 결정을 사람의 의지에 맡기지 않고 도구가 강제하게 만드는 게 가장 확실합니다. 바로 다음 Step에서 봅니다.


Step 7: "내 취향 말고 팀의 약속"

Step 6 끝에서 본 질문들(탭이냐 스페이스냐, 중괄호 위치)엔 정답이 없습니다. 그래서 이런 건 개인이 정하지 않아요. 팀이 한 번 정하고, 도구가 강제합니다.

코드는 여러 명이 함께 읽고 고칩니다. 한 사람이 자기 스타일을 고집하면, 코드 변경 이력이 포매팅 변경으로 뒤덮여서 정작 중요한 진짜 변경이 안 보이게 돼요. 그래서 "내 방식이 옳다"보다 "우리가 같다"가 먼저입니다.

그리고 그 약속을 사람이 일일이 지키는 게 아니라, 자동 포매터에게 맡깁니다. 저장하는 순간 도구가 합의된 규칙대로 형식을 자동으로 맞춰 줘요. Java에는 google-java-format이나 Spotless, 자바스크립트엔 Prettier, Python엔 black이 있습니다. 저장소에는 이렇게 규칙을 적어 둬서, 누가 어떤 IDE를 쓰든 같은 형식이 나오게 합니다.

텍스트
[*.java]
indent_style = space
indent_size = 4
max_line_length = 120

🌟 Python의 black은 "타협 없는 포매터"를 표방합니다. 설정 옵션을 거의 주지 않아서, "이건 이렇게 하자 저건 저렇게 하자"는 논쟁 자체를 없애 버려요. 포매팅 회의를 없애려고 만든 도구인 셈입니다. 새 프로젝트라면 이런 자동 포매터를 처음부터 켜 두는 걸 추천합니다.

⚠️ 언제 깨나 — 자동 포매터가 만능은 아닙니다. 포매터는 공백과 줄바꿈은 맞춰 주지만, "좋은 이름"이나 "작은 함수"는 못 만들어요. 형식은 도구에 맡기고, 사람은 그 위에서 이름·구조 같은 진짜 설계에 집중하라는 뜻입니다. 또 오래된 코드베이스에 갑자기 포매터를 돌리면 거대한 변경 덩어리가 생기니, 도입 시점은 팀이 합의해서 정해야 합니다.

💡 한 줄 정리

포매팅은 개인 취향이 아니라 팀의 약속 — 한 번 정하고 자동 포매터에 맡겨, 논쟁이 아니라 설계에 집중하라.

🙋 학생 질문 — "팀 컨벤션이 제 생각엔 별로인데, 그래도 따라야 하나요?"

네, 일단은 따르는 게 맞습니다. 포매팅에서 가장 중요한 가치는 "어떤 스타일이 더 예쁘냐"가 아니라 "모두가 같으냐"거든요. 내 취향이 더 낫더라도, 나 혼자 다르게 쓰면 팀 전체의 일관성이 깨져서 손해가 더 큽니다. 정말 바꾸고 싶다면, 혼자 어기는 게 아니라 팀에 제안해서 컨벤션 자체를 바꾸세요. 합의가 바뀌면 그때 다 같이 바꾸면 됩니다. "규칙이 마음에 안 들면 어기는 게 아니라 고친다" — 이게 협업의 기본 태도예요.


마무리

오늘은 거창한 설계 이전에, 코드를 읽는 사람을 배려하는 가장 작은 기본기를 익혔습니다. 핵심을 세 가지로 정리할게요.

오늘 배운 핵심 세 가지

  • 💡 하나 — 이름이 곧 문서다. 의도를 드러내고, 발음·검색이 되는 이름을 짓고, 매직 넘버엔 이름을 달아라. 타입을 표시하는 접두사는 거짓말의 원천이다.
  • 💡 둘 — 주석은 코드가 못 한 설명의 보충일 뿐이다. 거짓말하는 주석을 걷어내고, 설명은 이름으로 옮기되, 코드로 표현 못 하는 의도·경고만 주석으로 남겨라.
  • 💡 셋 — 형식은 읽는 리듬이고, 그 리듬은 팀의 약속이다. 가까운 건 붙이고 먼 건 띄우되, 탭이냐 스페이스냐 같은 논쟁은 자동 포매터에 맡겨라.

그리고 오늘 내내 한 가지를 반복했습니다 — 오늘 배운 규칙도 절대 법칙이 아니라는 것. 짧은 이름이 나을 때, 주석이 꼭 필요할 때, 포매팅이 합의의 문제일 때를 매번 함께 봤죠. 규칙을 외우는 건 주니어, 규칙의 이유를 알아 언제 적용하고 언제 느슨하게 둘지 판단하는 게 미들입니다. 이 안목이 이 과목 전체를 관통하는 척추예요.

다음 시간 예고

다음 시간엔 함수 설계로 갑니다. 오늘 Step 5에서 살짝 본 "복잡한 조건을 작은 함수로 뽑기"를 본격적으로 다뤄요. 함수를 작게, 한 가지 일만 하게, 인자를 줄이고, 숨은 부수 효과를 걷어내는 법. 오늘 이름을 잘 짓는 법을 배웠으니, 이제 그 좋은 이름이 붙는 함수 자체를 다듬을 차례입니다.


과제

오늘 배운 이름·매직 넘버·주석을, 직접 손으로 고치며 익혀 봅시다. 머리로 아는 것과 손으로 고치는 것은 다릅니다.

[기초] 정체불명 코드에 이름 붙이기

아래 코드는 동작은 맞지만 이름이 아무것도 설명하지 않습니다. 의미 있는 변수·메서드 이름으로 바꿔 보세요.

Java
public int c(int[] a) {
    int n = 0;
    for (int x : a) {
        if (x >= 60) {
            n++;
        }
    }
    return n;
}

힌트: 이 코드는 점수 배열에서 합격 점수 이상을 받은 사람 수를 셉니다. 메서드 이름, 매개변수, 지역 변수를 각각 의미가 드러나게 바꾸고, 60도 처리해 보세요.

[응용] 거짓말하는 주석과 복합 조건 정리하기

아래 두 메서드를 고쳐 보세요. 첫째는 주석과 코드가 어긋나 있고, 둘째는 주석이 없으면 안 읽히는 복합 조건입니다.

Java
// 성인(만 20세 이상)만 구매 가능
public boolean canPurchase(int age) {
    return age >= 19;
}

public boolean canGetCoupon(Member member) {
    // 가입 30일이 넘었고, 이번 달에 구매한 적이 있는 신규 알림 동의 회원
    return member.daysSinceJoined() > 30
            && member.purchasedThisMonth()
            && member.agreedToNotification();
}

힌트: 첫 메서드는 주석과 코드 중 무엇이 맞는지 결정하고 둘을 일치시키세요(매직 넘버도 처리). 둘째는 주석을 설명 변수로 옮겨 주석 없이도 읽히게 만드세요.

[심화] 매직 넘버 정리 + 도그마 판단

아래 함수에는 여러 숫자가 그대로 들어 있습니다. 상수로 빼야 할 숫자와, 그대로 두는 게 나은 숫자를 구분하고, 그 이유를 한 줄씩 적으세요.

Java
public int rankPoint(int score, int rank) {
    int point = score * 2;
    if (rank == 1) {
        point += 100;
    } else if (rank <= 3) {
        point += 50;
    }
    return point / 1;
}

힌트: 100, 50, 3은 정책 값이라 이름이 필요할 수 있습니다. 반면 1(1등)이나 * 2, / 1 같은 숫자는 상수로 빼는 게 과할 수 있어요. "이 숫자가 왜 이 값인지 설명이 필요한가"를 기준으로 판단하고, / 1처럼 의미 없는 코드는 정리해도 됩니다.


생각해볼 주제

규칙을 외우는 데서 멈추지 말고, "언제 이 규칙을 따르고 언제 느슨하게 둘까"를 스스로 판단해 봅시다. 정답이 하나가 아닌 질문들입니다.

1. 좋은 이름에 드는 시간은 낭비일까?

변수 이름 하나를 정하느라 5분을 고민하는 건 과한 일처럼 보일 수 있습니다. 당장은 진도가 안 나가니까요. 하지만 그 이름이 앞으로 몇 명에게, 몇 번이나 읽힐지를 생각하면 어떨까요? 좋은 이름에 쓰는 시간이 비용인지 투자인지, 변경 비용 관점에서 토론해 보세요.

2. 주석을 0으로 만드는 게 목표일까?

"주석 없는 코드가 진짜 깨끗한 코드"라는 말을 종종 듣습니다. 일리가 있지만, 이 말을 곧이곧대로 밀어붙이면 어떤 위험이 있을까요? 좋은 주석(의도·경고·배경)까지 지워 버린 코드는 정말 더 나은 코드일까요? "주석 0"이라는 목표 자체의 함정을 생각해 보세요.

3. 팀 컨벤션이 '틀렸다'고 느껴질 때

내 기준엔 들여쓰기 2칸이 분명히 더 낫다고 생각하는데, 우리 팀 컨벤션은 4칸입니다. 이때 나는 일관성을 위해 4칸을 따라야 할까요, 아니면 더 낫다고 믿는 2칸을 고집해야 할까요? "개인의 옳음"과 "팀의 일관성"이 부딪힐 때 무엇을 우선해야 하는지, 그리고 컨벤션을 바꾸고 싶다면 어떻게 해야 하는지 이야기해 보세요.

✅ 예시 답안정답 보기

과제와 생각해볼 주제의 예시답안입니다. 정답이 하나뿐인 건 아니에요. 풀이 예시의 이름은 "이렇게도 지을 수 있다"는 한 갈래일 뿐, 의도가 드러나면 다른 이름도 좋습니다. 중요한 건 "왜 이렇게 바꿨는가"입니다.


과제 예시답안

🎯 [과제 1 예시답안] 정체불명 코드에 이름 붙이기

채점 포인트

항목 보는 것 배점
메서드 이름 c → 무슨 일을 하는지 드러나는 이름(countPassed 등) ★★★
매개변수·지역 변수 a·n·x → 의미 있는 이름(scores·passedCount·score) ★★★
매직 넘버 60 → 이름 있는 상수(PASS_SCORE) ★★☆
동작 보존 세는 로직 자체는 그대로(이름만 바꿈) ★★★

풀이 예시

❌ Before

Java
public int c(int[] a) {
    int n = 0;
    for (int x : a) {
        if (x >= 60) {
            n++;
        }
    }
    return n;
}

✅ After

Java
private static final int PASS_SCORE = 60;

public int countPassed(int[] scores) {
    int passedCount = 0;
    for (int score : scores) {
        if (score >= PASS_SCORE) {
            passedCount++;
        }
    }
    return passedCount;
}

💡 튜터의 한마디: 단 한 군데도 로직을 바꾸지 않았습니다. 이름만 바꿨는데 메서드를 호출하는 쪽에서 countPassed(scores)라고 읽히니, 더 이상 안을 들여다보지 않아도 무슨 일을 하는지 압니다. 60PASS_SCORE로 빼 둔 덕에, 합격 기준이 70으로 바뀌어도 상수 한 곳만 고치면 됩니다. "동작은 그대로, 읽는 속도만 향상" — 이게 좋은 이름의 힘이에요.

🎯 [과제 2 예시답안] 거짓말하는 주석과 복합 조건 정리하기

채점 포인트

항목 보는 것 배점
주석-코드 불일치 해소 주석(20세)과 코드(19) 중 하나로 일치시킴 ★★★
매직 넘버 19ADULT_AGE 상수 ★★☆
설명 변수 복합 조건을 의미 있는 boolean 변수로 분해 ★★★
주석 제거 설명을 이름이 대신하므로 주석 삭제 ★★☆

풀이 예시

❌ Before

Java
// 성인(만 20세 이상)만 구매 가능
public boolean canPurchase(int age) {
    return age >= 19;
}

✅ After — 한국 법적 성인은 만 19세이므로, 코드(19)를 기준으로 보고 주석을 걷어냈습니다.

Java
private static final int ADULT_AGE = 19;

public boolean canPurchase(int age) {
    return age >= ADULT_AGE;
}

❌ Before

Java
public boolean canGetCoupon(Member member) {
    // 가입 30일이 넘었고, 이번 달에 구매한 적이 있는 신규 알림 동의 회원
    return member.daysSinceJoined() > 30
            && member.purchasedThisMonth()
            && member.agreedToNotification();
}

✅ After

Java
private static final int COUPON_ELIGIBLE_DAYS = 30;

public boolean canGetCoupon(Member member) {
    boolean joinedOverThreshold = member.daysSinceJoined() > COUPON_ELIGIBLE_DAYS;
    boolean purchasedThisMonth = member.purchasedThisMonth();
    boolean agreedToNotification = member.agreedToNotification();

    return joinedOverThreshold && purchasedThisMonth && agreedToNotification;
}

💡 튜터의 한마디: 첫 메서드는 주석과 코드가 어긋난 전형적인 거짓말 주석입니다. 둘 중 무엇이 맞는지 먼저 정하는 게 중요해요(여기선 법적 기준인 19세를 따랐습니다). 둘째 메서드는 조건을 설명 변수로 쪼개면서 흥미로운 일이 생겼습니다. 각 조건에 이름을 달다 보니, 원래 주석의 "신규"라는 단어가 "가입 30일이 넘음(joinedOverThreshold)"과 살짝 안 맞는다는 게 드러나죠. 이름을 붙이면 이렇게 모호한 표현까지 점검하게 됩니다.

🎯 [과제 3 예시답안] 매직 넘버 정리 + 도그마 판단

채점 포인트

항목 보는 것 배점
정책 값 상수화 100·50·3 → 의미 있는 상수 ★★★
과한 상수화 회피 1(1등)은 그대로 둠을 이유와 함께 판단 ★★★
의미 없는 코드 정리 / 1 제거 ★★☆
판단 근거 서술 "왜 이 숫자는 빼고, 저 숫자는 두는가" 설명 ★★★

풀이 예시

❌ Before

Java
public int rankPoint(int score, int rank) {
    int point = score * 2;
    if (rank == 1) {
        point += 100;
    } else if (rank <= 3) {
        point += 50;
    }
    return point / 1;
}

✅ After

Java
private static final int SCORE_WEIGHT = 2;
private static final int FIRST_PLACE_BONUS = 100;
private static final int TOP_THREE_BONUS = 50;
private static final int TOP_THREE_RANK = 3;

public int rankPoint(int score, int rank) {
    int point = score * SCORE_WEIGHT;
    if (rank == 1) {
        point += FIRST_PLACE_BONUS;
    } else if (rank <= TOP_THREE_RANK) {
        point += TOP_THREE_BONUS;
    }
    return point;
}

판단 근거:

  • 상수로 뺀다: 100·50은 "왜 하필 이 값인지" 설명이 필요한 정책 값이라 FIRST_PLACE_BONUS·TOP_THREE_BONUS로. 3도 "상위 몇 등까지인가"라는 기준이라 TOP_THREE_RANK로 빼면 의미가 또렷해진다.
  • 그대로 둘 만하다: rank == 11은 "1등"이 그 자체로 자명해서, FIRST_RANK = 1 같은 상수는 오히려 군더더기다(이건 판단이 갈릴 수 있다 — 정책 문서와 1:1로 맞추려는 팀이라면 빼기도 한다).
  • 정리한다: / 1은 아무 일도 하지 않는 의미 없는 연산이라 삭제했다.
  • 고민거리: * 2(점수 2배)는 정책이면 SCORE_WEIGHT로 빼는 게 낫지만, 맥락에 따라 과하다고 볼 수도 있다. 여기선 "점수 가중치"라는 정책으로 보고 상수화했다.

💡 튜터의 한마디: 이 과제의 진짜 목표는 "모든 숫자를 상수로 빼기"가 아니라 "뺄 숫자와 둘 숫자를 가리는 판단" 입니다. 매직 넘버를 없애라는 규칙을 맹목적으로 따르면 ONE = 1, TWO = 2 같은 우스운 코드가 나와요. 기준은 늘 하나 — "이 숫자가 왜 이 값인지 설명이 필요한가". 이 판단을 스스로 내릴 수 있으면, 규칙을 외우는 단계를 넘어선 겁니다.


생각해볼 주제 예시답안

🤔 [생각해볼 주제 1] 좋은 이름에 드는 시간은 낭비일까?

문제 상황 요약

변수 이름 하나를 정하느라 5분을 쓰는 게 과한지, 그 시간이 비용인지 투자인지 변경 비용 관점에서 판단해 보는 주제입니다.

튜터의 가이드 및 해설

핵심은 "코드는 쓰는 시간보다 읽는 시간이 훨씬 길다"는 사실입니다. 한 번 짠 코드는 그 뒤로 수십, 수백 번 읽히고 고쳐져요. 읽는 시간이 쓰는 시간의 10배에 이른다는 말이 있을 정도입니다.

그렇다면 이름에 쓰는 5분은 "지금 내 진도"를 5분 늦추는 대신, 앞으로 그 코드를 읽을 모두의 시간을 매번 조금씩 아껴 줍니다. 한 명이 한 번만 읽으면 손해겠지만, 여러 명이 여러 번 읽으면 금세 본전을 뽑고도 남아요. 나쁜 이름은 읽을 때마다 "이게 뭐였더라" 하는 작은 세금을 매기고, 그 세금이 코드가 살아 있는 내내 복리로 쌓입니다.

물론 끝없이 고민하라는 뜻은 아닙니다. 좁은 스코프의 임시 변수까지 5분씩 고민하면 그건 과해요. "멀리까지·여러 사람에게 읽힐 이름"일수록 시간을 더 들이고, 금방 사라질 이름은 가볍게 — 여기서도 판단이 핵심입니다.

🎯 면접에선 이렇게 나온다

"이름 짓는 시간은 비용이 아니라, 가장 싸게 미래의 유지보수 비용을 줄이는 투자라고 봅니다. 코드는 쓰는 횟수보다 읽히는 횟수가 압도적으로 많으니까요. 다만 스코프가 좁아 금방 사라질 변수까지 과하게 고민하진 않고, 오래 살아남을 이름에 시간을 집중합니다."

💡 실무에선

이름이 잘 안 떠오를 때는 일단 그럴듯한 이름으로 두고 진행한 뒤, 의미가 더 또렷해지는 순간 IDE의 이름 바꾸기 기능으로 한 번에 고칩니다. 이름은 처음에 완벽하게 짓기보다, 이해가 깊어질수록 다듬어 가는 것입니다.

🤔 [생각해볼 주제 2] 주석을 0으로 만드는 게 목표일까?

문제 상황 요약

"주석 없는 코드가 깨끗한 코드"라는 말을 곧이곧대로 밀어붙일 때의 위험을 짚어 보는 주제입니다.

튜터의 가이드 및 해설

"주석 0"을 목표로 잡으면 방향이 뒤집힙니다. 주석을 줄이는 것 자체가 목적이 되어, 코드로는 도저히 담을 수 없는 정보까지 지워 버리게 돼요.

코드가 답할 수 있는 건 "무엇을, 어떻게(what·how)"입니다. 하지만 "왜 이렇게 했는가(why)"는 코드만으로 표현하기 어렵습니다. 예를 들어 "이 외부 API가 가끔 음수를 반환하는 버그가 있어 방어 코드를 넣었다" 같은 배경, "이 순서를 바꾸면 데드락이 난다" 같은 경고, 법적 고지나 공개 API의 사용법 문서는 코드가 대신 말해 줄 수 없어요. 이런 좋은 주석까지 "주석 0"이라는 구호로 지우면, 오히려 위험한 코드가 됩니다.

그래서 진짜 목표는 "주석을 0으로"가 아니라 "코드가 스스로 말하게 만들기" 입니다. 이름과 구조로 의도를 충분히 드러내면 설명용 주석은 자연히 줄어들고, 그 결과로 남는 주석은 코드가 못 하는 말(why·경고)만 담은 값진 주석이에요. 주석이 줄어드는 건 목표가 아니라 좋은 코드의 결과입니다.

🎯 면접에선 이렇게 나온다

"주석을 줄이는 게 목적이 아니라, 코드가 스스로 의도를 말하게 만든 결과로 설명 주석이 줄어드는 것이라고 봅니다. what·how는 코드와 이름으로 드러내고, 코드가 답할 수 없는 why·경고·배경은 오히려 주석으로 분명히 남깁니다. '주석 0'은 좋은 주석까지 지우는 함정이 될 수 있습니다."

💡 실무에선

코드 리뷰에서 주석을 보면 먼저 묻습니다 — "이건 이름이나 구조로 대체할 수 있나, 아니면 코드가 못 하는 말인가?" 전자면 코드를 고치자고 제안하고, 후자면 그 주석을 지키자고 합니다.

🤔 [생각해볼 주제 3] 팀 컨벤션이 '틀렸다'고 느껴질 때

문제 상황 요약

내 기준엔 2칸 들여쓰기가 더 나은데 팀은 4칸일 때, 일관성을 따를지 개인의 옳음을 고집할지 판단하는 주제입니다.

튜터의 가이드 및 해설

먼저 포매팅에서 가장 중요한 가치가 무엇인지 짚어야 합니다. 그건 "어떤 스타일이 더 예쁜가"가 아니라 "모두가 같은가" 예요. 들여쓰기 2칸과 4칸은 옳고 그름이 아니라 취향의 영역이고, 양쪽 다 멀쩡히 잘 돌아갑니다.

그래서 내 취향이 더 낫다고 느껴져도, 나 혼자 다르게 쓰면 팀 전체의 일관성이 깨집니다. 코드 변경 이력이 포매팅 차이로 지저분해지고, 같은 파일에 두 스타일이 섞여 읽기 더 나빠져요. 내가 얻는 작은 만족보다 팀이 잃는 비용이 훨씬 큽니다. 그러니 일단은 컨벤션을 따르는 게 맞습니다.

그렇다고 "무조건 참아라"는 건 아니에요. 정말 더 나은 방식이라고 확신한다면, 혼자 어기는 게 아니라 팀에 제안해서 컨벤션 자체를 바꾸는 게 정답입니다. 합의가 바뀌면 자동 포매터 설정을 고쳐 다 같이 한 번에 바꾸면 돼요. "규칙이 마음에 안 들면 어기는 게 아니라 고친다" — 이게 협업의 성숙한 태도입니다.

🎯 면접에선 이렇게 나온다

"협업에서 포매팅의 best는 '내 방식이 옳다'가 아니라 '우리가 같다'라고 생각합니다. 취향 차이라면 일관성을 위해 팀 컨벤션을 따르고, 정말 더 낫다고 판단되면 혼자 어기는 대신 팀에 제안해 컨벤션과 포매터 설정을 함께 바꿉니다. 규칙은 어기는 게 아니라 고치는 것이니까요."

💡 실무에선

이런 소모적 논쟁을 아예 없애려고, 새 프로젝트는 시작할 때 자동 포매터(google-java-format·Prettier·black 등)를 켜고 그 기본값을 그대로 팀 컨벤션으로 삼는 경우가 많습니다. 사람이 정할 여지를 줄여, 다툴 일 자체를 없애는 거예요.

전체 목록 클린코드