[리팩터링 2판] - 코드에서 나는 악취

Lee Jeong Min·2022년 8월 7일
0

리팩터링 2판

목록 보기
3/12
post-thumbnail

리팩터링 2판의 Chatper 03을 보고 정리한 글입니다.

앞서 두 챕터에서 리팩터링이 어떻게 작동하는지 어느정도 감을 잡았을 것이다. 그러나 리팩터링을 언제 시작하고 언제 그만할지 판단하는 일 또한 중요하다.

리팩터링을 언제 시작하면 좋을지와 관련한 내용이 이번 Chapter 03의 내용이다.

리팩터링을 언제할지?

기이한 이름

코드를 명료하게 표현하는 데 가장 중요한 요소 중 하나는 '이름'이다. 그래서 이름만 보고 각각 무슨 일을 하고 어떻게 사용해야 하는지 명확히 알 수 있도록 신경써서 이름을 지어야한다.그 때문에 우리가 가장 많이 사용하는 리팩터링도 함수 선언 바꾸기, 변수 이름 바꾸기, 필드 이름 바꾸기 처럼 이름을 바꾸는 리팩터링들이다.

→ 코드에 기이한 이름이 있는 경우 리팩터링을 진행하자.

중복 코드

동일한 코드 구조가 여러 곳에서 반복된다면 하나로 통합하여 더 나은 프로그램으로 만들자.

이 경우 함수 추출하기, 문장 슬라이드하기, 메서드 올리기등의 방법을 사용하여 리팩터링을 진행할 수 있다.

긴 함수

함수는 길수록 이해하기 어렵기 때문에 함수를 적극적으로 쪼개야 한다. 또한 이러한 함수의 이름을 잘 지어두면 본문 코드를 볼 이유가 사라지고 코드를 이해하기 쉽게 만들 수 있다.

이러한 과정에서 함수를 짧게 만들고, 임시 변수 및 매개변수를 리팩터링하는 방법으로 아래와 같은 방법들이 있다.

  • 함수 추출하기
  • 임시 변수를 질의 함수로 바꾸기
  • 매개변수 객체 만들기
  • 객체 통째로 넘기기
  • 함수를 명령으로 바꾸기

조건문이나 반복문 관련 리팩터링

  • 조건문 분해하기
  • 거대한 switch문을 구성하는 case문마다 함수 추출하기
  • 같은 조건을 기준으로 나뉘는 switch문이 여러 개라면 조건부 로직을 다형성으로 바꾸기
  • 반복문 쪼개기

긴 매개변수 목록

매개변수 목록이 길어지면 그 자체로 이해하기 어려울 때가 많다. 따라서 이러한 경우 아래와 같은 기법들을 사용하여 리팩터링 하자.

  • 매개변수를 질의 함수로 바꾸기(다른 매개변수에서 값을 얻어올 수 있는 매개변수를 위함)
  • 객체 통째로 넘기기(원본 데이터 구조를 그대로 전달)
  • 매개변수 객체 만들기(하나로 묶어 전달)
  • 플래그 인수 제거하기
  • 여러 함수를 클래스로 묶기(여러 개의 함수가 특정 매개변수들의 값을 공통으로 사용할 때)

전역 데이터

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

→ 변수 캡슐화하기를 통한 리팩터링

가변 데이터

데이터를 변경했더니 예상치 못한 결과나 골치 아픈 버그로 이어지는 경우가 종종 있다. 이 문제가 아주 드문 조건에서만 발생한다면 원인을 알아내기가 매우 어렵다.

→ 함수형 언어와 같이 데이터를 변경하지 않고 복사본을 만드는 방법으로 사용하거나 무분별한 데이터 수정에 따른 위험을 줄이기 위해 아래와 같은 방법을 사용할 수 있다.

  • 변수 캡슐화하기
  • 변수 쪼개기(용도별 독립 변수에 저장하게 하여 값 갱신이 문제를 일으킬 여지를 없앰)
  • 문장 슬라이드하기와 함수 추출하기를 통한 코드 분리
  • 질의 함수와 변경함수 분리하기
  • 세터 제거하기
  • 파생 변수를 질의 함수로 바꾸기
  • 참조를 값으로 바꾸기

