개요

매직 리터럴

소스 코드에 여러 곳에 등장하는 일반적인 리터럴

 

자주 등장하지만 의미없는 값을 상수로 선언하여 이름을 부여하는 것이 이롭다.

 

예시

function potetialEnergy(mass, height){
    return mass * 9.81 * height;
}
const STANDARD_GRAVITY = 9.81;
function potetialEnergy(mass, height){
    return mass * STANDARD_GRAVITY * height;
}

 

개요

데이터 구조를 값으로 다루면, 복사된 값을 사용하게 된다. 일반적으로 권장되는 사용 방식이다.

데이터 구조를 참조로 다루면, 같은 데이터를 여러 곳에서 공유하게 된다.

 

보통은 데이터 구조를 값으로 다루는 것을 권장하나, 값을 갱신해야하는 경우는 예외다. 값으로 다룬다면, 복사본을 모두 찾아 수정해줘야한다.

보통 이런 경우 하나의 객체만 존재하게 된다. 이를 관리하는 저장소를 만들어 관리하는 좋다.

이렇게 되면 저장소를 통해서만 객체에 접근이 가능하다.

예시

class Order {
    constructor(data){
        this._number = data.number;
        this._customer = new Customer(data.customer);
        //여러 데이터 data.customer == id
    }
    get customer(){return this._customer;}
}
//불변 객체다.
class Customer{
    constructor(id){
        this._id = id;
    }
    get id(){return this._id;}
}
불변 객체라 같은 고객의 주문이 들어올때 마다 고객 객체가 늘어난다.
만약 고객 데이터가 변경된다면 복사본 모두를 변경해야 한다.


//물리적으로 똑같은 객체를 저장할 곳
let _repositoryData;//저장소 객체
export function initialize(){
    _repositoryData = {};
    _repositoryData.customers = new Map();
}
export function registerCustomer(id){
    if(!_repositoryData.customers.has(id)){
        _repositoryData.customers.set(id, new Customer(id));
    }
    return findCustomer(id);
}
export function findCustomer(id){
    return _repositoryData.customers.get(id);
}
프로젝트에 쓸만한 저장소가 존재한다면 그것을 사용해도 무방하다.
아래는 노드 모듈로 변환


import {findCustomer, initialize,registerCustomer,_repositoryData} from './repository.mjs';
export class Order {
    constructor(data){
        this._number = data.number;
        this._customer = new Customer(data.customer);
        //여러 데이터...
    }
    get customer(){return this._customer;}
}
//불변 객체다.
export class Customer{
    constructor(id){
        this._id = id;
    }
    get id(){return this._id;}
}
initialize();
registerCustomer("100");
registerCustomer("200");
registerCustomer("100");
console.log(findCustomer("100"));
console.log(_repositoryData);
////////////////////////////////////////////////////
import {Order, Customer} from './ex1.mjs'
//물리적으로 똑같은 객체를 저장할 곳
export let _repositoryData;//저장소 객체
export function initialize(){
    _repositoryData = {};
    _repositoryData.customers = new Map();
}
export function registerCustomer(id){
    if(!_repositoryData.customers.has(id)){
        _repositoryData.customers.set(id, new Customer(id));
    }
    return findCustomer(id);
}
export function findCustomer(id){
    return _repositoryData.customers.get(id);
}



완료


/* ex1.mjs */
import {findCustomer, initialize,registerCustomer,_repositoryData} from './repository.mjs';
export class Order {
    constructor(data){
        this._number = data.number;
        this._customer = registerCustomer(data.customer);
        //여러 데이터...
    }
    get customer(){return this._customer;}
}
//불변 객체다.
export class Customer{
    constructor(id){
        this._id = id;
    }
    get id(){return this._id;}
    set id(arg){this._id = arg;}
}
initialize();


const customer1 = new Customer("10");
const orders = [];
orders.push(new Order({number:1 , customer:"10"}));
orders.push(new Order({number:2 , customer:"10"}));
orders.push(new Order({number:3 , customer:"10"}));
orders.push(new Order({number:4 , customer:"10"}));
console.log(_repositoryData);
orders.forEach(o=>console.log(o));
findCustomer("10").id = "20";
orders.forEach(o=>console.log(o));


