반복하지 마라

소프트웨어에서 중복은 모든 악의 근원이다.

  • 관계형 DB에서도 정규화를 통해 중복을 제거한다.
  • OOP에서도 부모 클래스를 통해 중복을 없앤다.
  • 이렇듯 다양한 프로그래밍 방법에서 중복을 없애기 위한 여러 시도가 있다.

구조적 프로그래밍

모든 함수와 함수 내 모든 블록에 입구와 출구가 하나만 존재해야 한다고 말한다.
return 문이 하나여야 한다.

함수를 어떻게 짜죠?

논문이나 기사를 쓸 때도, 생각을 기록한 후에 읽기 좋게 다듬는다. 초안은 대게 어수선하다.
함수 또한 마찬가지다.

  • 처음에는 길고 복잡하다.
  • 인수 목록도 아주 길다.
  • 중복 코드, 루프도 많다.
  • 이름도 즉흥적으로 짓는다.
  • 하지만 코드를 빠짐없이 테스트하는 Unit Text Case를 작성한다*

그런 후, 코드를 다듬고, 함수를 만들고, 이름을 바꾸고, 중복을 제거하고
메서드를 줄이고, 전체 클래스를 쪼갠다. 이러한 작업을 하더라도 단위 테스트는 항상 통과한다.

결론

함수는 동사며, 클래스는 명사다.
정말 잘하는 프로그래머는 시스템을 프로그램이 아니라 이야기로 여긴다.
프로그래밍 언어라는 수단을 사용해 좀 더 풍부하고 표현력이 강한 언어를 만들어 이야기를 풀어간다.

규칙을 지킨다면 길이가 짧고 이름이 좋은 체계 잡힌 함수가 나온다

서술적인 이름을 사용하라

이름은 길어도 된다. 길고 서술적인 이름이 짧고 어려운 이름보다 좋다
이름을 정하느라 시간을 들여도 괜찮다.
서술적인 이름을 사용하면 개발자 머릿속에서도 설계가 뚜렷해지므로 코드를 개선하기 쉬워진다.

함수 인수

함수에서 이상적인 인수 개수는 0개다.
테스트 관점에서 보면 인수는 더 어렵다. 인수가 없는 함수와 있는 함수들을 검증하는 것을 상상해봐도 인수가 없는 것이 더 간단하다.
최선은 인수가 0개이며, 차선은 1개 이다.

많이 쓰는 단항 형식

인수를 1개 넘기는 이유로 가장 흔한 경우는 2가지이다.

  1. 인수에 질문을 던지는 경우다.
    ex) boolean fileExists("MyFile")
  2. 인수를 뭔가로 변환해 결과를 반환하는 경우다.
    ex) InputStream fileOpen("MyFile") 은 String 형의 파일 이름을 InputStream으로 변환한다.

아주 유용한 단항 함수 형식은 이벤트 이다.
이벤트 함수는 입력 인수만 있다.

플래그 인수

함수로 부울값을 넘기면 그 함수가 여러 가지를 처리한다는 의미이므로 별로다.

인수 목록

String.format 메서드가 가장 좋은 예

동사와 키워드

함수와 인수는 동사/명사 쌍을 이뤄야 한다.
ex) write(name:) or writeField(name: )

부수 효과를 일으키지 마라

부수 효과는 거짓말이다. 함수에서 한 가지를 하겠다고 약속하고선 다른 짓도 하니까!
때로는 예상치 못하게 클래스 변수를 수정한다. 때로는 함수로 넘어온 인수나 시스템 전역변수를 수정한다.

출력 인수

일반적으로 우리는 인수를 함수 입력으로 해석한다.

public void appendFooter(StringBuffer report)

appendFooter(s); -> report.appendFooter(); 로 수정

명령과 조회를 분리하라

함수는 뭔가를 수행하거나 뭔가에 답하거나 둘 중 하나만 해야 한다.
객체 상태를 변경하거나, 객체 정보를 반환하거나 둘 중 하나다.

ex) public boolean set(String attribute, String value);
attribute인 속성값을 찾아 value로 설정 후, true/false를 반환한다.
-> 사용 시 if(set("username", "unclebob"))...
호출 코드만 봐서는 어떤 동작을 하는지 모호하다.

-> 변경
if (attributeExists("username")){
setAttribute("username", "unclebob");
}

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

명령 함수에서 오류 코드를 반환하는 방식은 명령/조회 분리 규칙을 미묘하게 위반한다.

try/catch 블록 뽑아내기

코드 구조에 혼란을 일으키며, 정상 동작과 오류 처리 동작을 뒤섰는다.
따로 뽑는게 좋다.

try{
    deletePageAndAllReferences(page);
}catch(Exception e){
    logError(e);
}
...
private void deletePageAndAllReferences(Page page) throws Exception{
    ddeletePage(page);
    registry.deleteReference(page.name);
    configKeys.deleteKey(page.name.makeKey());
}

오류 처리도 한가지 작업이다.

함수

작게 만들어라

함수를 만드는 첫 번째 규칙은 "작게" 이다.
두 번째 규칙은 "더 작게" 이다.

들여쓰기

대부분 while, for, if 문 내부에서 함수를 호출한다.
중첩 구조가 생길만큼 함수가 커지면 안된다. 들여쓰기 수준은 1단이나 2단을 넘어서면 안된다.

한가지만 해라

함수는 한 가지를 해야 한다. 그 한 가지를 잘 해야 한다.

