개요

중복 코드 제거는 중요하다. 당장 문제 없더라도 미래에 요구사항 변경에 따른 코드 수정 모든 코드에 반영되지 않을 있다는 위험이 존재한다.

예시

실질적으로 같은 메서드
class Party{
}
class Employee extends Party{
    get annualCost(){
        return this.monthlyCost *12 ;
    }
}
class Department extends Party{
    get totalAnnualCost(){
        return this.monthlyCost *12 ;
    }
}


class Party{
    get annualCost(){
        return this.monthlyCost *12 ;
    }
}
//서브 클래스 중 하나로 이름을 통일하고, 슈퍼 클래스에 메서드를 복사한다.
class Employee extends Party{
    get annualCost(){
        return this.monthlyCost *12 ;
    }
}
class Department extends Party{
    get annualCost(){
        return this.monthlyCost *12 ;
    }
}


class Party{
    get annualCost(){
        return this.monthlyCost *12 ;
    }
}
//정상동작을 확인했다면 서브클래스 메서드를 제거한다.
class Employee extends Party{
}
class Department extends Party{
}


class Party{
    get annualCost(){
        return this.monthlyCost *12 ;
    }
    //Party 슈퍼클래스에서는 서브 클래스에 monthlyCost가 없다.
    //이대로 동작하는 이유는 자바스크립트는 동적언어이기 때문이다.
    //이를 알리기 위해 함정 메서드를 만들어두는 것이 도움된다.
    get monthlyCost(){
        throw new Error("이 값은 서브클래스에서 구현해야합니다.");
    }
}
//정상동작을 확인했다면 서브클래스 메서드를 제거한다.
class Employee extends Party{
    //monthlyCost 있다고 가정
}
class Department extends Party{
    //monthlyCost 있다고 가정
}

 

 

개요

예외라는 개념은 오류 코드를 연쇄적으로 전파하던 코드를 제거할 있게 해줬다.

예외는 과용하면 좋지 않다. 말그대로 예외인 경우에만 쓰여야 한다.

함수 호출 전에 문제를 제거할 있다면 그렇게 해야 한다.

예시

DB 연결 같은 자원을 관리하는 자원 클래스

풀은 어떤 자원이 할당되고 가용할 있는 지를 추적, 자원 바닥나면 새로 생성

 



import java.util.Deque;
import java.util.List;
import java.util.NoSuchElementException;
public class Refactoring1 {
    static class Resource{
        public static Resource create() {
            return null;
        }
    }
    static class ResourcePool{
        private Deque<Resource> available;
        private List<Resource> allocated;
       
        public Resource get() {
            Resource result;
            try {
                result = available.pop();
                allocated.add(result);
            }catch (NoSuchElementException e) {
                result = Resource.create();
                allocated.add(result);
            }
            return result;
        }
    }
}


import java.util.Deque;
import java.util.List;
import java.util.NoSuchElementException;
public class Refactoring1 {
    static class Resource{
        public static Resource create() {
            return null;
        }
    }
    static class ResourcePool{
        private Deque<Resource> available;
        private List<Resource> allocated;
       
        public Resource get() {
            Resource result;
            //풀에서 자원이 고갈되는 건 예상치 못한 조건이 아니다. 따라서 예외처리로 대응하는 건 옳지 않다.
            if(available.isEmpty()) {
                result = Resource.create();
                allocated.add(result);
            }else {
                try {
                    result = available.pop();
                    allocated.add(result);
                }catch (NoSuchElementException e) {
                    throw new AssertionError("도달 불가");
                }
            }
            return result;
        }
    }
}
예외에 도달하지 못한다면 이제 제거
import java.util.Deque;
import java.util.List;
public class Refactoring1 {
    static class Resource{
        public static Resource create() {
            return null;
        }
    }
    static class ResourcePool{
        private Deque<Resource> available;
        private List<Resource> allocated;
       
        public Resource get() {
            Resource result;
            if(available.isEmpty()) {
                result = Resource.create();
                allocated.add(result);
            }else {
                result = available.pop();
                allocated.add(result);
            }
            return result;
        }
    }
}


import java.util.Deque;
import java.util.List;
public class Refactoring1 {
    static class Resource{
        public static Resource create() {
            return null;
        }
    }
    static class ResourcePool{
        private Deque<Resource> available;
        private List<Resource> allocated;
       
