---
# System prepended metadata

title: Ch11 Refactoring APIs (重構API)

---

# Ch11 Refactoring APIs (重構API)

## Separate Query from Modifier (將查詢函式與修改函式分離)

**Motivation**

1. What is most valueable function?

> No side effect


**Before**

```javascript
function getTotalOutstandingAndSendBill() {
  const result = customer.invoices.reduce((total, each) => each.mount + total, 0);


  return () => sendBill();
}
```

**After**

```javascript
function getTotalOutstandingAndSendBill() {
  const result = customer.invoices.reduce((total, each) => each.mount + total, 0);
  sendBill();

  return result;
}

function totalOutstanding() {
  return customer.invoices.reduce((total, each) => each.amount + total, 0);
}

function sendBill() {
  emailGateway.send(formatBill(customer));
}
```

**Example**

```javascript
function alertForMiscreant(people) {
  for (const p of people) {
    if (p === 'Don') {
      setOffAlarms();
      return 'Don';
    }
    if (p === 'John') {
      setOffAlarms();
      return 'John'
    }

    return '';
  }
}
```

```javascript
function findMiscreant(people) {
  for (const p of people) {
    if (p === 'Don') return 'Don';
    if (p === 'John') return 'John';
    return '';
  }
}

function alertForMiscreant(people) {
  if (findMiscreant(people) !== '') setOffAlarms();
}
```
---

## Parameterize Function (將函式參數化)

```javascript
function tenPercentRaise(aPerson) {
  aPerson.salary = aPerson.salary.multiply(1.1);
}

function raise(aPerson) {
  aPerson.salary = aPerson.salary.multiply(1.05);
}

```

```javascript
function raise() {
  aPerson.salary = aPerson.salary.multiply(1 + factor);
}
```

**Motivation**

> 多個函式擁有相似的邏輯

> 1. 擁有相似的邏輯，但使用不同的常值

**How to do that on complexity structure**

> 1. 有範圍的程式，從中間開始切開並進行重構。

```javascript
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;
}
```

```javascript
function withinBand(usage, bottom, top) {
  return usage > bottom ? Math.min(usage, top) - bottom : 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);
}
```

---

## Remove Flag Argument (移除旗標引數)

**Motivation**

大家在以前寫程式時，一定會有個if...else..來當拆開邏輯，例如if (isXXX)，但這個對於caller而言，他不知道parameters是什麼，都要進去函式去逐一查每個arguments代表的是什麼意義。

```javascript
function bookConcert(aCustomer, isPremium) {
  if (isPremium) {
    // ....do premium things
  } else {
    // ...normal things
  }
}

```

**Motivtion**

1. 大家可以看到下面這些call function意義不明，it's not readable。

bookConcert(aCustomer, true);

bookConcert(aCustomer, CustomerType.PREMIUM);

bookConcert(aCustomer, 'premium');

> 在主程式已經將branch切分的很清楚的情況下，我們可以將被呼叫的程式中的flag上移到上一層。

---
## Preserve Whole Object (保留整個物件)

**Motivation**

1. 如果未來function需要從arguments取得更多的資料，勢必需要取得更多參數內容，進而導致需要更改傳進去的parameters -> 這有臭臭的感覺。

```javascript
// caller
const low = aRoom.daysTempRange.low;
const high = aRoom.daysTempRange.high;
if (!aPlan.withinRange(low, high)) {
  alerts.push('room temperature went outside range');
}

class HeatingPlan {
  constructor() {
    this._temperatureRange = {
      low: 100,
      high: 200,
    }
  }

  withinRange(bottom, top) {
    return (bottom >= this.temperatureRange.low) && (top <= this._temperatureRange.high);
  }
}
```

解決方案

1. 過渡期
```javascript
// caller
if (!aPlan.xxxNewWithinRange(aRoom.daysTempRange)) {
  alerts.push('room temperature went outside range');
}


class HeatingPlan {
  constructor() {
    this._temperatureRange = {
      low: 100,
      high: 200,
    }
  }

  withinRange(bottom, top) {
    return (bottom >= this.temperatureRange.low) && (top <= this._temperatureRange.high);
  }

  // it's a temp function to replace withinRange
  xxxNewWithinRange(aNumberRange) {
    return this.withinRange(aNumberRange.low, aNumberRange.high);
  }
}
```

2. 最後

```javascript
// caller
if (!aPlan.withinRange(aRoom.daysTempRange)) {
  alerts.push('room temperature went outside range');
}


class HeatingPlan {
  constructor() {
    this._temperatureRange = {
      low: 100,
      high: 200,
    }
  }

  withinRange(aNumberRange) {
    return aNumberRange.low >= this._temperatureRange.low && aNumberRange.high <= this._temperatureRange.high;
  }
}
```
衍伸

