[Clean Code] 클린코드 - 02장. 의미 있는 이름

7 분 소요

클린 코드 개발도서 관련 : 2장 의미 있는 이름이란 무엇인지 살펴보자.

들어가면서

소프트웨어에서 이름은 어디나 쓰인다.

그렇기 때문에 이름을 잘 지으면 여러 모로 편하다. 이름을 잘 짓는 간단한 규칙을 몇 가지 살펴보자.


의도를 분명히 밝혀라

좋은 이름을 지으려면 시간이 걸리지만 좋은 이름으로 절약하는 시간이 훨씬 더 많다.

주의깊게 생각하고 더 나은 이름이 떠오르면 개선하도록 하자. 그러면 코드를 읽는 사람도 좀 더 행복해질 것이다.

잘 지은 이름은 다음과 같은 질문에 모두 답할 수 있어야 한다.

이름을 잘 지었는지 확인 해보기

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

따로 주석이 필요하다면 의도를 분명히 드러내지 못한 것이다.

예시

public class SomethingClass {
    public List<int> getThem() {
        List<int[]> list1 = new ArrayList<int[]>();

        for (int[] x : theList)
            if (x[0] == 4)
                list1.add(x);
        return list1;
    }
}

위 코드는 특별히 복잡한 로직이 들어가거나 들여쓰기가 많거나 변수 또는 상수가 많지도 않은데, 수행하려는 일을 짐작하기 어렵다.

이 코드의 문제점은 단순성이 아니라 코드의 함축성 때문이다. 코드 맥락이 코드 자체에 명시적으로 드러나지 않는다.

위 코드는 사실 지뢰찾기 게임에 대한 소스였다. theList 가 게임판이고, 게임판의 각 칸을 단순 배열로 표현하며 0번째 값은 칸 상태를 뜻한다. 값 4는 깃발이 꽂힌 상태를 가리킨다.

이를 소스에 적용하면 다음과 같아진다.

public class SomethingClass {
    public List<int> getFlaggedCells() {
        List<int[]> flaggedCells = new ArrayList<int[]>();

        for (int[] x : gameBoard)
            if (cell[STATUS_VALUE] == FLAGGED)
                flaggedCells.add(cell);
        return flaggedCells;
    }
}

이제 훨씬 나아진 것이 보인다. 복잡도나 들여쓰기 등 모든 것이 동일하고 단순히 이름만 바뀌었을 뿐인데 코드는 더욱 명확해졌다.

좀 더 나아가 int 배열을 사용하는 대신 칸을 간단한 클래스로 만들어보자. 그리고 isFlagged 라는 좀 더 명시적인 함수를 통해 FLAGGED 상수도 감춰보자.

public class SomethingClass {
    public List<int> getFlaggedCells() {
        List<Cell> flaggedCells = new ArrayList<Cell>();

        for (Cell cell : gameBoard)
            if (cell.isFlagged())
                flaggedCells.add(cell);
        return flaggedCells;
    }
}

이것이 좋은 이름이 주는 위력이다.

내 생각…

개인적인 생각으로도 int -> Cell 와 같은 primitive 한 type 을 의미있는 클래스로 wrapping 하는 것에 대해 긍정적인 생각을 가지고 있었다.
그러나 실무에 적용하기에 너무나도 많은 클래스가 생겨난다는 점과 과하다는 의견에 반대를 받으며 적용하기 어려웠던 것 같다.

그리고 cell[STATUS_VALUE] == FLAGGED -> isFlagged() 와 같이 단순한 것도 함수로 대체함으로써 의미 부여하는 것도 가독성을 높이는 활동이라고 생각한다.


그릇된 정보를 피하라

그릇된 단서는 코드 의미를 흐린다.

널리 쓰이는 의미가 있는 단어를 다른 의미로 사용해도 안 된다.

여러 데이터를 묶을 때 실제 List가 아니라면 XxxList라 명명하지 않는다.

데이터를 담는 컨테이너가 실제 List가 아니라면 프로그래머에게 그릇된 정보를 제공하는 셈이다.
accountList대신 accountGroup, bunchOfAccounts, 아니면 단순히 Accounts라 명명하자.

