혼자 정리

2. 의미 있는 이름 본문

클린코드

2. 의미 있는 이름

tbonelee 2021. 9. 19. 18:18

2장 의미 있는 이름

의도를 분명히 밝혀라

  • 변수(혹은 함수나 클래스)의 존재 이ㅠ는?
  • 수행 기능은?
  • 사용 방법은?
    이러한 질문들에 따로 주석이 필요치 않게 이름을 지어야 한다.
  • ex1)
      int d; // 경과 시간(단위: 날짜)
    • 대신 다음과 같이 쓰는 것이 조금 더 명확하다.
      int elapsedTimeInDays;
      int daysSinceCreation;
      int daysSinceModification;
      int fileAgeInDays;
  • ex2)
      public List<int[]> getThem() {
          List<int[]> list1 = new ArrayList<int[]>();
          for (int[] x : theList)
              if (x[0] == 4)
                  list1.add(x);
          return list1;
      }
    • 위의 코드는 독자가 다음 정보를 안다고 가정.
    1. theList에 무엇이 들었는지?
    2. theList에서 0번째 값이 어째서 중요한가?
    3. 값 4는 무슨 의미인가?
    4. 함수가 반환하는 리스트 list1을 어떻게 사용하는가?
    • 이러한 정보를 코드에 직접 담는 것이 더 바람직하다.
    • (위의 코드는 사실 지뢰찾기. theList는 게임판. 값 4는 깃발이 꽂힌 상태. 배열에서 0번째 값은 칸 상태)
      public List<int[]> getFlaggedCells() {
        List<int[]> flaggedCells = new ArrayList<int[]>();
        for (int[] cell : gameBoard)
            if (cell[STATUS_VALUE] == FLAGGED)
                flaggedCells.add(cell);
        return flaggedCells;
      }
    • int 배열을 사용하지 않고 칸을 간단한 클래스로 만들어도 된다.
    • isFlagged라는 명시적 함수를 이용해서 FLAGGED라는 상수를 감출 수 있다.
      public List<Cell> getFlaggedCells() {
        List<Cell> flaggedCells = new ArrayList<Cell>();
        for (Cell cell : gameBoard)
            if (cell.isFlagged())
                flaggedCells.add(cell);
        return flaggedCells;
      }

그릇된 정보를 피하라

  • 널리 쓰이는 의미가 있는 단어를 다른 의미로 쓰지 말라
    • ex) hp, aix, sco는 유닉스 플랫폼이나 유닉스 변종을 가리키는 이름이기 때문에 이를 사용하면 혼동을 줄 수 있다.
  • 특별한 의미가 있는 단어를 그 의미가 없는데 사용하지 말아라
    • 프로그래머에게 List는 자료 구조에서 중요한 의미를 갖는다.
    • 계정을 담는 컨테이너가 실제 List가 아닌데 accountList라는 이름을 사용하는 것은 잘못된 정보를 전달하는 것
      • accountGroup, bunchOfAccounts, Accounts등으로 고치자
  • 서로 흡사한 이름을 사용하지 말아라
    • XYZControllerForEfficientHandlingOfStrings와 XYZControllerForEfficientStorageOfStrings
    • 헷갈리게 하지 말자
  • 유사한 개념은 유사한 표기법을 사용한다.(일관성)
    • IDE는 코드 자동 완성 기능을 제공한다. 여기서 뜨는 후보 목록에 유사한 개념이 뜨고, 그 개념 차이가 명백히 드러난다면 코드 자동 완성 기능은 유용해진다.
    • 즉 일관성을 가지면서 구분이 명확히 될 수 있는 이름을 짓자.
  • 구분되기 힘든 알파벳 사용에 주의하자.
    • ex) 소문자 L이나 대문자 O 변수
        int a = l;
        if ( O == l )
        a = O1;
        else
        l = 01;

