Dev/Books

[CleanCode] 2장. 의미있는 이름

kyeoneee 2020. 5. 2. 17:51
반응형

의미있는 이름

목차


의도를 분명히 밝혀라

이름을 지을 때 아래의 질문들을 고려해야 한다.

  • 변수(혹은 함수나 클래스)의 존재 이유는?
  • 수행 기능은?
  • 사용 방법은?

Bad

public List<int[]> getThem() {
  List<int[]> list1 = new ArrayList<int[]>();
  for (int[] x : theList)
    if (x[0] == 4)
        list1.add(x);
  return list1;
}

복잡한 코드는 아니지만 해당 메서드가 무엇을(them) 가져오는 것인지, List에 담기는 int[] 배열은 무슨 데이터인지, 4는 무슨 의미를 가지는지를 알 수 없다. 이 말은 곧 각 변수들의 정보를 모르는 경우 코드의 맥락을 완전히 이해 할 수 없다는 뜻이다.
각 변수들의 정보를 모르더라도 이해할 수 있도록 아래와 같이 수정할 수 있다.

Good

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

 

그릇된 정보를 피하라

  • 약어를 지양하자
  • 여러 계정을 그룹으로 묶을 때 List와 같은 특정 컨테이너의 사용을 지양하라 (e.g., accountList -> Accounts)
  • 네이밍은 서로 비슷한 이름을 사용하지 말자
  • 유사한 개념은 유사한 표기법을 사용하자, 일관성이 떨어지는 표기법은 그릇된 정보이다.
  • 다른 글자와 혼동되는 단일 알파벳을 피하자 (e.g., 소문자 L <-> 1, 대문자 I)

 

의미 있게 구분하라

한 scope에서 동일 이름을 사용할 수 없으니 컴파일러를 통과하기 위한 무의미한 철자 변경의 오류를 범하지 않도록 해야 한다. 무의미한 철자 변경의 대표적인 두가지 예시는 아래와 같다.

  • 연속된 숫자를 덧붙이기
    Bad :

    public static void copyChars(char a1[], char a2[])

    Good :

    public static void copyChars(char souce[], char destination[])
  • 불용어(noise word) 추가
    불용어란? 없어도 의미 전달에 영향이 없는 단어

    • 접두어 사용을 조심하자
      a, an, the와 같은 접두어는 의미가 분명히 다를 때만 사용하도록 한다. (e.g., 모든 지역변수는 a 접두사 사용, 모든 함수 인수는 the 접두사 사용)
    • 불용어를 사용하여 중복을 발생시키지 말자
      product라는 클래스가 존재 할 때 ProductInfo, ProductData와 같은 개념이 구분되지 않는 클래스를 생성하여 중복을 발생시키지 않도록 한다. (e.g., Money & MoneyAccaount, CustomerInfo & Customer, AccountData & Account)

 

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

발음하기 어려운 단어는 커뮤니케이션에도 영향을 끼친다. 이는 실제 존재하는 단어를 사용하는 것만으로도 해결이 된다.

Bad :

아 래의 변수는 generate date, year, month, day, hour, minute, second 의 줄임말이다.

Date genymdhms

Good :

Date generationTimetamp

 

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

  • 상수
    상수값에 버그가 있을 경우 검색으로 찾아낼 수 없다. 그러나 상수값의 의미를 나타내는 변수로 정의하여 사용하면 검색에 용의하다.

  • 변수
    알파벳 하나를 변수로 사용하면 검색이 어렵다.

Bad :

int s = 0;
for (int i = 0; i < 30; i++) 
  s += i * 4;

Good :

int maxIndex = 30;
const int MULTIPLICATION_CONDITION = 4;
int sum = 0;
for (int i = 0; i < maxIndex; i++) // 하나의 메서드에서만 쓰이며, 다시 쓰이지 않는다면 한 문자 변수도 나쁘지 않다
  sum += i * MULTIPLICATION_CONDITION;

 

인코딩을 피하라

  • 헝가리식 표기법을 지양하라
    과거 프로그래밍 언어는 변수 및 함수의 인자 이름 앞에 데이터 타입을 명시하는 헝가리식 표기법을 사용하였으나 현대의 프로그래밍 언어는 많은 컴파일러가 타입을 기억하고 강제하므로 네이밍 시 타입을 직접 명시하는 것을 피하는 것이 좋다. 오히려 아래와 같은 문제가 발생 할 수 있다.

    PhoneNumber phoneString; // 이러한 경우 타입이 바뀌게 되었을 때 변수명도 바꿔주어야 하는 문제가 생긴다.
  • 멤버 변수 접두어
    멤버 변수임을 명시하기 위해 "m_" 접두어를 붙이는 것을 지양하고 클래승와 함수는 접두어가 필요없을 정도로 작게 구현해야 한다.

  • 인코딩이 필요한 경우?
    Abstract Factory를 구현하는 경우, 인터페이스 클래스 이름보다는 구현 클래스의 이름을 인코딩 하는 것이 좋다.
    Bad :

    public interface IShapeFactory;  // 인터페이스 클래스
    public class ShapeFactory;  // 구현 클래스

    Good :

    public interface ShapeFactory;  // 인터페이스 클래스
    public class ShapeFactoryImp;  // 구현 클래스
    public class CShapeFactory;  // 구현 클래스

 

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

  • 하나의 문자만 사용하는 것을 지양하라
    • 루프에서 반복 횟수 변수는 전통적으로 한 글자만을 사용
      보통 i, j, k를 사용 (소문자 l은 대문자 I와 헷갈리므로 절대 안됨!)
    • 이외에는 대부분 적절하지 않다
  • 나만 이해할 수 있는 변수명이 아닌 모두가 이해할 수 있는 명료한 네이밍을 지향하자

 

