---
# System prepended metadata

title: '[第5章] 針對複雜應用的設計模式'

---

# [第5章] 針對複雜應用的設計模式



## :triangular_flag_on_post: 本章內容

:::success
1.命令是處理異常方式的問題<br>
2.使用容器,以防訪問無效數據<br>
3.用functor的實現來做數據轉換<br>
4.利于組合的Monad數據類型<br>
5.使用Monadic類型來鞏固錯誤處理策略<br>
6.Monadic類型的組合與交錯
:::

### :pencil2: 5.1 命令式錯誤處理的不足

:::success
在許多情況下都會發生javaScript錯誤，特別是在與服務器通信時，或是在試圖訪問一個Null對象屬性時。此外，第三方庫也有可能拋出異常來表示某些特定的錯誤。因此開發者在編程時總是需要做最好最壞的打算
異常是通過try-catch處理

try...catch 語法標記出一整塊需要測試的語句，並指定一個以上的回應方法，萬一有例外拋出時，try...catch 語句就會捕捉。


:::

### :pencil2: 5.1.1 try catch

``` javascript 
try {
   // code that might throw an exception in here
   //需要測試的語句
}
catch (e) {
   // 處理錯誤的發生，並做顯示
   console.log('ERROR' + e.message);
}
```
#### 這個語法會先將 try 區塊中的程式碼執行一次，如果裡面的程式碼有問題就會立刻中止執行，執行步驟會直接跳到 catch 區塊的程式碼，如果 catch 的程式碼又有問題，那網頁就會跳錯。


``` javascript 
// To name a few
// 資料查找 :: DB, String -> Object
//
    const findObject = R.curry(function (db, id) {
    const result = find(db, id)
    if(!result) {
        throw new Error('Object with ID [' + id + '] not found');
          }
          return result;
        });
       
        const findStudent = findObject(DB('students'));
```
``` javascript
     try {
            // 需要測試的語句
            var student = findStudent('444-44-4444');
        }
     catch (e) {
            
            console.log('ERROR' + e.message);
        }

//這段程式碼使用了柯里化的概念來實現一個具有錯誤處理的
//創建了一個特定資料庫的查找函數 findStudent。
```


### :pencil2: 5.1.2 函數式程序不應拋出異常 


命令式的javaScript代碼結構有很多缺陷,而且也會與函數式的設計有兼容問題。<br>
1.<b>可讀性和可維護性</b>：通常會一系列的指令和狀態改變來描述程式的執行流程，這可能導致程式碼變得難以閱讀、理解和維護。當程式碼的邏輯複雜時，命令式的結構可能導致程式碼變得冗長和難以管理。<br>
2.<b>可變狀態</b>：命令式的 JavaScript 常常使用可變的狀態（mutable state），這使得程式的行為難以預測。當多個部分共享同一個狀態時，容易出現錯誤和副作用，這可能導致難以追蹤程式行為的問題。<br>
3.<b>副作用</b>：命令式的 JavaScript 經常使用副作用（side effects），例如修改全域變數或修改DOM。這種副作用可能導致程式碼的可測試性降低，並增加理解和調整的困難。<br>
4.<b>兼容性問題</b>：函數式設計強調純函數（pure functions）和不可變數據（immutable data），而命令式的 JavaScript 通常偏向於可變狀態和副作用。這可能導致在使用函數式設計技巧時，需要克服一些兼容性問題，或進行額外的轉換和調整。



### :thought_balloon: 5.1.3 空值(null)檢查問題

<!-- 向前面所說的，Try & Cache 會造成無法連貫 -->
除去拋出錯誤外的另外一種錯誤處理，就是回傳 `null`

以下的例子是根據學校去查出學生的住址:point_down:
``` javascript
function getCountry(student) {
    let school = student.getSchool();
    if(school !== null) {
        let addr = school.getAddress();
        if(addr !== null) {
            var country = addr.getCountry();
            return country;
        }        
        // null 回傳最大的保證了單一回傳的特性，雖然沒有比較好。
        // 這增加了我們要多花點時間和程式去做棘手的 `null` 判定
        return null;
    }
    throw new Error('Error extracting country info');
}

```

## :triangular_flag_on_post: 5.2 Functor