```javascript
// caller 

const low = aRoom.daysTempRange.low;
const high = aRoom.daysTempRange.high;
if (!AnimationPlaybackEvent.withinRange(low, high)) {
  alerts.push("room temperature went outside range");
}

// caller v2

const low = aRoom.daysTempRange.low;
const high = aRoom.daysTempRange.high;
const isWithRange = aPlan.withinRange(low, high);
if (!isWithRange) {
  alerts.push('room temperature went outside range');
}

// caller v3
const tempRange = aRoom.daysTempRange;
const low = tempRange.low;
const hight = tempRange.high;
const isWithRange = aPlan.withinRange(low, high);
if (!isWithRange) {
  alerts.push('room temperature went outside range');
}

// caller v4
const tempRange = aRoom.daysTempRange;
const isWithRange = xxxNewWithinRange(aPlan, tempRange);
if (!isWithRange) {
  alerts.push('room temperature went outside range');
}

function xxxNewWithinRange(aPlan, tempRange) {
  const low = tempRange.low;
  const high = tempRange.high;
  const isWithRange = aPlan.withinRange(low, high);

  return isWithRange;
}
```


---
## Replace Parameter with Query (將參數換成查詢程式)

> 名言：當function有parameters時，values是caller的責任，否則就是function全權負責。
> 
> 老闆說他偏好這個技巧，而不是replace query with parameters

> 使用時機： 希望使用了這個技巧不會造成function內部與外部變數有依賴關係就是有沒有referential transparency(參考透明性)，使用同樣的參數傳進來，會不會得到相同的結果。

1. v1
```javascript
class Order {
  constructor() {
    this.quantity = 'I\'m number as number' ;
  }

  get finalPrice() {
    const basePrice = this.quantity * this.itemPrice;
    let discountLevel;
    if (this.quantity > 100) discountLevel = 2;
    else discountLevel = 1;

    return this.discountPrice(basePrice, discountLevel);
  }

  discountPrice(basePrice, discountLevel) {
    switch(discountLevel) {
      case 1: return basePrice * 0.95;
      case 2: return basePrice * 0.9;
    }
  }
}
```
2. v2
```javascript
class Order {
  constructor() {
    this.quantity = 'I\'m number as number' ;
  }

  get finalPrice() {
    const basePrice = this.quantity * this.itemPrice;

    return this.discountPrice(basePrice, this.discountLevel);
  }

  get discountLevel() {
    return this.quantity > 100 ? 2 : 1;
  }

  discountPrice(basePrice, discountLevel) {
    switch(discountLevel) {
      case 1: return basePrice * 0.95;
      case 2: return basePrice * 0.9;
    }
  }
}
```
3. v3

```javascript
class Order {
  constructor() {
    this.quantity = 'I\'m number as number' ;
  }

  get finalPrice() {
    const basePrice = this.quantity * this.itemPrice;

    return this.discountPrice(basePrice);
  }

  get discountLevel() {
    return this.quantity > 100 ? 2 : 1;
  }

  discountPrice(basePrice) {
    switch(this.discountLevel) {
      case 1: return basePrice * 0.95;
      case 2: return basePrice * 0.9;
    }
  }
}
```


---
## Replace Query with Parameter (將查詢程式換成參數)

**Motivation**

> 將所有東西都轉成參數化與共享大量的範圍是對立的
> 前者會造成parameters一長串，後者則是會造成function與環境造成大量的耦合

1. v1
```javascript
class HeatingPlan {
  constructor() {
    this._max = xxx;
    this._min = xxx;
  }

  get targetTemperature() {
    if (thermostat.selectedTemperature > this._max) return this._max;
    else if (thermostat.selectedTemperature < this._min) return this._min;
    else return thermostat.selectedTemperature;
  }
}

// caller
if (thePlan.targetTemperature > thermostat.currentTemperature) setToHeat();
else if (thePlan.targetTemperature < thermostat.currentTemperature) setToCool();
else setOff();
```

2. v2
```javascript
class HeatingPlan {
  constructor() {
    this._max = xxx;
    this._min = xxx;
  }

  get targetTemperature() {
    return this.xxxNewTargetTemperature(thermostat.selectedTemperature);
  }

  xxxNewTargetTemperature(selectedTemperature) {
    if (selectedTemperature > this._max) return this._max;
    else if (selectedTemperature < this._min) return this._min;
    else return selectedTemperature;
  }
}

// caller
if (thePlan.targetTemperature > thermostat.currentTemperature) setToHeat();
else if (thePlan.targetTemperature < thermostat.currentTemperature) setToCool();
else setOff();
```

3. v3
```javascript
class HeatingPlan {
  constructor() {
    this._max = xxx;
    this._min = xxx;
  }

  targetTemperature(selectedTemperature) {
    if (selectedTemperature > this._max) return this._max;
    else if (selectedTemperature < this._min) return this._min;
    else return selectedTemperature;
  }
}

// caller
if (thePlan.targetTemperature(thermostat.selectedTemperature) > thermostat.currentTemperature) setToHeat();
else if (thePlan.targetTemperature(thermostat.selectedTemperature) < thermostat.currentTemperature) setToCool();
else setOff();
```


note: 傳送同一組參數進去會reture同樣的value回來，是具有透明性
note: 在function內的elements, 沒有引用透明性會導致function也會失去引用透明性
note: 所以增加parameter移到呼叫方

---


