귀퉁이 서재

[로버드 C.마틴] 클린 코드 본문

책과 사유

[로버드 C.마틴] 클린 코드

Baek Kyun Shin 2019. 7. 23. 23:22

개발자 필독서 중 하나인 클린 코드는 어떻게 하면 코드를 깨끗하고 품질 좋게 짤 수 있는지에 대한 방법론을 설명한 책이다. 밥 아저씨라 불리는 로버트 C. 마틴이 쓴 책이다. 이제는 나이가 지긋이 들어 백발의 노인이 다 된 전설적인 분이다. 이 책은 지금까지 내가 얼마나 코드를 엉망으로 짰는지 일깨워줬다. 그동안 네이밍, 추상화, 코딩 컨벤션 정도만 주의한 수준에서 돌아가는 코드를 짰었다. 그마저 급할 땐 무시하기도 했었다. 혼자 개발을 하든 여럿이서 개발을 하든 깨끗한 코드를 짜는 건 정말 중요한 일인 것 같다. 혼자 개발을 하더라도 코드를 엉망으로 짜면 나중에 다시 그 코드를 볼 때 무슨 코드인지 못 알아볼 수도 있다. 누구나 알아볼 수 있는 깨끗한 코드를 짜기 위해서는, 이 책에 소개된 귀중한 방법론을 두고두고 상기해야 할 것이다. 

1장 깨끗한 코드

깨끗한 코드(클린 코드)란 어떤 것일까? 거의 모든 플랫폼에서 거의 모든 언어로 코드를 구현해온 론 제프리스가 말하는 클린 코드는 이렇다.

내게 있어 표현력은 의미 있는 이름을 포함한다. 보통 나는 확정하기 전에 이름을 여러 차례 바꾼다. 하지만 표현력은 이름에만 국한되지 않는다. 객체가 여러 기능을 수행한다면 여러 객체로 나눈다. 중복과 표현력만 신경 써도 깨끗한 코드라는 목표에 성큼 다가선다.... 중략... 중복 줄이기, 표현력 높이기, 초반부터 간단한 추상화 고려하기. 내게는 이 세 가지가 깨끗한 코드를 만드는 비결이다.

중복 제거, 표현력 높이기, 추상화. 세가지만 말했지만 이 세가지를 잘 지키는 것도 많은 노력이 필요한 작업이다.

2장 의미 있는 이름

"의도가 분명하게 이름을 지으라"고 말하기는 쉽다. 여기서는 의도가 분명한 이름이 정말로 중요하다는 사실을 거듭 강조한다. 좋은 이름을 지으려면 시간이 걸리지만 좋은 이름으로 절약하는 시간이 훨씬 더 많다.
이름이 달라야 한다면 의미도 달라져야 한다. 연속적인 숫자를 덧붙인 이름(a1, a2,... , aN)은 의도적인 이름과 정반대다. 이런 이름은 아무런 정보를 제공하지 못하는 이름일 뿐이다. 
Product라는 클래스가 있다고 가정하자. 다른 클래스를 ProductInfo 혹은 ProductData라 부른다면 개념을 구분하지 않은 채 이름만 달리 한 경우다. Product가 있다는 이유만으로 ProductInfo라 이름 지어서는 안 된다는 말이다.
getActiveAccount(), getActiveAccounts(), getActiveAccountInfo() 이 세 함수가 어떤 기능을 하는지 알겠는가? 읽는 사람이 차이를 알도록 이름을 지어라.
클래스나 객체 이름은 명사나 명사구가 적합하다. 메서드 이름은 동사나 동사구가 적합하다. 접근자, 변경자, 조건자는 javabean 표준에 따라 값 앞에 get, set, is를 붙인다.
한 개념에 한 단어를 사용하라. ...중략... 똑같은 메서드를 클래스마다 fetch, retrieve, get으로 제각각 부르면 혼란스럽다.

솔직히 그간 네이밍을 크게 신경 쓰지는 않았다. 의미가 통하는 선에서 적당히 지었다. 하지만 이장에서는 네이밍의 중요성을 강조한다. 적당한 이름이 떠오르지 않는다면 충분한 시간을 들여 네이밍을 해도 될 만큼 가치가 크다고 한다. 다른 사람이 변수 이름, 함수 이름만 봐도 그것이 무엇인지 명확하게 알 수 있도록 네이밍을 해야 한다는 것이다. 

3장 함수

