[개발 도서 공부 - 📗 Clean Code] 2장: 의미 있는 이름

Hyunjoon Choi·2023년 10월 11일
0
post-thumbnail

코드를 작성하면서 이름은 어디에서나 쓰인다. 가독성을 위해 이름을 잘 지을 수 있는 방법에 대해 알아보자.

이름을 잘 짓기 위한 규칙

1. 의도를 분명히 밝혀라

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

코드에 주석을 작성하지 마라! 주석이 필요하다면 의도를 분명히 드러내지 못했다는 뜻이다.

예시로 List<int[]> list1 = new ArrayList<int[]>(); 보다 List<int[]> flaggedCells = new ArrayList<int[]>();가 더 낫다.

개인적인 의견: 책의 이후 내용을 보면, 저번에 작성한 매직 넘버와 관련되어 보인다. 다른 개발자들이 더 쉽게 이해할 수 있도록 매직 넘버들은 가급적 상수 처리하도록 하자. 그러나 어느 것도 과하면 좋지 않듯이, 너무 세세한 것들에 대해서도 전부 상수 처리를 하진 말도록 하자. 상수 처리에 대한 필요성 등과 같은 이러한 구분은 개발을 더 해야 와닿을 것 같다.

2. 그릇된 정보를 피하라

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

Account를 여러 개로 담는 경우를 예로 들어보면, 실제 타입이 List가 아니라면 AccountList는 적합하지 않다.

실제 컨테이너가 List인 경우라도 컨테이너 유형을 이름에 넣지 않는 게 바람직하다.

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

개인적인 의견: 그동안 개발을 했었을 때 간혹 이렇게 컨테이너 유형을 포함하여 선언했던 경우가 종종 있었던 것 같다. 아마도 이런 습관을 지양하라는 이유는 컨테이너 유형이 변경될 경우 이름까지 바꿔야 하고, 위에서 나왔듯이 실제 타입과 불일치하는 경우가 생길 수도 있기 때문으로 보인다.

3. 의미 있게 구분하라

컴파일러를 통과할지라도 연속된 숫자를 덧붙이거나 불용어 (noise word)를 추가하는 방식은 적절하지 못하다. 이름이 달라야 한다면 의미도 달라져야 한다.

지양해야 할 예시

  • a1, a2 등과 같이 숫자를 덧붙인 이름
  • NameStrig보다는 Name이 낫다. 정확히 말하면, Name은 String 이외의 타입으로 변환될 확률이 극히 낮다. (이는 그릇된 정보를 피하라와 내용이 비슷해 보인다.)

a나 the와 같은 접두어를 사용하지 말라는 뜻이 아니다! 의미가 분명히 다르다면 사용해도 무방하다.

그런데 궁금한 부분이 있었다. 바로 Info와 Data는 a, an, the와 마찬가지로 의미가 불분명한 불용어라는 점이다.

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

곰곰이 생각해보니, Product라는 클래스가 있다면 그 자체로 Product에 대한 정보 (Info), 데이터 (Data)가 들어있기 때문에 굳이 사용하지 않아도 될 것 같다는 생각이 들었다. 만약 이러한 별도의 클래스가 필요하다면, DTO (Data Transfer Object)를 사용하여 ProductResponse와 같은 형태로 사용하면 되지 않을까라는 생각이 들었다.

그리고 이 부분은 내가 자동차 경주 미션을 하며 리뷰받은 내용이기도 하다. 나는 아래처럼 작성했었다.

public HashMap<String, Integer> getAllCarsDistanceStatus() {
    HashMap<String, Integer> carsInfo = new HashMap<>();
}

지금 생각해보면, 모든 차들의 거리에 대한 상태를 조회하는 것이므로 carsDistance 이런 식으로 사용하면 더 좋지 않았을까라는 생각이 든다.

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

예시로 genymdhms라는 괴상한 이름 (generate data, year, month, day, hour, minute, sec-ond)보다 generationTimestamp라는 이름이 더 명료하고 발음하기도 쉬우며 지적인 대화가 가능해진다.

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

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

