본문으로 바로가기

https://www.inflearn.com/course/리팩토링

 

코딩으로 학습하는 리팩토링 - 인프런 | 강의

리팩토링은 소프트웨어 엔지니어가 갖춰야 할 기본적인 소양 중 하나입니다. 이 강의는 인텔리J와 자바를 사용하여 보다 실용적인 방법으로 다양한 코드의 냄새와 리팩토링 기술을 설명하고 직

www.inflearn.com

 

긴 매개 변수 목록

냄새 4. 긴 매개변수 목록
  • 어떤 함수에 매개변수가 많을수록 함수의 역할을 이해하기 어려워진다.
  •  과연 그 함수는 한가지 일을 하고 있는게 맞는가?
  •  불필요한 매개변수는 없는가?
  •  하나의 레코드로 뭉칠 수 있는 매개변수 목록은 없는가?
  • 어떤 매개변수를 다른 매개변수를 통해 알아낼 수 있다면, "매개변수를 질의 함수로 바꾸기" 를 사용할 수 있다.
  •  기존 자료구조에서 세부적인 데이터를 가져와서 여러 매개변수로 넘기는 대신, "객체 통째로 넘기기" 를 사용할 수 있다.
  • 일부 매개변수들이 대부분 같이 넘겨진다면, "매개변수 객체 만들기" 를 적용할 수 있다.
  • 매개 변수가 플래그로 사용된다면, " 플래그 인수 제거하기"를 사용할 수 있다.
  • 여러 함수가 일부 매개변수를 공통적으로 사용한다면 "여러 함수를 클래스로 묶기"를 통해 매개변수를 해당 클래스의 필드로 만들고 메서드에 전달해야할 매개변수 목록을 줄일 수 있다.
리팩토링 14. 매개변수를 질의 함수로 바꾸기
  • 함수의 매개변수 목록은 함수의 다양성을 대변하며, 짧은수록 이해하기 좋다.
  • 어떤 한 매개변수를 다른 매개변수를 통해 알아낼 수 있다면 "중복 매개변수"라 생각할 수 있다.
  • 매개변수에 값을 전달하는 것은 "함수를 호출하는 쪽"의 책임이다. 가능하면 함수를 호출하는 쪽의 책임을 줄이고 함수 내부에서 책임지도록 노력한다.
  • "임시 변수를 질의 함수로 바꾸기"와 "함수 선언 변경하기"를 통해 이 리팩토링을 적용한다.
  • 매개변수의 수에 따라 함수의 역할을 구분하는 경우도 있기때문에 매개변수를 잘 관리해야한다.
리팩토링 15. 플래그 인수 제거하기
  • 플래그는 보통 함수에 매개변수로 전달해서, 함수 내부의 로직을 분기하는데 사용한다.
  • 플래그를 사용하는 함수는 차이를 파악하기 어렵다.
  • 조건문 분해하기를 활용할 수 있다.
리팩토링 16. 여러 함수를 클래스로 묶기
  • 비슷한 매개변수 목록을 여러 함수에서 사용하고 있다면 해당 메소드를 모아서 클래스를 만들 수 있다.
  • 클래스 내부로 메소드를 옮기고, 데이터를 필드로 만들면 메소드에 전달해야하는 매개변수 목록도 줄일 수 있다.

전역 데이터

냄새 5. 전역 데이터
  • 전연 데이터 (예, 자바의 public static 변수)
  • 전역 데이터는 아무곳에서나 변경될 수 있다는 문제가 있다.
  • 어떤 코드로 인해 값이 바뀐 것인지 파악하기 어렵다.
  • 클래스 변수 (필드) 도 비슷한 문제를 겪을 수 있다.
  • "변수 캡슐화하기 (Encapsulate Variable)"를 적용해서 접근을 제어하거나 어디서 사용하는지 파악하기 쉽게 만들 수 있다.
  • 파라켈수스의 격언, "약과 독의 차이를 결정하는 것은 사용량일 뿐이다."
리팩토링 17. 변수 캡슐화하기
  • 메소드는 점진적으로 새로운 메소드로 변경할 수 있으나, 데이터는 한번에 모두 변경해야 한다.
  • 데이터 구조를 변경하는 작업을 그보다는 조금 더 수월한 메소드 구조 변경 작업으로 대체할 수 있다.
  • 데이터가 사용되는 범위가 클수록 캡슐화를 하는 것이 더 중요해진다.
    - 함수를 사용해서 값을 변경하면 보다 쉽게 검증 로직을 추가하거나 변경에 따르는 후속 작업을 추가하는 것이 편리하다.
  • 불변 데이터의 경우에는 이런 리팩토링을 적용할 필요가 없다.

가변 데이터

