클린 코드

Hugo Kim·2019년 10월 20일
1

클린코드

목록 보기
1/1
post-thumbnail

2장 의미있는 이름

의도를 분명히 밝혀라

이름을 지을 때 답해야하는 질문들

  • 변수의 존재 이유는?
  • 수행 기능은?
  • 사용 방법은?

주석이 필요한 코드는 코드에 의도를 분명히 드러내지 못한 코드다.

int d; // 경과 시간(날짜)

int elapsedTimeInDays;
int daysSinceCreation;
int daysSinceModification;
int fileAgeInDays;

위의 코드와 아래 코드를 비교하면 단박에 내가 이름을 어떻게 지어야 할지 감이 온다. 지금까지는 변수 명을 짓다가 적당한 이름이 없어서 너무 길어지면 의도를 알기 어렵더라도 차라리 사용하기 편한 짧고 간단한 이름을 사용했다. 편리함보다는 의도를 나타내기 위한 코드를 작성하도록 노력하자.

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를 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;
}

코드의 단순성의 정도는 크게 다르지 않지만 코드를 읽을 수 있다. 더 나아가 각 칸들을 Cell 클래스로 추상화하면 더 나은 코드를 만들 수 있다. 그리고 isFlagged라는 메서드를 만들어 FLAGGED라는 상수를 감추면 금상첨화다.

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

그릇된 정보를 피하라

그릇된 단서는 코드 의미를 흐린다. 널리 쓰이는 의미가 있는 단어를 다른 의미로 사용해도 안된다.

여러 계정을 담는 변수 이름을 지을 때, 실제 List 자료형이 아니라면 accountList라고 하면 안된다. 해당 변수가 List 자료형이라는 그릇된 정보가 드러나기 때문이다. 따라서 accountGroup, accounts, bunchOfAccounts라고 명명하자.

또한 서로 비슷한(구분하기 어려워 헷갈리는) 이름을 짓지 않아야 한다.

의미있게 구분하라

이름에 연속된 숫자를 붙이거나 불용어(noise word)를 추가하는 방식은 옳지 못한 방식이다. 이름이 달라야 한다면 의미도 달라져야 한다.

연속적인 숫자를 덧붙인 이름(a1, a2, ... aN)은 의도적인 이름이라고 할 수 없다. 이런 이름은 그릇된 정보를 제공하지 않지만 아무런 정보도 제공하지 않는다.

public static void copyChars(char a1[], char a2[]) {
  for (int i = 0; i < a1.length; i++) {
    a2[i] = a1[i];
  }
}

함수의 인수 이름으로 sourcedestination을 사용한다면 코드읽기가 더 편해질 것이다.

불용어를 추가한 이름 역시 아무런 정보도 제공하지 못한다. Product라는 클래스가 있다고 하자. 다른 클래스 이름을 ProductionInfo나 ProductData라고 부른다면 개념을 구분하지 않고 이름만 다르게 한 경우다. 읽는 사람이 차이를 알도록 이름을 짓자.

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

프로그래밍은 사회활동이다. 발음하기 쉬운 이름은 매우 중요한 사항이다. genymdhms라는 변수를 어떻게 발음할 것인가? 새로운 개발자가 들어올 때마다 해당 변수가 어떤 역할을 하는지 알려줘야할 것이다. 발음하기 쉬운 이름의 중요성을 알려주는 단적인 예시를 보자.

class DtaRcrd102 {
  private Date genymdhms;
  private Date modyymdhms;
};

class Customer {
  private Date generationTimestamp;
  private Date modificationTimestamp;
};

두 코드는 똑같은 구조를 가지고 있지만, 해당 변수가 어떤 역할을 할 지 예측이 가능해졌음을 알 수 있다.

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

문자 하나로 이루어진 이름과 상수는 코드에서 쉽게 눈에 띄지 않는다.(i, j, k, 등등...)

MAX_STUDENT는 검색으로 쉽게 찾을 수 있지만, 숫자 10은 그에 비해 까다롭다. 또한, 상수 값의 변경이 필요할 때, MAX_STUDENT의 경우엔 선언부에서 값을 수정하면 되지만, 상수로 10을 사용했다면 모든 1012로 변경할 수는 없으니 유지보수에 있어서 불리하다.

이름 길이는 변수 스코프의 크기의 비례해야 한다. 스코프가 작다면 아주 짧은 이름을 사용하더라도 괜찮다. 하지만 스코프가 크다면 검색하기 쉬운 긴 이름을 사용해야 한다.

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

독자가 코드를 읽으면서 변수 이름을 자신이 아는 이름(개념)으로 변환해야 한다면 그 변수 이름은 바람직하지 않다. 이는 일반적으로 문제 영역이나 해법 영역에서 사용하지 않는 이름을 사용했기 때문에 생기는 문제다.

문자 하나만 사용하는 변수 이름은 문제가 있다. 하지만 루프에서 반복 횟수를 세는 변수 i,j,k는 괜찮다. (l은 절대 안된다) 단, 루프 범위가 매우 작고 다른 이름과 충돌하지 않을 때만 사용 가능하다. 루프 반복 횟수 변수는 전통적으로 한 글자로 사용하기 때문이다. 그 외에는 독자가 실제 개념으로 치환해서 생각해야 하기 때문에 적절하지 못하다.

