개요

함수는 여러 동작을 하나로 묶어준다.

함수 이름은 코드 동작 방식보다 목적을 말해주기에 함수를 활용하면 코드 이해가 쉬워진다.

 

함수는 중복 제거에도 효과적이다.

 

라이브러리가 제공하는 함수로 대체할 있다면 좋다. 함수 본문을 작성할 필요조차 없기 때문이다.

따라서 라이브러리가 제공하는 API 파악할 수록 이팩터링 활용 빈도가 높아진다.

 

 



let appliesToMass = false;
for(const s of states){
    if(s === "MA") appliesToMass = true;
}


let appliesToMass = states.includes("MA");

 

개요

함수는 추상화의 기본 빌딩 블록

추상화의 경계를 항상 올바르게 긋기는 쉽지 않다.

코드 베이스의 기능 범위가 달라지면 추상화의 경계도 움직인다.

초기에 응집도 높고 가지 일만 수행하던 함수가 이상 다른 일을 수행하게 바뀔 있다는

 

예시

function renderPerson(outStream, person){
    outStream.write(`<p>${person.name}</p>\n`);
    renderPhoto(outStream, person.photo);
    emitPhotoData(outStream, person.photo);
}
function listRecentPhotos(outStream, photos){
    photos
        .filter(p=>p.date > recentDateCutoff())
        .forEach(p=>{
            outStream.write("<div>\n");
            emitPhotoData(outStream, p)
            outStream.write("</div>\n");
        });
}
function emitPhotoData(outStream, photo){
    outStream.write(`<p>제목: ${photo.title}</p>\n`);
    outStream.write(`<p>날짜: ${photo.date.toDateString()}</p>\n`);
    outStream.write(`<p>위치: ${photo.location}</p>\n`);
}
위치 정보만 다르게 해야 한다.


function emitPhotoData(outStream, photo){
    zztmp(outStream, photo);
    outStream.write(`<p>위치: ${photo.location}</p>\n`);
}
function zztmp(outStream, photo) {//이동하지 않을 코드
    outStream.write(`<p>제목: ${photo.title}</p>\n`);
    outStream.write(`<p>날짜: ${photo.date.toDateString()}</p>\n`);
}


function renderPerson(outStream, person){
    outStream.write(`<p>${person.name}</p>\n`);
    renderPhoto(outStream, person.photo);
    zztmp(outStream, person.photo);
    outStream.write(`<p>위치: ${photo.location}</p>\n`);
}


function listRecentPhotos(outStream, photos){
    photos
        .filter(p=>p.date > recentDateCutoff())
        .forEach(p=>{
            outStream.write("<div>\n");
            zztmp(outStream, p)
            outStream.write(`<p>날짜: ${photo.date.toDateString()}</p>\n`);
            outStream.write("</div>\n");
        });
}
function renderPerson(outStream, person){
    outStream.write(`<p>${person.name}</p>\n`);
    renderPhoto(outStream, person.photo);
    emitPhotoData(outStream, person.photo);
    outStream.write(`<p>위치: ${photo.location}</p>\n`);
}
//기존 emitPhotoData제거 후 새 함수 이름 emitPhotoData로 변경
function listRecentPhotos(outStream, photos){
    photos
        .filter(p=>p.date > recentDateCutoff())
        .forEach(p=>{
            outStream.write("<div>\n");
            emitPhotoData(outStream, p)
            outStream.write(`<p>날짜: ${photo.date.toDateString()}</p>\n`);
            outStream.write("</div>\n");
        });
}
function emitPhotoData(outStream, photo) {
    outStream.write(`<p>제목: ${photo.title}</p>\n`);
    outStream.write(`<p>날짜: ${photo.date.toDateString()}</p>\n`);
}

 

 

개요

중복 제거는 건강한 코드를 위해 필요하다.

중복되는 코드를 발견하면 반복 부분을 함수로 합치는 방법을 강구한다.

합쳐놓으면 변경이 발생 한곳만 수정하면 된다.

 

코드를 여러 변형으로 나눠야 하는 순간이 오면 문장을 호출한 곳으로 옮기기를 적용하면 된다.

 

