리팩터링 | 마틴 파울러 | 한빛미디어- 교보ebook

코드 구조를 체계적으로 개선하여 효율적인 리팩터링 구현하기, 20여 년 만에 다시 돌아온 마틴 파울러의 리팩터링 2판 리팩터링 1판은 1999년 출간되었으며, 한국어판은 2002년 한국에 소개되었다

ebook-product.kyobobook.co.kr

개요

클래스는 대다수 언어가 제공하는 기본적인 빌딩 블록

클래스는 데이터와 함수를 하나의 공유 환경으로 묶어 그중 일부를 외부에 제공한다.

 

함수 호출 인수로 전달되는 공통 데이터를 중심으로 작동하는 함수들을 클래스 하나로 묶는다.

클래스로 묶으면 함수들 공통 환경을 명확히 표현할 있다.

 

클래스로 묶을 장점은 클라이언트가 객체의 핵심 데이터를 변경할 있고, 파생 객체들을 일관되게 관리할 있다.

 

함수들을 중첩함수로 묶는도 되지만, 클래스를 지원하는 언어라면 클래스로 묶는다.

만약 클래스를 지원하지 않는 언어라는 '함수를 객체처럼 패턴' 사용을 고려한다.

//함수를 객체처럼
function Cat(name){
    return {
        name : name,
        getName : function(){return name;},
        setName : function(arg){name=arg},
        sound : function(){sound(name);}
    };
    function sound(name){console.log(name + " 냐아아아옹");}
}
var cat = Cat('나비');
cat.sound();
cat.setName('배추나비');
cat.sound();
///
나비 냐아아아옹
배추나비 냐아아아옹

 

예시

마시는 차를 정부에서 수돗물처럼 제공한다고 가정, 계량기를 읽어서 측정값을 기록 계산하는 코드

//측정값
let reading={customer : "ivan", quantity:10, month:5, uear:2017};
//클라이언트1
const aReading = acquireReading();
const baseCharge = baseRate(aReading.month, aReading.year) * aReading.quantity;
//클라이언트2
const aReading2 = acquireReading();
const baseCharge2 = baseRate(aReading2.month, aReading2.year) * aReading2.quantity;
const taxableCharge2 = Math.max(0,base-taxTreshold(aReading2.year));
//클라이언트3
const aReading3 = acquireReading();
const basicChargeAmount3 = calculateBaseCharge(aReading3);
function calculateBaseCharge(aReading){
    return baseRate(aReading.month, aReading.year);
}
//레코드를 클래스로 캡슐화
class Reading{
    constructor(data){
        this._customer = data.customer;
        this._quantity = data.quantity;
        this._month = data.month;
        this._year = data.year;
    }
    get customer(){return this._customer;}
    get quantity(){return this._quantity;}
    get month(){return this._month;}
    get year(){return this._year;}
}


//클라이언트3
const rawReading = acquireReading();
const aReading3 = new Reading(rawReading);
const basicChargeAmount = aReading3.calculateBaseCharge;
//레코드를 클래스로 캡슐화
class Reading{
    constructor(data){
        this._customer = data.customer;
        this._quantity = data.quantity;
        this._month = data.month;
        this._year = data.year;
    }
    get customer(){return this._customer;}
    get quantity(){return this._quantity;}
    get month(){return this._month;}
    get year(){return this._year;}
    //함수 옮김
    get calculateBaseCharge(){
        return baseRate(this.month, this.year);
    }
}
const rawReading = acquireReading();
const aReading3 = new Reading(rawReading);
const basicChargeAmount = aReading3.baseCharge;
class Reading{
    constructor(data){
        this._customer = data.customer;
        this._quantity = data.quantity;
        this._month = data.month;
        this._year = data.year;
    }
    get customer(){return this._customer;}
    get quantity(){return this._quantity;}
    get month(){return this._month;}
    get year(){return this._year;}
    //알맞은 이름 짓기
    get baseCharge(){
        return baseRate(this.month, this.year);
    }
}
//클라이언트1
const rawReading = acquireReading();
const aReading = new Reading(rawReading);
const baseCharge = aReading.baseRate;


//클라이언트2
const rawReading2 = acquireReading();
const aReading2 = new Reading(rawReading);
const taxableCharge2 = Math.max(0,aReading2.baseRate-taxTreshold(aReading2.year));


//클라이언트는 aReading.baseRate  이렇게 호출한다.
이게 필드인지 함수호출로 계산된 값인지 구분할 수없다. 이를 단일 접근 원칙이라 한다.
//클라이언트2
const rawReading2 = acquireReading();
const aReading2 = new Reading(rawReading);
const taxableCharge2 = taxableChargeFn() ;
//함수로 추출
function taxableChargeFn() {
    return Math.max(0, aReading2.baseRate - taxTreshold(aReading2.year));
}


//측정값
let reading={customer : "ivan", quantity:10, month:5, uear:2017};
//클라이언트1
const rawReading = acquireReading();
const aReading = new Reading(rawReading);
const baseCharge = aReading.baseRate;
//클라이언트2
const rawReading2 = acquireReading();
const aReading2 = new Reading(rawReading);
const taxableCharge2 = aReading2.taxableCharge;
//클라이언트3
const rawReading3 = acquireReading();
const aReading3 = new Reading(rawReading3);
const basicChargeAmount = aReading3.baseCharge;
class Reading{
    constructor(data){
        this._customer = data.customer;
        this._quantity = data.quantity;
        this._month = data.month;
        this._year = data.year;
    }
    get customer(){return this._customer;}
    get quantity(){return this._quantity;}
    get month(){return this._month;}
    get year(){return this._year;}
    get baseCharge(){
        return baseRate(this.month, this.year);
    }
    get taxableCharge() {
        return Math.max(0, this.baseRate - taxTreshold(aReading2.year));
    }
}
 

리팩터링 | 마틴 파울러 | 한빛미디어- 교보ebook

코드 구조를 체계적으로 개선하여 효율적인 리팩터링 구현하기, 20여 년 만에 다시 돌아온 마틴 파울러의 리팩터링 2판 리팩터링 1판은 1999년 출간되었으며, 한국어판은 2002년 한국에 소개되었다

ebook-product.kyobobook.co.kr

개요

명확한 프로그래밍의 핵심은 이름짓기.

자바스크립트 같은 동적 타입 언어라면 앞에 타입을 명시하는 식으로 이름 짓는 것도 좋다. (String name ==> let strName)

예시

가장 간단한 바꾸기는 유효범위가 좁은 변수다.

