大家好,我叫謝偉,是一名程式設計師。
除了本職工作,還有點幻燈片演示設計的愛好。隨著編寫程式碼的增多,製作的的幻燈片越來越多,越來越意識到,很多事物都存在相通性。
這就解釋了為什麼有很多人,能擅長多個領域。難道不是因為掌握了底層本質的東西嗎?
為什麼琅琅上口的口頭禪能傳播的更廣泛?
為什麼好的文案既精簡又足夠引起使用者的注意?
為什麼謠言也傳播的更為廣泛?
是的,他們一定都準確的抓住了使用者的心理。
本文結合一些簡易的設計規範來解釋:編寫可讀程式碼的藝術。
《寫給大家看的設計書》一書中全文在詮釋設計的四個規範:親密、對齊、重複、對比。
《編寫可讀程式碼的藝術》一書中全文在詮釋編寫可讀程式碼的藝術:讓人易於理解。
是的,市面上存在很多很優秀的設計師,設計的作品,既足夠精美,又讓使用者秒懂。好的設計者一定深諳心理學。
編寫程式碼,實質是在梳理邏輯,為了完善整個邏輯流程,我們借用程式語言的變數、函式、流程控制、迴圈、註釋、方法等串接起來,完善一套系統的邏輯。
為了完善這套邏輯,我們藉助了許多工具:設計方法、架構設計、專案組織等。
意識到沒有,程式碼的好壞一定程度上可以從邏輯層面評判。
- 符合邏輯,不一定是最優的程式碼
- 不符合邏輯,一定不是好的程式碼
從這層面來看,梳理邏輯極為重要,邏輯通了,剩下的就是實現了。
邏輯的串接靠的是程式語言的變數、函式、流程控制、迴圈、註釋等。
本文從這些層面講述,如何編寫可讀程式碼。
0. 規範
絕大多數的人,不會從零完整的完成一個複雜的專案,大多是團隊共同合作,完成一個大的專案。
這個時候,假如你是中途參與進來。你在實現邏輯的時候,你是照著自己的邏輯來還是依照團隊的風格來。
比如專案組織,命名等…
依照團隊的風格來
尤其針對複雜的上線的專案,完整的理解整個專案都存在困難,這個時候,風格統一尤其重要。
是的,這個時候,風格一致性重要,當然具體的實現邏輯,還是應該遵從易於理解這個大方向。
1. 程式語言規範
準則:堅持程式語言的風格
每門程式語言,都存在一定的規範,比如 Python 採用的下劃線的變數命令規則,Go 則採用駝峰式的變數命令規則等。
這個時候,程式語言的整體規範需要遵從。
大家可能會多參考 google 出品的各種程式語言規範。方向沒錯。
2. 命名
- 變數
- 函式
- 方法
準則:易於理解
如何做到易於理解:
- 專業的單詞:使用領域內的單詞
- 避免空泛的名字
- 具體的名字
- 變數名帶上更多細節
- 不使用令人誤解的名字
- 布林值命名
- 不建議使用的單詞
2.1 領域內的單詞
這個和專案相關,比如系統是個智慧零售後臺,那們領域內單詞多是:顧客、商品、商鋪、店員、店長、價格等。
- customer
- produce
- shop
- shopLeader
- price
2.2 避免空泛的名字
變數的命名一般要賦予一定的意義,極少情況下可以使用沒有什麼意義的單詞。比如最常見的:
var (
numberOne int
temp int
numberTwo int
)
tmp = numberOne
numberOne = numberTwo
numberTwo = tmp
複製程式碼
再比如:
var i int
for i=0;i<10;i++{
fmt.Println(i)
}
複製程式碼
這種沒什麼意義的單詞,一般適用於區域性作用域。
儘量少用。
2.3 具體的名字
完成什麼任務就使用什麼單詞。一般變數使用名詞居多,函式使用動詞開頭居多。
函式多用動詞,變數多用名詞
比如:
var toString = func(){}
var serverLoop = func(){}
var pages int
var userName string
複製程式碼
2.4 帶上更多細節
一般命名不建議過長,也不建議過短,那多長,多短合適呢?
最長三個單詞的長度吧
如何帶上更多的細節。
- 嘗試使用字尾
- 嘗試使用單位
- 嘗試指向具體的細節
比如:
var timeIntervalMs
var lengthCm
var delaySecs
var sizeMb
var maxKbps
var degreesCw
var numberMax
var numberMin
var numberFirst
var numberLast
複製程式碼
贈送幾組對仗的字尾:
- max/min
- first/last
- begin/end
…
ServerCanStart() 不如 CanListenOnPort()
複製程式碼
2.5 不使用令人誤解的詞
比如:Filter 在資料庫操作中容易使用這個單詞,這個單詞沒有帶上更多的細節,實質上在使用的過程中,還是需要檢視編寫的SQL 語句等才能知道具體的過濾細節。整體思考多了幾步。不易讓人理解。
建議多讀幾遍自己命名的單詞
2.6 布林值
提到布林值,因為就存在兩種結果。所有,一般使用是否這樣意思的詞。
比如:
var (
ok bool
notOk bool
)
var (
is bool
)
var (
has bool
)
var (
found bool
)
var (
should bool
)
var isCompany = func(){}
var isVip = func(){}
var hasSpace = func(){}
複製程式碼
2.7 不建議使用的單詞
- get
- read
- util
恰恰這幾個單詞,在寫程式碼中最容易使用。選擇替代方案。
贈送一波動詞:
- send
deliver、dispatch、announce、distribute、route
- find
search、extract、locate、recover
- start
launch、create、begin、open
- make
create、setUp、build、generate、compose、add、new
複製程式碼
3. 設計
一份好的幻燈片演示設計需要考慮什麼?
- 符合場景的配色,確定原始基調
- 符合場景的事物,借用來表達觀念
- 統一整體風格
- 對齊、重複、親密、比較
看到沒,幻燈片演示設計,強調場景化,選擇適合場景的主體和配色,比如黨政風格,當然選擇國旗色;比如學術答辯,當然選擇藍色;比如手機釋出會,當然使用暗黑科技風格…
所以程式碼也需要場景化,選擇符合場景的單詞等
這裡以設計的四個規範類比程式碼的組織。
3.1 對齊
程式語言為什麼強調縮排?難道不是為了閱讀程式碼的人更容易看懂程式碼嗎?寫程式碼的人更容易組織程式碼嗎?僅僅是設計者為了好玩?
當然不是。
舉例:編寫web路由和控制器
// student.go
func Register(r *gin.Group) {
r.POST("/v1/api/students", PostHandler)
r.GET("/v1/api/students/:sid", GetHandler)
r.PATCH("/v1/api/students/:sid", PatchHandler)
r.PUT("/v1/api/students/:sid", PutHandler)
r.DELETE("/v1/api/students/:sid", DeleteHandler)
}
複製程式碼
- 放眼望去,確實知道實現什麼任務
- 風格統一
- 整整齊齊
這個例子可能解釋對齊,不夠友好。
再舉個例子:
CheckFullName("No Such Guy","", "not match found")
CheckFullName("John", , "", "more than one resule")
複製程式碼
3.2 重複、親密、比較
當然,作為程式設計師,最應該避免的其實就是寫重複的程式碼,一般的做法往玩是提煉,將重複的抽象出一個函式之類的。這裡的重複,是風格的統一
// teacher.go
func Register(r *gin.Group) {
r.POST("/v1/api/teachers", PostHandler)
r.GET("/v1/api/teachers/:tid", GetHandler)
r.PATCH("/v1/api/teachers/:tid", PatchHandler)
r.PUT("/v1/api/teachers/:tid", PutHandler)
r.DELETE("/v1/api/teachers/:tid", DeleteHandler)
}
複製程式碼
可以結合比較下 student.go 和 teacher.go
這樣的組織方式,講道理,並不太會給閱讀程式碼的人帶來太多的認知負擔。
- 一致的順序
- 始終一致的使用,使使用者其實閱讀一個,類似的都能秒懂
3.3 留白
設計領域頁面的設計,並不強調內容越多越好,恰當的在頁面上留有空白,使整體設計有呼吸感。
那程式設計如何實現留白?
- 恰當的換行,使相似的內容更緊湊
- 提取,使用方法來組織不規範的東西
- 程式碼分段
假如你一個函式需要寫 100 多行,不好意思,我可能建議你,不要這麼做。
- 拆分,邏輯梳理、提取方法
- 儘量維持最長 30~50行左右(這樣使螢幕能裝載下,一次就能完成的閱讀整個函式的邏輯)
4 註釋
準則:幫助閱讀程式碼的人對程式碼瞭解的和寫程式碼的人一樣多
- 什麼時候不需要註釋
- 什麼時候需要註釋
4.1 什麼時候不需要註釋
是的,前文的一系列準則,命名啊之類的,是內容,也是註釋,通過閱讀變數、函式名等就瞭解了程式碼完成的任務。
這樣的地方不需要註釋,因為顯而易見,從程式碼本身就能推斷事實。
有時候可能需要考慮是不是命名不好,導致需要註釋,這個時候,命名優於註釋。
給不好的程式碼註釋,和在錯誤的路上持續努力一樣令人可怕。
4.2 什麼時候需要註釋
- 關鍵點
- 缺陷點
- 常量
- 全域性註釋
- 總結性註釋
關鍵點:
有些時候,僅僅靠之前的“表面工作” 已經不能完全能夠滿足讓人易於理解。這個時候需要在關鍵點新增註釋。
缺陷點:
是的,承認自己的程式碼寫的不是最優的,僅僅只是實現,還存在更優的辦法,所以需要在有缺點的地方加上註釋。
常量:(各程式語言建議常量大寫)
給常量註釋,賦予了更多的意義。
比如:
NUMTHRADS = 8 // as long as it is >= 2 * number_processors, that is good enough.
複製程式碼
好,假如你正在優化程式碼,看到這注釋,是的,你知道該如何調整了。
全域性註釋:
一般在檔案開頭,表明檔案內程式碼完成的任務。
總結性註釋:
一般函式開頭,表明函式程式碼完成的任務。
其他:
潤色語句。言簡意賅由於冗長的解釋。(這些貌似對文字功底要求高點)
5. 總結
從“表面”給出編寫可讀程式碼的建議,下一篇介紹從流程、迴圈、抽象、組織程式碼等角度談編寫可讀程式碼的建議。
下節再會,我是謝偉。
謝謝。