標籤:
命名基本原則
- 仿照 Cocoa 風格來,使用長命名風格
- 變數命名推薦的命名語素順序是:最開頭是命名空間簡寫,然後越重要、區別度越大的語素越要往前放。經典的結構是:作用範圍+限定修飾+類型。例:
extern ushort APIDefaultPageSize; // 還行,能明白意思了extern ushort APIDefaultFetchPageSize; // 加上些限定更好一些extern ushort APIFetchPageSizeDefault; // 再好些,把重要的往前放YHToolbarComment // 不推薦YHCommentToolbar // OK,把類型(toolbar)置後
命名空間
- 類名、protocols、C 函數、常量、結構體和枚舉應帶有命名空間首碼;
- 類方法不要帶首碼,結構體欄位也不要帶首碼
視圖命名
為了舉例,我們假定有 User、Tag、Category 這幾種 model 類型。
對象展示一般分列表和單個詳情,其 view controller 分別使用 ModelListController 和 ModelDetailController,推薦的語素順序是:Model名 + 限定與修飾 + ListController|DetailController。舉例說明:
// OKTagUserUsedListControllerTagInCategoryListControllerCategoryDetailController// 不推薦,列表統一使用 ListController,不指明是 table view 還是 collection viewUserFollowerTableViewController// OKUserFollowerListController// 不推薦UserLikedTagListController// OK,把顯示的對象放在第一位TagUserLikedListController// 糟糕,如果是 view controller,必須以 Controller 或 Displayer 結尾TagListView
經常為了便於多個介面複用,我們會把 model 的顯示統一在一個 view controller 中,在其他介面嵌入這個 view controller。我們把這類專門管理顯示的 view controller 叫做 displayer。如:
UserListDisplayerTagListDisplayer
UIView 層級的組件不要以 Controller 或 Displayer 結尾,如果起到管理作用可以使用 control 結尾。
動機
把 model 名放在首位(如 TagUserLikedListController 而不是 UserLikedTagListController)的主要考量是便於搜尋。因為 Xcode 不支援亂序搜尋,關鍵詞只能從前往後才會有結果。
如果限定詞在前,因為不同人理解差異,自己也會遺忘,這個限定詞經常是輸入不能的,只能搜 TagList 再從列表中尋找,等於第一位的尋找語素就廢掉了。當 model 類型在第一位時,基本上熟悉這個項目的人都清楚要尋找的視圖顯示的是什麼類型,第一位正確了,後面添加/修改限定就很方便了。
另一個便利的情境是參考之前介面實現另一個介面時,尋找的大都是相同類型的介面,如實現 UserFollowerListController 參考 UserFollowingListController;而相同限定的情境比較少見,像 UserLikedTagListController 參考 UserLikedCategoryListController 的可能性就較少。
PS: 如果你不用 Xcode 的 Open Quickly(預設快速鍵 Command+Shift+O),強烈建議你改一下習慣
方法名
- 以
alloc、copy、init、mutableCopy、new 開頭的方法要注意,它們會改變ARC的行為。[^1]
- 以
get、set 開頭的方法有特殊的意義,不要隨意定義。
- set 是屬性預設的設定方法,如果函數不是為了設定類成員,則不要用
set 開頭,可用 setup 替代。
- get 和屬性方法無關,但在 Cocoa 中,其標準行為是通過引用傳值,而不是直接返回結果的。欲擷取變數,直接以變數名為名,如:
userInfomation,而不是 getUserInfomation。
例:
// OK- (NSString *)name;// 糟糕,應用上面的寫法- (NSString *)getName;// OK,但極少使用- (void)getName:(NSString **)buffer range:(NSRange)inRange;// OK- (NSSize)cellSize;// 糟糕,應用上面的寫法- (NSSize)calcCellSize;// 對 controller 做一般設定,OK- (void)setupController;// 列出具體設定的內容,更好- (void)setupControllerObservers;// 糟糕,set 專用於設定屬性- (void)setController;
// 來自官方文檔insertObject:atIndex: // OKinsert:at: // 不清晰,插入了什嗎?at 具體指哪裡?removeObjectAtIndex: // OKremoveObject: // OKremove: // 糟糕,什麼被移除了?
協議名
好的協議名應能立刻讓人分辨出這不是一個類名,除了以常用的 delegate、dateSource 做結尾外,還可以使用 …ing 這種形式,如:NSCoding、NSCopying、NSLocking。
通知命名
基本命名格式是:[與通知相關的類名] + [Did | Will] + [UniquePartOfName] + Notification,例:
NSApplicationDidBecomeActiveNotificationNSWindowDidMiniaturizeNotificationNSTextViewDidChangeSelectionNotificationNSColorPanelColorDidChangeNotification
臨時變數命名
- 臨時變數可以寫得很短,如 i、k、vc 這樣;
- 臨時變數可以使用匈牙利首碼,但資料類型不可以作為首碼:
// OKwCell, vcMaster, vToolbar// 糟糕,資料類型作為首碼bool_switchState, floatBoxHeight
推薦的首碼:
| 首碼 |
含義 |
| ix |
序號,起始為0 |
| in |
序號(自然數範圍),起始為1 |
| if |
類型為浮點的“序號” |
| x |
座標 |
| y |
座標 |
| w |
寬度 |
| h |
高度 |
| vc |
視圖控制器 |
| v |
視圖 |
常量命名
除以上規則約定外,其他常量約定了以下首碼:
| 首碼 |
含義 |
| k |
宏常量 |
| CDEN |
Core Data entity name |
| UDk |
User Default key |
| APIURL |
介面地址 |
另見:常量管理
大小寫
- 類名採用大駝峰(
UpperCamelCase)
- 類成員、方法小駝峰(
lowerCamelCase)
- 局部變數大小寫首選小駝峰,也可使用小寫底線的形式(
snake_case)
- C函數的命名用大駝峰
縮寫
可以使用廣泛使用的縮寫,如 URL、JSON,並且縮寫要大寫。但像將download簡寫為dl這種是不可以的。
// OKID, URL, JSON, WWW// 糟糕id, Url, json, wwwdestinationSelection // OKdestSel // 糟糕setBackgroundColor: // OKsetBkgdColor: // 糟糕
其他
i,j專用於迴圈標號
為私人方法命名不要直接以“”開頭,而應以“命名空間”開頭。
代碼格式化空格
類方法聲明在方法類型與傳回型別之間要有空格。
// 糟糕-(void)methodName:(NSString *)string;// OK- (void)methodName:(NSString *)string;
條件判斷的括弧內側不應有空格。
// 糟糕if ( a < b ) { // something}// OKif (a < b) { // something}
關係運算子(如 >=、!=)和邏輯運算子(如 &&、||)兩邊要有空格。
// OK(someValue > 100)? YES : NO// OK(items)?: @[]
二元算數運算子兩側是否加空格不確定,根據情況自己定。一元運算子與運算元之前沒有空格。
多個參數逗號後留一個空格(這也符合正常的西文文法)。
花括弧
方法的花括弧推薦另起一行。方法內部需要寫在一行。
- (void)methodName:(NSString *)string { ↑空格 ↑空格,推薦花括弧在一行 if () { 空格↑ ↑空格,花括弧不要另起一行 } else {要換行↑ ↑空格,花括弧不要另起一行 } }
動機
Xcode 預設的花括弧位置是這樣的:方法內部的各種補全都是在同一行的;方法定義的比較混亂,預設模版另起一行,但從 Interface Builder 中連線產生的方法在同一行的。
考慮到 Xcode 的預設行為,方法內部要另起一行,方法所在行不強制定死。另外,模版可以定製,而 IB 產生的程式碼不可定製,所以不另起一行的寫法優先。
另起一行的寫法在程式碼摺疊功能後非常難看。
折行
與多數其他規範不同,不建議手動折行。
動機
手動折行的效果嚴重寬度依賴於視窗寬度——視窗過寬浪費寶貴的螢幕空間,較窄時可能無法閱讀。而且 Xcode 自動折行的效果還是不錯的。
程式碼群組織
- 函數長度(行數)不應超過2/3螢幕,禁止超過70行。 : 例外:對於順序執行的初始化函數,如果其中的過程沒有提取為獨立方法的必要,則不必限制長度。
- 單個檔案方法數不應超過30個
- 不要按類別排序(如把IBAction放在一塊),應按任務把相關的組合在一起
- 禁止出現超過兩層迴圈的代碼,用函數或block替代。
儘早返回錯誤:
// 為了簡化樣本,沒有錯誤處理,並使用了虛擬碼// 糟糕的例子- (Task *)creatTaskWithPath:(NSString *)path { Task *aTask; if ([path isURL]) { if ([fileManager isWritableFileAtPath:path]) { if (![taskManager hasTaskWithPath:path]) { aTask = [[Task alloc] initWithPath:path]; } else { return nil; } } else { return nil; } } else { return nil; } return aTask;}// 改寫的例子- (Task *)creatTaskWithPath:(NSString *)path { if (![path isURL]) { return nil; } if (![fileManager isWritableFileAtPath:path]) { return nil; } if ([taskManager hasTaskWithPath:path]) { return nil; } Task *aTask = [[Task alloc] initWithPath:path]; return aTask;}類
禁止在類的 interface 中定義任何 iVar 成員,只允許使用屬性,但可以在特定情形中使用屬性產生的 iVar。
盡量總是使用點操作符訪問屬性,而不是屬性產生的 iVar 變數。以下情形除外:
- 明確要避免修改產生 KVO 通知的;
- 需重寫屬性 getter 或 setter 的;
- 效能分析確定使用屬性會導致效能不可接受的;
- 多線程環境中,為防止互斥一次進行多個修改的;
- init、dealloc 方法中。
動機
如果使用 iVar,很多情況要特殊處理,容易出錯。總是使用成員,規則簡單,不易出問題。
直接存取 iVar 的 block 會 retain iVar 所屬的對象,這點很容易被忽略
定義和使用 iVar 都會產生編譯警告,只不過預設設定沒啟用這兩個警告
Property attributes
什麼時候使用 copy?
- block 屬性要定義成 copy。
- 當一個屬性賦值後不期望改變時應當用 copy,最常見的類型如 NSString、NSURL。可變類型的成員,如 NSMutableArray、NSMutableDictionary 不能定成 copy 的。
相關 Demo 可在 https://github.com/BB9z/PropertyTest 獲得。
常量
除非調試用的、控制不同編譯模式行為的常量可用宏外,其他常量不得用宏定義。
常量定義樣本:
// 標頭檔extern ushort APIFetchPageSizeDefault; // 無const,可在外部修改// 實現檔案ushort APIFetchPageSizeDefault = 10;
注釋
盡量讓代碼可以自表述,而不是依賴注釋。
注釋應該表達那些代碼沒有表達以及無法表達的東西。如果一段注釋被用於解釋一些本應該由這段代碼自己表達的東西,我們就應該將這段注釋看成一個改變代碼結構或編碼慣例直至代碼可以自我表達的訊號。我們重新命名那些糟糕的方法和類名,而不是去修補。我們選擇將長函數中的一些程式碼片段抽取出來形成一些小函數,這些小函數的名字可以表述原程式碼片段的意圖,而不是對這些程式碼片段進行注釋。儘可能的通過代碼進行表達。你通過代碼所能表達的和你想要表達的所有事情之間的差額將為注釋提供了一個合理的候選使用場合。對那些代碼無法表達的東西進行注釋,而不要僅簡單地注釋那些代碼沒有表達的東西。”[^2]
塊注釋
方法內部禁止使用塊注釋。除非要臨時注釋大段代碼,一般情況總應使用行注釋。
動機
因為塊注釋不能正確嵌套。
其他異常
- 作為被調用模組的維護者,當被調用不當時(參數有問題、不和時宜),如何處理需要考慮(拋出異常還是返回錯誤狀態);
- 不要依賴 try catch,它不是代替你做檢查、填補遺漏的工具。
ios 代碼規範