/*repository.mjs */
import {Order, Customer} from './ex1.mjs'
//물리적으로 똑같은 객체를 저장할 곳
export let _repositoryData;//저장소 객체
export function initialize(){
    _repositoryData = {};
    _repositoryData.customers = new Map();
}
export function registerCustomer(id){
    if(!_repositoryData.customers.has(id)){
        _repositoryData.customers.set(id, new Customer(id));
    }
    return findCustomer(id);
}
export function findCustomer(id){
    return _repositoryData.customers.get(id);
}



 

고객 정보를 갱신하면 같은 고객을 공유하는 모든 주문에서 갱신된다.

다만, 방법에서 저장소가 전역 객체다. 전역 객체가 꺼림직 하다면 생성자 주입으로 저장소 객체를 주입하는 것도 방법이다.

 

 

개요

객체를 다른 객체에 중첩하면 내부 객체를 참조 혹은 값으로 취급할 있다.

참조, 차이는 다음과 같다.

참조로 다루는 경우는 내부 객체는 그대로 객체 속성만 갱신한다.

값으로 다루는 겨우는 새로운 속성을 담은 객체로 기존 객체를 대체한다.

 

필드를 값으로 다루고 싶다면 내부 객체의 클래스를 수정하여 객체로 만들면 된다.

객체는 불변이기에 자유롭게 활용할 있다.

불변이라면 외부로 값을 내줘도 몰래 값이 변경되어 내부에 영향 받을 걱정이 없다.

특징은 특히 분산 시스템과 동시성 시스템에서 유용하다.

 

예시



//아직 전화번호가 올바로 설정되지 못하게 짜여 있다고 가정
class Person{
    constructor(){
        this._telephoneNumber = new TelephoneNumber();
    }
    get officeAreaCode(){return this._telephoneNumber.areaCode;}
    set officeAreaCode(arg){this._telephoneNumber.areaCode = arg;}
    get officeNumber(){return this._telephoneNumber.officeNumber;}
    set officeNumber(arg){this._telephoneNumber.officeNumber = arg;}
}
class TelephoneNumber{
    get areaCode(){return this._areaCode;}
    set areaCode(arg){this._areaCode = arg;}
    get number(){return this._number;}
    set number(arg){this._number = arg;}
}


class TelephoneNumber{
    //불변으로 만들기 위해 세터 제거 후 생성자로만 받는다.
    constructor(areaCode, number){
        this._areaCode = areaCode;
        this._number = number;
    }
    get areaCode(){return this._areaCode;}
    get number(){return this._number;}
}


class Person{
    constructor(areaCode,officeNumber){
        this._telephoneNumber = new TelephoneNumber();
    }
    get officeAreaCode(){return this._telephoneNumber.areaCode;}
    //값을 설정할 때 마다 매번 새로운 객체를 리턴한다.
    set officeAreaCode(arg){this._telephoneNumber = new TelephoneNumber(arg, this.officeNumber);}
    get officeNumber(){return this._telephoneNumber.officeNumber;}
    set officeNumber(arg){this._telephoneNumber = new TelephoneNumber(this.officeAreaCode,arg);}
}
이제 전화번호 클래스는 불변 클래스이다.
이제 논리적 동치성을 위해 기반 비교를 해야한다.
자바스크립트는 참조 기반 동치성을 기반 동치성으로 대체하는 메서드가 없다.
그냥 equals()함수를 직접 만든다.


class TelephoneNumber{
    constructor(areaCode,officeNumber){
        this._areaCode = areaCode;
        this._officeNumber = officeNumber;
    }
    get areaCode(){return this._areaCode;}
    set areaCode(arg){this._areaCode = arg;}
    get number(){return this._number;}
    set number(arg){this._number = arg;}
    //논리적 동치성 검사 메서드
    equals(other){
        if(!(other instanceof TelephoneNumber)) return false;
        return Object.keys(this).every(a=>this[a] === other[a]);
    }
}


class TelephoneNumber{
    constructor(areaCode, number){
        this._areaCode = areaCode;
        this._number = number;
    }
    get areaCode(){return this._areaCode;}
    get number(){return this._number;}
    //논리적 동치성 검사 메서드
    equals(other){
        if(!(other instanceof TelephoneNumber)) return false;
        return Object.keys(this).every(a=>this[a] === other[a]);
    }
}


