Skip to main content

2장 의미 있는 이름

1. 📌 핵심 개념 정리

✅ 요약하기

  1. 의도를 분명히 밝혀라
    좋은 이름을 지으려면 시간이 걸리지만, 좋은 이름으로 절약하는 시간이 훨씬 더 많다. 주석이 필요하다면, 이름이 의도를 분명히 드러내지 못했다는 신호일 수 있다. 코드의 맥락이 이름 자체에 명시적으로 드러나도록 작성해야 한다.

    • 개선 전 코드 예시:
      getThem() 함수는 무엇을 하는지, theList는 무엇을 담고 있는지, x[0] == 4 조건은 무엇을 의미하는지 코드만으로는 파악하기 어렵다.

      public List<int[]> getThem() {
          List<int[]> list1 = new ArrayList<int[]>();
          for (int[] x: theList)
              if (x[0] == 4)
                  list1.add(x);
          return list1;
      }
      
    • 개선 후 코드 예시:
      getFlaggedCells() 함수명, gameBoard, cell, STATUS_VALUE, FLAGGED, isFlagged() 등의 명확한 이름 사용으로 코드의 의도가 분명하게 드러난다.

      public List<int[]> getFlaggedCells() {
          List<int[]> flaggedCells = new ArrayList<int[]>();
          for (int[] cell : gameBoard)
              if (cell[STATUS_VALUE] == FLAGGED)
                  flaggedCells.add(cell);
          return flaggedCells;
      }
      
    • 변수명 개선 예시:
      의미 없는 d 대신, elapsedTimeInDays, daysSinceCreation 등 측정 값과 단위를 포함한 이름을 사용한다.

      • 변경 전: int d; // 경과 시간 (단위: 날짜)
      • 변경 후: int elapsedTimeInDays;
    • 클래스와 함수를 활용한 개선:
      단순 배열 대신 Cell 클래스를 사용하고, 조건문을 isFlagged() 함수로 추상화하여 코드의 명확성을 높일 수 있다.

      public List<Cell> getFlaggedCells () {
          List<Cell> flaggedCells = new ArrayList<Cell>();
          for (Cell cell: gameBoard)
              if (cell.isFlagged())
                  flaggedCells.add(cell);
          return flaggedCells;
      }
      

  1. 그릇된 정보를 피해라
    그릇된 정보는 코드의 의미를 흐리고 오해를 유발한다. 널리 쓰이는 약어나 특정 플랫폼 관련 용어, 유사한 이름의 변수/함수, 배열인데도 이름에 List를 포함하는 등의 경우 오해의 소지가 있다.
    • 혼란을 야기하는 이름 예시:
      getActiveAccount(), getActiveAccouts(), getActiveAccountInfo() 등은 어떤 함수를 호출해야 할지 혼란을 줄 수 있다.
    • 개선 전:
      int hp, aix, sco;
      int[] accountList;
      
      void XYZControllerHandlingOfStrings();
      void XYZControllerStorageOfStrings();
      
      public static void copyChars(char a1[], char a2[]) { ... }
      
    • 개선 후:
      int healthPoints, auxiliaryIndex, groupScore;
      int[] accounts;
      
      void convertStringsInXYZController();
      void storeStringsInXYZController();
      
      public static void copyCharacters(char source[], char destination[]) { ... }
      

  1. 의미 있게 구분해라
    컴파일러를 위한 코드가 아닌, 사람이 이해하기 쉬운 코드를 작성해야 한다. 연속된 숫자나 불용어를 사용하여 이름만으로는 의미를 구분하기 어렵게 만드는 것은 피해야 한다.
    • 지양해야 할 방식:
      • 연속된 숫자 덧붙이기 (a1, a2, a3 등)
      • 불용어 추가 (ProductInfo, ProductData, Product 등)
    • 지양해야 할 단어:
      noise word (a, an, the 등), 의미가 비슷하거나 일반적인 단어 (info, data 등)

  1. 검색하기 쉬운 이름을 사용하라
    이름 길이는 범위 크기에 비례해야 하며, 넓은 범위에서 사용되는 변수나 상수는 검색하기 쉽도록 더 명확하고 긴 이름을 사용하는 것이 좋다.
    • 예시 (매직 넘버 vs. 상수):
      • 개선 전:
        for (int j=0; j<34; j++){
            s += (t[j]*4)/5;
        }
        
      • 개선 후:
        int realDaysPerIdealDay = 4;
        const int WORK_DAYS_PER_WEEK = 5;
        int sum = 0;
        for (int j=0; j < NUMBER_OF_TASKS; j++){
            int realTaskDays = taskEstimate[j] * realDaysPerIdealDay;
            int realTaskWeeks = (realTaskDays / WORK_DAYS_PER_WEEK);
            sum += realTaskWeeks;
        }
        
    • 발음 및 검색 용이성 예시:
      • 개선 전:
        class DtaRcrd102 {
          private Date genymdhms;
        }
        
      • 개선 후:
        class Customer {
          private Date generationTimestamp;
        }
        

  1. 인코딩을 피하라
    헝가리안 표기법, 접두사/접미사 기반 네이밍, 불필요한 축약어 등은 코드 가독성을 떨어뜨리고 유지보수를 어렵게 만든다. 현대적인 IDE는 타입 정보를 쉽게 제공하므로, 변수 이름에 타입을 인코딩할 필요가 없다.
    • 불필요한 접두어 사용 예시:
      • 개선 전:
        interface IShapeFactory {
          void print();
        }
        
        class CShapeFactory implements IShapeFactory {
          private String m_name;
          // ...
        }
        
      • 개선 후:
        interface ShapeFactory {
          void print();
        }
        
        class ShapeFactoryImpl implements ShapeFactory {
          private String name;
          // ...
        }
        
    • 인터페이스와 구현 클래스 네이밍:
      인터페이스 이름에 I를 붙이는 대신, 구현 클래스 이름에 Impl 접미사를 붙이는 방식도 있다.

  1. 자신의 기억력을 자랑하지 마라
    짧고 함축적인 이름보다는 명료하고 이해하기 쉬운 이름을 사용해야 한다. 넓은 범위에서 사용되는 변수나 함수는 문자 하나의 이름보다 의미를 담은 이름을 사용하는 것이 중요하다.
    • 지양:
      문자 하나로 된 변수 이름 (l, o 등), 불필요한 축약어
    • 허용:
      루프 변수 i, j, k (로컬 범위 내)

  1. 클래스 이름과 메서드 이름
    • 클래스 이름:
      명사나 명사구 (예: Customer, WikiPage, Account, AddressParser). Manager, Processor, Data, Info와 같이 모호한 이름은 피한다.
    • 메서드 이름:
      동사나 동사구 (예: getName, postPayment, deletePage, save). name, pay와 같이 모호한 이름은 피하며, 접근자(getter), 변경자(setter), 조건자(is)에는 해당 접두사를 사용한다.
    • 생성자 중복 정의:
      생성자를 중복 정의할 때는 정적 팩토리 메서드 패턴을 사용하여 객체 생성 의도를 명확하게 한다.
      예: Complex.FromRealNumber(23.0)

  1. 기발한 이름은 피하라
    재미있거나 기발한 이름보다는 명확하고 솔직한 이름을 사용해야 한다. 특정 문화권에서만 이해할 수 있는 농담이나 유머는 피하고, 누구나 이해하기 쉬운 이름을 선택한다.
    • 예: HolyHandGrenade보다는 DeleteItems

  1. 한 개념에 한 단어를 사용하라
    일관성 있는 용어 사용은 코드의 이해도를 높이고 혼란을 줄여준다. 같은 개념을 지칭할 때 여러 단어를 혼용하지 않고 하나의 단어를 선택하여 꾸준히 사용해야 한다.
    • 예: fetch, retrieve, get 등 유사한 의미의 단어를 혼용하지 않는다.

  1. 말장난을 하지 마라
    • 한 단어를 두 가지 목적으로 사용하지 마라.
    • 예시
      • 지금까지 구현한 add() 메서드는 기존 값 두 개를 더하거나 이어서 새로운 값을 만든다.
      • 새로 작성하는 메서드는 집합에 값 하나를 추가한다. 이 메서드를 add라 불러도 괜찮을까?
      • 새 메서드는 맥락이 다르기 때문에 insertappend라는 이름이 적당하다.

  1. 해법 영역에서 가져온 이름을 사용하라
    • 코드를 읽을 사람도 프로그래머라는 사실을 명심하라. 전산 용어, 알고리즘 이름, 패턴 이름, 수학 용어 등을 사용해도 괜찮다.
    • 프로그래머에게 익숙한 기술 개념에는 기술 이름이 가장 적합하다.

  1. 문제 영역에서 가져온 이름을 사용하라
    • 적절한 프로그래머 용어가 없다면 문제 영역에서 이름을 가져온다.
    • 우수한 프로그래머와 설계자라면 해법 영역과 문제 영역을 구분할 줄 알아야 한다.

  1. 의미 있는 맥락을 추가하라
    • 스스로 의미가 분명한 이름이 있다면, 클래스, 함수, 이름 공간에 넣어 맥락을 부여한다. 모든 방법이 실패하면 마지막 수단으로 접두어를 붙인다.
    • 예시
      • 변경 전: firstName, lastName, street, houseNumber, city, state, zipcode
      • 변경 후: addrFirstName, addrLastName, addrStreet, addrHouseNumber, addrCity, addrState, addrZipcode
      • 변수가 좀 더 큰 구조에 속한다는 사실이 분명해진다.

  1. 불필요한 맥락을 없애라
    • 일반적으로 짧은 이름이 긴 이름보다 좋다. 단, 의미가 분명한 경우에 한해서이다. 이름에 불필요한 맥락을 추가하지 않도록 주의한다.

