ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Refactoring] 11장 : API 리팩터링
    책/Refactoring 2판 2024. 2. 14. 11:43
    반응형

    질의 함수와 변경 함수 분리하기

    // before
    function getSomeDataAndSetAnother() {
        const result = func();
        setAnother();
    
        return result;
    }
    
    // after
    function getSomeData() {
        return func();
    }
    
    function setAnother() {
        thie.field = 0;
    }
    • 질의 함수 (읽기 함수) 는 어떠한 부수효과도 없어야 한다.
      • 명렁-질의 분리 (Command-query seperation)
    • 만약 읽기 함수 내에 무언가 다른 일을 하는 게 있다면, 바로 추출해주자.

    함수 매개변수화하기

    // before
    function addTwoTimes(number) {
        return number + number;
    }
    
    function addThreeTimes(number) {
        return number + number + number;
    }
    
    // after
    function addForCounts(number, count) {
        return number * count;
    }
    • 두 함수의 로직이 아주 비슷하고 리터럴 값만 다를 경우, 그 리터럴 값을 매개변수로 받아 처리하는 함수 하나로 합쳐 중복을 없앰

    플래그 인수 제거하기

    // before
    function setDimention(name, value) {
        if (name === "height") {
            this.height = value;
            return;
        }
        if (name === "width") {
            this.width = value;
            return;
        }
    }
    
    // after
    function setHeight(value) {}
    function setWidth(value) {}
    • 플래그 인수 : 호출되는 함수가 실행할 로직을 호출하는 쪽에서 선택하기 위해 전달하는 인수
    • 플래그 인수는 함수 시그니처만 보고도 내부 동작을 알기 쉽지 않다.
      • 만약 boolean 값일 경우, true, false로는 어떤 동작이 되는지 매우 어려울 것이다.
    • 이러한 플래그 인수는 제거해주고, 각기 다른 함수로 분리해주는 것이 함수가 하나의 기능만 하게 만들 수 있다.
      • 만약 플래그 인수가 너무 많은 분기문으로 사용된다면, 이미 함수 하나가 너무 많은 역할을 하는 것이므로 더 간단한 로직을 구성해야 한다 → 각기 다른 함수로 빼기에는 너무 많을 수 있기 때문

    객체 통째로 넘기기

    // before
    const low = room.daysTempRange.low;
    const high = room.daysTempRange.high;
    if (plan.withinRange(low, high))
    
    // after
    if (plan.withinRange(room.daysTempRange))
    • 어떤 객체에서 값을 뽑아 매개변수로 주는 형태라면, 그냥 그 객체 자체를 주어 매개변수의 수를 줄일 수 있다.
    • 하지만 이 리팩터링의 경우 해당 함수가 해당 레코드에 종속되는 결과를 가져올 수도 있다. → 레코드나 객체의 필드가 바뀌면 동작이 안되는 경우
      • 이는 해당 함수가 애초에 해당 객체의 메소드로 들어갔어야 한다는 의미일 수도 있다.

    매개변수를 질의 함수로 바꾸기

    // before
    someFunction(object, object.field);
    
    function someFunction(object, field) {}
    
    // after
    someFunction(object);
    
    function someFunction(object) {
        const field = object.field;
    }
    • 매개변수 목록은 중복을 피하는 게 좋으며 짧을수록 좋다.
    • 호출되는 함수 안에서 쉽게 결정할 수 있는 값을 매개변수로 받는 것 또한 중복이라고 볼 수 있다.
    • 이러한 부분이 존재한다면 해당 매개변수를 제거하고 피호출 함수 내에서 뽑아 쓰자.

    질의 함수를 매개변수로 바꾸기

    // before
    someFunction(object);
    
    function someFunction(object) {
        const field = object.field;
    }
    
    // after
    someFunction(object, object.field);
    
    function someFunction(object, field) {}
    • 위와 반대 경우로, 더 이상 해당 객체에 의존적이지 않게 함수가 수정되는 경우 해당 참조를 매개변수로 바꿔 풀어낼 수 있다.

    세터 제거하기

    // before
    class Person {
        get name() {}
        set name(string) {}
    }
    
    // after
    class Person {
        get name() {}
    }
    • 불변으로 만들고 싶은 객체의 경우 세터가 있어서는 안되고, 상수 필드를 가지며 생성자로 값을 초기화한 후로는 변경되지 않아야 한다.
    • 따라서 불변 객체라는 의미를 확실히 주기 위해서는 세터가 제거되어야 한다.
    • 세터로 받는 필드를 하나씩 생성자에 추가해주고, 세터를 하나씩 제거하면서 리팩토링할 수 있다.

    생성자를 팩터리 함수로 바꾸기

    // before
    leadEngineer = new Employee(document.leadEngineer, 'E');
    
    // after
    leadEngineer = createEngineer(document.leadEngineer);
    • 자바 생성자의 경우 반드시 그 생성자를 정의한 클래스의 인스턴스를 반환해야하며, 서브클래스나 프록시를 반환할 수 없다.
    • 팩터리 함수의 경우 이러한 제약이 없다. 또한 이름을 부여할 수 있어 뭔가 다른 객체를 반환할 때 그 의도 또한 명확히 해줄 수 있다.

    함수를 명령으로 바꾸기

    // before
    function score(candidate, medicalExam, scoringGuide) {
        let result = 0;
        let healthLevel = 0;
        // 긴 코드 생략
    }
    
    // after
    class Scorer {
        constructor(candidate, medicalExam, scoringGuide) {
            this.candidate = candidate;
            this.medicalExam = medicalExam;
            this.scoringGuide = scoringGuide;
        }
    
        execute() {
            this.result = 0;
            this.healthLevel = 0;
            // 긴 코드 생략
        }
    }
    • 명령(Command), 명령 객체 : 어떠한 함수만을 위한 객체를 만들어 메소드 하나로 요청해 실행하는 것
    • 이는 함수를 객체로 만들어 표현한다는 점에서 주목할 만하다.
      • undo와 같은 보조 연산 제공이 가능하고, 상속, 훅을 사용해 사용자 맞춤형으로 만들 수도 있다 (뭔소리지..)
    • 디자인 패턴의 명령 패턴과 같다

    명령을 함수로 바꾸기

    // before
    class ChargeCalculator {
        constructor(customer, usage) {
            this.customer = customer;
            this.usage = usage;
        }
    
        execute() {
            return this.customer.rate * this.usage;
        }
    }
    
    // after
    function charge(customer, usage) {
        return customer.rate * usage;
    }
    • 명령은 복잡한 연산을 여러 메서드로 쪼개 필드를 이용해 쪼개진 메서드끼리 정보 공유를 하는 역할이 크다.
    • 달리 말하면, 너무 단순한 작업의 경우는 굳이 명령으로 만들 필요가 없다는 얘기이다.
    • 객체를 생성하는 것도 그렇고 메모리를 무시할 수 없으니 이러한 경우에는 명령을 함수로 바꿔주자.

    수정된 값 반환하기

    // before
    let total = 0;
    addAll();
    
    function addAll() {
        for (const p of this.point) {
            total += p;
        }
    }
    
    // after
    const total = addAll();
    
    function addAll() {
        let result = 0;
        for (const p of this.point) {
            result += p;
        }
    
        return result;
    }
    • 함수가 외부 변수를 통해 결과를 바꾸고 아무것도 리턴하지 않는 형태는 나중에 이해하기 어렵다.
    • 함수는 웬만하면 외부 변수의 상태를 변경하는 형태가 아닌 내부에서 값을 계산해서 리턴하고, 외부 변수가 이 값을 받아 저장하는 형태가 가독성 및 유지보수성이 좋다.

    오류 코드를 예외로 바꾸기

    // before
    if (data) {
        return new Object(data);
    }
    else {
        return -1;
    }
    
    // after
    if (data) {
        return new Object(data);
    }
    else {
        throw new Exception(-1);
    }
    • 오류 코드를 사용할 경우 이 함수를 사용한 부모 함수에서 해당 값을 검증하는 로직이 반드시 들어가야 한다.
    • 그러나 예외를 사용하면 이러한 부분을 신경쓰지 않아도 되고, 예외 클래스나 메시지를 통해 더 명확하게 디버깅이 가능하다.
    • 하지만 남발하면 안되고, 정확히 예상 밖의 동작일 때만 사용해야 한다.
      • 예외 대신에 프로그램 종료 코드로 바꾸고, 이게 정상 동작할지를 생각한다. → 만약 정상 동작하지 않을 것 같다면 예외가 아닌 오류를 검출하여 정상 흐름으로 되돌리게 해야함(무슨 말인지 정확하게는 모르겠다)

    예외를 사전확인으로 바꾸기

    // before
    function getNumber(index) {
        try {
            return values[index]; 
        }
        catch (ArrayIndexOutOfBoundsException) {
            return 0;
        }
    }
    
    // after
    function getNumber(index) {
        if (index < 0) {
            return 0;
        }
    
        return values[index];
    }
    • 앞서 말했듯이, 예외는 과용되어서는 안된다.
    • 문제가 될 수 있는 조건을 함수 호출 전에 검사할 수 있다면, 예외를 던지는 대신 호출하는 곳에서 조건을 검사하도록 하는 것도 좋다.

     

    https://product.kyobobook.co.kr/detail/S000001810241

    반응형
Designed by Tistory.