리팩터링 원칙

리팩터링이란 소프트웨어의 겉보기 동작은 그대로 유지한 채, 코드를 이해하고 수정하기 쉽도록 내부 구조를 변경하는 기법이다.

  • 좋은 설계, 쉬운 이해, 버그 찾기, 프로그래밍 속도 향상에 목적이 있다.
  • 언제 리팩터링 해야 할까?
    • 리팩터링 작업 대부분은 드러나지 않게, 기회가 될 때마 다해야한다
    • 비슷한 일을 세 번째 하게 되면 리팩터링 한다
    • 새로운 기능을 추가학 ㅣ전에 한다
    • 코드를 파악할 때 마다 코드의 의도가 더 명확하게 드러나도록 리팩토링 해야 한다
    • 코드 리뷰 할 때
  • 리팩터링하는 동안에는 코드가 항상 정상 작동하기 때문에 전체 작업이 끝나지 않았더라도 언제든 멈출 수 있다.

코드에서 나는 악취

1. 기이한 이름

함수, 모듈, 변수 , 클래스 등은 그 이름만 보고도 각각이 무슨 일을 하고 어떻게 사용해야 하는지 명확히 알 수 있도록 신경써서 이름을 지어야 한다.

2. 중복 코드

3. 긴 함수

  • 함수를 적극적으로!! 쪼개기
  • 주석을 달아야 할 만한 부분을 함수로 만들기
  • 조건문 반복문 처리 : 조건문 분해하기(10.1), 조건문 통합하기(10.2)

    4. 긴 매개변수 목록

  • 매개변수는 함수 동작에 변화를 줄 수 있는 일차적인 수단
  • 매개변수를 질의 함수로 바꾸기(11.5) : 피호출함수가 매개변수로 받지 않고 함수 내부에서 값을 얻을 수 있으면 이렇게 한다. 단, 매개변수를 제거했을 때 피호출 함수에 원치않는 의존성이 생기지 않는 경우에만!
  • 객체를 통째로 넘기기(11.4) : 하나의 레코드에서 값을 가져와 넘길 때에는 레코드를 통째로 넘긴다. 함수 내부에서 레코드를 사용하기 편리하고, 사용하는 값이 추가되거나 바뀔 때 매개변수를 수정하지 않아도 됨
  • 매개변수 객체 만들기(6.8)
  • 플래그 인수 제거하기(11.3)

    5. 전역 데이터

  • 전역 데이터는 코드베이스 어디서든 건드릴 수 있고, 값을 누가 바꿨는지 찾아낼 메커니즘이 없다
  • 6.6 함수 캡슐화하기 : 데이터로의 접근을 독점하는 함수를 만든다. → 데이터를 변경하고 사용하는 코드를 감시할 수 있다

    6. 가변 데이터

  • 데이터를 변경했더니 예상치 못한 결과가 나올 가능성이 있다.
  • 9.1 변수 쪼개기 : 하나의 변수에 값을 한 번만 할당한다. 두 번 이상 할당해야한다면 여러가지 역할을 하는 것이므로 쪼개야 한다. const로 선언할 것을 권장!
  • 8.6 문장 슬라이스 : 관련된 코드들을 모아놓기, 변수를 한 곳에서 선언하기 보다 처음 사용할 때 선언
  • 6.1 함수 추출
  • 11.1 질의함수와 변경함수 분리하기

    7. 뒤엉킨 변경

  • 하나의 모듈이 여러 가지 방식으로 변경되는 일이 많은 경우
  • 원인: 단일 책임 원칙(SRP) 지켜지지 않아서
  • 맥락별로 분리하기
  • 맥락이 다르면 독립된 모듈로 분리
  • 단계 쪼개기: 맥락끼리 특정한 데이터 구조를 담아 전달
  • 함수 옮기기: 맥락이 동일한 함수들을 모듈로 모으기

8. 산탄총 수술

  • 코드를 변경할 때마다 자잘하게 수정해야 하는 클래스가 많을 때
  • 함께 변경되는 대상을 한 모듈에 묶어두기 : 필드 옮기기(8.2), 함수 옮기기(8.1)
  • 비슷한 데이터를 다루는 함수 묶기 : 여러 함수를 클래스로 묶기(6.9), 여러 함수를 변환 함수로 묶기(6.10)
  • 인라인 리팩터링