함수를 만드는 첫째 규칙은 '작게!'다. 함수를 만드는 둘째 규칙은 '더 작게!'다. 이 규칙은 근거를 대기가 곤란하다. 함수가 작을수록 더 좋다는 증거나 자료를 제시하기도 어렵다. 하지만 나는 지난 40여 년 동안 온갖 크기로 함수를 구현해봤다. 거의 3,000줄에 육박하는 끔찍한 함수도 짰다. 100줄에서 300줄에 달하는 함수도 많이 짰다. 20줄에서 30줄 정도인 함수도 짰다. 지금까지 경험을 바탕으로 그리고 오랜 시행착오를 바탕으로 나는 작은 함수가 좋다고 확신한다.... 중략... 함수는 100줄을 넘어서는 안 된다. 아니, 20줄도 길다.
함수는 한 가지를 해야 한다. 그 한 가지를 잘해야 한다. 그 한 가지만을 해야 한다.
서술적인 이름을 사용하라!... 중략... 이름이 길어도 괜찮다. 길고 서술적인 이름이 짧고 어려운 이름보다 좋다. 길고 서술적인 이름이 길고 서술적인 주석보다 좋다.... 중략... 이름을 정하느라 시간을 들여도 괜찮다. 이런저런 이름을 넣어 코드를 읽어보면 더 좋다.
삼수에서 이상적인 인수 개수는 0개다. 다음은 1개고, 다음은 2개다. 3개는 가능한 피하는 편이 좋다. 4개 이상은 특별한 이유가 필요하다. 특별한 이유가 있어도 사용하면 안 된다.
플래그 인수는 추하다. 함수로 부울 값을 넘기는 관례는 정말로 끔찍하다. 왜냐고? 함수가 한꺼번에 여러 가지를 처리한다고 대놓고 공표하는 셈이니까! 플래그가 참이면 이걸 하고 거짓이면 저걸 한다는 말이니까!
내가 함수를 짤 때도 마찬가지다. 처음에는 길고 복잡하다. 들여 쓰기 단계도 많고 중복된 루프도 많다. 인수 목록도 아주 길다. 이름은 즉흥적이고 코드는 중복된다. 하지만 나는 그 서투른 코드를 빠짐없이 테스트하는 단위 테스트 케이스도 만든다. 그런 다음 나는 코드를 다듬고, 함수를 만들고, 이름을 바꾸고, 중복을 제거한다. 메서드를 줄이고 순서를 바꾼다. 때로는 전체 클래스를 쪼개기도 한다. 이 와중에도 코드는 항상 단위 테스트를 통과한다. 최종적으로는 이 장에서 설명한 규칙을 따르는 함수가 얻어진다. 처음부터 탁 짜내지 않는다. 그게 가능한 사람은 없으리라.

코드를 추상화하고 중복을 피하기 위해 함수를 작성한다. 함수는 최대한 작게 만들어 인수도 최소화해야 하고, 한 가지 일만 하게 해야 한다. 당연히 함수의 이름도 명확해야 한다. 하지만 처음부터 완벽한 함수를 만들 수는 없으므로 우선 돌아가는 코드를 짠 뒤에 리팩터링을 하면 된다고 한다.

4장 주석

코드에 주석을 추가하는 일반적인 이유는 코드 품질이 나쁘기 때문이다. 표현력이 풍부하고 깔끔하며 주석이 거의 없는 코드가, 복잡하고 어수선하며 주석이 많이 달린 코드보다 훨씬 좋다. 자신이 저지른 난장판을 주석으로 설명하려 애쓰는 대신에 그 난장판을 깨끗이 치우는 데 시간을 보내라!

코드로 표현할 수 없는 정보를 전달하기 위해 주석을 쓴다. 하지만 주석에도 좋은 주석과 나쁜 주석이 있다. 좋은 주석으로는 정보를 제공하는 주석, 의도를 설명하는 주석, 의미를 명료하게 밝히는 주석, 중요성을 강조하는 주석 등이 있다. 나쁜 주석으로는 주절거리는 주석, 같은 이야기를 중복하는 주석, 오해할 여지가 있는 주석, 의무적으로 다는 주석, 있으나 마나 한 주석, 함수나 변수로 표현할 수 있는 주석, 위치를 표시하는 주석, 닫는 괄호에 다는 주석, 주석으로 처리한 코드 등이 있다. 주석으로 코드를 설명하면 안 된다. 코드로 표현할 수 있는 부분은 최대한 코드로 표현하고, 정 안 되는 부분만 주석을 다는 습관을 들이는 게 필요할 것이다. 사실 지금까지 코드를 짤 때 주석은 '친절함의 척도'라고 생각을 했다. 많이 달수록 좋다는 생각이었고, 주석을 달지 않는 건 개발자가 귀찮아서 그렇다고 생각을 했었다. 내 생각을 180도 바꾸어주었다.

5장 형식 맞추기

대부분 200줄 정도인 파일로도 커다란 시스템을 구축할 수 있다.
한 함수가 다른 함수를 호출한다면 두 함수는 세로로 가까이 배치한다. 또한 가능하다면 호출하는 함수를 호출되는 함수보다 먼저 배치한다.... 중략... 호출되는 함수를 찾기가 쉬워지며, 그만큼 모듈 전체의 가독성도 높아진다.

거대한 시스템이라고 한 파일에 어마어마한 라인의 코드가 있는 것은 아니다. 추상화의 개념을 적용해 파일도 여러 개로 나눌 수 있다. 

6장 객체와 자료 구조

