개요

함수는 기본적인 빌딩 블록

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

이런 객체를 "명령 객체(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;
    }
}

 

개요

플래그 인수란 호출되는 함수가 실행할 로직을 호출하는 쪽에서 선택하기 위해 전달하는 인수



function bookConcert(aCustomer, isPreminum){
    if(isPreminum){
        // 프리미엄 예약용 로직
    }else{
        // 일반 예약용 로직
    }
}

 

//호출하는 쪽 예시

bookConcert(aCustomer, true); // 불리언

bookConcert(aCustomer, Customer.PREMINUM); //열거형

bookConcert(aCustomer,"premium"); //리터럴

플래그 인수는 이렇게 호출할 있는 함수들이 무엇이고 어떻게 호출해야 하는지 이해하기 어려워 진다.

 

API 익힐 함수에 플래그 인수가 있으면 함수들의 기능 차이가 드러나지 않는다.

또한 함수 인자로 무엇을 넘겨야하는지 추가 학습이 필요하다.

대상 함수에게 있어서 true, false 의미가 무엇인지 명확하지 않다.

 

플래그 인수는 정확히 말하면 변수 형태가 아닌 리터럴 형태로 값을 건네는 것을 말한다.

 

리터럴이란 자체를 의미한다.

true , "스트링" , 123 등등…

 

 

예시

배송일자를 계산하는 호출



//도대체 이 불리언 플래그가 무슨 의미인지 알 수 있나?
//호출하는 곳 1
aShipment.deliveryDate = deliveryDate(anOrder, true);
//다른 어딘가 호출하는 곳2
aShipment.deliveryDate = deliveryDate(anOrder, false);


function deliveryDate(anOrder, isRush){
    if(isRush){
        let deliveryTime;
        if(["MA","CT"].includes(anOrder.deliveryState)) deliveryTime = 1;
        else if(["NY", "NH"].includes(anOrder.deliveryState)) deliveryTime = 2;
        else deliveryTime = 3;
        return anOrder.placedOn.plusDays(1 + deliveryTime);
    }else{
        let deliveryTime;
        if(["MA","CT","NY"].includes(anOrder.deliveryState)) deliveryTime = 2;
        else if(["ME","NH"].includes(anOrder.deliveryState)) deliveryTime = 3;
        else deliveryTime = 4;
        return anOrder.placedOn.plusDays(2 + deliveryTime);
    }
}




//함수 추출하기로 조건문 분해
function deliveryDate(anOrder, isRush){
    if(isRush) return rushDeliveryDate(anOrder);
    else return regularDeliveryDate(anOrder);
}
function regularDeliveryDate(anOrder) {
    let deliveryTime;
    if (["MA", "CT", "NY"].includes(anOrder.deliveryState))
        deliveryTime = 2;
    else if (["ME", "NH"].includes(anOrder.deliveryState))
        deliveryTime = 3;
    else
        deliveryTime = 4;
    return anOrder.placedOn.plusDays(2 + deliveryTime);
}
function rushDeliveryDate(anOrder) {
    let deliveryTime;
    if (["MA", "CT"].includes(anOrder.deliveryState))
        deliveryTime = 1;
    else if (["NY", "NH"].includes(anOrder.deliveryState))
        deliveryTime = 2;
    else
        deliveryTime = 3;
    return anOrder.placedOn.plusDays(1 + deliveryTime);
}


//목적을 더 잘 드러낸다.
aShipment.deliveryDate = rushDeliveryDate(anOrder);
aShipment.deliveryDate = regularDeliveryDate(anOrder);


function regularDeliveryDate(anOrder) {
    let deliveryTime;
    if (["MA", "CT", "NY"].includes(anOrder.deliveryState))
        deliveryTime = 2;
    else if (["ME", "NH"].includes(anOrder.deliveryState))
        deliveryTime = 3;
    else
        deliveryTime = 4;
    return anOrder.placedOn.plusDays(2 + deliveryTime);
}
function rushDeliveryDate(anOrder) {
    let deliveryTime;
    if (["MA", "CT"].includes(anOrder.deliveryState))
        deliveryTime = 1;
    else if (["NY", "NH"].includes(anOrder.deliveryState))
        deliveryTime = 2;
    else
        deliveryTime = 3;
    return anOrder.placedOn.plusDays(1 + deliveryTime);
}

 

