Bad Smells in Code

최완식·2023년 7월 7일
0

Refactoring

목록 보기
9/15
post-thumbnail

냄새나면 당장 갈아라.

  • 어떻게 하는지에 대해서 지금까지 배웠다.
  • 하지만, "언제" 시작하고 그만할지를 판단하는 것은 또 다른 얘기다.
  • 어떤 "냄새"를 맡았을 때 손을 대야할까?

기이한 이름

  • 이름으로 이게 뭐하는 것인지 딱 볼 수 있게 작성해야 한다.
  • 아쉽게도 이름 짓기는 프로그래밍에서 가장 어렵기로 손꼽히는 두 가지중 하나다.
  • 이름이 제대로 안떠오르면 설계에 더 근본적인 문제가 있을 가능성이 높다.

중복 코드

  • 코드가 중복되면 하나로 통합하자.
  • 중복되면 각각을 볼 때마다 서로 차이점은 없는지 주의 깊게 살펴봐야 한다는 부담이 생긴다.

긴 함수

  • 오랜기간 잘 동작하는 프로그램들은 짧은 함수의 다발로 구성되어 있다.
  • 짧게 함수를 구성하면 코드를 이해하고, 공유하고, 선택하기 쉬워진다.
  • 예전 언어에서는 서브루틴을 호출하는 비용이 커, 짧은 함수를 꺼렸다.
  • 하지만, 요즘은 프로세스 안에서 함수 호출 비용이 거의 없다.
  • 주석을 달 바에 함수를 쪼개라.
  • 함수 이름에 의도를 담아라.
  • 이런 추출의 방법은 워낙 많아 뒤쪽에 설명하도록 하겠다.

긴 매개변수 목록

  • 상태값을 없애고 파라미터로 다 넘겨버리는게 나은 경우가 많다.
  • 그런데 파라미터 개수가 계속해서 늘어나는 것을 그냥 두는 것도 문제다.
  • 이런 경우 "매개변수를 질의 함수로 바꾸기", "객체 통째로 넘기기", "매개변수 객체 만들기" 등의 방법을 사용할 수 있다.

전역 데이터

  • 전역데이터는 매우 주의해야 한다.
  • 코드 베이스 어디에서든 건드릴 수 있어 누가 바꿨는지 찾아낼 메커니즘이 없다.
  • 양자 얽힘과 같은 형태라 볼 수 있다. 멀리 떨어트려놓아도 상태를 변경할 수 있으니까.

가변 데이터

  • 상태값을 두면 버그로 이어지는 경우가 종종있다.
  • 이런 이유로 함수형 프로그래밍에서는 데이터는 불변하며, 복사본을 만들어 반환하는 개념을 기본으로 삼는다.
  • 하지만 현실은 모두 함수형 프로그래밍을 적용하기 어렵다. 그래도 위험을 줄일 수는 있다.
  • 메서드를 통해 수정할 수 있게 하는 등의 방법을 사용하자.

뒤엉킨 변경

  • 코드를 수정할 때, 고쳐야 할 부분 하나를 갖고 고칠 수 있어야 한다.
  • 이렇게 안되면 난장판이 일어날 것이다.
  • 이런 문제는 SRP가 제대로 안지켜져서 그렇다.
  • 뒤엉킨 변경은 무언가 추가될 때, 이에 영향받는 요소가 여러개인 상황을 말한다.
  • 정확히 처리해야하는 부분을 분리함으로써 해결하자.

산탄총 수술

  • 뒤엉킨 변경과 좀 다르다.
  • 코드를 수정할 때마다 흩어져 있는 코드를 함께 수정해야 하는 경우다.
  • 이런 경우도 한곳으로 모아서 처리하는 것이 좋다.
뒤엉킨 변경산탄총 수술
원인맥락을 잘 구분하지 못함맥락을 잘 구분하지 못함
해법(원리)맥락을 명확히 구분맥락을 명확히 구분
발생 과정(현상)한 코드에 섞여 들어감여러 코드에 흩뿌려짐
해법(실제 행동)맥락별로 "분리"맥락별로 "모음"

기능 편애

  • 모듈화를 한다면, 경계는 최소화하고, 내부에서 상호작용을 늘려야 한다.
  • 그런데 내가 속한 데이터를 처리하는 것보다, 바깥쪽에서 들고와서 처리하는 게 많은 경우가 있다.
  • 이런 경우를 기능 편애라 한다.
  • 해결법은 쉽다. 들고와서 처리하는 친구들 바깥으로 빼내자.

데이터 뭉치

  • 데이터는 뭉쳐다니는 경우가 흔하다.
  • 이런 경우 하나로 관리하는 것이 좋다.
  • 묶을 때도 클래스와 같은 구조를 선택하여, 해당 데이터 내에서 처리하는 것이 좋은 동작들을 묶어주자.