여기서 내가 새롭게 알게 된 것은, 어쩌면 당연한 것이겠지만 매직 넘버를 없애려고 할 때 반드시 상수화할 필요 없이 간단한 변수로 설정해도 없앨 수 있다는 것이다.

6. 인코딩을 피하라

문제 해결에 집중하는 개발자에게 인코딩은 불필요한 정신적 부담이다. 인코딩한 이름은 거의가 발음하기 어려우며 오타가 생기기도 쉽다.

사실 이 부분은 잘 지키고 있던 부분이라 자연스럽게 이해됐다. 언급되는 게 헝가리안 표기법인데, 이때는 컴파일러가 타입을 점검하지 않았기 때문이었다. 최근의 IDE를 사용하고 있는 사람들이라면 자연스럽게 지켜왔던 내용인 것 같다.

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

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

루프에서 반복 횟수 변수는 전통적으로 한 글자를 사용했었음을 책을 보고 알았다. 그런데 요즘은 향상된 for문이나 stream 등의 방법이 있는 등, 이 변수들을 아예 활용하지 않는 쪽도 괜찮을 것 같다.

8. 클래스 이름

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

개인적인 의견: Manager도 피하라고 이야기하는 게 의외였다. Data와 Info는 위에서 작성했지만, 웹서비스를 만들 때 Manager가 반드시 필요한 예도 있지 않을까?

9. 메서드 이름

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

개인적인 의견: 변경자 같은 경우에는 일반적인 setter 메서드로 표기하기보다는 비즈니스 표현을 이용하도록 하자. (update 등)
이 부분에 대한 것은 갓영한님의 의견을 더 보자.

객체를 생성할 때는 3가지 방법중 하나를 사용합니다.
1. 생성자
2. 정적 팩토리 메서드
3. Builder 패턴
엔티티에 따라 이 방법중 상황에 따라서 하나를 선택하고, 파라미터에 객체 생성에 필요한 데이터를 다 넘기는 방법을 사용합니다. 그리고 정적 팩토리 메서드나, Builder 패턴을 사용할 때는 생성자를 private 처리합니다. 객체 생성이 간단할 때는 단순히 생성자를 사용하고, 만약 객체 생성이 복잡하고, 의미를 가지는 것이 좋다면 나머지 방법 중 하나를 선택합니다.
그러면 setter가 없는데, 엔티티를 어떻게 수정할까요?
이것은 setter를 만들기 보다는 의미있는 변경 메서드 이름을 사용합니다. 예를들어서 고객의 등급이 오른다면 member.levelUp() 같은 메서드가 있겠지요? 이 메서드는 내부의 필드 값을 변경하고요.
만족하실 만한 답변이 되었는지 모르겠네요^^ 혹시 더 궁금하신 내용이 있으면, 구체적인 예제 코드로 질문을 해주시면 제가 더 자세히 답변을 드릴께요^^
감사합니다.

핵심은 정적 팩토리, 단순 생성자, 빌더, 어떤것을 사용하든 상관이 없습니다. 중요한 것은 이렇게 생성자에 파라미터를 넘기는 기법을 사용해서, 변경이 필요없는 필드에 추가적인 setter를 외부에 노출하는 것을 줄이는 것이 핵심입니다.(생성 이후에 변경할 필요가 없는데, setter가 외부에 노출되어 있으면 이것을 사용하는 다른 개발자들은 이 setter를 호출해야 하나? 말아야 하나 고민하니까요. 그런데 setter나 변경 가능한 메서드가 없으면, 아 이건 내가 막 변경하면 안되는구나 생각하겠지요.)

객체지향의 사실과 오해 책을 공부했더니 왜 setter를 지양해야 하는지 더 명확해졌다.
완벽한 객체지향을 구축하기 위해서는, 객체가 충분히 자율적이어야 한다.
그런데 외부에서 setter 메서드를 사용한다면, 객체의 필드값을 수동적으로 바꿔버리는 듯한 느낌을 준다. 예시로 Person의 나이 (age)를 증가시킨다고 해 보면, person.setAge(person.getAge() + 1) 등과 같은 식으로 작성할 것이다. 이때 person 객체의 입장에서 보면, 자신이 원하지도 않았는데 자신의 나이가 증가된 것이다!