let tpHd = "untitled";
//참조하는 곳
result += `<h1>${tpHd}</h1>`;
obj['articleTitle'] = 'Hello World';
//바꾸는 곳
tpHd = obj['articleTitle'];
let tpHd = "untitled";
result += `<h1>${title()}</h1>`;
obj['articleTitle'] = 'Hello World';
setTitle(obj['articleTitle']);
//캡슐화
//게터
function title(){ return tpHd;}
//세터
function setTitle(arg){tpHd = arg;}
let 이제변수이름바꿔도됨 = "untitled";
result += `<h1>${title()}</h1>`;
obj['articleTitle'] = 'Hello World';
setTitle(obj['articleTitle']);
function title(){ return 이제변수이름바꿔도됨;}
function setTitle(arg){이제변수이름바꿔도됨 = arg;}
이름 변경 다시 함수를 인라인해도 된다. , 캡슐화한 변수가 전역으로 두루 사용된다면, 나중을 위해 함수로 두는 것이 좋다.

 

예시:상수 이름 바꾸기

상수의 이름은 캡슐화하지 않고 복제 방식으로 점진적으로 바꿀 있다.

const cpyNm = "애크미 구스베리";
//원본 이름 바꾼 후 이전 원본 이름에 바꾼 이름 복제본 대입
const companyName = "애크미 구스베리";
const cpyNm = companyName;

방식은 상수 포함. 클라이언트가 읽기전용인 변수에도 적용할 있다.

자바스크립트 익스포트한 변수

 

리팩터링 | 마틴 파울러 | 한빛미디어- 교보ebook

코드 구조를 체계적으로 개선하여 효율적인 리팩터링 구현하기, 20여 년 만에 다시 돌아온 마틴 파울러의 리팩터링 2판 리팩터링 1판은 1999년 출간되었으며, 한국어판은 2002년 한국에 소개되었다

ebook-product.kyobobook.co.kr

개요

함수 함수 사이를 같이 다니는 여러 데이터 항목을 하나의 데이터 구조로 묶는다.

 

데이터 뭉치를 데이터 구조로 묶으면 데이터 사이 관계가 명확해진다.

그리고 함수 인자 수가 줄어든다.

묶인 데이터는 이름을 가지기 때문에 인자로 사용 일관성을 가진다.

 

생성된 매개변수 객체는 새로운 리팩터링 기회를 만들 있다.

 

예시

const station = {
    name: "ZB1",
    readings: [
        {temp:47, time: "2016-11-10 09:10"},
        {temp:53, time: "2016-11-10 09:20"},
        {temp:58, time: "2016-11-10 09:30"},
        {temp:53, time: "2016-11-10 09:40"},
        {temp:51, time: "2016-11-10 09:50"},
    ]
};
//정상 범위를 벗어난 측정값 탐색
function readingsOutsideRange(station, min, max){
    return station.readings
        .filter(r => r.temp < min || r.temp > max);
}
//호출
let alerts = readingsOutsideRange(station,
     operationPlan.temperatureFloor,
    operationPlan.temperatureCeiling);
//묶을 클래스
class NumberRange{
    constructor(min,max){
        this._data = {min:min, max:max};
    }
    get min(){return this._data.min};
    get max(){return this._data.max};
}
//새로운 데이터 구조 매개변수 추가
function readingsOutsideRange(station, min, max, range){
    return station.readings
        .filter(r => r.temp < min || r.temp > max);
}
let alerts = readingsOutsideRange(station,
     operationPlan.temperatureFloor,
    operationPlan.temperatureCeiling
    ,null);//일단 null
//점진적으로 하나씩 줄여가며 테스트 한다.
function readingsOutsideRange(station, min, /*max,*/ range){
    return station.readings
        .filter(r => r.temp < min || r.temp > range.max);
}
const range = new NumberRange(operationPlan.temperatureFloor,
                             operationPlan.temperatureCeiling);
let alerts = readingsOutsideRange(station,
     operationPlan.temperatureFloor,
    // operationPlan.temperatureCeiling,
    range);
 // 완료
const station = {
    name: "ZB1",
    readings: [
        {temp:47, time: "2016-11-10 09:10"},
        {temp:53, time: "2016-11-10 09:20"},
        {temp:58, time: "2016-11-10 09:30"},
        {temp:53, time: "2016-11-10 09:40"},
        {temp:51, time: "2016-11-10 09:50"},
    ]
};
function readingsOutsideRange(station, range){
    return station.readings
        .filter(r => r.temp < range.min || r.temp > range.max);
}
const range = new NumberRange(operationPlan.temperatureFloor,
                             operationPlan.temperatureCeiling);
let alerts = readingsOutsideRange(station, range);
class NumberRange{
    constructor(min,max){
        this._data = {min:min, max:max};
    }
    get min(){return this._data.min};
    get max(){return this._data.max};
}
//연관된 데이터를 묶음으로서 새로운 설계의 기회를 얻는다.
class NumberRange{
    constructor(min,max){
        this._data = {min:min, max:max};
    }
    get min(){return this._data.min;}
    get max(){return this._data.max;}
    contains(arg){return arg>=this.min&&arg<=this.max;}
}

 

 

 

리팩터링 | 마틴 파울러 | 한빛미디어- 교보ebook

코드 구조를 체계적으로 개선하여 효율적인 리팩터링 구현하기, 20여 년 만에 다시 돌아온 마틴 파울러의 리팩터링 2판 리팩터링 1판은 1999년 출간되었으며, 한국어판은 2002년 한국에 소개되었다

ebook-product.kyobobook.co.kr

개요

리팩터링은 결국 프로그램의 요소를 조작하는 , 조작은 변수보다 함수가 쉽다.

함수는 변경 기존 함수에 전달 함수를 활용해 임시 버퍼 역할을 수도 있어 유연하다. (차후 기존함수명으로 변경)

 

반면에 변수는 번에 바꿔야 해서 까다롭다.

변수의 유효 범위가 좁으면 문제가 거의 없지만 넓어질수록 문제가 된다.

 

넓은 범위 변수는 함수로 데이터를 캡슐화하는 것이 가장 좋은 방법이다.

데이터 재구성 ==> 함수 재구성으로 변환

 

함수는 동작이기에 중간에 검증 같은 조직을 끼워 넣을 있다.

 

이러한 이유가 객체지향에서 객체 데이터를 최대한 private 유지하는 이유다. (데이터에 대한 결합도를 떨어트리기 위함)

 

불변 데이터는 가변 데이터보다 캡슐화할 이유가 적다. 데이터가 변경될 일이 없어서 그냥 복사해서 사용하면 된다.

예시

