본문 바로가기
핌팩토링

9. 데이터 조직화

by AsCE_hyunseung 2022. 4. 28.

9. 데이터 조직화

9.1 변수 쪼개기

배경

  • 역할이 둘 이상인 변수가 있다면 쪼개야 한다.
    • 여러 용도로 쓰인 변수는 코드를 읽는 이에게 커다란 혼란을 주기 때문이다.

절차, 예시

let temp = 2 * (height + width);
console.log(temp)
temp = height * width;
console.log(temp)
  1. 변수를 선언한 곳과 값을 처음 대입하는 곳에서 변수 이름을 바꾼다.
  2. let **perimeter** = 2 * (height + width); console.log(temp) temp = height * width; console.log(temp)
  3. 가능하면 이때 불변으로 선언한다.
  4. **const** perimeter = 2 * (height + width); console.log(temp) temp = height * width; console.log(temp)
  5. 이 변수에 두 번째로 값을 대입하는 곳 앞까지의 모든 참조(이 변수가 쓰인 곳)를 새로운 변수 이름으로 바꾼다.
  6. const perimeter = 2 * (height + width); console.log(**perimeter**) temp = height * width; console.log(temp)
  7. 두 번째 대입시 변수를 원래 이름으로 다시 선언한다.
  8. const perimeter = 2 * (height + width); console.log(perimeter) const **area** = height * width; console.log(temp)
  9. 반복한다. 매 반복에서 변수를 새로운 이름으로 선언하고 다음번 대입 때까지의 모든 참조를 새 변수명으로 바꾼다. 이 과정을 마지막 대입까지 반복한다.
  10. const perimeter = 2 * (height + width); console.log(perimeter) const area = height * width; console.log(**area**)

9.2 필드 이름 바꾸기

배경

  • 데이터 구조는 무슨 일이 벌어지는지를 이해하는 열쇠다.
  • 데이터 구조가 중요한 만큼 반드시 깔끔하게 관리해야 한다.
  • 개발을 진행할수록 데이터를 잘 이해하게 되고, 그 깊어진 이해를 프로그램에 반드시 반영해야한다.

절차, 예시

  • name → title로 바꾸려고 한다.
const organization = {name: "애크미 구스베리"}
  1. 레코드의 유효 범위가 제한적이라면 필드에 접근하는 모든 코드를 수정한 후 테스트한다.
  2. 레코드가 캡슐화되지 않았다면 우선 레코드를 캡슐화한다.
  3. class Organization { constructor(data) { this._name = data.name; } get name() { return this._name; } set name(aString) { this._name = aString; } } const organization = new Organization({name: "애크미 구스베리"})
  4. 캡슐화된 객체 안의 private 필드명을 변경하고, 그에 맞게 내부 메서드들을 수정한다.
  5. class Organization { constructor(data) { this.**_title** = data.**title**; } get name() { return this.**_title**; } set name(aString) { this.**_title** = aString; } }
  6. 생성자의 매개변수 중 필드와 이름이 겹치는 게 있다면 함수 선언 바꾸기로 변경한다.
  7. 접근자들의 이름도 바꿔준다.
  8. class Organization { constructor(data) { this._title = data.title; } get **title**() { return this._title; } set **title**(aString) { this._title = aString; } }

9.3파생 변수를 질의 함수로 바꾸기

배경

  • 가변 데이터는 소프트웨어에 문제를 일으키는 가장 큰 골칫거리에 속한다.
    • 그래서 유효 범위를 최대한 좁혀야한다.
  • 값을 쉽게 계산해낼 수 있는 변수들을 모두 제거할 수 있다.
    • 계산 과정을 보여주는 코드 자체가 데이터의 의미를 분명히 드러내는 경우도 있다.
    • 변경된 값을 실수로 결과 변수에 반영하지 않는 실수를 막아준다.

절차, 예시

class ProductionPlan {
    get production() {return this._production;}
    applyAdjustment(anAdjustment) {
        this._adjectments.push(anAdjustment);
        this._production += anAdjustment.amount;
    }
}
  1. 변수 값이 갱신되는 지점을 모두 찾는다. 필요하면 변수 쪼개기를 활용해 각 갱신 지점에서 변수를 분리한다.
  2. 해당 변수의 값을 계산해주는 함수를 만든다.
  3. class ProductionPlan { get production() {return this._production;} applyAdjustment(anAdjustment) { this._adjectments.push(anAdjustment); this._production += anAdjustment.amount; } **get calculatedProduction() { return this._adjustments .reduce((sum, a) => sum + a.amount, 0) }** }
  4. 변수를 읽는 코드를 모두 함수 호출로 대체한다.
    • 여기서는 메서드 인라인까지 한다
    class ProductionPlan {
        get production() {
            return **this._adjustments
                .reduce((sum, a) => sum + a.amount, 0);**
        }
    
        applyAdjustment(anAdjustment) {
            this._adjectments.push(anAdjustment);
            this._production += anAdjustment.amount;
        }
    }
    
  5. class ProductionPlan { get production() {return this.**calculatedProduction**;} applyAdjustment(anAdjustment) { this._adjectments.push(anAdjustment); this._production += anAdjustment.amount; } get calculatedProduction() { return this._adjustments .reduce((sum, a) => sum + a.amount, 0) } }
  6. 변수를 선언하고 갱신하는 코드를 죽은 코드 제거하기로 없앤다.
  7. class ProductionPlan { get production() { return **this._adjustments .reduce((sum, a) => sum + a.amount, 0);** } applyAdjustment(anAdjustment) { this._adjectments.push(anAdjustment); } }

 

