리팩터링 (3장 코드에서 나는 악취 & 4장 테스트 구축하기)

박주진·2021년 10월 5일
0

리팩터링

목록 보기
2/7

아래 내용은 리팩터링 2판 내용과 한달한권 읽기 강의를 기반하여 정리한 글입니다.

코드에서 악취가 풍길때 리팩토링이 필요한 시점이다. 어디까지 리팩터링 해야하는지 즉 악취를 어느정도까지 제거해야 하는지에 대한 기준은 숙련된 개발자의 직관을 따라야 한다.결국엔 개개인이 경험을 통해 감을 키워 인스턴스 변수가 몇 개가 적당한지, 메서드가 몇 줄을 넘어가면 안되는지를 판단해야 된다.

책에서는 악취에 종류마다 적용할 수 있는 리팩터링 기법도 같이 제시하지만 기법에 대한 사전 설명이 아직 없어 오히려 이해하기에는 어려울 것 같아 해당 부분은 빼고 정리하였다.

악취의 종류들

기이한 이름

  • 함수, 모듈, 변수, 클래스 등은 이름만 보고 무슨 일을 하고 어떻게 사용하는 지를 파악할 수 있어야 한다.
  • 마땅한 이름이 떠오르지 않는다면 설계가 문제가 있을 수 있다.

중복 코드

  • 똑같은 코드 구조가 반복되는 경우.
  • 코드가 중복되면 중복된 코드끼리 차이점에 대해 주의 깊게 살펴야 한다.
  • 코드 변경시에도 중복된 코드를 살펴보고 적절히 수정해야 한다.

긴 함수

  • 함수가 길어질수록 이해하기 힘들다.
  • 예전과 다르게 요즘 언어는 프로세스 안에서 함수 호출 비용이 거의없어 꺼려할 이유가 없다.
  • 긴 함수를 동작 방식이 아닌 의도가 잘 들어나는 이름을 가진 짧은 함수들로 쪼개라.

긴 매개변수 목록

  • 매개변수 목록이 길어지면 이해하기 힘들다.

전역 데이터

  • 코드베이스 어디에서든 건드릴 수 있고 값을 누가 바꿨는지 찾아낼 매커니즘이 없다.

가변 데이터

  • 변경된 데이터가 코드의 다른 곳에서 버그를 일으킬 수 있다. 그리고 이 문제가 드문 조건에서 발생한다면 원인을 찾기 매우 어렵다.
  • 이런 이유로 함수형 프로그램밍은 데이터는 절대 변하지 않는다는 개념을 기본으로 삼고 있다.

뒤엉킨 변경

  • SRP(단일 책임의 원칙)이 제대로 지켜지지 않을때 발생 한다.
  • 하나의 모듈이 서로 다른 이유로 인해 여러 가지 방식으로 변경 되는 경우.
  • 보통 개발 초기에 맥락 사이의 경계가 제대로 나눠지지 않아서 발생한다.
  • 맥락별로 분리하여 해결 할 수 있다.

산탄총 수술

  • SRP(단일 책임의 원칙)이 제대로 지켜지지 않을때 발생 한다.
  • 코드를 변경해야 할때 변경해야 할 부분이 코드 전반에 퍼져 있는 경우.
  • 개발 초기에 맥락 사이의 경계가 제대로 나눠지지 않아서 발생한다.
  • 맥락별로 다시 모아 해결할 수 있다.

기능 편애

  • 특정 함수가 자기가 속한 모듈의 함수나 데이터보다 다른 모듈의 함수나 데이터와 상호작용 할 일이 더 많은 경우.

데이터 뭉치

  • 데이터 들이 여러 메서드의 시그니처에서 같이 자주 사용되거나 몇개의 클래스에서 같이 필드로 사용되는 경우.
  • 같이 사용되는 데이터를 뭉처 클래스로 만들어주어 해결할 수 있다.
  • 데이터 뭉치인지 판단하는 방법은 데이터 뭉치 중 값 하나를 삭제해보고 나머지 값으로는 데이터가 의미가 없어진다면 데이터 뭉치이다.

기본형 집착

  • 기본형 보다는 자신에게 주어진 문제에 맞는 좀더 의미 있는 타입을 직접 정의해라.
  • 예) 금액 int -> currency class

반복되는 switch문

  • 반복되는 switch문은 조건 추가시 모든 switch문을 함께 수정해야 하기때문에 문제가 된다.

반복문

  • 반복문을 파이프라인으로(map, filter)변경하면 각 원소들이 어떻게 처리되는지 쉽게 파악할 수 있다.