        public Resource get() {
            //더 다듬기
            Resource result = available.isEmpty()? Resource.create():available.pop();
            allocated.add(result);
            return result;
        }
    }
}

 

 

개요

과거 오류 처리는 오류 코드를 통해서 했다.

 

예외는 프로그래밍 언어에서 제공하는 독립적인 오류 처리 메커니즘이다

오류가 발견되면 예외를 던지고, 생성된 예외는 적절한 예외 핸들러를 찾을 때까지 콜스택을 타고 위로 전파된다.

예외를 사용하면 오류코드를 일일이 검사하거나 오류를 식별하는 일을 신경 쓰지 않아도 된다.

 

예외는 정확히 예상 밖의 동작일 때만 쓰여야 한다.

판단하기 도움되는 기준은 예외를 던지는 코드를 프로그램 종료 코드로 바꿔도 프로그램이 여전히 정상동작하는지 보는것이다. 정상 동작하지 않는다면 예외를 사용하지 말고, 오류를 검출하여 프로그램을 정상 흐름으로 되돌려야 한다.

 

예시

전역 테이블에서 배송지의 배송 규칙을 알아내는 코드



function localShippingRules(country){//country 는 항상 유효함을 가정
    const data = countryData.shippingRules[country];
    if(data) return new shippingRules(data);
    else return -23; //오류
}
function calculateShippingCosts(anOrder){
    //코드들...
    const shippingRules = localShippingRules(anOrder.country);
    if(shippingRules < 0) return shippingRules; //오류 전파
    //코드들...
}
const status = calculateShippingCosts(orderData);
if(status < 0) errorList.push({order:orderData, errorCode:statuts});


try {//최상위 예외 핸들러를 만든다.
    const status = calculateShippingCosts(orderData);
} catch (error) {/*예외처리*/}
if(status < 0) errorList.push({order:orderData, errorCode:statuts});


//최상위
let status; //스코프 문제로 선언과 초기화를 어쩔 수 없이 분리
try {
    status = calculateShippingCosts(orderData);
} catch (error) {/*예외처리*/}
if(status < 0) errorList.push({order:orderData, errorCode:statuts});


//최상위
let status;
try {
    status = calculateShippingCosts(orderData);
} catch (error) {throw error;}//잡은 예외 다시 던지기
if(status < 0) errorList.push({order:orderData, errorCode:statuts});


//최상위
let status;
try {
    status = calculateShippingCosts(orderData);
} catch (error) {
    if(error instanceof OrderProcessingError)
        errorList.push({order:orderData, errorCode:statuts});
    else throw error;
    throw error;
}
if(status < 0) errorList.push({order:orderData, errorCode:statuts});
//별도 추가된 예외만들 처리하는 경우 처리방식
class OrderProcessingError extends Error{
    constructor(errorCode){
        super(`주문 처리 오류 : ${errorCode}`);
        this.code = errorCode;
    }
    get name(){return "OrderProcessingError";}
}


function localShippingRules(country){//country 는 항상 유효함을 가정
    const data = countryData.shippingRules[country];
    if(data) return new shippingRules(data);
    //오류 코드 대신 예외를 던진다.
    else return new OrderProcessingError(-23);
}


function calculateShippingCosts(anOrder){
    //코드들...
    const shippingRules = localShippingRules(anOrder.country);
    //이 상태로 두고 아직 오류 코드를 다 못처리한 부분있는지 테스트
    if(shippingRules < 0) return new Error("오류 코드가 다 사라지지 않았습니다.");
    //코드들...
}


function localShippingRules(country){//country 는 항상 유효함을 가정
    const data = countryData.shippingRules[country];
    if(data) return new shippingRules(data);
    //오류 코드 대신 예외를 던진다.
    else return new OrderProcessingError(-23);
}
function calculateShippingCosts(anOrder){
    //코드들...
    const shippingRules = localShippingRules(anOrder.country);
    //코드들...
}
//최상위
try {
    calculateShippingCosts(orderData);
} catch (error) {
    if(error instanceof OrderProcessingError)
        errorList.push({order:orderData, errorCode:statuts});
    else throw error;
    throw error;
}
class OrderProcessingError extends Error{
    constructor(errorCode){
        super(`주문 처리 오류 : ${errorCode}`);
        this.code = errorCode;
    }
    get name(){return "OrderProcessingError";}
}

 

 