의미 있게 구분하라

  • 다른 두 개념에 같은 이름을 붙이면 동일한 scope내에서는 컴파일 되지 않는다.
    • 그냥 연속적인 숫자를 덧붙이거나 불용어(noise word)를 붙여서 컴파일 되게 넘어가고 싶은 생각이 들 수 있다.
    • 그러면 컴파일은 되겠지만 이름이 아무 정보를 주지 못하기 때문에 후에 유지, 보수에 어려움을 겪게 될 것이다.
  • 연속적인 숫자를 덧붙인 이름(a1, a2, ... , aN)은 의도적인 이름과 정반대이다.
    • ex)
        public static void copyChars(char a1[], char a2[]) {
            for (int i = 0; i < a1.length; i++) {
                a2[i] = a1[i];
            }
        }
      • 함수 인수 이름으로 source와 destination을 사용하면 코드 읽기가 훨씬 더 쉬워질 것.
  • 불용어를 추가한 이름도 마찬가지
    • Product라는 클래스가 있을 때 다른 클래스를 ProductInfo 또는 ProductData라고 부르면 개념을 구분하지 않은 채 이름만 바꾼 것이라 할 수 있다.
      • Info, Data는 a, an, the와 마찬가지로 의미가 불분명한 불용어이다.
    • a, the 같은 접두어를 사용하지 말라는 것이 아니라 의미가 분명히 다를 때만 사용하라는 것이다.
      • ex) 모든 지역 변수는 a를 사용하고 모든 함수 인수는 the를 사용하기로 한 경우는 명확히 구분된다.
        • 하지만 zork라는 변수가 있다는 이유만으로 theZork라고 이름 지어서는 안된다는 말이다.
    • 불용어는 중복이다.
      • 변수 이름에 variable이라는 단어는 쓸데없다.
      • 표 이름에 table이라는 단어도 마찬가지.
      • NameString에서 Name은 당연히 String일 것이므로 String은 쓸데없다.
      • 코드에 Customer 클래스와 CustomerObject 클래스를 발견한다면 대체 무슨 차이를 찾을 수 있을까
      • ex)
          getActiveAccount();
          getActiveAccounts();
          getActiveAccountInfo();
        • 세 개의 함수가 있다면 대체 뭘 호출해야 할지 구분할 수 있을까
      • moneyAmount는 money와 구분이 되지 않는다.
      • customerInfo는 customer와, accountData는 account와, theMessage는 message와 구분이 되지 않는다.

발음하기 쉬운 이름을 사용하라

  • 발음하기 쉬운 단어가 기억하기도 쉽고 소통하기도 쉽다.
  • ex) genymdhms (generate date, year, month, day, hour, minute, second)라는 단어는 발음하기 어렵.
      class DtaRcrd102 {
          private Date genymdhms;
          private Date modymdhms;
          private final String pszquint = "102";
          /* ... */
      };
    • 위의 코드보다는 아래 코드가 더 이해하고 소통하기 쉬울 것이다.
      class Customer {
        private Date generationTimestamp;
        private Date modificationTimestamp;
        private fianl String recordId = "102";
        /* ... */
      };

검색하기 쉬운 이름을 사용하라

  • 문자 하나만 사용하는 이름이나 상수는 텍스트 코드에서 쉽게 찾기 어렵다.
    • ex) grep으로 MAX_CLASSES_PER_STUDENT를 찾는 것과 7을 찾는 것을 생각해보면 된다.
      • 7이 들어가는 파일 이름이나 수식이 모조리 검색될 것이다.
      • 검색으로 찾았더라도 7을 사용한 의도가 다를 수도 있다.
      • 상수가 여러 자리 숫자이고 누군가 상수 내 숫자 위치를 바꿨다면 상수에 버그가 있지만 검색으로 찾지 못한다.
        • 12345671234576으로 바꾸면 분명 있는데 코드에서는 오류가 나는 상황..?(확실치 않음)
    • ex2) e도 문자 변수 이름으로 적합하지 않음
  • 이름 길이는 범위 크기에 비례해야 한다.
    • 간단한 메소드에서 로컬 변수만 한 문자를 사용한다.
    • 변수나 상수를 코드 여러 곳에서 사용한다면 검색하기 쉬운 이름이 바람직하다.
      • (추측) 여러 곳에서 사용될 수록 코드를 고칠 때 해당 변수나 상수를 찾아서 고쳐야 될 때 검색을 통해 다른 변수나 상수와 쉽게 구분될 수 있어야 하기 때문?
        • 범위가 작으면 검색 없이 범위 안에서 직접 찾을 수 있다.
    • ex)
        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;
        }