把值裝進一個容器，而且只能使用對外的 API 處理值，這樣做有什麼好處？換個方式問：讓容器自己去運用函數的好處是什麼？答案就是抽象，也更 陳述性的declarative，我們換個角度看，當我們傳入一個函數給 map() 時，我們請求容器幫我們對值執行這個函數

參考：https://ithelp.ithome.com.tw/articles/10240162


<!-- 雖然說我們可以用一個簡單的過濾去判斷是否為`Null`在做對應的回傳 -->
<!-- 但這和 Try & Cache 都依樣是被動的檢查，如果能夠更有效並且減少這些冗余代碼能夠省下多少時間。 -->

### :thought_balloon: 5.2.1 Wrapping unsafe values

- Containerizing (or wrapping) values（比較實際的例子是封裝）能夠最大程度的保證值的不可變動與安全的存取或修改。
<!-- 這段我們將會用第三章講過的 Map 來做解釋  -->
- Map 就是Functional programing 所說的 pure function

5.1 以下將用 Wrapper 這個範例來做解釋

補充
``` javascript
// 封裝一個值，可以避免使用者去直接的 access 或是 manipulate
class Wrapper {
    constructor(value) {
        this._value = value;
    }
    // map :: (A -> B) -> A -> B
    map(f) {
      // 錯誤 1
      return f(this.val);
    };
Simple type that stores a single value of any type
Maps a function over this type (just like arrays)
    toString() {
                            // 錯誤 2
      return 'Wrapper (' + this.value + ')';
} }
// wrap :: A -> Wrapper(A)
// 注意：這個 wrap 後面會很常使用到
const wrap = (val) => new Wrapper(val);
Helper function that quickly creates wrappers around values
```
例：
``` javascript
const wrappedValue = wrap('Get Functional');
// 用 Ramda 去取得值
// tips: identity 並不做任何事情，只回傳接收到的參數
wrappedValue.map(R.identity); //-> 'Get Functional'
或是
wrappedValue.map(console.log);

// 好處是如果需要 access Wrapper 都必須要經過 .map，而不能直接取得值
// 也最大程度的提升我們整個參數的不可變動性，並且保護我們的變數
```
以上的方法雖然保護到變數，但依然沒辦法很好的處理 null/undefined，接下來我們會根據 null/undefined 來做處理。

#### 如果錯誤能夠被其他方法來做預先的判斷並處理，並保證我們進入 Function 時候的值不會是*錯誤*的
* 這裡錯誤包含 null, 空字串, 負數等非預期的回應資訊

``` javascript
// 以這來當例子，我們建立一個 fmap 靜態 function
// fmap :: (A -> B) -> Wrapper[A] -> Wrapper[B]
Wrapper.prototype.fmap = function (f) {
    // 判斷邏輯可以塞這裡，在驗證之後，才送給 wrap 做儲存
    return wrap(f(this.val));
};
```

Factor 就是像洋蔥一樣層層的包裹著變數，然後做處理．
- 上面的 Function 先新建一個*容器
- 接著將值拋給由外部傳入的 function
- 最後再將取得到的最終運算值，回傳給*同屬性的容器

後面我們會再進一步的說明這個容器，還有他的相關用法。

### :pencil2: 5.2.2 Functors explained

A functor is simply something that can be mapped over
這句話分成兩部分，一個是 something 一個是 can be mapped over，只要符合這兩個就是 Functor 了