개요

데이터가 어떻게 수정되는지 추적하는 일이 코드 파악에서 어려분 부분 하나다

 

특히 같은 데이터를 읽고 수정하는 곳이 여러 곳이라면 스크롤을 마구잡이로 이동하면서 봐야한다.

이런 이유로 데이터가 수정된 사실을 명확히 알리는 것은 중요하다.

수정 사실을 알리는 가장 좋은 것은 반환값으로 수정 대상 변수에 다시 담는 것이다.

코드 상으로도 수정된다는 사실이 명확해진다.

 

예시



let totalAscent = 0;
let totalTime = 0;
let totalDistance = 0;
calculateAscent();
calculateTime();
calculateDistance();
const pace = totalTime / 60 / totalDistance;
function calculateAscent(){
    for(let i = 1; i<ProcessingInstruction.length;i++){
        const verticalChange = points[i].elevation - points[i-1].elevation;
        totalAscent += (verticalChange>0) ? verticalChange : 0;
    }
}
totalAscent 갱신된다는 사실이 명확하지 않다.


let totalAscent = 0;
let totalTime = 0;
let totalDistance = 0;
totalAscent = calculateAscent();
calculateTime();
calculateDistance();
const pace = totalTime / 60 / totalDistance;
//값을 반환하도록 수정
function calculateAscent(){
    for(let i = 1; i<ProcessingInstruction.length;i++){
        const verticalChange = points[i].elevation - points[i-1].elevation;
        totalAscent += (verticalChange>0) ? verticalChange : 0;
    }
    return totalAscent;
}


function calculateAscent(){
    let totalAscent  = 0; //반환할 변수 선언
    for(let i = 1; i<ProcessingInstruction.length;i++){
        const verticalChange = points[i].elevation - points[i-1].elevation;
        totalAscent += (verticalChange>0) ? verticalChange : 0;
    }
    return totalAscent;
}


function calculateAscent(){
    let result  = 0; //적절한 이름으로 변경
    for(let i = 1; i<ProcessingInstruction.length;i++){
        const verticalChange = points[i].elevation - points[i-1].elevation;
        result += (verticalChange>0) ? verticalChange : 0;
    }
    return result;
}


const totalAscent = calculateAscent();
let totalTime = 0;
let totalDistance = 0;
calculateTime();
calculateDistance();
const pace = totalTime / 60 / totalDistance;
function calculateAscent(){
    let result  = 0; //적절한 이름으로 변경
    for(let i = 1; i<ProcessingInstruction.length;i++){
        const verticalChange = points[i].elevation - points[i-1].elevation;
        result += (verticalChange>0) ? verticalChange : 0;
    }
    return result;
}

const totalAscent = calculateAscent();
const totalTime = calculateTime();
const totalDistance = calculateDistance();
const pace = totalTime / 60 / totalDistance;

 

 

개요

명령 객체는 복잡한 연산을 다룰 있는 강력한 메커니즘을 제공한다.

연산을 하나를 여러 작은 메서드로 쪼갠다. 로컬 변수를 인스턴스 변수로 만들어 메서드 끼리 정보 공유할 수도 있다. 쪼게진 보조 메서드들은 좋은 디버깅 포인트가 된다.

 

다만, 복잡하지 않은 함수에 굳이 명령 사용하면 득보다 실이 크다.

 

예시



class ChargeCalculator{
    constructor(customer, usage, provider){
        this._customer = customer;
        this._usage = usage;
        this._provider = provider;
    }
    get baseCharge(){
        return this._customer.baseRate * this._usage;
    }
    get charge(){
        return this.baseCharge + this._provider.connectionCharge;
    }
}
//호출자...
const monthCharge = new ChargeCalculator(customer, usage, provider).charge;


//클래스를 생성하고 호출하는 함수 만들기
function charge(customer, usage, provider){
    return new ChargeCalculator(customer, usage, provider).charge;
}


class ChargeCalculator{
    constructor(customer, usage, provider){
        this._customer = customer;
        this._usage = usage;
        this._provider = provider;
    }
    get baseCharge(){
        return this._customer.baseRate * this._usage;
    }
    //보조함수 제거하기,먼저 로컬 변수로 추출
    get charge(){
        const baseCharge = this.baseCharge;
        return baseCharge + this._provider.connectionCharge;
    }
}