인코딩을 피하라

  • 굳이 추가하지 않아도 이름에 인코딩할 정보는 많다.
    • 유형이나 범위 정보까지 인코딩에 넣으면 해독하기 어려워진다.

      헝가리식 표기법

  • 이름 길이가 제한된 언어를 사용하던 옛날에 어쩔 수 없이 사용하던 방법
    • 타입을 변수명에 넣어주는 것
  • 요즘은 컴파일러가 타입을 체크 및 강제해주고, 클래스와 함수는 점차 작아져서 변수를 선언한 위치와 사용하는 위치가 멀지 않으므로(타입을 쉽게 알 수 있으므로) 굳이 쓸 이유가 없다.
  • 특히 자바는 강타입이므로 더더욱..
  • 오히려 변수, 함수, 클래스 이름이나 타입을 바꾸기 어려워지고 읽기도 어려워진다. 또 독자를 오도할 가능성도 커진다.
      PhoneNumber phoneString;
      // 타입이 바뀌어도 이름은 바뀌지 않는다.

    멤버 변수 접두어

  • 멤버 변수에 m_이라는 접두어를 붙이는 것.
  • 이제는 그럴 필요가 없다.
  • 클래스와 함수는 접두어가 필요없을 정도로 작아야 마땅하다.
  • 또한 멤버 변수를 다른 색상으로 표시하거나 눈에 띄게 보여주는 IDE를 사용해야 마땅하다.
  • ex)
      public class Part {
          private String m_dsc; // 설명 문자열
          void setName(String name) {
              m_dsc = name;
          }
      }
    • 대신 다음이 더 적합
      public class Part {
        String description;
        void setDescription(String description) {
            this.description = description;
        }
      }
  • 또 사람들이 접두어(또는 접미어)에 익숙해지고 해당 정보는 자연스레 무시하게 되므로 더더욱 의미가 없어진다.

    인터페이스 클래스와 구현 클래스

  • 인코딩이 필요한 경우도 있다.
    • 예를 들어 도형 생성하는 '추상 팩토리'를 구현한다고 가정.
      • 해당 팩토리는 인터페이스 클래스로 하고, 구현은 구체 클래스(concrete class)에서 한다.
      • 그러면 각각을 IShapeFactoryShapeFactory로 네이밍?
      • 저자 의견은 인터페이스에 접두어를 붙이지 않는 방향
        • 굳이 인터페이스 클래스 이름과 구현 클래스 이름 중 하나를 인코딩해야 한다면 구현 클래스 이름을 선택
        • ShapeFactoryImpCShapeFactory 같은 형식

자신의 기억력을 자랑하지 마라

  • 자신만 알 수 있는 모호한 변수를 사용하지 말자
  • 보통 문자 하나만 사용하는 변수는 문제가 있다.
    • 루프에서 반복 횟수를 세는 변수 i, j, k정도만 전통적으로 용인되는 범위
  • 명료함이 최고다

클래스 이름

  • 클래스 이름과 객체 이름은 명사나 명사구가 적합하다.
  • ex) Customer, WikiPage, Account, AddressParser
  • Manager, Processor, Data, Info 등과 같은 단어는 피하고, 동사는 사용하지 않는다.

메소드 이름

  • 동사나 동사구가 적합.
  • ex) postPayment, deletePage, save
  • 접근자(Accessor), 변경자(Mutator), 조건자(Predicate)는 javabean 표준에 따라 값 앖에 get, set, is를 붙인다.
      string name = employee.getName();
      customer.setName("mike");
      if (paycheck.isPosted())...
  • 생성자(Constructor)를 중복정의(overload)할 때는 정적 팩토리 메소드를 사용
      Complex fulcrumPoint = Complex.FromRealNumber(23.0);
    • 아래 코드보다는 위의 코드가 낫다.
      Complex fulcrumPoint = new Complex(23.0);
    • 만약 여기서 생성자 사용을 제한하고 싶다면 해당 생성자를 private으로 선언하자.

기발한 이름은 피하라

  • 괜히 농담 쓰는 것보다 명료한 이름을 쓰자.