뒤엉킨 변경

뒤엉킨 변경은 단일 책임 원칙과 관련이 있는데, 하나의 모듈이 서로 다른 이유들로 인해 여러 가지 방식으로 변경되는 일이 많을 때 발생한다.

예를 들어 데이터베이스가 추가될 때마다 함수 세개를 바꿔야 하고, 금융 상품이 추가될 때마다 또 다른 함수 네 개를 바꿔야 하는 모듈이 있다면 뒤엉킨 변경이 발생했다는 의미이다. 이 경우 독립된 모듈로 분리해야하는데, 맥락에 따른 단계를 분리하는 단계 쪼개기나 함수 옮기기, 함수 추출하기, 클래스 추출하기 등을 사용하여 리팩터링을 진행한다.

산탄총 수술

이 문제는 코드를 변경할 때마다 자잘하게 수정해야 하는 클래스가 많을 때 발생한다. 뒤엉킨 변경과 비슷하면서도 정반대인데, 변경할 부분들을 함수 옮기기와 필드 옮기기로 모두 한 모듈에 묶어두고, 여러 함수를 클래스로 묶기를 적용한다.

어설프게 분리된 로직을 함수 인라인하기나 클래스 인라인하기 같은 인라인 리팩터링으로 하나로 합치는 것도 산탄총 수술에 대처하는 좋은 방법이다. 이 경우 나중에 추추랗기 리팩터링으로 더 좋은 형태로 분리할 수도 있다.

기능 편애

흔히 어떤 함수가 자기가 속한 모듈의 함수나 데이터보다 다른 모듈의 함수나 데이터와 상호작용할 일이 더 많을 때 발생한다. 해결방법으로 데이터 근처로 함수를 옮기는 방법과 독립 함수로 빼내어 함수를 추출하여 원하는 모듈로 보내주는 방법이 있다.

위에서 설명한 규칙을 거스르는 복잡한 패턴으로 전략 패턴과 방문자 패턴이 있는데 이들은 뒤엉킨 변경을 해결할 때 활용하는 패턴들로, 함께 변경할 대상을 한데 모으는 것이다.

데이터 뭉치

데이터 항목들은 서로 같이 몰려있는 경향이 있기 때문에 한곳에서 관리하기 위한 공간이 필요하다. 보통 간단한 레코드 구조와 같은 객체로 만들어도 좋지만, 클래스를 이용하면 상당한 중복을 없애고 향후 개발을 가속하는 유용한 클래스를 탄생시키는 결과로 이어지기도 한다.

기본형 집착

프로그래밍을 할 때, 자신에게 주어진 문제에 맞는 기초타입을 직접 정의하기를 꺼리는 사람이 많다. 그러나 이를 객체로 바꾸면(기본형을 객체로 바꾸기) 의미있는 자료형의 코드로 만들 수 있다.

이 부분은 잘 이해가 가지 않아 스터디 때 구체적인 예시를 들으면 좋을듯?

반복되는 switch문

중복되는 switch문은 조건절을 하나 추가할 때마다 다른 switch문 모두 찾아서 변경해야 한다. 따라서 이런 경우 다형성을 활용하여 코드를 리팩터링하자.

반복문

최근에는 일급 함수를 지원하는 언어가 많아졌기 때문에 반복문을 파이프라인으로 바꾸어 코드를 리팩터링할 수 있다. 그 예시로 js의 filter 혹은 map 함수가 있다.

성의 없는 요소

코드의 구조를 잡을 때 프로그램 요소(프로그래밍 언어가 제공하는 함수, 클래스, 인터페이스 등 코드 구조를 잡는데 활용되는 것)를 이용하는 걸 좋아한다. 그렇지만 이러한 구조가 필요 없을 땐 요소들을 제거하는 편이 좋다.

이 부분도 정확하게 어떤 의미인지 잘 와닿지는 않았던거 같음

추측성 일반화