예시

사진 관련 데이트럴 HTML 내보내는 코드



function renderPerson(outStream, person){
    const result = [];
    result.push(`<p>${person.name}</p>`);
    result.push(renderPhoto(person.photo));
    result.push(`<p>제목: ${person.photo.title}</p>`);
    result.push(emitPhotoData(person.photo));
    return result.join("\n");
}
function photoDiv(p){
    return [
        "<div>",
        `<p>제목: ${p.title}</p>`,
        emitPhotoData(p),
        "</div>",
    ].join("\n");
}
function emitPhotoData(aPhoto){
    const result = [];
    result.push(`<p>위치: ${aPhoto.location}</p>`)
    result.push(`<p>날짜: ${aPhoto.date.toDateString()}</p>`)
    return result.join("\n");
}


function photoDiv(p){
    return [
        "<div>",
        zznew(p),
        "</div>",
    ].join("\n");
}
//중복 제거를 위해 함수 추출
function zznew(p){
    return [
        `<p>제목: ${p.title}</p>`,
        emitPhotoData(p) ,
    ].join("\n");
}


function renderPerson(outStream, person){
    const result = [];
    result.push(`<p>${person.name}</p>`);
    result.push(renderPhoto(person.photo));
    //추가 중복 제거
    result.push(zznew(person.photo));
    return result.join("\n");
}


function renderPerson(outStream, person){
    const result = [];
    result.push(`<p>${person.name}</p>`);
    result.push(renderPhoto(person.photo));
    //추가 중복 제거
    result.push(zznew(person.photo));
    return result.join("\n");
}
function photoDiv(p){
    return [
        "<div>",
        zznew(p),
        "</div>",
    ].join("\n");
}
//emitPhotoData()인라인하기
function zznew(p){
    return [
        `<p>제목: ${p.title}</p>`,
        `<p>위치: ${p.location}</p>` ,
        `<p>날짜: ${p.date.toDateString()}</p>`,
    ].join("\n");
}


function renderPerson(outStream, person){
    const result = [];
    result.push(`<p>${person.name}</p>`);
    result.push(renderPhoto(person.photo));
    result.push(emitPhotoData(person.photo));
    return result.join("\n");
}
function photoDiv(p){
    return [
        "<div>",
        emitPhotoData(p),
        "</div>",
    ].join("\n");
}
//함수 이름 바꾸기로 마무리
function emitPhotoData(p){
    return [
        `<p>제목: ${p.title}</p>`,
        `<p>위치: ${p.location}</p>` ,
        `<p>날짜: ${p.date.toDateString()}</p>`,
    ].join("\n");
}
function renderPerson(outStream, person){
    const result = [];
    result.push(`<p>${person.name}</p>`);
    result.push(renderPhoto(person.photo));
    result.push(emitPhotoData(person.photo));
    return result.join("\n");
}
function photoDiv(aPhoto){
    return [
        "<div>",
        emitPhotoData(aPhoto),
        "</div>",
    ].join("\n");
}
function emitPhotoData(aPhoto){
    return [
        `<p>제목: ${aPhoto.title}</p>`,
        `<p>위치: ${aPhoto.location}</p>` ,
        `<p>날짜: ${aPhoto.date.toDateString()}</p>`,
    ].join("\n");
}

 

 

 

 

 

 

 

 

개요

적합한 데이터 구조를 활용하면 동작 코드는 자연스럽게 단순하고 직관적으로 짜여진다.

데이터 구조를 잘못 선택하면 데이터를 다루기 위한 코드로 범벅이 된다. 결국 이해하기 힘든 코드가 된다.

 

현재 데이터 구조가 적절치 않으면 바로 수정해야 한다.

 

함수에 어떤 레코드를 넘길 때마다 다른 레코드의 필드도 함께 넘기고 있다면, 필드 옮기기가 필요하다.

레코드의 변경을 때마다 다른 레코드의 필드까지 변경해야 한다면, 필드 옮기기가 필요하다.

여러 레코드에 정의된 똑같은 필드가 있을 레코드의 필드가 갱신될 다른 레코드 필드도 갱신해야 한다면 필드 옮기기가 필요하다.

 