한 개념에 한 단어를 사용하라

  • 추상적인 개념 하나에 단어 하나를 선택해서 이를 고수하자
    • 예를 들어서 똑같은 메소드를 클래스마다 fetch, retrieve, get으로 제각기 부르면 혼란스럽다.
    • 그렇게 되면 어떤 용어를 썼는지 알기 위해 라이브러리를 작성한 회사나 그룹이나 개인을 기억해야 한다.
  • 이클립스, 인텔리제이 등에서는 문맥에 맞는 단서를 제공.
    • 예를 들어 객체를 사용하면 객체가 제공하는 메소드 목록을 보여주지만 목록은 보통 함수 이름과 매개변수만 보여주고 주석은 보여주지 않는다. 운이 좋아야 매개변수 이름을 보여준다.
    • 그러니 메소드 이름은 독자적이고 일관적이으로 지어야 프로그래머가 주석을 뒤지지 않고도 올바른 메소드를 선택할 수 있다.
  • 마찬가지로 동일 코드 기반에 controller, manager, driver를 섞어 쓰면 DeviceManager와 ProtocolController는 근본적으로 어떻게 다른지 설명하기 어려워진다.

말장난을 하지 마라

  • 한 단어를 두 가지 목적으로 사용하지 마라. 다른 개념에 같은 단어를 사용하면 그것은 말장난에 불과하다.
  • "한 개념에 한 단어를 사용하라"는 규칙을 따르니 여러 클래스에 add라는 메소드가 생겼다.
    • 모든 add 메소드의 매개변수와 반환값이 의미적으로 똑같으면 문제가 없다.
    • 하지만 같은 맥락이 아닌데 '일관성'을 고려한답시고 add라는 단어를 선택하면 다른 개념에 같은 단어를 사용한 것이 될 수 있다.
      • 기존의 add 메소드는 모두 기존 값 두 개를 더하거나 이어서 새로운 값을 만드는데, 새로 작성하는 메소드는 집합에 값 하나를 추가한다면 기존 add 메소드와 맥락이 다르므로 insert나 append라는 이름이 적당하다.

해법 영역에서 가져온 이름을 사용하라

  • 코드를 읽을 사람도 프로그래머다.
    • 그러니 전산 용어, 알고리즘 이름, 패턴 이름, 수학 용어 등을 사용해도 좋다.
  • 모든 이름을 문제 영역에서 가져오는 정책은 같은 개념을 다른 이름으로 이해하던 동료들이 매번 고객에게 의미를 물어야 하기 때문에 좋지 못하다.
  • VISITOR 패턴에 친숙한 프로그래머는 AccountVisitor라는 이름을 금방 이해하고, JobQueue는 모든 프로그래머가 알 것이다.
  • 기술 개념에는 기술 이름이 가장 적합하다.

문제 영역에서 가져온 이름을 사용하라

  • 적절한 '프로그래머 용어'가 없다면 문제 영역에서 이름을 가져와라.
    • 그리 하면 코드를 유지/보수하는 프로그래머가 분야 전문가에게 의미를 물어서 파악할 수 있다.
  • 우수한 프로그래머와 설계자라면 해법 영역과 문제 영역을 잘 구분해서 문제 영역 개념과 관련이 깊은 코드라면 문제 영역에서 이름을 가져와야 한다.