class ChargeCalculator{
    constructor(customer, usage, provider){
        this._customer = customer;
        this._usage = usage;
        this._provider = provider;
    }
    //보조함수 제거하기, 보조 함수 인라인 하기
    get charge(){
        const baseCharge = this._customer.baseRate * this._usage;
        return baseCharge + this._provider.connectionCharge;
    }
}


function charge(customer, usage, provider){
    return new ChargeCalculator(customer, usage, provider)
        .charge(customer, usage, provider);
}
class ChargeCalculator{
    constructor(customer, usage, provider){
        this._customer = customer;
        this._usage = usage;
        this._provider = provider;
    }
    //함수로 변경하기 위에 함수 포맷에 맞게 함수 선언 바꾸기 적용
    charge(customer, usage, provider){
        const baseCharge = this._customer.baseRate * this._usage;
        return baseCharge + this._provider.connectionCharge;
    }
}


function charge(customer, usage, provider){
    return new ChargeCalculator(usage, provider)
        .charge(customer, usage, provider);
}
class ChargeCalculator{
    constructor(usage, provider){
        this._usage = usage;
        this._provider = provider;
    }
    //인스턴스 변수를 하나씩 점진적으로 제거하면서 매개변수를 사용하도록 수정
    charge(customer, usage, provider){
        const baseCharge = customer.baseRate * this._usage;
        return baseCharge + this._provider.connectionCharge;
    }
}


function charge(customer, usage, provider){
    return new ChargeCalculator()
        .charge(customer, usage, provider);
}
class ChargeCalculator{
    //나머지도 하나씩 적용하면서 테스트
    charge(customer, usage, provider){
        const baseCharge = customer.baseRate * usage;
        return baseCharge + provider.connectionCharge;
    }
}
function charge(customer, usage, provider){
    const baseCharge = customer.baseRate * usage;
    return baseCharge + provider.connectionCharge;
}
//호출자...
const monthCharge = new ChargeCalculator(customer, usage, provider).charge;

 

 

 

개요

함수는 기본적인 빌딩 블록

함수를 함수만을 위한 객체로 캡슐화하면 유용해질 있다.

이런 객체를 "명령 객체(command object)" 혹은 "명령(command)"이라 한다.

명령 객체는 대게 메서드 하나로 구성된다.

 

명령은 함수보다 유연하다. 되돌리기 기능같은 보조 연산을 제공할 있다.

객체를 지원하지만 일급 함수를 지원하지 않은 언어에서 명령을 이용해 일급 함수의 기능 대부분을 흉내 있다.

마찬가지로 중첩 함수를 지원하지 않는 언어에서도 이를 이용해 복잡한 함수를 잘게 쪼갤 있다. 잘게 쪼갤 수록 디버깅이 쉬워진다.

 

명령 리팩터링은 유연성을 얻는 대신 복잡성이 증가한다. 따라서 적용할 대상을 신중히 선택해야 한다.

 

일급 함수를 지원하는 언어에서는 대부분 명령 대신 일급 함수를 사용한다

 

예시

건강보험 점수 계산 함수



function score(candidate, medicalExam, scoringGuide){
    let result = 0;
    let healthLevel = 0;
    let highMedicalRiskFlag = false;
    if(medicalExam.isSmoker){
        healthLevel += 10;
        highMedicalRiskFlag =true;
    }
    let certificationGrade = "regular";
    if(scoringGuide.stateWithLowCertification(candidate.originState)){
        certificationGrade = "low";
        result -= 5;
    }
    //많은 코드...
    result -= Math.max(healthLevel -5 , 0);
    return result;
}


function score(candidate, medicalExam, scoringGuide){
    return new Score().execute(candidate,medicalExam,scoringGuide);
}
//클래스를 만들고 함수 옮기기
class Score{
    execute(candidate,medicalExam, scoringGuide){
        let result = 0;
        let healthLevel = 0;
        let highMedicalRiskFlag = false;
   
        if(medicalExam.isSmoker){
            healthLevel += 10;
            highMedicalRiskFlag =true;
        }
        let certificationGrade = "regular";
        if(scoringGuide.stateWithLowCertification(candidate.originState)){
            certificationGrade = "low";
            result -= 5;
        }
        //많은 코드...
        result -= Math.max(healthLevel -5 , 0);
        return result;
    }
}
명령 객체로 바꿀 함수 매개변수를 최대한 생성자로 옮겨 매개변수 목록을 줄이는 것이 좋다. 가장 좋은 것은 없는 것이다.