앞선 챕터 2에서 설명한 유연성 메커니즘과 관련한 것 같다. 즉, 당장은 필요 없는 케이스 처리 코드를 현재 작성해 두어 악취가 생기는 것으로, 당장에 필요하지 않은 코드는 제거 하자.

임시 필드

특정 상황에서만 값이 설정되는 필드를 가진 클래스가 있는데, 이러한 경우 임시 필드를 갖는 코드를 작성하기보다 클래스 추출하기와 함수 옮기기로 임시 필드들과 관련된 코드를 모조리 새 클래스에 몰아넣자.

메시지 체인

메시지 체인은 다른 객체를 요청하는 작업이 연쇄적으로 이어지는 코드를 말한다. 이는 클라이언트가 객체 내비게이션 구조에 종속되어, 내비게이션 중간 단계를 수정하면 클라이언트 코드도 수정해야한다. 따라서 이러한 경우 위임 숨기기로 문제를 해결하자.

예시 코드

// 메시지 체인의 전형적인 예
let managerName = aPerson.department.manager.name;

// 위임 숨기기
managerName = aPerson.department.managerName; // 관리자 객체(manager)의 존재를 숨김
managerName = aPerson.manager.name // 부서 객체(department)의 존재를 숨김
managerName = aPerson.managerName // 부서 객체와 관리자 객체 모두의 존재를 숨김

중개자

외부로부터 세부사항을 숨겨주는 캡슐화가 있고, 이 과정에서 위임이 자주 활용된다. 그러나 지나치게 사용하면 안되고 이럴땐 중개자 제거하기를 활용하여 실제로 하는 일을 객체와 직접 소통하게 하자.

내부자 거래

모듈 사이의 데이터 결합도를 낮추기 위해 데이터를 주고 받는 모듈을 함수 옮기기와 필드 옮기기 기법으로 리팩터링하자. 또한 관심사를 공유한다면 공통 부분을 정식으로 처리하는 제3의 모듈을 새로 만들거나 위임 숨기기를 이용하여 다른 모듈이 중간자 역할을 하게 만든다.

거대한 클래스

한 클래스가 너무 많은 일을 하려고 하여 필드수가 늘어날땐 클래스 추출하기로 필드들 일부를 따로 묶어보자.

서로 다른 인터페이스의 대안 클래스

클래스의 장점은 필요에 따라 언제든 다른 클래스로 교체할 수 있다는 것인데 이럴려면 인터페이스가 같아야 한다. 이를 위해 함수 선언 바꾸기로 메서드 시그니처를 일치시키고 함수 옮기기를 통해 인터페이스가 같아질 때까지 필요한 동작들을 클래스 안으로 밀어 넣는다. 이 과정에서 중복이 생기면 슈퍼클래스 추출하기를 고려하자.

데이터 클래스

데이터 클래스란 데이터 필드와 게터/세터 메서드로만 구성된 클래스를 말한다. 이런 클래스에 public 필드가 있다면 캡슐화하를 하고 변경하면 안되는 필드는 세터 제거하기로 접근을 봉쇄하자.

이러한 데이터 클래스는 필요한 동작이 엉뚱한 곳에 정의돼 있다는 신호일 수 있는데 이런 경우 클라이언트 코드를 데이터 클래스로 옮기기만 해도 개선된다.

상속 포기

이는 서브클래스가 부모의 동작은 필요로하지만 인터페이스는 따르고 싶지 않을때 발생한다. 이럴 땐 서브클래스를 위임으로 바꾸기나 슈퍼클래스를 위임으로 바꾸기 기법을 활용하여 상속 메커니즘에서 벗어나자.

주석

주석이 장황하게 달린 원인이 코드를 잘못 작성했기 때문인 경우가 의외로 많기 때문에 다음의 말을 기억하자.

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


이번 챕터를 읽으면서 코드없이 줄글로만 되어있어 구체적인 예시가 잘 떠오르지 않아 약간 집중도가 떨어졌던것 같다. 그리고 리팩터링 기법들이 다른 챕터에 떨어져 있어 책을 왔다갔다하며 보기 어려웠었던것 같다.

profile
It is possible for ordinary people to choose to be extraordinary.

0개의 댓글