그러나 setter 메서드를 person.growOlder()와 같이 뜻을 가진 메서드로 변경하면, 객체의 입장에서는 외부에서 자신에게 요청하였을 때 자신의 의도대로 자신의 나이를 증가시키는 듯한 효과를 줄 수 있다. (덤으로 외부에게 자신의 필드를 노출시키지 않아 캡슐화에 도움이 되기도 한다.)

생성자를 중복정의할 때는 정적 팩터리 메서드를 사용한다. 메서드 이름은 인수를 설명하는 이름을 사용한다. 생성자 사용을 제한하려면 해당 생성자를 private로 선언한다.

개인적인 의견: 정적 팩터리 메서드를 사용할 때는 관례에 맞게 기본 인자가 하나일 때는 from, 두 개일 때는 of, 특별한 로직이 들어갈 경우에는 createDefault 등과 같이 작성하자. 정적 팩터리 메서드 이름을 인수를 설명하는 이름도 좋은 점이 있지만 (각 값이 어떻게 들어가는지 파악할 수 있는 등), 여러 클래스에서 정적 팩터리 메서드들을 쓸텐데 그렇다면 관례를 따라 작성 방법을 통일시키는게 더 낫지 않을까라는 생각이 든다.

그렇다면, 왜 사람들은 from, of 등의 이름을 관례상으로 잡았을까? 아마 이펙티브 자바에 기술된 것 때문으로 보인다. of 같은 경우에는 List에서 사용되는 List.of()와 같은 기능으로 유추할 수 있기 때문으로 생각된다. (from은 구체적인 이유는 잘 모르겠다.. 이펙티브 자바도 하루빨리 더 읽어야겠다.)

10. 기발한 이름은 피하라

특정 문화에서만 사용하는 농담, 기발한 이름 등은 피하라. 의도를 분명하게 파악하기 어렵다.

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

추상적인 개념 하나에 단어 하나를 선택해 이를 고수한다. 예를 들어, 똑같은 메서드를 클래스마다 fetch, retrieve, get으로 제각각 부르면 혼란스럽다.

개인적인 의견: 아래의 말장난을 하지 마라에 기술되었듯, 같은 개념일지라도 완벽히 같은 맥락에서만 같은 단어를 사용하는 것으로 이해하면 될 듯 하다. 즉, 여기에서 언급된 똑같은 메서드는 같은 맥락인 메서드들이다!

12. 말장난을 하지 마라

같은 맥락에만 같은 단어를 사용해야 한다.

예시로 add로 시작되는 여러 메서드를 만들었다고 해 보자. 이때 모든 add 메서드의 매개변수와 반환값이 의미적으로 같다면 문제가 없으나, 같은 맥락이 아닌데도 일관성 때문에 add로 시작되게 했다면 문제가 있다.

집합에 값을 추가하는 것과 기존 값 두개를 더하는 것은 맥락이 다르다. 즉 전자에는 add보다는 insert, append 등이 적합할 것이다.

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

모든 이름을 문제 영역 (도메인)에서 가져오는 정책은 현명하지 못하다. 같은 개념을 다른 이름으로 이해하던 동료들이 매번 고객에게 의미를 물어야하기 때문이다.

개인적인 의견: 필자는 코드를 읽을 사람도 프로그래머기에, 전산 용어, 알고리즘 이름 등을 사용해도 괜찮다고 말한다. 그러면서 JobQueue를 예시로 들고 있는데, 나는 모르는 개념이었다 (...)
따라서 해법 영역에서보다는 문제 영역에서 가져온 이름 위주로 선택하는 게 더 낫지 않을까라는 생각이 들었다. 프로젝트를 함께 개발하는 사람들이라면 해결하고자 하는 도메인에 대해 충분한 이해를 갖추었을 확률이 높기 때문이다.

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