기본형 집착

  • 원시 타입으로 그냥 매핑해두는 프로그래머가 흔하다.
  • 내가 풀 문제에 딱 맞는 타입으로 정의하지 않는 사람이 흔하다.
  • 이런 경우 아예 객체로 바꿔 의미 있는 자료형으로 바꾸자.
  • 이는 문명사회로 이끄는 행위다.

반복되는 switch문

  • 객체 지향을 선호하는 사람에게 switch문은 눈살을 찌푸리게 한다.
  • 이제는 시대가 바뀌어서 무조건적으로 switch를 보면 바꿔! 라고 말하긴 그렇다.
  • 하지만 반복적으로 나온다면 고민해보자.
  • 중복된 switch문의 경우에는 case를 추가할 대마다 모두 수정해야 하기 때문이다.
  • 이런 경우 다형성으로 바꾼다면 세련된 방식으로 처리할 수 있다.

반복문

  • 이제는 일급함수로 바뀌어 버린 함수를 사용하여 파이프라인으로 많이 처리한다.

성의 없는 요소

  • 클래스가 있긴 한데, 메서드가 하나인 경우
  • 사용하는 것이 별로 의미 없는 경우.
  • 이럴 때는 고이 보내주자.

추측성 일반화

  • "나중에 필요할 거야"
  • 만약에 사용하지 않으면 낭비일 뿐이다.
  • 당장 필요없으면 지워버리자.

임시 필드

  • 특정 상황에서만 값이 설정되는 필드를 가지는 경우도 있다.
  • 예를 들면 10개의 필드가 있는데, A, B를 통해 생성할 수 있다.
  • 그런데 A일 때와 B일 때만 각각 넣어지는 값이 있는 것이다.
  • 이런 경우 사용자는 코드를 파악하기 어렵다.
  • 클래스 추출하기를 통해 아예 분리시키는 것이 좋다.

메시지 체인

  • a.getB().getC().getD() 이런 식으로 작성된 코드를 말한다.
  • 이런 경우 위임 숨기기 방법을 사용한다.
// AS-IS
let managerName = person.department.manager.name

// TO-BE
let managerName = person.department.managerName
let managerName = person.manager.name
let managerName = person.managerName
  • 값을 위임함으로서 체인을 단축했다.

중개자

  • 캡슐화하는 과정에서는 "위임"이라는 개념이 자주 사용된다.
  • 책임을 이동시키는 것이다.
  • 보통은 좋으나, 지나치면 문제가 된다.
  • 만약 클래스가 제공하는 메서드가 10개인데, 이중 절반을 다른 클래스에게 구현을 위임하고 있다면?
  • 이럴 경우 오히려 위임한 책임을 가져오는 것이 나을 수 있다.

내부자 거래

  • 개발자는 모듈 사이에 벽을 두껍게 세우기를 좋아한다.
  • 그래서 사이의 데이터 거래가 많으면 결합도가 높아진다 말한다.
  • 이렇게 처리되는 것을 최소로 줄이고, 투명하게 변경해야 한다.

거대한 클래스

  • 클래스에 필드수가 늘어난다면, 분리할 필요가 있다.
  • 중복을 제거하면서, 책임을 분리하자.

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

  • 클래스를 사용하면 필요에 따라 다른 클래스로 교체할 수 있다.
  • 단 교체하려면 인터페이스가 같아야 한다.

데이터 클래스

  • 데이터 클래스는 필드와 게터, 세터만 가지는 클래스를 말한다.
  • 단순 저장 용도로만 쓰이다보니 함부로 다루는 경우가 많다.
  • 필드의 변경 통제에 관해 확실하게 관리하자.

상속 포기

  • 서브 클래스는 부모로 부터 메서드와 데이터를 물려받는다.
  • 하지만 모든 유산을 원치않는다면 어떻게 해야 할까?
  • 이전에는 "계층 구조"를 잘못 설계했기 때문이라고 보았다.
  • 이런 경우 물려받지 않을 코드들을 모조리 새로만든 서브 클래스로 넘겼다.
  • 하지만 이제는 이 방법을 권하지 않는다.
  • 이렇게 동작은 필요하지만 인터페이스는 따르고 싶지 않은 경우는, 아예 동작을 분리키는 방법을 사용하자.

주석

  • 주석은 향기처럼 사용할 수 있다.
  • 하지만 우리는 이걸 탈취제처럼 사용한다.
  • 주석이 장황한 이유가 곧 코드를 잘못 작성했기 때문인 경우가 많다는 것이다.

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

Reference

profile
Goal, Plan, Execute.

0개의 댓글