서로 흡사한 이름을 사용하지 않도록 주의하자.

XYZControllerForEfficientHandlingOfStrings, XYZControllerForEfficientStorageOfStrings 둘의 차이점을 알겠는가?

유사한 개념은 유사한 표기법을 사용하게 된다. 이 또한 정보다. 일관성이 떨어지는 표기법은 그릇된 정보다.
요즘 IDE에서는 자동 완성 기능을 제공하는데, 개발자들은 십중팔구 이름만 보고 객체를 선택한다.

그렇기 때문에 개념 차이가 명백히 들어나도록 명명하도록 하자.

이름으로 그릇된 정보를 제공하는 끔찍한 예가 소문자 L이나 대문자 O 변수다. 그렇다…ㅎ
(실제 글꼴을 바꾸어 l, 1, O, 0 차이를 드러내는 해결책을 제안하는 경우도 있다고 한다.. 그러지 말자…)

이름만 바꾸면 문제가 깨끗이 풀린다. 이름 때문에 괜스레 일거리를 만들 필요가 없다.


의미 있게 구분하라

컴파일이나 인터프리터만 통과하려는 생각으로 코드를 구현하는 프로그래머는 스스로 문제를 일으킨다.

컴파일러를 통과할지라도 연속된 숫자를 덧붙이거나 불용어를 추가하는 방식은 적절하지 못하다.

연속적인 숫자를 덧붙인 이름(a1, a2, .. aN)은 그릇된 정보를 제공하는 이름도 아니며, 아무런 정보도 제공하지 못한다. 저자의 의도가 전혀 드러나지 않는다.

불용어를 추가한 이름 역시 아무런 정보도 제공하지 못한다. Product 라는 클래스가 있을 때, ProductInfo 혹은 ProductData 라 부른다면 개념을 구분하지 않은 채 이름만 달리한 경우다.

Info, Data는 a, an, the와 마찬가지로 의미가 불분명한 불용어다.

a나 the와 같은 접두어를 사용하지 말라는 의미는 아니다. 둘의 의미가 분명히 다르다면 사용하라.

불용어는 중복이나 마찬가지다. (ex. NameString = Name, CustomerObject = Customer)

읽는 사람이 차이를 알도록 이름을 지어라.


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

발음하기 쉬운 이름을 선택하자.

발음하기 어려운 이름은 토론하기도 어렵다.

프로그래밍 또한 사회 활동이기 때문에 발음하기 쉬운 이름은 중요하다.


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

긴 이름이 짧은 이름보다 좋다. 검색하기 쉬운 이름이 상수보다 좋다.

(저자의 개인적인 입장)간단한 메서드인 경우에만 로컬 변수로 한 문자를 사용한다.

이름 길이는 범위 크기에 비례해야 한다. 즉, 변수나 상수를 코드 여러 곳에서 사용한다면 이름이 길어지더라도 검색하기 쉬운 이름이 바람직하다.

for (int j=0; j<34 ; j++) {
    s += (t[j]*4)/5;
}

VS

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

위 코드의 sum 은 별로 유용하진 않지만 적어도 검색이 가능하다. 이름을 의미있게 지으면 함수는 길어진다. 하지만 각 단어들을 찾기 얼마나 쉬운지 생각해보자. 그냥 5를 사용한다면 5가 들어간 이름을 모두 찾은 후 의미를 분석해나가야 한다.


인코딩을 피하라

문제 해결에 집중하는 개발자에게 인코딩은 불필요한 정신적 부담을 남긴다.

인코딩한 이름은 발음하기 어려우며 오타가 생기기 쉽다.

자바 프로그래머는 변수 이름에 타입을 인코딩할 필요가 없다. IDE는 코드를 컴파일하지 않고도 타입 오류를 감지할 정도로 발전했다.

이제는 헝가리식 표기법이나 기타 인코딩 방식이 오히려 방해가 될 뿐이다. 사용할 경우 변수, 함수, 클래스 이름이나 타입을 바꾸기가 어려워질 뿐만 아니라, 읽기도 어려워진다. 독자를 오도할 수 있다.