적절한 '프로그래머 용어'가 없다면 문제 영역에서 이름을 가져온다. 그러면 코드를 보수하는 프로그래머가 분야 전문가에게 의미를 물어 파악할 수 있다.

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

스스로 의미가 분명한 이름이 없지 않다. 하지만 대다수 이름은 그렇지 못하다. 그래서 클래스, 함수, 이름 공간에 넣어 맥락을 부여한다. 모든 방법이 실패하면 마지막 수단으로 접두어를 붙인다.

책에서는 firstName, lastName, street 변수 등을 그대로 작성하기보다는, addr이라는 접두어를 붙이거나 Address라는 클래스를 만들어 그 안에 넣음으로써 맥락을 분명하게 하는 경우를 소개하고 있다. 특정 클래스로 위치를 옮김으로써도 맥락을 추가할 수 있다는 사실이 참신하게 느껴졌고, 클래스를 별도로 만든다면 함수를 쪼갤 수 있다는 것을 새롭게 알게 되었다.

하지만 접두어를 붙이는 방식은 잘 와닿지 않는다. addr과 같은 접두어를 붙인다고 해서 addr이 address임을 이해하기까지는 예상하기보다 조금 더 시간이 걸릴 것 같다. 앞서 인코딩을 피하라에서 있듯, 클래스와 함수는 접두어가 필요없을 정도로 작아야 마땅하다는 것, 코드를 읽을수록 접두어는 관심 밖으로 밀려난다는 것 등 접두어를 지양하라는 표현들이 더 많았고 더 이해가 되었기 때문에 접두어를 사용하는 방식보다는 특정 클래스로 분리하도록 하는 것이 더 낫다고 판단되었다.

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

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

나는 이 부분에서 쓰인 클래스 이름들이 살짝 의문이 들었다. 예시로 나온 클래스 중 GSDAccountAddress, MAC 등이 나왔기 때문이다. 이런 경우, 용어의 어느 부분까지 대문자로 취해야 할 지 팀원 간의 협의가 제대로 되지 않을 수도 있다는 생각이 들었다. 아무리 특정 용어로 시작되는 클래스더라도, 전체적인 네이밍 컨벤션을 맞추기 위해 첫 번째 글자만 대문자로 (단, PascalCase) 취하도록 하는 게 좋을 것 같다는 생각이 들었다.

책에서는 AccountAddress를 예시로 들며 도메인이 바뀔 경우 기존에 GSDAccountAddress 클래스를 사용한다면 이름이 적절하지 않을 것이라고 한다. 내 이해로는, GSD (고급 휘발유 충전소: Gas Sation Deluxe)라는 맥락이 여기에서 이야기하는 불필요한 맥락이라는 생각이 들었다. GSD를 삭제하고 AccountAddress라고만 명시해놓으면, 도메인이 변경되더라도 충분히 문제 없을 수 있기 때문이다.

읽으며 느낀 점

한 매체에서 다음과 같은 짤을 접했었다.

깨끗한 코드를 작성하기 위해서는 이름을 짓는 게 가독성에 있어 매우 중요함을 깨달았다.

또한, 책에 작성되어 있듯이 책의 내용을 무조건 정답이라도 생각하지 말고 커스텀하게 바꾸고 싶은 원칙이 있다면 (예: setter 메서드 대신 비즈니스 로직으로 변경하는 등) 원하는대로 취하는 것이 좋을 것 같다는 생각이 들었다. (단, 명확한 반대 이유가 있을 경우)

아직 2장까지밖에 나가지 못했는데 클린 코드를 작성하기 위해서는 더 많은 노력이 필요함을 알게 되었다. 꾸준히 공부해보자.

추가로, 구글 컨벤션과 충돌되는 것들이 있을 것이다. 만약 충돌되는 게 있다면 구글 컨벤션을 정리하는 글에 작성하자.

profile
개발을 좋아하는 워커홀릭

0개의 댓글