[Clean Code] Chapter 2. 의미 있는 이름

joyful·2024년 7월 2일
0

개발서적

목록 보기
2/9

들어가기 앞서

이 글은 개발자 필독서인 클린 코드를 읽으며 습득한 내용을 정리한 글입니다. 모든 출처는 해당 저서에 있습니다.


1. 의도를 분명히 밝혀라

  • 의도가 분명한 이름은 중요하다.

  • 좋은 이름을 짓는 데 걸리는 시간보다 앞으로 절약할 수 있는 시간이 더 많다.

  • 변수•함수•클래스 이름을 지을 때 고려해야 할 사항들은 다음과 같다.

    • 존재 이유
    • 수행 기능
    • 사용 방법
  • 주석이 필요하다면 의도를 분명히 드러내지 못한 것이다.

    • 나쁜 예
      /* 아무런 의미가 드러나지 않음 */
      int d;	// 경과 시간(단위: 날짜 수)
    • 좋은 예
      /* 측정하려는 값과 단위를 표현 */
      int elapsedTimeInDays;
  • 의도가 드러나는 이름을 사용하면 코드 이해와 변경이 쉬워진다.

    • 예시 : 지뢰찾기 게임
      • 나쁜 예
        /* 코드가 하는 일을 짐작하기 어려움 */
        public List<int[]> getThem() {
        		  List<int[]> list1 = new ArrayList<>();
            // theList에 무엇이 들었는가?
            for (int[] x : theList) {
          	  // theList에서 0번째 값이 어째서 중요한가? 값 4는 무슨 의미인가?
          	  if (x[0] == 4) {
              	  list1.add(x);
                }
            }
            return list1;	// 함수가 반환하는 리스트 list1을 어떻게 사용하는가?
        }
        💡 코드 맥락이 코드 자체에 명시적으로 드러나지 않음(코드의 함축성)
      • 좋은 예 1 - 개념에 이름을 붙임
        /**
         * 게임판 : theList → gameBoard
         * 0번째 값 : 상태
         * 값 4 : 깃발이 꽂힌 상태 
         */
        public List<int[]> getFlaggedCells() {
        		  List<int[]> flaggedCells = new ArrayList<int[]>();
            for (int[] cell : gameBoard) {
          	  if (cell[STATUS_VALUE] == FLAGGED) {
              	  flaggedCells.add(cell);
                }
            }
            return flaggedCells;
        }
        💡 코드가 더욱 명확해짐
      • 좋은 예 2 - 예시 1을 개선
        /**
         * 배열 대신 클래스 사용(int 배열 → Cell 클래스)
         * 상수 대신 함수 사용(FLAGGED → isFlagged)
         
        public List<Cell> getFlaggedCells() {
        	List<Cell> flaggedCells = new ArrayList<Cell>();
          for (Cell cell : gameBoard) {
          	if (cell.isFlagged()) {
              	flaggedCells.add(cell);
              }
          }
          return flaggedCells;
        }
        💡 함수가 하는 일을 이해하기 쉬워짐



2. 그릇된 정보를 피하라

코드에 그릇된 단서를 남겨서는 안 된다. 그릇된 단서는 코드 의미를 흐린다.

  • 널리 쓰이는 의미가 있는 단어를 다른 의미로 사용하면 안 된다.
    ex) hp : 유닉스 플랫폼/변종을 가리키는 이름 → 변수 이름으로 적합하지 않음
  • 여러 계정을 그룹으로 묶을 때 실제로 List가 아니라면 List가 들어간 이름으로 명명하지 않는다.
    ex) accountList → accountGroup / bunchOfAccounts / Accounts
  • 서로 흡사한 이름을 사용하지 않도록 주의한다.
    ex) XYZControllerForEfficientHandlingOfStringsXYZControllerForEfficientStorageOfStrings
  • 유사한 개념은 유사한 표기법을 사용한다.
    • 일관성이 떨어지는 표기법은 그릇된 정보다.

✅ 이름을 그릇된 정보를 제공하는 예

  • 소문자 L과 대문자 O 변수 사용
    int a = l;
    if (O == l) {
    	  a = 01;
    } else {
    		  l = 01;
    }
    → 소문자 L은 숫자 1로, 대문자 O는 숫자 0으로 보임



3. 의미 있게 구분하라

이름이 달라야 한다면 의미도 달라져야 한다. 읽는 사람이 차이를 알도록 이름을 지어라.

  • 연속적인 숫자를 덧붙인 이름은 어떠한 정보도 제공하지 못하며, 저자 의도가 전혀 드러나지 않는다.
    • 수정 전
    public static void copyChars(char a1[], char a2[]) {
    	for (int i = 0; i < a1.length; i++) {
      	a2[i] = a1[i];
      }
    }
    • 수정 후
    public static void copyChars(char source[], char destination[]) {
    	for (int i = 0; i < source.length; i++) {
      	destination[i] = source[i];
      }
    }
  • 불용어를 추가한 이름 역시 의미가 불분명한 경우 아무런 정보도 제공하지 못한다.
    ex) 접두어(a, an, th) 및 Info, Data 등
    • 의미가 다르다면 사용해도 무방하다.
      ex) 지역변수 → a, 함수 인수 → the
    • 중복의 의미를 가진 불용어를 사용해서는 안 된다.
      ex) 변수 → xxxVariable, 표 → xxxTable 등



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

  • 사람들은 단어에 익숙하며, 우리 두뇌에서 상당 부분은 단어라는 개념만 전적으로 처리한다. 정의상으로 단어는 발음이 가능하다.
  • 발음하기 쉬운 이름은 중요하다.
    • 프로그래밍은 사회적 활동이다.
    • 발음하기 어려운 이름은 토론하기 어려우며, 바보처럼 들리기 십상이다.



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

문자 하나를 사용하는 이름과 상수는 텍스트 코드에서 쉽게 눈에 띄지 않는다.

✅ 숫자

  • 해당 숫자가 들어가는 파일 이름이나 수식이 모두 검색된다.
  • 해당 숫자를 사용한 의도가 다른 경우도 존재한다.
  • 상수가 여러 자리 숫자이고, 누군가 상수 내 숫자 위치를 바꾼다면 버그가 있어도 검색으로 찾아내지 못하게 된다

✅ 문자

  • 많이 쓰이는 문자일수록 검색하기 어렵다.
  • 검색하기 쉬운 이름이 상수보다 낫다.
    • 거의 모든 프로그램, 문장에 등장하는 문자라면 차라리 긴 이름(의미 있는 이름)이 낫다.



6. 인코딩을 피하라

  • 유형이나 범위 정보까지 인코딩에 넣으면 이름을 해독하기 어려워진다.
  • 문제 해결에 집중하는 개발자에게 인코딩은 불필요한 정신적 부담이다.
  • 인코딩한 이름은 발음하기 어려우며 오타가 생기기 쉽다.

6.1 헝가리식 표기법

  • 현대 언어는 과거에 비해 훨씬 많은 타입을 지원하며, 컴파일러가 타입을 기억하고 강제한다.
  • 클래스와 함수는 점차 작아지는 추세로, 변수를 선언한 위치와 사용하는 위치가 멀지 않다.
  • 자바 프로그래머의 경우, 변수 이름에 타입을 인코딩할 필요가 없다.
    • 객체는 강한 타입이며, 현대적인 IDE는 코드를 컴파일하지 않고도 타입 오류를 감지할 정도로 발전했다.
    • 인코딩은 이름 및 타입을 변경하는 것이 어려워지고, 읽기도 어려워질 뿐만 아니라 독자를 오도할 가능성도 커진다.

6.2 멤버 변수 접두어

  • 클래스와 함수는 접두어가 필요없을 정도로 작아야 한다.
  • 멤버 변수를 다른 색상으로 표시하거나 눈에 띄게 보여주는 IDE를 사용해라.
  • 사람들은 접두어(또는 접미어)를 무시하고 이름을 해독하는 방식과 친밀하다.

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

  • 인터페이스 이름은 접두어를 붙이지 않도록 한다.
    • 접두어는 주의를 흩트리고 과도한 정보를 제공한다.
  • 차라리 구현 클래스 이름을 인코딩한다.



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

전문가 프로그래머는 자신의 능력을 좋은 방향으로 사용해 남들이 이해하는 코드를 내놓는다.

  • 문제 영역이나 해법 영역에서 사용하는 이름을 선택해야 한다.
    • 독자가 코드를 읽으며 변수 이름을 자신이 아는 이름으로 변환하는 일이 없도록 해야 한다.
  • 문자 하나를 사용하는 변수 이름은 되도록이면 사용하지 않는다.
    • 단, 루프 범위가 아주 작고 다른 이름과 충돌하지 않을 경우, 반복 횟수를 세는 변수는 괜찮다.
      ex) i, j, k



8. 클래스 이름

  • 클래스 이름과 객체 이름은 명사나 명사구를 사용한다.
    ex) Customer, WikiPage, Account, AddressParser 등
  • 애매모호한 단어는 피한다.
    ex) Manager, Processor, Data, Info 등
  • 동사는 사용하지 않는다.



9. 메소드 이름

  • 메소드 이름은 동사나 동사구를 사용한다.
    ex) postPayment, deletePage, save 등

  • 접근자, 변경자, 조건자는 자바 빈 표준에 따라 값 앞에 get, set, is를 붙인다.

    String name = employee.getName();
    customer.setName("mike");
    if (paycheck.isPosted()) { ... }
  • 생성자를 중복해 정의할 때(overload)는 정적 팩토리 메소드를 사용한다.

    • 메소드는 인수를 설명하는 이름을 사용한다.
    • 생성자를 private으로 선언하여 생성자 사용을 제한하려면 해당 생성자를 private으로 선언한다.
    // 좋은 예
    Complex fulcrumPoint = Complex.FromRealNumber(23.0);
    
    // 나쁜 예
    Complex fulcrumPoint = new Complex(23.0);



10. 기발한 이름은 피하라

의도를 분명하고 솔직하게 표현해야 한다.

  • 재미난 이름보다 명확한 이름을 선택한다.
    ex) HolyHandGernade → DeleteItems
  • 특정 문화에서만 사용하는 농담은 피하는 편이 좋다. 구어체나 속어를 이름으로 사용하지 않도록 한다.
    ex) whack() → kill(), eatMyShort() → abort()



11. 개념 하나에 단어 하나를 사용하라

  • 추상적인 개념 하나에 단어 하나를 선택하여 이를 고수한다.
  • 메소드 이름은 독자적이고 일관적이어야 한다.
    • 주석을 뒤져보지 않고도 올바른 메소드를 선택할 수 있다.
    • 이름이 다르면 독자는 클래스와 타입이 다르리라 생각하게 된다.



12. 말장난을 하지 마라

  • 한 단어를 두 가지 목적으로 사용하지 않는다.
    • 같은 맥락이 아닌데도 일관성 때문에 같은 단어를 선택하지 않도록 한다.
      ex) 기존의 메소드와 맥락이 다른 경우
      값 두 개를 더하거나 이어서 새로운 값을 만드는 메소드 → add
      집합에 값 하나를 추가하는 메소드 → insert 혹은 append



13. 해법 영역에서 사용하는 이름을 사용하라

  • 코드를 읽는 사람도 프로그래머다. 전산 용어, 알고리즘 이름, 패턴 이름, 수학 용어 등을 사용해도 괜찮다.
    • 문제 영역에서 모든 이름을 가져오면 같은 개념을 다른 이름으로 이해하게 된다.
  • 기술적인 개념에는 기술적인 이름을 선택하도록 한다.



14. 문제 영역과 관련 있는 이름을 사용하라

  • 적절한 프로그래머 용어가 없을 경우 문제 영역에서 이름을 가져온다.
  • 문제 영역 개념과 관련 깊은 코드라면 문제 영역에서 이름을 가져오도록 한다.



15. 의미 있는 맥락을 추가하라

  • 의미가 불분명한 이름을 가진 클래스, 함수, 변수에 맥락을 부여하는 경우, 마지막 수단으로 접두어를 붙인다.

✅ 예시 1 - 주소

firstName → addrFirstName
lastName → addrLastName
state → addrState

  • 접두어를 추가하여 변수가 좀더 큰 구조에 속한다는 사실을 독자에게 전할 수 있다.
  • 클래스를 생성하여 독자뿐만 아니라 컴파일러에게도 좀더 큰 개념에 속한다는 사실을 전할 수 있다.

✅ 예시 2 - 통계 추측 메시지

  • 맥락이 불분명한 변수

    private void printGuessStaticstics(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);
    }
    • 독자가 맥락을 유추해야만 하며, 메소드만 훑어서는 number, verb, pluralModifier라는 변수의 의미가 불분명하다.
  • 맥락이 분명한 변수

    public class GuessStatisticMessage {
    	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) {
                therAreNoLetters();
            } else if (count == 1) {
                thereIsOneLetter();
            } else {
                thereAreManyLetters(count);
            }
        }
    
        private void thereAreManyLetters(int count) {
            number = Integer.toString(count);
            verb = "are";
            pluralModifier = "s";
        }
    
        private void thereIsOneLetter() {
            number = "1";
            verb = "is";
            pluralModifier = "";
        }
    
        private void thereAreNoLetters() {
            numberr = "no";
            verb = "are";
            pluralModifier = "s";
        }
    }
    • 함수를 작은 조각으로 쪼개고자 GuessStatisticsMessage라는 클래스를 만들어 세 변수를 클래스에 넣음으로써 세 변수의 맥락이 분명해졌다.
    • 맥락을 개선하면 함수를 쪼개기가 쉬워져서 알고리즘이 좀더 명확해진다.



16. 불필요한 맥락을 없애라

  • 의미가 분명한 경우에 한해 짧은 이름이 긴 이름보다 낫다. 중복 등 불필요한 맥락을 추가하지 않도록 주의한다.

    • 예시 1) 클래스 이름
      xxxAddress(x), Address(o)

    • 예시 2) 주소 구분
      MAC 주소 : MAC
      포트 주소 : PortAddress
      웹 주소 : URI


📖 참고

  • 로버트 C. 마틴, 『Clean Code 클린 코드 애자일 소프트웨어 장인 정신』, 박재호·이해영 옮김, 케이앤피북스(2010), p53-69.
profile
기쁘게 코딩하고 싶은 백엔드 개발자

0개의 댓글