function score(candidate, medicalExam, scoringGuide){
    return new Score(candidate).execute(medicalExam,scoringGuide);
}
//명령 매개변수 최대한 생성자로 옮기기
class Score{
    constructor(candidate){
        this._candidate = candidate;
    }
    execute(medicalExam, scoringGuide){
        let result = 0;
        let healthLevel = 0;
        let highMedicalRiskFlag = false;
   
        if(medicalExam.isSmoker){
            healthLevel += 10;
            highMedicalRiskFlag =true;
        }
        let certificationGrade = "regular";
        if(scoringGuide.stateWithLowCertification(candidate.originState)){
            certificationGrade = "low";
            result -= 5;
        }
        //많은 코드...
        result -= Math.max(healthLevel -5 , 0);
        return result;
    }
}


function score(candidate, medicalExam, scoringGuide){
    return new Score(candidate,medicalExam,scoringGuide).execute();
}
//명령 매개변수 최대한 생성자로 옮기기
class Score{
    constructor(candidate,medicalExam,scoringGuide){
        this._candidate = candidate;
        this._medicalExam = medicalExam;
        this._scoringGuide = scoringGuide;
    }
    execute(){
        let result = 0;
        let healthLevel = 0;
        let highMedicalRiskFlag = false;
   
        if(this._medicalExam.isSmoker){
            healthLevel += 10;
            highMedicalRiskFlag =true;
        }
        let certificationGrade = "regular";
        if(this._scoringGuide.stateWithLowCertification(this._candidate.originState)){
            certificationGrade = "low";
            result -= 5;
        }
        //많은 코드...
        result -= Math.max(healthLevel -5 , 0);
        return result;
    }
}

 

가다름기

명령으로 바꾼 주된 목적은 복잡한 함수를 잘게 나누는 것에 있다.

먼저 모든 지역 변수를 필드로 변경



class Score{
    constructor(candidate,medicalExam,scoringGuide){
        this._candidate = candidate;
        this._medicalExam = medicalExam;
        this._scoringGuide = scoringGuide;
    }
    //더 가다듬기, 명령의 목적인 복잡한 함수를 잘게 쪼게기 시작
    execute(){
        this._result = 0;
        this._healthLevel = 0;
        this._highMedicalRiskFlag = false;
   
        if(this._medicalExam.isSmoker){
            this._healthLevel += 10;
            this._highMedicalRiskFlag =true;
        }
        let certificationGrade = "regular";
        if(this._scoringGuide.stateWithLowCertification(this._candidate.originState)){
            certificationGrade = "low";
            this._result -= 5;
        }
        //많은 코드...
        this._result -= Math.max(this._healthLevel -5 , 0);
        return result;
    }
}
함수가 사용하던 로컬 변수를 필드로 바꾸면, 변수를 자유롭게 접근할 있어 다른 리팩터링이 수월해진다.


function score(candidate, medicalExam, scoringGuide){
    return new Score(candidate,medicalExam,scoringGuide).execute();
}
class Score{
    constructor(candidate,medicalExam,scoringGuide){
        this._candidate = candidate;
        this._medicalExam = medicalExam;
        this._scoringGuide = scoringGuide;
    }
    execute(){
        this._result = 0;
        this._healthLevel = 0;
        this._highMedicalRiskFlag = false;
   
        this.scoreSmoking();
        let certificationGrade = "regular";
        if(this._scoringGuide.stateWithLowCertification(this._candidate.originState)){
            certificationGrade = "low";
            this._result -= 5;
        }
        //많은 코드...
        this._result -= Math.max(this._healthLevel -5 , 0);
        return result;
    }
    //이렇게 쪼게놓으면 디버깅이 훨씬 쉬워진다.(디버깅 포인트가 더욱 촘촘해짐)
    scoreSmoking() {
        if (this._medicalExam.isSmoker) {
            this._healthLevel += 10;
            this._highMedicalRiskFlag = true;
        }
    }
}

 

 

개요

대부분의 객체 지향 언어에서 생성자는 객체를 초기화하는 특별한 용도의 함수다.