9.4 참조를 값으로 바꾸기

배경

  • 필드를 값으로 다룬다면 내부 객체의 클래스를 수정하여 불변 데이터인 값 객체로 만들 수 있다.
  • 불변 데이터 값은 프로그램 외부로 건네줘도 나중에 그 값이 나 몰래 바뀌어서 내부에 영향을 줄까 염려하지 않아도 된다.
    • 그래서 값 객체는 분산 시스템과 동시성 시스템에서 유용하다.

절차, 예시

class TelephoneNumber {
    constructor() {
        this._number = ''
    }

    get number() {
        return this._number
    }

    set number(arg) {
        this._number = arg
    }
}

class Person {
    constructor() {
        this._telephoneNumber = new TelephoneNumber()
    }

    get officeNumber() {
        return this._telephoneNumber.number
    }

    set officeNumber(arg) {
        this._telephoneNumber.number = arg
    }
}
  1. 후보 클래스가 불변인지, 혹은 불변이 될 수 있는지 확인하고, 각각의 세터를 하나씩 제거한다
    • TelephoneNumber 클래스의 setter를 제거했다.
    class TelephoneNumber {
        constructor(number) {
            this._number = number;
        }
    
        get number() {
            return this._number
        }
    }
    
    class Person {
        constructor() {
            this._telephoneNumber = new TelephoneNumber()
        }
    
        get officeNumber() {
            return this._telephoneNumber.number
        }
    
        set officeNumber(arg) {
            **this._telephoneNumber = new TelephoneNumber(arg)**
        }
    }
    
  2. 이 값 객체의 핃드들을 사용하는 동치성 비교 메서드를 만든다.

9.5 값을 참조로 바꾸기

배경

  • 논리적으로 같은 데이터를 물리적으로 복제해 사용할 때 가장 크게 문제되는 상황은 그 데이터를 갱신해야할 때다.
    • 모든 복제본을 찾아서 갱신해야하기 때문이다.
    • 이 경우에는 복제된 데이터들을 모두 참조로 바꿔주는 게 좋다.
  • 값을 참조로 바꾸면 엔티티 하나당 객체도 단 하나만 존재하게 된다.
    • 각 엔티티를 표현하는 객체를 한 번만 만들고, 객체가 필요한 곳에서는 객체를 모아놓고 클라이언트들의 접근을 관리해주는 저장소로부터 얻어 쓰는 방식이 된다.

절차, 예시

class Order {
    constructor(data) {
        this._number = data.number
        this._customer = new Customer(data.customerId)
    }

    get customer() {
        return this._customer
    }
}

class Customer {
    constructor(id) {
        this._id = id
    }

    get id() {
        return this._id
    }
}
  1. 같은 부류의 속하는 객체들을 보관할 저장소를 만든다.
  2. // 저장소 객체 let _repositoryData export const initialize = () => { _repositoryData = {} _repositoryData.customer = new Map() } export const findCustomer = (id) => { return _repositoryData.customer.get(id) } export const registerCustomer = (id) => { if (!_repositoryData.customer.has(id)) { _repositoryData.customer.set(id, new Customer(id)) } return findCustomer(id) }
  3. 생성자에서 이 부류의 객체들 중 특정 객체를 정확히 찾아내는 방법이 있는지 확인한다.
  4. 호스트 객체의 생성자들을 수정하여 필요한 객체를 이 저장소에서 찾도록 한다.
  5. class Order { constructor(data) { this._number = data.number **this._customer = registerCustomer(data.customer)** } get customer() { return this._customer } }

9.6 매직 리터럴 바꾸기

  • 코드를 읽는 사람이 이 값의 의미를 모른다면 숫자 자체로는 의미를 명확히 알려주지 못하므로 매직 리터럴이다.
    • 코드 자체가 뜻을 분명하게 드러내는 게 좋다. 상수를 정의하고 숫자 대신 상수를 사용하도록 바꾸면 될 것이다.
  • 그러나 리터럴이 함수 하나에서만 쓰이고 그 함수가 맥락 정보를 충분히 제공하여 헷갈릴 일이 없다면 상수로 바꿔 얻는 이득이 줄어든다.

절차

  1. 상수를 선언하고 매직 리터럴을 대입한다.
  2. 해당 리터럴이 사용되는 곳을 모두 찾는다.
  3. 찾은 곳 각각에서 리터럴이 새 상수와 똑같은 의미로 쓰였는지 확인하여, 같은 의미라면 상수로 대체한 후 테스트한다.

예시

function potentialEnergy(mass, height) {
    return mass * 9.81 * height;
}

------------------------------------------
const STANDARD_GRAVITY = 9.81;

function potentialEnergy(mass, height) {
    return mass * STANDARD_GRAVITY * height;
}

'핌팩토링' 카테고리의 다른 글

11. API 리팩터링  (0) 2022.06.19
10. 조건문 로직 간소화  (0) 2022.04.28
8. 기능 이동  (0) 2022.04.27
7. 캡슐화  (0) 2022.04.27
6. 기본적인 리팩터링  (0) 2022.03.26