냄새 6. 가변 데이터
  • 데이터를 변경하다보면 예상치 못했던 결과나 해결하기 어려운 버그가 발생하기도 한다.
  • 함수형 프로그래밍 언어는 데이터를 변경하지 않고 복사본을 전달한다. 하지만 그밖의 프로그래밍 언어는 데이터 변경을 허용하고 있다. 따라서 변경되는 데이터 사용 시 발생할 수 있는 리스크를 관리할 수 있는 방법을 적용하는 것이 좋다.
  • 관련 리팩토링
    - "변수 캡슐화하기"를 적용해 데이터를 변경할 수 있는 메소드를 제한하고 관리할 수 있다.
    - "변수 쪼개기"을 사용해 여러 데이터를 저장하는 변수를 나눌 수 있다.
    - "코드 정리하기"를 사용해 데이터를 변경하는 코드를 분리하고 피할 수 있다.
    - "함수 추출하기" 으로 데이터를 변경하는 코드로부터 사이드 이팩트가 없는 코드를 분리할 수 있다.
    - "질의 함수와 변경 함수 분리하기" 를 적용해서 클라이언트가 원하는 경우에만 사이드 이팩트가 있는 함수를 호출하도록 API를 개선할 수 있다.
    - 가능하다면 "세터 제거하기"를 적용한다.
    - 계산해서 알아낼 수 있는 값에는 "파생 변수를 질의 함수로 바꾸기" 를 적용할 수 있다.
    - 변수가 사용되는 범위를 제한하려면 "여러 함수를 클래스로 묶기" 또는 "여러 함수를 변환 함수로 을 적용할 수 있다.
    - "참조를 값으로 바꾸기"를 적용해서 데이터 일부를 변경하기 보다는 데이터 전체를 교체할 수 있다.
리팩토링 18. 변수 쪼개기
  • 어떤 변수가 여러번 재할당 되어도 적절한 경우
    - 반복문에서 순회하는데 사용하는 변수 또는 인덱스
    - 값을 축적시키는데 사용하는 변수
  • 그밖에 경우에 재할당 되는 변수가 있다면 해당 변수는 여러 용도로 사용되는 것이며 변수를 분리해야 더 이해하기 좋은 코드를 만들 수 있다.
    - 변수 하나 당 하나의 책임을 지도록 만든다.
    - 상수를 활용하자. (자바스크립트의 const, 자바의 final)
리팩토링 19. 질의 함수와 변경 함수 분리하기
  • "눈에 띌만한" 사이드 이펙트 없이 값을 조회할 수 있는 메소드는 테스트하기도 쉽고, 메소드를 이동하기도 편하다.
  • 명령-조회 분리 규칙 :
    - 어떤 값을 리턴하는 함수는 사이드 이펙트가 없어야 한다.
  • "눈에 띌만한 사이드 이팩트"
    - 가령, 캐시는 중요한 객체 상태 변화는 아니다. 따라서 어떤 메소드 호출로 인해, 캐시 데이터를 변경하더라도 분리할 필요는 없다.
리팩토링 20. 세터 제거하기
  • 세터를 제공한다는 것은 해당 필드가 변경될 수 있다는 것을 뜻한다.
  • 객체 생성시 처음 설정된 값이 변경될 필요가 없다면 해당 값을 설정할 수 있는 생성자를 만들고 세터를 제거해서 변경될 수 있는 가능성을 제거해야 한다.
리팩토링 21. 파생 변수를 질의 함수로 바꾸기
  • 변경할 수 있는 데이터를 최대한 줄이도록 노력해야 한다.
  • 계산해서 알아낼 수 있는 변수는 제거할 수 있다.
    - 계산 자체가 데이터의 의미를 잘 표현하는 경우도 있다.
    - 해당 변수가 어디선가 잘못된 값으로 수정될 수 있는 가능성을 제거할 수 있다.
  • 계산에 필요한 데이터가 변하지 않는 값이라면, 계산의 결과에 해당하는 데이터 역시 불변 데이터기 때문에 해당 변수는 그대로 유지할 수 있다.
  • assert 사용하여 체크 할 수 있다.
  • stream의 reduce, sum 등을 사용하여 연산을 간단히 할 수 있다.
리팩토링 22. 여러 함수를 변환 함수로 묶기
  • 관련있는 여러 파생 변수를 만들어내는 함수가 여러곳에서 만들어지고 사용된다면 그러한 파생 변수를 "변환 함수"를 통해 한 곳으로 모아둘 수 있다.
  • 소스 데이터가 변경될 수 있는 경우에는 "여러 함수를 클래스로 묶기" 를 사용하는 것이 적절한다.
  • 소스 데이터가 변경되지 않는 경우에는 두 가지 방법을 모두 사용할 수 있지만, 변환 함수를 사용해서 불변 데이터의 필드로 생성해 두고 재사용할 수도 있다.
  • record : 자바 14부터 지원, 생성자를 통해 생성하고 모든 필드가 final인 class => 질의문을 위해 사용함
  • record를 만들고, 부가적인 필드를 추가하여 중복되는 질의 함수를 모두 record를 통해 조회