특별한 함수라 제약있는 경우가 있다. 자바의 경우 생성자는 반드시 자신의 인스턴스를 반환해야 한다. 서브클래스나 프락시를 반환할 없다. 생성자 이름도 클래스 이름으로 제한된다. 그리도 대부분의 언어에서 생성자 함수를 호출하기 위해 키워드가 필요하다(대게 new)

 

팩터리 함수에는 이런 제약이 없다.

예시

직원 유형을 다루는 예시

class Employee{
    constructor(name, typeCode){
        this._name = name;
        this.typeCode = typeCode;
    }
    get name(){return this._name;}
    get type(){
        return Employee.legalTypeCodes[this._typeCode];
    }
    static get legalTypeCodes(){
        return {"E" : "Engineer","M":"Manager","S":"Salesperson"};
    }
}


//클라이언트
const candidate = new Employee(document.name, document.empType);


//가장 먼저 팩터리 함수 작성
function createEmployee(name, typeCode){
    return new Employee(name,typeCode);
}


//클라이언트, 생성자 호출 코드 대체
const candidate = createEmployee(document.name, document.empType);


//메서드 호출에 리터럴을 쓰는 것은 악취다
const candidate2 = createEmployee(document.name, 'E');
//팩터리 메서드 이름을 이용해 악취를 제거한다.
function createEngineer(name){
    return new Employee(name, "E");
}


function createEmployee(name, typeCode){
    return new Employee(name,typeCode);
}


class Employee{
    constructor(name, typeCode){
        this._name = name;
        this.typeCode = typeCode;
    }
    get name(){return this._name;}
    get type(){
        return Employee.legalTypeCodes[this._typeCode];
    }
    static get legalTypeCodes(){
        return {"E" : "Engineer","M":"Manager","S":"Salesperson"};
    }
}
//클라이언트, 생성자 호출 코드 대체
const candidate = createEmployee(document.name, document.empType);
//메서드 호출에 리터럴을 쓰는 것은 악취다
const candidate2 = createEmployee(document.name, 'E');
//팩터리 메서드 이름을 이용해 악취를 제거한다.
function createEngineer(name){
    return new Employee(name, "E");
}

 

개요

세터가 있다는 것은 필드를 수정할 있다는

변경이 싫다면 생성자로만 초기화해야 한다.

 

세터 제거하기 리팩터링 필요한 상황

생성 스크립트를 사용해 객체를 생성할

생성 스크립트란 기본 생성자를 호출한 인련의 세터를 호출하여 객체를 완성하는 형태의 코드를 말한다. , 생성 후에만 세터를 호출한다. 이럴 경우 세터를 제거해 의도를 명확히 한다.

 

 

 

class Person{
    get name(){return this._name;}
    set name(arg){this._name = arg;}
    get id(){return this._id;}
    set id(arg){this._id = arg;}
}
const martin = new Person();
martin.name = "마틴";
martin.id = "1234";
id 객체 생성 변경되면 안된다.


class Person{
    constructor(id){
        this._id = id;
    }
    get name(){return this._name;}
    set name(arg){this._name = arg;}
    get id(){return this._id;}
}
const martin = new Person("1234");
martin.name = "마틴";

 

 

 

 

 

 

 

개요

매개변수는 함수의 동작에 변화를 있는 일차적 수단

매개변수 목록은 적을 수록 좋다.

 

 

매개변수를 질의 함수로 바꾸지 말아야 상황도 있다. 매개변수를 제거하면 피호출 함수에 의존성이 생길 때다.

해당 함수가 몰랐으면 하는 프로그램 요소에 접근해야 하는 상황 생기는 경우(의존성 생김)

 

예시

어떠한 리팩터링을 수행한 이상 매개변수가 필요없어지는 경우



class Order{
    get finalPrice(){
        const basePrice = this.quantity * this.itemPrice;
        let discountLevel;
        if(this.quantity > 100) discountLevel = 2;
        else discountLevel = 1;
        return this.discountedPrice(basePrice, discountLevel);
    }
    discountedPrice(basePrice, discountLevel){
        switch(discountLevel){
            case 1: return basePrice * 0.95;
            case 2: return basePrice * 0.9;
        }
    }
}


class Order{
    get finalPrice(){
        const basePrice = this.quantity * this.itemPrice;
        return this.discountedPrice(basePrice, this.discountLevel);
    }
    get discountLevel(){
        return this.quantity > 100 ? 2 : 1;
    }
    discountedPrice(basePrice, discountLevel){
        switch(discountLevel){
            case 1: return basePrice * 0.95;
            case 2: return basePrice * 0.9;
        }
    }
}


