읽고쓰고

[책] 클린코드 - 애자일 소프트웨어 장인 정신 (1, 2, 3장)

별토끼. 2021. 3. 29. 07:33
반응형

클린코드(w.로버트 C. 마틴)를 읽고 개인적인 학습을 위해 요약한 글 입니다. 문제 시 하단에 댓글 달아주시면 감사하겠습니다.

1. 깨끗한 코드

코드에 대한 태도

나쁜 코드의 위험을 이해하지 못하는 관리자 말을 그대로 따르는 행동은 전문가답지 못하다.
예를 들면, 환자가 의사에게 수술 전 손을 씻지 말라고 요구한다면 의사가 응하지 않을 것이다. 환자가 (갑)임에도 불구하고. 의사가 더 잘 알기 때문이다. 환자의 말을 따르는 행동은 전문가 답지 못하다. 즉, 코드에 대한 책임은 프로그래머 본인에게 있다는 것이다.

깨끗한 코드라는 예술?

전제는
1. 나쁜 코드는 심각한 장애물임을 납득
2. 빨리 가려면 코드를 깨끗하게 유지
그렇다면, 어떻게?

깨끗한 코드란

우아하고 효율적인 코드를 좋아한다. 논리가 간단해야 버그가 숨어들지 못한다. 의존성을 최대한 줄여야 유지보수가 쉬워진다. 오류는 명백한 전략에 의거해 철저히 처리한다. 깨끗한 코드는 한 가지를 제대로 한다.
비야네 스트롭스트룹(C++ 창시자)

단순하고 직접적인 코드 - 그래디 부치
다른 사람이 고치기 쉬운 코드 - 데이브 토마스
주의 깊게 작성한 코드 - 마이클 페더스
중복이 없는 코드, 작게 추상화하기 - 론 제프리스
짐작했던 대로 수행하는 코드 - 워드 커닝햄

깨끗한 코드에 대해 유명 개발자들은 위와 같이 칭하더라. 나에게 깨끗한 코드란?

2. 의미있는 이름

의도 밝히기

변수마다 의도를 정확히 밝혀줘야 한다. int배열 대신 클래스를 만ㄷㄹ고, 명시적 메서드를 만드는 것도 하나의 방법이다.

그릇된 정보 피하기

그릇된 단서를 남기지 않는다. 또한, 널리 쓰이는 의미있는 단어를 다른 의미로 사용하지 않는다. 여러 계정을 그룹으로 묶을 때, AccountList라 칭하지 안흔다. List는 개발자에게 특수한 의미이기 때문. 차라리, accountGroup, Accounts라 명명해라.

헷갈릴만한 흡사한 이름을 사용하지 않도록 주의하고, 유사 개념은 유사 표기법을 사용하자. 일관성이 떨어져도 그릇된 정보이다.

의미있게 구분해라

moneyAmount, money의 차이를 어떻게 구분할까? getActiveAccount(), getActiveAccounts(), getActiveAccountInfo() 이렇게 세 가지 함수 중 어느 함수를 호출할지 어떻게 알까?

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

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

private Date genymdhms;
private Date generationTimeStamp;

뜻을 모르면 "젠 와이 엠 디 에이치 엠 에스"라거나 "젠 야 무다 힘즈"라고 부르기 십상. 차라리 조금 길더라도 뜻을 밝히자.

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

문자 하나를 사용하는 이름과 상수는 검색도 어렵고 눈에 띄지 않는다. MAX_CLASSES_PER_STUDENT는 grep으로 찾기 쉽지만, 숫자 7은 은근히 까다롭다.

인코딩을 피하라

인코딩한 이름은 거의 발음이 어렵고, 오타가 생기기 쉽다.
인터페이스 클래스 이름에서는 굳이 접두어를 쓰지 않는 것이 좋다. IShapeFactory와 ShapeFactory 중 어떤 것이 좋을까. 후자이다. 후자를 택하고, 차라리 구현 클래스 이름을 ShapeFactoryImp로 짓는 것이 낫다. I라는 접두어는 옛날 코드에서 많이 사용했고, 주의를 흐트린다.

기억력을 자랑하지 마라

문자 하나만 사용하는 변수 이름은 문제가 있다. 루프 반복 횟수 세는 변수는 i, j, k 괜찮다.

클래스 이름, 메서드 이름

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

메서드 이름은 동사나 동사구가 적합하다. postPayment, deletePage, save 등이 좋은 예.

너무 기발한 이름은 피하자.

한 개념에 한 단어

추상적인 개념 하나에 단어 하나를 선택해 이를 고수한다. fetch, retreive, get으로 제각각 부르면 혼란스럽다. 독자적이고 일관적이어야한다. 마찬가지로, controller, manager, driver를 섞어 쓰면 혼란스럽다. 이름이 다르면 독자는 당연히 클래스도 다르고 타입도 다르리라 생각한다.
한 단어를 두 가지 목적으로 사용하지 마라. 다른 개념에 같은 단어를 사용한다면 그것은 말장난이다.

해법 영역, 문제영역에서 가져온 이름을 사용해라

