클린 코드 - 3. 함수

이정우·2021년 9월 7일
1

Clean Code

목록 보기
3/10

함수란, 프로그램을 구성하는 가장 기본적인 단위이다.

작게 만들어라

함수를 만드는 첫째 규칙은 작게, 둘째 규칙은 더 작게다.

블록과 들여쓰기

IF, ELSE, WHILE 등에 들어가는 블록은 한 줄로 작성하며, 내부에서 다른 함수를 호출하도록 작성한다.
이를 통해 바깥의 함수의 크기를 줄일 수 있을 뿐만 아니라, 호출하는 함수 이름을 적절히 짓는다면 코드를 이해하기도 쉬워진다.


한 가지만 해라

함수를 만드는 이유는 큰 개념을 다음 수준에서 여러 단계로 나눠 수행하기 위해서이다. 따라서, 각각의 함수는 한 가지 기능만을 잘 수행하는 것이 중요하다.

함수가 한 가지 기능을 수행하는지 판단하는 위해서는 의미 있는 이름으로 다른 함수를 만들어낼 수 있는지 확인하면 된다.


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

함수가 확실하게 한 가지 기능을 수행하려면 함수 내 모든 문장의 추상화 수준이 동일해야 한다. 추상화 수준이 다른 코드들이 존재하면 가독성이 낮아지게 된다.

내려가기 규칙 : 위에서 아래로 코드 읽기
문단을 읽듯이 코드를 구현하면 한 단계씩 추상화 단계가 낮아지기 때문에 추상화 수준을 유지하기 쉬워진다.


Switch 문

Switchif ~ else if ~ else 등으로 여러 분기가 있는 조건문은 작게 만들기가 어려울 뿐만 아니라, 한 가지 기능만을 수행하는 것도 어렵다.

Switch를 아예 쓰지 않는 것은 어렵기 때문에, 다형성을 이용하여 저차원의 클래스 내에서 사용하여 겉으로 드러내지 않게 작성한다.


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

함수의 이름이 수행하는 기능을 서술적으로 표현하고 있다면 그 코드는 잘 짜여진 코드라고 부를 수 있다.
서술적인 이름을 사용한 함수는 머릿속에서 설계를 뚜렷하게 할 수 있고, 코드를 개선하기도 쉬워진다.

이름이 길어도 괜찮고, 이름을 짓는데 오래 걸려도 괜찮다. 단, 명심해야 하는 것은 일관성을 가지고 이름을 지어야한다는 것이다.


함수 인수

함수에서 가장 이상적인 인수 개수는 0개, 1개, 2개, ··· 순이다. 가능하다면 인수를 사용하지 않는 것이 코드를 이해하기가 더 쉽다.
함수를 테스트할 때도, 인수가 적은 경우가 테스트 케이스를 만드는 것이 더욱 간단할 것이다.

많이 쓰는 단항 형식

함수에 인수 하나를 넘기는 가장 흔한 경우는 두 가지다.
boolean fileExists("myFile")과 같이 인수를 통해 질문을 하는 경우와, InputStream openFile("myFile")과 같이 인수를 다른 무언가로 변환하는 경우이다.
이외에도 이벤트 함수가 있는데, 이벤트 함수는 입력 인수만 존재하며 시스템 상태를 바꾸는 역할을 수행한다.

함수의 이름을 지을 때는, 이러한 경우들을 구분해서 지어야 한다.

이외의 경우는 가급적 단항 함수를 사용하지 않는 것이 좋다.

플래그 인수

함수로 boolean 값을 전달하는 것은, 대놓고 함수가 여러 가지 기능을 처리한다는 것을 나타내는 것이다.

플래그 인수를 전달하는 것이 아니라, 각각을 처리하는 함수를 따로 구현하는 것이 옳은 방법이다.

이항 함수

인수 두 개가 명백한 의미를 가지고 있더라도, 인수가 1개인 함수보다는 2개인 함수가 이해하기 어렵다.
또한, 함수를 호출할 때 각각의 인수의 순서를 기억하고 있어야 한다.

따라서 이항 함수는 될 수 있는 한, 단항 함수로 변경하는 것이 좋다.

삼항 함수

삼항 함수는 이항 함수의 단점이 더욱 부각된다. 왠만하면 사용하지 않는 것이 좋다.

인수 객체

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

인수가 여러 개 필요한 상황이라면, 일부를 새로운 객체로 만들어서 전달하자.

인수 목록

