alt
Home 리팩터링 3장. 코드에서 나는 악취
Post
Cancel

리팩터링 3장. 코드에서 나는 악취


1. 기이한 이름

: 가장 어려운 부분 중 하나. 네이밍이 좋을수록 세부내용을 볼 필요성이 줄어들 수 있다.


2. 중복 코드

: 같은 코드가 여러군데서 반복된다면 하나의 메서드로 추출할 수 있다. 이렇게 하면 항상 그 메서드는 일관된 동작을 할 것을 보장한다.


3. 긴 함수

  • 함수가 길수록 이해하기 어렵기 때문에 메서드 추출을 생각해야한다. 임시변수를 질의 함수로 만들거나 줄이고 문장을 슬라이드해 연관코드끼리 묶어버리면 추출이 더 쉬워진다.
  • 초기에는 서브루틴을 호출하는 비용이 커서 한 함수에 몰아넣었다면 최근의 언어들은 이러한 비용이 없다시피한다.
  • 잘 만들어진 코드일수록 코드가 잘 분리되어있다. 메서드 분리에는 항상 좋은 네이밍이 필요하다.


4. 긴 매개변수 목록

: 매개변수가 복잡할수록 코드 이해가 어렵다. DTO를 만들거나 객체를 통째로 넘기는게 더 이해하기 좋은 코드이다.

1
2
3
4
// before
foo(john.id, john.name, john.email);
// after
foo(john);


5. 전역 데이터

: 버그 발생 시 어디서 전역 데이터가 변경되는지 파악하기 어렵고 thread-safety도 고려해야한다. 꼭 전역적으로 다뤄야하고 mutable 해야 한다면, 캡슐화해 하나의 객체로 만들고, 접근 제어자를 적극 활용하는 방법이 있다.


6. 가변 데이터

  • Immutability: 함수형 패러다임의 장점 중 하나로, side effect가 발생하지 않도록 불변하게 만드는 것은 큰 이점이 있다.
  • 변수를 캡슐화하거나, 변경이 있는 코드를 따로 분리하고, setter 없애기, 변하는 값을 중첩 객체로 바꾸기 등의 작업을 통해 가변 데이터를 좀더 안전하게 만들 수 있다.


7. 뒤엉킨 변경

: 하나의 모듈이 서로 다른 이유로 변경되는 일이 많은 경우이다. 일반적으로 SRP를 지키지 않은 경우에 해당한다. 같은 맥락의 코드들로 모으고 모듈을 분리해 SRP를 지킬 수 있도록 리팩토링한다.


8. 산탄총 수술

: 뒤엉킨 변경의 반대. 같은 맥락의 코드들이 나뉘어져있어, 하나의 변경이 여러 클래스의 변경을 야기하는 경우에 해당한다. 이 경우 관련 코드끼리 뭉쳐야하는데, 하나의 모듈 크기가 커지는 것에 대해 거부감이 들더라도 두려워하지 않아야한다.


9. 기능 편애

: 모듈 내부와는 최대한으로 소통하고, 외부와는 최소한으로 소통해야한다. 모듈 내부보다 외부와 소통하는 것이 많다면, 해당 메서드의 위치를 다시 고민해봐야 한다.


10. 데이터 뭉치

  • 데이터 서너개가 여러 로직에서 항상 함께 사용되는 데이터들이 있다. 이 데이터들을 하나의 객체 내부의 중첩객체로 만들면 코드가 훨씬 간결해진다.
  • 구분하는 방법은 “데이터들 중 하나를 없애도 가능한 데이터들인가?”를 고민해보면 된다.


11. 기본형 집착

: 의미있는 비즈니스 로직들을 기본형과 로직으로만 처리하지말고 객체로 만들어서 사용하는 것이 좋다.


12. 반복되는 switch문

: 단순히 switch문이 사용되었다고해서 무조건 다형성으로 바꿔야하는 것은 아니지만, switch가 여러군데서 사용된다면 고민해봐야한다. 왜냐하면 조건절이 하나 늘때마다 여러 switch를 모두 바꿔줘야하기 때문이다.


13. 반복문

: 일급 함수의 특징을 갖는 언어가 많아져 반복문을 stream으로 처리하는 것이 더욱 트렌드가 되었다. side effect 방지, 가독성 등 여러 장점이 있다.


14. 성의 없는 요소

: 메서드가 하나뿐인 클래스라던지, 본문 코드를 그대로 쓰는게 더 나은 함수 등등 역할이 거의 없는 함수나 클래스 등이 있다. 만들다보니 그렇게 된 경우도 있고 리팩토링하면서 역할이 줄어든 경우도 있다. 이 경우 그냥 합치는걸 고민해보면 된다.


15. 추측성 일반화

: 나중에 필요할 것이라고 추측해 만든 코드가 실제로 사용되지 않는 경우이다. 과감히 테스트 케이스를 날리고 죽은 코드를 제거하면 된다.


16. 임시 필드

: 특정 상황에서만 쓰이는 필드들을 클래스로 추출하거나 유효하지 않을 때를 위한 대안 클래스를 만든다.


17. 메시지 체인

: 기차 충돌이 발생하고 디미터 법칙이 깨진 코드이다. 캡슐화의 개념이 깨지게 되면 객체 내부구조가 밖으로 노출된다.


객체 내부구조를 묻기보다 객체에게 메시지를 보내 무언가를 시켜라


18. 중개자 제거하기

: 메시지 체인과 반대로, 디미터 법칙을 너무 신봉하는 경우 중간에서 전달만 하는 중개자가 생기기 마련이다. 중개자를 없애고 직접 호출하도록 리팩토링한다.


19. 내부자 거래

  • 모듈 사이에 결합도가 증가하는 경우이다. 이 경우 한 모듈이 변경되면 다른 모듈에서도 변경이 발생할 확률이 높다.
  • 해결: 여러 모듈의 공통 도메인을 제3의 모듈로 빼던가 디미터 법칙을 활용해 다른 모듈이 중간자 역할을 하도록 de-coupling한다.


20 거대한 클래스

  • 클래스 역할이 커질수록 필드가 커지고 중복 코드가 생길 가능성이 높다.
  • 해결
    • 접두어가 접미사가 같은 필드들끼리 추출한다.
    • 클라이언트들이 거대 클래스를 이용하는 패턴을 파악해 단서를 얻을 수도 있다.


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

: 클래스를 유연하게 교체할 수 있도록 만들기 위해 인터페이스화한다. 인터페이스를 일치시키기 위해서는 메서드 시그니처를 바꾸거나 함수 옮기기를 수행한다. 상속도 고민해본다.


22. 데이터 클래스

  • 데이터 클래스: 데이터 필드와 getter / setter로만 이루어진 클래스. Kotlin에서는 실제로 이러한 용도의 클래스가 구현되어있다.
  • 아무 일도 수행하지 않는 데이터 클래스가 있다는 것은 필요한 동작이 엉뚱한 곳(클라이언트 코드)에 정의되어있다는 뜻일 수도 있다.


23. 상속 포기

: 부모 클래스로부터 일부만 상속받고싶어하는 경우가 있을 수 있다. 과감히 상속을 없애는 방안을 고민해봐야한다.


24. 주석

: 주석이 필요하단 생각이 들면 일단 메서드 추출이나 네이밍 변경을 고민해본다. 확실하지 않은 부분이나 앞으로 할 것들을 주석으로 달아놓으면 좋다.



Reference)

리팩터링 2판

This post is licensed under CC BY 4.0 by the author.

리팩터링 2장. 리팩터링 원칙

리팩터링 4장. 테스트 구축하기