class Person{
    constructor(){
        this._telephoneNumber = new TelephoneNumber();
    }
    get officeAreaCode(){return this._telephoneNumber.areaCode;}
    set officeAreaCode(arg){this._telephoneNumber = new TelephoneNumber(areaCode, this.officeNumber);}
    get officeNumber(){return this._telephoneNumber.officeNumber;}
    set officeNumber(arg){this._telephoneNumber = new TelephoneNumber(this.officeAreaCode, arg);}
}
class TelephoneNumber{
    constructor(areaCode, number){
        this._areaCode = areaCode;
        this._number = number;
    }
    get areaCode(){return this._areaCode;}
    get number(){return this._number;}
    //논리적 동치성 검사 메서드
    equals(other){
        if(!(other instanceof TelephoneNumber)) return false;
        return Object.keys(this).every(a=>this[a] === other[a]);
    }
}
const t1 = new TelephoneNumber("12345","67890");
const t2 = new TelephoneNumber("12345","67890");
console.log(t1.equals(t2));

 

 

 

개요

가변 데이터는 소프트웨어 문제를 일으키기 쉽다.

 

값을 쉽게 계산해낼 있는 변수들을 모두 제거한다.

계산 과정을 보여주는 코드 자체가 데이터의 의미를 분명히 드러내는 경우도 많다.

 

예외사항, 피연산자가 불변이라면 계산 결과도 일정하므로 그대로 두는 것도 좋다.

 

변형연산

데이터 구조를 감싸 데이터에 기초하여 계산한 결과를 속성으로 제공하는 객체

데이터 구조를 받아 다른 데이터 구조로 변환해 반환하는 함수

 

예시

class ProductionPlan{
    get production() {return this._production;}
    applyAdjustment(anAdjustment){
        this._adjustments.push(anAdjustment);
        this._production += anAdjustment.amount;
    }
}


class ProductionPlan{
    get production() {
        assert(this._production === this.calculatedProduction);
        return this._production;
    }
    get calculatedProduction(){
        return this._adjustments
            .reduce((sum, a)=>sum+a.amount,0);
    }
    applyAdjustment(anAdjustment){
        this._adjustments.push(anAdjustment);
        this._production += anAdjustment.amount;
    }
}


class ProductionPlan{
    get production() {
        //어셔선 실패하지 않으면 변경
        return this.calculatedProduction;
    }
    get calculatedProduction(){
        return this._adjustments
            .reduce((sum, a)=>sum+a.amount,0);
    }
    applyAdjustment(anAdjustment){
        this._adjustments.push(anAdjustment);
        this._production += anAdjustment.amount;
    }
}


class ProductionPlan{
    get production() {
        //인라인
        return this._adjustments
            .reduce((sum, a)=>sum+a.amount,0);
    }
    applyAdjustment(anAdjustment){
        this._adjustments.push(anAdjustment);
        this._production += anAdjustment.amount;
    }
}


class ProductionPlan{
    get production() {
        return this._adjustments
            .reduce((sum, a)=>sum+a.amount,0);
    }
    applyAdjustment(anAdjustment){
        this._adjustments.push(anAdjustment);
    }
}

예시: 소스가 이상일

예시는 변경하려는 값에 영향을 주는 요소가 하나뿐이였다.

예시는 이상일



class ProductionPlan {
    constructor(production){
        this._production = production;
        this._adjustments = [];
    }
    get production(){return this._production;}
    applyAdjustment(anAdjustment){
        this._adjustments.push(anAdjustment);
        this._production += anAdjustment.amout;
    }
}


class ProductionPlan {
    constructor(production){
        this._initialProduction = production;
        this._productionAccumulator = 0;
        this._adjustments = [];
    }
    get production(){return this._initialProduction + this._productionAccumulator;}
    applyAdjustment(anAdjustment){
        this._adjustments.push(anAdjustment);
        this._production += anAdjustment.amout;
    }
}


class ProductionPlan {
    constructor(production){
        this._initialProduction = production;
        this._productionAccumulator = 0;
        this._adjustments = [];
    }
    get production(){
        assert(this._productionAccumulator === this.calculatedProductionAccumulator);
        return this._initialProduction + this._productionAccumulator;
    }
    get calculatedProductionAccumulator(){
        return this._adjustments
        .reduce((sum, a)=>sum+a.amount,0);
    }
    applyAdjustment(anAdjustment){
        this._adjustments.push(anAdjustment);
        this._production += anAdjustment.amout;
    }
}

 