let spaceship = {};
let defaultOwner = {firstName:"마틴",lastName:"파울러"};
//참조하는 코드
spaceship.owner = defaultOwner;
//갱신하는 코드(불변이라면 갱신 걱정이 없다)
defaultOwner = {firstName:"레베카",lastName:"파슨스"};
let spaceship = {};
let defaultOwner = {firstName:"마틴",lastName:"파울러"};
spaceship.owner = defaultOwner;
defaultOwner = {firstName:"레베카",lastName:"파슨스"};
//게터/세터 생성
function getDefaultOwner(){return defaultOwner;}
function setDefaultOwner(arg){return defaultOwner = arg;}
let spaceship = {};
let defaultOwner = {firstName:"마틴",lastName:"파울러"};
//게터 호출, 대입문은 세터 호출
spaceship.owner = getDefaultOwner();
setDefaultOwner({firstName:"레베카",lastName:"파슨스"});
function getDefaultOwner(){return defaultOwner;}
function setDefaultOwner(arg){return defaultOwner = arg;}
let spaceship = {};
//접근 제한자가 없는 언어에서는 이런 식으로 표현하는 것도 도움된다.
let __privateOnly_defaultOwner = {firstName:"마틴",lastName:"파울러"};
spaceship.owner = getDefaultOwner();
setDefaultOwner({firstName:"레베카",lastName:"파슨스"});
//저자는 자바스크립트에서는 게터에 get을 빼는 것을 선호함
function defaultOwner(){return __privateOnly_defaultOwner;}
function setDefaultOwner(arg){return __privateOnly_defaultOwner = arg;}

 

자바스크립트에선 게터와 세터 이름을 똑같이 짓고 인수 존재 여부로 구분하는 방식을 많이 쓰기때문에 get

 

캡슐화하기

//test_refac.mjs
import {defaultOwner} from './test_refac.mjs';
import { strict as assert } from 'node:assert';
const owner1 = defaultOwner();
assert.equal("파울러",owner1.lastName,"처음 값 확인");
const owner2 = defaultOwner();
owner2.lastName ="파슨스";
assert.equal("파슨스",owner2.lastName,"owner2 변경 후");
import {defaultOwner} from './test_refac.mjs';
import { strict as assert } from 'node:assert';
const owner1 = defaultOwner();
assert.equal("파울러",owner1.lastName,"처음 값 확인");
const owner2 = defaultOwner();
owner2.lastName ="파슨스";
console.log(owner1);
assert.equal("파슨스",owner1.lastName,"owner2 변경 후");
console.log(owner1);
////// 객체 값이 변경된 것을 볼 수 있다.
Debugger attached.
{ firstName: '마틴', lastName: '파슨스' }
{ firstName: '마틴', lastName: '파슨스' }
Waiting for the debugger to disconnect…
let spaceship = {};
let defaultOwnerData = {firstName:"마틴",lastName:"파울러"};
spaceship.owner = defaultOwner();
//방법1 게터에서 객체 복사본을 리턴한다.
export function defaultOwner(){return Object.assign({},defaultOwnerData);}
export function setDefaultOwner(arg){defaultOwnerData = arg;}
결과


클라이언트에서 오히려 원본을 원하는 경우를 주의해야 한다.
let defaultOwnerData = {firstName:"마틴",lastName:"파울러"};
//방법 2 레코드 캡슐화하기
export function defaultOwner(){return new Person(defaultOwnerData);}
export function setDefaultOwner(arg){defaultOwnerData = arg;}
class Person{
    constructor(data){
        this._lastName = data.lastName;
        this._firstName = data.firstName;
    }
    get lastName(){return this._lastName;}
    get firstName(){return this._firstName;}
}

경우에 따라 세터도 복사본을 저장하는게 좋을 수도 있다.

 

복제가 성능에 주는 영향을 미미하다. 반면에 원본을 그대로 사용하면 나중에 디버깅하기 어렵고 시간도 오래 걸릴 위험이 있다.

 

리팩터링 | 마틴 파울러 | 한빛미디어- 교보ebook

코드 구조를 체계적으로 개선하여 효율적인 리팩터링 구현하기, 20여 년 만에 다시 돌아온 마틴 파울러의 리팩터링 2판 리팩터링 1판은 1999년 출간되었으며, 한국어판은 2002년 한국에 소개되었다

ebook-product.kyobobook.co.kr

개요

함수는 프로그램을 작은 부분으로 나누는 주된 수단이다.

이런 함수 선언부는 소프트웨어의 연결부 역할을 한다. 따라서 선언부를 정의할 필요가 있다.

 

가장 중요한 함수 이름이다. 이름이 좋으면 구현부를 필요가 없다.

이름은 IDE기능으로 쉽게 변경가능하니 좋은 이름이 있다면 주저하지 않고 바꾸자

 

함수 매개변수는 함수를 사용하는 문맥을 설정한다.

전화번호 포맷팅 함수가 있는데 매개변수로 사람 타입을 받아 전화번호를 포맷팅한다고 하면, 활용도가 적다. 전화번호만 인자로 받는다면 어떤 전화번호라도 활용이 가능하다.

절차

간단한 절차와 마이그레이션 절차가 있다.

간단한 절차를 최대한 사용하되 불가능하다면 마이그레이션 절차를 사용한다.

 

간단한 절차

  1. 매개변수를 제거하려거든 먼저 함수 본문에서 제거 대상 매개변수를 참조하는 곳은 없는지 확인한다.
  2. 메서드 선언을 원하는 형태로 바꾼다.
  3. 기존 메서드 선언을 참조하는 부분을 모두 찾아서 바뀐 형태로 수정한다.
  4. 테스트한다.

 

마이그레이션 절차

  1. 이어지는 추출 단계를 수월하게 만들어야 한다면 함수의 본문을 적절히 리팩터링한다.
  2. 함수 본문을 새로운 함수로 추출한다.
  3. 추출한 함수에 매개변수를 추가해야 한다면 '간단한 절차' 따라 추가한다.
  4. 테스트한다.
  5. 기존 함수를 인라인한다.
  6. 이름을 임시로 붙여뒀다면 함수 선언 바꾸기를 적용해서 원래 이름으로 되돌린다.
  7. 테스트한다.

 

상속 구조 속에 있는 클래스 메서드 변경할 때는 다른 서브 클래스들에도 변경이 반영되어야 한다. 이때 복잡하기에 간접 호출 방식으로 우회하는 방법도 쓴다.

타이핑 처럼 슈퍼클래스와의 연결을 제공하지 않는 언어라면 전달 메서드를 모든 구현 클래스에 추가해야 한다.

 

 

 

예시 : 함수 이름 바꾸기 간단한 절차

