幾個步驟,讓你的 iOS 程式碼容易閱讀

張嘉夫_Joseph發表於2018-04-23

本文翻譯自 Making your iOS application easy to read with these simple steps.


優秀的程式設計師會用盡可能簡單的方式來解釋他們的程式碼,即使是物理學家都可以用一張白紙和一隻鉛筆來解釋蟲洞,我們又何嘗不可?

我會盡可能讓程式碼寫地簡單、易讀,包括選擇合適的變數名、使用編碼規範(code conventions)等等,但還是缺了點東西,理解程式碼不應該是去理解“如何”實現的,而是要理解想要“達成”什麼。

甚至可以說要讓讀程式碼像讀小說一樣,而不是一大堆程式碼。


下面討論三大主題:

問題

閱讀其他人的程式碼可能會非常折磨,如果不提供合適的上下文,我們會迷失在尋找某個函式或屬性的意義中。

建議

不論是二進位制語言、低階語言還是高階語言,語法都在變得越來越友好,以便吸引更多開發者。而隨著語法變得更接近英語,我們的程式碼也應該簡明扼要、不言自明。

結果

寫出良好的程式碼,讀起來像小說一樣,容易閱讀和理解(即使沒有給出上下文)。


函式命名

正確的方式是:

我們寫函式時都會假設閱讀它的人擁有足夠的上下文,能夠理解函式想實現什麼。用模糊的含義來命名函式,例如“handleRedView()”會引起很多問題,”RedView”是啥?這個函式主要是想幹啥?

所以在某些情況下函式的用途會很模糊,如果沒有提供足夠的上下文,閱讀起來會非常困難

我們可以把函式的用途分為四類:

  1. 通知(Informer)函式
  2. 管理(Management)函式
  3. 路由(Router)函式
  4. 執行(Execution)函式

1. 通知函式

通常會觸發路由/管理函式,例子如下:

 delegate.dataHasUpdated()

func dataHasUpdated()
{
  //通知某事的發生
 }

// 通知函式
override func engineStarted()
{
  super.engineStarted()
  handleCarStarted()
 }
複製程式碼

回撥函式,通知某事已經/即將發生,並給機會進行響應。

大部分情況下用於被代理(delegate)觸發的操作,或是通知(notification)處理函式。

2. 管理函式

用於聯合多個函式以實現更高階的用途,不需要依賴引數,block 中的所有程式碼都會執行。

// 管理函式
func handleCarStarted()
{
  turnLights(on: true)
  turnAC(on: true)
}
複製程式碼

上面的函式包含所有需要的資訊,汽車啟動時執行這些函式,此時我們不關注這是“如何”實現的,而是關注它做了“什麼”。

3. 路由函式

用於聯合多個函式以實現更高階的用途,需要依賴一些引數,只在我們想執行的時候才執行。

// 路由函式
private func turnLights(on shouldTurnLightsOn: Bool)
{
  if shouldTurnLightsOn
 {
   turnExteriorLightsOn()
   checkForBurnedBulbs()
 }
 else { turnExteriorLightsOff() }
 如果 if 語句只執行一件事,我喜歡把 "if" "else" 和執行語句寫在同一行,這樣程式碼讀起來會更流暢。
複製程式碼

路由函式通常同於指向執行函式,但在某些情況下,如果邏輯程式碼不超過一行,也可以包含自己的邏輯。

4. 執行函式

函式名字的具體實現

// 執行函式
private func turnExteriorLightsOn()
{
  leftFrontLight.isOn = true
  rightFrontLight.isOn = true
  leftBackLight.isOn = true
  rightBackLight.isOn = true
 }
 private func checkForBurnedBulbs()
 {
   for lightBulb in bulbs where !lightBulb.isUseable
   {
     Dashboard.(errorType: .lights)
     break
   }
 }
複製程式碼

這樣就能寫出一個乾淨、簡短的類,可讀性強,容易維護。


* 謹記一項原則,每個函式都應該只有一個責任。

避免在函式名稱裡使用”and“: playAndMinimize() loadAndPlay() 這個壞習慣會打破單一責任原則,寫出能夠適應兩種情況的程式碼。

避免在函式名稱裡進行猜測: moveRedViewIfNeeded() 上面的例子會導致後面的程式設計師必須深入此函式,才能理解觸發移動 Red View 的時機,這樣不夠清晰。

不,layoutIfNeeded() 並不屬於這種情況,因為我們知道如果某個 view 的 setNeedDisplay 為 true,就應該重新佈局。這種情況在 Swift 語言裡很普遍,因為函式基本上都是應用私有的。


談及程式碼可讀性,我首先會想到編碼規範(code convention),它們被普遍接受、應用廣泛,但使用編碼規範並不一定會提升程式碼質量,雖然有跨應用性但可讀性更差。

”is“字首應該用於布林型變數和方法,以便解釋返回值是布林型別的。#編碼規範

既然”if“語句總是用於布林值,那還有必要給每個布林屬性都加上”is“嗎?為什麼蘋果要把 Swift 語法從 view.hidden 改成 view.isHidden?我只能想到一種答案……因為**“if view.isHidden”**看起來更自然。

嘗試以以下原則使用“is”字首:

  • 如果類的某個布林屬性/方法用於該類(公開)的例項,就有正當理由使用“is”字首。
public var isHidden: Bool
{
  return alpha == 0.0
 }
 if containerView.isHidden
複製程式碼
  • 如果布林屬性在類內部(私有)使用,字首就是多餘的。
 private var positionedVerticaly: Bool
 {
   return view.frame.width/2 == centerX
 }
 if positionedVerticaly
 if positionedVerticaly && positionedHorizontally
 VS
 if isPositionedVerticaly
 if isPositionedVerticaly && isPositionedHorizontally
複製程式碼
  • 如果布林型屬性/方法同時被私有公開使用,那麼應該用計算屬性來返回該私有屬性值。
public var isPositionedVerticaly: Bool
{
  return positionedVerticaly
 }
 if containerView.isPositionedVerticaly
複製程式碼

雖然用私有 set 並公開使用該屬性也是可以的,但上面這種方式可以實現封裝(encapsulation)。

封裝用於隱藏類中結構化資料物件的值或狀態,防止未授權方直接訪問。

en.wikipedia.org/wiki/Encaps…

那如果不是自己直接處理的布林型,如果如何命名呢?

private var playerIsPlaying: Bool

private var gridConstraintIsEnabled()

“is” 需要指向某個東西:view.isHidden, “is”指向 view。上面的例子裡使用了此原則,playerIsPlaying,“is”指向 player。

謹記:開發者通常會在閱讀屬性宣告之前先閱讀函式內部的程式碼,嘗試搞明白這些屬性的用途。

/if playerIsPlaying { }/ 對比 /if isPlayerIsPlaying {}/
複製程式碼

哪個更自然?我想你已經有答案了。

相關文章