[리팩터링] Chapter01: 첫 번째 예시

Hyeonjeong Jeon·2022년 10월 13일
1
post-thumbnail

들어가며
한달동안 진행했던 리팩터링 스터디를 마무리하면서 책에서 소개하는 리팩터링 기법에 대해서 복습하며 정리하는 글이다.

다루는 내용
이 책은 전문 프로그래머를 대상으로 쓴 리팩터링 지침서로, 절제되고 효율적인 방식으로 리팩터링하는 법을 알려주는 것이 목표라고 한다. 코드에 버그가 생기지 않게 하면서 구조를 더 체계적으로 바꾸는 식으로 리팩터링하는 방법을 설명한다.

리팩터링이란?

소프트웨어의 겉보기 동작은 그대로 유지한 채, 코드를 이해하고 수정하기 쉽도록 내부 구조를 변경하는 기법이다. 특정한 방식에 따라 코드를 정리하는 것으로, 동작을 보존하는 작은 단계들을 거쳐 코드를 수정하고 이러한 단계들을 순차적으로 연결하여 큰 변화를 만들어내는 일이다.

  • 소프트웨어를 개발할 때 목적이 기능 추가리팩터링이냐를 명확히 구분해야 한다.
    • 기능 추가: 기존 코드는 건드리지 않고 새 기능 추가
    • 리팩터링: 기능 추가는 하지 않고 코드 재구성에만 전념
  • 켄트 벡은 이를 두 개의 모자(two hats)에 비유하며 상황하게 맞게 바꿔 쓰며 작업하라고 명시했다.
  • 항상 어떤 모자를 쓰고 있는지와, 그에 따른 미묘한 작업 방식의 차이를 분명하게 인식해야한다.

리팩터링을 하는 이유

  1. 소프트웨어 설계 좋아진다.
    • 규칙적인 리팩토링을 통해 내부 구조를 유지
    • 중복 코드량을 줄여 모든 코드가 고유한 일을 수행할 수 있도록 보장
  2. 소프트웨어를 이해하기 쉬워진다.
    • 컴퓨터에 시키려는 일과 이를 표현한 코드의 차이를 줄여 코드의 목적이 잘 드러나게 함
  3. 버그를 쉽게 찾을 수 있다.
    • 코드를 쉽게 이해할 수 있다는건 버그를 찾기 쉬워진다는 것을 의미
  4. 프로그래밍 속도를 높일 수 있다.
    • 리팩터링 시 초기 시간이 소요되지만, 장기적으로는 지구력이 높아져 빠르게 개발 가능

리팩터링을 해야하는 시점

  • 3의 법칙 (돈 로버츠 Don Roberts) -> 삼진 리팩터링
    1. 처음에는 그냥 한다.
    2. 비슷한 일을 두 번째로 하게되어도, 그냥 한다.
    3. 비슷한 일을 세 번째로 하게 될 때는 리팩터링을 실시한다.
  • 새로운 기능을 추가하기 직전에 한다.

리팩터링의 첫 단계

  • 리팩터링할 코드 영역을 꼼꼼하게 검사해줄 테스트 코드들부터 마련한다.
  • 테스트는 반드시 자가진단하도록 만든다.

🛠 리팩터링 기법 🛠

함수 추출하기(6.1절)

: 코드 조각을 별도 함수로 추출하는 방식으로, 앞서 파악한 정보를 코드에 반영하는 것

  • e.g. 공연에 대한 요금 계산을 하는 코드 조각 → amountFor(aPerformance) 함수로 분리
  • 함수로 빼냈을 때 유효범위를 벗어나는 변수, 즉 새 함수에서는 곧바로 사용할 수 없는 변수가 있는지 확인하기

임시 변수를 질의 함수로 바꾸기(7.4절)

  • 긴 함수의 한 부분을 별도 함수로 추출하고자 할 때 먼저 변수들을 각각의 함수로 만들면 추출한 함수에 변수를 따로 전달할 필요가 없어져 일이 수월해진다.
  • 비슷한 계산을 수행하는 다른 함수에서도 사용할 수 있어 코드 중복이 줄어든다.
  • e.g.
    const basePrice = this._queantity * this._item.price;
    get basePrice() { return this._quantity * this._item.price; }
    const basePrice = this.basePrice;

변수 인라인하기(6.4절)

  • e.g. const play = playFor(perf) → let this.Amount = amounFor(perf, playFor(perf));

