『No25: 編寫可讀程式碼的藝術(2)』

GOPHER_VIKING.png
大家好,我叫謝偉,是一名程式員。
上節從程式語言特性的角度講述了編寫可讀程式碼的幾個要點。
ofollow,noindex">編寫可讀的程式碼的藝術
本節接著從程式語言的語言特性:流程控制和迴圈等角度,再次談談編寫可讀程式碼的要點。
還記得嗎,編寫可讀程式碼的核心的要點是什麼?
寫易於理解的程式碼
1. 流程控制
1.1 條件引數的順序
程式語言關於流程控制的語句有哪些?
- if ... else
- while
- switch
涉及流程控制的話,一般涉及條件判斷,你有認真思考條件判斷語句中的引數的順序嗎?
比如:
if (number < 10) {} // A if (10 > number) {} // B if (receivedNumber < expectedNumber) // C if (expectNumber > receivedNumber) // D
上述例項,你會條件語句的引數順序你會選擇哪個?
A, C
那麼應該準從什麼樣的尊則?
左邊傾向於變數,右邊傾向於常量;
其實這不是什麼新的東西,在我們學習數學中的未知數的時候就是這麼做的。
x < 2
1.2 if...else 語句塊的順序
可以參照下面的下面準則:
- 先判斷正向邏輯的,再判斷負向邏輯
- 先處理簡單
- 先處理有趣的或者可疑的
if createParam.Data.ShopType != RegionEntrances { newShop.ShopUUID = tools.GenerateUUID(16, createParam.Data.Name, company.Name) } else { var tmpShop models.Shop if notFound := database.POSTGRES.Where("company_id = ? AND shop_type = ?", company.ID, createParam.Data.ShopType).First(&tmpShop).RecordNotFound(); notFound { newShop.ShopUUID = tools.GenerateUUID(16, createParam.Data.Name, company.Name) } else { newShop.ShopUUID = tmpShop.ShopUUID } }
上文根據輸入的 ShopType 欄位是否是 RegionEntrances;
- 如果是,搜尋資料庫,看資料是否是有此欄位,存在則獲取shopUUID
- 否則 產生 shopUUID
- 如果根本不是 RegionEntrances 欄位,則產生 shopUUID
上文沒有一定的規範,搞的整個流程不容易理解。
根據:先處理正向邏輯,處理簡單的,處理可疑或者有趣的準則,改善如下(僅僅只是調換順序)
if createParam.Data.ShopType == RegionEntrances { var tmpShop models.Shop if notFound := database.POSTGRES.Where("company_id = ? AND shop_type = ?", company.ID, createParam.Data.ShopType).First(&tmpShop).RecordNotFound(); !notFound { newShop.ShopUUID = tmpShop.ShopUUID } else { newShop.ShopUUID = tools.GenerateUUID(16, createParam.Data.Name, company.Name) } } else { newShop.ShopUUID = tools.GenerateUUID(16, createParam.Data.Name, company.Name) }
1.3 避免使用三目運算子
三目運算子一定程度上能夠精簡程式碼,減少程式碼的行數,但是卻存在另外一個缺點,即:不容易理解(雖然大學教材總會考這類題目,判斷執行的順序和結果)
只在簡單場景下使用三目運算子。
1.4 函式什麼時候返回
經常我們編寫函式的時候,喜歡宣告一個變數用來儲存結果,到所有的邏輯結束後返回這個變數作為函式的返回值。
幾個建議:
- 可以提前進行函式返回值,多幾個 return, 沒關係
- 最好函式都要有返回值,Golang 裡建議至少返回一個 錯誤資訊
1.5 減少多層級的巢狀
層級的增多,增加了認知的負擔。而且容易出現不容易發現的 bug。
如何減少巢狀:
- 提前函式返回
- 在循壞內使用 continue
2. 表示式
建議使用短表示式
如何做到短表示式:
- 已有的專案:拆分
- 新的程式碼:有意識的使用短表示式
如何拆分:
使用中間變數
中間變數的用途可以劃分為:
- 解釋型變數
- 總結性變數
比如:
if createParam.Data.ShopType == RegionEntrances {}
感覺表示式長了,怎麼做:
var shopTypeEqual = createParam.Data.ShopType == RegionEntrances if shopTypeEqual {}
3. 變數
程式語言支援顯式申明,也支援自動識別變數型別,你覺得哪種好?
var number int number = 10 numberMax := 100
顯式的命名更好,強型別程式語言遇到的問題可能還不多,弱型別的程式語言,可能存在隱藏的 bug.
變數的申明區分:全域性和區域性
問:全域性變數多點好?還是少點好?
問:區域性變數是統一在函式下側統一命名,還是靠近需要使用變數的語句處?
全域性變數少用,隨著專案越來越複雜,可能在某個角落,全域性變數就進行了更改。這樣引起的 異常處理很難進行追蹤和分析。
區域性變數在靠近使用該變數的地方宣告並使用,這樣,邏輯、思維不容易斷。
比如:
var fetchMaxNumber = func( ) int { var maxNumber int var minNumber int ... ... ... do... return maxNumber }
var fetchMaxNumber = func()int{ ... ... var ( maxNumber int minNUmber int ) do... return maxNumber }
第二種變數的組織方式優於第一種,而且更利於思維。像第一種,讀到真正的處理邏輯,還需要回過頭去看下變數的宣告,給思維造成了額外的認知負擔,尤其你還喜歡寫大段程式碼的函式。
一個準則:全域性變數的個數需要儘可能少,如果有可能,使用常量替代。區域性變數最好在需要使用變數的地方進行申明。
好,那麼我們的目的便是儘可能的減少變數。
如何減少?
- 減少沒有價值的變數,甚至是沒有價值的程式碼
- 減少控制流變數(經常會使用一個諸如 Flag 的變數等來進行控制流的判斷,其實完成可以省略,僅靠調整語句遍可實現)
- 縮小變數的作用域:全域性變數多處使用,賦值之類的可能變更變數,在函式內的變數作用域有限,不影響外部變數
4. 重新組織程式碼,持續迭代
軟體架構有一種很流行的設計方法,叫:領域驅動設計,對持續迭代的微服務有很大的幫助。該領域驅動方法將專案劃分為4個層級。
- 領域層:即領域內操作的集合
- 基礎設施層:即輔助服務操作的集合
- 使用者介面層:即使用者層
- 應用層
其中談到領域,和我們之前變數的命名建議使用專業的詞、領域內的詞不謀而合。
同時,基礎設施層是將一些輔助性的任務集合。比如檔案處理、比如網路請求處理、比如字串處理等
組織程式碼節也提倡這麼做。
實現核心的業務需求時,儘量將這些工具類的功能規整在獨立的基礎設施裡,專注於實現核心的業務。
程式碼的組織,一個是專案的組織,一個良好的專案組織方式,一定程度上能體現程式碼的邏輯性。
另外一個比較重要的是函式的組織。
有下面幾條準則:
- 不相干的任務,提取出來
- 一次只專注幹一件事
- 梳理邏輯時,如果你能使用自然語言表述出來,對你寫出邏輯清晰的程式碼很有幫助
- 單函式行數不宜過長 30 ~ 50 為佳。再一個評判方法是,檢視函式的內容無需滾動滑鼠進行翻頁。
- 少些程式碼:每寫一行都需要維護;不需要的功能,砍掉,不需要的程式碼,刪掉
全文完,我是謝偉,再會。