[리팩터링(2판)] CHAPTER 3 — 코드에서 나는 악취 \_ 켄트 벡, 마튼 파울러 공저

hansol yang
6 min readOct 4, 2021
  • 냄새 나면 당장 갈아라. — 켄트 백 할머니의 육아 원칙
  • 이 장은 켄트와 내가 함께 집필했다는 점을 강조하기 위해 ‘나’가 아닌 ‘우리’란 표현을 사용한다. 어느 부분을 누가 쓴 것인지는 쉽게 구분할 수 있다. 웃긴 농담은 필자가 쓴 것이고 나머지는 켄트가 쓴 것이다.
  • 우리는 주석을 달아야 할 만한 부분은 무조건 함수로 만든다. 그 함수 본문에는 원래 주석으로 설명하려던 코드가 담기고, 함수 이름은 동작 방식이 아닌 ‘의도(intention)’가 드러나게 짓는다. 이렇게 함수로 묶는 코드는 여러 줄일 수도 있고 단 한 줄일 수도 있다. 심지어 원래 코드보다 길어지더라도 함수로 뽑는다. 단, 함수 이름에 코드의 목적을 드러내야 한다. 여기서 핵심은 함수의 길이가 아닌, 함수의 목적(의도)과 구현 코드의 괴리가 얼마나 큰가다. 즉, ‘무엇을 하는지’를 코다가 잘 설명해주지 못할수록 함수로 만드는 게 유리하다.
  • 전역 데이터는 코드베이스 어디에서든 건드릴 수 있고, 값을 누가 바꿨는지 찾아낼 메커니즘이 없다는 게 문제다. 그래서 마치 ‘유령 같은 원격작용(spooky action at a distance)’처럼, 버그는 끊임없이 발생하는데 그 원인이 되는 코드를 찾아내기가 굉장히 어렵다. 전역 데이터의 대표적인 형태는 전역 변수지만 클래스 변수와 싱글톤(singleton)에서도 같은 문제가 발생한다.
    이를 방지하기 위해 우리가 사용하는 대표적인 리팩터링은 변수 캡슐화하기다. 다른 코드에서 오염시킬 가능성이 있는 데이터를 발견할 때마다 이 기법을 가장 먼저 적용한다. 이런 데이터를 함수로 감싸는 것만으로도 데이터를 수정하는 부분을 쉽게 찾을 수 있고 접근을 통제할 수 있게 된다. 더 나아가 접근자 함수들을 클래스나 모듈에 집어넣고 그 안에서만 사용할 수 있도록 접근 범위를 최소로 줄이는 것도 좋다.
  • 우리는 소프트웨어의 구조를 변경하기 쉬운 형태로 조직한다. 소프트웨어는 자고로 소프트해야 마땅하기 때문이다. 코드를 수정할 때는 시스템에서 고쳐야 할 딱 한군데를 찾아서 그 부분만 수정할 수 있기를 바란다.
  • 뒤엉킨 변경은 단일 책임 원칙(Single Reponsibility Principle(SRP))이 제대로 지켜지지 않을 때 나타난다. 즉, 하나의 모듈이 서로 다른 이유들로 인해 여러 가지 방식으로 변경되는 일이 많을 때 발생한다.
  • 물론 데이터베이스와 금융 상품 여러 개를 추가하고 나서야 이 악취가 느껴지는 경우도 많다. 개발 초기에는 맥락 사이의 경계를 명확히 나누기가 어렵고 소프트웨어 시스템의 기능이 변경되면서 이 경계도 끊임없이 움직이기 때문이다.
    데이터베이스에서 데이터를 가져와서 금융 상품 로직에서 처리해야 하는 일처럼 순차적으로 실행되는 게 자연스러운 맥락이라면, 다음 맥락에 필요한 데이터를 특정한 데이터 구조에 담아 전달하게 하는 식으로 단계를 분리한다(단계 쪼개기). 전체 처리 과정 곳곳에서 각기 다른 맥락의 함수를 호출하는 빈도가 높다면, 각 맥락에 해당하는 적당한 모듈들을 만들어서 관련 함수들을 모은다(함수 옮기기). 그러면 처리 과정이 맥락별로 구분된다. 이때 여러 맥락의 일에 관여하는 함수가 있다면 옮기기 전에 함수 추출하기부터 수행한다. 모듈이 클래스라면 클래스 추출하기가 맥락별 분리 방법을 잘 안내해줄 것이다.
  • 사실 우리는 작은 함수와 클래스에 지나칠 정도로 집착하지만, 코드를 재구성하는 중간 과정에서는 큰 덩어리로 뭉쳐지는데 개의치 않는다.
  • 프로그램을 모듈화할 때는 코드를 여러 영역으로 나눈 뒤 영역 안에서 이뤄지는 상호작용은 최대한 늘리고 영역 사이에서 이뤄지는 상호작용은 최소로 줄이는 데 주력한다. 기능 편애는 흔히 어떤 함수가 자기가 속한 모듈의 함수나 데이터보다 다른 모듈의 함수나 데이터와 상호작용할 일이 더 많을 때 풍기는 냄새다.
  • 추측성 일반화는 우리가 민감하게 반응하는 냄새로, 이름은 브라이언 푸트(Brian Foote)가 지어줬다. 이 냄새는 ‘나중에 필요할 거야’라는 생각으로 당장은 필요 없는 모든 종류의 후킹(hooking) 포인트와 특이 케이스 처리 로직을 작성해둔 코드에서 풍긴다. 그 결과는 물론 이해하거나 관리하기 어려워진 코드다. 미래를 대비해 작성한 부분을 실제로 사용하게 되면 다행이지만, 그렇지 않는다면 쓸데없는 낭비일 뿐이다. 당장 걸리적거리는 코드는 눈앞에서 치워버리자.
  • 간혹 특정 상황에서만 값이 설정되는 필드를 가진 클래스도 있다. 하지만 객체를 가져올 때는 당연히 모든 필드가 채워져 있으리라 기대하는 게 보통이라, 이렇게 임시 필드를 갖도록 작성하면 코드를 이해하기 어렵다. 그래서 사용자는 쓰이지 않는 것처럼 보이는 필드가 존재하는 이유를 파악하느라 머리를 싸매게 된다.
    이렇게 덩그러니 떨어져 있는 필드들을 발견하면 클래스 추출하기로 제 살 곳을 찾아준다. 그런 다음 함수 옮기기로 임시 필드들과 관련된 코드를 모조리 새 클래스에 몰아넣는다. 또한, 임시 필드들이 유효한지를 확인한 후 동작하는 조건부 로직이 있을 수 있는데, 특이 케이스 추가하기로 필드들이 유효하지 않을 때를 위한 대안 클래스를 만들어서 제거할 수 있다.
  • 객체의 대표적인 기능 하나로, 외부로부터 세부사항을 숨겨주는 캡슐화(encapsulation)가 있다. 캡슐화하는 과정에서는 위임(delegation)이 자주 활용된다. 예를 들어 여러분이 팀장에게 미팅을 요청한다고 해보자. 팀장은 자신의 일정을 확인한 후 답을 준다. 이러면 끝이다. 팀장이 종이 다이어리를 쓰든, 일정 서비스를 쓰든, 따로 비서를 두든 우리는 알 바 아니다.
  • 가령 부분부분 상당량의 로직이 똑같은 100줄짜리 메서드 다섯 개가 있다면 각각의 공통 부분을 작은 메서드들로 뽑아내자. 그러면 원래의 다섯 메서드들에는 작은 메서드들을 호출하는 코드 10줄만 남게 될지도 모른다.
  • 특정 코드 블록이 하는 일에 주석을 남기고 싶다면 함수 추출하기를 적용해본다. 이미 추출되어 있는 함수임에도 여전히 설명이 필요하다면 함수 선언 바꾸기로 함수 이름을 바꿔본다. 시스템이 동작하기 위한 선행조건을 명시하고 싶다면 어서션 추가하기가 대기하고 있다.
    주석을 남겨야겠다는 생각이 들면, 가장 먼저 주석이 필요 없는 코드로 리팩터링해본다.
    뭘 할지 모를 때라면 주석을 달아두면 좋다. 현재 진행 상황뿐만 아니라 확실하지 않은 부분에 주석에 남긴다. 코드를 지금처럼 작성한 이유를 설명하는 용도로 달 수도 있다. 이런 정보는 나중에 코드를 수정해야 할 프로그래머에게, 특히 건망증이 심한 프로그래머에게 도움될 것이다.
hs: 문제를 해결하려면 문제를 먼저 알아야 하기에 이번장이 굉장히 인상깊었다. 이번 챕터에서 소개하는 많은 냄새들을 움찔움찔하며 봤지만 특히나 기능 편애와 같은 내용은 종종 고민이 되는 부분이라 재미있었다. 이런 냄새들을 잘 알아두고 감지할 수 있도록 해야겠다고 생각한다. 냄새를 감지하고 조심하는 것으로 시작해도 좋다고 생각한다. 거기에서 더 나아가 그에 맞는 해결방법을 활용한다면 더 좋을 것이고!

--

--