1. 程式人生 > >iOS程式碼程式設計規範 根據專案經驗彙總

iOS程式碼程式設計規範 根據專案經驗彙總

帶出幾十位從零開始學iOS的實習生或試用期的開發人員後,覺得真的是千人千面,每個人寫的程式碼都風格迥異,如果沒有一個文件規範,每次都和新人進行口頭的說教,大概自己是不用敲程式碼了,所以吃了虧了就開始編寫iOS的程式設計規範。由於本人在寫iOS程式碼前一直是C語言的開發,所以很多規範都受C語言的影響。

程式設計規範.png

與大家分享下我總結的程式設計規範,有不合適的請大家指出(最好能舉例說明為何不好,並給一個好的推薦)^_^

1. 指導原則

(1)首先是為人編寫程式,其次才是計算機。

軟體的生命週期貫穿產品的開發,測試,生產,使用者使用,版本升級和後期維護等過程,只有易讀,易維護的軟體程式碼才具有生命力。

(2)保持程式碼的簡明清晰,避免過分的程式設計技巧。

簡單是最美。不要過分追求技巧,否則會降低程式的可讀性。

(3)程式設計時首先達到正確性,其次考慮效率。

程式設計首先考慮的是滿足正確性,健壯性,可維護性,可移植性等質量因素。

(4)編寫程式碼時需要考慮到程式碼的可測試性。

不可以測試的程式碼是無法保障質量的。實現設計功能的同時,要提供可以測試、驗證的方法。

(5)函式(方法)是為一特定功能而編寫,不是萬能工具箱。

方法是一個處理單元,是由特定功能的,所以應該很好地規劃方法,不能是所有東西都放在一個方法裡實現。

(6)鼓勵多註釋。

當代碼有改動時,註釋一定要修改,並且不要新增多餘或重複的註釋。

2. 佈局

程式佈局的目的是現實出程式良好的邏輯結構,提高程式的準確性、連續性、可讀性、可維護性。統一的程式佈局和程式設計風格,有助於提高整個專案的開發質量,提高開發效率,降低開發成本。

遵循統一的佈局順序書寫標頭檔案和實現檔案:

檔案頭註釋 #import 檔案內部使用的巨集 常量定義 檔案內部使用的資料型別 全域性變數 本地變數 類定義/實現

(1)If、else、else if、for、while、do等語句自佔一行,執行語句不得緊跟其後,不論執行語句有多少都要加{}。

