클린 코드의 목적은 독자가 더 좋은 개발자를 위해 자신들의 시행착오를 통해 배운 경험을 전달하려는 목적이다.
40년 이상 개발을 해온 저자가 겪은 경험이란 매우 귀한 자산이다. 그들이 오늘은 함수를 구현할 때 어떤 시행착오 끝에 최선이라고 내놓는 방법을 알려준다고 한다.
읽기 쉽고 이해하기 쉬운 함수를 구현하기 위해서 첫 번째로 함수를 만들 때 작게 만들어야 한다는 것이다.
그리고 저자는 2~4줄 안으로 함수를 끊어 만들어야 한다고 제시한다.
조건문, 반복문 등에 들어가는 블록은 한 줄이어야 한다는 의미라고 한다.
대부분 우리는 조건문 및 반복문 안에서 함수를 호출하는 경우가 많다.
만약 이 블록 내의 코드가 한 줄이라면 외부 블록의 함수가 작아지고, 블록 안에서 호출하는 함수 이름이 적절하다면 코드를 이해하기도 쉽다고 설명한다.
정리하자면 중첩 구조가 생길 만큼 함수는 커지면 안 되고 들여 쓰기는 1단 혹은 2단을 넘어서면 안 된다. 간단명료해야 한다는 말이다.
두 번째로는 함수의 역할은 하나여야만 한다는 것이다.
물론 한 가지의 기준은 매우 다르다. 조건을 검사하는 것? 함수를 호출하는 것? 반환하는 것?
이에 대해서 저자는 함수 이름 아래에서 추상화 수준이 하나라면 여러 단계도 하나로 보는 것 같았다.
어쨌든 함수를 만드는 이유가 큰 개념을 다음 추상화 수준에서 여러 단계로 나눠 수행하기 위해서가 아니던가?
이를 간단히 한 가지만 하는지 판단하는 데 쉬운 방법이 있는데
바로 "함수 내 섹션을 나눌 수 있는가 없는가?"이다.
이러한 섹션이 단순히 다른 표현이 아닌 의미 있는 이름으로 다른 함수로 추출할 수 있다면
그 함수는 여러 가지 일을 하는 것이다.
함수의 역할이 한 가지라면 플래그 인수를 넘기는 일은 하지 말아야 한다. 플래그 인수를 넘긴다는 이야기는 함수가 한 번에 여러 가지 일을 한다는 것을 의미하고 있다.
그럼 다른 표현으로 만든다는 것이 무엇일까?
public static String renderPageWithSetupAndTeardowns(
PageData pageData, boolean isSuite) throws Exception {
if (isTestPage(pageData))
includesSetupAndTeardownPages(pageData, isSuite);
retrun pageData.getHtml();
}
renderPageWithSetupAndTeardowns이라는 함수는 함수 이름 아래 추상화 수준은 하나이다.
다만 진행되는 단계는 3단계일 뿐이다.
1. 테스트 페이지를 검사하고 테스트 페이지라면
2. 테스트 페이지에 에 설정 페이지와 해제 페이지를 포함시킨 후
3. 테스트 페이지 여부와 상관없이 페이지를 HTML로 렌더링 한다.
이를 같은 의미지만 다른 표현으로 만든다는 의미는 개발자에게 달렸다.
if문을 포함한 함수를 만들 것인가? 아닌가 만약 if문을 포함한 함수를 새로 만들었다면 이는 똑같은 내용을 다르게 표현할 뿐 추상화 수준은 변경되지 않는다.
진행되는 단계는 전부 동일한 추상화 수준으로 표현되고 있다.
분명 함수 내부에는 더 낮은 추상화 수준들로 구현되어 있을 것이다.
이야기는 위에서 아래로 읽는 것은 본능이자 규칙이다. 이러한 규칙을 통해 함수를 구성하면 추상화 수준을 일관되게 유지하면서 함수를 구현하기가 쉬워진다.
다음은 분기문 처리에 관한 이야기이다.
분기문이 함수 내에 존재하는 경우 코드도 매우 길어지며 단일 블록이나 함수를 선호한다고 한다.
또한 함수가 한 가지만 하도록 만들기도 매우 어렵다. 본질적으로 분기문은 N가지를 처리하도록 설계되어 있기에 완전히 피할 방법은 없다. 하지만 낮은 추상화 수준의 분기문을 저 차원 클래스에 숨기고 동일한 구조를 반복하지 않는 방법은 존재한다.
저자는 이 경우 추상화 팩토리 패턴을 사용했다.
추상화 팩토리 패턴을 간단히 설명하면 연관된 객체들의 조합을 만드는 팩토리를 구현하는 것이다.
분기문을 추상 팩토리 인터페이스에 숨긴 후 추상 팩토리의 구현체는 연관된 클래스의 인스턴스를 생성하여 넘기는 역할을 하도록 한다. 즉 분기에 따라 처리되어야 할 것을 더 추상화하여 처리하도록 하게 한다.
반복을 줄이고 더 추상화된 역할을 하는 함수를 제공하여 클라이언트와의 의존성을 낮춘다는 효과도 있지만 설계의 잘못 혹은 사용자 요구사항으로 팩토리 인터페이스의 수정이 일어난 경우 수정되어야 하는 부분이 많아진다는 점은 알아두어야겠다.
함수의 의도나 인수의 순서와 의도를 제대로 표현하려면 좋은 함수 이름이 필수이다.
단항 함수는 함수와 인수가 동사/명사 쌍을 이뤄야 한다.
예를 들어 write(name)은 쉽게 이해한다.
좀 더 나은 이름은 writeField(name) 이름은 필드라는 의미가 내포되어 있다.
또는 함수 이름에 인수 이름을 넣는 방법이다.
assertEquals보다 assertExpectedEqualsActual(expected, actual)이 더 좋다.
오류 처리도 한 가지 작업이다.
함수는 한 가지 작업을 해야 한다. 오류 처리도 그 작업에 속한다. 그러므로 오류를 처리하는 함수는 오류만 처리해야 마땅하다. 즉 함수에 try 키워드가 있다면 catch/finally 문으로 끝나야 한다.
함수는 어떻게 작성해야 할까
처음부터 잘 짜인 맥락을 포함한 함수를 작성하기엔 무리가 있다. 물론 처음부터 잘 짜이면 좋겠지만 대개 초안은 서투르고 어수선하다. 처음에는 길고 복잡하고, 중복도 많고, 인수도 많을 수 있고 이름도 되게 즉흥적이다. 하지만 이 코드를 빠짐없이 테스트하는 단위 테스트 케이스도 만든다.
그런 다음 코드를 다듬고, 함수를 만들고 , 이름을 바꾸고 중복을 제거하고 가끔 클래스를 쪼개기도 한다.
이 와중에도 코드는 항상 단위 테스트를 통과한다. 최종적으로 이상향에 가까운 함수가 얻어진다.
처음부터 바로 좋은 코드를 작성하는 사람은 없을 것이다.
[3장을 끝으로]
하루에 한 파트씩 읽고 이해하려고 했다.
이 책은 매우 어렵고 이해하기 난해하다. 배경지식으로 객채 지향을 염두로 두고 있으며 디자인 패턴을 알아야 하고 여러 소프트웨어 방법론을 소개한다.
이 세 가지만으로도 매우 무거운 책이라고 나는 느낀다. 다만 어느 정도 벽에 막히거나 현실에 지쳤던 사람이 읽는다면 왜 늦게 까지 책을 읽는 게 재밌는지 알게 될 것이다.
'독서에서 한걸음' 카테고리의 다른 글
Clean Code .Part5 (0) | 2022.04.10 |
---|---|
Clean Code .Part4 (0) | 2022.04.09 |
Clean Code .Part 2 (0) | 2022.04.06 |
도메인 (0) | 2022.04.01 |
메시지 (0) | 2022.03.31 |
댓글