함수 선언 바꾸기(6.5절)

  • e.g. play를 playFor() 호출로 변경해서 매개변수를 제거

    	${play.type} → ${playFor(performance).type}

    play 변수를 제거한 결과 로컬 유효범위의 변수가 하나 줄어서 적립 포인트 계산 부분을 추출하기가 쉬워졌다.

  • 지역 변수를 제거해서 얻는 가장 큰 장점은 추출 작업이 훨씬 쉬워진다는 것이다.
    → 유효범위를 신경 써야 할 대상이 줄어들기 때문에

반복문 쪼개기(8.7절)

: 여러가지 일을 수행하는 반복문을 각각의 반복문으로 분리하는 방법

  • 수정할 동작 하나만 이해하면 된다.

    리팩터링과 최적화를 구분하자!
    → 최적화는 코드를 깔끔히 정리한 이후에 수행

  • 각 반복문을 함수로 추출한다.

    • 반복문을 파이프라인으로 바꾸기(8.7절) → 가독성 good 라인수 down

문장 슬라이드하기(8.6절)

  • 모든 변수 선언을 함수 첫머리에 모아두기 → 변수를 처음 사용할 때 선언하기
  • 관련된 코드들이 가까이 모여 있다면 이해하기가 더 쉽다.
  • e.g. 변수 선언(초기화)을 반복문 앞으로 이동

단계 쪼개기(6.11절)

: 서로 다른 두 대상을 한꺼번에 다루는 코드를 별개 모듈로 나누는 방법 → 두 대상을 동시에 생각할 필요 없이 하나에만 집중 가능하다.

함수 옮기기(8.1절)

  • 어떤 함수가 자신이 속한 모듈 A의 요소들보다 다른 모듈 B의 요소들을 더 많이 참조한다면 모듈 B로 옮겨줘야 마땅하다.
  • 함수를 옮길지 말지 고민되는 경우, 대상 함수의 현재 컨텍스트와 후보 컨텍스트를 둘러보면 도움이 된다.

💡 1-6. 계산 단계와 포맷팅 단계 분리하기

  • statement()의 HTML 버전을 만드는 작업
    - 분리된 계산 함수들이 텍스트 버전인 statement() 안에 중첩 함수로 들어가 있는 문제점
  1. statement()에 필요한 데이터 처리
    • 중간 데이터 구조 역할을 할 객체 만들어서 renderPlainText()에 인수로 전달
      → renderPlainText()의 두 인수(invoice, plays)를 통해 전달되는 데이터를 중간 데이터 구조로 옮기면, 계산 관련 코드는 전부 statement() 함수로 모으고 renderPlainText()는 data 매개변수로 전달된 데이터만 처리하게 만들기 가능
  • result = Object.assign({}, aPerformance) // 얕은 복사 수행
    → 함수로 건넨 데이터를 수정하기 싫어서. 데이터를 최대한 불변처럼 취급하기
  1. 앞서 처리한 결과를 텍스트나 HTML로 표현

반복문을 파이프라인으로 바꾸기(8.8절)

  • AS - IS
function totalVolumeCredits() {
	let result = 0;
	for (let perf of data.performances) {
		result += perf.volumeCredits;
	}
	return result;
}
  • TO - BE
    function totalVolumeCredits(data) {
    	return data.performances
    	reduce((total, p) ⇒ total + p.volumeCredits, 0);
    }

조건부 로직을 다형성으로 바꾸기(10.4절)

: 조건부 코드 한 덩어리를 다형성을 활용하는 방식으로 바꿔준다.

  • 클래스에 로직 담기
  • 타입 코드 대신 서브클래스를 사용하도록 변경하기

타입 코드를 서브클래스로 바꾸기(12.6절)

↔ 서브클래스를 제거하기(12.7절)

  • 하위 리팩터링:
    - 타입코드를 상태/전략 패턴으로 바꾸기
    - 서브클래스 추출하기
  • 서브클래스
    - 조건에 따라 다르게 동작하도록 해주는 다형성 제공
    - 특정 타입에서만 의미가 있는 값을 사용하는 필드나 메서드가 있을 때 발현
    (e.g. ‘판매 목표’는 ‘영업자’ 유형일 때만 의미가 있다.)

📚 메모 📚

좋은 코드를 가늠하는 확실한 방법은 ‘얼마나 수정하기 쉬운가’다.

참고

https://ifuwanna.tistory.com/503
https://asce-hyunseung.tistory.com/171

profile
👩🏻‍🌾기억을 기록하고 공유하자📚

0개의 댓글