기본적인 리팩터링

  • 가장 많이 하는 리팩터링은 함수 추출하기와 변수 추출하기 (리팩터링은 본래 코드를 변경하는 작업인 만큼 반대로 함수, 변수 인라인하기도 많이함)
  • 추출은 결국 이름 짓기
  • 자주 함께 뭉쳐다니는 인수는 매개변수 객체로 만들면 편리하다
  • 함수를 그룹으로 묶을 때는 클래스로 묶기

함수 추출하기

  • 이름만 봐도 무엇을 하는지 알 수 있게
  • 목적과 구현을 분리
  • 함수를 추출할 때 추출한 코드에서만 사용하는 변수가 추출한 함수 밖에 선언되어 있다면 추출한 함수 안에서 선언하도록 수정
  • 중첩 함수도 가능 (지원하는 언어에서만)
  • 코드를 독립된 함수로 묶는 시점
    • 길이가 한 화면 안 넘어가기
    • 재사용성
    • 두 번 이상 사용될 코드
  • 함수가 긴 것 보다 짧게 여러번 호출되는게 오히려 성능에 유리하다
  • 함수를 짧게 작성할때의 이점은 이름을 잘 지어야만 발휘된다

매개변수 객체 만들기

  • 데이터의 관계 명확해짐, 매개 변수 줄어듦, 데이터의 일관성이 높아짐
  • 데이터 구조를 활용하는 형태로 프로그램 동작을 재구성 할 수 있다 = 데이터 구조에 담긴 데이터에 공통으로 적용되는 동작을 추출

여러 함수를 클래스로 묶기

  • 같은 인수로 긴밀하게 엮여 작동하는 함수들은 클래스로 묶는다
  • 장점 : 함수들이 공유하는 공통 환경을 명확하게 표현할 수 있다, 함수 인수를 줄여서 객체 안에서 호출을 간결하게 할 수 있다

여러 함수를 변환 함수로 묶기

  • 원본 데이터를 입력받아서 필요한 정보를 모두 도출한 뒤 각각을 출력 데이터 필드에 넣어서 반환

캡슐화

  • 모듈을 분리하는 가장 중요한 기준은 자신을 제외한 다른 부분에 드러내지 않아야 할 비밀을 얼마나 잘 숨기고 있느냐
  • 레코드 캡슐화하기, 컬렉션 캡슐화하기로 숨길 수 있다.
  • 기본형 데이터는 기본형을 객체로 바꾸기로 캡슐화 할 수 있다.
  • 클래스는 본래 정보를 숨기는 용도로 설계되었다.
  • 클래스 추출하기, 인라인하기도 활용할 수 있다.

기본형을 객체로 바꾸기

  • 단순한 출력 이상의 기능이 필요해지는 순간 데이터를 표현하는 전용 클래스를 정의하는 편
  • 특별한 동작이 추가되거나 프로그램이 커질 수록 유용한 도구가 된다.
  • 절차
    • 데이터 값을 다루기 전 변수부터 캡슐화
    • 단순한 값 클래스를 만든다 (인수로 받아서 저장하는 생성자와 값을 반환하는 게터 추가)
    • 기존 클래스에서 게터를 통해 새로만든 클래스의 게터를 호출한 결과를 반환하도록 게터를 수정
    • 테스트

임시 변수를 질의 함수로 바꾸기

  • 함수에서 어떤 결과값을 다시 참조하는 목적으로 임시 변수를 쓴다
  • 임시 변수를 사용하면 값을 계산하는 코드 반복을 줄이고 설명할 수도 있어서 유용하다
  • 함수로 만들어 사용하는 편이 낫다.
  • 각각의 임시 변수들을 함수로 만들어 사용하면 변수를 따로 전달할 필요도 없고 함수의 경계가 더 분명해지고 부자연스러운 의존 관계나 부수효과를 찾는데 도움이된다
  • 다른 함수에서도 사용할 수 있어 도움이된다.
  • 스냅숏 용도로 쓰이는 변수에는 이 리펙터링을 하면 안된다

기능이동

  • 관련된 코드끼리 모이도록 코드 조각을 옮기기
  • ex. 변수를 첫머리에 모아두지 않고 처음 사용할 때 선언하기

함수 옮기기