멤버 변수에 m_ 이라는 접두어를 붙일 필요도 없다. 클래스와 함수는 접두어가 필요없을 정도로 작아야 마땅하다.

접두어는 옛날에 작성한 구닥다리 코드라는 징표가 되어버렸다.

때로는 인코딩이 필요한 경우도 있다. 인터페이스와 구현 클래스를 구분하는 경우가 그러하다.

인터페이스 이름에는 접두어를 붙이지 않는 편이 좋다.(ex. IShapeFactory (x)) I는 주의를 흐트리고 과도한 정보를 제공한다.

아마 개발자라면 다루는 클래스가 인터페이스라는 사실을 남에게 알리고 싶지 않을 것이다. 그저 사용자는 ShapeFactory라고 생각하길 바랄것 이다.

인터페이스 클래스 이름과 구현 클래스 이름 중 하나를 인코딩해야 한다면 구현 클래스 이름을 택하겠다.(ex. ShapeFactoryImp)


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

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

문자 하나만 사용하는 변수 이름은 문제가 있다. But, 루프에 정의된 반복 횟수를 세는 용도는 괜찮다.
단, 루프 범위가 아주 작고 다른 이름과 충돌하지 않을 때만 괜찮다.

똑똑한 프로그래머전문가 프로그래머 사이에서 나타나는 차이점은 전문가 프로그래머는 명료함이 최고라는 사실을 안다는 것이다. 전문가는 자신의 능력을 좋은 방향으로 사용해 남들이 이해하는 코드를 내놓는다.


클래스 이름

클래스 이름과 객체 이름은 명사나 명사구가 적합하다.

Data, Processor, Manager 와 같은 단어는 피하고, 동사는 사용하지 않는다.


메서드 이름

메서드 이름은 동사나 동사구가 적합하다.

접근자, 변경자, 조건자는 javabean 표준에 맞게 값 앞에 get, set, is를 붙이자.

생성자를 중복정의할 때는 정적 팩토리 메서드를 사용하자. 메서드는 인수를 설명하는 이름을 사용하도록 하자.

// Bad
Complex fulcrumPoint = new Complex(23.0);

// Good
Complex fulcrumPoint = Complex.FromRealNumber(23.0);

생성자 사용을 제한하기 위해 생성자를 private 으로 선언하자.


기발한 이름은 피하라

재미난 이름보다 명료한 이름을 선택하라.

특정 문화에서만 사용하는 농담은 피하는 편이 좋다. 의도를 분명하고 솔직하게 표현하라.


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

추상적인 개념 하나에 단어 하나를 선택해 이를 고수해야한다.

똑같은 메서드를 클래스마다 제각각 부르면 혼란스럽다. (ex. fetch, retrieve, get …)

메서드 이름은 독자적이고 일관적이어야 한다. 그래야 주석을 뒤져보지 않고도 프로그래머가 올바른 메서드를 선택한다.

이름이 다르면 독자는 당연히 클래스도 다르고 타입도 다르리라 생각한다.

일관성 있는 어휘를 만나게되면 개발자는 반가울 것이다.


말장난을 하지 마라

한 단어를 두 가지 목적으로 사용하지 마라.

“한 개념에 한 단어를 사용하라”는 규칙 때문에 같은 맥락이 아닌데도 ‘일관성’을 고려해 동일한 단어를 선택하는 경우가 있다.

예를들어, add 라는 메서드가 있는데 기존에는 값 두 개를 더하거나 이어서 새로운 값을 만드는 경우에 사용되었다고 하자. 그러나 새로 작성하는 메서드는 집합에 값 하나를 추가하는 기능을 제공한다.

이 경우에도 add 라고 불러도 괜찮을까? add 메서드가 많으므로 일관성을 지키는 것은 좋으나, 엄연히 새 메서드는 기존 add 메서드와 맥락이 다르다. 그러므로 insert나 append와 같은 이름이 적당할 것이다.

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

