『No22: 編寫可讀程式碼的藝術(1)』

wuxiaoshen發表於2019-01-27

大家好,我叫謝偉,是一名程式設計師。

除了本職工作,還有點幻燈片演示設計的愛好。隨著編寫程式碼的增多,製作的的幻燈片越來越多,越來越意識到,很多事物都存在相通性。

這就解釋了為什麼有很多人,能擅長多個領域。難道不是因為掌握了底層本質的東西嗎?

為什麼琅琅上口的口頭禪能傳播的更廣泛?

為什麼好的文案既精簡又足夠引起使用者的注意?

為什麼謠言也傳播的更為廣泛?

是的,他們一定都準確的抓住了使用者的心理。

本文結合一些簡易的設計規範來解釋:編寫可讀程式碼的藝術。

《寫給大家看的設計書》一書中全文在詮釋設計的四個規範:親密、對齊、重複、對比。
《編寫可讀程式碼的藝術》一書中全文在詮釋編寫可讀程式碼的藝術:讓人易於理解。

是的,市面上存在很多很優秀的設計師,設計的作品,既足夠精美,又讓使用者秒懂。好的設計者一定深諳心理學。

編寫程式碼,實質是在梳理邏輯,為了完善整個邏輯流程,我們借用程式語言的變數、函式、流程控制、迴圈、註釋、方法等串接起來,完善一套系統的邏輯。

為了完善這套邏輯,我們藉助了許多工具:設計方法、架構設計、專案組織等。

意識到沒有,程式碼的好壞一定程度上可以從邏輯層面評判。

  • 符合邏輯,不一定是最優的程式碼
  • 不符合邏輯,一定不是好的程式碼

從這層面來看,梳理邏輯極為重要,邏輯通了,剩下的就是實現了。

邏輯的串接靠的是程式語言的變數、函式、流程控制、迴圈、註釋等。

本文從這些層面講述,如何編寫可讀程式碼。

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. 總結

從“表面”給出編寫可讀程式碼的建議,下一篇介紹從流程、迴圈、抽象、組織程式碼等角度談編寫可讀程式碼的建議。

下節再會,我是謝偉。

謝謝。

相關文章