객체는 동작을 공개하고 자료를 숨긴다. 그래서 기존 동작을 변경하지 않으면서 새 객체 타입을 추가하기는 쉬운 반면, 기존 객체에 새 동작을 추가하기는 어렵다. 자료 구조는 별다른 동작 없이 자료를 노출한다. 그래서 기존 자료 구조에 새 동작을 추가하기는 쉬우나, 기존 함수에 새 자료 구조를 추가하기는 어렵다. 시스템을 구현할 때, 새로운 자료 타입을 추가하는 유연성이 필요하면 객체가 더 적합하다. 다른 경우로 새로운 동작을 추가하는 유연성이 필요하면 자료 구조와 절차적인 코드가 더 적합하다.

객체 지향과 절차 지향에 대한 설명이다. '모든 것은 객체다'라는 생각보다는 때에 맞게 절차 지향적으로 코드를 짜도 좋다고 한다.

9장 단위 테스트

첫째 법칙: 실패하는 단위 테스트를 작성할 때까지 실제 코드를 작성하지 않는다.
둘재 법칙: 컴파일은 실패하지 않으면서 실행이 실패하는 정도로만 단위 테스트를 작성한다.
셋째 법칙: 현재 실패하는 테스트를 통과할 정도로만 실제 코드를 작성한다.
위 세 가지 규칙을 따르면 개발과 테스트가 대략 30초 주기로 묶인다. 테스트 코드와 실제 코드가 함께 나올뿐더러 테스트 코드가 실제 코드보다 불과 몇 초 전에 나온다.

TDD(Test Driven Development, 테스트 주도 개발)을 해본 적은 없다. 혹시 나중에 참고가 될까 하여 기록으로 남겨 둔다.

10장 클래스

클래스를 만들 때 첫 번째 규칙은 크기다. 클래스는 작아야 한다. 두 번째 규칙도 크기다. 더 작아야 한다.
클래스 이름은 해당 클래스 책임을 기술해야 한다. 간결한 이름이 떠오르지 않는다면 필경 클래스 크기가 너무 커서 그렇다. 클래스 이름이 모호하다면 필경 클래스 책임이 너무 많아서다. 예를 들어, 클래스 이름에 Processor, Manager, Super 등과 같이 모호하나 단어가 있다면 클래스에다 여러 책이을 떠 안겼다는 증거다.
단일 책임 원칙(SRP, Single Responsibility Principle)은 클래스나 모듈을 변경할 이유가 하나, 단 하나뿐이어야 한다는 원칙이다. 

함수와 마찬가지로 클래스의 크기도 작아야 한다. 책임이 많아서도 안 된다. 클래스든 함수든 파일이든 작을수록 좋다는 걸 알 수 있다. 제조업에서도 재고가 줄어들어야 한다고 강조한다. 재고가 줄어들어야 어디서 문제가 있는지 바로 알 수 있기 때문이다. 코드에서도 마찬가지인 것 같다. 함수, 클래스가 작아야 버그가 있을 때 어디서 문제가 발생했는지 쉽게 알 수 있을 것이다. 

14장 점진적인 개선

깨끗한 코드를 짜려면 먼저 지저분한 코드를 짠 뒤에 정리해야 한다. 

3장에서도 설명한 것이다. 글을 쓸 때도 처음부터 잘 쓰기는 힘들다. 생각나는 대로 막 쓴 뒤 퇴고를 하는 것이 좋듯이 우선 돌아가는 지저분한 코드를 짠 뒤 리팩터링을 하는 것이 깨끗한 코드를 짜는 방법이라고 한다.

17장 냄새와 휴리스틱

대다수 괴상한 코드는 사람들이 알고리즘을 충분히 이해하지 않은 채 코드를 구현한 탓이다. 잠시 멈추고 실제 알고리즘을 고민하는 대신 여기저기 if문과 플래그를 넣어보며 코드를 돌리는 탓이다.... 중략... 이 방식이 틀렸다는 말이 아니다. 사실상 대다수 상황에서는 원하는 대로 함수를 돌리는 유일한 방법이다. 하지만 '돌아간다'라고 말하기는 뭔가 부족하다. 구현이 끝났다고 선언하기 전에 함수가 돌아가는 방식을 확실히 이해하는지 확인하라. 작성자가 알고리즘이 올바르다는 사실을 알아야 한다.

이 문장을 읽고 좀 뜨끔했다. 이래 저래 코드를 바꾼 뒤 실행해보고 잘 돌아가면 '잘 되네!'라고 생각했었던 적도 있기 때문이다. 물론 그렇게 코드를 짰어도 궁극적으로 그 코드가 어떻게 돌아가는지 명확히 알고 있어야 한다는 것이다. 알고리즘을 명확히 알지 못하고 '구현'에만 신경을 쓰다 보면 언젠가 버그와 마주할 것임이 분명하다. 

코드를 짜는 일은 재밌다. 막 짜는 것보다 깨끗한 코드에 유의하며 짜는 것은 더 재밌는 일이 아닐 수 없다.

Comments