owned this note
owned this note
Published
Linked with GitHub
<style>
.h1 {
font-size: 64px;
font-weight: 700;
}
.h2 {
font-size: 48px;
font-weight: 700;
}
.h3 {
font-size: 36px;
font-weight: 700;
}
.h4 {
font-size: 24px;
font-weight: 700;
}
.p {
font-size: 20px;
}
.fixed {
position: fixed;
}
.markdown-body b {
font-weight: 700;
}
.left {
text-align: start;
margin-left: 28px;
padding-top: 16px;
}
.ol {
font-size: 20px;
margin: 0 !important;
}
.ol li {
margin: 8px 0; /* 5px top and bottom, 0 left and right */
}
.markdown-body code,
code {
font-size: 16px !important;
color: #99999 !important;
background-color: #4A4A4A;
}
</style>
<div style="position: relative; min-width: 1080px; min-height: 320px;">
<p class="h1 fixed" style="top: 180px; left: 50%; transform: translateX(-50%); width:1080px;" >Grokking Simplicity FP CH4-5</p>
<p class="h4 fixed" style="right: 0px; bottom: -140px;"
>Presenter : Steven Chang</p>
<p class="h4 fixed" style="right: 82px; bottom: -180px;"
>Date : 2025/04/17</p>
</div>
---
<p class="h2"> 前次讀書會回顧 </p>
<div class="left">
<p class="p">1. Side Effect 定義:一個函數在計算過程中除了返回一個值之外,還對外部產生了影響,簡單來說,如果一個函數在運算時除了計算結果外,還改變了其他部分的狀態,那麼這個函數就有副作用。</p>
<p class="p">2. 實務中 FP 仍需要 Side Effect,這些 Side Effect 是我們使用軟體的根本原因</p>
<p class="p">3. Actions: 執行結果會因為不同時間點或次數而有所不同 </p>
<p class="p">4. Calculations: 不管在何時或執行多少次,只要輸入不變,輸出都會相同 </p>
<p class="p">5. Data: 資料可紀錄與事件有關的事實 </p>
<p class="p">6. 對 FP Programmer 來說,在程式碼中分辨 Actions、Calculations 與 Data 是自然且重要的 </p>
<p class="p">7. FP Programmers 喜好 Data > Calculations > Actions,因為 Actions 在不同的時間跟執行次數會產生不同的結果,所以盡量將 Action 拆分為 Calculation 或 Data,可以降低程式執行中的不確定性,尤其是在複雜的分散式系統架構下。</p>
</div>
---
<p class="h3"> Actions </p>
<div class="left" style="margin-left: 48px;">
<p class="p">執行結果會因為不同時間點或次數而有所不同</p>
<p class="p">例子:擀麵團、送披薩、進原料</p>
</div>
```javascript
sendEmail(to, from, subject, body) // 寄送電子郵件
saveUserDB(user) // 儲存資料到資料庫
getCurrentTime() // 每次呼叫,傳回的時間都不一樣
```
---
<p class="h3"> Calculations </p>
<div class="left" style="margin-left: 48px;">
<p class="p">不管在何時或執行多少次,只要輸入不變,輸出都會相同</p>
<p class="p">例子:準備食譜、列採購清單</p>
</div>
```javascript
sum(numbers) // 快速求出一串數字的總和
string_length(str) // 輸入相同字串,回傳的字串長度值永遠相同
```
---
<p class="h3"> Data </p>
<div class="left" style="margin-left: 48px;">
<p class="p">資料紀錄與事件有關的事實</p>
<p class="p">例子:庫存清單、披薩食譜、點餐資訊、收據</p>
</div>
```javascript
{"firstname":"Eric", "lastname": "Normand"} // 關於某人的資訊
[1, 10, 2, 45, 3, 98] // 單純的數字串列
```
---
<p class="h2"> 第四章 擷取 Actions 函式中的 Calculations </p>
---
<p class="h3"> 本章著重於從 Actions 中抽離出 Calculations </p>
<p class="h4"> 以 MegaMart 線上商店作為本章範例 </p>
---
<p class="h3"> 第一段程式碼:更新購物車金額 </p>
```javascript
var shopping_cart = []; // 全域變數
var shopping_cart_total = 0; // 全域變數
function add_item_to_cart(name, price) {
shopping_cart.push ({ // 加入購物車
name: name,
price: price
});
calc_cart_total(); // 計算總金額
}
```
```javascript
function calc_cart_total() {
shopping_cart_total = 0;
for(var i = 0; i < shopping_cart.length; i++) {
var item = shopping_cart[i];
shopping_cart_total += item.price ; // 加總購物車金額
}
set_cart_total_dom(); // 更新 DOM 來顯示最新的價格
}
```
---
<p class="h4"> 第二段程式碼:確認是否免運</p>
```javascript
function update_shipping_icons() {
var buy_buttons = get_buy_buttons_dom(); // 取得所有的購買按鈕,然後遍歷他們
for(var i = 0; i < buy_buttons.length; i++) {
var button = buy_buttons[i];
var item = button.item;
if(item.price + shopping_cart_total >= 20) // 判斷是否符合免運的條件
button.show_free_shipping_icon(); // 顯示圖示
else
button.hide_free_shipping_icon(); // 隱藏圖示
}
}
```
<p class="p"> 在 <code>calc_cart_total()</code> 呼叫這個新函式,每當購物總金額改變時,會更新所有的圖示</p>
```javascript
function calc_cart_total() { // 與之前相同的函數
shopping_cart_total = 0;
for(var i = 0; i < shopping_cart.length; i++) {
var item = shopping_cart[i];
shopping_cart_total += item.price;
}
set_cart_total_dom();
update_shipping_icons(); // 新增一行,用來更新圖示
}
```
---
<p class="h4"> 第三段程式碼:計算稅金</p>
```javascript
function update_tax_dom() { // ❶ 更新稅金的函式
set_tax_dom(shopping_cart_total * 0.10); // ❷ 更新 DOM,將總額乘 10%
}
```
<p class="p"> 在 <code>calc_cart_total()</code> 計算出新的購物車總額後,再呼叫這個函式</p>
```javascript
function calc_cart_total() {
shopping_cart_total = 0;
for(var i = 0; i < shopping_cart.length; i++) {
var item = shopping_cart[i];
shopping_cart_total += item.price;
}
set_cart_total_dom();
update_shipping_icons();
update_tax_dom(); // ❶ 更新頁面上的稅額
}
```
---
<p class="h3"> 我們需要使程式碼更容易測試 </p>
<p class="h4" style="margin-left: 40px; text-align:left"> 程式碼中包含一些不易測試的業務規則 (business rule) </p>
<div class="left">
<p class="p" style="margin-left: 16px;">每次程式碼改動時,都得寫一個測試來確認下列步驟:</p>
<ol class="ol">
<li>設定瀏覽器</li>
<li>點擊按鈕將商品加入購物車</li>
<li>等待 DOM 更新</li>
<li>從 DOM 擷取出值</li>
<li>將字串解析成數字</li>
<li>將結果與預期值做比對</li>
</ol>
</div>
<div class="left">
<p class="p" style="margin-left: 16px;">為了能更輕鬆地進行測試,需要完成下列步驟:</p>
<ol class="ol">
<li>將業務規則與 DOM 的更新分離</li>
<li>消除那些全域變數!</li>
</ol>
</div>
---
<p class="h3"> 我們需要讓程式碼更具可重用性 </p>
<div class="left">
<p class="p" style="margin-left: 16px;">會計和運輸部門希望能重用我們的程式碼,但以下幾個原因使他們無法重用:</p>
<ol class="ol">
<li>程式碼從全域變數中讀取購物車資料,但他們需要從資料庫中處理訂單,而不是從變數中取資料</li>
<li>程式碼直接寫入 DOM,但他們需要列印稅單和運送標籤</li>
</ol>
</div>
---
```javascript
function update_shipping_icons() {
var buy_buttons = get_buy_buttons_dom();
for(var i = 0; i < buy_buttons.length; i++) {
var button = buy_buttons[i];
var item = button.item;
if(item.price + shopping_cart_total >= 20 ) // 想要重用的業務規則(價格大於等於 20)
button.show_free_shipping_icon(); // 此函式只能設定 shopping_cart_total 之後才可執行
else
button.hide_free_shipping_icon(); // 這些操作只有在 DOM 已建立時才能正常運作
}
} // 由於沒有回傳值,因此無法直接從函式取得結果
```
<div class="left">
<p class="p" style="margin-left: 16px;">為了使它更具可重用性,請做到以下幾點:</p>
<ol class="ol">
<li>不要依賴全域變數</li>
<li>不要假設答案會被寫入 DOM</li>
<li>讓函式回傳答案</li>
</ol>
</div>
---
<p class="h3">區分 Actions, Calculations, and Data </p>
---
```javascript
// 全域變數是可變的 ==> Actions
var shopping_cart = []; // A
var shopping_cart_total = 0; // A
```
```javascript
// 修改一個全域變數 ==> Actions
function add_item_to_cart(name, price) { // A
shopping_cart.push({
name: name,
price: price
});
calc_cart_total();
}
```
```javascript
function update_shipping_icons() { // A
var buy_buttons = get_buy_buttons_dom(); // 從 DOM 讀取資料 ==> Action
for (var i = 0; i < buy_buttons.length; i++) {
var button = buy_buttons[i];
var item = button.item;
if (item.price + shopping_cart_total >= 20)
button.show_free_shipping_icon(); // 修改 DOM ==> Action
else
button.hide_free_shipping_icon(); // 修改 DOM ==> Action
}
}
```
---
```javascript
function update_tax_dom() { // A
set_tax_dom(shopping_cart_total * 0.10); // 修改 DOM ==> Action
}
```
```javascript
function calc_cart_total() { // A
shopping_cart_total = 0; // 修改全域變數 ==> Action
for (var i = 0; i < shopping_cart.length; i++) {
var item = shopping_cart[i];
shopping_cart_total += item.price;
}
set_cart_total_dom();
update_shipping_icons();
update_tax_dom();
}
```
---
<p class="h3"> Key Points </p>
<p class="h4"> 1. Action 會擴散:只要在一個函式內發現一個 Action,那該函式就被視為 Action</p>
<p class="h4"> 2. 上面這些程式碼全都是 Actions,沒有單純的 Calculations 及 Data</p>
</br>
<p class="h3"> 接下來看如何透過 FP 設計來重構程式碼 </p>
---
<p class="h3"> 函式擁有輸入與輸出 </p>
<div class="left">
<ol class="ol">
<li>每個函式都有輸入和輸出</li>
<li>輸入是函式在計算時使用的外部資訊</li>
<li>輸出是從函式中離開的資訊或動作</li>
<li>我們使用函式主要是為了獲得它的輸出,而輸入就是函式要產生輸出所必須的資料</li>
</ol>
</div>
---
<p class="h3"> 程式範例 </p>
```javascript
var total = 0;
function add_to_total(amount) { // 參數就是輸入
console.log("Old total: " + total); // 將訊息印出與讀取全域變數都是一種輸入。
total += amount; // 修改全域變數則是一種輸出
return total; // 回傳值是輸出
}
```
</br>
<p class="h3"> 整體而言就是關於追蹤資訊如何進入函式和離開函式 </p>
---
<p class="h3"> 輸入與輸出可以是隱式 (Implicit) 或顯式 (Explicit) </p>
<div class="left">
<ol class="ol">
<li>顯式 (Explicit) 輸入是那些以參數形式傳進函式的資料</li>
<li>顯式 (Explicit) 輸出是函式的回傳值</li>
<li>任何其他形式進入或離開函式的方式則屬於隱式 (Implicit) 輸入/輸出</li>
</ol>
</div>
```javascript
var total = 0;
function add_to_total(amount) { // 顯式 (Explicit) 輸入
console.log("Old total: " + total); // 隱式 (Implicit) 輸入
total += amount; // 隱式 (Implicit) 輸出
return total; // 顯式 (Explicit) 輸出
}
```
</br>
<p class="h4"> FP 概念稱這些隱式的輸入與輸出為 副作用(side effects),它們並不是函式的主要作用(主要目的是計算並回傳一個值)</p>
---
<p class="h3"> 若能夠消除 Action 中的所有隱式輸入與輸出 </p>
<p class="h3"> 那其就會變成 Calculation </p>
<div class="left" style="margin-left: 152px;">
<p class="h3" style="margin-left: 16px;"> 關鍵在於:</p>
<ol class="ol">
<li>用參數取代隱式輸入</li>
<li>用回傳值取代隱式輸出</li>
</ol>
</div>
---
<p class="h3"> 測試跟可重用性與函式的輸入和輸出相關 </p>
<div class="left" style="margin-left: 174px;">
<p class="h4" style="margin-left: 16px;">測試</p>
<ol class="ol">
<li>將業務規則與 DOM 更新分離</li>
<li>消除全域變數</li>
</ol>
</div>
<div class="left" style="margin-left: 174px;">
<p class="h4" style="margin-left: 16px;">開發可重用性</p>
<ol class="ol">
<li>不要依賴全域變數</li>
<li>不要預設答案會寫入 DOM</li>
<li>從函式中回傳答案</li>
</ol>
</div>
---
<p class="h3"> 從 Action 中分離出 Calculation </p>
<p class="h4" style="text-align: left; margin-left: 48px;">原始版本</p>
```javascript
function calc_cart_total() {
// 分離出計算總額的程式碼
/*
shopping_cart_total = 0;
for(var i = 0; i < shopping_cart.length; i++) {
var item = shopping_cart[i];
shopping_cart_total += item.price;
} */
set_cart_total_dom();
update_shipping_icons();
update_tax_dom();
}
```
---
<p class="h3" style="display: fixed; "> 從 Action 中分離出 Calculation </p>
<p class="h4" style="text-align: left; margin-left: 48px;">分離後</p>
```javascript
function calc_cart_total() {
calc_total(); // 在原本的位置 Call 新創建的函式
set_cart_total_dom();
update_shipping_icons();
update_tax_dom();
}
function calc_total() {
shopping_cart_total = 0;
for(var i = 0; i < shopping_cart.length; i++) {
var item = shopping_cart[i];
shopping_cart_total += item.price;
}
}
```
---
<p class="h3"> 從 Action 中分離出 Calculation </p>
<div class="left" style="margin-left: 174px;">
<p class="h4" style="margin-left: 16px;">開發可重用性:</p>
<ol class="ol">
<li>先建立並命名了一個新的函式,並把原本的程式碼直接貼進這個函式中</li>
<li>原本程式碼的位置則改為呼叫這個新函式</li>
<li>目前這個新函式仍然是一個 Action</li>
</ol>
</div>
</br>
<div class="left" style="margin-left: 190px;">
<p class="h4"> 我們在第二步所做的操作,使程式在改動後仍能保持原本的功能 </p>
<p class="h4"> 這類在改變程式碼卻不破壞既有功能的操作,稱為重構(refactoring)</p>
</br>
<p class="h4"> 這次重構過程可稱為「抽取子程序」(extract subroutine)</p>
</div>
---
<p class="h3"> 從 Action 中分離出 Calculation </p>
<p class="h4"> 要將新函式轉換成一個計算,首先必須先辨識其輸入與輸出 </p>
```javascript
function calc_total() { // 輸出,將全域變數進行賦值
shopping_cart_total = 0;
for (var i = 0; i < shopping_cart.length; i++) { // 輸入,讀取全域變數
var item = shopping_cart[i];
shopping_cart_total += item.price; // 輸出,修改全域變數
}
}
```
---
<p class="h3"> 從 Action 中分離出 Calculation </p>
<p class="h4"> 替換全域變數為區域變數 </p>
```javascript
function calc_total() {
var total = 0; // 將全域變數替換成區域變數
for (var i = 0; i < shopping_cart.length; i++) {
var item = shopping_cart[i];
total += item.price; // 對區域變數進行操作
}
return total; // 返回區域變數
}
```
<p class="h4"> 如此就可將隱式 (Implicit) 的輸出轉換為顯式 (Explicit) 的</p>
---
<p class="h3"> 從 Action 中分離出 Calculation </p>
<p class="h4"> 將隱式輸入轉換為顯式 (Explicit) 的參數 </p>
```javascript
function calc_total(cart) {
var total = 0;
for(var i = 0; i < cart.length; i++) { // 新增一個參數,用它取代全域變數
var item = cart[i];
total += item.price;
}
return total;
}
```
<p class="h4"> 如此<code>calc_total()</code>其唯一的輸入與輸出就是參數和回傳值 </p>
<p class="h4"> 成為一個純粹的 Calculation 函式 </p>
---
<p class="h3"> 從 Action 中分離出 Calculation </p>
```javascript
function calc_cart_total() {
shopping_cart_total = calc_total(shopping_cart); // 在原處呼叫這個 Calculation
set_cart_total_dom();
update_shipping_icons();
update_tax_dom();
}
```
<div class="left" style="margin-left: 174px;">
<p class="h4" style="margin-left: 16px;">在原處呼叫這個新的 Calculation 函式</p>
<ol class="ol">
<li>在呼叫 <code>calc_total</code> 時,將全域變數 <code>shopping_cart</code> 作為參數傳入</li>
<li>新增一個參數並在函式內部使用,取代對全域變數的直接讀取</li>
</ol>
</div>
---
<p class="h3"> 確認是否成功的抽離出 Calculation </p>
<div class="left" style="margin-left: 174px;">
<p class="h4" style="margin-left: 16px;">測試</p>
<ol class="ol">
<li>將業務規則與 DOM 更新分離 ✅</li>
<li>消除全域變數 ✅</li>
</ol>
</div>
<div class="left" style="margin-left: 174px;">
<p class="h4" style="margin-left: 16px;">開發可重用性</p>
<ol class="ol">
<li>不要依賴全域變數 ✅</li>
<li>不要預設答案會寫入 DOM ✅</li>
<li>從函式中回傳答案 ✅</li>
</ol>
</div>
---
<p class="h3"> 再次練習從 Action 抽離出 Calculation </p>
```javascript
function add_item_to_cart(name, price) {
// 將這段程式碼抽離成新的函式
shopping_cart.push({
name: name,
price: price
});
calc_cart_total();
}
```
```javascript
function add_item_to_cart(name, price) {
add_item(name, price); // 在原位置 call 新函式以取代原本的程式碼
calc_cart_total();
}
function add_item(name, price) { // 新的函式
shopping_cart.push({ // 讀取且修改 shopping_cart 全域變數
name: name,
price: price
});
}
```
---
<p class="h3"> 再次練習從 Action 抽離出 Calculation </p>
<div class="left" style="margin-left: 180px">
<p class="h3"> 提醒: </p>
<p class="h4"> 讀取全域變數是一種輸入,因為資料是從外部進來 </p>
<p class="h4"> 修改全域變數是一種輸出,因為資料離開了函式內部 </p>
</div>
---
<p class="h3"> 再次練習從 Action 抽離出 Calculation </p>
```javascript
function add_item(name, price) {
shopping_cart.push({ // 讀取且修改 shopping_cart 全域變數
name: name,
price: price
});
}
```
```javascript
function add_item(cart, name, price) { // 將全域變數以參數方式傳入
cart.push({ // 改為使用參數而非全域變數
name: name,
price: price
});
}
```
<p class="h4"> 因為仍然是透過 <code>.push()</code> 方法來修改全域陣列,仍屬於隱式輸出 </p>
---
<p class="h3"> 再次練習從 Action 抽離出 Calculation </p>
```javascript
function add_item(cart, name, price) {
var new_cart = cart.slice(); // 複製傳入的陣列並將複製品賦值給局部變數
new_cart.push({ // 修改複製陣列
name: name,
price: price
});
return new_cart; // 返回修改後的複製陣列
}
```
<p class="h4"> <code>add_item()</code> 已經不包含任何隱式輸入或輸出,成為一個純粹的 Calculation 函式 </p>
<div class="left" style="margin-left: 48px">
<p class="p"> 補充: </p>
<p class="p"> 在修改一個可變的值之前先複製它,是實現不可變性的一種方法,稱為「寫時複製」(copy-on-write) </p>
</div>
---
<p class="h3"> 再次練習從 Action 抽離出 Calculation </p>
<p class="h4 left" style="margin-left: 48px"> 修改後 </p>
```javascript
function add_item_to_cart(name, price) {
shopping_cart = add_item(shopping_cart, name, price); // 呼叫新的 Calculation 函式
calc_cart_total();
}
```
```javascript
function add_item(cart, name, price) { // Explicit Inputs
var new_cart = cart.slice();
new_cart.push({
name: name,
price: price
});
return new_cart; // Explicit Output
}
```
---
<p class="h3"> 確認是否成功的抽離出 Calculation </p>
<div class="left" style="margin-left: 174px;">
<p class="h4" style="margin-left: 16px;">測試</p>
<ol class="ol">
<li>將業務規則與 DOM 更新分離 ✅</li>
<li>消除全域變數 ✅</li>
</ol>
</div>
<div class="left" style="margin-left: 174px;">
<p class="h4" style="margin-left: 16px;">開發可重用性</p>
<ol class="ol">
<li>不要依賴全域變數 ✅</li>
<li>不要預設答案會寫入 DOM ✅</li>
<li>從函式中回傳答案 ✅</li>
</ol>
</div>
---
<p class="h3"> 觀念確認 </p>
<div class="left" style="margin-left: 48px;">
<ol class="ol">
<li>看起來程式碼量在不斷增加,這正常嗎?難道不是程式碼越少越好嗎?</li>
<li>重用性和可測試性真的是函數式程式設計唯一關注的點嗎?</li>
<li>我注意到你將某些計算獨立出來,使它們能在原本的使用情境之外被單獨使用,這很重要嗎?</li>
<li>在我們抽取出來的計算中,仍然對某個變數進行了修改。我聽說在 FP 設計中,所有東西都是不可變的,這是怎麼回事?</li>
</ol>
</div>
---
<p class="h3"> 觀念確認 </p>
<div class="left" style="margin-left: 48px;">
<p class="p" style="font-weight: 700;">Q1: 看起來程式碼量在不斷增加,這正常嗎?難道不是程式碼越少越好嗎?</p>
<p class="p">A: 一般來說,程式碼越少越好,但我們的總行數卻在增加</p>
<p class="p" style="margin-left: 24px; margin-top: -8px;">原因是我們新增了許多新函式,每個新函式至少需要兩行程式碼:一行用來定義函式的名稱,一行用來關閉大括號。但將程式拆解成許多函式,長遠來看一定會有回報</p>
<p class="p" style="margin-left: 24px; margin-top: -8px;">我們已經開始看到這些回報:程式碼變得更容易重用和測試</p>
</div>
---
<p class="h3"> 觀念確認 </p>
<div class="left" style="margin-left: 48px;">
<p class="p" style="font-weight: 700;">Q2: 重用性和可測試性真的是 FP 設計唯一關注的點嗎?</p>
<p class="p">A: 絕對不是!FP 設計確實在這方面有幫助,但還有很多其他優點</p>
<p class="p" style="margin-left: 24px; margin-top: -8px;">在書的後半部份會談到:並發(concurrency)、架構(architecture)以及資料建模(data modeling)。FP 設計是一個龐大的領域,這本書無法涵蓋所有內容</p>
</div>
<div class="left" style="margin-left: 48px;">
<p class="p" style="font-weight: 700;">Q3: 我注意到你將某些計算獨立出來,使它們能在原本的使用情境之外被單獨使用,這很重要嗎?
</p>
<p class="p">A: 相當重要。在 FP 設計中,我們喜歡將程式拆解,這樣可以組成更多、更小的部分</p>
<p class="p" style="margin-left: 24px; margin-top: -8px;">而小的部分更容易重用、測試和理解</p>
</div>
---
<p class="h3"> 觀念確認 </p>
<div class="left" style="margin-left: 48px;">
<p class="p" style="font-weight: 700;">Q4: 在我們抽取出來的計算中,仍然對某個變數進行了修改。我聽說在函數式程式設計中,所有東西都是不可變的,這是怎麼回事?</p>
<p class="p">A: 這是個好問題。不可變性(immutability)指的是一個東西在創建後就不應再被修改</p>
<p class="p" style="margin-left: 24px; margin-top: -8px;">然而,在創建過程中,它需要被初始化,而這個初始化過程就需要進行修改</p>
<p class="p" style="margin-left: 24px; margin-top: -8px;">舉例來說,你可能需要初始化陣列中的值,初始化完成後你就不再修改它,但在最初的建立階段,你必須先新增項目</p>
</div>
---
<p class="h3"> 從 Action 中抽離 Calculation 是一個可重複進行的過程 </p>
<div class="left" style="margin-left: 36px;">
<p class="h4" style="margin-left: 16px;">以下是具體步驟:</p>
<ol class="ol">
<li>選取並抽離 Calculation 的程式碼</li>
<li>辨認函式的隱式輸入與輸出</li>
<li>將輸入轉換為參數,並將輸出轉換為回傳值</li>
</ol>
</div>
<p class="p" style="margin: 40px 52px 0; text-align: start;">Note:</p>
<p class="p" style="margin: 8px 52px; text-align: start;">參數和回傳值都是不可變的,也就是說它們不應在函式內部被修改。如果我們回傳一個值,然後函式的某個部分又改變了這個值,那就會變成一種隱式輸出。同樣,如果在函式接收參數後,參數的值被改變,那也算是一種隱式輸入</p>
---
<p class="h3"> 練習題 1 </p>
```javascript
function update_tax_dom() {
set_tax_dom(shopping_cart_total * 0.10 ); // 將這個函式抽離為 Calculation
}
```
<div class="left" style="margin: -32px 36px;">
<p class="h4" style="margin-left: 16px;">步驟:</p>
<ol class="ol">
<li>選取並抽離 Calculation 的程式碼</li>
<li>辨認函式的隱式輸入與輸出</li>
<li>將輸入轉換為參數,並將輸出轉換為回傳值</li>
</ol>
</div>
---
<p class="h3"> 練習題 </p>
<p class="p" style="margin: 8px 52px; text-align: start;"> 1. 選取並抽離Calculation 的程式碼 </p>
<p class="p" style="margin: 8px 52px; text-align: start;"> 2. 辨認函式的隱式輸入與輸出 </p>
<p class="p" style="margin: 8px 52px; text-align: start;"> 3. 將輸入轉換為參數,並將輸出轉換為回傳值 </p>
```javascript
function update_tax_dom() {
set_tax_dom(calc_tax());
}
function calc_tax() { // 抽離Calculation 的程式碼
return shopping_cart_total * 0.10; // 讀取全域變數,隱式輸入與輸出
}
```
<p class="p" style="margin: 8px 52px; text-align: start;"> 抽離後: </p>
```javascript
function update_tax_dom() {
set_tax_dom(calc_tax(shopping_cart_total));
}
function calc_tax(amount) { // 抽離全域變數到參數
return amount * 0.10; // 將輸出轉換為回傳值
}
```
---
<p class="h3"> 練習題 2 </p>
<div class="left" style="margin: -16px 36px;">
<p class="h4" style="margin: 0 16px;">步驟:</p>
<ol class="ol">
<li>選取並抽離 Calculation 的程式碼</li>
<li>辨認函式的隱式輸入與輸出</li>
<li>將輸入轉換為參數,並將輸出轉換為回傳值</li>
</ol>
</div>
```javascript
function update_shipping_icons() {
var buy_buttons = get_buy_buttons_dom();
for(var i = 0; i < buy_buttons.length; i++) {
var button = buy_buttons[i];
var item = button.item;
if(item.price + shopping_cart_total >= 20) // 運輸部門想要重用這段程式碼
button.show_free_shipping_icon();
else
button.hide_free_shipping_icon();
}
}
```
---
<p class="h3"> 練習題 2 </p>
```javascript
function update_shipping_icons() {
var buy_buttons = get_buy_buttons_dom();
for(var i = 0; i < buy_buttons.length; i++) {
var button = buy_buttons[i];
var item = button.item;
if(gets_free_shipping(item.price)) // 抽離重用函式
button.show_free_shipping_icon();
else
button.hide_free_shipping_icon();
}
}
```
```javascript
function gets_free_shipping(item_price) {
return item_price + shopping_cart_total >= 20; // Implicit Input
}
```
---
<p class="h3"> 練習題 2 </p>
```javascript
function update_shipping_icons() {
var buy_buttons = get_buy_buttons_dom();
for(var i = 0; i < buy_buttons.length; i++) {
var button = buy_buttons[i];
var item = button.item;
if(gets_free_shipping(shopping_cart_total, item.price)) // 全域變數設為參數
button.show_free_shipping_icon();
else
button.hide_free_shipping_icon();
}
}
```
```javascript
function gets_free_shipping(total,item_price) { // 抽離為 Calculation,可重用
return item_price + total >= 20;
}
```
<div class="left" style="margin: -16px 36px;">
<ol class="ol">
<li>沒有 DOM 操作參與 ✅</li>
<li>沒有讀取/修改全域變數 ✅</li>
<li>函式回傳值 ✅</li>
</ol>
</div>
---
<p class="h3"> 第四章總結 </p>
<div class="left" style="margin: -16px 144px;">
<p class="h4" style="font-weight: 400;">1. Action 函式內會有隱式 (Implicit) 輸入與輸出</p>
<p class="h4" style="font-weight: 400;">2. Calculation 函式內不會有隱式 (Implicit) 輸入與輸出</p>
<p class="h4" style="font-weight: 400;">3. 全域變數為常見的隱式 (Implicit) 輸入與輸出</p>
<p class="h4" style="font-weight: 400;">4. 透過帶入函式的參數可去除隱式 (Implicit) 輸入</p>
<p class="h4" style="font-weight: 400;">5. 作為回傳值可去除隱式 (Implicit) 輸出</p>
<p class="h4" style="font-weight: 400;">6. FP 設計中,我們會將程式碼盡量從 Action 抽離成 Calculation </p>
</div>
---
<p class="h3"> 第四章 Q&A </p>
<div class="left" style="margin-left: 48px;">
<p class="p" style="font-weight: 700;">Q1: 請辨識函式 <code>calc_tax()</code> 中的輸入/輸出,並說明是隱式 (Implicit) 還是顯示 (Explicit)</p>
</div>
```javascript
var total = 0;
function add_to_total(amount) { // 1 Amount 是?
console.log ("Old total: " + total ); // 2 console.log 是? 讀取 total 是?
total += amount; // 3 修改 total 是?
return total; // 4 回傳 total 是?
}
```
---
<p class="h3"> 第四章 Q&A </p>
<div class="left" style="margin-left: 48px;">
<p class="p" style="font-weight: 700;">Q1: 請辨識函式 <code>calc_tax()</code> 中的輸入/輸出,並說明是隱式 (Implicit) 還是顯示 (Explicit)</p>
</div>
```javascript
var total = 0;
function add_to_total(amount) { // 參數是顯式輸入
// Console.log()是隱式輸出,讀取 total 全域變數是隱式輸入
console.log ("Old total: " + total );
// 修改全域變數 total 是隱式輸出
total += amount; // 3
// 回傳值是顯式輸出
return total; // 4
}
```
---
<p class="h3"> 第四章 Q&A </p>
<div class="left" style="margin-left: 48px;">
<p class="p" style="font-weight: 700;">Q2: 請辨識下列函式中的 Actions,並說明原因</p>
</div>
```javascript
function update_shipping_icons() {
var buy_buttons = get_buy_buttons_dom();
for(var i = 0; i < buy_buttons.length; i++) {
var button = buy_buttons[i];
var item = button.item;
if(item.price + shopping_cart_total >= 20)
button.show_free_shipping_icon();
else
button.hide_free_shipping_icon();
}
}
```
---
<p class="h3"> 第四章 Q&A </p>
<div class="left" style="margin-left: 48px;">
<p class="p" style="font-weight: 700;">Q2: 請辨識下列函式中的 Actions,並說明原因 </p>
</div>
```javascript
function update_shipping_icons() {
var buy_buttons = get_buy_buttons_dom(); // Action,讀取 DOM
for(var i = 0; i < buy_buttons.length; i++) {
var button = buy_buttons[i];
var item = button.item;
if(item.price + shopping_cart_total >= 20)
button.show_free_shipping_icon(); // Action,操作 DOM
else
button.hide_free_shipping_icon(); // Action,操作 DOM
}
}
```
---
<p class="h3"> 第四章 Q&A </p>
<div class="left" style="margin-left: 48px;">
<p class="p" style="font-weight: 700;"> Q3: 函式 <code>calc_tax()</code> 似乎還是 Action,怎麼做才能抽離成 Caculation 呢? </p>
</div>
```javascript
var shopping_cart_total = 0
function update_tax_dom() {
set_tax_dom(calc_tax());
}
function calc_tax() {
shopping_cart_total * 0.10;
}
```
---
<p class="h3"> 第四章 Q&A </p>
<div class="left" style="margin-left: 48px;">
<p class="p" style="font-weight: 700;"> Q3: 函式 <code>calc_tax()</code> 似乎還是 Action,怎麼做才能抽離成 Caculation 呢? </p>
</div>
```javascript
var shopping_cart_total = 0
function update_tax_dom() {
set_tax_dom(calc_tax(shopping_cart_total));
}
function calc_tax(amount) { // 抽離全域變數到參數
return amount * 0.10; // 將輸出轉換為回傳值
}
```
---
<p class="h3"> 第四章 Q&A </p>
<div class="left" style="margin-left: 148px;">
<p class="p" style="font-weight: 700;"> Q4: 把 Calculation 從 Action 抽離出來可以有哪些好處呢?(試舉兩項) </p>
</div>
---
<p class="h3"> 第四章 Q&A </p>
<div class="left" style="margin-left: 148px;">
<p class="p" style="font-weight: 700;"> Q4: 把 Calculation 從 Action 抽離出來可以有哪些好處呢?(試舉兩項) </p>
<p class="p"> 1. 增加可重用性 </p>
<p class="p"> 2. 使其更容易進行測試 </p>
</div>
---
<p class="h2"> 第 5 章 改良 Actions 的設計 </p>
---
<p class="h2"> 第 5 章 改良 Actions 的設計 </p>
</br>
<p class="h4"> 在第四章我們了解如何將隱式輸入及輸出移除來將 Action 轉換成 Calculation </p>
<p class="h4"> 然而,我們也不能將所有的 Action 移除 </p>
</br>
<p class="h4"> 所以第五章我們要來了解,如何透過移除部分的隱式輸入及輸出,來改良 Actions 的設計 </p>
---
<p class="h3"> 設計與業務需求保持一致 </p>
<p class="h4"> 第四章重構 Action 的過程是比較機械化的動作,不一定是最佳設計 </p>
<p class="h4"> 以下面 <code>gets_free_shipping</code> 及 <code>calc_total</code> 的函式為例 </p>
```javascript
function gets_free_shipping(total, item_price) { // 這些參數並不是我們真正想要的
return item_price + total >= 20; // 這裡重複計算了 (總額 + 單品價格)
}
```
```javascript
function calc_total(cart) {
var total = 0;
for (var i = 0; i < cart.length; i++) {
var item = cart[i];
total += item.price; // 這裡重複計算了 (總額 + 單品價格)
}
return total;
}
```
<div class="left" style="margin: -32px 48px;">
<p class="p"> Note: 在兩個地方重複了「單品價格加到總額」這段程式碼。重複不一定有問題,但它通常是一種 Code Smell,代表可能有更深層次的問題。 </p>
</div>
---
<p class="h3"> 設計與業務需求保持一致 </p>
<p class="h4"> 我們希望將 <code>gets_free_shipping</code> 直接代入購物車為參數 </p>
<p class="h4"> 並且重用 <code>calc_total</code> 來去除重複的程式碼邏輯 </p>
```javascript
function gets_free_shipping(cart) {
return calc_total(cart) >= 20; // 重用 calc_total() 來去除重複的程式碼邏輯
}
```
<p class="h4"> 現在這個函式直接對「購物車」操作,而不是對「總額」和「單品價格」分開處理 </p>
---
<p class="h3"> 對齊函式功能與業務需求 </p>
<p class="h4"> 這其實不算是重構(refactoring),因為我們正在改變行為 </p>
---
<p class="h3"> 修改呼叫 <code>gets_free_shipping</code> 的函式 </p>
```javascript
function update_shipping_icons() {
var buttons = get_buy_buttons_dom();
for (var i = 0; i < buttons.length; i++) {
var button = buttons[i];
// var item = button.item;
var new_cart = add_item(shopping_cart, item.name, item.price);
// if (gets_free_shipping(shopping_cart_total, item.price))
if (gets_free_shipping(new_cart))
button.show_free_shipping_icon();
else
button.hide_free_shipping_icon();
}
}
```
<div class="left" style="margin: -32px 48px;">
<p class="p"> 1. 先用 <code>add_item</code> 建立一個包含新商品的「新購物車」</p>
<p class="p"> 2. 呼叫修改後的 <code>gets_free_shipping</code> 函式 </p>
<p class="p"> 3. 現在的程式碼符合業務需求:它會告訴我們「某個購物車是否能享有免運優惠」</p>
</div>
---
<p class="h3"> 觀念討論 </p>
<div class="left" style="margin: -32px 48px;">
<p class="h4"> Q1: 程式碼行數不斷增加!這樣到底有什麼好處?</p>
<p class="p" style="margin-top: -10px;"> A: 程式碼行數的確是衡量程式庫撰寫與維護難易度的一個指標,但它並不完美 </p>
<p class="p" style="margin: -10px 21px 0;">另一種衡量維護難度的方法是看每個函式的規模有多大,函式越精簡,就越容易理解和維護</p>
<p class="h4" style="margin-top: 48px;"> Q2: 每次執行 <code>add_item()</code> 都會複製一次購物車陣列,這樣不會很耗效能嗎?</p>
<p class="p" style="margin-top: -10px;"> A: 的確,這比直接修改陣列要耗效能,不過現今的垃圾回收機制對這種操作處理得很好 </p>
<p class="p" style="margin: -10px 21px 0;"> 事實上,我們經常做「複製」這個動作:例如 JavaScript 的字串是不可變的</p>
<p class="p" style="margin: 8px 21px 0;"> 每次把兩個字串串接在一起,就會產生一個新的字串,所有字元都必須被複製過去 </p>
<p class="p" style="margin: 8px 21px 0;"> 這些好處通常大於額外的成本,能夠在不改變原物件的前提下,製作修改後的複本非常有用 </p>
</div>
---
<p class="h3"> 原則 - 將隱式輸入與輸出降到最低 </p>
<div class="left" style="margin: -32px 48px;">
<p class="h4"> 隱式輸入:不是函式參數的輸入 </p>
<p class="h4"> 隱式輸出:則指那些不是回傳值的輸出 </p>
<p class="p"> 帶有隱式輸入與輸出的函式就像是焊死的線路,缺乏模組化,無法重用 </p>
<p class="p"> 當將其轉換為顯式輸入與輸出後,就像在元件之間裝上易於拆卸的連接器 </p>
</div>
---
<p class="h3"> 原則 - 將隱式輸入與輸出降到最低 </p>
<div class="left" style="margin: -32px 48px;">
<p class="p"> 隱式輸入及隱式輸出都會限制函式何時才能被呼叫 </p>
<p class="p"> 由於呼叫時機受限,就使更難測試。要先準備好所有隱式輸入再執行函式,然後再檢查所有隱式輸出。因此,隱式輸入與輸出的數量越多,測試就越困難 </p>
<p class="p"> Calculation 函式最容易測試,因為不含任何隱式輸入與輸出 </p>
<p class="p"> 即便是 Action 函式,也應盡量消除隱式輸入與輸出,能明顯提升函式的可測試性與可重用性 </p>
</div>
---
<p class="h3"> 實作最小化隱式輸入與輸出 </p>
<p class="h4"> 以函式 <code>update_shipping_icons()</code> 為例</p>
<p class="h4"> 原始版本 </p>
```javascript
function update_shipping_icons() {
var buttons = get_buy_buttons_dom();
for(var i = 0; i < buttons.length; i++) {
var button = buttons[i];
var item = button.item;
var new_cart = add_item(shopping_cart, // 讀取全域變數 shopping_cart
item.name,
item.price);
if(gets_free_shipping(new_cart))
button.show_free_shipping_icon();
else
button.hide_free_shipping_icon();
}
}
```
---
<p class="h3"> 實作最小化隱式輸入與輸出 </p>
<p class="h4"> 修改後 </p>
```javascript
function update_shipping_icons(cart) { // 新增 cart 參數,取代全域變數
var buttons = get_buy_buttons_dom();
for(var i = 0; i < buttons.length; i++) {
var button = buttons[i];
var item = button.item;
var new_cart = add_item(cart, // 新增 cart 參數,取代全域變數
item.name,
item.price);
if(gets_free_shipping(new_cart))
button.show_free_shipping_icon();
else
button.hide_free_shipping_icon();
}
}
```
---
<p class="h3">實作最小化隱式輸入與輸出</p>
<p class="h4"> 在原呼叫 <code>update_shipping_icons()</code> 處加上參數
</p>
```javascript
function calc_cart_total() {
shopping_cart_total =
calc_total(shopping_cart);
set_cart_total_dom();
update_shipping_icons(shopping_cart); // shopping_cart 作為參數傳入
update_tax_dom();
}
```
---
<p class="h3"> 重新檢視程式碼 </p>
```javascript
function add_item_to_cart(name, price) { // 這個函式會在按下「立即購買」按鈕時被呼叫
shopping_cart = add_item(shopping_cart, name, price);
calc_cart_total(shopping_cart);
}
// calc_cart_total() 看起來有點多餘,可直接在 add_item_to_cart() 中做這件事?
function calc_cart_total(cart) {
var total = calc_total(cart);
set_cart_total_dom(total);
update_shipping_icons(cart);
update_tax_dom(total);
// 我們寫入了這個全域變數,卻沒有任何地方讀取它,可以直接刪除
shopping_cart_total = total;
}
function set_cart_total_dom(total) {
…
}
function update_shipping_icons(cart) {
var buy_buttons = get_buy_buttons_dom();
for (var i = 0; i < buy_buttons.length; i++) {
var button = buy_buttons[i];
var item = button.item;
var new_cart = add_item(cart, item.name, item.price);
if (gets_free_shipping(new_cart))
button.show_free_shipping_icon();
else
button.hide_free_shipping_icon();
}
}
function update_tax_dom(total) {
set_tax_dom(calc_tax(total));
}
```
<div class="left" style="margin: -32px 48px;">
<p class="p"> 1. 全域變數 <code>shopping_cart_total</code> 並未被使用,可移除</p>
<p class="p"> 2. <code>calc_cart_total()</code> 是多餘的函式,可將其程式碼移至 <code>add_item_to_cart()</code> 內執行</p>
</div>
---
<p class="h3"> 重新檢視程式碼 </p>
<p class="h4"> 優化後 </p>
```javascript
function add_item_to_cart(name, price) {
shopping_cart = add_item(shopping_cart, name, price);
var total = calc_total(shopping_cart);
set_cart_total_dom(total);
update_shipping_icons(shopping_cart);
// 刪除 calc_cart_total() 以及全域變數 shopping_cart_total,並將程式碼移入 add_item_to_cart()
update_tax_dom(total);
}
```
<p class="h4">現在,Action 函式已經更乾淨</p>
<p class="h4">接下來可以依照意義層次(layer of meaning)對這些函式做進一步分層整理</p>
---
<p class="h3"> 為 Calculation 函式分類 </p>
<p class="h4"> 分類對照表 </p>
<p class="p"> C - Cart 操作</p>
<p class="p"> I - Item 操作 </p>
<p class="p"> B - 業務規則 </p>
---
```javascript
function add_item(cart, name, price) { // C I
var new_cart = cart.slice(); // 用 .slice() 來複製陣列
new_cart.push({
name: name,
price: price
});
return new_cart;
}
```
```javascript
function calc_total(cart) { // C I B
var total = 0;
for (var i = 0; i < cart.length; i++) {
var item = cart[i];
total += item.price;
}
return total;
}
```
```javascript
function gets_free_shipping(cart) { // B
return calc_total(cart) >= 20;
}
function calc_tax(amount) { // B
return amount * 0.10;
}
```
---
<p class="h3"> 原則:設計就是將事物拆解 </p>
<div class="left" style="margin: -32px 48px;">
<p class="h4"> 關注點分離 </p>
<p class="p"> 函式(functions)提供了一種非常自然的方式來分離關注點 </p>
<p class="p"> 把「值如何被傳入」(arguments)與「值如何被使用」分開處理 </p>
<p class="p"> 人們常常傾向認為越大、越複雜的東西越有份量 </p>
<p class="p"> 但被拆解開的部分隨時都能再組合起來,真正的挑戰在於,如何找到有用的拆解方式 </p>
</div>
---
<p class="h3"> 原則:設計就是將事物拆解 </p>
<div class="left" style="margin: -32px 48px;">
<p class="h4"> 拆解帶來的好處 </p>
<p class="p"> 1. 更易於重用 </p>
<p class="p"> 2. 更易於維護 </p>
<p class="p"> 3. 更易於測試 </p>
</br>
<p class="p"> 即便一支函式目前看不出任何明顯的問題,但如果有可以拆出的部分,至少試著把它抽取出來,往往這樣做可以帶來更好的設計 </p>
</div>
---
<p class="h3"> 透過拆解 <code>add_item()</code> 來改進設計 </p>
<p class="h4"> 這個 <code>add_item()</code> 看似只是將物品加入購物車,但仔細一看他其實執行了四個不同的動作 </p>
```javascript
function add_item(cart, name, price) {
var new_cart = cart.slice(); // 1 複製一個陣列
new_cart.push({ // 2 建立一個物件 3 將物品加入複製的陣列
name: name,
price: price
});
return new_cart; // 4 回傳這個複製陣列
}
add_item(shopping_cart, "shoes", 3.45);
```
<p class="h4" style="text-align: start;"> 這個函式同時「知道」購物車的結構,也「知道」物品的結構。我們可以把「物品」的部分抽出成自己的函式 </p>
---
<p class="h4"> 拆解後 </p>
```javascript
function make_cart_item(name, price) { // 建立一個「物品建構函式」
return { // 建立一個物件
name: name,
price: price
};
}
function add_item(cart, item) {
var new_cart = cart.slice(); // 複製一個陣列 ** Note
new_cart.push(item); // 將物品加入複製的陣列
return new_cart; // 回傳這個複製陣列
}
// 調整呼叫方式,先用 make_cart_item 建立物品,再傳給 add_item
add_item(shopping_cart, make_cart_item("shoes", 3.45));
```
<p class="p" style="text-align: start; margin-left: 48px;"> Note 1: 先複製再修改再回傳,是實現 Immutability 的常用策略,稱為「寫時複製」(copy-on-write)</p>
<p class="p" style="text-align: start; margin-left: 48px;"> Note 2: 目前這兩個函式各自處理不同的物件結構,同時這樣拆解讓其不再特定於「購物車」或「物品」 </p>
---
<p class="h3"> 抽離寫時複製(copy-on-write)模式 </p>
<p class="h4"> <code>add_item()</code> 只是將元素加入陣列,是通用函式,而目前的命名還是針對購物車 </p>
<p class="h4"> 試著把函式和參數改成更通用的名稱 </p>
```javascript
/* function add_item(cart, item) {
var new_cart = cart.slice();
new_cart.push(item);
return new_cart;
} */
function add_element_last(array, elem) { // 通用的名稱,可用於任何陣列和元素
var new_array = array.slice();
new_array.push(elem);
return new_array;
}
```
---
<p class="h3"> 抽離寫時複製(copy-on-write)模式 </p>
<p class="h4"> 改名後我們就可以非常簡單地定義 <code>add_item()</code> </p>
```javascript
function add_item(cart, item) {
return add_element_last(cart, item);
}
```
<p class="p"> 這樣就抽離出一個高度可重用的函式,能作用於任何陣列和元素,而不侷限於購物車與商品 </p>
---
<p class="h3"> 使用 <code>add_item()</code> </p>
<p class="h4"> <code>add_item()</code> 之前接受三個參數:cart、name 和 price,現在只接受 cart、item </p>
<p class="h4"> 代表我們需要修改那些呼叫 <code>add_item()</code> 的函式,傳入正確的參數數量 </p>
```javascript
function add_item_to_cart(name, price) {
shopping_cart = add_item(shopping_cart, name, price); // 原先的寫法
var total = calc_total(shopping_cart);
set_cart_total_dom(total);
update_shipping_icons(shopping_cart);
update_tax_dom(total);
}
```
```javascript
function add_item_to_cart(name, price) {
var item = make_cart_item(name, price); // 先建構出 item
shopping_cart = add_item(shopping_cart, item); // 再把它傳給 add_item()
var total = calc_total(shopping_cart);
set_cart_total_dom(total);
update_shipping_icons(shopping_cart);
update_tax_dom(total);
}
```
---
<p class="h3"> 幫 Calculation 函式分類 </p>
<p class="h4"> 在修改了程式碼之後,再一次檢視這些 Calculation 函式 </p>
<div class="left" style="margin-left: 180px;">
<p class="h4"> 分類對照表 </p>
<p class="p"> C - Cart 操作</p>
<p class="p"> I - Item 操作 </p>
<p class="p"> B - 業務規則 </p>
<p class="p"> A - Array 工具 </p>
</div>
---
<p class="h3"> 幫 Calculation 函式分類 </p>
```javascript
function add_element_last(array, elem) { // A - Array 工具
var new_array = array.slice();
new_array.push(elem);
return new_array;
}
function add_item(cart, item) { // C - Cart 操作
return add_element_last(cart, item);
}
```
```javascript
function make_cart_item(name, price) { // I: Item 操作
return {
name: name,
price: price
};
}
```
---
<p class="h3"> 幫 Calculation 函式分類 </p>
```javascript
function calc_total(cart) { // C I B - Cart + Item + 業務規則
var total = 0;
for (var i = 0; i < cart.length; i++) {
var item = cart[i];
total += item.price;
}
return total;
}
```
```javascript
function gets_free_shipping(cart) { // B - 業務規則
return calc_total(cart) >= 20;
}
// B: Business rule
function calc_tax(amount) { // B - 業務規則
return amount * 0.10;
}
```
---
<p class="h3"> 觀念討論 </p>
<div class="left" style="margin: -32px 48px;">
<p class="h4"> Q1: 為什麼我們要再把函式分類成 C、I、B、A?</p>
<p class="p" style="margin-top: -10px;"> A: 這只是對本書後面要學習的設計技巧做個鋪陳,最後會把這些分組整理成完全獨立的層次 </p>
<p class="h4" style="margin-top: 48px;"> Q2: 那麼業務規則和購物車操作有什麼差別?電商業務不就是處理購物車嗎? </p>
<p class="p" style="margin-top: -10px;"> A: 可以想成,多數電商都有購物車功能,這些購物車相關的操作對任何電商平台來說都是通用的 </p>
<p class="p" style="margin: -10px 21px 0;"> 但「業務規則」則是針對 MegaMart 這家公司的獨特做法,例如在別家商店也會有購物車</p>
<p class="p" style="margin: 8px 21px 0;"> 但不一定會有同樣的免運門檻 </p>
</div>
---
<p class="h3"> 觀念討論 </p>
<div class="left" style="margin: -32px 48px;">
<p class="h4"> Q3: 一個函式可以同時算是「業務規則」又是「購物車操作」嗎?</p>
<p class="p" style="margin-top: -10px;"> A: 目前看來確實可以,但這也是一種 Code Smell,當後續開始討論分層時,可能要把它們分開</p>
<p class="p" style="margin: -10px 21px 0;"> 如果業務規則必須知道購物車是陣列實作,那可能帶來未來的維護問題,因為通常業務規則 </p>
<p class="p" style="margin: 8px 21px 0;"> 變動購物車這類底層結構更頻繁 </p>
</div>
---
<p class="h3"> 更小的函式與更多 Calculations </p>
```javascript
var shopping_cart = []; // A,定義全域變數
function add_item_to_cart(name, price) { // A
var item = make_cart_item(name, price);
shopping_cart = add_item(shopping_cart, item); // 讀取全域變數
var total = calc_total(shopping_cart);
set_cart_total_dom(total);
update_shipping_icons(shopping_cart);
update_tax_dom(total);
}
function update_shipping_icons(cart) { // A
var buttons = get_buy_buttons_dom();
for (var i = 0; i < buttons.length; i++) {
var button = buttons[i];
var item = button.item;
var new_cart = add_item(cart, item);
if (gets_free_shipping(new_cart))
button.show_free_shipping_icon(); // 操作 DOM
else
button.hide_free_shipping_icon();
}
}
function update_tax_dom(total) { // A
set_tax_dom(calc_tax(total)); // 操作 DOM
}
function add_element_last(array, elem) { // C 沒有隱式輸入/輸出
var new_array = array.slice();
new_array.push(elem);
return new_array;
}
function add_item(cart, item) {
return add_element_last(cart, item); // C 沒有隱式輸入/輸出
}
function make_cart_item(name, price) { // C 沒有隱式輸入/輸出
return {
name: name,
price: price
};
}
function calc_total(cart) { // C 沒有隱式輸入/輸出
var total = 0;
for (var i = 0; i < cart.length; i++) {
var item = cart[i];
total += item.price;
}
return total;
}
function gets_free_shipping(cart) { // C 沒有隱式輸入/輸出
return calc_total(cart) >= 20;
}
function calc_tax(amount) { // C 沒有隱式輸入/輸出
return amount * 0.10;
}
```
---
<p class="h3"> 小結 </p>
<p class="h4"> 現在的 Action 函式不再需要了解資料的結構 </p>
<p class="h4"> 出現許多有用且可重用的介面函式 </p>
<p class="h4"> 購物車中還潛伏著更多的 bug,後續章節會持續探討 </p>
---
<p class="h3"> 第五章總結 </p>
<div class="left" style="margin: -16px 144px;">
<p class="h4" style="font-weight: 400;">1. 通則是,我們應該透過「參數」和「回傳值」來取代所有隱 </p>
<p class="h4" style="font-weight: 400; margin: -16px 24px 0;"> 式輸入與隱式輸出 </p>
<p class="h4" style="font-weight: 400;">2. 設計就是要將事物拆解,拆開後的部分隨時都能再組合回來 </p>
<p class="h4" style="font-weight: 400;">3. 我們將程式拆得更細,並讓每個函式只負責一件事,就會發</p>
<p class="h4" style="font-weight: 400; margin: -16px 24px 0;">現更容易依照概念來組織它們 </p>
</div>
---
<p class="h3"> 第五章 Q&A </p>
<div class="left" style="margin-left: 48px;">
<p class="p" style="font-weight: 700;"> Q1: ”我們應該透過「OO」和「XXX」來取代所有隱式輸入輸出“ => OO 跟 XXX 是什麼呢? </p>
</div>
---
<p class="h3"> 第五章 Q&A </p>
<div class="left" style="margin-left: 48px;">
<p class="p" style="font-weight: 700;"> Q1: ”我們應該透過「OO」和「XXX」來取代所有隱式輸入輸出“ => OO 跟 XXX 是什麼呢? </p>
<p class="p" style="font-weight: 700;"> A: 「參數」及「回傳值」</p>
</div>
---
<p class="h3"> 第五章 Q&A </p>
<div class="left" style="margin-left: 48px;">
<p class="p" style="font-weight: 700;"> Q2: 下列程式碼在設計上有哪些地方可以再優化呢? </p>
</div>
```javascript
function gets_free_shipping(total, item_price) {
return item_price + total >= 20;
}
```
```javascript
function calc_total(cart) {
var total = 0;
for (var i = 0; i < cart.length; i++) {
var item = cart[i];
total += item.price;
}
return total;
}
```
---
<p class="h3"> 第五章 Q&A </p>
<div class="left" style="margin-left: 48px;">
<p class="p" style="font-weight: 700;"> Q2: 下列程式碼在設計上有哪些地方可以再優化呢? </p>
</div>
```javascript
function gets_free_shipping(total, item_price) { // 直接帶入購物車為參數會更好
return item_price + total >= 20; // 這裡重複計算了 (總額 + 單品價格)
}
```
```javascript
function calc_total(cart) {
var total = 0;
for (var i = 0; i < cart.length; i++) {
var item = cart[i];
total += item.price; // 這裡重複計算了 (總額 + 單品價格)
}
return total;
}
```
---
<p class="h3"> 第五章 Q&A </p>
<div class="left" style="margin-left: 48px;">
<p class="p" style="font-weight: 700;"> Q3: 在過去章節進行隱式輸入輸出替換的過程中,程式碼行數不斷增加,這樣有好處嗎? </p>
</div>
---
<p class="h3"> 第五章 Q&A </p>
<div class="left" style="margin-left: 48px;">
<p class="p" style="font-weight: 700;"> Q3: 在過去章節進行隱式輸入輸出替換的過程中,程式碼行數不斷增加,這樣有好處嗎? </p>
<p class="p" style="font-weight: 700;"> A: 雖然行數不斷增加,但個別函式越精簡,這樣也就更容易理解和維護 </p>
</div>
---
<p class="h3"> 第五章 Q&A </p>
<div class="left" style="margin-left: 48px;">
<p class="p" style="font-weight: 700;"> Q4: 一般在 JavaScript 中,會用什麼方式直接複製一個陣列 </p>
<p class="p" style="font-weight: 700;"> 1. <code>.copy()</code> </p>
<p class="p" style="font-weight: 700;"> 2. <code>.clone()</code> </p>
<p class="p" style="font-weight: 700;"> 3. <code>.duplicate()</code> </p>
<p class="p" style="font-weight: 700;"> 4. <code>.slice()</code> </p>
</div>
---
<p class="h3"> 第五章 Q&A </p>
<div class="left" style="margin-left: 48px;">
<p class="p" style="font-weight: 700;"> Q4: 一般在 JavaScript 中,會用什麼方式直接複製一個陣列 </p>
<p class="p" style="font-weight: 700;"> 1. <code>.copy()</code> </p>
<p class="p" style="font-weight: 700;"> 2. <code>.clone()</code> </p>
<p class="p" style="font-weight: 700;"> 3. <code>.duplicate()</code> </p>
<p class="p" style="font-weight: 700;"> 4. <code>.slice()</code> ✅ </p>
</div>
---
<p class="h3"> 第五章 Q&A </p>
<div class="left" style="margin-left: 48px;">
<p class="p" style="font-weight: 700;"> Q5: 章節中有提到一個實現不可變性的策略是:先複製再修改再回傳,這種策略稱為?</p>
<p class="p" style="font-weight: 700;"> 1. 即時複製 (Instant Copy) </p>
<p class="p" style="font-weight: 700;"> 2. 讀時複製 (Copy-on-Read) </p>
<p class="p" style="font-weight: 700;"> 3. 寫時複製 (Copy-on-Write) </p>
<p class="p" style="font-weight: 700;"> 4. 延遲複製 (Lazy Copy) </p>
</div>
---
<p class="h3"> 第五章 Q&A </p>
<div class="left" style="margin-left: 48px;">
<p class="p" style="font-weight: 700;"> Q5: 章節中有提到一個實現不可變性的策略是:先複製再修改再回傳,這種策略稱為?</p>
<p class="p" style="font-weight: 700;"> 1. 即時複製 (Instant Copy) </p>
<p class="p" style="font-weight: 700;"> 2. 讀時複製 (Copy-on-Read) </p>
<p class="p" style="font-weight: 700;"> 3. 寫時複製 (Copy-on-Write) ✅ </p>
<p class="p" style="font-weight: 700;"> 4. 延遲複製 (Lazy Copy) </p>
</div>
---
<div style="position: relative; min-width: 1080px; min-height: 320px;">
<p class="h3 fixed" style="top: 100px; left: 50%; transform: translateX(-50%); width:1080px;" > 下次讀書會預告 </p>
<p class="h4 fixed" style="top: 168px; left: 50%; transform: translateX(-50%); width:1080px;" > 在接下來的兩章,我們將仔細探討「不可變性」: </p>
<p class="h4 fixed" style="top: 208px; left: 50%; transform: translateX(-50%); width:1080px;" > 如何在撰寫新程式時實現不可變性,同時又能與現有程式互動? </p>
<p class="h4 fixed" style="right: 132px; bottom: -40px;"
>Presenter : Yo0</p>
<p class="h4 fixed" style="right: 76px; bottom: -80px;"
>Note Taker : Hannah</p>
<p class="h4 fixed" style="right: 106px; bottom: -120px;"
>Date : 2025/05/01</p>
</div>