필드 옮기기는 대체로 리펙터링을 위한 사전 작업인 경우가 많다.

 

레코드 뿐만 아니라 클래스도 똑같다. 데이터에 함수가 해진 레코드가 클래스이기 때문이다.

 

클래스는 접근자 메서드로 캡슐화가 되어 있어 비교적 리팩터링이 쉽다.

캡슐화되자 않은 날레코드는 훨씬 까다롭다.



//날레코드 예시,게터세터 없이 데이터를 직접 접근
public class RawRecord{
    public String data;
    public Integer data2;
}

예시



class Customer{
    constructor(name, discountRate){
        this._name = name;
        this._discountRate = discountRate;
        this._contract = new CustomerContract(dateToday());
    }
    get discountRate(){return this._discountRate;}
    becomePreferred(){
        this._discountRate += 0.03;
        //코드들 ...
    }
    applyDiscount(amount){
        return amount.subtract(amount.multiply(this._discountRate));
    }
}
class CustomerContract{
    constructor(startDate){
        this._startDate = startDate;
    }
}
Customer 클래스에 discountRate 변수를 CustomerContract 옮기고 싶다.
세터는 만들고 싶이 않아 메서드를 사용
class Customer{
    constructor(name, discountRate){
        this._name = name;
        this._setDiscountRate(discountRate);
        this._contract = new CustomerContract(dateToday());
    }
    get discountRate(){return this._discountRate;}
    //먼저 변수 캡슐화부터
    _setDiscountRate(aNumber){this._discountRate = aNumber;}
    becomePreferred(){
        this._setDiscountRate(this._discountRate +0.03);
        //코드들 ...
    }
    applyDiscount(amount){
        return amount.subtract(amount.multiply(this._discountRate));
    }
}


}
//필드와 접근자 추가
class CustomerContract{
    constructor(startDate,discountRate){
        this._startDate = startDate;
        this._discountRate = discountRate;
    }
    get discountRate(){return this._discountRate;}
    set discountRate(arg){this._discountRate = arg;}
}


class Customer{
    //계약 인스턴스를 사용하도록 수정
    constructor(name, discountRate){
        this._name = name;
        this._contract = new CustomerContract(dateToday());
        this._setDiscountRate(discountRate);
    }
    get discountRate(){return this._contract.discountRate;}
    _setDiscountRate(aNumber){this._contract.discountRate = aNumber;}
    becomePreferred(){
        this._setDiscountRate(this._discountRate +0.03);
        //코드들 ...
    }
    applyDiscount(amount){
        return amount.subtract(amount.multiply(this._discountRate));
    }
}
class CustomerContract{
    constructor(startDate,discountRate){
        this._startDate = startDate;
        this._discountRate = discountRate;
    }
    get discountRate(){return this._discountRate;}
    set discountRate(arg){this._discountRate = arg;}
}

리팩터링은 객체를 활용할 수월하다.

캡슐화 덕분에 데이터 접근이 메서드로도 메끄럽다.

함수로는 까다롭다.

 

예시: 공유 객체로 이동하기

이자율을 계좌 별로 설정하는 예시



class Account{
    constructor(number, type, interestRate){
        this._number = number;
        this._type = type;
        this._interestRate = interestRate;
    }
    get interestRate(){return this._interestRate;}
}
class AccountType{
    constructor(nameString){
        this.name = nameString;
    }
}
계좌 종류에 따라 정해지도록 수정


//interestRate 이자율 필드 생성 및 접근자 추가
class AccountType{
    constructor(nameString, interestRate){
        this._name = nameString;
        this._interestRate = interestRate;
    }
    get interestRate(){return this._interestRate;}
}
Acount AcountType 이자율을 가져오도록 수정하면 문제가 생길 있다.
전엔 Acount별로 자신만의 이자율을 가졌다. 지금은 같은 종류 계좌끼리 이자율을 공유하려 한다.
정말로 같은 종류 계좌는 이자율이 같은지가 관건이다. 반약 다르다면 리팩터링의 핵심인 "수정 전과 수정 겉보기 동작이 같아야 한다" 깨져 이상 리팩터링이 아니게 된다.
이를 위해 먼저 DB에서 같은 종류 계좌가 같은 이자율 같은지 검증하는 것이 필요하다. 그리고 어셔션을 추가하면 도움된다.