리팩토링 23. 참조를 값으로 바꾸기
  • 레퍼런스 객체 vs 값 객체
    - 값 객체는 객체가 가진 필드의 값으로 동일성을 확인하다.
    - 값 객체는 변하지 않는다.
    - 어떤 객체의 변경 내역을 다른 곳으로 전파시키고 싶다면 레퍼런스, 아니면 값 객체를 사용한다.
  • hashcode와 equals를 구현하는것을 record를 사용하여 대체할수 있다.
  • getter, setter 생성시 인텔리제이에서 스타일을 선택하여 생성할 수 있다.

뒤엉킨 변경

냄새 7. 뒤엉킨 변경
  • 소프트웨어는 변경에 유연하게 대처할 수 있어야 한다.
  • 어떤 한 모듈이 (함수 또는 클래스가) 여러가지 이유로 다양하게 변경되어야 하는 상황.
    - 예) 새로운 결제 방식을 도입하거나, DB를 변경할 때 동일한 클래스에 여러 메소드를 수정해야 하는 경우.
  • 서로 다른 문제는 서로 다른 모듈에서 해결해야 한다.
    - 모듈의 책임이 분리되어 있을수록 해당 문맥을 더 잘 이해할 수 있으며 다른 문제는 신경쓰지 않아도 된다.
  • 관련 리팩토링 기술
    - 단계 쪼개기 를 사용해 서로 다른 문맥의 코드를 분리할 수 있다.
    - 함수 옮기기 를 사용해 적절한 모듈로 함수를 옮길 수 있다.
    - 여러가지 일이 하나의 함수에 모여 있다면 함수 추출하기를 사용할 수 있다.
    - 모듈이 클래스 단위라면 클래스 추출하기 를 사용해 별도의 클래스로 분리할 수 있다.
  • 좋은 소프트웨어는 응집도는 높아야하며, 결합도는 낮아야한다.
리팩토링 24. 단계 쪼개기
  • 서로 다른 일을 하는 코드를 각기 다른 모듈로 분리한다.
    - 그래야 어떤 것을 변경해야 할 때, 그것과 관련있는 것만 신경쓸 수 있다.
  • 여러 일을 하는 함수의 처리 과정을 각기 다른 단계로 구분할 수 있다.
    - 예) 전처리 -> 주요 작업 -> 후처리
    - 예) 컴파일러 : 텍스트 읽어오기 -> 실행 가능한 형태로 변경
  • 서로 다른 데이터를 사용한다면 단계를 나누는데 있어 중요한 단서가 될 수 있다.
  • 중간 데이터를 만들어 단계를 구분하고 매개변수를 줄이는 데 활용할 수 있다.
리팩토링 25. 함수 옮기기
  • 모듈화가 잘 된 소프트웨어는 최소한의 지식만으로 프로그램을 변경할 수 있다.
  • 관련있는 함수나 필드가 모여있어야 더 쉽게 찾고 이해할 수 있다.
  • 하지만 관련있는 함수나 필드가 항상 고정적인 것은 아니기 때문에 때에 따라 옮겨야 할 필요가 있다.
  • 함수를 옮겨야 하는 경우
    - 해당 함수가 다른 문맥(클래스)에 있는 데이터(필드)를 더 많이 참조하는 경우.
    - 해당 함수를 다른 클라이언트(클래스)에서도 필요로 하는 경우.
  • 함수를 옮겨갈 새로운 문맥(클래스)이 필요한 경우에는 "여러 함수를 클래스로 묶기" 또는 "클래스 추출하기"를 사용한다.
  • 함수를 옮길 적당한 위치를 찾기가 어렵다면, 그대로 두어도 괜찮다. 언제든 나중에 옮길수 있다.
리팩토링 26. 클래스 추출하기
  • 클래스가 다루는 책임이 많아질수록 클래스가 점차 커진다.
  • 클래스를 쪼개는 기준
    - 데이터나 메소드 중 일부가 매우 밀접한 관련이 있는 경우
    - 일부 데이터가 대부분 같이 바뀌는 경우
    - 데이터 또는 메소드 중 일부를 삭제한다면 어떻게 될 것인가?
  • 하위 클래스를 만들어 책임을 분산 시킬 수도 있다.

산탄총 수술