의미 있는 맥락을 추가하라

  • 스스로 의미가 분명한 이름이 있긴 하지만 대다수 이름은 그렇지 못하다.

  • 그래서 클래스, 함수, 이름 공간 안에 속하도록 해서 맥락을 부여한다.

  • 그것조차 실패하면 마지막 수단으로 접두어를 붙인다.

    • 예를 들어 firstName, lastName, street, houseNumber, city, state, zipcode라는 변수가 있다면 훑어보면 state가 주소라는 사실을 알아챌 수 있다.
    • 하지만 어느 메소드가 state라는 변수 하나만 사용한다면 변수 state가 주소 일부라는 사실을 금방 알아채기 어렵다.
    • 그런 경우 addr라는 접두어를 추가해서 addrFirstName, addrLastName, addrState라 쓰면 맥락이 조금 더 분명해진다.
      • 적어도 변수가 조금 더 큰 구조에 속한다는 사실이 명확해진다.
      • 물론 Address라는 클래스를 생성하면 더 좋다.
        • 그러면 변수가 조금 더 큰 개념에 속한다는 사실이 컴파일러에도 분명해진다.
  • ex)

    • 다음 예시에서 변수에 대한 맥락은 함수 이름이 일부분만 제공하고 나머지는 알고리즘이 제공한다. 따라서 함수를 끝까지 읽어봐야 number, verb, pluralModifier라는 변수 세 개가 '통계 추측' 메시지에 사용된다는 것을 알 수 있다. 이는 독자가 맥락을 유추해야만 한다.

        private void printGuessStatistics(char candidate, int count) {
            String number;
            String verb;
            String pluralModifier;
      
            if (count == 0) {
                number = "no";
                verb = "are";
                pluralModifier = "s";
            } else if (count == 1) {
                number = "1";
                verb = "is";
                pluralModifier = "";
            } else {
                number = Integer.toString(count);
                verb = "are";
                pluralModifier = "s";
            }
            String guessMessage = String.format(
                "There %s %s %s%s", verb, number, candidate, pluralModifier
            );
            print(guessMessage);
        }
      • 위의 메소드는 일단 좀 길고, 세 변수를 함수 전반에서 사용한다.

      • 대신 GuessStatisticsMessage라는 클래스를 만들고 세 변수를 클래스에 넣으면 맥락이 분명해진다.

        • 즉, 세 변수는 확실히 GuessStaticsMessage에 속한다.

        • 이렇게 맥락을 개선하면 함수를 쪼개기 쉬워지므로 알고리즘도 조금 더 명확해진다.

          public class GuessStatisticsMessage {
          private String number;
          private String verb;
          private String pluralModifier;
          
          public String make(char candidate, int count) {
            createPluralDependentMessageParts(count);
            return String.format(
                "There %s %s %s%s",
                    verb, number, candidate, pluralModifier );
          }
          
          private void createPluralDependentMessageParts(int count) {
            if (count == 0) {
                thereAreNoLetters();
            } else if (count == 1) {
                thereIsOneLetter();
            } else {
                thereAreManyLetters(count);
            }
          }
          
          private void thereAreManyLetters(int count) {
            number = Integer.toString(count);
            verb = "are";
            pluralModifier = "s";
          }
          
          private void thereAreNoLetters() {
            number = "no";
            verb = "are";
            pluralModifier = "s";
          }
          }

불필요한 맥락을 없애라

  • 예를 들어 '고급 휘발유 충전소(Gas Station Deluxe)'라는 애플리케이션을 짠다 했을 때 모든 클래스 이름을 GSD로 시작하겠다는 생각은 바람직하지 않다.
    • 오히려 IDE에서 G를 입력하고 자동 완성 키를 누르면 IDE는 모든 클래스를 열거한다.
  • 또 비슷하게 GSD 회계 모듈에 MailingAddress 클래스를 추가하면서 GSDAccountAddress로 이름을 바꿨다고 하자.
    • 나중에 다른 고객 관리 프로그램에서 고객 주소가 필요하면 GSDAccountAddress클래스는 부적절한 이름이 된다.
    • 의미가 분명한 경우에 한해서 짧은 이름이 긴 이름보다 좋다. 이름에 불필요한 맥락을 추가하지 않도록 주의하자.
    • accountAddress와 customerAddress는 Address 클래스 인스턴스로는 좋은 이름이지만 클래스 이름으로는 적합하지 못하다.
    • Address는 클래스 이름으로 적합하다.
  • 포트 주소, MAC 주소, 웹 주소를 구분해야 한다면 PostalAddress, MAC, URI가 의미가 조금 더 분명할 것이다.

마치면서

  • 좋은 이름을 선택하려면 설명 능력이 뛰어나야 하고 문화적인 배경이 같아야 한다.
  • 또 다른 개발자가 반대할까봐 이름을 바꾸지 않으려고 생각하는 사람들이 많지만 조금 다르게 생각해보자.
    • 어차피 암기는 컴퓨터가 하는 것이고 프로그래머는 코드를 짜는 데만 집중해야 한다.

'클린코드' 카테고리의 다른 글

6. 객체와 자료구조  (0) 2021.10.10
5. 형식 맞추기  (0) 2021.10.06
4. 주석  (0) 2021.09.27
3. 함수  (0) 2021.09.23
1. 깨끗한 코드  (0) 2021.09.14