function circum(radius){
    return 2 * Math.PI * radius;
}
function circumference(radius){
    return 2 * Math.PI * radius;
}

 

함수 이름 변경 핵심은 함수를 호출하는 부분을 얼마나 쉽게 찾는가에 있다.

정적 타입 언어와 좋은 IDE 조합이라면 쉽게 함수 이름 바꾸기를 처리할 있다.

정적 타입 언어가 아니라면 상당히 귀찮아지고 버그 발생 가능성이 높아진다.

추가로 상속관계에 있는 클래스라면 다른 메서드도 수정해야 된다.

 

리팩터링은 항상 문제를 작게 쪼개서 정복한다.

매개변수 추가/삭제 같은 변경은 반드시 함수 호출 부분을 모두 찾아 수정한 수행한다.

 

간단한 방식은 함수 이름 바꾸기로 코드에 영향이 적을 적합하다.

 

예시: 함수 이름 바꾸기 마이그레이션 절차

function circum(radius){
    return 2 * Math.PI * radius;
}
//마이그레이션 절차
function circum(radius){
    return circumference(radius);
}
function circumference(radius) {
    return 2 * Math.PI * radius;
}
상태에서 정상 동작을 테스트하고,  circum()호출하는 부분을 전부 찾아 circumference() 바꾼다.

 

방식은 API같은 선언부를 고칠 없는 외부 코드 사용 부분을 리팩터링 하기 좋다.

circumference() 만들어 두고, circum() @Deprecated 같은 표시를 해두어 circumference() 호출하도록 유도를 한다. 이후 모든 클라이언트가 변경 했으면 circum() 지운다.

 

 

 

예시: 매개변수 추가하기

예약 우선순위 큐를 지원하라는 요구사항이 들어왔다.

기존 큐와 우선순위 어떤 것으로 할지 결정 매개변수를 추가해야 한다.

 

class Book{
    addReservation(customer){
        this._reservations.push(customer);
    }
}
class Book{
    addReservation(customer){
        this.zz_addReservation(customer);
    }
    //임시이름
    zz_addReservation(customer) {
        this._reservations.push(customer);
    }
}
class Book{
    addReservation(customer){
        this.zz_addReservation(customer, false);
    }
    //매개변수 추가
    zz_addReservation(customer,isPriority) {
        //자바스크립트 한정 assert 테스트
        assert(isPriority === true || isPriority === false);
        this._reservations.push(customer);
    }
}
이제 호출 부분을 전부 zz_addReservation() 변경 zz_addReservation() 함수 이름을 기존 함수 이름으로 바꾼다.
class Book{
    addReservation(customer,isPriority) {
        this._reservations.push(customer);
    }
}

 

 

예시: 매개변수를 속성으로 바꾸기

고객이 뉴잉글랜드 살고있는지 파악하는 함수

/* 현재 문제는 매개변수를 객체로 받고 있다. 재사용성이 제한된다.*/
function inNewEngland(aCustomer){
    return ["MA","CT","ME","VT","NH","RI"].includes(aCustomer.address.state);
}
//호출부
const newEnglanders = someCustomers.filter(c=>inNewEngland(c));
function inNewEngland(aCustomer){
    const stateCode = aCustomer.address.state;
    return ["MA","CT","ME","VT","NH","RI"].includes(stateCode);
}
function inNewEngland(aCustomer){
    const stateCode = aCustomer.address.state;
    return newFunction(stateCode);
}
//임시 함수 추출
function newFunction(stateCode) {
    return ["MA", "CT", "ME", "VT", "NH", "RI"].includes(stateCode);
}
function inNewEngland(aCustomer){
    //변수 인라인하기
    return newFunction(aCustomer.address.state);
}
function newFunction(stateCode) {
    return ["MA", "CT", "ME", "VT", "NH", "RI"].includes(stateCode);
}
//호출부 변경
const newEnglanders = someCustomers.filter(c=>newFunction(c));
//함수 선언 바꾸기로 새함수이름을 기존함수이름으로 변경
function inNewEngland(stateCode) {
    return ["MA", "CT", "ME", "VT", "NH", "RI"].includes(stateCode);
}
//호출부 변경
const newEnglanders = someCustomers.filter(c=>inNewEngland(c));

 

 

리팩터링 | 마틴 파울러 | 한빛미디어- 교보ebook

코드 구조를 체계적으로 개선하여 효율적인 리팩터링 구현하기, 20여 년 만에 다시 돌아온 마틴 파울러의 리팩터링 2판 리팩터링 1판은 1999년 출간되었으며, 한국어판은 2002년 한국에 소개되었다

ebook-product.kyobobook.co.kr

개요

함수 안에서 변수를 사용하는 것보다 원래 표현식과 다를바 없는 경우 인라인한다.

예시

function isOver(anOrder){
    let basePrice = anOrder.basePrice;
    return (basePrice > 1000);
}
function isOver(anOrder){
    return (anOrder.basePrice > 1000);
}
 

리팩터링 | 마틴 파울러 | 한빛미디어- 교보ebook

코드 구조를 체계적으로 개선하여 효율적인 리팩터링 구현하기, 20여 년 만에 다시 돌아온 마틴 파울러의 리팩터링 2판 리팩터링 1판은 1999년 출간되었으며, 한국어판은 2002년 한국에 소개되었다

ebook-product.kyobobook.co.kr

개요

표현식이 너무 복잡해서 이해하기 어려울 지역 변수를 활용하면 표현식을 쪼개 관리하기 쉽게 만들 있다.

 

이렇게 추가된 변수는 디버깅에도 도움된다. 디버거 breakpoint 활용할 있기 때문이다.

 

추출된 변수에 이름을 붙일 변수의 문맥을 고려해야한다. 함수 내에서만 의미있다면, 변수로 추출한다. 함수 외부까지 의미가 있다면, 함수로 추출해야 한다.

예시

//기본형
function price(order){
    return order.quantity * order.itemPrice -
        Math.max(0, order.quantity - 500) * order.itemPrice * 0.05 +
        Math.min(order.quantity * order.itemPrice * 0.1, 100);
}
function price(order){
    const basePrice = order.quantity * order.itemPrice;
    //가격 = 기본 가격 = 수량 할인 + 배송비
    return basePrice -
        Math.max(0, order.quantity - 500) * order.itemPrice * 0.05 +
        Math.min(order.quantity * order.itemPrice * 0.1, 100);
}
function price(order){
    const basePrice = order.quantity * order.itemPrice;
    //똑같은 표현식 부분 치환
    return basePrice -
        Math.max(0, order.quantity - 500) * order.itemPrice * 0.05 +
        Math.min(basePrice * 0.1, 100);
}
function price(order){
    const basePrice = order.quantity * order.itemPrice;
    const quantityDiscount = Math.max(0, order.quantity - 500) * order.itemPrice * 0.05;
    const shipping = Math.min(basePrice * 0.1, 100);
    return basePrice - quantityDiscount + shipping;
}

