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

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

ebook-product.kyobobook.co.kr

개요

레코드는 프로그래밍에서 데이터를 표현하는 구조이다.

연관된 데이터를 묶어 하나의 의미있는 단위를 만든다.

 

예시: 간단한 레코드 캡슐화하기

//이 상수는 레코드 구조로 사용되는 객체
const organization = {name:"애크미 구스베리", country:"GB"};
let result;
result += `<h1>${organization.name}</h1>` //일기
organization.name = '새로운 이름' // 쓰기
//상수를 캡슐화하기, 이 게터는 임시 사용
function getRawDataOfOrganization() {
    return organization
}
let result;
result += `<h1>${getRawDataOfOrganization().name}</h1>` //일기
getRawDataOfOrganization().name = '새로운 이름' // 쓰기
const organization = new Organization({ name: "애크미 구스베리", country: "GB" });
function getRawDataOfOrganization() {return organization._data;}
function getOrganization(){return organization;}
let result;
result += `<h1>${getRawDataOfOrganization().name}</h1>` //일기
getRawDataOfOrganization().name = '새로운 이름' // 쓰기
//레코드를 캡슐화하는 목적은 변수를 통제하기 위함이다.
//그러기 위해선 레코드를 클래스로 바꿔야한다.
class Organization{
    constructor(data){
        this._data = data;
    }
}


const organization = new Organization({ name: "애크미 구스베리", country: "GB" });
// function getRawDataOfOrganization() {return organization._data;} 정상동작하면 제거
function getOrganization(){return organization;}
let result;
result += `<h1>${getOrganization().name}</h1>` //일기
getOrganization().name = '새로운 이름' // 쓰기
//게터, 세터 생성 후 사용하도록 코드 수정
class Organization{
    constructor(data){
        this._data = data;
    }
    set name(aString){this._data.name = aString;}
    get name(){return this._data.name;}
}
//현재 레코드를 그대로 받아 사용하고 있다.
//레코드는 참조변수로 다루기에 캡슐화가 깨질 우려가 있다. 제거한다.
//만일 _data를 그대로 쓸 것이라면 복제해서 써야한다.
class Organization{
    constructor(data){
        this._name = data.name;
        this._country = data.country;
    }
    set name(aString){this._name = aString;}
    get name(){return this._name;}
    set _country(aString){this._country = aString;}
    get _country(){return this._country;}
}

 

예시: 중첩된 레코드 캡슐화하기

JSON 같은 여러 겹으로 중첩된 레코드에 대한 캡슐화



//중첩구조가 심할수록 데이터 일고/쓰기가 힘들어진다.
customerData[customerID].usages[year][month] = amount;//쓰기
function compareUsage(customerID, laterYear, month){//읽기
    const later = customerData[customerID].usages[laterYear][month];
    const earlier = customerData[customerID].usages[laterYear-1][month];
    return {laterAmount: later, change:later-earlier};
}


getRawDataOfCustomers()[customerID].usages[year][month] = amount;//쓰기
function compareUsage(customerID, laterYear, month){//읽기
    const later = getRawDataOfCustomers()[customerID].usages[laterYear][month];
    const earlier = getRawDataOfCustomers()[customerID].usages[laterYear-1][month];
    return {laterAmount: later, change:later-earlier};
}
//변수 캡슐화부터 시작
function getRawDataOfCustomers(){return customerData;}
function setRawDataOfCustomers(arg){customerData = arg;}


function getCustomerData(){return customerData;}
function getRawDataOfCustomers(){return customerData._data;}//기존 호환
function setRawDataOfCustomers(arg){customerData = new CustomerData(arg);}
//데이터 구조를 표햔하는 클래스 정의 후 이를 반환하는 함수 만듦
class CustomerData{
    constructor(data){
        this._data = data;
    }
}


setUsage(customerID, year, month, amount);//쓰기
//데이터 구조 안으로 들어가는 세터 함수화
function setUsage(customerID, year, month, amount) {
    getRawDataOfCustomers()[customerID].usages[year][month] = amount;
}


getCustomerData().setUsage(customerID, year, month, amount);//쓰기


//클래스로 함수 옮기기
class CustomerData{
    constructor(data){
        this._data = data;
    }
    setUsage(customerID, year, month, amount) {
        getRawDataOfCustomers()[customerID].usages[year][month] = amount;
    }
}


class CustomerData{
    constructor(data){
        this._data = data;
    }
    setUsage(customerID, year, month, amount) {
        getRawDataOfCustomers()[customerID].usages[year][month] = amount;
    }
    //객체의 모든 필드가 불변이 아니라면, 캡슐화가 꺠질 수 있다.
    //그렇다고 customerData 를 사용하는 모든 코드를 확인 했는지 알 수가 없다.
    //복사본을 리턴해서 처리한다.
    get rawData(){
        return _.cloneDeep(this._data);
    }
}


프록시 구현으로 객체 수정 예외를 던지는 방법도 있다.


function compareUsage(customerID, laterYear, month){//읽기
    const later = getCustomerData().usage(customerID,laterYear,month);
    const earlier = getCustomerData().usage(customerID,laterYear-1,month);
    return {laterAmount: later, change:later-earlier};
}


class CustomerData{
    constructor(data){
        this._data = data;
    }
    setUsage(customerID, year, month, amount) {
        getRawDataOfCustomers()[customerID].usages[year][month] = amount;
    }
    get rawData(){
        return _.cloneDeep(this._data);
    }
    //읽기 처리
    usage(customerID, year, month){
        return this._data[customerID].usages[year][month];
    }
}


//사용자가 데이터를 요청해 그 값을 수정하면 캡슐화가 깨질 수 있다.
//복사본으로 처리하는 게 가장 간단한 방식
function compareUsage(customerID, laterYear, month){
    const later = getCustomerData().rawData[customerID].usages[year][month];
    const earlier = getCustomerData().rawData[customerID].usages[year][month];
    return {laterAmount: later, change:later-earlier};
}

요청 데이터를 리턴할 복사본으로 작업하면 간단하나 구조가 거대하면 성능이 감당 안될 수도 있다. 이럴 프록시나 객체를 동결해 값을 비교해 틀어지면 예외를 던지도록 구현하는 방법도 있다.

 

다른 방법으로 레코드 캡슐화를 재귀적으로 하는 방식이 있다. 가장 손이 많이간다.

+ Recent posts