코드에서 나는 악취

niyu·2022년 1월 10일

리팩터링 2판

목록 보기
3/11
post-thumbnail

어떤 코드가 악취가 나는 코드일까? 그리고 악취가 나는 코드를 어떻게 리팩터링하면 좋을까? 🤔

글에서 ... 으로 강조 표시한 부분들은 저자가 해법으로 제시한 리팩터링 기법이니, 자세한 부분은 책의 6~12장에서 찾아보자.

기이한 이름

함수, 모듈, 변수, 클래스 등 그 이름만 보고도 각각이 무슨 일을 하고 어떻게 사용해야 하는 지 명확히 알 수 있도록 신경 써어 이름을 지어야 한다. 마땅한 이름이 떠오르지 않는다면 설계에 더 근본적인 문제가 숨어 있을 가능성이 높다. 그래서 혼란스러운 이름을 잘 정리하다 보면 코드가 훨씬 간결해질 때가 많다.

💡 해법: 함수 선언 바꾸기, 변수 이름 바꾸기, 필드 이름 바꾸기

코드를 명료하게 표현하는 데 가장 중요한 요소 하나는 바로 이름이다.

중복 코드

코드가 중복되면 각각을 볼 때마다 서로 차이점은 없는지 주의 깊게 살펴봐야 하는 부담이 생긴다. 또한 하나를 변경할 때는 다른 비슷한 코드들도 모두 살펴보고 수정해야 한다.

💡 해법:
📚 한 클래스의 두 메서드가 같은 표현식을 사용하는 경우: 함수 추출하기를 통해 양쪽 모두 추출된 메서드를 호출하게 바꾼다.
📚 코드가 비슷하긴 한데 완전히 똑같지는 않은 경우: 문장 슬라이드하기로 비슷한 부분을 한 곳에 모아 함수 추출하기를 더 쉽게 적용할 수 있는지 살펴본다.
📚 서브 클래스들에 코드가 중복되어 있는 경우: 각자 따로 호출되지 않도록 메서드 올리기를 적용해 부모로 옮긴다.

긴 함수

함수가 길수록 코드는 이해하기 어렵다.

💡 해법: 함수를 짧게 만드는 작업의 99%는 함수 추출하기이다. 함수 본문에서 따로 묶어 빼내 새로운 함수로 만든다.

  • 매개변수와 임시 변수는 많이 사용하지 않도록 하자. 함수를 추출하다보면 추출된 함수에도 매개변수가 너무 많아져서 리팩터링 전보다 난해해질수 있다.
    📚 임시변수 수 줄이는 방법: 임시 변수를 질의 함수로 바꾸기
    📚 매개변수 수 줄이는 방법: 매개변수 객체 만들기객체 통째로 넘기기
    📚 여전히 많다면? 함수를 명령으로 바꾸기

  • 주석을 참고해 추출할 코드 덩어리를 찾아보자. 주석이 설명하는 코드와 함께 함수로 빼내고 함수 이름은 주석 내용을 토대로 짓는다. 코드가 단 한줄이어도 따로 설명할 필요가 있다면 함수로 추출하는게 좋다.

  • 조건문과 반복문도 추출 대상이다.
    📚 조건문: 조건문 분해하기
    📚 switch문: case문마다 함수 추출하기를 적용해서 각 case의 본문을 함수 호출문 하나로 바꾼다. 같은 조건을 기준으로 나뉘는 switch문이 여러 개라면 조건문을 다형성으로 바꾸기를 적용해보자.
    📚 반복문: 그 안의 코드와 함께 추출해서 독립된 함수로 만든다. 반복문 쪼개기를 적용해서 적절하게 작업을 분리할 수 있도록 하자. 또한 반복문을 파이프라인으로 바꾸기를 적용하면 반복문 자체를 제거할 수도 있다.

함수를 짧게 구성할수록 코드를 이해하고, 공유하고, 선택하기 쉬워진다. 적극적으로 함수를 쪼개야 한다.

긴 매개변수 목록

매개변수 목록이 길어지면 그 자체로 이해하기 어려울 때가 많다.

💡 해법
📚 다른 매개변수에서 값을 얻어 올 수 있는 매개변수라면: 매개변수를 질의 함수로 바꾸기
📚 데이터 구조에서 값들을 뽑아 각각을 별개의 매개변수로 전달한다면: 객체 통째로 넘기기
📚 항상 함께 전달되는 매개변수라면: 매개변수 객체 만들기
📚 함수의 동작 방식을 정하는 플래그 역할의 매개변수라면: 플래그 인수 제거하기
📚 여러 개의 함수가 특정 매개변수들의 값을 공통으로 사용한다면: 여러 함수를 클래스로 묶기

전역 데이터

전역 데이터는 코드베이스 어디에서든 건드릴 수 있고, 값을 누가 바꿨는지 찾아낼 수 없다.

💡 해법
📚 변수 캡슐화하기: 전역 데이터를 함수로 감싸는 것만으로도 데이터를 수정하는 부분을 쉽게 찾을 수 있고 접근을 통제할 수 있다.

가변 데이터

값을 다른 곳에서 설정할 수 있는 가변 데이터는 혼동과 버그를 일으키기 쉽다.