사용한 자동 리팩터링

예시: 클래스 안에서

추출하는 대상이 price()메서드 범위를 넘어 Order 클래스 전체에 영향을 미친다. 이럴 변수가 아닌 메서드로 추출한다.

//기본형
class Order {
    constructor(aRecord){
        this._data = aRecord;
    }
    get quantity() {return this._data.quantity;}
    get itemPrice() {return this._data.itemPrice;}
    get price(){
        return this.quantity * this.itemPrice -
        Math.max(0, this.quantity - 500) * this.itemPrice * 0.05 +
        Math.min(this.quantity * this.itemPrice * 0.1, 100);
    }
}
//리팩터링
class Order {
    constructor(aRecord){
        this._data = aRecord;
    }
    get quantity() {return this._data.quantity;}
    get itemPrice() {return this._data.itemPrice;}
    get price(){
        return this.basePrice - this.quantityDiscount + this.shipping;
    }
    get shipping() {
        return Math.min(this.basePrice * 0.1, 100);
    }
    get quantityDiscount() {
        return Math.max(0, this.quantity - 500) * this.itemPrice * 0.05;
    }
    get basePrice() {
        return this.quantity * this.itemPrice;
    }
}

이렇게 메서드로 추출해두면 객체를 데이터를 외부에서 쉽게 활용할 있다.

 

사용한 자동 리팩터링

주의 것은 메서드를 추출해주지 게터 메서드로 만들어 주지 않아 주의해야 한다.

 

 

리팩터링 | 마틴 파울러 | 한빛미디어- 교보ebook

코드 구조를 체계적으로 개선하여 효율적인 리팩터링 구현하기, 20여 년 만에 다시 돌아온 마틴 파울러의 리팩터링 2판 리팩터링 1판은 1999년 출간되었으며, 한국어판은 2002년 한국에 소개되었다

ebook-product.kyobobook.co.kr

개요

리팩터링에서 기조는 짧막한 함수를 명료한 이름을 붙여 사용하는 것이다. 때로는 그보다 함수 본문이 이름만큼 명확한 경우도 있다. 이런 경우 간접 호출은 거슬리므로 제거한다.

 

이유로 함수 추출하기로 추출한 경우도 (잘못 추출한 것이므로) 다시 함수 인라인하기로 합칠 수도 있다.

 

간접 호출을 너무 과하게 쓰는 코드도 인라인 대상이다.

 

인라인하기 너무 복잡한 함수는 인라인하기 적용 대상이 아니다.

예시

가장 간단한 예시

function rating(aDriver){
    return moreThanFiveLateDeliveries(aDriver) ? 2 : 1;
}
function moreThanFiveLateDeliveries(aDriver){
    return aDriver.numberOfLateDeliveres > 5;
}
function rating(aDriver){
    return (aDriver.numberOfLateDeliveres > 5) ? 2 : 1;
}

 

매개변수가 조금 다른 경우

//기본형
function reportLines(aCustomer){
    const lines = [];
    gatherCustomerData(lines, aCustomer);
    return lines;
}
function gatherCustomerData(out, aCustomer){
    out.push(["name",aCustomer.name]);
    out.push(["location",aCustomer.location]);
}
//매개변수가 다른 경우 리팩터링
function reportLines(aCustomer){
    const lines = [];
    out.push(["name",aCustomer.name]);
    out.push(["location",aCustomer.location]);
    return lines;
}

 

함수 인라인하기가 너무 복잡하다면 반드시 단계를 나누어 처리하자. 그래야 과정 중간에 테스트 실패 지점에서 버그 구간을 좁게 특정할 있다.

 

 

리팩터링 | 마틴 파울러 | 한빛미디어- 교보ebook

코드 구조를 체계적으로 개선하여 효율적인 리팩터링 구현하기, 20여 년 만에 다시 돌아온 마틴 파울러의 리팩터링 2판 리팩터링 1판은 1999년 출간되었으며, 한국어판은 2002년 한국에 소개되었다

ebook-product.kyobobook.co.kr

개요

목적과 구현을 분리하는 방식을 기준으로 삼는다.

코드를 보고 무슨 일을 하는지 파악하는데 함참 걸린다면 부분을 함수로 추출한 '무슨 ' 대한 명확한 이름을 붙인다.

 

장황한 주석이 붙은 코드 뭉치는 함수 추출 대상이 가능성이 높다.

 

일반적으로 함수가 6 이하가 되도록 잘게 유지한다. 이로 인한 성능 저하는 거의 없다.

함수가 짧으면 오히려 컴파일러가 최적화할 기회를 얻는다.

최적화에 대한 고민이 생길 최적화 지침을 상기하자

번째, 하지 마라

번째, 전문가일 경우라도 아직 하지 마라

예시 : 유효범위를 벗어나는 변수가 없을

 

function printOwing(invoice){
    let outstanding = 0;
    console.log("******************");
    console.log("**** 고객 채무 ****")
    console.log("******************");
    //미해결 채무
    for(const o of invoice.orders){
        outstanding += o.amount;
    }
    //마감일
    const today = Clock.today;
    invoice.dueDate = new Date(today.getFullYear(), today.getMonth(), today.getDate()+30);
    console.log(`고객명 : ${invoice.customer}`);
    console.log(`채무액 : ${outstanding}`);
    console.log(`마감일 : ${invoice.dueDate.toLocaleDateString()}`);
}
//유효범위를 벗어나는 변수가 없을 때
function printOwing(invoice){
    let outstanding = 0;
    printBanner();
    //미해결 채무
    for(const o of invoice.orders){
        outstanding += o.amount;
    }
    //마감일
    const today = Clock.today;
    invoice.dueDate = new Date(today.getFullYear(), today.getMonth(), today.getDate()+30);
    printDetails();
    function printDetails() {
        console.log(`고객명 : ${invoice.customer}`);
        console.log(`채무액 : ${outstanding}`);
        console.log(`마감일 : ${invoice.dueDate.toLocaleDateString()}`);
    }
    function printBanner() {
        console.log("******************");
        console.log("**** 고객 채무 ****");
        console.log("******************");
    }
}

리팩터링 란에 존재

 

 

 

예시: 지역 변수를 사용할

지역변수를 사용하지만 다른 값을 다시 대입하지 않을

