Clean Code(클린 코드) 작성하기

5 분 소요

들어가며


“클린코드”를 신입인 내가 한번에 이해할 수 있을거라 생각하지 않는다.
가볍게 여러번 읽는 것을 목표로 하며, 읽으며 배운 것을 정리해 볼 예정이다.

의미 있는 이름


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

그냥 상수나 뜻을 나타내지 않는 단어는 나중에 검색하기가 힘들다.
이름을 의미있게 지으면 함수가 길어지지만, 반면 이름으로 검색하기는 쉬워진다.

헝가리식 표기법

과거 C언어에서는 헝가리식 표기법을 중요하게 여겼다.
컴파일러가 타입을 따로 점검하지 않았으므로, 프로그래머에게 타입을 기억하할 단서가 필요했다.
요즘 나오는 언어는 컴파일러가 타입을 기억하고, 강제한다. 또한, IDE가 컴파일 전에 타입 오류를 감지한다.

굳이 프로그래밍 시에 변수명에 타입을 인코딩할 필요가 사라졌다.
오히려 이름이나 타입을 변경하기만 더 어렵게 할 뿐이다.

멤버 변수 접두어

멤버 변수에 m_이라는 접두어를 붙일 필요가 없다.
클래스와 함수는 접두어가 필요없을 정도로 작아져야 한다.
또한, 멤버 변수를 다른 색상으로 표시해주는 IDE를 사용하는 것이 마땅하다.

인터페이스 클래스와 구현 클래스

인터페이스 클래스와 구현 클래스가 존재할 때, 하나를 인코딩 해야한다면 구현 클래스 이름을 택하자.
나로서는 내가 사용하는 클래스가 인터페이스라는 사실을 남에게 알리고 싶지 않다.
~Impl을 뒤에 붙이는 것이 낫다.

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

루프 안에 들어가는 변수(i, j, k: 전통적으로 루프안에 들어가는 변수는 한 글자로 사용해 왔다)를 제외하고 남들이 명확하고 이해하기 쉬운 변수명을 사용해라.

클래스 이름

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

메서드 이름

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

  • 생성자를 오버로딩 할 땐 정적 팩토리 메소드를 사용한다.
    메서드는 인수를 설명하는 이름을 사용한다.
    1번 코드가 2번 코드보다 좋다.

1. Complex fulcrumPoint = Complex.FromRealNumber(23.0);
2. Complex fulcrumPoint = new Complex(23.0);
  • 생성자 사용을 제한하려면 private로 해당 생성자를 선언한다.

기발한 이름은 피해라

재미난 이름보다는 명료한 이름을 선택해라.
HolyHandGrenade( 가상의 무기(수류탄) ) 보다는 DeleteItems가 낫다.
의도를 분명하고 솔직하게 표현하라.

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

추상클래스를 구현하는 여러 클래스에서 동일한 기능의 메소드를 각자 다른 이름으로 짓지마라.
같은 기능, 같은 개념이라면 같은 의미의 단어를 사용해라.
같은 기능인데, get, fetch, retrieve 등으로 제각각 부르면 혼란스러울 뿐이다.

IDE에는 객체를 사용하면 그 객체가 제공하는 메소드 목록을 보여준다.
일관적이여야지 주석을 뒤져보지 않고 프로그래머가 올바른 메서드를 선택한다.

말장난을 하지마라

같은 의미가 아닌데 “일관성”을 고려해서 메소드명을 짓지마라.
만약 동일한 기능을 가진 메소드명을 여러 클래스에서 사용하는 것은 문제가 되지 않는다.
하지만, 다른 기능의 메소드를 생성할 때 해당 이름이 많다는 이유만으로 동일하게 짓는 것은 말장난일 뿐이다.
예로, a와 b를 더해주는 add라는 메소드가 존재하고, list에 추가하는 기능인 메소드를 만들었을 때 동일하게 add를 사용하면 안된다는 뜻이다.
insert, append 등으로 분명하게 구별해야할 필요가 있다.

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