### Something (或稱 Category)<br>
A set of values arranged in some shape
![](https://hackmd.io/_uploads/HyD3d8iH3.jpg)
↑ The values are in yellow, and the shape is in blue

``` javascript 
// Something 1: Array
[1,2,3,4,5] // 被存放在 Array 裡的一組值

// Something 2: Object
{ age: 22, name: 'Tom'} // 被存在物件裡的一組值

// Something 3: Single value
39 // 某個值，除了 Number 也可以是任何型別
// => [ 5, 7, 9 ]

```
不止 Array 是 Something，任何原始型別 ( string 、 number... ) 或是 Object 甚至 Function 都是 Something

### Can be mapped over<br>
代表你可以把 list 中每一個值做一些事然後輸出
``` javascript 
// Something 1: Array
[ 1, 2, 3, 4, 5 ].map(x => x + 3)); // [ 4, 5, 6, 7, 8 ]

```
![](https://hackmd.io/_uploads/rynz38irn.jpg)


## :triangular_flag_on_post:  5.3 使用Monad函數式地處理錯誤 <span style="font-size:18px">Functional error handling using monads</span>
``` javascript
// 找到學生
const findStudent = R.curry(function(db, ssn) {
  return wrap(find(db, ssn));
});

// 找到學生的地址
const getAddress = function(student) {
  return wrap(student.fmap(R.prop('address')));
}

// Compose 兩個功能為一個，目的尋找特定學生地址
const studentAddress = R.compose(
    getAddress,
    findStudent(DB('student'))
);

// 範例 studentAddress('444-44-4444'); //-> Wrapper(Wrapper(address))


// 第一次 map，应用 R.identity
// const result1 = studentAddress('444-44-4444').map(R.identity);

// 第二次 map，再次应用 R.identity
// const result2 = result1.map(R.identity);

// 為了取得最後的 Result - address
studentAddress('444-44-4444').map(R.identity).map(R.identity);


``` 

### :pencil2: 5.3.1 Monad 從控制流到數據流
Data control flow
``` javascript
Wrapper(2).fmap(half); //-> Wrapper(1)
Wrapper(3).fmap(half); //-> Wrapper(1.5)

// Functor 被賦予函數並返回結果
const Empty = function(_){};
Empty.prototype.map = function() { return this; };
const empty = () => new Empty();


//(n % 2 == 0) 来判断 n 是否是偶数。如果 n 是有限数字且是偶数，则返回 true，否则返回 false
// half(4), half(7)
const isEven = (n) => Number.isFinite(n) && (n % 2 == 0);
const half = (val) => isEven(val) ? wrap(val / 2) : empty();


half(4).fmap(plus3); //-> Wrapper(5)
//由于 3 不是偶数，所以会返回一个表示空值的 Empty 实例
half(3).fmap(plus3);// Empty
``` 
有關 Monad

1.或是說 FP，就是一個抽象也沒有任何意義的 Fn<br>
2.簡單來說就是將值包裝起來，不停的轉送，中間會經過拆解、加工（Chain），直到最後解開外包後顯示內容．<br>
3.如上方的範例 Wrapper，但不同的 Monad 有不同的實現原則（例如: map, fmap）。

重構 Wraper

``` javascript
class Wrapper {
    constructor(value) {
        this._value = value;
    }
    static of(a) {
      return new Wrapper(a);
}
    map(f) {
      return Wrapper.of(f(this.value));
}
    // 遞回自己，用於解構取值
    join() {
      if(!(this.value instanceof Wrapper)) {
        return this;
      }
      return this.value.join();
    }
    toString() {
      return `Wrapper (${this.value})`;
} }

// 使用 Wrapper 类
const flattenedWrapper = nestedWrapper.join();
console.log(flattenedWrapper.toString()); // Wrapper (7)
``` 

### :thought_balloon: 5.3.2 Error handling with Maybe and Either monads

- 除了有效的包裝值（如前面所講的例子）外，一元結構(monadic structures)也可以用來模擬不存在(absence of one)的狀況 - 例如 null 或 undefined。
- 所以 FP 具體的將錯誤轉變成一件可以預期的事情，用 Maybe 或 Either 來做處理，而不是直接中斷出錯。

兩者都有以下的優勢
- 隔離不需要的邏輯與變數
- 將 null/undeined 處理併入程式中
- 避免錯誤拋出中斷程式
- 支援功能合併處理的作法
- 集中邏輯並且設置預設值

### Maybe 做空值處理
Maybe 一個空型別和兩個具體的子類型，它幫忙處理了 “nullable” 的值 (null and undefined)，所以我們可以專心的處理邏輯相關的事情。

    - Just - 用於回傳一個包裹過的值
    // 可以想像 nothing 就是一個什麼事情都不做，讓這段程式 pass 過去
    - Nothing - 用於回傳兩種包裹過的值 *空值* 或是 *不重要的錯誤資訊*

底下我們來看個範例
``` javascript
// 這宣告一個外框架
class Maybe {
    static just(a) {
         return new Just(a);
    }
    static nothing() {
        return new Nothing();
    }
    // 這裡來做判斷使用 Just 或是 nothing
    static fromNullable(a) {
        return a !== null ? just(a) : nothing();
    }
    static of(a) {
        return just(a);
    }
    get isNothing() {
        return false;
    }
    get isJust() {
        return false;
    }
}

// 用來做事情的 Just
class Just extends Maybe {
    constructor(value) {
        super();
        this._value = value;
    }
    get value() {
        return this._value;
    }
    map(f) {
        return of(f(this.value));
    }
    getOrElse() {
        return this.value;
    }
    filter(f) {
       Maybe.fromNullable(f(this.value) ? this.value : null);
    }
    get isJust() {
        return true;
    }
    toString () {
        return `Maybe.Just(${this.value})`;
    } 
}

// 就真的啥事都不做的 Nothing，最多就是當你要值的時候，給你個錯誤訊息
class Nothing extends Maybe {
    map(f) {
        return this;
    }
    get value()  {
        throw new TypeError('Can't extract the value of a Nothing.');
    }
    getOrElse(other) {
        return other;
    }
    filter() {
        return this.value;
    }
    get isNothing() {
        return true;
    }
    toString() {
        return 'Maybe.Nothing';
    } 
}
```
從上面程式可以看得出來，Maybe 就是一個骨架，而有兩個子類別（Just, Nothing）去繼承，最終的流向則會由程式的邏輯去處理會觸發哪一個。
<!-- 這邊可以舉個例子 jQuery -->
實例上來說，這裡 `wrap.map` 的結果會決定最後要使用那一個子類別。
如 Students 有取到就會儲存於 Just 內部
![](https://hackmd.io/_uploads/rkM4jJm8n.png)

讓我們回到 5.4 的例子，要從本地端的 DB 去取得學生地址，我們沒辦法確定是不是一定能取到值，所以我們需要做一些檢查。

``` javascript
// 我們把空值的檢查 Curry 化，這樣其他地方有需要也可以重複的使用
// safeFindObject :: DB -> String -> Maybe
const safeFindObject = R.curry(function(db, id) {
    return Maybe.fromNullable(find(db, id));
})

// 指定我要尋找 student 的 table
// safeFindStudent :: String -> Maybe(Student)
const safeFindStudent = safeFindObject(DB('student'));

const address = safeFindStudent('444-44-4444').map(R.prop('address'));
address; //-> Just(Address(...)) or Nothing
// 如果給予不正確的值，當取用變數 (get()) 時 我們會得到對應的錯誤，不取用則不會有任何事發生。
```

<!-- 根據學號取的地址 也就是 Just('444-44-4444') 或是 nothing -->
看完上面之後，可以很直白的由命名知道他要做什麼或用途是什麼。

另外一個有趣得應用是 `getOrElse()`
``` javascript
const userName = findStudent('444-44-4444').map(R.prop('firstname'));
// 如果到了這裡正確，會直接顯示結果，但錯誤的話則會顯示預設的文字作替代
document.querySelector('#student-firstname').value =
    username.getOrElse('Enter first name');

```
接下來讓我們再回頭看一下之前那個醜醜的 `getCountry()`
``` javascript
function getCountry(student) {
    let school = student.getSchool();
    if(school !== null) {
        let addr = school.getAddress();
        if(addr !== null) {   
         return addr.country();
        }
    }
    // 當這被回傳，我們很難清楚到底原因是什麼
    return 'Country does not exist!';
}
```
<!-- 甚至於可能會被繁雜的 null/undefined 檢查搞得很火大，或是避免型別錯誤造成的意外錯誤。 -->

讓我們用 Maybe 來寫這個實際的運用
``` javascript
const country = R.compose(getCountry, safeFindStudent);

// 如同上面描述，這邊會根據你是 Just 或是 Nothing 來做呈現，如果是 Nothing 那我就會拋出最底下的 else 錯誤。
const getCountry = (student) => student
      .map(R.prop('school'))
      .map(R.prop('address'))
      .map(R.prop('country'))
         .getOrElse('Country does not exist!');
```
Function lifting 
```javascript
const safeFindObject = R.curry(function(db, id) {
   return Maybe.fromNullable(find(db, id));
}
```
> 這裡可以提出一個問題，我真的每個地方都需要去做安全檢查嗎？ 


接下來如果想要更精確的知道到底錯誤是什麼原因發生的，有另一種 Error handling 叫做 Either.......

------------------------------------- To be continue -------------------------------------


### 5.3.2 Either Monad 來處理異常

#### 使用Either從故障中恢復

一條是 Happy Path (Right)，就是運算過程一切順利;<br>
另一條是 Sad Path (Left)，只要某一處的運算出現錯誤，就會跳過之後運算直接輸出失敗結果