저자가 플래그 인수를 싫어하는 이유

변수가 같은 데이터가 아니라 리터럴로 설정하기 때문이다.

 

const isRush = determineIfRush(anOrder);

aShipment.deliveryDate = deliveryDate(anOrder, isRush);

 

예시:매개변수를 까다로운 방식으로 사용할



function deliveryDate(anOrder, isRush){
    let result;
    let deliveryTime;
    if(anOrder.deliveryState === "MA" || anOrder.deliveryState === "CT")
        deliveryTime = isRush ? 1 : 2;
    else if (anOrder.deliveryState === "NY" || anOrder.deliveryState === "NH"){
        deliveryTime = 2;
        if(anOrder.deliveryState === "NH" && !isRush)
            deliveryTime = 3;
    }
    else if (isRush)
        deliveryTime = 3;
    else if (anOrder.deliveryState === "ME")
        deliveryTime = 3;
    else
        deliveryTime = 4;
    result = anOrder.placeOn.plusDays(2 + deliveryTime);
    if(isRush) result = result.minusDays(1);
    return result;
}


//지나치게 까다로워 고치려면 일이 커질 것 같으면 래핑하는 함수도 고려할만 하다.
function rushDeliveryDate(anOrder) {return deliveryDate(anOrder, true);}
function regularDeliveryDate(anOrder) {return deliveryDate(anOrder, false);}
function deliveryDate(anOrder, isRush){
    let result;
    let deliveryTime;
    if(anOrder.deliveryState === "MA" || anOrder.deliveryState === "CT")
        deliveryTime = isRush ? 1 : 2;
    else if (anOrder.deliveryState === "NY" || anOrder.deliveryState === "NH"){
        deliveryTime = 2;
        if(anOrder.deliveryState === "NH" && !isRush)
            deliveryTime = 3;
    }
    else if (isRush)
        deliveryTime = 3;
    else if (anOrder.deliveryState === "ME")
        deliveryTime = 3;
    else
        deliveryTime = 4;
    result = anOrder.placeOn.plusDays(2 + deliveryTime);
    if(isRush) result = result.minusDays(1);
    return result;
}
//원래플래그 함수를 호출하지 말라는 의미에서 함수 이름 변경
function rushDeliveryDate(anOrder) {return deliveryDateHelperOnly(anOrder, true);}
function regularDeliveryDate(anOrder) {return deliveryDateHelperOnly(anOrder, false);}
function deliveryDateHelperOnly(anOrder, isRush){
    let result;
    let deliveryTime;
   
    if(["MA","CT"].includes(anOrder.deliveryState))
        deliveryTime = isRush ? 1 : 2;
    else if ( ["NY","NH"].includes(anOrder.deliveryState)){
        deliveryTime = 2;
        if(anOrder.deliveryState === "NH" && !isRush)
            deliveryTime = 3;
    }
    else if (isRush || anOrder.deliveryState === "ME")
        deliveryTime = 3;
    else
        deliveryTime = 4;
    result = anOrder.placeOn.plusDays(2 + deliveryTime);
    if(isRush) result = result.minusDays(1);
    return result;
}

 

개요

함수 로직이 거의 같고, 리터럴 값만 다르다면, 다른 부분만 매개변수로 받아 중복을 제거한다.

 

간단한 예시



//가장 간단한 예시
function tenPercentRaise(aPerson){
    aPerson.salary = aPerson.salary.multiply(1.1);
}
function fivePercentRaise(aPerson){
    aPerson.salary = aPerson.salary.multiply(1.05);
}
//함수 매개변수화
function rise(aPerson, factor){
    aPerson.salary = aPerson.salary.multiply(1+factor);
}

 

간단하지 않은 예제