class Account{
    constructor(number, type, interestRate){
        this._number = number;
        this._type = type;
        //어셔션 추가한 상태로 당분간 운영하기
        assert(interestRate === this._type.interestRate);
        this._interestRate = interestRate;
    }
    get interestRate(){return this._interestRate;}
}


class Account{
    constructor(number, type, interestRate){
        this._number = number;
        this._type = type;
    }
    get interestRate(){return this._type.interestRate;}
}
class AccountType{
    constructor(nameString, interestRate){
        this._name = nameString;
        this._interestRate = interestRate;
    }
    get interestRate(){return this._interestRate;}
}

 

 

 

 

 

 

개요

좋은 소프트웨어 설계 핵심은 모듈화 수준이다.

모듈성이란 프로그램 어딘가를 수정 해당 기능과 관련된 극히 작은 부분만 이해 해도되는 능력이다.

 

모듈성이 높으려면 연관된 요소를 하나의 요소로 묶고, 요소 사이 연결 관계를 쉽게 이해할 있어야 한다.

 

객체 지향 프로그래밍의 핵심 모듈화 컨텍스트는 클래스다.

 

A 함수가 자신이 속한 모듈보다 다른 모듈을 참조한다면 옮겨야한다.

 

예시: 중첩 함수를 최상위로 옮기기

 



/* GPS 추적 기록의 총 거리를 계산하는 함수 */
function trackSummary(points){
    const totalTime = calculateTime();
    const totalDistance = calculateDistance();
    const pace = totalTime / 60 / totalDistance;
    return {
        time : totalTime,
        distance : totalDistance,
        pace : pace,
    };
    function calculateDistance() { //총 거리 계산
        let result = 0;
        for(let i = 1; i < points.length ; i++){
            result += distance(points[i-1], points[i]);
        }
        return result;
    }
    function distance(p1, p2) {/* 두 지점의 거리 계산 */}
    function radians(degrees) {/* 라디안값으로 변환 */}
    function calculateTime() {/* 총 시간 계산 */}
}
중첩 함수 calculateDistance() 최상위로 옮겨서 다른 정보와 독립적으로 계산하고 싶다.


function trackSummary(points){
    const totalTime = calculateTime();
    const totalDistance = calculateDistance();
    const pace = totalTime / 60 / totalDistance;
    return {
        time : totalTime,
        distance : totalDistance,
        pace : pace,
    };
    function calculateDistance() { //총 거리 계산
        let result = 0;
        for(let i = 1; i < points.length ; i++){
            result += distance(points[i-1], points[i]);
        }
        return result;
    }
    function distance(p1, p2) {/* 두 지점의 거리 계산 */}
    function radians(degrees) {/* 라디안값으로 변환 */}
    function calculateTime() {/* 총 시간 계산 */}
}
function top_calculateDistance() { //최상위로 복사,임시이름
    let result = 0;
    for(let i = 1; i < points.length ; i++){
        result += distance(points[i-1], points[i]);
    }
    return result;
}


function trackSummary(points){
    const totalTime = calculateTime();
    const totalDistance = calculateDistance();
    const pace = totalTime / 60 / totalDistance;
    return {
        time : totalTime,
        distance : totalDistance,
        pace : pace,
    };
    function calculateDistance() { //총 거리 계산
        let result = 0;
        for(let i = 1; i < points.length ; i++){
            result += distance(points[i-1], points[i]);
        }
        return result;
        //distance, radians 는 현재 문맥에서 calculateDistance에서만 사용중
        //따라서 calculateDistance()함수 안으로 옮기는게 적절하다.
        function distance(p1, p2) {
            const EARTH_RADIUS = 3959; // 단위 마일
            const dLat = radians(p2.lat) - radians(p1.lat);
            const dLon = radians(p2.lon) - radians(p1.lon);
            const a = Math.pow(Math.sin(dLat / 2), 2)
                + Math.cos(radians(p2.lat))
                * Math.cos(radians(p1.lat))
                * Math.pow(Math.sin(dLon /2), 2);
            const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a));
            return EARTH_RADIUS * c;
        }
        function radians(degrees) {
            return degrees * Math.PI / 180;
        }
    }
    function calculateTime() {/* 총 시간 계산 */}
}
function top_calculateDistance(points) {
    let result = 0;
    for(let i = 1; i < points.length ; i++){
        result += distance(points[i-1], points[i]);
    }
    return result;
}