개요

가변 데이터는 소프트웨어 문제를 일으키기 쉽다.

 

값을 쉽게 계산해낼 있는 변수들을 모두 제거한다.

계산 과정을 보여주는 코드 자체가 데이터의 의미를 분명히 드러내는 경우도 많다.

 

예외사항, 피연산자가 불변이라면 계산 결과도 일정하므로 그대로 두는 것도 좋다.

 

변형연산

데이터 구조를 감싸 데이터에 기초하여 계산한 결과를 속성으로 제공하는 객체

데이터 구조를 받아 다른 데이터 구조로 변환해 반환하는 함수

 

예시

class ProductionPlan{
    get production() {return this._production;}
    applyAdjustment(anAdjustment){
        this._adjustments.push(anAdjustment);
        this._production += anAdjustment.amount;
    }
}


class ProductionPlan{
    get production() {
        assert(this._production === this.calculatedProduction);
        return this._production;
    }
    get calculatedProduction(){
        return this._adjustments
            .reduce((sum, a)=>sum+a.amount,0);
    }
    applyAdjustment(anAdjustment){
        this._adjustments.push(anAdjustment);
        this._production += anAdjustment.amount;
    }
}


class ProductionPlan{
    get production() {
        //어셔선 실패하지 않으면 변경
        return this.calculatedProduction;
    }
    get calculatedProduction(){
        return this._adjustments
            .reduce((sum, a)=>sum+a.amount,0);
    }
    applyAdjustment(anAdjustment){
        this._adjustments.push(anAdjustment);
        this._production += anAdjustment.amount;
    }
}


class ProductionPlan{
    get production() {
        //인라인
        return this._adjustments
            .reduce((sum, a)=>sum+a.amount,0);
    }
    applyAdjustment(anAdjustment){
        this._adjustments.push(anAdjustment);
        this._production += anAdjustment.amount;
    }
}


class ProductionPlan{
    get production() {
        return this._adjustments
            .reduce((sum, a)=>sum+a.amount,0);
    }
    applyAdjustment(anAdjustment){
        this._adjustments.push(anAdjustment);
    }
}

 

 

 

예시: 소스가 이상일

예시는 변경하려는 값에 영향을 주는 요소가 하나뿐이였다.

예시는 이상일



class ProductionPlan {
    constructor(production){
        this._production = production;
        this._adjustments = [];
    }
    get production(){return this._production;}
    applyAdjustment(anAdjustment){
        this._adjustments.push(anAdjustment);
        this._production += anAdjustment.amout;
    }
}


class ProductionPlan {
    constructor(production){
        this._initialProduction = production;
        this._productionAccumulator = 0;
        this._adjustments = [];
    }
    get production(){return this._initialProduction + this._productionAccumulator;}
    applyAdjustment(anAdjustment){
        this._adjustments.push(anAdjustment);
        this._production += anAdjustment.amout;
    }
}


class ProductionPlan {
    constructor(production){
        this._initialProduction = production;
        this._productionAccumulator = 0;
        this._adjustments = [];
    }
    get production(){
        assert(this._productionAccumulator === this.calculatedProductionAccumulator);
        return this._initialProduction + this._productionAccumulator;
    }
    get calculatedProductionAccumulator(){
        return this._adjustments
        .reduce((sum, a)=>sum+a.amount,0);
    }
    applyAdjustment(anAdjustment){
        this._adjustments.push(anAdjustment);
        this._production += anAdjustment.amout;
    }
}

 

 

배경

데이터 구조는 프로그램을 이해하는 역할을 한다.

데이터 구조는 깔끔하게 관리해야 한다. 개발을 이어갈 수록 데이터를 이해하게 것이다. 깊어진 이해를 프로그램에 반드시 반영해야 한다.

예시

const organization = {name: "애크미 구스베리",country:"GB"};
상수에 name title 변경해야 한다. 객체는 코드베이스 곳곳에서 사용된다.