클래스 이름

  • 동사가 들어가지 않는 명사/명사구가 적합하다.
  • 불용어를 지양하자 (e.g., Manager, Processor, Data, Info)

Good :

public class Customer
public class WikiPage
public class AddressParser

 

메서드 이름

  • 동사/동사구가 적합하다.

  • 접근자(Accessor), 변경자(Mutator), 조건자(Predicate)는 javabean 표준에 따라 get, set, is를 붙인다.
    e.g.,

    String name = employee.getName();
    customer.setName("mike");
    if (paycheck.isPosted());
  • 생성자의 중복정의 시 정적 팩토리 메서드를 사용한다. (이때, 생성자 사용을 제한하려면 해당 생성자를 private으로 사용한다.)

    • 해당 내용의 상세한 특징은 Effective Java 3/E 기준 Item1을 참고한다.

    Bad : 

    Complex fulcrumPoint = new Complex(23.0);

    Good : 

    Complex fulcrumPoint = Complex.FromRealNumber(23.0);

 

기발한 이름은 피하라

  • 특정 문화, 배경지식이 있어야 이해할 수 있는 이름이 아닌 명료한 이름을 선택하여 의도를 분명하고 솔직하게 표현하라

 

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

  • 똑같은 기능을 하는 메서드는 모든 클래스에서 하나의 이름으로 통일한다.
    (e.g., 각 클래스마다 fetch, retrieve, get으로 다르게 구현하지 않도록 한다.)
  • 주석이 필요하지 않도록 메서드 이름은 독자적이고, 일관적이어야 한다.
  • 동일 layer의 클래스는 하나의 이름으로 통일한다.
    (e.g., 하나의 프로젝트에서 DeviceManager, ProtocolController와 같이 같은 의미지만 다른 단어의 혼용을 피한다.)

 

말장난을 하지마라

  • 한 개념에 한 단어를 사용하라 규칙을 따르되, 일관성을 고려한다.
  • 같은 맥락일때만 같은 이름을 사용하여 코드 내용을 확인하지 않아도 기능이 보장되도록 해야 한다.

 

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

  • 모든 이름에 도메인 영역의 단어를 사용하지 않는다. 기술 개념에는 기술 이름이 가장 적합한 선택이다. (e.g., JobQueue, AccountVisitor)

 

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

  • 적절한 기술 이름이 없는 경우 또는 도메인 영역과 관련이 깊은 코드에 도메인 영역의 이름을 사용한다.
  • 우수한 프로그래머라면, 해법(기술) 영역과 문제(도메인) 영역을 구분할 줄 알고 각 영역에 알맞은 네이밍을 정해야 한다.

 

의미 있는 맥락을 추가하라

  • 네이밍은 독자적으로 문맥을 가지기 보단 클래스, 메서드, name space의 위치에 따라 맥락을 부여한다.
  • 접두어는 최후의 수단으로 붙인다.
  • 특정 문맥을 가지는 클래스를 생성하면 컴파일러에게도 문맥이 분명해진다.

Bad : 

  • 메서드의 멤버 변수로 사용될 때 어디에 어떻게 이용될지는 길고 복잡한 메서드를 다 읽어야만 이해할 수 있다.
  • 조건문으로 분기되어 변수의 값을 세팅해주지만 해당 값의 의미는 알 수 없다.
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);
}

Good : 

  • 클래스의 멤버 변수가 됨으로써 통계 추측 메세지에 사용되는 값임을 클래스의 이름만으로 알 수 있다.
  • 메서드로 분리함으로서 값을 세팅할 때 어떠한 의미를 가지는 값을 세팅하는지 명확해진다.
public class GuessStatisticsMessage {
  private String number;
  private String verb;
  private String pluralModifier;

  public String maek(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() {
    number = Integer.toString(count);
    verb = "are";
    pluralModifier = "s";
  }

  private void thereIsOneLetter() {
    number = "1";
    verb = "is";
    pluralModifier = "";
  }

  private void thereAreNoLetters() {
    number = "no";
    verb = "are";
    pluralModifier = "s";
  }
}

 

불필요한 맥락을 없애라

  • 모든 클래스를 아우르는 맥락을 굳이 접두어로 사용하지 않는다.

    • IDE에서 해당 접두어를 입력하는 순간 수많은 클래스가 열거되어 정말 필요한 클래스를 찾기 어려워진다.
    • 굳이 접두어를 붙임으로서 이름이 불필요하게 너무 길어진다. 긴 이름이 짧은 이름보다 좋지만 의미가 분명한 경우에 한해서이다.

    Bad : 

    package com.gsd;
    
    public class GsdAccountAddress

    Good : 

    package com.gsd;
    
    public class AccountAddress
  • 클래스의 이름으로 좋은 것과 인스턴스의 이름으로 좋은 것은 다르다.

 

마치면서

좋은 이름을 선택한다는 것은 결국 가독성을 높이기 위한 것


참고하면 좋을 문서

반응형

'Dev > Books' 카테고리의 다른 글

[CleanCode] 7장. 예외 처리  (0) 2020.05.16
[CleanCode] 6장. 객체와 자료 구조  (0) 2020.05.15
[CleanCode] 5장. 형식 맞추기  (0) 2020.05.12
[CleanCode] 4장. 주석  (0) 2020.05.12
[CleanCode] 3장. 함수  (0) 2020.05.06