성의 없는 요소

  • 메서드가 하나뿐인 클래스, 본문 코드를 그대로 가지고 있는 함수.
  • 나중에 함수 본문을 더 채우거나 클래스의 다른 메서드를 추가할 생각이여도 좋지 않다.

추측성 일반화

  • YAGNI와 동일
  • 나중에 필요할 거야 라는 생각으로 만든 코드는 쓸데없는 낭비일 수 있다.

임시 필드

  • 특정 상황에서만 값이 설정된는 필드를 가진 클래스.

메시지 체인

  • 꼬리의 꼬리를 무는 게터 (기차 충돌).
  • 클라이언트가 메시지 체인에 속한 객체들과 결합도가 높아진다.

중개자

  • 클래스가 제공하는 메서드 중 절반이 다른 클래스에 구현을 위임하여 중개자로 전락하는 경우.
  • 이런 경우 해당 클래스를 제거하고 클라이언트가 직접 소통하게 하자.

내부자 거래

  • 위임하는 클래스와 결합도가 너무 높아지는 경우.

거대한 클래스

  • 클래스 내부의 필드가 너무 많으면 중복코드가 생기기 쉽다.
  • 코드량이 너무 많은 클래스도 중복 코드가 생기기 쉽고 혼동을 일으킬 여지가 크다.

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

  • 비슷한 두 클래스 끼리 다른 인터페이스를 구현하는 경우.

데이터 클래스

  • 게터/세터로만 구성된 클래스로 종종 사용하다 보면 다른 클래스가 너무 깊이까지 함부로 다룰 때가 많다.
  • 필요한 동작이 엉뚱한 곳에 정의돼 있다는 신호일 수도 있다. 예를 들면 게터로 호출된 결과물을 클라언트에서 가공해서 사용하는 경우 해당 로직을 데이터 클래스 내부로 옮겨 일반 클래스로 변경할 수 있다.

상속 포기

  • 서브 클래스가 부모의 public 인터페이스중 일부만 받아서 사용하는 경우.
  • 부모의 동작만 필요해서 사용하고 인터페이스는 따르고 싶지않는 건 무례하다.

주석

  • 주석을 달면 안된다가 아니라 장황하게 달린 주석인 경우.

테스트 구축하기

  • 리팩토링 중 생기는 실수를 방지하기 위해서 견고한 테스트가 뒷받침되어야 한다!!
  • 자동화된 테스트는 버그를 찾는 시간을 대폭 줄여줌으로 개발 생산성이 증가한다.( 특히 회귀 버그르 찾는 데 수동으로 하는 것보다 빠르고 지루하지 않다.)
  • 프로그래밍을 시작하기 전이 테스트를 작성하기 가장 좋은 시점이다. 왜냐하면 구현보다 인터페이스에 집중할 수 있고 원하는 기능을 추가하기 위해 무엇이 필요한지를 고민할 수 있기 때문이다.(TDD)
  • 일시적으로 오류를 코드에 주입해서라도 테스트가 실패하는 상황에서는 실패하는지 확인해 보는게 좋다.
  • 모든 public method에 대해 테스트를 작성하기 보다는 위험한 요소 중심으로 작성하는 게 좋다.
  • 코드를 작성한후 문제가 없는지 확인하기 위해 수시로 테스트를 실행하는게 좋다.
  • 테스트 마다 공유 픽스처 보다는 독립적인 픽스처를 생성하는게 좋다.
  • 테스트 마다 하나의 속성 값을 검증하는게 좋다. 왜냐하면 앞쪽 검증이 실패하면 나머지 검증은 자동으로 실패해 실패 원인에 대한 유용한 정보를 놓치기 쉽다.(검증하는 두 속성이 밀접하다고 판단되면 같이 검증해도 무방하다.)
  • 문제가 생길 수 있는 경계 조건을 고민해보고 이 부분을 집중적으로 테스트 하는게 좋다.(음수, null, 빈 컬렉션, 범위를 벗어나는 수)
  • 테스트를 어느 수준까지 해야 할까에 대해서는 너무 많은 테스트는 의욕이 떨어질 수 있어 위험한 부분에 집중해서 작성하는게 좋다.(수확 체감의 법칙)
  • 버그 리포트를 받으면 해당 버그를 드러내는 테스트를 먼저 작성하면 코드 수정후 버그가 fix 되었는지 확인하기 쉽고 다른 테스트에도 비슷한 구멍은 없는지 살펴보게 된다.
  • 테스트 커버리지는 테스트 되지 않은 영역을 찾는데 도움이 될 뿐 테스트 품질과는 크게 상관이 없다.

0개의 댓글