//지역 변수를 사용할 때
function printOwing(invoice){
    let outstanding = 0;
    printBanner();
    //미해결 채무
    for(const o of invoice.orders){
        outstanding += o.amount;
    }
    //마감일
    const today = Clock.today;
    invoice.dueDate = new Date(today.getFullYear(), today.getMonth(), today.getDate()+30);
    //세부 사항을 출력한다
    console.log(`고객명 : ${invoice.customer}`);
    console.log(`채무액 : ${outstanding}`);
    console.log(`마감일 : ${invoice.dueDate.toLocaleDateString()}`);
    function printBanner() {
        console.log("******************");
        console.log("**** 고객 채무 ****");
        console.log("******************");
    }
}
 //중첩 함수가 지원되지 않는 언어는 이렇게 넣어줘야 할 것
    printDetails(invoice, outstanding);
    function printDetails(invoice, outstanding) {
        console.log(`고객명 : ${invoice.customer}`);
        console.log(`채무액 : ${outstanding}`);
        console.log(`마감일 : ${invoice.dueDate.toLocaleDateString()}`);
    }
recordDueDate(invoice);
    //중첩 함수가 지원되지 않는 언어는 이렇게 넣어줘야 할 것
    printDetails(invoice, outstanding);
    function recordDueDate(invoice) {
        const today = Clock.today;
        invoice.dueDate = new Date(today.getFullYear(), today.getMonth(), today.getDate() + 30);
    }

 

 

예시: 지역 변수의 값을 변경할

이럴 경우 매개변수가 들어가는 지역 변수에 대한 임시 변수를 새로 하나 만들어 변수를 사용한다.

    let outstanding = 0;
    printBanner();
    //미해결 채무
    for(const o of invoice.orders){
        outstanding += o.amount;
    }
    printBanner();
    //문장 슬라이드로 사용되는 곳 근처로 옮김
    let outstanding = 0;
    for(const o of invoice.orders){
        outstanding += o.amount;
    }
    printBanner();
    let outstanding = 0;
    for(const o of invoice.orders){
        outstanding += o.amount;
    }
    //추출할 부분을 새로운 함수로 복사
    function calculateOutstanding(invoice){
        let outstanding = 0;
        for(const o of invoice.orders){
            outstanding += o.amount;
        }
        return outstanding;
    }
    printBanner();
    //추출한 함수가 반환한 값을 원래 변수에 저장한다.
    let outstanding = calculateOutstanding(invoice);
    function calculateOutstanding(invoice){
        let outstanding = 0;
        for(const o of invoice.orders){
            outstanding += o.amount;
        }
        return outstanding;
    }
    //마지막으로 반환 값의 이름을 코딩 스타일에 맞게 변경
    const outstanding = calculateOutstanding(invoice);
    function calculateOutstanding(invoice){
        let result = 0;
        for(const o of invoice.orders){
            result += o.amount;
        }
        return result;
    }

 

 

function printOwing(invoice){
    printBanner();
    const outstanding = calculateOutstanding(invoice);
   
    recordDueDate(invoice);
    printDetails(invoice, outstanding);
    function calculateOutstanding(invoice){
        let result = 0;
        for(const o of invoice.orders){
            result += o.amount;
        }
        return result;
    }
    function recordDueDate(invoice) {
        const today = Clock.today;
        invoice.dueDate = new Date(today.getFullYear(), today.getMonth(), today.getDate() + 30);
    }
    function printDetails(invoice, outstanding) {
        console.log(`고객명 : ${invoice.customer}`);
        console.log(`채무액 : ${outstanding}`);
        console.log(`마감일 : ${invoice.dueDate.toLocaleDateString()}`);
    }
    function printBanner() {
        console.log("******************");
        console.log("**** 고객 채무 ****");
        console.log("******************");
    }
}

 

 

 

값을 반환할 변수가 여러 개라면, 추출할 코드를 재구성하는 식으로 처리한다.

하나의 함수가 하나의 값을 반환하는 방식으로 여러 함수로 재구성한다.

 

 

 

리팩터링 | 마틴 파울러 | 한빛미디어- 교보ebook

코드 구조를 체계적으로 개선하여 효율적인 리팩터링 구현하기, 20여 년 만에 다시 돌아온 마틴 파울러의 리팩터링 2판 리팩터링 1판은 1999년 출간되었으며, 한국어판은 2002년 한국에 소개되었다

ebook-product.kyobobook.co.kr

 최대한 부가 설치없이 브라우저에서 확인하려 했으나, 부득이하게 개발환경을 구축(4.3 부분)

코드 링크, 변화과정은 history로 commit 확인

 

GitHub - rkwhr0010/refactoring: 리팩터링책 내용

리팩터링책 내용. Contribute to rkwhr0010/refactoring development by creating an account on GitHub.

github.com

 


리팩터링을 제대로 하려면 불파기하게 저지르는 실수를 잡아주는 견고한 테스트 스위트가 뒷받침돼야 한다.

 

테스트 작성은 개발 효율을 높여준다. , 그냥 개발하는 것보다 테스트 코드 작성 시간을 따로 할애하고 개발하는 것이 생산성이 높다는 말이다.

 

4.1 자가 테스트 코드의 가치

개발자는 실제 코드 작성 시간의 비중은 크지 않다. 외에 분석, 설계나 특히 디버깅에 시간을 쓴다. 버그 자체도 발견만 하면 수정은 금방이다. 진짜 시간이 많이드는 것은 버그를 찾는 일이다.

 

코드 변경 마다 자가 테스트 코드를 돌린다면, 버그를 찾기 쉬워진다. 직전까지 테스트가 성공했으면 이후 작성한 코드에서 버그가 발생했음을 있기 때문이다. 이렇게 쉽게 버그를 찾을 있게 된다.

 

테스트를 자주 수행하는 습관도 버그를 찾는 좋은 습관이다.

 

테스트를 작성하려면 부가적인 코드를 상당량 작성해야 한다. 그래서 테스트가 실제로 프르그래밍 속도를 높여주는 경험을 직접 해보지 않고서는 자가 테스트의 진가를 납득하기 어렵다.

 

테스트를 작성하기 가장 좋은 시점은 프로그래밍을 시작하기 전이다. 순서가 바뀐 같지만 아니다.

테스트를 먼저 작성하다 보면 원하는 기능을 추가하기 위해 무엇이 필요한지 고민하게 된다. 그리고 구현보다 인터페이스에 집중하게 된다는 장점도 있다.

 

이렇게 테스트부터 작성하는 습관을 바탕으로 테스트 주도 개발(Test-Driven Development:TDD) 나왔다.

TDD에선 테스트를 작성하고, 테스트를 통과하도록 코드를 작성한다. 통과하면 최대한 깔끔하게 리팩터링하는 과정을 짧은 주기로 반복한다.

 