function baseCharge(usage){
    if(usage < 0) return usd(0);
    const amount = bottomBand(usage) * 0.03
        + middleBand(usage) * 0.05
        + topBand(usage) * 0.07;
    return usd(amount);
}
function bottomBand(usage){
    return Math.min(usage, 100);
}
function middleBand(usage){
    return usage > 100 ? Math.min(usage,200) - 100 : 0;
}
function topBand(usage){
    return usage > 200 ? usage - 200 : 0;
}


function baseCharge(usage){
    if(usage < 0) return usd(0);
    const amount = bottomBand(usage) * 0.03
        + withinBand(usage, 100, 200) * 0.05
        + topBand(usage) * 0.07;
    return usd(amount);
}
function bottomBand(usage){
    return Math.min(usage, 100);
}
//통합할 함수를 선정할 때 보통 중간 함수가 적절
//매개변수화 하여 넣기
function withinBand(usage,bottom,top){
    return usage > bottom ? Math.min(usage,top) - bottom : 0;
}
function topBand(usage){
    return usage > 200 ? usage - 200 : 0;
}


function baseCharge(usage){
    if(usage < 0) return usd(0);
    const amount = withinBand(usage,0,100) * 0.03
        + withinBand(usage, 100, 200) * 0.05
        + topBand(usage) * 0.07;
    return usd(amount);
}
function topBand(usage){
    return usage > 200 ? usage - 200 : 0;
}


function baseCharge(usage){
    if(usage < 0) return usd(0);
    const amount = withinBand(usage,0,100) * 0.03
        + withinBand(usage, 100, 200) * 0.05
        + withinBand(usage, 200, Infinity) * 0.07;
    return usd(amount);
}

 

개요

겉보기 부수효과가 전혀 없는 함수를 추구해야 한다. 이런 함수는 옮기기도 쉽고, 아무 시점에 호출해도 항상 같은 값을 리턴한다.

 

부수효과가 있는 함수와 없는 함수는 구분해야 한다.

 

질의함수는 모두 부수효과가 없어야 한다. 규칙을 명령/질의 분리라 한다.

 

겉보기 부수효과란, 부수효과가 전혀 없는 함수와 똑같은 결과를 리턴하지만, 실제론 상태를 변경하는 것을 의미한다. 예를들어 캐싱 기능을 지원하면, 분명 호출로 상태가 변경된다. 하지만 같은 결과를 리턴하는 사실은 똑같다.

 

예시

악당을 찾는 함수



function alertForMiscreant(people){
    for(const p of people){
        if(p==="조커"){
            setOffAlarms();
            return "조커";
        }
        if(p==="사루만"){
            setOffAlarms();
            return "사루만";
        }
    }
    return "";
}


//함수 복제 후 질의 목적에 맞는 이름 짓기
function findMiscreant(people){
    for(const p of people){
        if(p==="조커"){
            setOffAlarms();
            return "조커";
        }
        if(p==="사루만"){
            setOffAlarms();
            return "사루만";
        }
    }
    return "";
}


//부수효과 제거
function findMiscreant(people){
    for(const p of people){
        if(p==="조커") return "조커";
        if(p==="사루만") return "사루만";
    }
    return "";
}


//원래 함수 호출하던 곳, 새로 만든 질의함수로 바꾸기
// const found = alertForMiscreant(people);
const found = findMiscreant(people); //질의
alertForMiscreant(people);//명령
완료


function alertForMiscreant(people){
    for(const p of people){
        if(p==="조커"){
            setOffAlarms();
            return ;
        }
        if(p==="사루만"){
            setOffAlarms();
            return ;
        }
    }
    return "";
}
function findMiscreant(people){
    for(const p of people){
        if(p==="조커") return "조커";
        if(p==="사루만") return "사루만";
    }
    return "";
}
//원래 함수 호출하던 곳, 새로 만든 질의함수로 바꾸기
// const found = alertForMiscreant(people);
const found = findMiscreant(people); //질의
alertForMiscreant(people);//명령
function alertForMiscreant(people){
    if(findMiscreant(people) !=="") setOffAlarms();
}
function findMiscreant(people){
    for(const p of people){
        if(p==="조커") return "조커";
        if(p==="사루만") return "사루만";
    }
    return "";
}
//원래 함수 호출하던 곳, 새로 만든 질의함수로 바꾸기
// const found = alertForMiscreant(people);
const found = findMiscreant(people); //질의
alertForMiscreant(people);//명령

 

 