코드를 읽는 사람도 프로그래머란 사실을 명심하라.
전산 용어, 알고리즘 이름, 패턴 이름, 수학 용어 등을 사용해도 된다.
기술 개념에는 기술 이름이 가장 적합한 선택이다.

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

만약, 위에서 말한 것처럼 해법 영역에서 마땅한 이름이 존재하지 않을 땐, 문제 영역에서 이름을 가져온다.
문제 영역 개념과 관련이 깊은 코드라면 문제 영역에서 이름을 가져와야 한다.

불필요한 맥락을 없애라

예를 들어, 고급 위발유 출전소(Gas Station Deluxe) 어플리케이션을 개발한다고 가정할 때, 모든 클래스 이름을 GSD로 시작하는 것은 바람직하지 못하다.

IDE에서는 G를 입력하고 자동완성을 누르면 G로 시작하는 모든 클래스가 나온다.
IDE는 개발자를 지원하는 도구이다. IDE를 방해할 이유는 없다.

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

함수

작게 만들어라!

함수를 만드는 첫 번째 규칙은 “작게”이다.
사실 이에 대한 근거를 책에서는 제공하고 있지 않다.
로버트의 오랜 경험을 바탕으로 작은 함수가 좋다고 확신하고 있다.

한가지만 해라!

함수는 한가지의 일만 해야한다.
함수가 한 가지 작업만 하는지 판단하는 방법은 단순히 다른 표현이 아닌 의미 있는 이름으로 다른 함수를 추출할 수 있다면 그 함수는 여러 작업을 하는 것이다.

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

Switch 문

서술적인 이름을 사용해라!

이름이 길어도 좋다.
서술적인 이름을 사용하면 개발자 머릿속에도 설계가 뚜렷해지므로 코드를 개선하기 쉬워진다.

함수 인수

함수에서 이상적인 인수 개수는 0개(무항)이다.
다음은 1개(단항), 다음은 2개(이항) 이다. 삼항은 왠만하면 피하는 것이 좋다.
4개 이상은 특별한 이유가 필요하다. 아니, 특별한 이유가 있어도 사용하면 안된다.
코드를 읽는 사람은 인수에 대해 해석을 해야한다. ( 현 시점에서 중요하지 않은 세부사항까지 알아야 한다. )

  • 많이 쓰는 단항 형식 함수에 인수를 1개 넘기는 경우는 인수에 질문을 던지는 경우, 인수를 뭔가로 변환해 결과를 반환하는 경우 두 가지 이다.
    인수를 뭔가로 변환하는 경우는 반환값을 꼭 돌려주도록 하자.
StringBuffer transform(StringBuffer in)
// 위의 방식이 밑의 방식보다 좋다.  
// 그냥 입력 인수를 그대로 돌려주더라도 위의 방식이 좋다.  
// 적어도 변환 형태를 유지하기 때문이다.  
void transform(StringBuffer out)
  • 플래그 인수 플래그 인수는 좋지 못하다.
    왜냐하면, 이미 함수에서 여러 가지를 처리한다고 말하는 셈이다.
    플래그 함수가 True이면 이걸, False이면 저걸 한다는 말이니까 말이다.

  • 이항 함수 인수가 2개이면, 1개인 함수보다 이해하기 어렵다.
    좌표계 함수처럼 인수를 2개 취하는 경우를 제외하고, 일반적으로 이항 함수는 인수에 자연적인 순서도 존재하지 않는다.
    프로그래머가 인수 순서를 기억하고 있어야 한다.
    이항 함수가 무조건 나쁜 것은 아니지만, 최소화하려는 노력을 해야한다.
    예를 들어 넘기는 인수 클래스 안에 메소드로 넣어 인수를 하나로 줄이는 방법이 있다.

writeField(outputStream, name)
// 위의 코드를 밑에 처럼 변환할 수 있다.  
outputStream.writeField(name)