function top_calculateDistance(points) {
    let result = 0;
    for(let i = 1; i < points.length ; i++){
        result += distance(points[i-1], points[i]);
    }
    return result;
    //top_calculateDistance()함수도 똑같이 옮겨준다.
    function distance(p1, p2) {
        const EARTH_RADIUS = 3959; // 단위 마일
        const dLat = radians(p2.lat) - radians(p1.lat);
        const dLon = radians(p2.lon) - radians(p1.lon);
        const a = Math.pow(Math.sin(dLat / 2), 2)
            + Math.cos(radians(p2.lat))
            * Math.cos(radians(p1.lat))
            * Math.pow(Math.sin(dLon /2), 2);
        const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a));
        return EARTH_RADIUS * c;
    }
    function radians(degrees) {
        return degrees * Math.PI / 180;
    }
}


function trackSummary(points){
    const totalTime = calculateTime();
    const totalDistance = calculateDistance();
    const pace = totalTime / 60 / totalDistance;
    return {
        time : totalTime,
        distance : totalDistance,
        pace : pace,
    };
    //복사 함수를 호출하도록 수정한다.
    function calculateDistance() { //총 거리 계산
        return top_calculateDistance(points);
    }
    function calculateTime() {/* 총 시간 계산 */}
}
시점에서 반드시 테스트를 정상 동작을 확인한다.
정상 동작한다면 기존 함수 제거 복사 함수를 직접 호출하도록 수정한다.
function trackSummary(points){
    const totalTime = calculateTime();
    const totalDistance = top_calculateDistance();
    const pace = totalTime / 60 / totalDistance;
    return {
        time : totalTime,
        distance : totalDistance,
        pace : pace,
    };
    function calculateTime() {/* 총 시간 계산 */}
}


function trackSummary(points){
    const totalTime = calculateTime();
    const pace = totalTime / 60 / totalDistance();
    return {//적절한 이름으로 변경 후 인라인
        time : totalTime,
        distance : totalDistance(),
        pace : pace,
    };
    function calculateTime() {/* 총 시간 계산 */}
}


function trackSummary(points){
    const totalTime = calculateTime();
    const pace = totalTime / 60 / totalDistance();
    return {
        time : totalTime,
        distance : totalDistance(),
        pace : pace,
    };
    function calculateTime() {/* 총 시간 계산 */}
}
function totalDistance(points) {
    let result = 0;
    for(let i = 1; i < points.length ; i++){
        result += distance(points[i-1], points[i]);
    }
    return result;
}
//distance, radians totalDistance안에 어떤 것도 의존하지 않으니
//최상단으로 옮겼다.
function distance(p1, p2) {
    const EARTH_RADIUS = 3959; // 단위 마일
    const dLat = radians(p2.lat) - radians(p1.lat);
    const dLon = radians(p2.lon) - radians(p1.lon);
    const a = Math.pow(Math.sin(dLat / 2), 2)
        + Math.cos(radians(p2.lat))
        * Math.cos(radians(p1.lat))
        * Math.pow(Math.sin(dLon /2), 2);
    const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a));
    return EARTH_RADIUS * c;
}
function radians(degrees) {
    return degrees * Math.PI / 180;
}

 

예시: 다른 클래스로 옮기기