테스트가 없는 코드를 리팩터링하게 테스트 코드부터 작성한다.

 

4.2 테스트할 샘플 코드

//샘플 데이터

export function sampleProvinceData(){

    return {

        name : "Asia" ,

        producers : [

            {name:"Byzantium",  cost:10, production:9},

            {name:"Attalia",    cost:12, production:10},

            {name:"Sinope",     cost:10, production:6},

        ],

        demand : 30,

        price : 20

    };

}

//지역 전체를 표현하는 클래스

export class Province {

    //JSON 데이터로부터 지역 정보를 읽어온다.

    constructor(doc){

        this._name = doc.name;

        this._producers = [];

        this._totalProduction = 0;

        this._demand = doc.demand;

        this._price = doc.price;

        doc.producers.forEach(d => this.addProducer(new Producer(this, d)));

    }

    addProducer(arg){

        this._producers.push(arg);

        this._totalProduction += arg.production;

    }

    get name() {return this._name;}

    get producers(){return this._producers.slice();}

    get totalProduction(){return this._totalProduction;}

    set totalProduction(arg){this._totalProduction = arg;}

    get demand(){return this._demand;}

    set demand(arg){this._demand = parseInt(arg);}

    get price(){return this._price;}

    set price(arg){this._price = parseInt(arg);}

   

    //생산 부족분

    get shortfall(){

        return this._demand - this.totalProduction;

    }

    //수익 계산

    get profit(){

        return this.demandValue - this.demandCost;

    }

    get demandValue(){

        return this.satisfiedDemand * this.price;

    }

    get satisfiedDemand(){

        return Math.min(this._demand, this.totalProduction);

    }

    get demandCost(){

        let remainingDemand = this.demand;

        let result = 0 ;

        this.producers

            .sort((a,b)=>a.cost-b.cost)

            .forEach(p=>{

                const contribution = Math.min(remainingDemand, p.production);

                remainingDemand = contribution;

                result += contribution * p.cost;

            });

        return result;

    }

}

export class Producer {

    constructor(aProvince, data){

        this._province = aProvince;

        this._cost = data.cost;

        this._name = data.name;

        this._production = data.production || 0;

    }

    get name(){return this._name;}

    get cost(){return this._cost;}

    set cost(arg){this._cost = arg;}

    get production(){return this._production;}

    set production(amountStr){

        const amount = parseInt(amountStr);

        const newProduction = Number.isNaN(amount) ? 0 : amount;

        this._province.totalProduction += newProduction - this._production;

        this._production = newProduction;

    }

}

 

4.3 번째 테스트

테스트를 위해선 테스트 프레임워크가 필요하다.

책에서는 자세히 나와 있지 않아, 인터넷에서 찾아보면서 했다.

 

VSCode 설치

https://code.visualstudio.com/download

NODE.JS 설치

https://nodejs.org/ko
mocha 테스트 프레임 워크 설치

https://mochajs.org/#installation

실행 참고

https://mochajs.org/#nodejs-native-esm-support

검증 chai 라이브러리

https://www.chaijs.com/

 

 

import {Province, sampleProvinceData} from "../sample.mjs";

import { describe } from "node:test";//Mocha 테스트 프레임워크

import assert from 'assert';

describe('province', function(){

    it('shortfall',function(){

        const asia = new Province(sampleProvinceData()); //픽스처 설정

        assert.equal(

            asia.shortfall

            ,5);//검증

    })

})

"npm test" 또는 "npm run test" 실행

 

    //생산 부족분

    get shortfall(){

        return this._demand - this.totalProduction * 2; // 실패 테스트

    }

 

성공 메시지는 너무 간결해서, 일부로 실패를 유도해 정상적으로 동작하는 확인하면 좋다.

 

 

원상 복구한다.

 

 

    //생산 부족분

    get shortfall(){

        return this._demand - this.totalProduction;

    }

 

 

 

작성 중인 코드는 최소한 간격으로 테스트하고, 하루에 번은 전체 테스트를 돌려보는게 좋다.

 

expect사용

 

describe('province', function(){

    it('shortfall',function(){

        const asia = new Province(sampleProvinceData()); //픽스처 설정

        assert.equal(asia.shortfall,5);//검증

    })

    it('shortfall2',function(){

        const asia = new Province(sampleProvinceData()); //픽스처 설정

        expect(asia.shortfall).equal(5);//검증

    })

})

 

 

테스트 코드가 하나라도 실패한다면, 리팩터링을 하면 안된다. 실패 모든 테스트를 통과한 가장 최근 체크포인트로 돌아가서 다시 작업해야 한다. (잘게 쪼개서 자주 커밋하는 것이 중요하다)

 

테스트 모든 테스트가 전부 통과했는지 빠르게 파악할 있는 방식을 찾아 사용하자

 

 

4.4 테스트 추가하기

클래스가 하는 일을 모두 살피고, 오류가 생길 있는 조건을 테스트하는 식으로 진행한다.

테스트는 위엄 요소 중심으로 작성해야 한다. 테스트 목적은 버그를 찾기 위함이다.

로직이 안들어간 게터/세터 같은 곳엔 테스트 불필요

 

가장 걱정되는 영역은 집중적으로 테스트한다. 테스트에 들어갈 노력을 집중시킨다.

 

 

수익 계산 테스트

 

describe('province', function(){

    it('shortfall',function(){

        const asia = new Province(sampleProvinceData());

        expect(asia.shortfall).equal(5);

    })

    //총 수익 계산

    it('profit', function(){

        const asia = new Province(sampleProvinceData());

        expect(asia.profit).equal(230);

    })

})

 

 

겹치는 코드를 줄여보자

 

좋은

describe('province', function(){

    const asia = new Province(sampleProvinceData()); //테스트 간 상호작용을 하는 안좋은 예시

    it('shortfall',function(){

        expect(asia.shortfall).equal(5);

    })

    //총 수익 계산

    it('profit', function(){

        expect(asia.profit).equal(230);

    })

})

 

테스트 관련 버그 가장 지저분한 유형이다. 테스트끼리 상호작용하게 하는 공유 방식

이전 테스트 결과가 이후 테스트 결과에 영향을 미친다.

 

좋은

describe('province', function(){

    //좋은 예시

    let asia;

    beforeEach(function(){

        asia = new Province(sampleProvinceData());

    })

    it('shortfall',function(){

        expect(asia.shortfall).equal(5);

    })

    it('profit', function(){

        expect(asia.profit).equal(230);

    })

})

 