함수 당 추상화 수준은 하나로

함수가 확실히 '한 가지' 작업만 하려면 함수 내 모든 문장의 추상화 수준이 동일해야 한다.

위에서 아래로 코드 읽기

코드는 위에서 아래로 이야기처럼 읽혀야 좋다.
한 함수 다음에는 추상화 수준이 한 단계 낮은 함수가 온다.

Switch 문

switch 문은 작게 만들기 어렵다.
다형성을 이용하여 저차원 클래스에 숨기고 반복하지 않는 방법은 있다. 다형성을 이용하는 것

 

 

 

위 함수는 길다.

새로운 EmployeeType을 추가하면 끝도 없이 길어진다.

 

문제를 해결하기 위해 switch 문을 추상 팩토리에 숨기고 아무에게도 보여주지 않는다.

팩토리는 switch를 사용해 Employee 파생 클래스의 인스턴스를 생성한다.

calculatePay, isPayday, deliverPay 등과 같은 함수는 Employee 인터페이스를 거쳐 호출된다.

 

 

 

 

 

 

 

 

'스터디 > 클린코드' 카테고리의 다른 글

[Clean Code] 클린 코드 (함수) - 8  (0) 2021.03.08
[Clean Code] 클린 코드 (함수) - 7  (0) 2021.03.08
[Clean Code] 클린 코드 - 5  (0) 2021.03.08
[Clean Code] 클린 코드 - 4  (0) 2021.03.08
[Clean Code] 클린 코드 - 3  (0) 2021.03.08

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

자신이 항상 URL을 r이라는 변수에 사용했다면, 자신은 그것을 항상 기억하기 때문에 사용해도 괜찮다고 생각한다.
하지만 정말 능력자들은 자신의 능력을 좋은 방향으로 사용해 남들이 이해하는 코드를 내놓는다.

클래스 이름

명사나 명사구가 적합하다.
Customer, WikiPage, Account, AddressParse 등은 OK
Manager, Processor, Data, Info 등과 같은 단서 X

메서드 이름

동사나 동사구가 적합하다.
postPayment, deletePage, save OK
접근자, 변경자, 조건자는 접두어로 get, set, is 를 붙인다.

Complex fulcrumPoint = Complex.FromRealNumber(23.0); // 정적 팩토리 메소드
vs
Complex fulcrumPoint = new Complext(23.0); // 생성자

생성자를 오버로딩할 때는 정적 팩토리 메서드를 사용한다.

기발한 이름은 피하라

농담이나 유머감각이 비슷한 사람만 알 수 있는 명명을 피하라

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

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

예를 들어, 같은 메서드를 클래스마다 fetch, retrieve, get 으로 제각각 부르면 혼란스럽다.

말장난을 하지마라

한 단어를 두 가지 목적으로 사용하지 마라.
앞선 '한 개념에 한 단어를 사용하라'와 조금은 충돌되는 부분이다.
모든 add 메서드의 매개변수와 반환값이 의미적으로 같으면 상관이 없다.
하지만 같은 맥락이 아닌데도 '일관성' 을 고려해 add라는 단어를 선택한다.
ex) 지금까지의 add메서드는 2개의 값을 더하거나 새로운 값을 만든다고 가정하자.
새로 작성하는 메서드는 집합에 값 하나를 추가한다.
이 메서드도 add라고 불러도 괜찮은가? -> insert나 append의 이름이 적합하지 않을까? -> 의미가 다르니

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

코드를 읽을 사람도 프로그래머이다.
전산 용어, 프로그래머 용어, 알고리즘 이름, 패턴이름, 수학 용어 등을 사용해도 무방하다
ex) Visitor 패턴에 친숙한 프로그래머는 AccountVisitor라는 이름을 금방 알 것이다.
JobQueue, ReadyQueue를 모르는 프로그래머는 없을 것이다.

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

적절한 프로그래머들의 용어가 없다면 문제 영역에서 이름을 가져온다.

의미있는 맥락을 추가하라

스스로 의미가 분명한 이름이 있다. 하지만 대부분의 이름은 그렇지 못하다
그래서 클래스, 함수, 이름 공간에 넣어 맥락을 부여한다.
firstName, lastName, street, houseNumber, city, state, zipcode라는 변수가 있다. 변수를 보면 "주소"라는 사실을 금방 알아챈다.

하지만 어느 메서드가 state라는 변수를 하나만 사용한다면? 해당 state가 주소의 state인지 상태인지 모를 것이다.
따라서 이럴 때는 접두어를 붙여서 맥락을 분명히 한다. addrState 처럼

마치며

좋은 이름을 선택하려면 설명 능력이 뛰어나야하고 문화적 배경이 같아야한다.
사람들이 이름을 바꾸지 않으려는 이유 중 하나는 다른 개발자가 반대할까 두려워서이다.
하지만 좋은 이름으로 바꾼다면 그것은 고마운 일이다. 따라서 개선하려는 노력을 중단해서는 안 된다.

'스터디 > 클린코드' 카테고리의 다른 글

[Clean Code] 클린 코드 (함수) - 7  (0) 2021.03.08
[Clean Code] 클린 코드(함수) - 6  (0) 2021.03.08
[Clean Code] 클린 코드 - 4  (0) 2021.03.08
[Clean Code] 클린 코드 - 3  (0) 2021.03.08
[Clean Code] 클린 코드 - 2  (0) 2021.03.08

+ Recent posts