class Account{
    get bankCharge(){ //은행이자
        let result = 4.5;
        if(this._daysOverdrawn > 0) result += this.overdraftCharge;
        return result
    }
    get overdraftCharge(){
        if(this.type.isPremium){//초과 인출 이자 계산
            const baseCharge = 10;
            if(this.daysOverdrawn <= 7){
                return baseCharge;
            }else{
                return baseCharge + (this.daysOverdrawn - 7) * 0.85;
            }
        }else{
            return this.daysOverdrawn * 1.75;
        }
    }
}
계좌 종류에 따라 이자 책정 알고리즘이 달라지도록 수정


class AccountType{
    //계좌 타입으로 복사 후 적절하게 수정
    //daysOverdrawn는 계좌별로 달라지므로 원래 클래스에 남겨두었다.
    overdraftCharge(daysOverdrawn){
        if(this.isPremium){
            const baseCharge = 10;
            if(this.daysOverdrawn <= 7){
                return baseCharge;
            }else{
                return baseCharge + (daysOverdrawn - 7) * 0.85;
            }
        }else{
            return daysOverdrawn * 1.75;
        }
    }
}


class Account{
    get bankCharge(){
        let result = 4.5;
        if(this._daysOverdrawn > 0) result += this.overdraftCharge;
        return result
    }
    get overdraftCharge(){//위임 메서드
        return this.type.overdraftCharge(this.daysOverdrawn);
    }
}
class AccountType{
    overdraftCharge(daysOverdrawn){
        if(this.isPremium){
            const baseCharge = 10;
            if(this.daysOverdrawn <= 7){
                return baseCharge;
            }else{
                return baseCharge + (daysOverdrawn - 7) * 0.85;
            }
        }else{
            return daysOverdrawn * 1.75;
        }
    }
}


class Account{
    get bankCharge(){
        let result = 4.5;
        if(this._daysOverdrawn > 0)
            //인라인할지 고민한 결과 인라인하기로 결정
            result += this.type.overdraftCharge(this.daysOverdrawn);
        return result
    }
}
class AccountType{
    overdraftCharge(daysOverdrawn){
        if(this.isPremium){
            const baseCharge = 10;
            if(this.daysOverdrawn <= 7){
                return baseCharge;
            }else{
                return baseCharge + (daysOverdrawn - 7) * 0.85;
            }
        }else{
            return daysOverdrawn * 1.75;
        }
    }
}

 

 

 

 

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

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

ebook-product.kyobobook.co.kr

개요

목적을 달성하는 방법은 여러 가지

기존 방식보다 쉬운 방법을 발견하면 변경한다.

 

예를들어 기존 기능을 똑같이 지원하는 라이브러리를 찾았다면 라이브러리를 쓰는 편이 좋을

 

작업을 위해선 메서드 기능을 가능한 잘게 나눠야 한다.

 

예시



function foundPerson(people){
    for(let i = 0; i < people.length; i++){
        if(people[i] === "Don"){
            return "Don";
        }
        if(people[i] === "John"){
            return "John";
        }
        if(people[i] === "Kent"){
            return "Kent";
        }
    }
    return "";
}


function foundPerson(people){
    const candidates = ["Don", "John", "Kent"];
    return people.find(p=> candidates.includes(p))||'';
}

 

 

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

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

ebook-product.kyobobook.co.kr

 

개요

클라이언트가 서버의 위임 객체의 다른 기능을 사용하고 싶을 마다 위임 메서드를 추가해야한다. 과도하면 단순히 위임 메서드만 들어나 성가셔진다.

 

서버는 단순히 중개자 역할로 전락할 있어 이럴 바엔 그냥 클라이언트가 위임 객체를 직접 호출하는 나을 있다.

데메테르 법칙을 너무 지나치게 지킬 이런 상황이 발생한다.

 

최소 지식 원칙 == 데메테르 법칙

내부 정보를 가능한 숨기고 밀접한 모듈과만 상호작용하여 결합도를 낮추자는 원칙

과정에서 래퍼 클래스나 위임 객체가 너무 늘어나는 부작용이 있다.

 

균형을 잡는 것이 중요하다.

균형점을 잡는 것은 너무 고민하지 않아도 된다. 리팩터링으로 다시 변경하기 쉽기 때문이다.

 

예시

Department 객체를 통해 manager 찾는다.

 