const organization = {name: "애크미 구스베리",country:"GB"};
//organization 레코드 클래스로 캡슐화
class Organization{
    constructor(data){
        this._name = data.name;
        this._country = data.country;
    }
    get name(){return this._name;}
    set name(arg){ this._name = arg;}
    get country(){return this._country;}
    set country(arg){ this._country = arg;}
}
레코드를 클래스 캡슐화한 덕에 변경을 작은 단계로 나눠할 있게 됐다.
작은 단계라 함은 잘못될 일도 작아진다는 말과 같다.


const organization = {name: "애크미 구스베리",country:"GB"};
class Organization{
    constructor(data){
        //생성자와 접근자를 구분하여 점진적으로 변경
        this._title = data.name;
        this._country = data.country;
    }
    get name(){return this._title;}
    set name(arg){ this._title = arg;}
    get country(){return this._country;}
    set country(arg){ this._country = arg;}
}


const organization = {name: "애크미 구스베리",country:"GB"};
class Organization{
    constructor(data){
        //생성자 조정
        this._title = (data.title !== undefined) ? data.title : data.name;
        this._country = data.country;
    }
    get name(){return this._title;}
    set name(arg){ this._title = arg;}
    get country(){return this._country;}
    set country(arg){ this._country = arg;}
}


//호출하는 곳 수정
const organization = new Organization({title: "애크미 구스베리",country:"GB"});


class Organization{
    constructor(data){
        this._title =  data.title;
        this._country = data.country;
    }
    get name(){return this._title;}
    set name(arg){ this._title = arg;}
    get country(){return this._country;}
    set country(arg){ this._country = arg;}
}


const organization = new Organization({title: "애크미 구스베리",country:"GB"});
class Organization{
    constructor(data){
        this._title =  data.title;
        this._country = data.country;
    }
    get title(){return this._title;}
    set title(arg){ this._title = arg;}
    get country(){return this._country;}
    set country(arg){ this._country = arg;}
}

지금 과정은 데이터 구조가 여러 곳에서 참조되고 있을 리팩터링 과정이다.

만약 참조되는 적거나 곳이라면 캡슐화도 필요없었을 것이다.

복잡할 수록 이처럼 작게 나눠서 정복해야한다.

 

자바스크립트가 아닌 데이터 구조를 불변으로 만들 있는 언어는 캡슐화 대신 데이터 구조를 값을 복제해 새로운 이름으로 선언 복제한 값을 사용하게 바꾸는 식으로 진행한다.

 

개요

반복문 루프 변수, 수집 변수 같이 값을 여러 대입할 밖에 없는 경우를 제외하고, 변수는 번만 값을 대입해야 한다.

 

대입이 이상 이뤄진다면 여러 가지 역할을 수행한다는 신호다.

 

예시



function distanceTravelled(scenario, time){
    let result;
    let acc = scenario.primaryForce / scenario.mass;//가속도=/질량
    let primaryTime = Math.min(time, scenario.delay);
    result = 0.5 * acc * primaryTime * primaryTime; // 전파된 거리
    let secondaryTime = time - scenario.delay;
    if(secondaryTime > 0){
        let primaryVelocity = acc * scenario.delay;
        acc = (scenario.primaryForce + scenario.secondaryForce) /scenario.mass;
        result += primaryVelocity * secondaryTime + 0.5 * acc * secondaryTime *secondaryTime;
    }
    return result;
}
acc 변수가 가지 역할을 하고 있다.
하나는 번째 힘이 유발한 초기 가속도 저장
다른 하나는 번째 힘까지 반영된 가속도 저장


function distanceTravelled(scenario, time){
    let result;
    //불변으로 정의
    const primaryAcceleration = scenario.primaryForce / scenario.mass;
    let primaryTime = Math.min(time, scenario.delay);
    result = 0.5 * primaryAcceleration * primaryTime * primaryTime;
    let secondaryTime = time - scenario.delay;
    if(secondaryTime > 0){
        let primaryVelocity = primaryAcceleration * scenario.delay;
        //별도 변수
        let acc = (scenario.primaryForce + scenario.secondaryForce) /scenario.mass;
        result += primaryVelocity * secondaryTime + 0.5 * acc * secondaryTime *secondaryTime;
    }
    return result;
}