똑똑한 프로그래머와 전문가 프로그래머의 차이는 전문가 프로그래머는 명료함이 최고라는 사실을 이해한다는 것이다. 전문가 프로그래머는 자신이 가지고 있는 능력을 남들이 이해할 수 있는 코드를 만드는 것에 쓴다.

클래스 이름

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

메서드 이름

메서드 이름은 동사나 동사구가 적합하다. postPayment, deletePage 등이 좋은 예시다. 접근자, 변경자, 조건자는 javabean 표준에 따라 값 앞에 get, set, is를 붙인다.

string name = employee.getName();
customer.setName("mike");
if (payment.isPosted())...

생성자를 오버로딩할 때는 정적 팩토리 메서드를 사용한다. 메서드는 인수를 설명하는 이름을 사용한다.

Car truck = Car("truck")
Car truck = Car.Truck();

위의 코드보다 아래 코드가 더 좋다. 생성자 사용을 제한하려면 해당 생성자를 private으로 선언해줘야 한다.

기발한 이름은 피해라.

특정 집단만 이해할 수 있는 이름은 좋지 않은 이름이다. (비즈니스 로직에 포함되는 도메인 정보가 아니라면) 의도를 분명하고 직설적으로 표현해라.

한 개념에 한 단어를 사용해라.

추상적인 개념 하나에 단어 하나를 선택해서 고수해야 한다. 예를 들어, 똑같이 서버에서 데이터를 가져오는 역할을 하는 메서드를 클래스마다 fetch, get, retrieve로 정의하면 혼란스럽다. 메서드 이름은 일관적이어야 한다.

말장난을 하지 마라.

한 단어두 가지 목적으로 사용하지 마라. 다른 개념에 같은 단어를 사용하는 것은 말장난에 지나지 않는다.

예를 들어, 매개 변수에 일정한 값을 추가하는 add 메서드가 있다고 하자. 새로 작성해야하는 메서드는 배열에 새로운 요소를 추가하는 메서드이다. 이 메서드 이름을 일관성에 맞춰 add~ 메서드라고 지어야 할까? 하지만 새 메서드는 기존의 메서드와 맥락이 다르다. 두 메서드가 하는 기능이 다르기 때문에 같은 이름으로 부르는 것은 말장난에 불과하다.

프로그래머는 코드를 이해하기 쉽게 짜야한다. 대충 훑어봐도 이해할 수 있는 코드 작성이 목표다.

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

우리는 코드를 읽을 사람도 프로그래머라는 사실을 알아야 한다. 그러므로 전상 용어, 알고리즘 이름, 패턴 이름, 수학 용어등을 사용해도 괜찮다. 모든 이름을 문제 영역(도메인 domain)에서 가져오는 것은 현명하지 못하다. 프로그래머가 더 잘 이해할 수 있는 코드는 도메인 영역보다 기술 영역의 이름이 아닐까?

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

적절한 프로그래머 용어가 없을 땐, 문제 영역(도메인)에서 이름을 가져온다. 우수한 프로그래머는 해법 영역과 문제 영역을 구분할 줄 알아야 한다. 문제 영역 개념과 관련이 깊은 코드라면 문제 영역에서 이름을 가져와야 한다.

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

홀로 있을 때 의미가 분명한 이름이 없는 건 아니지만, 대다수 이름은 그렇지 못하다. 그래서 클래스, 함수, 네임 스페이스에 넣어 문맥을 부여한다. 모든 방법이 실패하면 마지막 수단으로 접두어를 붙인다.

예를 들어, city, state, houseNumber 같은 변수들이 함께 있으면 주소를 나타내는 변수라는 사실을 알 수 있다. 하지만 state라는 변수 하나만 사용하면 state가 주소의 일부라는 사실을 금방 알 수 없을 것이다.

addr이라는 접두어를 추가해서 addrCity, addrState, addrHouseNumber라고 쓰면 문맥이 좀 더 분명해진다. 적어도 변수가 더 큰 구조의 일부라는 사실을 알 수 있다. 물론 Address라는 클래스를 생성해서 멤버 변수로 선언하는 게 더 좋다.

불필요한 맥락을 없애라.

고급 휘발유 충전소(Gas Station Deluxe)라는 애플리케이션을 만든다고 하자. 모든 클래스 이름을 GSD로 시작하겠다는 생각은 전혀 바람직하지 못하다.

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

accountAddress와 customerAddress는 Address 클래스 인스턴스로는 좋은 이름이지만 클래스 이름으로는 부적절하다. 포트 주소, MAC주소, 웹 주소를 구분해야한다면 PostalAddress, MAC, URI라는 이름도 좋다. 의미가 더 분명해지기 때문이다.

마치면서.

우리는 문장처럼 읽히는 코드 혹은 표나 자료 구조처럼 읽히는 코드를 짜는 데만 집중해야 마땅하다. 이름을 마음대로 바꿨다가는 누군가 질책할지도 모른다. 그렇다고 코드를 개선하려는 노력을 중단해서는 안 된다.

profile
ts와 react를 사랑하는 프론트엔드 개발자입니다.

0개의 댓글