// 혹은 인수 하나를 생성자에서 받은 뒤 사용하는 방법이 있다.  
  • 삼항 함수 당연히 인수가 3개인 것이 2개인 것보다 이해하기 어렵다.
    가능하면 인수를 묶어 객체로 만들어 인수를 줄이는 방법이 있다.
    결과적으로 눈속임이라고 생각할 수 있지만, 묶으려면 이름을 붙여야 함으로 결국 개념을 표현하게 된다.
    결국 두 3개의 개념에서 두 개의 개념만 인수로 넘길 수 있게 된다.

  • 동사와 키워드 함수의 의도나 인수의 순서와 의도를 제대로 표현하려면 좋은 함수 이름이 필수다.
    단항 함수는 함수와 인수가 동사/명사 쌍을 이뤄야 한다.
    ``` java // name이 “무엇이든” 쓴다.
    write(name)

// 위보다 더 좋은 방식이 있다.
// 이렇게 하면 name은 Field하는 것을 이제 알 수 있다.
writeField(name)


``` java
assertEquals(expected, actual)
// 위의 코드도 밑의 함수명처럼 바꾼다면 충분히 인수의 순서를 기억할 필요가 없어진다.  
assertExpectedEqualsActual(expected, actual)

부수 효과를 일으키지 마라!

부수효과1 는 거짓말이다!
함수에서는 한 가지 작업만을 한다고 약속 했는데, 남몰래 다른 작업을 하는 것이다.
이럴 경우 함수이름에 추가하여 사용하는 것이 좋다.
물론, 한 가지 작업을 한다는 규칙에는 위반하지만.

  • 출력 인수 과거 객체 지향 언어가 존재하지 않을 땐, 값을 바꾸기 위해서 인수로 넣어 해당 인수의 값을 변경했었다.
    appendFooter(a)
    

    위의 코드와 같은 느낌으로 작성하게 되면 밑에 a를 붙인다는 것인지, a의 끝에 붙인다는 것인지 알 수 가 없다.
    선언 형식을 봐야지만이 알 수 가 있다.

하지만, 이제는 그렇게 할 필요가 없다.
a.appendFooter() 이렇게 작성하면 된다.
출력 인수를 사용하지 않아야 한다.

명령과 조회를 분리해라!

함수는 명령을 수행하거나 무언가에 답하거나 둘 중 하나만 해야한다.
둘 다하면 혼란을 초래한다.

// 설정에 성공하면 True, 실패하면 False 를 반환하는 함수이다.
// 이런 식으로 코드를 짜면 안된다. 
boolean set()

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

오류 코드를 반환하면 사용자는 오류를 곧바로 해결해야만 하는 문제에 빠진다.
대신, 예외를 사용하면 원래 코드에서 오류 코드가 깔끔하게 제외된다.

  • try/catch 블럭 뽑아내기 try/catch 코드도 사실 추하다.
    정상 코드, 오류코드가 뒤엉켜 있다.
    블록 별로 별도 함수를 만들어 사용하는 것이 좋다.

  • 오류 처리도 한 가지 작업이다.
    그러므로 오류에 대한 작업만 하는 것이 맞다.

  • Error.java 의존성 자석 오류 코드를 반환하는 것은, 클래스(class) 든 열거형(enum)이든 어딘가에 정의가 되어 있다는 의미이다.
    다른 클래스에서 해당 클래스 혹은 Enum을 import 해서 사용해야 함으로, 수정이 발생하면 Error Enum을 사용하는 모든 클래스가 재 컴파일 되야한다. 그래서 기존에 존재하는 오류 코드를 사용하는 경우가 많다.

오류 코드 대신 예외를 사용하게 되면, Exception 클래스에서 파생된다. 즉, 원래 재 컴파일 할 필요가 없이 추가 할 수 있다.

반복하지 마라

구조적 프로그래밍

함수를 어떻게 짜죠?

Reference

본 블로그는 개인이 공부하고, 정리한 걸 기록하는 공간입니다.
오타, 오류가 존재할 수 있습니다. 댓글을 달아주시면 수정할 수 있도록 하겠습니다.
감사합니다 :D

  1. Side Effect라고 하며, 함수 내의 실행으로 인해 함수 외부가 영향을 받는 것을 의미한다. 매개변수도 포함이다. 

댓글남기기