IOS團隊編程規范
前 言
需求是暫時的,只有變化才是永恒的,面向變化編程,而不是面向需求編程。
不要過分追求技巧,降低程序的可讀性。
簡潔的代碼可以讓bug無處藏身。要寫出明顯沒有bug的代碼,而不是沒有明顯bug的代碼。
先把眼前的問題解決掉,解決好,再考慮將來的擴展問題。
一、命名規范
1、統一要求
含義清楚,盡量做到不需要注釋也能了解其作用,若做不到,就加注釋,使用全稱,不使用縮寫。
2、類名
大駝峰式命名:每個單詞的首字母都采用大寫字母
==例:== MFHomePageViewController
3、私有變量
- 私有變量放在 .m 文件中聲明
- 以 _ 開頭,***個單詞首字母小寫,后面的單詞的首字母全部大寫。
==例:== NSString *_somePrivateVariable
4、property變量
- 小駝峰式命名:***個單詞以小寫字母開始,后面的單詞的首字母全部大寫
- 屬性的關鍵字推薦按照 原子性,讀寫,內存管理的順序排列。
- Block、NSString屬性應該使用copy關鍵字
- 禁止使用synthesize關鍵詞
==例:==
- typedef void (^ErrorCodeBlock) (id errorCode,NSString *message);
- @property (nonatomic, readwrite, strong) UIView *headerView; //注釋
- @property (nonatomic, readwrite, copy) ErrorCodeBlock errorBlock; //將block拷貝到堆中
- @property (nonatomic, readwrite, copy) NSString *userName;
5、宏和常量命名
- 對于宏定義的常量
#define 預處理定義的常量全部大寫,單詞間用 _ 分隔
宏定義中如果包含表達式或變量,表達式或變量必須用小括號括起來。
- 對于類型常量
對于局限于某編譯單元(實現文件)的常量,以字符k開頭,例如kAnimationDuration,且需要以static const修飾
對于定義于類頭文件的常量,外部可見,則以定義該常量所在類的類名開頭,例如EOCViewClassAnimationDuration, 仿照蘋果風格,在頭文件中進行extern聲明,在實現文件中定義其值
==例:==
- //宏定義的常量
- #define ANIMATION_DURATION 0.3
- #define MY_MIN(A, B) ((A)>(B)?(B):(A))
- //局部類型常量
- static const NSTimeInterval kAnimationDuration = 0.3;
- //外部可見類型常量
- //EOCViewClass.h
- extern const NSTimeInterval EOCViewClassAnimationDuration;
- extern NSString *const EOCViewClassStringConstant; //字符串類型
- //EOCViewClass.m
- const NSTimeInterval EOCViewClassAnimationDuration = 0.3;
- NSString *const EOCViewClassStringConstant = @"EOCStringConstant";
6、Enum
- Enum類型的命名與類的命名規則一致
- Enum中枚舉內容的命名需要以該Enum類型名稱開頭
- NS_ENUM定義通用枚舉,NS_OPTIONS定義位移枚舉
==例:==
- typedef NS_ENUM(NSInteger, UIViewAnimationTransition) {
- UIViewAnimationTransitionNone,
- UIViewAnimationTransitionFlipFromLeft,
- UIViewAnimationTransitionFlipFromRight,
- UIViewAnimationTransitionCurlUp,
- UIViewAnimationTransitionCurlDown,
- };
- typedef NS_OPTIONS(NSUInteger, UIControlState) {
- UIControlStateNormal = 0,
- UIControlStateHighlighted = 1
7、Delegate
- 用delegate做后綴,如
- 用optional修飾可以不實現的方法,用required修飾必須實現的方法
- 當你的委托的方法過多, 可以拆分數據部分和其他邏輯部分, 數據部分用dataSource做后綴. 如
- 使用did和will通知Delegate已經發生的變化或將要發生的變化。
- 類的實例必須為回調方法的參數之一
- 回調方法的參數只有類自己的情況,方法名要符合實際含義
- 回調方法存在兩個以上參數的情況,以類的名字開頭,以表明此方法是屬于哪個類的
==例:==
- @protocol UITableViewDataSource
- @required
- //回調方法存在兩個以上參數
- - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section;
- @optional
- //回調方法的參數只有類自己
- - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView; // Default is 1 if not implemented
- @protocol UITableViewDelegate
- @optional
- //使用`did`和`will`通知`Delegate`
- - (nullable NSIndexPath *)tableView:(UITableView *)tableView willSelectRowAtIndexPath:(NSIndexPath *)indexPath;
- - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath;
8、方法
- 方法名用小駝峰式命名
- 方法名不要使用new作為前綴
- 不要使用and來連接屬性參數,如果方法描述兩種獨立的行為,使用and來串接它們。
- 方法實現時,如果參數過長,則令每個參數占用一行,以冒號對齊。
- 一般方法不使用前綴命名,私有方法可以使用統一的前綴來分組和辨識
- 方法名要與對應的參數名保持高度一致
- 表示對象行為的方法、執行性的方法應該以動詞開頭
- 返回性的方法應該以返回的內容開頭,但之前不要加get,除非是間接返回一個或多個值。
- 可以使用情態動詞(動詞前面can、should、will等)進一步說明屬性意思,但不要使用do或does,因為這些助動詞沒什么實際意義。也不要在動詞前使用副詞或形容詞修飾
==例:==
- //不要使用 and 來連接屬性參數
- - (int)runModalForDirectory:(NSString *)path file:(NSString *)name types:(NSArray *)fileTypes; //推薦
- - (int)runModalForDirectory:(NSString *)path andFile:(NSString *)name andTypes:(NSArray *)fileTypes; //反對
- //表示對象行為的方法、執行性的方法
- - (void)insertModel:(id)model atIndex:(NSUInteger)atIndex;
- - (void)selectTabViewItem:(NSTableViewItem *)tableViewItem
- //返回性的方法
- - (instancetype)arrayWithArray:(NSArray *)array;
- //參數過長的情況
- - (void)longMethodWith:(NSString *)theFoo
- rect:(CGRect)theRect
- interval:(CGFloat)theInterval
- {
- //Implementation
- }
- //不要加get
- - (NSSize) cellSize; //推薦
- - (NSSize) getCellSize; //反對
- //使用情態動詞,不要使用do或does
- - (BOOL)canHide; //推薦
- - (BOOL)shouldCloseDocument; //推薦
- - (BOOL)doesAcceptGlyphInfo; //反對
二、代碼注釋規范
優秀的代碼大部分是可以自描述的,我們完全可以用代碼本身來表達它到底在干什么,而不需要注釋的輔助。
但并不是說一定不能寫注釋,有以下三種情況比較適合寫注釋:
- 公共接口(注釋要告訴閱讀代碼的人,當前類能實現什么功能)。
- 涉及到比較深層專業知識的代碼(注釋要體現出實現原理和思想)。
- 容易產生歧義的代碼(但是嚴格來說,容易讓人產生歧義的代碼是不允許存在的)。
除了上述這三種情況,如果別人只能依靠注釋才能讀懂你的代碼的時候,就要反思代碼出現了什么問題。
***,對于注釋的內容,相對于“做了什么”,更應該說明“為什么這么做”。
1、import注釋
如果有一個以上的import語句,就對這些語句進行分組,每個分組的注釋是可選的。
- // Frameworks
- #import ;
- // Models
- #import "NYTUser.h"
- // Views
- #import "NYTButton.h"
- #import "NYTUserView.h"
2、屬性注釋
寫在屬性之后,用兩個空格隔開
==例:==
- @property (nonatomic, readwrite, strong) UIView *headerView; //注釋
3、方法聲明注釋:
一個函數(方法)必須有一個字符串文檔來解釋,除非它:
- 非公開,私有函數。
- 很短。
- 顯而易見。
而其余的,包括公開接口,重要的方法,分類,以及協議,都應該伴隨文檔(注釋):
- 以/開始
- 第二行是總結性的語句
- 第三行永遠是空行
- 在與第二行開頭對齊的位置寫剩下的注釋。
建議這樣寫:
- /This comment serves to demonstrate the format of a doc string.
- Note that the summary line is always at most one line long, and after the opening block comment,
- and each line of text is preceded by a single space.
- */
方法的注釋使用Xcode自帶注釋快捷鍵:Commond+option+/
==例:==
- /**
- @param tableView
- @param section
- <a href='http://www.jobbole.com/members/wx1409399284'>@return</a>
- */
- - (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section
- {
- //...
- }
4、代碼塊注釋
單行的用//+空格開頭,多行的采用/* */注釋
5、TODO
使用//TODO:說明 標記一些未完成的或完成的不盡如人意的地方
==例:==
- - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
- {
- //TODO:增加初始化
- return YES;
- }
三、代碼格式化規范
1、指針*位置
定義一個對象時,指針*靠近變量
==例:== NSString *userName;
2、方法的聲明和定義
在 - 、+和 返回值之間留一個空格,方法名和***個參數之間不留空格
==例:==
- - (void)insertSubview:(UIView *)view atIndex:(NSInteger)index;
3、代碼縮進
- 不要在工程里使用 Tab 鍵,使用空格來進行縮進。在 Xcode > Preferences > Text Editing 將 Tab 和自動縮進都設置為 4 個空格
- Method與Method之間空一行
- 一元運算符與變量之間沒有空格、二元運算符與變量之間必須有空格
==例:==
- !bValue
- fLength = fWidth * 2;
- - (void)sampleMethod1;
- - (void)sampleMethod2;
4、對method進行分組
使用#pragma mark -對method進行分組
- #pragma mark - Life Cycle Methods
- - (instancetype)init
- - (void)dealloc
- - (void)viewWillAppear:(BOOL)animated
- - (void)viewDidAppear:(BOOL)animated
- - (void)viewWillDisappear:(BOOL)animated
- - (void)viewDidDisappear:(BOOL)animated
- #pragma mark - Override Methods
- #pragma mark - Intial Methods
- #pragma mark - Network Methods
- #pragma mark - Target Methods
- #pragma mark - Public Methods
- #pragma mark - Private Methods
- #pragma mark - UITableViewDataSource
- #pragma mark - UITableViewDelegate
- #pragma mark - Lazy Loads
- #pragma mark - NSCopying
- #pragma mark - NSObject Methods
5、大括號寫法
對于類的method:左括號另起一行寫(遵循蘋果官方文檔)
對于其他使用場景(if,for,while,switch等): 左括號跟在***行后邊
==例:==
- - (void)sampleMethod
- {
- BOOL someCondition = YES;
- if(someCondition) {
- // do something here
- }
- }
6、property變量
==例:==
- @property (nonatomic, readwrite, strong) UIView *headerView; //注釋
四、編碼規范
1、if語句
①、須列出所有分支(窮舉所有的情況),而且每個分支都須給出明確的結果。
==推薦這樣寫:==
- var hintStr;
- if (count < 3) {
- hintStr = "Good";
- } else {
- hintStr = "";
- }
==不推薦這樣寫:==
- var hintStr;
- if (count < 3) {
- hintStr = "Good";
- }
②、不要使用過多的分支,要善于使用return來提前返回錯誤的情況,把最正確的情況放到***返回。
==推薦這樣寫:==
- if (!user.UserName) return NO;
- if (!user.Password) return NO;
- if (!user.Email) return NO;
- return YES;
==不推薦這樣寫:==
- BOOL isValid = NO;
- if (user.UserName)
- {
- if (user.Password)
- {
- if (user.Email) isValid = YES;
- }
- }
- return isValid;
③、條件過多,過長的時候應該換行。條件表達式如果很長,則需要將他們提取出來賦給一個BOOL值,或者抽取出一個方法
==推薦這樣寫:==
- if (condition1 &&
- condition2 &&
- condition3 &&
- condition4) {
- // Do something
- }
- BOOL finalCondition = condition1 && condition2 && condition3 && condition4
- if (finalCondition) {
- // Do something
- }
- if ([self canDelete]){
- // Do something
- }
- - (BOOL)canDelete
- {
- BOOL finalCondition1 = condition1 && condition2
- BOOL finalCondition2 = condition3 && condition4
- return condition1 && condition2;
- }
==不推薦這樣寫:==
- if (condition1 && condition2 && condition3 && condition4) {
- // Do something
- }
④、條件語句的判斷應該是變量在右,常量在左。
==推薦:==
- if (6 == count) {
- }
- if (nil == object) {
- }
- if (!object) {
- }
==不推薦:==
- if (count == 6) {
- }
- if (object == nil) {
- }
if (object == nil)容易誤寫成賦值語句,if (!object)寫法很簡潔
⑤、每個分支的實現代碼都須被大括號包圍
==推薦:==
- if (!error) {
- return success;
- }
==不推薦:==
- if (!error)
- return success;
可以如下這樣寫:
- if (!error) return success;
2、for語句
①、不可在for循環內修改循環變量,防止for循環失去控制。
- for (int index = 0; index < 10; index++){
- ...
- logicToChange(index)
- }
②、避免使用continue和break。
continue和break所描述的是“什么時候不做什么”,所以為了讀懂二者所在的代碼,我們需要在頭腦里將他們取反。
其實***不要讓這兩個東西出現,因為我們的代碼只要體現出“什么時候做什么”就好了,而且通過適當的方法,是可以將這兩個東西消滅掉的:
- 如果出現了continue,只需要把continue的條件取反即可
- var filteredProducts = Array()
- for level in products {
- if level.hasPrefix("bad") {
- continue
- }
- filteredProducts.append(level)
- }
我們可以看到,通過判斷字符串里是否含有“bad”這個prefix來過濾掉一些值。其實我們是可以通過取反,來避免使用continue的:
- for level in products {
- if !level.hasPrefix("bad") {
- filteredProducts.append(level)
- }
- }
- 消除while里的break:將break的條件取反,并合并到主循環里
在while里的break其實就相當于“不存在”,既然是不存在的東西就完全可以在最開始的條件語句中將其排除。
while里的break:
- while (condition1) {
- ...
- if (condition2) {
- break;
- }
- }
取反并合并到主條件:
- while (condition1 && !condition2) {
- ...
- }
- 在有返回值的方法里消除break:將break轉換為return立即返回
有人喜歡這樣做:在有返回值的方法里break之后,再返回某個值。其實完全可以在break的那一行直接返回。
- func hasBadProductIn(products: Array) -> Bool {
- var result = false
- for level in products {
- if level.hasPrefix("bad") {
- result = true
- break
- }
- }
- return result
- }
遇到錯誤條件直接返回:
- func hasBadProductIn(products: Array) -> Bool {
- for level in products {
- if level.hasPrefix("bad") {
- return true
- }
- }
- return false
- }
這樣寫的話不用特意聲明一個變量來特意保存需要返回的值,看起來非常簡潔,可讀性高。
3、Switch語句
①、每個分支都必須用大括號括起來
推薦這樣寫:
- switch (integer) {
- case 1: {
- // ...
- }
- break;
- case 2: {
- // ...
- break;
- }
- default:{
- // ...
- break;
- }
- }
②、使用枚舉類型時,不能有default分支, 除了使用枚舉類型以外,都必須有default分支
- RWTLeftMenuTopItemType menuType = RWTLeftMenuTopItemMain;
- switch (menuType) {
- case RWTLeftMenuTopItemMain: {
- // ...
- break;
- }
- case RWTLeftMenuTopItemShows: {
- // ...
- break;
- }
- case RWTLeftMenuTopItemSchedule: {
- // ...
- break;
- }
- }
在Switch語句使用枚舉類型的時候,如果使用了default分支,在將來就無法通過編譯器來檢查新增的枚舉類型了。
4、函數
①、一個函數只做一件事(單一原則)
每個函數的職責都應該劃分的很明確(就像類一樣)。
==推薦:==
- dataConfiguration()
- viewConfiguration()
==不推薦:==
- void dataConfiguration()
- {
- ...
- viewConfiguration()
- }
②、對于有返回值的函數(方法),每一個分支都必須有返回值
==推薦:==
- int function()
- {
- if(condition1){
- return count1
- }else if(condition2){
- return count2
- }else{
- return defaultCount
- }
- }
==不推薦:==
- int function()
- {
- if(condition1){
- return count1
- }else if(condition2){
- return count2
- }
- }
③、對輸入參數的正確性和有效性進行檢查,參數錯誤立即返回
==推薦:==
- void function(param1,param2)
- {
- if(param1 is unavailable){
- return;
- }
- if(param2 is unavailable){
- return;
- }
- //Do some right thing
- }
④、如果在不同的函數內部有相同的功能,應該把相同的功能抽取出來單獨作為另一個函數
原來的調用:
- void logic() {
- a();
- b();
- if (logic1 condition) {
- c();
- } else {
- d();
- }
- }
將a,b函數抽取出來作為單獨的函數
- void basicConfig() {
- a();
- b();
- }
- void logic1() {
- basicConfig();
- c();
- }
- void logic2() {
- basicConfig();
- d();
- }
⑤、將函數內部比較復雜的邏輯提取出來作為單獨的函數
一個函數內的不清晰(邏輯判斷比較多,行數較多)的那片代碼,往往可以被提取出去,構成一個新的函數,然后在原來的地方調用它這樣你就可以使用有意義的函數名來代替注釋,增加程序的可讀性。
舉一個發送郵件的例子:
- openEmailSite();
- login();
- writeTitle(title);
- writeContent(content);
- writeReceiver(receiver);
- addAttachment(attachment);
- send();
中間的部分稍微長一些,我們可以將它們提取出來:
- void writeEmail(title, content,receiver,attachment)
- {
- writeTitle(title);
- writeContent(content);
- writeReceiver(receiver);
- addAttachment(attachment);
- }
然后再看一下原來的代碼:
- openEmailSite();
- login();
- writeEmail(title, content,receiver,attachment)
- send();