냄새 8. 산탄총 수술
  • 어떤 한 변경 사항이 생겼을 때 여러 모듈을 (여러 함수 또는 여러 클래스를) 수정해야 하는 상황.
    - "뒤엉킨 변경" 냄새와 유사하지만 반대의 상황이다.
    - 예) 새로운 결제 방식을 도입ㄹ하려면 여러 클래스의 코드를 수정해야 한다.
  • 변경 사항이 여러곳에 흩어진다면 찾아서 고치기도 어렵고 중요한 변경 사항을 놓칠 수 있는 가능성도 생긴다.
  • 관련 리팩토링 기술
    - "함수 옮기기" 또는 "필드 옮기기"를 사용해서 필요한 변경 내역을 하나의 클래스로 모을 수 있다.
    - 비슷한 데이터를 사용하는 여러 함수가 있다면 "여러 함수를 클래스로 묶기" 를 사용할 수 있다.
    - "단계 쪼개기" 를 사용해 공통으로 사용되는 함수의 결과물들을 하나로 묶을 수 있다.
    - "함수 인라인" 과 "클래스 인라인"로 흩어진 로직을 한곳으로 모을 수 있다.
리팩토링 27. 필드 옮기기
  • 좋은 데이터 구조를 가지고 있다면, 해당 데이터에 기반한 어떤 행위를 코드로 (메소드나 함수) 옮기는 것도 간편하고 단순해진다.
  • 처음에는 타당해 보였던 설계적인 의사 결정도 프로그램이 다루고 있는 도메인과 데이터 구조에 대해 더 많이 익혀나가면서, 틀린 의사 결정으로 바뀌는 경우도 있다.
  • 필드를 옮기는 단서:
    - 어떤 데이터를 항상 어떤 레코드와 함께 전달하는 경우.
    - 어떤 레코드를 변경할 때 다른 레코드에 있는 필드를 변경해야 하는 경우.
    - 여러 레코드에 동일한 필드를 수정해야 하는 경우
    - (여기서 언급한 '레코드'는 클래스 또는 객체로 대체할 수도 있음)
  • getter와 setter 생성 후, 옮기는게 안전할 수도 있다.
리팩토링 28. 함수 인라인
  • "함수 추출하기" 의 반대에 해당하는 리팩토링
    - 함수로 추출하여 함수 이름으로 의도를 표현하는 방법.
  • 간혹, 함수 본문이 함수 이름 만큼 또는 그보다 더 잘 의도를 표현하는 경우도 있다.
  • 함수 리팩토링이 잘못된 경우에 여러 함수를 인라인하여 커다란 함수를 만든 다음에 다시 함수 추출하기를 시도할 수 있다.
  • 단순히 메소드 호출을 감싸는 우회형 메소드라면 인라인으로 없앨 수 있다.
  • 상속 구조에서 오버라이딩 하고 있는 메소드는 인라인 할 수 없다. (해당 메소드의 일종의 규약이니까)
리팩토링 29. 클래스 인라인
  • "클래스 추출하기"의 반대에 해당하는 리팩토링
  • 리팩토링을 하는 중에 클래스의 책임을 옮기다보면 클래스의 존재 이유감 빈약해지는 경우가 발생할 수 있다.
  • 두개의 클래스를 여러 클래스로 나누는 리팩토링을 하는 경우에, 우선 "클래스 인라인"을 적용해서 두 클래스의 코드를 한 곳으로 모으고 그런 다음에 "클래스 추출하기"를 적용해서 새롭게 분리하는 리팩토링을 적용할 수 있다.

 

기능 편애

냄새 9. 기능 편애
  • 어떤 모듈에 있는 함수가 다른 모듈에 있는 데이터나 함수를 더 많이 참조하는 경우에 발생한다.
    - 예) 다른 객체의 getter를 여러개 사용하는 메소드
  • 관련 리팩토링 기술
    - "함수 옮기기"를 사용해서 함수를 적절한 위치로 옮긴다.
    - 함수 일부분만 다른 곳의 데이터와 함수를 많이 참조한다면 "함수 추출하기" 로 함수를 나눈 다음에 함수를 옮길 수 있다.
  • 만약에 여러 모듈을 참조하고 있다면? 그 중에서 가장 많은 데이터를 참조하는 곳으로 옮기거나, 함수를 여러개로 쪼개서 각 모듈로 분산 시킬 수도 있다.
  • 데이터와 해당 뎅터를 참조하는 행동을 같은 곳에 두도록 하자.
  • 예외적으로 데이터와 행동을 분리한 디자인 패턴 (전략 패턴 또는 방문자 패턴)을 적용할 수도 있다.

데이터 뭉치

냄새 10. 데이터 뭉치
  • 항상 뭉쳐 다니는 데이터는 한 곳으로 모아두는 것이 좋다.
    - 여러 클래스에 존재하는 비슷한 필드 목록
    - 여러 함수에 전달하는 매개변수 목록
  • 관련 리팩토링 기술
    - "클래스 추출하기"를 사용해 여러 필드를 하나의 객체나 클래스로 모을 수 있다.
    - "매개변수 객체 만들기" 또는 "객체 통째로 넘기기" 를 사용해 메소드 매개변수를 개선할 수 있다.