# Ch3-1、2 #### 前言 ##### 1. Ch2-了解為何需要重構(How)(小複習) - 目的: 使軟體內部程式更**容易理解、修改、加速開發流程**。 - 時間: 不是另外特別花時間重構,遇到 **feature** 時來做。 - 成效: 有重構**長時間**而言,整體軟體品質好的**容易修改、理解**。 <!-- ~~ 希望軟體長期是紅線樣子->長期重構可以加速軟體開發的進程。(功能越多.開發時程還是很順。不會中後期因為架構不好很難增加新功能、修改很辛苦)~~ --> ![](https://i.imgur.com/gxJDFiS.png) ##### 2. Ch3-使用時機(When) - 注意: 這章講的不是一個標準。 作者:沒有一個標準比得上一個見識廣博者的直覺。 - 培養敏感度、判斷力的章節 提出跡象=>這裡有一個可以用重構來解決的問題。 ------------- #### 一、Mysterious Name 神秘的名稱 ##### 優缺點: - (優)好名稱明確傳達如何被使用。 - (優)省下好幾個小時的疑惑,不用再去看實作的程式碼。 - (缺)很難一次取對名稱。 一開始可能不清楚怎麼命名,隨著時間逐漸知道關節應該長什麼樣子,而不斷改善程式 - (缺)人通常很害怕改變名稱認為不用這麼麻煩。 ##### 目的&效果 作者最常做的重構 => 重新命名 1. 將難懂的名稱變清楚來簡化程式。 2. 重新命名不僅僅是改變名稱.當你無法幫某個東西想個好名稱.往往代表深層設計不良。 ##### Tips - 修改時機: 當知道更好的名稱時.立刻改動.之後需要再次釐清來龍去脈。 - 修改地方: function、param、class - 命名方法: 寫下註解來說明函式的用途,再把註解變成一個名稱 (根據函式的目的、做什麼來命名,而不是如何如何做那件事情) - 作法: 1.宣告成你要的樣子 2.使用副本把新舊名稱都放上去 (移除時確保內文沒有使用它.或先多寫測試完再移除) 3.每個引用舊方法的地方更新 4.測試 =>分別進行修改,用不同步驟分別處理它 (分區塊一直重複上面3個步驟) (小步驟代表每個步驟容易出錯的地方,切成小步驟修改出錯率降低減少工作量) ##### 範例 - Change Function Declaration 147 改函式宣告 (ch6) ``` // 名稱過度簡寫 function circum(radius) {...} //環繞(半徑) // 1.修改宣告名稱 function circumference(radius) {...} //圓周(半徑) // 2.把使用到的名稱都修改 // 3.測試 ``` - Rename Variable 162 (ch6) -最容易改變的變數是區域變數。 -作法:使用副本藉由複製方式逐漸修改名稱。 (副本>舊名稱改成新名稱>測試(失敗回去舊名稱)) ``` const companyName = "Acme Gooseberries"; const cpyNm = companyName; ``` - Rename Field 285 更改欄位名稱 (ch9) -影響比較大範圍在細拆。 -class.資料結構、欄位名稱 Fred Brooks: 如果你讓我看一下流程圖,再把圖表收起來,我會繼續困惑不解。但是如果你讓我看一下表格,我通常就不需要流程圖,他們會歷歷在目。(資料結構是了解來龍去脈的關鍵) 目標: 希望把 name 換成 title ``` const organization = {name: 'Acme Gooseberries', country: "GB"}; // name 換成 title class Organization{ constructor(data){ this._name = data.name; this._country = data.country; } get name() {return this._name;} set name(aString) {this._name = aString;} get country() {return this._country;} set country(aCountryCode) {this._country = aCountryCode;} } ``` step1. 先改名稱: _name 換成 _title ``` class Organization{ constructor(data){ this._title = data.name; // 改名稱。(原)this._name = data.name; this._country = data.country; } get name() {return this._title;} // 改名稱。(原) {return this._name;} set name(aString) {this._title = aString;} // 改名稱。(原) {this._name = aString;} get country() {return this._country;} set country(aCountryCode) {this._country = aCountryCode;} } ``` step2. 保留新舊名稱 -class 底層的資料可能被很多地方使用。 用物件容納資料.而不是紀錄。(Encapsulate Record 188 ch7) ``` class Organization{ constructor(data){ // 2. 新舊都保留(title 優先) // 原: this._title = data.name; this._title = (data.title !== undefined)? data.title: data.name; this._country = data.country; } get name() {return this._title;} set name(aString) {this._title = aString;} get country() {return this._country;} set country(aCountryCode) {this._country = aCountryCode;} } ``` step3. 一一檢查使用到的地方換成新的名稱 ``` const organization = new Organization({title: 'Acme Gooseberries', country: "GB"}); ``` step4. 移除舊名支援 ``` class Organization{ constructor(data){ // 2. 移除舊名 // 原: this._title = (data.title !== undefined)? data.title: data.name; this._title = data.title; this._country = data.country; } get name() {return this._title;} set name(aString) {this._title = aString;} get country() {return this._country;} set country(aCountryCode) {this._country = aCountryCode;} } ``` step5. 建構式、資料使用新名稱後,開始修改存取函式。 ``` class Organization{ constructor(data){ this._title = data.title; this._country = data.country; } get title() {return this._title;} // 改名稱。(原) get name() set title(aString) {this._title = aString;} // 改名稱。(原) get name(aString) get country() {return this._country;} set country(aCountryCode) {this._country = aCountryCode;} } ``` #### 補充: 如何命名 ##### 目的: 一眼看出含意、種類 (*可讀性、一致性_相同的規則命名整個程式的變數) ##### 類型與使用情境: 一般可以使用: 英文、數字(不可開頭)、特殊符號$_。 - Pascal Case 大駝峰 (ex: UserName、FileName) component 檔名、typeScript 的 interface - Camel Case 小駝峰 (最常用) (ex: userName、fileName) - Snack Case _分離 (最常用) (ex: user_name、file_name) - Kebab Case -分離 (ex: user-name、file-name) 1. html的裡面的class和id ``` <div class="col-md-6" id="user-password"></div> ``` 2. restful api的分隔符號 ``` http://localhost/api/order-list ``` - Screaming Case (ex: USER_NAME、FILE_NAME) 1. 常量變數(constant variable) ``` MAX_EXECUTION_TIME = 60; ``` 2. enum 枚舉 數學、計算機科學中”集合的名詞” (ex: Level、Week) ``` enum Level { LOW, MEDIUM, HIGH } ``` - 私有變數 -前面加「_」。 -多使用在物件導向程式。函式、屬性上可以使用。 -目的:警示開發者不要使用該私有變數,不要動到! ``` class Organization{ constructor(data){ this._title = data.title; this._country = data.country; } } ``` > 補: #開頭,私有變數有些有強制有些沒有 > https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Classes/Private_class_fields - 型態命名 1. object 取名 userObj。 2. array 取名 userList、userArray。 (比命名 users 更清楚相關訊息,不是取型態 userString、strUser) 3. boolean 取名 isLoading。 - is/has/can + 名詞或形容詞 (isUser、hasName、canSignup、hidden、available) - enable/disable + 動詞 (enableOrder、disableUpate) - function 返回 boolean,前面可以加 check 注意下面第10項 (checkHasName()、checkIsUser()) ##### 命名方式參考: 1. 團隊共識。 2. 開發時,有更適合的變數名稱立刻更改,不要忌諱隨時改名。 3. 在清楚表達的前提下,名稱越短越好。 4. 區分單複數。 單數 user。 複數 users、userArray、userList、userCollection。 5. 選擇好單字使用到底。 ex: user 換成 member、get 換成 fetch、retrieve。 6. 變數組成避免單一動詞,加一點形容詞、名詞。 ex: create = 1 > 使用「動詞-名詞-形容詞-副詞」命名,可以清晰、簡潔了解函式或變數的目的和行為。 > 動詞-函式或變數的動作 (ex:get, fetch, calculate, filter) > 名詞-它們的含意 (ex:data, information, results, options) > 形容詞-它們的屬性、特徵 (ex:sorted, filtered, current, previous) > 副詞-它們的方式、特定條件或環境 (ex:quickly, easily, efficiently, correctly) > --> ex:getData、processTask、filterResults、sortArray。 7. 有異議或公認的縮寫。 -專案中有獨有的縮寫.利用註釋、準備備用語集,來幫助新成員理解。 ex: id(identity)、int(integer)。 ex: order system 裡面的 user, 不好的縮寫: os_user、soUser。 比較好的: system_order_user或 systemOrderUser。 8. 使用無意義的單字、字母時,確保作用域很小。 作用域越廣,名稱需要越清楚描述變數的意思。 ex: i, j, data,使用在(for loop、if block) 9. 具備可搜尋性.盡量不要使用無意義的單字 ex: data、temp、value,使用太多沒有分辨性、意義。 10. 選擇不會模擬兩可的單字 checkMessage: 可能被解讀成很多訊息 -檢查訊息是否存在 existsMessage -檢查格式是否正確 isMessageFormatValid -詢問伺服器是否有新訊息 queryNewMessage -過濾滿足某些條件的訊息 takeMessageIf ![](https://i.imgur.com/ttfjI93.jpg) ##### 參考資料 - https://medium.com/程式愛好者/變數命名-f53cd1115076 - coding style https://ithelp.ithome.com.tw/articles/10197116 - https://hackmd.io/@HITSC/NamingConvention - https://engineering.linecorp.com/zh-hant/blog/code-readability-vol2-ch/ ------------- #### 二、Duplicated Code 重複的程式碼 在一個以上的地方看到重複的程式,重構是為了減少重複看.比較程式之間的差異 ##### Extract Function (一樣)(ch6) 90%情況可以使用這個縮短函式 - 有些判斷情況: 程式長度超過螢幕、作者認為超過6行、重複程式(但比較推下面方式) - ***意圖與實作分離** => **需要花時間理解的程式碼應該拉成函式** ``` function printOwing(invoice) { printBanner(); let outstanding = calculateOutstanding(); //print details console.log(`name: ${invoice.customer}`); console.log(`amount: ${outstanding}`); } // Refactor to: function printOwing(invoice) { printBanner(); let outstanding = calculateOutstanding(); printDetails(outstanding); //實作獨立 function printDetails(outstanding) { console.log(`name: ${invoice.customer}`); console.log(`amount: ${outstanding}`); } } ``` ##### Slide Statements (類似)(ch8) - 類似、相關的程式放在一起容易理解,中間明確隔開的函式。 ex: 宣告的變數放在使用的放面,不是統一放在最上面。 ``` const pricingPlan = retrievePricingPlan(); //定價計畫 = 檢索定價計劃 const order = retrieveOrder(); //訂單 = 檢索訂單 let charge; //收費 const chargePerUnit = pricingPlan.unit; //每單位收費 = 定價計畫.單元 // Refactor to: const pricingPlan = retrievePricingPlan(); //pricingPlan const chargePerUnit = pricingPlan.unit; //pricingPlan const order = retrieveOrder(); let charge; ``` ##### Pull Up Method (class)(ch12) - 子類別相同.使用的方法相同直接重構.看看測試狀況。 ``` class Employee {...} class Salesman extends Employee { get name() {...} } class Engineer extends Employee { get name() {...} } // refactor to: class Employee { get name() {...} } class Salesman extends Employee {...} class Engineer extends Employee {...} ```