방식은 테스트 마다 beforeEach구문이 실행된다. 따라서 새로운 Province객체로 독립적으로 테스트가 진행된다.

 

이렇게 한다고 지나치게 테스트가 느려지진 않는다. 만약 너무 느려진다면 공유방식을 쓰되 불변값이 되도록 확실한 검증을 해야한다. 테스트 단계에서 테스트 결과가 버그가 생겨버리면 테스트 자체를 불신하게 된다.

 

 

4.5 픽스처 수정하기

실전에서는 사용자가 값을 변경하면서 픽스처의 내용도 수정되는 경우가 흔하다.

수정 대부분은 세터에서 이뤄진다. 단순 설정 세터가 아닌 로직이 들어간 세터는 테스트해볼 필요가 있다.

 

    it('change production', function(){

        asia.producers[0].production = 20;

        expect(asia.shortfall).equal(-6);

        expect(asia.profit).equal(292);

    })

 

테스트는 가지 속성을 검증한다. 원래라면 분리해야하는 것이 맞다. 서로 밀접하게 연관되어 있어 하나의 테스트로 합친 것이다.

 

    it('change production', function(){

        asia.producers[0].production = 20;

        expect(asia.shortfall).equal(-6);

    })

    it('change production2', function(){

        asia.producers[0].production = 20;

        expect(asia.profit).equal(292);

    })

beforeEach 블록에서 "설정"하고, 테스트를 "수행"하고, 결과를 "검증"하는 패턴은 흔히 보는 패턴이다. 패턴을 "설정-실행-검증" 또는 "조건-발생-결과" 유사하게 불리는 이름이 많다.

 

beforeEach 사용하지 않으면, 단계에서 추가로 "해체" 단계가 존재한다. 픽스처를 제거하여 테스트들이 서로 영향을 주기 못하게 막는 단계다. 하지만 보통 beforeEach 사용하면 프레임 워크에서 알아서 해체해주기 때문에 무시하는 경우가 많다.

만약 생상 비용이 높아 공유해야 한다면, 단계가 필요할 것이다.

 

4.6 경계 조건 검사하기

산정 내의 범위가 아닌 경계 지점에서 발생하는 일을 미리 확인하는 것도 중요하다

 

describe('no producers', function(){

    let noProducers;

    this.beforeEach(function(){

        const data = {

            name : "No producers",

            producers : [],//비어있다.

            demand: 30,

            price: 20

        };

        noProducers = new Province(data);

    });

    it('shortfall', function(){

        expect(noProducers.shortfall).equal(30);

    });

    it('profit', function(){

        expect(noProducers.profit).equal(0);

    });

})

 

    it('zero demand', function(){

        asia.demand = 0; // 수요가없다.

        expect(asia.shortfall).equal(-25);

        expect(asia.profit).equal(0);

    })

    it('negative demand', function(){

        asia.demand = -1; // 수요가 마이너스

        expect(asia.shortfall).equal(-26);

        expect(asia.profit).equal(-10);

    })

 

수요가 음수일 수익이 음수가 나오는 것은 고객이 프로그램을 사용할 납득 못할 것이다. 음수를 세터 설정할 예외처리나 0으로 처리해야할 같다.

처럼 경계를 테스트하면 특이 상황을 처리할 단서를 얻게 된다.

 

물제가 생길 가능성이 있는 경계 조건을 생각해보고 부분을 집중적으로 테스트하자.

 

 

의미상 숫자를 받아야하지만 UI로부터 문자열을 받는다. 따라서 "" 같은 공백이 있다.

 

    it('empty string demand', function(){

        asia.demand = "";// 수요가 비어있다.

        expect(asia.shortfall).NaN;

        expect(asia.profit).NaN;

    })

 

의도적으로 코드를 망가트리는 방법을 모색한다.

 

describe('string for producers', function(){

    it('', function(){

        const data = {

            name:"String producers",

            producers: "",

            demand:30,

            price:20

        };

        const prov = new Province(data);

        expect(prov.shortfall).equal(0);

    })

})

mocha 테스트 프레임워크에선 경우 실패로 처리한다.

다른 테스트 프레임워크에선 에러와 실패를 구분하는 경우도 많다.

 

실패는 허용된 타입 안에서 예상 범위를 벗어났다는 뜻이다.

처리는 했지만, 허용 범위를 벗어남

에러는 예상치 못한 상황이 닥친 것이다.

처리조차 못함

 

이런 상황에선 어떻게 처리하는 것이 좋을까? 경우엔 외부에서 JSON입력이 들어온 것이기에 에러가 나지 않도록 처리하는 것이 좋다.

같은 코드 베이스의 모듈 에러 처리는 생각해봐야한다. 중복 유효성 검사로 오히려 문제가 있다.

 

어차피 모든 버그를 잡아낼 수는 없다고 생각하여 테스트를 작성하지 않는다면 대다수의 버그를 잡을 있는 기회를 날리는 셈이다.

 

테스트에도 수확 체감 법칙이 적용된다. 따라서 테스트는 위험한 부분이나 중요한 부분에 집중하는 좋다.

 

리팩터링 전에 테스트 스위트를 작성하지만, 리팩터링하는 동안에도 계속해서 테스트를 추가하자.

 

자가 테스트 코드는 리팩터링 개발자에게 코드가 정상동작할 것이라는 안도감을 준다.

 

4.7 끝나지 않은 여정

테스트로 리팩터링 못지 않게 중요하다. 리팩터링에 필요한 토대일 뿐만 아니라, 자체로도 프로그래밍에 중요한 역할을 한다.

 

장에서 보여준 테스트는 단위 테스트다.

단위 테스트는 코드의 작은 영역만 대상으로 빠르게 실행되도록 셀계된 테스트를 의미한다.

 

번에 완벽한 테스트를 만들 수는 없으므로 지속적으로 테스트 스위트도 보강을 해야 한다.

 

특히, 버그를 발견하는 즉시 발견한 버그를 명확히 잡아내는 테스트부터 작성하는 습관을 들여야 한다.

 

새로운 기능을 추가하기 전에 테스트부터 작성한다.

 

테스트를 충분히 헀는지 확인하려는 지표로 테스트 커버리지를 많이 언급한다. 하지만 테스트 커버리지 분석은 말그대로 코드에서 테스트하지 않은 영역을 찾는 것이지, 테스트 스위트 품질과는 크게 상관 없다.

 

테스트 스위트가 충분한지는 정량적으로 측정할 없으므로, 주관적인 영역이다. 리팩터링 과정에서 테스트 스위트가 전부 초록불일 , 리팩터링 과정에서 버그가 없다고 확신이 들면 좋은 테스트 스위트다.

 

 

 

+ Recent posts