function distanceTravelled(scenario, time){
    let result;
    const primaryAcceleration = scenario.primaryForce / scenario.mass;
    let primaryTime = Math.min(time, scenario.delay);
    result = 0.5 * primaryAcceleration * primaryTime * primaryTime;
    let secondaryTime = time - scenario.delay;
    if(secondaryTime > 0){
        let primaryVelocity = primaryAcceleration * scenario.delay;
        //불변으로 정의
        const secondaryAcceleration = (scenario.primaryForce + scenario.secondaryForce) /scenario.mass;
        result += primaryVelocity * secondaryTime + 0.5 * secondaryAcceleration * secondaryTime *secondaryTime;
    }
    return result;
}

 

예시: 입력 매개변수의 값을 수정할



function discount(inputValue, quantity){
    if(inputValue > 50) inputValue = inputValue - 2;
    if(quantity > 100) inputValue = inputValue - 1;
    return inputValue;
}


function discount(originalInputValue, quantity){
    //call by value 로 가정, 값을 복사해서 쓴다.
    let inputValue = originalInputValue;
    if(inputValue > 50) inputValue = inputValue - 2;
    if(quantity > 100) inputValue = inputValue - 1;
    return inputValue;
}


function discount(inputValue, quantity){
    //적절한 이름으로 변경
    let result = inputValue;
    if(inputValue > 50) result = result - 2;
    if(quantity > 100) result = result - 1;
    return result;
}

 

개요

소프트웨어 가격은 코드량에 비례하지 않는다.

사용하지 않는 코드가 있다고 시스템이 느려지는 것도 아니고, 런타임 메모리도 많이 잡아먹지 않는다.

최신 컴파일러들은 이런 코드를 알아서 제거해준다.

 

하지만 사용되지 않는 코드는 소프트웨어 동작을 이해하는 개발자에게 걸림돌이 있다.

이런 코드들이 스스로 나는 필요없지만 혹시해서 남겨놨어요 라고 알려주지 않기 때문이다.

이런 코드는 과감히 지워라. 현재 프로젝트들은 모두 버전 관리 시스템에 의해 관리되므로 정말 필요한 날이 올때를 위해 제거 커밋에 표기만 해두면 된다. 사실 심지어 커밋 메시지에 제거한 것을 따로 남겨놓지 않아서 후회한 적도 거의 없을 것이다.

 

예전 버전 관리 시스템이 없었을 적에 코드를 주석해서 사용했다.

개요

반복문 하나에 가지 일을 수행하는 모습을 자주보게 된다.

이렇게 되면 변경이 가해질 가지를 모두 고려해야 한다.

 

반면에 각각으로 분리되어 있다면, 수정할 곳만 확인하면 된다.

 

예시



function example(){
    let youngest = people[0] ? people[0].age : Infinity;
    let totalSalary = 0;
    for(const p of people){
        if(p.age<youngest) youngest = p.age;
        totalSalary += p.salary;
    }
    return `최연소:${youngest}, 총 급여: ${totalSalary}`;
}
하나의 반복문에 서로 다른 가지를 계산하고 있다.


function example(){
    let youngest = people[0] ? people[0].age : Infinity;
    let totalSalary = 0;
    for(const p of people){
        if(p.age<youngest) youngest = p.age;
        totalSalary += p.salary;
    }
    //먼저 단순한 복사
    for(const p of people){
        if(p.age<youngest) youngest = p.age;
        totalSalary += p.salary;
    }
    return `최연소:${youngest}, 총 급여: ${totalSalary}`;
}


function example(){
    let youngest = people[0] ? people[0].age : Infinity;
    let totalSalary = 0;
    //부수효과가 있는 코드는 한쪽만 남기고 제거
    for(const p of people) totalSalary += p.salary;
    for(const p of people) if(p.age<youngest) youngest = p.age;
   
    return `최연소:${youngest}, 총 급여: ${totalSalary}`;
}

 

가다듬기

반복문 쪼개기는 여기서 끝이다. 하지만 반복문 쪼개기의 진가는 리팩터링 자체가 아닌 다음 단계로 가는 디딤돌 역할에 있다.