“{”前面新增一個空格,緊跟語句後。方法(函式)時,“{”另起一行並獨        佔一行書寫。

if ( numberA > numberB ) {
    return numberA;
}
複製程式碼

(2)定義指標型別的變數,“*****” 應該放在變數前。

NSString *str1 = @"Hello World!";
複製程式碼

(3)原始碼中關係較為緊密的程式碼應儘可能相鄰。

CGFloat fWidth;
CGFloat fLength;
CGFloat fHeight;
複製程式碼

(4)禁止使用TAB鍵,必須使用空格進行縮排,縮排為4個空格。

在Xcode->Preferences->Text Editing->Indentation->Prefer indent using中,將值設定為Spaces。

(5)程式的分界符“{”和“}”在if、else、else if、for、while、do等語句時,“{”前新增空格緊跟語句後。在方法(函式)應獨佔一行並且位於同一列,同時與引用他們的語句對齊。“{}”之內的程式碼塊使用縮排規則對齊。

- (void)dealloc
{
    // Do Something
}

if (isUpdated) {
        // Do Something
}
複製程式碼

(6)相關的賦值語句等號對齊。

promotionsEntity.promotionImageStr   = activityItemDict[@"promotion_image"];
promotionsEntity.promotionIdNum      = activityItemDict[@"promotion_id"];
promotionsEntity.promotionNameStr    = activityItemDict[@"promotion_name"];
promotionsEntity.promotionColorStr   = activityItemDict[@"promotion_color"];
複製程式碼

(7)在switch語句中,每一個case分支和default要用“{}”括起來,“{}”中的內容需要縮排。

(8)函式(方法)塊之間使用一個空行分隔。

(9)一元操作符如!、~、++、--、*、&、和[]、.、->、前後不加空格。

!bValue
~iValue
++iCount
*strSource
&fSum
複製程式碼

(10)多元運算子和他們的運算元之間至少需要一個空格。

fWidth = 5 + 5;
fLength = fWidth * 2;
fHeight = fWidth + fLength;
複製程式碼

(11)關鍵字之後要留空格。“(”後和“)”前 不新增空格。

if、for、while等關鍵字之後應留一個空格再跟左括號”(”。

if (0 == fWidth)
複製程式碼

(12)方法名與形參不能留空格,返回型別與方法識別符號有一個空格。

方法名後緊跟”:”,然後緊跟形參,返回型別”(”與”-”之間有一個空格。

- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
複製程式碼

(13)註釋符與註釋內容之間要用一個空格進行分割。

/* 設定Frame的值 */
// TO DO
複製程式碼

(14)長表示式(超過80列)要在低優先順序操作符處拆分成新行,操作符放在新行之首(以便突出操作符)。拆分出的新行要進行適當地縮排,使排版整齊。

if ( 0 == fWidth
    || 0 < fLength )
複製程式碼

(15)函式(方法)宣告時,型別與名稱不允許分行書寫。

- (void)didReceiveMemoryWarning
複製程式碼

(16)類中功能模組以#pragma mark – 分割,上空兩行,下空一行

#pragma mark – UITextFieldDelegate
複製程式碼

(17)方法實現時,引數過長則每個引數用一行,以冒號對齊。

- (void)doSomethingWith:(NSString *)theFoo
                   rect:(CGRect)theRect
               interval:(CGFloat)theInterval
{
複製程式碼

如果方法名比引數名短,則每個引數佔用一行,至少縮排4個字元,且為垂直對齊(非冒號對齊)。

- (void)short:(NSString *)theFoot
    longKeyword:(CGRect)theRect
    evenLongerkeyword:(float)theInterval
{
}
複製程式碼

(18)方法呼叫沿用宣告方法的習慣。

在同一行

[self doSomethingWith:@"test" rect:self.view.frame interval:1.0f];
複製程式碼

或 冒號對齊

[self doSomethingWith:@"test"
                 rect:self.view.frame
             interval:1.0f];
複製程式碼

(19)@public 和 @private 使用單獨一行,且縮排1個字元

(20)Protocals宣告中型別識別符號、代理名稱、尖括號間不留空格。

id<MyProtocalDelegate> delegate;
複製程式碼

在類宣告中包含多個protocal,每個protocal佔用一行,縮排2個字元。

@interface CustomBackButtonViewController () <UITextFieldDelegate,
  MyProtocalDelegate,
  UITabBarControllerDelegate,
  UITabBarDelegate>
複製程式碼

也可以與第一個對齊,保持清晰易讀

@interface ShopViewController () <UIGestureRecognizerDelegate,
                                  HXSClickEventDelegate,
                                  UITableViewDelegate,
                                  UITableViewDataSource>
複製程式碼

如果並非所有方法都是必須得,使用@optional標示。

(21)UIViewController.m的檔案佈局,不同變數和方法的先後順序:

<1>#import “LocationCustomButton.h”

最開始是匯入需要使用到的其他class標頭檔案,標頭檔案按型別分類

// controller
#import “TextSelectionViewController.h"
// model
#import "PayPasswordUpdateModel.h"
#import "WalletModel.h"
// views
#import "PayPasswordAlertView.h"
複製程式碼
<2>#define KEY_BANK_TAIL @“bank_tail"

新增巨集定義,將在檔案中需要使用到的常量,字串等用巨集定義。

<3>@property (nonatomic, strong) NSArray *bankCardList;

property的屬性定義。不用將需要使用的methods宣告,在iOS7後Methods已經不需要先宣告再使用了。

<4>- (void)viewDidLoad

#pragma mark - Life Cycle 新增生命週期的方法

- (void)viewWillAppear:(BOOL)animated
 - (void)viewDidAppear:(BOOL)animated
- (void)viewWillDisappear:(BOOL)animated
-(void)viewDidDisappear:(BOOL)animated
- (void)didReceiveMemoryWarning
- (void)dealloc
複製程式碼
<5>#pragma mark - override

過載父類的方法

<6>#pragma mark - Intial Methods

初始化的方法,如

- (void)initialNavigationBar
- (void)initialTableView
複製程式碼
<7>#pragma mark - Target Methods

點選事件或通知事件

<8>#pragma mark - UITextFieldDelegate

Delegate的事件,將所有的delegate放在同一個pragma下

<9>#pragma mark - Setter Getter Methods

所有的property使用懶載入,並將setter或getter放在底部,不影響業務程式碼的閱讀。

<10>#pragma mark - private method

私有方法的程式碼儘量抽取建立公共class。

3. 註釋

(1)多行註釋採用”/* … */”, 單行註釋採用 “// …”.

(2)一般情況下,源程式有效註釋量必須在30%以上。

註釋語言不宜太多也不能太少,註釋語言必須準確、易懂、簡潔。

(3)註釋應與其描述的程式碼相近,對程式碼的註釋應放在其上方或右方相鄰位置,不可放在下方,如放於上方則需要與其上面的程式碼用空行隔開。

(4)註釋與所描述內容進行同樣地縮排。

(5)檔案最前面的註釋,是保留了工程自動生成的註釋,但是需要進行如下修改:檔案作者改為個人的名字,公司名為Insigma Hengtian software Ltd. 如:

//
//  ViewController.m
//  test
//
//  Created by ArthurWang on 14-5-7.
//  Copyright (c) 2014年 Insigma HengTian Software Ltd. All rights reserved.
//
複製程式碼

(6)類、協議、結構體註釋。如:

/*
 @class HTServerDatamanager
 @abstract 非同步連線伺服器管理類
 @discussion 非同步請求伺服器,接收到響應後,通過回撥把資料回傳到物件
 */
複製程式碼

(7)成員方法、介面註釋。如:

/*
 @method  initWithTarget:selector:
 @abstract  類初始化函式
 @discussion  本類使用時必須呼叫此函式進行初始化
 @param target 響應回撥物件
 @param selector  回撥物件的selector
 @result   類物件
 */
複製程式碼

4. 命名

(1)識別符號要採用英文單詞或其組合,便於記憶和閱讀,切忌使用漢語拼音來命名。

識別符號應當直觀且可以拼讀,可望文知意,英文單詞一般不要太複雜,用詞應當準確。

(2)嚴格禁止使用連續的下劃線,下劃線也不能出現在識別符號頭或結尾。(例項變數及特殊用法除外)

CGFloat variable__name;
NSString *variale___name;
複製程式碼

(3)程式中不要出現僅靠大小區分的相似的識別符號。

NSString *contentOfView;
NSString *ContentOfView;
複製程式碼

(4)巨集、常量名都要使用大寫字母,用下劃線‘_’分割單詞。

#define URL_GAIN_QUOTE_LIST @"/v1/quote/list"
#define URL_UPDATE_QUOTE_LIST @"/v1/quote/update"
#define URL_LOGIN   @"/v1/user/login”
複製程式碼

(5)程式中區域性變數不要與全域性變數重名。

儘管區域性變數和全域性變數的作用域不同而不會發生語法錯誤,但容易使人誤解。

(6)使用一致的字首來區分變數的作用域。

g_  全域性變數 s_  模組內靜態變數

(7)方法名用小寫字母開頭的單詞組合而成。

方法名力求清晰、明瞭、通過方法名就能夠判斷方法的主要功能。方法名中不同意義欄位之間不要用下劃線連線,而要把每個欄位的首字母大寫以示區分。

- (NSString *)descriptionWithLocale:(id)locale;
複製程式碼

(8)儘量避免名字中出現數字編號,如Value1, Vlaue2等,除非邏輯上的確需要編號。

(9)宣告例項變數,都採用property。

不在類宣告和實現的“{”與“}”之間宣告。

@interface LumberjackViewController ()
{
    NSArray *dataSource;  // 錯誤:
}
複製程式碼

(10)類名(及其category 和protocal)的首字母大寫,使用字母大寫的形式分割單詞。

5. 變數

變數、常量和資料型別是程式編寫的基礎,是直接關係到程式設計的成敗。

(1)一個變數有且只有一個功能,儘量不要把一個變數用作多種用途。

一個變數只用來表示一個特定功能,不要把一個變數作多種用途。

(2)迴圈語句與判斷語句中,不允許對其它變數進行計算與賦值。

錯誤: if ( 100 > (fWidth = 50 * fLength) )

(3)巨集定義中如果包含表示式或變數,表示式和變數必須用小括號括起來。

#define MY_MIN(A, B)   ((A)>(B)?(B):(A))
複製程式碼

(4)巨集名大寫字母

(5)對於全域性變數通過統一的函式訪問。

(6)最好不要在語句塊內宣告區域性變數。

(7)系統常用類作例項變數宣告時加入字尾:

UIViewController: VC                      UIImage: Img
UIImageView:ImagView                   UIView:View
UILabel: Lbl                                    UIButton:Btn
UINavigationBar:Nbar                     UIToolbar:Tbar
UISearchBar:Sbar                            UITextField:TextField
UITextView:TextView                      NSArray:Array
NSMutableArray:Marray                  NSDictionary:Dict
NSMutableDictionary:Mdict             NSString:Str
NSMutableString:MStr                     NSSet:Set
NSMutableSet:Mset
複製程式碼

(8)屬性宣告嚴把許可權,對不需要外部修改的屬性使用readonly。

(9)NSString使用copy而非retain。

在ARC中NSString的使用Strong與Copy的效果一樣。

(10)CFType 使用@dynamic,禁止使用@synthesize

(11)除非必須,使用nonatomic。

(12)定義NSArray和NSDictionary使用泛型,提高程式碼可讀性和健壯性。

NSArray<NSString *> *testArr = [NSArray arrayWithObjects:@"Hello", @"world", nil];
NSDictionary<NSString *, NSNumber *> *dic = @{@"key":@(1), @"age":@(10)};
複製程式碼

在* 符號前面都新增一空格。

6. 表示式

表示式是語句的一部分,他們是不可分割的。

(1)一條語句只完成一個功能。

複雜的語句閱讀起來,難於理解,並容易隱含錯誤。

(2)在表示式中使用括號,使表示式的運算順序更清晰。

由於將運算子的優先順序與結合律熟記是比較困難的,為了防止產生歧義並提高可讀性,即使不加括號時運算順序不會改變,也應當用括號確定表示式的操作順序。

if ( (( 0 == iYear%4 ) && ( 0 != iYear%100 )) || ( 0 == iYear%400 ) )
複製程式碼

(3)避免表示式中的附加功能,不要編寫太複雜的複合表示式。

錯誤:int iResult = iYear++-++iMonth+iDay++;

(4)不可將布林變數和邏輯表示式直接與YES、NO或則1、0進行比較。

if (isSuccess)   //真
if (!isSuccess)  //假
複製程式碼

(5)在條件判斷語句中,當整型變數與0比較時,不可模仿布林變數的風格,應當將整型變數用“==”或“!=”直接與0比較。

if (0 == iYear)
if ( 0 != iMonth )
複製程式碼

(6)應當將指標變數用“==”或“!=”與nil比較。

指標變數的零值是“空”(即nil),nil的值與0相同,但是兩者含義不同。

if ( nil == strName )
複製程式碼

(7)在switch語句中,每一個case分支必須使用break結尾,最後一個分支必須是default分支。

避免漏掉break語句造成程式錯誤,同時保持程式簡潔。對於多個分支相同處理的情況可以共用一個break,但是要用註釋加以說明。

(8)不可在for迴圈內修改迴圈變數,防止for迴圈失去控制。

(9)迴圈巢狀次數不大於3次。

(10)do while 語句和while語句僅使用一個條件。

(11)如果迴圈體記憶體在邏輯判斷,並且迴圈次數很大,宜將邏輯判斷移到迴圈體的外面。

(12)for語句的迴圈控制變數的取值採用“半開半閉區間”寫法。

這樣做更能適應陣列的特點,陣列的下標屬於一個“半開半閉區間”。

int iMax[1000];
for (int i = 0; i < 1000; i++)
{
    NSLog(@"%d", iMax[i]);
}
複製程式碼

(13)將int值轉換為BOOL時應特別小心。

(14)OC中,BOOL被定義為unsigned char,這意味著除了YES(1)和NO(0)外它還可以是其他值。禁止將int直接轉換為BOOL。

(15)將整型值轉換為BOOL的方法:使用三元運算子返回YES/NO,或使用&&,||。

(16)BOOL、_BOOL和bool之間的轉換是安全的,但是BOOL和Boolean間的轉換不是安全的,所以將Boolean堪稱整型值。

(17)在OC中,只允許使用BOOL。

7. 函式

(1)方法不能為多個目的服務。

一個方法一個功能。

(2)在介面中應該儘量少使用外部定義的型別(減少耦合)。

(3)避免函式有太多的引數,引數個數儘量控制在5個以內。

如果引數的確比較多,不妨把這些引數定義成一個結構(或一個類)。

(4)對於有返回值的函式(方法),每一個分支都必須有返回值。

為了保證對被呼叫函式返回值的判斷,有返回值的函式中都每一個退出點都需要有返回值。

(5)對輸入引數的正確性和有效性進行檢查。

很多程式錯誤和崩潰是由非法引數引起的。

(6)防止將函式(方法)的引數作為工作變數。

將函式的引數作為工作變數,有可能錯誤地改變引數內容。對必須改變的引數,最好先用區域性變數代之,最後再將該區域性變數的內容賦給該引數。

(7)函式(方法)體的規模不能太大,儘量控制在200行之內。

冗長的函式不利於除錯,可讀性差。

(8)禁止直接呼叫NSObject的類方法+new,也不要在子類中過載它。使用alloc和init方法。

(9)建立物件時儘量使用autorelease,建立臨時物件時,儘量同時在同一行中autorelease掉,而非使用單獨的release語句。

(10)Dealloc的順序要與變數宣告的順序相同。

這樣有利於review程式碼。 如果dealloc中呼叫其他方法來release變數,將被release的變數以註釋的形式標註清楚。 先release自身成員變數,再呼叫父類dealloc方法。

8. 標頭檔案

(1)申明成員類,應該引用該類申明,而不是包含該類的標頭檔案。

@class MyViewController;
@interface ViewController : UIViewController
{
    MyViewController *_myViewController;
}
複製程式碼

(2)共同的介面、結構體、常量和資料型別要定義在同一個標頭檔案裡。

(3)使用#import引入OC和OC++標頭檔案,使用#include引入c和c++標頭檔案。

9. 可靠性

為保證程式碼的可靠性,程式設計時請遵循如下基本原則,優先順序遞減: 正確性 穩定性 可測試性 規範/可讀性 全域性效率 區域性效率 個人表達方式/個人方便性

(1)防止記憶體操作越界

記憶體操作主要是指對陣列、指標、記憶體地址等得操作,記憶體操作越界是軟體系統主要錯誤之一,後果往往非常嚴重,引起崩潰。

(2)當變數釋放後,需要將變數置為nil。

避免因為野指標引起的程式崩潰。

(3)變數在使用前應初始化,防止未初始化的變數被引用。

引用未初始化的變數,會引起程式的崩潰。

(4)指標型別變數必須初始化為nil。

(5)指標不要進行復雜的邏輯或算術操作。

通過複雜的邏輯或算術操作後,指標的位置就很難確定。

(6)減少指標和資料型別的強制型別轉化。

強制型別轉化如果型別強轉錯誤會引起崩潰。

(7)對變數進行賦值時,必須對其值進行合法性檢查,防止越界等現象發生。

(8)非初始化方法中的alloc操作之前必須要nil判斷。

(9)在編寫派生類的賦值時,主要不要忘記對基類的成員變數重新賦值。

- (void)didReceiveMemoryWarning
{
    [super didReceiveMemoryWarning];
}
複製程式碼

(10)私有方法應該在實現檔案中申明。

@interface ViewController ()
- (void)login;
@end
複製程式碼

10. 斷言

斷言是對某種假設條件進行檢查,可以快速發現並定位軟體檔案,同時對系統錯誤進行自動報警。

(1)整個軟體系統應該採用統一的斷言。

assert(str);

(2)正式軟體產品中應把斷言及其它的調測程式碼去掉。

加快軟體執行速度。

11. 其他

(1) 避免過多直接使用立即數。

應該都使用巨集定義,採用立即數不容易理解含義並容易出錯。

(2) 列舉第一個成員要賦初始值。

(3) addObject之前要非空判斷。

(4) release版本程式碼去掉NSLog列印,除了保留異常分支的NSLog。

(5) 禁止在程式碼中直接寫死字串資源,必須要用字串ID替代。

應該考慮多語言國際化,儘量使用NSLocalizedStringFromTable實現對字串ID的引用。

(6) 對於框架設計,邏輯層儘量與UI層分離,降低耦合度。

(7) delegate物件使用assign,禁止使用retain。

因為retain會引起導致迴圈索引導致記憶體洩露,並且對型別的記憶體洩露無法被Instrument發現,極難除錯。

(8) Controller獨立於View和Controller。

不要在View相關的類中新增過多的業務邏輯程式碼,這讓程式碼的可重用性很差。Controller負責業務邏輯程式碼,且Controller的程式碼與View儘量無關。

(9)init方法和dealloc方法是最常用的方法,所以將他們放在類實現的開始位置。

(10) 使用空格將相同的變數、屬性對齊,使用換行分組。

// END

總結寫完了,複製黏貼好累啊。發現很多條在開發中都沒有遵守,因為需求變動實在太頻繁了,並且要求的開發時間實在太短。吐槽下產品組,能不能多考慮多考慮然後聽聽我們開發的意見啊。!_!