때로는 String.format과 같이 인수 개수가 가변적인 함수가 필요할 수 있다.

String.format("My name is %s, and I'm %d years old", name, age);

하지만 가변 인수를 동등하게 생각한다면 public String format(String format, Object ...args)와 같이 List 형 인수 하나로 취급이 가능하다.
실제로는 String.format 함수는 가변적인 인수를 가지는 것이 아닌, 이항 함수인 것이다.

이와 같이 가변 인수를 가지는 함수는 모두 같은 원리가 적용되지만, 3개를 넘는 인수를 사용하는 경우는 최대한 자제하자.

동사와 키워드

함수의 의미를 제대로 표현하려면 좋은 이름이 필요하다.

단항 함수는 인수가 동사/명사 쌍을 이뤄야 하고, 다항 함수는 함수 이름에 인수 이름을 넣어 순서를 기억하지 않도록 작성한다.

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

부수 효과는 많은 단점을 불러온다. 함수 이름만 보고 함수를 호출할 경우 의도하지 않은 기능을 수행할 수 있기 때문이다.

출력 인수

appendFooter(s)라는 함수가 있다고 생각해보자.

s에 Footer를 추가하는 함수인지, Footer에 s를 추가하는 함수인지, s가 무엇인지 알기가 어렵다.
확인을 위해서는 함수의 선언부를 찾아야 하는데, 이는 바람직하지 못한 행동이다.

따라서 s.appendFooter()과 같이 상태를 변경해야 할 경우에는, 함수가 속한 객체의 상태를 변경하는 것이 좋다.


명령과 조회를 분리하라!

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

public boolean set(String attribute, String value);

set 함수는 이름이 attribute인 속성을 찾아 value로 변경하며, 변경에 성공하면 true, 실패하면 false를 반환한다.

if(set("username", "sorious")) { ... }

set 함수를 사용하여 위와 같이 코드를 작성했을 경우, 어떠한 동작을 하는지 알기 힘들 것이다.
username을 sorious로 바꾸는 건지, username이 sorious인지 알기가 어렵기 때문이다.

이는 set 함수가 조회와 변경을 동시에 수행하고 있기 때문이다. 따라서 이를 분리하여 사용하는 것이 좋다.

if(attributeExists("username")){
    setAttribute("username", "sorious");
    ...
}

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

오류 코드를 통해 오류를 잡고자 하는 경우, 각각의 반환값마다 조건문을 통해 확인을 해야한다.
이는 중첩된 if문을 부르기 때문에 가독성이 낮아진다.

따라서 try ~ catch문을 사용하는 것이 좋다.

try ~ catch 블록 뽑아내기

try ~ catch문 역시 가독성을 낮추는 요인 중 하나이기 때문에, trycatch를 각각의 함수로 뽑아내서 사용하는 것이 좋다.

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

함수는 한 가지 작업만 처리해야 하는데, 오류 처리도 한 가지 작업에 속한다.
따라서 try가 함수 내에 존재한다면, 해당 함수는 catchfinally로 끝나도록 작성해야 한다.

Error.java

오류 코드를 반환하는 경우는, 클래스나 enum 등 어디선가 오류 코드 목록을 작성했다는 뜻이다.
이를 사용하기 위해서 각각의 클래스는 오류 코드를 사용하기 위해 import를 해야한다.

하지만, 목록이 변경된다면 import를 한 클래스 하나하나를 변경해야 하는 번거로움이 발생한다.
따라서 오류 코드 대신 Exception 클래스를 상속받는 예외 클래스를 선언하여 사용해야 한다.


반복하지 마라!

중복되는 코드를 제거하면 모듈의 가독성과 유지보수에 장점이 생긴다.


구조적 프로그래밍

다익스트라(dijkstra)의 구조적 프로그래밍 원칙에서는 모든 함수는 return을 하나만 가져야 한다고 말한다.
루프 안에서 breakcontinue를 사용해서는 안 되며, goto절대로 안 된다고 한다.

하지만 이는 함수가 아주 클 때만 이점이 있기 때문에, 함수가 작다면 사용해도 좋다.
그래도 goto는 함수가 클 때만 사용해야 한다.


함수를 어떻게 짜죠?

함수를 만들면, 처음에는 코드가 길고 복잡하며 앞서 말했던 함수 작성 규칙을 지키지 못하는 경우가 많다.

이를 해결하기 위해서는 단위 케스트 케이스를 만들고, 코드를 계속해서 수정하며 발전시켜야 한다.

0개의 댓글