function example(){
    let totalSalary = 0;
    for(const p of people) totalSalary += p.salary;
    //더 나아가 각 반복문을 각 함수로 만들기, 먼저 근처로 문장 슬라이드
    let youngest = people[0] ? people[0].age : Infinity;
    for(const p of people) if(p.age<youngest) youngest = p.age;
    return `최연소:${youngest}, 총 급여: ${totalSalary}`;
}


function example(){
    return `최연소:${youngestAge()}, 총 급여: ${totalSalary()}`;
    //각각 함수로 추출
    function youngestAge() {
        let youngest = people[0] ? people[0].age : Infinity;
        for (const p of people)
            if (p.age < youngest)
                youngest = p.age;
        return youngest;
    }
    function totalSalary() {
        let totalSalary = 0;
        for (const p of people)
            totalSalary += p.salary;
        return totalSalary;
    }
}


function example(){
    return `최연소:${youngestAge()}, 총 급여: ${totalSalary()}`;
    //알고리즘 교체하기
    function youngestAge() {
        return people.map(p=>p.age).reduce((a1,a2)=>a1<a2?a1:a2, Infinity);
    }
    //파이프라인으로 바꾸기
    function totalSalary() {
        return people.reduce((total, p)=> total+ p.salary,0);;
    }
}

 

개요

관련된 코드들이 가까이 뭉쳐 있으면 이해하기 쉽다.

관련 코드끼리 모으는 작업은 다른 리팩터링의 준비 단계로 자주 행해진다.

취향 차이지만 함수 모든 변수를 머리에 모으는 사람도 있는데 사용하는 근처에 선언하는 것도 나쁘진 않다.

 

예시

코드 슬라이드 가지를 고려

무엇을 슬라이드할지, 슬라이드 가능한지

무엇을 슬라이드할지는 맥락과 관련 있다.

 

const pricingPlan = retrievePricingPlan();
const order = retreiveOrder();
const baseCharge = pricingPlan.base;
let charge;
const chargePerUnit = pricingPlan.unit;
const units = order.units;
let discount;
charge = baseCharge + units * chargePerUnit;
let discountableUnits = Math.max(units - princingPlan * discountThreshold, 0);
discount = discountableUnits * pricingPlan.discountFactor;
if(order.isRepeat) discount += 20;
charge = charge - discount;
chargeOrder(charge);


//사이드 이펙트가 없는 코드끼리는 마음대로 재배치 할 수 있다.
//부수효과가 없는 프로그래밍을 하는 이유
const pricingPlan = retrievePricingPlan();
const baseCharge = pricingPlan.base;
let charge;
const chargePerUnit = pricingPlan.unit;
const order = retreiveOrder(); // 바로 근처로 옮김
const units = order.units;
charge = baseCharge + units * chargePerUnit;
let discountableUnits = Math.max(units - princingPlan * discountThreshold, 0);
let discount; //바로 근처로 옮김
discount = discountableUnits * pricingPlan.discountFactor;
if(order.isRepeat) discount += 20;
charge = charge - discount;
chargeOrder(charge);
retrieveOrder() 내부를 확인해야 부수효과가 있는지 확인할 있는데 여기선 자기가 작성한 함수고 명령-질의 분리 원칙을 지키고 있다고 가정하고 있다.
만약 남이 작성한 코드라면 살펴봐야한다.


const pricingPlan = retrievePricingPlan();
const baseCharge = pricingPlan.base;
let charge;
const chargePerUnit = pricingPlan.unit;
const order = retreiveOrder();
const units = order.units;
charge = baseCharge + units * chargePerUnit;
let discountableUnits = Math.max(units - princingPlan * discountThreshold, 0);
let discount;
discount = discountableUnits * pricingPlan.discountFactor;
if(order.isRepeat) discount += 20; //이 줄은 맨끝으로 슬라이드하고 싶어도 참조하는 라인이 있어 막힌다.
charge = charge - discount;
chargeOrder(charge);

 

 

예시:조건문이 있을 때의 슬라이드

 



function example(){
    let result;
    if(availableResources.length === 0){
        result = createResource();
        allocatedResources.push(result);
    }else{
        result = availableResources.pop();
        allocatedResources.push(result);
    }
    return result;
}


function example(){
    let result;
    if(availableResources.length === 0){
        result = createResource();
    }else{
        result = availableResources.pop();
    }
    allocatedResources.push(result);
    return result;
}

 

+ Recent posts