코드는 독자가 해독하는 것이 아닌 저자가 의도를 밝히는 것이다.


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

코드를 읽는 사람도 프로그래머이므로, 전산 용어, 알고리즘 이름, 패턴 이름, 수학 용어 등을 사용해도 괜찮다.

모든 이름을 문제 영역(domain)에서 가져오는 정책은 현명하지 못하다.

기술 개념에는 기술 이름이 가장 적합한 선택이다.


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

적절한 프로그래머 용어가 없을 경우 문제 영역(domain)에서 이름을 가져와라. 그러면 개발자는 해당 분야 전문가에게 의미를 물어 파악할 수 있다.

문제 영역 개념과 관련이 깊은 코드라면 문제 영역에서 이름을 가져와아야 한다.


의미 있는 맥락을 추가하라

이름 스스로가 분명한 의미를 지니지 못하는 경우가 있다. 예를들면, firstName, lastName, street, houseNumber, city, state, zipcode 라는 변수가 있을 때, 어느 메서드가 state라는 변수 하나만 사용한다면 해당 변수가 주소의 일부라는 사실을 알 수 있을까?

이 경우 addr 라는 접두어를 추가해 addrFirstName, addrLastName, addrState라 쓰면 맥락이 좀 더 분명해진다.

좀 더 나아가 Address라는 클래스를 생성하는 것도 좋다. 그러면 변수가 좀 더 큰 개념에 속한다는 사실이 컴파일러에게도 분명해질 것이다.


불필요한 맥락을 없애라

‘고급 휘발유 충전소(Gas Station Deluxe)’라는 어플리케이션을 개발하기 위해 모든 클래스 이름이 GSD로 시작한다면 바람직할까?

IDE에서 G를 눌렀을때 모든 클래스가 열거 될 것이다. IDE는 개발자를 지원해주는 도구인데, 오히려 우리가 IDE를 방해하는 샘이다.

일반적으로 짧은 이름이 긴 이름보다 좋다. 단, 의미가 분명한 경우에 한해서다.

이름에 불필요한 맥락을 추가하지 않도록 주의하자.

accountAddress, customerAddress 는 Address 클래스의 인스턴스로서 좋은 이름이다. 클래스 이름으로는 적합하지 못하다.

Address 는 클래스 이름으로 적합하다.

포트 주소, MAC 주소, 웹 주소를 구분해야 한다면 PostalAddress, MAC, URI라는 이름도 괜찮다.


마치며

사람들이 이름을 바꾸지 않으려는 이유 중 하나는 다른 개발자들이 반대할까 두려워서다.

하지만 그와 반대다. 오히려 좋은 이름으로 바꿔주면 반갑고 고맙다.

우리는 이름을 모두 암기하지 못하기 때문에 암기는 도구에게 맡기고, 우리는 읽히는 코드를 짜는 데만 집중해야 마땅하다.


고찰

그 동안 코딩 할 때 이름 짓는 것에 너무 많은 시간을 할애 한다고 생각했었다. 그래서 내가 너무 쓸데 없는 데에 에너지를 쏟고 있는게 아닌가 싶었다.

그러나 이번 장을 읽으면서 다행히도 내가 헛된 시간을 보내고 있던건 아니구나 라는 생각이 들었다.

그 동안 이름을 지으면서 또는 기존에 지어진 이름들을 읽으면서 이 장에 나오는 하지 말아야 할 것 들을 무의식 중에 많이 접해온 것 같다. 분명 당시에도 문제점은 보였다. 하지만 이 장의 마지막 부분에 나온대로 다른 개발자들이 반대할까 두려워 고치지 못했다.

왜냐하면, 그들도 나름의 정성을 들여 지은 이름이었을 텐데 내가 바꾼다 한들 내 입맛에만 맞는 단어일 수 도 있기 때문이다.
그치만 앞으로는 적극적인 자세로 더 나은 이름을 지어주기 위해 노력하고, 남들에게 제시하는 자세를 취해보도록 해야겠다.


⬅️ 클린코드 목차보기

댓글남기기