필드 옮기기

  • 필요한 경우 : 함수에 어떤 레코드를 넘길 때마다 또 다른 레코드의 필드도 함께 넘기는 경우, 레코드를 변경할 때 다른 것도 변경해야 할 경우, 여러 개에 정의된 똑같은 필드를 갱신해야한다면 한 번만 갱신해도 되게끔

데이터 조직화

변수 쪼개기

  • 하나의 변수가 여러 역할을 가지지 않도록
  • 변수에 값이 두 번 이상 대입되면 역할이 두 개라는 신호 (수집 변수 제외)
  • 웬만하면 불변으로 선언

필드 이름 바꾸기

  • 우선 캡슐화 하기
  • 객체 안의 필드명과 메서드를 수정
  • 이후, 접근자 수정
  • 리팩터링 도중 테스트에 실패한다면 더 작은 단계로 나눠야 한다!

파생 변수를 질의 함수로 바꾸기

  • 질의 함수: 연산을 통해 값을 구하는 함수
  • 계산해 낼 수 있는 변수를 제거하여 함수로 분리하기

참조를 값으로 바꾸기

  • 객체 내부의 속성을 변경하는 경우, 참조를 이용하지 않고 값으로 기존 객체를 통째로 대체하기
  • 계산해 낼 수 있는 변수를 제거하여 함수로 분리하기

값을 참조로 바꾸기

  • 같은 데이터를 여러번 복제하여 사용할 때, 참조를 이용하기
  • 값을 복제하면 데이터를 갱신할 때 일관성이 깨지는 문제 발생
  • 같은 데이터를 참조할 수 있는 저장소 객체 생성하여 이용
  • 유일하게 접근할 수 있는 위치

매직 리터럴 바꾸기

  • 코드의 의미를 분명히 드러내는 이름의 상수로 바꾸기
  • 리터럴이 함수 하나에서 쓰이거나, 맥락 정보를 충분히 제공할 때에는 굳이 사용할 필요

조건부 로직 간소화

조건문 분해하기

  • 복잡한 조건부 로직은 프로그램을 복잡하게 만드는 흔한 원흉
  • 조건식을 먼저 함수로 추출하고 다음으로 조건절을 함수로 추출한다
  • 해당 리펙토링을 진행하면 조건식과 조건절이 좀 더 명확해진다.
  • 조건식과 그 조건식에 딸린 조건절 각각을 함수로 추출 → 조건이 무엇인지 강조하고, 무엇을 분기했는지 명확해짐

조건식 통합하기

  • 비교하는 조건은 다르지만 그 결과로 수행하는 동작은 같은 코드들을 통합시킨다.
  • OR 연산자를 통해 합친 조건식을 함수로 추출해서 사용한다.
  • 동작이 다른 독립적인 조건문은 리펙토링 해서는 안된다.

중첩된 조건문을 보호 구문으로 바꾸기

  • 모두 정상 동작으로 이어지는 형태의 조건문은 if else 절
  • 정상, 비정상이 나뉘어 진다면 보호 구문으로 바꾸기

특이 케이스 추가하기

  • 데이터 구조의 특정 값을 확인한 후 똑같은 동작을 수행 === 중복코드
  • 이처럼 특정 값에 대해 똑같이 반응하는 코드가 여러 곳이라면 그 반응들을 한 데로 모으는게 좋다.
  • 특수한 경우의 공통 동작을 요소 하나에 모아서 사용것을 특이 케이스 패턴이라 한다.
  • 해당 패턴을 사용하면 코드 대부분을 단순한 함수 호출로 변경할 수 있다.
  • Null을 특이 케이스로 보는 경우가 많아사 위 패턴을 널 객체 패턴이라고도 한다.
  • 절차
    • 컨테이너 (데이터구조, 클래스) 에서 특이 케이스를 검사하는 속성을 추가하고 false 값을 반환하게 한다.
    • 특이 케이스 객체를 만든다. 이 객체는 특이 케이스인지를 검사하는 속성만 포함하고 true를 반환한다.
    • 특이 케이스인지 검사하는 코드를 함수로 추출한다.
    • 특이 케이스를 검사하는 함수에서 특이 케이스 객체의 속성을 사용하게 변경한다.
    • 특이 케이스 검사 함수를 클래스로 묶거나 여러 함수를 변환함수로 묶기를 적용해서 공통 동작을 새로운 요소로 옮긴다.

'' 카테고리의 다른 글

클린코드  (0) 2022.01.18

+ Recent posts