class Order{
    get finalPrice(){
        const basePrice = this.quantity * this.itemPrice;
        return this.discountedPrice(basePrice);
    }
    get discountLevel(){
        return this.quantity > 100 ? 2 : 1;
    }
    discountedPrice(basePrice){
        switch(this.discountLevel){
            case 1: return basePrice * 0.95;
            case 2: return basePrice * 0.9;
        }
    }
}

개요

하나의 레코드에서 갑을 여러 꺼내서 인수로 넘길 바에 레코드 자체를 넘기는게 좋다

 

이점으로 매개변수 목록이 짧아지고, 수정될 가능성이 낮아진다.

 

, 함수가 레코드에 의존하게 되므로, 서로 다른 모듈일 경우 리팩터링을 하지 않는다.

 

예시

실내온도 모니터링 시스템

function example(){
    const low = aRoom.datsTempRange.low;
    const high = aRoom.datsTempRange.high;
    if(!aPlan.withinRange(low,high))
        alerts.push("방온도가 지정 범위를 벗어났습니다.");
}
class HeatingPlan{
    withinRange(bottom, top){
        return (bottom >= this._temperatureRange.low)
            && (top <= this._temperatureRange.high);
    }
}
class HeatingPlan{
    withinRange(bottom, top){
        return (bottom >= this._temperatureRange.low)
            && (top <= this._temperatureRange.high);
    }
    //기존 메서드 대체할 빈 메서드 생성
    xxNEWwithinRange(aNumberRange){
    }
}
class HeatingPlan{
    withinRange(bottom, top){
        return (bottom >= this._temperatureRange.low)
            && (top <= this._temperatureRange.high);
    }
    //기존 메서드 호출코드 추가
    xxNEWwithinRange(aNumberRange){
        return this.withinRange(aNumberRange.low, aNumberRange.high);
    }
}
function example(){
    //기존 함수 호출하는 곳 새 함수 호출로 변경
    if(!aPlan.xxNEWwithinRange(aRoom.datsTempRange))
        alerts.push("방온도가 지정 범위를 벗어났습니다.");
}
function example(){
    //기존 함수 호출하는 곳 새 함수 호출로 변경
    if(!aPlan.withinRange(aRoom.datsTempRange))
        alerts.push("방온도가 지정 범위를 벗어났습니다.");
}
class HeatingPlan{
    withinRange(aNumberRange){
        return (aNumberRange.low >= this._temperatureRange.low)
            && (aNumberRange.high <= this._temperatureRange.high);
    }
}

 

예시: 함수를 다른 방식으로 만들기

변형된 방법, 코드 작성 없이 다른 리팩터링을 연달아 수행하여 메서드 생성

function example(){
    const low = aRoom.daysTempRange.low;
    const high = aRoom.daysTempRange.high;
    if(!aPlan.withinRange(low,high))
        alerts.push("방 온도가 지정 범위를 벗어났습니다.");
}

function example(){
    const low = aRoom.daysTempRange.low;
    const hihg = aRoom.daysTempRange.high;
    const isWithinRange = aPlan.withinRange(low, high);
    if(!isWithinRange)
        alerts.push("방 온도가 지정 범위를 벗어났습니다.");
}


function example(){
    const tempRange = aRoom.daysTempRange;
    const isWithinRange = xxNEWwithinRange();
    //함수 추출하기
    function xxNEWwithinRange() {
        const low = tempRange.low;
        const high = tempRange.high;
        const isWithinRange = aPlan.withinRange(low, high);
        if (!isWithinRange)
            alerts.push("방 온도가 지정 범위를 벗어났습니다.");
        return isWithinRange;
    }
}
function example(){
    const tempRange = aRoom.daysTempRange;
    const isWithinRange = aPlan.xxNEWwithinRange();
    if (!isWithinRange)
            alerts.push("방 온도가 지정 범위를 벗어났습니다.");
}
class HeatingPlan{
    //함수 옮기기
    xxNEWwithinRange() {
        const low = tempRange.low;
        const high = tempRange.high;
        const isWithinRange = aPlan.withinRange(low, high);
        return isWithinRange;
    }
}

 

+ Recent posts