摘要:編寫可維護的代碼前言我們在修改他人代碼的時候,閱讀他人代碼所花的時間經常比實現功能的時間還要更多如果程序結構不清晰,代碼混亂。這樣可以去除重復的代碼,提高靈活性關鍵點找出不同的地方和重復的地方。
編寫可維護的代碼 前言
我們在修改他人代碼的時候,閱讀他人代碼所花的時間經常比實現功能的時間還要更多
如果程序結構不清晰,代碼混亂 。牽一發而動全身。那維護起來就更難維護了
可讀性可理解性:他人可以接手代碼并理解它
直觀性 : 代碼邏輯清晰
可調試性 :出錯時,方便定位問題所在
如何提高可讀性代碼格式化
適當添加注釋
函數與方法
大段代碼
注釋需有意義
如何優化代碼找出代碼的壞味道
使用重構手法將其解決掉
代碼的壞味道在我們的程序中,可以聞到很多的壞味道。主要有以下這些點
命名不規范或無意義命名存在使用縮寫、不規范、無意義
例子:var a = xxx,b = xxx
重復代碼相同(或相似)的代碼在項目中出現了多次,如果需求發生更改,則需要同時修改多個地方
過長函數程序越長越難理解,一個函數應該只完成一個功能
過長的類一個類的職責過多,一個類應該是一個獨立的整體。
過長參數列表太長的參數列表難以理解,不易使用。當需要修改的時候,會更加容易出錯
數據泥團有些數據項總是成群結隊的待在一起。例如兩個類中相同的字段、許多函數簽名相同的參數。
這些都應該提煉到一個對象中,將很多參數列縮短,簡化函數調用
類似的函數整體上實現的功能差不多,但是由于有一點點區別。所以寫成了多個函數
重構手法 提煉函數針對一個比較長的函數,提煉成一個個完成特定功能的函數。
例子// 提煉前 function test11() { var day = $("day"); var yearVal = "2016"; var monthVal = "10"; var dayVal = "10"; day.val(dayVal); switch (monthVal) { case 4: case 6: case 9: case 11: if (dayVal > 30) { day.val(30); } break; case 2: if ( yearVal % 4 == 0 && (yearVal % 100 != 0 || yearVal % 400 == 0) && monthVal == 2 ) { if (dayVal > 29) { day.val(29); } } else { if (dayVal > 28) { day.val(28); } } break; default: if (dayVal > 31) { day.val(31); } } }
// 提煉后 function test12() { var day = $("day"); var yearVal = "2016"; var monthVal = "10"; var dayVal = "10"; var maxDay = getMaxDay(yearVal, monthVal); if (dayVal > maxDay) { day.val(maxDay); } else { day.val(dayVal); } } function getMaxDay(year, month) { var maxDay = 0; switch (month) { case 4: case 6: case 9: case 11: maxDay = 30; break; case 2: if (year % 4 == 0 && (year % 100 != 0 || year % 400 == 0)) { maxDay = 29; } else { maxDay = 28; } break; default: maxDay = 31; } return maxDay; }
例子中,提煉前的代碼,需要很費勁的看完整個函數,才會明白做了什么處理,提煉后的代碼。只需要稍微看一下,就知道 getMaxDay 是獲取當前月份的最大天數優點:
如果每個函數的粒度都很小,那么函數被復用的機會就更大;
這會使高層函數讀起來就想一系列注釋;
如果函數都是細粒度,那么函數的覆寫也會更容易些
內聯函數有時候,一個函數的本體與函數名一樣簡單易懂,就要用到這種手法。
這種手法用于處理優化過度的問題
舉個例子:
function biggerThanZero(num) { return num > 0; } function test() { var num = 10; if (biggerThanZero(num)) { //do something } } //內聯后 function test() { var num = 10; if (num > 0) { //do something } }引入解釋性變量
當表達式比較復雜難以閱讀的時候,就可以通過臨時變量來幫助你將表達式分解為容易管理的形式
有些時候,運用提煉函數會更好一點
舉兩個簡單的例子:
// 例子 1 // before function test2() { if ( platform.toUpperCase().indexOf("MAC") > -1 && browser.toUpperCase().indexOf("IE") > -1 && wasInitialized() && resize > 0 ) { // do something } } // after function test2() { var isMacOs = platform.toUpperCase().indexOf("MAC") > -1; var isIEBrowser = browser.toUpperCase().indexOf("IE") > -1; var wasResized = resize > 0; if (isMacOs && isIEBrowser && wasInitialized() && wasResized) { // do something } } // -------------------------------------------------- // 例子2 // before function caluPrice(quantity, itemPrice) { return ( quantity * itemPrice - Math.max(0, quantity - 500) * itemPrice * 0.05 + Math.min(quantity * itemPrice * 0.1, 100) ); } // after function caluPrice(quantity, itemPrice) { var basePrice = quantity * itemPrice; var discount = Math.max(0, quantity - 500) * itemPrice * 0.05; var shiping = Math.min(basePrice * 0.1, 100); return basePrice - discount + shiping; }
在兩個例子中,引入解釋性的變量之后,可讀性大大增加。函數的意圖就比較明顯,單看變量命名就已經能大概知道具體的實現分解臨時變量
除了 for 循環里用來收集結果的變量,其他的臨時變量都應該只被賦值一次。
因為被賦值超過一次,就意味著他在函數中承擔了多個責任。
一個變量承擔多個責任。會令代碼看起來容易迷惑
舉個例子:
// 分解臨時變量 // before function test3() { var temp = 2 * (width + height); console.log(temp); // do something temp = height * width; // do something console.log(temp); } // after function test4() { var perimeter = 2 * (width + height); console.log(perimeter); // do something var area = height * width; // do something console.log(area); }
在這個例子中,temp 分別被賦予了兩次,如果代碼塊較長的情況,會增加風險,因為你不知道他在哪里被改掉了替換算法
當你重構的時候,發現實現同樣的功能有一個更清晰的方式,就應該將原有的算法替換成你的算法。
舉個例子:
// 替換算法 // before function getWeekDay() { var weekStr = ""; switch (date.format("d")) { case 0: weekStr = "日"; break; case 1: weekStr = "一"; break; case 2: weekStr = "二"; break; case 3: weekStr = "三"; break; case 4: weekStr = "四"; break; case 5: weekStr = "五"; break; case 6: weekStr = "六"; break; } return weekStr; } // after function getWeekDay() { var weekDays = ["日", "一", "二", "三", "四", "五", "六"]; return weekDays[date.format("d")]; }以字面常量取代魔法數(eg:狀態碼)
在計算機科學中,魔法數是歷史最悠久的不良現象之一。
魔法數是指程序中莫名其妙的數字。擁有特殊意義,卻又不能明確表現出這種意義的數字
舉個例子:
// before function test5(x) { if (x == 1) { console.log("完成"); } else if (x == 2) { console.log("上傳中"); } else if (x == 3) { console.log("上傳失敗"); } else { console.log("未知的錯誤"); } } function test6(x) { if (x == 3) { // do something } } // after var UploadStatus = { START: 0, UPLOADING: 1, SUCCESS: 2, ERROR: 3, UNKNOWN: 4 }; function test7(x) { if (x == UploadStatus.START) { console.log("未開始"); } else if (x == UploadStatus.UPLOADING) { console.log("上傳中"); } else if (x == UploadStatus.SUCCESS) { console.log("上傳成功"); } else if (x == UploadStatus.ERROR) { console.log("上傳失敗"); } else { console.log("未知的錯誤"); } } function test8(x) { if (x == UploadStatus.ERROR) { // do something } }
對于魔法數,應該用一個枚舉對象或一個常量來賦予其可見的意義。這樣,你在用到的時候,就能夠明確的知道它代表的是什么意思分解條件表達式
而且,當需求變化的時候,只需要改變一個地方即可
復雜的條件邏輯是導致復雜度上升的地點之一。因為必須編寫代碼來處理不同的分支,很容易就寫出一個相當長的函數
將每個分支條件分解成新函數可以突出條件邏輯,更清楚表明每個分支的作用以及原因
舉個例子:
// 分解條件表達式 // 商品在冬季和夏季單價不一樣 // before var SUMMER_START = "06-01"; var SUMMER_END = "09-01"; function test9() { var quantity = 2; var winterRate = 0.5; var winterServiceCharge = 9; var summerRate = 0.6; var charge = 0; if (date.before(SUMMER_START) || date.after(SUMMER_END)) { charge = quantity * winterRate + winterServiceCharge; } else { charge = quantity * summerRate; } return charge; } // after function test9() { var quantity = 2; return notSummer(date) ? winterCharge(quantity) : summerCharge(quantity); } function notSummer(date) { return date.before(SUMMER_START) || date.after(SUMMER_END); } function summerCharge(quantity) { var summerRate = 0.6; return quantity * summerRate; } function winterCharge(quantity) { var winterRate = 0.5; var winterServiceCharge = 9; return quantity * winterRate + winterServiceCharge; }合并條件表達式
當發現一系列的條件檢查,檢查條件不一樣,但是行為卻一致。就可以將它們合并為一個條件表達式
舉個例子:
// 合并條件表達式 // before function test10(x) { var isFireFox = "xxxx"; var isIE = "xxxx"; var isChrome = "xxxx"; if (isFireFox) { return true; } if (isIE) { return true; } if (isChrome) { return true; } return false; } // after function test10(x) { var isFireFox = "xxxx"; var isIE = "xxxx"; var isChrome = "xxxx"; if (isFireFox || isIE || isChrome) { return true; } return false; }
合并后的代碼會告訴你,實際上只有一個條件檢查,只是有多個并列條件需要檢查而已合并重復的條件片段
條件表達式上有著相同的一段代碼,就應該將它搬離出來
// 合并重復片段 // before function test11(isSpecial) { var total, price = 1; if (isSpecial) { total = price * 0.95; // 這里處理一些業務 } else { total = price * 0.8; // 這里處理一些業務 } } // after function test12(isSpecial) { var total, price = 1; if (isSpecial) { total = price * 0.95; } else { total = price * 0.8; } // 這里處理一些業務 }
在不同的條件里面做了同樣的事情,應該將其抽離出條件判斷。這樣代碼量少而且邏輯更加清晰以衛語句取代嵌套條件表達式
如果某個條件較為罕見,應該多帶帶檢查該條件,并在該條件為真時立即從函數中返回。這樣的檢查就叫衛語句
舉個例子:
// 以衛語句取代嵌套條件表達式 // before function getPayMent() { var result = 0; if (isDead) { result = deadAmount(); } else { if (isSepartated) { result = separtedAmount(); } else { if (isRetired) { result = retiredAmount(); } else { result = normalPayAmount(); } } } return result; } // after function getPayMent() { if (isDead) { return deadAmount(); } if (isSepartated) { return separtedAmount(); } if (isRetired) { return retiredAmount(); } return normalPayAmount(); }函數改名(命名)
當函數名稱不能表達函數的用途,就應該改名
變量和函數應使用合乎邏輯的名字。
eg:獲取產品列表 -> getProductList()
變量名應為名詞,因為變量名描述的大部分是一個事物。
eg: 產品 -> product
函數名應為動詞開始,因為函數描述的是一個動作
eg:獲取產品列表 -> getProductList()將查詢函數和修改函數分開
如果某個函數只向你提供一個值,沒有任何副作用。這個函數就可以任意的調用。
這樣的函數稱為純函數
如果遇到一個既有返回值,又有副作用的函數。就應該將查詢與修改動作分離出來
舉個例子:
// before function test13(people) { for (var i = 0, len = people.length; i < len; i++) { if (people[i].name == "andy") { // do something 例如進行DOM 操作之類的 return "andy"; } if (people[i].name == "ChunYang") { // do something 例如進行DOM 操作之類的 return "ChunYang"; } } } // after function test14(people) { var p = find(people); // do something 例如進行DOM 操作之類的 // doSomeThing(p); } function find(people) { for (var i = 0, len = people.length; i < len; i++) { if (people[i].name == "andy") { return "andy"; } if (people[i].name == "ChunYang") { return "ChunYang"; } } }令函數攜帶參數
如果發現兩個函數,做著類似的工作。區別只在于其中幾個變量的不同。就可以通過參數來處理。
這樣可以去除重復的代碼,提高靈活性
關鍵點: 找出不同的地方和重復的地方。
推薦書籍《重構 改善既有代碼的設計 》 基于 java 的
《代碼大全》
相關鏈接個人博客
代碼片段
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/100248.html
摘要:沒有初始化的變量都會賦值為盡量避免使用因為沒有聲明的變量也會判斷為類型。對象直接量,不建議使用構造函數創建對象數組直接量,不建議使用構造函數創建數組 編寫可維護代碼的重要性 程序是給人讀的,只是偶爾給機器運行一下 1、軟件生命周期的80%成本是發生在為維護上;2、幾乎所有的軟件維護者都不是最初的創建者;3、編寫規范提高了軟件代碼的可讀性,它讓軟件工程師快速充分的理解代碼; 編寫規范 縮...
摘要:最近讀完編寫可維護的,讓我受益匪淺,它指明了編碼過程中,需要注意的方方面面,在團隊協作中特別有用,可維護性是一個非常大的話題,這本書是一個不錯的起點。擴展閱讀編寫可維護的歡迎來到石佳劼的博客,如有疑問,請在原文評論區留言,我會盡量為您解答。 最近讀完《編寫可維護的JavaScript》,讓我受益匪淺,它指明了編碼過程中,需要注意的方方面面,在團隊協作中特別有用,可維護性是一個非常大的話...
摘要:與此類似,理所當然的,我們程序員也會有自己的圣經。這便是程序員的圣經三個原則我認為做為一個程序員,最神圣的就是三個原則,它幾乎能完整無誤的定義做為一個程序員應該如何去編碼。 ...
摘要:寫在前面新司機最近讀完編寫可維護的,學到不少東西。書分為編程風格編程實踐自動化三個部分。編程風格并不是絕對的,每個人或團隊都有自己的編程風格,但知道哪些地方需要注意的話,還是有助于新司機完成代碼風格的轉變。 寫在前面 新司機最近讀完《編寫可維護的JavaScript》,學到不少東西。書分為編程風格、編程實踐、自動化三個部分。其中編程風格是你的代碼格式約定,統一的格式不僅僅有利于團隊,也...
摘要:所有的塊語句都應當使用花括號包括花括號的對齊方式第一種風格第二種風格塊語句間隔第一種在語句名圓括號和左花括號之間沒有空格間隔第二種在左圓括號之前和右圓括號之后各添加一個空格第三種在左圓括號后和右圓括號前各添加一個空格我個人喜歡在右括號之后添 所有的塊語句都應當使用花括號, 包括: if for while do...while... try...catch...finally 3....
閱讀 731·2021-11-17 09:33
閱讀 3761·2021-09-01 10:46
閱讀 1756·2019-08-30 11:02
閱讀 3284·2019-08-29 15:05
閱讀 1401·2019-08-26 11:39
閱讀 2278·2019-08-23 17:04
閱讀 1978·2019-08-23 15:43
閱讀 1376·2019-08-23 14:12