//클라이언트
let manager = aPerson.manager;
class Person{
    get manager(){return this._department.manager;}
}
class Department{
    get manager(){return this._manager;}
}


//클라이언트
let manager = aPerson.department.manager;
class Person{
    //게터 추가 후 클라이언트 코드 수정 후 위임(중개) 메서드 제거
    get department(){return this._department;}
}
class Department{
    get manager(){return this._manager;}
}

 

중개 메서드는 균형이 중요하다. 반드시 하나를 정해서 적용하거나 적용하지 않거나 필요가 없다.

자신이 처한 상황에 따라 절적히 섞어쓰는 지혜가 필요하다.

 

 

 

개요

모듈화 설계를 제대로하는 핵심은 캡슐화

캡슐화는 모듈들이 시스템의 다른 부분에 대해 알아야 내용을 줄여 준다.

코드 변경이 쉬워진다.

 

서버 객체의 필드가 가리키는 객체(위임) 메서드를 호출하려면 클라이언트는 위임 객체를 알아야 한다.

위임 객체 인터페이스 변경 인터페이스를 사용하는 모든 클라이언트 코드를 수정해야 한다.

이런 의존성을 없애기 위해 서버 클래스에서 자체 위임 메서드를 만들어 위임 객체의 존재를 숨기면 된다.

 

 

예시

class Person{
    constructor(name){
        this._name = name;
    }
    get name(){return this._name;}
    get department(){return this._department;}
    set department(arg){this._department = arg;}
}
class Department{
    get chargeCode(){return this._chargeCode;}
    set chargeCode(arg){this._chargeCode=arg;}
    get manager(){return this._manager;}
    set manager(arg){this._manager=arg;}
}
//클라이언트
let manager = aPerson.department.manager;


class Person{
    constructor(name){
        this._name = name;
    }
    get name(){return this._name;}
    get department(){return this._department;}
    set department(arg){this._department = arg;}
    get manager(){return this.department.manager;}
}
class Department{
    get chargeCode(){return this._chargeCode;}
    set chargeCode(arg){this._chargeCode=arg;}
    get manager(){return this._manager;}
    set manager(arg){this._manager=arg;}
}
//클라이언트
let manager = aPerson.manager;


class Person{
    constructor(name){
        this._name = name;
    }
//접근자 삭제
    get name(){return this._name;}
    get manager(){return this.department.manager;}
}
class Department{
    get chargeCode(){return this._chargeCode;}
    set chargeCode(arg){this._chargeCode=arg;}
    get manager(){return this._manager;}
    set manager(arg){this._manager=arg;}
}
//클라이언트
let manager = aPerson.manager;

 

 

 

 

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

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

ebook-product.kyobobook.co.kr

 

개요

클래스 추출하기의 정반대 리팩터링

이상 제역할을 못하는 클래스를 인라인한다. 판단 기준은 이상 클래스 역할이 거의 없을 때이다. 옮기는 위치는 클래스를 자주 사용하는

 

다른 케이스로 클래스의 기능을 지금과 다르게 배분하고 싶을 때도 일단 클래스를 인라인한다. 그리고 다시 클래스를 추출한다.

상황에 따라 컨텍스트 요소를 하나씩 옮기는 편할 수도, 아니면 일단 클래스를 인라인하고, 다시 클래스 추출하기가 편할 수도 있다.

 

예시

//현재 제일을 못하는 클래스라 가정
class TrackingInformation{
    get shippingCompany(){return this._shippingCompany;}
    set shippingCompany(arg){this._shippingCompany = arg;}
    get trackingNumber(){return this._trackingNumber;}
    set trackingNumber(arg){this._trackingNumber = arg;}
    get display(){
        return `${this.shippingCompany} : ${this.trackingNumber}`;
    }
}
class Shipment{
    get trackingInfo(){
        return this._trackingInformation.display;
    }
    get trackingInformation() {return this._trackingInformation;}
    set trackingInformation(aTrackingInformation) {
        this._trackingInformation = aTrackingInformation;
    }
}
//클라이언트
aShipment.trackingInformation.shippingCompany = request.vender;