💡 해법
📚 변수 캡슐화하기: 정해놓은 함수를 거쳐야만 값을 수정할 수 있도록 한다.
📚 변수 쪼개기: 용도별로 독립 변수에 저장하여 값 갱신이 문제를 일으킬 여지를 없앤다.
📚 질의 함수와 변경 함수 분리하기: 꼭 필요한 경우가 아니라면 부작용이 있는 코드를 호출할 수 없도록 한다.
📚 세터 제거하기: 변수의 유효범위를 줄인다.
📚 여러 함수를 클래스로 묶기, 여러 함수를 변환 함수로 묶기: 변수를 갱신하는 코드들의 유효범위를 제한한다.
📚 참조를 값으로 바꾸기: 내부 필드를 직접 수정하지 말고 구조체를 통째로 교체하도록 한다.

뒤엉킨 변경

하나의 모듈이 서로 다른 이유들로 인해 여러 가지 방식으로 변경된다면 리팩터링의 대상이다.

💡 해법
📚 단계 쪼개기: 순차적으로 실행하는 코드라면 단계를 분리한다.
📚 함수 추출하기, 함수 옮기기: 각 맥락에 해당하는 모듈들을 만들어서 관련 함수들을 모은다.

산탄총 수술

코드를 변경할 때마다 변경할 부분이 코드 전반에 퍼져있다면 찾기도 어렵고 꼭 수정해야 할 곳을 지나치기 쉽다.

💡 해법
📚 함수 옮기기, 필드 옮기기, 여러 함수를 클래스로 묶기, 여러 함수를 변환 함수로 묶기: 한 모듈에 묶어두도록 한다.
📚 단계 쪼개기: 묶은 함수들의 출력 결과를 묶어서 다음 단계의 로직으로 전달할 수 있다면 단계를 분리한다.
📚 함수 인라인하기, 클래스 인라인하기: 분리된 로직을 하나로 합친다.

뒤엉킨 변경산탄총 수술의 차이?
뒤엉킨 변경산탄총 수술은 비슷하면서도 정반대다. 뒤엉킨 변경은 한 코드에 섞여 들어가는 코드를 말하고, 산탄총 수술은 여러 코드에 흩뿌려진 코드를 말한다. 뒤엉킨 변경은 맥락별로 분리하여 해결하지만, 산탄총 수술의 경우에는 맥락별로 모아 해결한다.

데이터 뭉치

데이터 항목 중 몇 개가 여러 곳에서 항상 함께 뭉쳐서 사용될 경우 해당 데이터 뭉치는 새로운 객체로 분리하도록 한다.

💡 해법
📚 클래스 추출하기: 필드 형태의 데이터 뭉치를 찾아 하나의 객체로 묶는다.
📚 매개변수 객체 만들기, 객체 통째로 넘기기: 매개변수 수를 줄인다.

추측성 일반화

당장은 필요 없는 케이스 처리 로직 등을 미리 작성한 경우 나중에 실제로 사용하게 되면 다행이지만, 그렇지 않는다면 쓸데없는 낭비일 뿐이다.

💡 해법
📚 계층 합치기: 하는 일이 거의 없는 추상 클래스는 제거한다.
📚 함수 인라인하기, 클래스 인라인하기: 쓸데없이 위임하는 코드는 삭제한다.
📚 함수 선언 바꾸기: 본문에서 사용되지 않는 매개변수는 없앤다.
📚 죽은 코드 제거하기: 사용되지 않는 코드는 제거한다.

임시 필드

특정 상황에서만 값이 설정되는 임시 필드를 작성하면 코드는 이해하기 어렵다. 쓰이지 않는 것처럼 보이는 필드가 존재하는 이유에 대해서 파악해야 하기 때문이다.

💡 해법
📚 클래스 추출하기, 함수 옮기기: 임시 필드들과 관련된 코드들을 한 군데에 몰아넣는다.
📚 특이 케이스 추가하기: 필드들이 유효하지 않을 때를 위한 대안 클래스를 만들어서 제거한다.

내부자 거래

모듈 사이의 데이터 거래가 많으면 결합도는 높아진다. 그 양을 최소로 줄이고 모두 투명하게 처리해야 한다.

💡 해법
📚 함수 옮기기, 필드 옮기기: 결합된 모듈들을 분리한다.
📚 위임 숨기기: 공통 부분을 처리하는 제3의 모듈을 새로 만들거나 다른 모듈이 중간자 역할을 하도록 한다.
📚 서브클래스를 위임으로 바꾸기, 슈퍼클래스를 위임으로 바꾸기: 상속 관계에 있는 부모 자식 사이의 결합도를 낮춘다.

거대한 클래스

한 클래스가 너무 많은 일을 하면 필드 수가 많아지고, 필드가 많을 경우 중복 코드가 생기기 쉽다.

💡 해법
📚 클래스 추출하기, 슈퍼클래스 추출하기, 타입 코드를 서브클래스로 바꾸기: 필드 일부를 따로 묶어 개별 클래스 등으로 추출한다.

주석

주석을 달면 안되는 것이 아니라 주석을 장황하게 많이 다는 것이 문제다.

💡 해법
📚 특정 코드 블록이 하는 일에 주석을 남기고 싶다면: 함수 추출하기
📚 이미 추출되어 있는 함수임에도 여전히 설명이 필요하다면: 함수 선언 바꾸기
📚 시스템이 동작하기 위한 선행조건을 명시하고 싶다면: 어서션 추가하기

주석을 남겨야겠다는 생각이 들면, 가장 먼저 주석이 필요 없는 코드로 리팩터링해보도록 한다.

0개의 댓글