개요

제어 플래그란 코드 동작을 변경하는 사용되는 변수

어딘가에서 값을 계산해 다른 어딘가 조건문에 쓰인다.

, 계산과 평가가 곳에서 이루어 지지지 않는다.

 

제어 플래그는 반목문(혹은 재귀) 주로 존재한다.

 

예시



function eaxmple(){
    let found = false;
    for(const p of people){
        if( ! found){
            if(p === "조커"){
                sendAlert();
                found = true;
            }
            if(p === "사루만"){
                sendAlert();
                found = true;
            }
        }
    }
}


function eaxmple(){
    checkForMiscreants(people);
}
//함수로 추출
function checkForMiscreants(people) {
    let found = false;
    for (const p of people) {
        if (!found) {
            if (p === "조커") {
                sendAlert();
                found = true;
            }
            if (p === "사루만") {
                sendAlert();
                found = true;
            }
        }
    }
}


function eaxmple(){
    checkForMiscreants(people);
}
//함수로 추출
function checkForMiscreants(people) {
    let found = false;
    for (const p of people) {
        if (!found) {
            if (p === "조커") {
                sendAlert();
                found = true;
            }
            if (p === "사루만") {
                sendAlert();
                found = true;
            }
        }
    }
}


function checkForMiscreants(people) {
    let found = false;
    for (const p of people) {
        if (!found) {
            if (p === "조커") {
                sendAlert();
                // found = true;
                return;
            }
            if (p === "사루만") {
                sendAlert();
                // found = true;
                return;
            }
        }
    }
}


function checkForMiscreants(people) {
    for (const p of people) {
        if (p === "조커") {
            sendAlert();
            return;
        }
        if (p === "사루만") {
            sendAlert();
            return;
        }
    }
}


//더 가다듬기
function checkForMiscreants(people) {
    if(people.some(p=>["조커","사루만"].includes(p))) sendAlert();
}

 

 

개요

특정 조건이 참일 때만 제대로 동작하는 코드

이런 코드는 조건이 코드에 항상 명시적으로 기술되어 있지 않다.

알고리즘을 보고 알아내야 한다. 주석도 없는 경우도 있다.

좋은 방법은 assertion 코드 추가다.

 

어셔션은 항상 참이라 가정하는 조건부 문장이다.

이셔션은 프로그램에 영향을 주지 않도록 작성돼야 한다.

 

예시

할인 관련 코드



class Customer {
    applyDiscount(aNumber){
        return (this.discountRate)
            ? aNumber - (this.discountRate * aNumber)
            : aNumber;
    }
}
//간단한 구조 개선
class Customer {
    applyDiscount(aNumber){
        if(!this.discountRate) return aNumber;
        else return aNumber - (this.discountRate * aNumber);
    }
}
//어서션 추가
class Customer {
    applyDiscount(aNumber){
        if(!this.discountRate) return aNumber;
        else {
            assert(this.discountRate>=0);
            return aNumber - (this.discountRate * aNumber);
        }
    }
}


class Customer {
    applyDiscount(aNumber){
        if(!this.discountRate) return aNumber;
        else return aNumber - (this.discountRate * aNumber);
    }
    //문제 범위를 좁히기 위해 세터에 옮겼다.
    //applyDiscount에서 통과 못했을 때 추가로 추적을 해야 하기 때문이다.
    set discountRate(aNumber){
        assert(null === aNumber || aNumber >= 0);
        this._discountRate = aNumber;
    }
}

위와 같은 어서션은 오류의 출처를 특정하기에 좋다.

 

어서션은 참이라고 '생각되는' 가정에 붙이는 아니다.

'항상' 참인 것에 붙인다.

 

 

+ Recent posts