코드를 읽는 사람도 프로그래머이다. 그러므로, 전산용어, 알고리즘 이름, 패턴 이름, 수학 용어 등을 사용해도 괜찮다. 적절한 프로그래머 용어가 없다면 문제 영역에서 이름을 가져온다. 그러면 코드 보수하는 프로그래머가 분야 전문가에게 의미를 물어 파악할 수 있다.

의미있는 맥락을 추가, 불필요한 맥락은 없애라

클래스, 함수, 이름 공간에 넣어 맥락을 부여하고 모든 방법이 실패하면 접두어를 붙인다. state보다는 addrState가 맥락이 분명해진다.
반대로, GasStationDelux 앱을 짜는데 모든 클래스 이름을 GSD로 시작한다면 이는 잘못된 것이다. 중복되고 불필요한 맥락이다.

3. 함수

작게 만들어라

함수를 만드는 첫번째 규칙, 그리고 두번째 규칙 모두 작게 만들기다. 어느 정도가 "작게" 일까? 각 함수가 이야기 하나를 표현한다 다시 말해, if문, else문, while문 등에 들어가는 블록은 한 줄이어야 한다는 의미이다. 중첩 구조가 생길만큼 함수가 켜져서는 안 된다는 뜻이다. 들여쓰기 수준은 1단이나 2단을 넘어서면 안된다.

한 가지만 해라

함수는 한 가지만 해야한다. 큰 개념을 다음 추상화 수준에서 여러 단계로 나눠 수행하기 위해서 함수를 만드는 것이다. 함수가 확실히 한 가지 작업만 하려면 함수 내 모든 문장의 추상화 수준이 동일해야 한다.
getHtml()은 아주 높은 추상화 수, String pagePathName = PathParser.render(pagepath)는 중간 수준, .append("\n")은 아주 낮은 추상화 수준을 갖고 있다.

Switch문을 작성하면 한 가지만 하기가 쉽지 않다. 그래서 저자는 추상 팩토리에 꽁꽁 숨긴다고 한다. 불가피한 상황은 어쩔 수 없지만..

서술적인 이름을 사용해라

좋은 이름이 주는 가치는 아주 아주 강조할만 하다. 함수가 작고 단순할수록 서술적인 이름을 고르기도 쉬워진다. 길어도 괜찮다. 시간을 들여도 괜찮다. 최대한 서술적인 이름을 골라라. 그러면 개발자 머릿속에서도 설계가 뚜렷해지고 개선도 쉬워진다.
다만, 일관성이 있어야 한다. 문체가 비슷하면 순차적으로 풀어가기 좋다. includeSetupAndTeardownPage, includeSetupPages, includeSuitesetupPage....등. 그러면 암시적으로 "아, inCludeTeardownPage도 있겠지?"라고 생각할 수 있다.

함수 인수

이상적인 인수 개수는 0개, 차선은 1개이다. 3개는 피하는 것이 좋고, 4개는 특별한 이유가 있어도 사용하면 안된다.
많이 쓰는 단항 형식에는 fileExists("MyFile")처럼 질문을 던지는 경우와 이벤트이다.이벤트로 해석해 입력 인수로 시스템 상태를 바꾼다. passwordAttemptFailedNtimes(int attemps)처럼 말이다. 이벤트는 코드에 그 사실이 명확히 드러나야 한다.

플래그 인수는 추하다. 함수로 부울 값을 넘기는 관례는 끔찍하다. 플래그가 참이면 이걸 하고, 거짓이면 저걸 한다는 말. 즉, 한꺼번에 여러 가지를 처리한다고 대놓고 공표해서이다.

이항함수는 이해가 단항보다 이해가 어렵다. 직교좌표 같은 것은 쓸 수도 있다. 그러나 그 외에는 위험이 따른다. 두 인수 순서에 대한 인위적 기억이 필요하기 때문이다. 삼항함수는 훨씬 더 이해가 어렵다. 순서, 주춤, 무시로 야기되는 문제가 두 배 이상 늘어난다.

인수 2-3개가 필요할 경우, 독자적 클래스 변수로 선언이 가능하다.

Circle makeCircle(double x, double y, double radius);
Circle makeCircle(Point center, double radius);

부수효과 피해라

함수 이름만 봐서 드러나지 않는 부수효과는 피해라. 예를 들어, 패스워드를 바꿔주는 함수에서 세션을 초기화 한다던지 그런 행위.

인수를 출력으로 사용하는 함수에 어색함을 느낀다. 차라리 호출을 해라.

public void appendFooter(StringBuffer report)
report.appendFooter()

오류코드보다 예외를 사용하자

오류 코드 반환 시 호출자는 오류 코드를 곧바로 처리해야 한다. 예외를 사용하면 원래 코드에서 분리되므로 코드가 깔끔해진다(P.58). Try/Catch 블록은 코드 구조에 혼란을 주고, 정상 동작과 오류 동작을 섞는다. 그러므로, 별도 함수로 뽑아내는 편이 좋다(P.59, 코드 참고).

어떻게 짜는 게 좋을까?

처음은 길고 복잡하다. 다듬고, 이름을 바꾸고, 중복을 제거한다. 메서드를 줄이고 순서도 바꾼다. 전체 클래스를 쪼개기도 한다. 이 와중에도 코드는 항상 단위 테스트를 통과한다. 한번에 가능한 사람은 없ㅅ다.

반응형