2. 🤔 이해가 어려운 부분

🔍 질문하기

  1. 생성자 중복 정의

    • 어려웠던 부분: 정적 팩토리 메서드를 통해 생성자 중복 정의하는 방법과 실제 사용 예시.

    • 이해한 점: 생성자 오버로딩의 단점을 극복하고, 객체 생성 의도를 명확하게 드러내기 위해 정적 팩토리 메서드를 사용하는 것이다.

    • 궁금한 점: 정적 팩토리 메서드의 실제 사용 방법과 다양한 예시.

      ✅ 답변:
      정적 팩토리 메서드는 클래스 내부에 private 생성자를 만들고, public static 메서드를 통해 객체를 생성하여 반환하는 방식이다.

      개선 후 Person 클래스 (정적 팩토리 메서드 사용) 예시

      class Person {
          private String name;
          private int age;
      
          // private 생성자로 외부에서 직접 객체 생성 방지
          private Person(String name, int age) {
              this.name = name;
              this.age = age;
          }
      
          // 정적 팩토리 메서드 1: 이름만 설정하는 경우
          public static Person withName(String name) {
              return new Person(name, 0);  // 기본 나이 설정
          }
      
          // 정적 팩토리 메서드 2: 이름과 나이를 설정하는 경우
          public static Person withNameAndAge(String name, int age) {
              return new Person(name, age);
          }
      
          @Override
          public String toString() {
              return "Person{name='" + name + "', age=" + age + "}";
          }
      }
      

      사용 방법

      public class Main {
          public static void main(String[] args) {
              Person p1 = Person.withName("Alice");      // 이름만 설정 (나이: 0)
              Person p2 = Person.withNameAndAge("Bob", 25); // 이름과 나이 설정
      
              System.out.println(p1); // Person{name='Alice', age=0}
              System.out.println(p2); // Person{name='Bob', age=25}
          }
      }
      

      정적 팩토리 메서드 withName()withNameAndAge()는 객체 생성 의도를 명확하게 드러내어 코드 가독성을 높인다.


  1. 인터페이스 클래스 이름과 구현 클래스 이름

    • 이해한 부분: 인터페이스 이름은 역할을, 구현 클래스 이름은 구체적인 객체를 나타내도록 직관적이고 명확하게 지어야 하며, 인터페이스 이름에 I 접두사를 붙이지 않는다.
    • 이해 코드:
      // 인터페이스: 역할을 표현
      public interface UserService {
          void registerUser(String name);
      }
      
      // 구현 클래스: 구체적인 구현체를 표현
      public class DefaultUserService implements UserService {
          public void registerUser(String name) {
              System.out.println("Registering user: " + name);
          }
      }
      

  1. 의미 있는 맥락 추가하기

    • 어려웠던 부분: 함수를 작은 기능 단위로 쪼개어 맥락을 추가하는 개념과 기능 단위의 모호성.

    • 궁금한 점: 함수 기능 단위의 기준과 좋은 함수 설계 방식.

      ✅ 답변:
      **'의미 있는 맥락 추가하기'**는 함수를 작고 명확한 기능 단위로 분리하여, 각 함수가 하나의 책임만 수행하도록 만드는 것을 의미한다.
      함수 기능 단위는 **'하나의 명확하게 정의된 작업'**으로 판단하며, 다음 원칙들을 참고할 수 있다:

      1. 단일 책임 원칙 (SRP): 함수는 오직 하나의 책임만 가져야 한다.
      2. 함수 이름: 기능을 명확하게 설명해야 한다.
      3. 단일 역할: 하나의 함수는 '무엇'을 하는지를 명확하게 나타내야 한다.
      4. 코드 재사용성: 중복 코드를 제거하고 재사용 가능해야 한다.
      5. 낮은 의존성: 다른 함수에 과도하게 의존하지 않도록 해야 한다.

      예시: "주문 처리" 기능을 "주문 접수", "결제 처리", "재고 관리", "배송 정보 생성"과 같이 작은 함수 단위로 분리하여 코드 가독성, 재사용성, 응집도를 높일 수 있다.


  1. 그릇된 정보는 피하고 의미 있게 구분하라.

    • 어려웠던 부분: 그릇된 정보에 대한 기준의 주관성.

    • 궁금한 점: 그릇된 정보를 피하기 위해 코드를 어느 수준까지 구분해야 하는가?

      ✅ 답변:
      "그릇된 정보"는 코드 독자를 혼란스럽게 하거나, 코드의 실제 동작 방식과 다르게 오해하도록 유도하는 모든 것을 의미한다.
      판단 기준은 상황에 따라 다르지만, 다음 가이드라인을 참고할 수 있다:

      • 오해의 소지가 있는 단어 피하기: 일반적이지 않은 약어, 특정 플랫폼/기술 용어, 예를 들어 배열인데도 accountList처럼 List로 오해될 수 있는 이름 등.
      • 유사한 이름으로 혼란 야기하지 않기: getActiveAccount, getActiveAccounts 등 이름만으로 역할 구분이 어려운 경우.
      • 모호한 이름 사용하지 않기: XYZControllerHandlingOfStrings, copyChars(char a1[], char a2[]) 등 기능 파악이 어려운 이름.

      핵심: 코드 독자의 입장에서 "이 이름이 코드를 오해 없이 정확하게 이해하는 데 도움이 되는가?"를 기준으로 판단하며, 코드 리뷰를 통해 동료 개발자들과 함께 개선하는 것이 중요하다.


  1. 인코딩을 피하라.

    • 어려웠던 부분: 인코딩의 정확한 개념.

    • 이해한 점: 인코딩은 변수에 부가 정보를 덧붙여 표기하는 방법이다.

    • 궁금한 점: 인코딩을 피하라는 권장과 달리, 일부 언어(C#)에서 인코딩 규칙을 권장하는 이유는 무엇인가?

      ✅ 답변:
      과거 C와 같이 타입 검사가 약하거나 IDE 기능이 부족했던 환경에서는 인코딩이 유용했을 수 있다. 그러나 현대적인 언어와 IDE는 강력한 타입 시스템과 개발 지원 기능을 제공하므로, 인코딩은 가독성을 해칠 수 있다.
      C#의 인코딩 규칙: 인터페이스 이름에 I 접두사, private 멤버 변수에 _ 접두사 등을 권장하기도 하지만, 이는 과거 유산이며 최근에는 인코딩을 지양하고 의미 명확한 이름 짓기를 선호하는 추세이다.
      결론: "클린 코드"는 최신 개발 트렌드를 반영하여 인코딩을 피하는 것을 권장하지만, 특정 언어나 프로젝트에서 인코딩 규칙이 강제된다면 그에 따르는 것이 중요하다.


3. 📚 참고 사항

📢 논의하기

  1. 관련 자료 공유

    • 추가 자료:

    • 생성자 중복 정의 (정적 팩토리 메서드)

      • 개선 전: 생성자 이름이 동일하여 의미 파악이 어렵고, 생성자가 늘어날수록 관리하기 힘들어짐.

        class Person {
            private String name;
            private int age;
        
            public Person(String name) { // 첫 번째 생성자
                this.name = name;
                this.age = 0;
            }
        
            public Person(String name, int age) { // 두 번째 생성자 (오버로딩)
                this.name = name;
                this.age = age;
            }
        }
        
      • 개선 후: 정적 팩토리 메서드를 사용하여 각 생성자의 의미를 명확히 하고, new 연산자 없이 객체 생성을 제어하여 불필요한 객체 생성을 방지.

        class Person {
            private String name;
            private int age;
        
            private Person(String name, int age) { // private 생성자
                this.name = name;
                this.age = age;
            }
        
            public static Person withName(String name) { // 정적 팩토리 메서드 1
                return new Person(name, 0);
            }
        
            public static Person withNameAndAge(String name, int age) { // 정적 팩토리 메서드 2
                return new Person(name, age);
            }
        
            @Override
            public String toString() {
                return "Person{name='" + name + "', age=" + age + "}";
            }
        }
        
      • 사용 예시

        public class Main {
            public static void main(String[] args) {
                Person p1 = Person.withName("Alice");
                Person p2 = Person.withNameAndAge("Bob", 25);
        
                System.out.println(p1); // Person{name='Alice', age=0}
                System.out.println(p2); // Person{name='Bob', age=25}
            }
        }
        
  2. 논의하고 싶은 주제

    • 이름 길이는 범위 크기에 비례:
      변수, 함수, 클래스 등의 이름 길이는 해당 이름의 범위 크기에 비례하여 결정해야 한다.
      • 범위 결정 기준:
        • 좁은 범위 (로컬 변수, 짧은 함수): 짧고 간결한 이름.
        • 넓은 범위 (전역 변수, 클래스 필드, 공용 API 메서드): 길고 명확하며 의미 있는 이름.
      • 길이 기준: 범위가 좁을수록 짧은 이름, 범위가 넓을수록 긴 이름을 사용하는 것이 일반적이지만, 절대적인 기준은 없으며 팀/프로젝트 컨벤션에 따르는 것이 중요하다.