class Shipment{
    get trackingInfo(){
        return this._trackingInformation.display;
    }
    get shippingCompany(){return this._trackingInformation.shippingCompany;}
    set shippingCompany(arg){this._trackingInformation.shippingCompany = arg;}
    get trackingInformation() {return this._trackingInformation;}
    set trackingInformation(aTrackingInformation) {
        this._trackingInformation = aTrackingInformation;
    }
}
//클라이언트- 호출부분에서 TrackingInformation 제거
let aShipment = new Shipment();
aShipment.shippingCompany = request.vender;


class Shipment{
    get trackingInfo(){
        return this._trackingInformation.display;
    }
    get display(){
        return `${this.shippingCompany} : ${this.trackingNumber}`;
    }
    //나머지도 옮긴 후 클래스 TrackingInformation 제거
    get shippingCompany(){return this._shippingCompany;}
    set shippingCompany(arg){this._shippingCompany = arg;}
    get display(){
        return `${this.shippingCompany} : ${this.trackingNumber}`;
    }
    get trackingInformation() {return this._trackingInformation;}
    set trackingInformation(aTrackingInformation) {
        this._trackingInformation = aTrackingInformation;
    }
}
let aShipment = new Shipment();
aShipment.shippingCompany = request.vender;

 

 

개요

함수 안에서 임시 변수는 값을 함수 안에서 다시 사용하거나 값에 명확한 이름을 부여하기 위한 수단이다.

 

함수를 별도 함수로 추출할 변수들이 함수로 만들어져 있으면 수월하다. 추출할 함수에 변수를 따로 전달할 필요가 없기 때문이다.

추가로 추출한 함수와 원래 함수의 경계가 분명해진다. 부자연스러운 의존 관계가 제거되기 때문이다.

 

변수 대신 함수로 만들어 두면, 혹시나 다른 곳에서 같은 방식으로 계산하는 변수를 찾으면 재사용할 기회를 얻는다.

 

이런 변수는 특히 클래스 리팩터링에 효과적이다. 같은 문맥을 공유하기 때문이다.

 

리팩터링은 계산된 뒤에 반드시 읽기만 해야한다.

예시



class Order {
    constructor(quantity, item){
        this._quantity = quantity;
        this._item = item;
    }
    get price(){
        var basePrice = this._quantity * this._item.price;
        var discountFactor = 0.98;
        if(basePrice>1000) discountFactor -= 0.03;
        return basePrice * discountFactor;
    }
}


    get price(){
        //var const로 바꿔보기 봇보고 지나진 대입문을 찾을 수 있다
        const basePrice = this._quantity * this._item.price;
        var discountFactor = 0.98;
        if(basePrice>1000) discountFactor -= 0.03;
        return basePrice * discountFactor;
    }


    get price(){
        const basePrice = this.basePrice();
        var discountFactor = 0.98;
        if(basePrice>1000) discountFactor -= 0.03;
        return basePrice * discountFactor;
    }
    //게터로 추출
    get basePrice() {
        return this._quantity * this._item.price;
    }


    get price(){
        var discountFactor = 0.98;
        //변수 인라인
        if(this.basePrice>1000) discountFactor -= 0.03;
        return this.basePrice * discountFactor;
    }


    get price(){
        const discountFactor = 0.98;
        if(this.basePrice>1000) discountFactor -= 0.03;
        return this.basePrice * discountFactor;
    }


    get price(){
        const discountFactor = this.discountFactor;
        return this.basePrice * discountFactor;
    }
    //게터 추출
    get discountFacter() {
        var discountFactor = 0.98;
        if (this.basePrice > 1000)
            discountFactor -= 0.03;
        return discountFactor;
    }


    get price(){
        return this.basePrice * this.discountFactor;
    }


class Order {
    constructor(quantity, item){
        this._quantity = quantity;
        this._item = item;
    }
    get price(){
        return this.basePrice * this.discountFactor;
    }
    get discountFacter() {
        var discountFactor = 0.98;
        if (this.basePrice > 1000)
            discountFactor -= 0.03;
        return discountFactor;
    }
    get basePrice() {
        return this._quantity * this._item.price;
    }
}

 

+ Recent posts