程式碼不規範,同事兩行淚

minjing_lin發表於2019-03-18

任何一個傻瓜都能寫出計算機可以理解的程式碼。唯有寫出人類容易理解的程式碼,才是優秀的程式設計師。 —— 佚名

本文是筆者結合公司程式碼規範要求,和之前看的《禪與 Objective-C 程式設計藝術》與《Effective Objective-C 2.0》書籍,以及參考相關部落格,總結出的一套iOS開發規範。有不足的地方,歡迎部落格下留言。

圖片發自簡書App

大括號

除了 .m 檔案中方法,其他的地方大括號"{"不需要另起一行。推薦:

if (!error) {
    return success;
}

- (void)doHomework
{
    if (self.hungry) {
        return;
    }
    //doSomething
}
複製程式碼

運算子

1. 一元運算子與變數之間沒有空格:

!aValue
-aValue  //負號
~aValue  //位非
++iCount
*strSource
複製程式碼

2. 二元運算子與變數之間必須有空格

fWidth = 5 + 5;
fLength = fWidth * 2;
for(int i = 0; i < 10; i++)
複製程式碼

3.三元運算子
當三元運算子的第二個引數(if 分支)返回和條件語句中已經檢查的物件一樣的物件的時候,下面的表達方式更靈巧:

result = object ? : [self createObject];
複製程式碼

不推薦:

result = object ? object : [self createObject];
複製程式碼

if語句

1. 儘量列出所有的情況,且給出明確的結果。

推薦:

var hintStr;
if (count < 3) {
  hintStr = "Good";
} else {
  hintStr = "";
}

複製程式碼

2. 黃金大道
在使用條件語句程式設計時,程式碼的左邊距應該是一條“黃金”或者“快樂”的大道,也就是說善於使用return來提前返回不符合的情況。

推薦:

- (void)someMethod {
    if (![someOther boolValue]) {
        return;
    }
    // Do something important
}
複製程式碼

3. 複雜的表示式
條件表示式如果比較複雜,則需要將他們提取出來賦給一個BOOL變數。 推薦:

BOOL nameContainsSwift  = [sessionName containsString:@"Swift"];
BOOL isCurrentYear      = [sessionDateCompontents year] == 2019;
BOOL isSwiftSession     = nameContainsSwift && isCurrentYear;

if (isSwiftSession) {
    // Do something very cool
}
複製程式碼

4.尤達表示式
尤達表示式是指,拿一個常量去和變數比較而不是拿變數去和常量比較。 推薦:

if (count == 6) {
}
複製程式碼
if (myValue == nil) {
}
複製程式碼
if (!object ) {
}
複製程式碼

不推薦:

if ( 6 == count) {
}
複製程式碼
if ( nil == object ) {
}
複製程式碼

5. 條件語句體應該總是被大括號包圍
儘管有時候你可以不使用大括號(比如,條件語句體只有一行內容),但是這樣做會帶來問題隱患。 推薦:

if (!error) {
  return success;
}
複製程式碼

不推薦:

if (!error)
    return success;
複製程式碼
if (!error) return success; 
複製程式碼

Switch語句

1. 每個分支都必須用大括號括起來

推薦:

switch (integer) {  
  case 1:  {
    // ...  
    break;  
  }
  case 2: {  
    // ...  
    break;  
  }  
  case 3: {
    // ...  
    break; 
  }
  default:{
    // ...  
    break; 
  }
}
複製程式碼

2.除了使用列舉型別以外,都必須有default分支

switch (menuType) {  
  case menuTypeLeft: {
    // ...  
    break; 
   }
  case menuTypeRight: {
    // ...  
    break; 
  }
  case menuTypeTop: {
    // ...  
    break; 
  }
  case menuTypeBottom: {
    // ...  
    break; 
  }
}
複製程式碼

在Switch語句使用列舉型別的時候,如果使用了default分支,在將來就無法通過編譯器來檢查新增的列舉型別了。


函式

1. 一個函式的長度儘量限制在50行以內
如果一個方法裡面的程式碼行數過多,程式碼的閱讀體驗極差。

2. 一個函式只做一件事(單一原則)
每個函式的職責都應該劃分的很明確(就像類一樣)。

3. 對於有返回值的函式,確保每個分支都有返回值

推薦:

int function()
{
    if(condition1){
        return count1
    }else if(condition2){
        return count2
    }else{
       return defaultCount
    } 
}
複製程式碼

4. 外部傳入的引數需要檢驗引數的非空、資料型別的合法性,引數錯誤立即返回或斷言

推薦:

void function(param1,param2)
{
      if(!param1){
           return;
      }
      if(!param2){
           return;
      }
     //Do some right thing
}
複製程式碼

5. 多個函式如果有邏輯重複的程式碼,建議將重複的部分抽取出來,成為獨立的函式進行呼叫

6. 如果方法引數過多過長,建議多行書寫,每個引數佔用一行,用冒號進行對齊 推薦:

- (void)initWithAge:(NSInteger)age
               name:(NSString *)name
             weight:(CGFloat)weight;
複製程式碼

7. 方法名中不應使用and,而且簽名要與對應的引數名保持一致

推薦:

- (instancetype)initWithWidth:(CGFloat)width height:(CGFloat)height;
複製程式碼

不推薦:

- (instancetype)initWithWidth:(CGFloat)width andHeight:(CGFloat)height;
複製程式碼

註釋

1.類的註釋
對於類的註釋寫在當前類檔案的頂部。

2.屬性註釋
對於屬性的註釋建議寫在屬性上面,用的時候,會有提示功能。

/// 重新整理按鈕
@property (nonatomic, strong) UIButton *refreshBtn;
複製程式碼

3.方法註釋
對於.h檔案中方法的註釋,通過快捷鍵command+option+/快速註釋; 對於.m檔案中方法的註釋,在方法的上邊新增//,註釋符和註釋內容需要間隔一個空格。例如

// load network data
複製程式碼

4.功能註釋
版本迭代中,在同事寫的程式碼基礎上開發,一定要寫上版本功能註釋,方便詢問具體功能。推薦

// 這是一個新加的功能 v5.20.0 by minjing.lin
複製程式碼

變數

1. 變數名必須使用駝峰格式
類,協議使用大駝峰:

HomePageViewController.h
<HeaderViewDelegate>
複製程式碼

物件等區域性變數使用小駝峰:

NSString *personName = @"";
NSUInteger totalCount = 0;
複製程式碼

2.變數的名稱必須同時包含功能與型別

UIButton *addBtn 
UILabel *nameLbl 
NSString *addressStr
複製程式碼

3. 系統常用類作例項變數宣告時加入字尾

型別 字尾
UIViewController VC
UIView View
UILabel Lbl
UIButton Btn
UIImage Img
UIImageView ImagView
NSArray Arr
NSMutableArray Marr
NSDictionary Dict
NSMutableDictionary Mdict
NSString Str
NSMutableString Mstr
NSSet Set
NSMutableSet Mset

常量

1. 常量以相關類名作為字首

推薦:

static const NSTimeInterval ZOCSignInViewControllerFadeOutAnimationDuration = 0.4;
複製程式碼

不推薦:

static const NSTimeInterval fadeOutTime = 0.4;
複製程式碼

2. 建議使用型別常量,不建議使用#define預處理命令

首先比較一下這兩種宣告常量的區別:

  • 預處理命令:簡單的文字替換,不包括型別資訊,並且可被任意修改。
  • 型別常量:包括型別資訊,並且可以設定其使用範圍,而且不可被修改。

推薦:

static const CGFloat ZOCImageThumbnailHeight = 50.0f;
複製程式碼

不推薦:

#define CompanyName @"Apple Inc." 
#define magicNumber 42 
複製程式碼

3. 對外公開某個常量

如果我們需要傳送通知,那麼就需要在不同的地方拿到通知的“頻道”字串(通知的名稱),那麼顯然這個字串是不能被輕易更改,而且可以在不同的地方獲取。這個時候就需要定義一個外界可見的字串常量。

推薦:

//.h
extern NSString *const ZOCCacheControllerDidClearCacheNotification;
複製程式碼
//.m
static NSString * const ZOCCacheControllerDidClearCacheNotification = @"ZOCCacheControllerDidClearCacheNotification";
複製程式碼

巨集

1. 字母全部大寫,單詞與單詞之間用_分割

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

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

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

列舉

當使用 enum 的時候,建議使用新的固定的基礎型別定義,因為它有更強大的型別檢查和程式碼補全。

typedef NS_ENUM(NSInteger, UIControlContentVerticalAlignment) {
    UIControlContentVerticalAlignmentCenter  = 0,
    UIControlContentVerticalAlignmentTop     = 1,
    UIControlContentVerticalAlignmentBottom  = 2,
    UIControlContentVerticalAlignmentFill    = 3,
};
複製程式碼

範型

建議在定義NSArray和NSDictionary時使用泛型,可以保證程式的安全性:

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

NSMutableArray

1. addObject之前要非空判斷。

2. 取下標的時候要判斷是否越界。

3. 取第一個元素或最後一個元素的時候使用firtstObject和lastObject


字面量語法

儘量使用字面量值來建立 NSString , NSDictionary , NSArray , NSNumber 這些不可變物件:

推薦:

NSArray *names = @[@"Brian", @"Matt", @"Chris", @"Alex", @"Steve"];
NSDictionary *productManagers = @{@"iPhone" : @"Kate", @"iPad" : @"Kamal", @"Mobile Web" : @"Bill"}; 
NSNumber *shouldUseLiterals = @YES;
NSNumber *buildingZIPCode = @10018; 
複製程式碼

不推薦:

NSArray *names = [NSArray arrayWithObjects:@"Brian", @"Matt", @"Chris", @"Alex", @"Steve", nil];
NSDictionary *productManagers = [NSDictionary dictionaryWithObjectsAndKeys: @"Kate", @"iPhone", @"Kamal", @"iPad", @"Bill" ];
NSNumber *shouldUseLiterals = [NSNumber numberWithBool:YES];NSNumber *buildingZIPCode = [NSNumber numberWithInteger:10018]; 

複製程式碼

Block

為常用的Block型別建立typedef
如果我們需要重複建立某種block(相同引數,返回值)的變數,我們就可以通過typedef來給某一種塊定義屬於它自己的新型別。

例如:

int (^variableName)(BOOL flag, int value) =^(BOOL flag, int value)
{
     // Implementation
     return someInt;
}
複製程式碼

這個Block有一個bool引數和一個int引數,並返回int型別。我們可以給它定義型別:

typedef int(^EOCSomeBlock)(BOOL flag, int value);
複製程式碼

再次定義的時候,就可以通過簡單的賦值來實現:

EOCSomeBlock block = ^(BOOL flag, int value){
     // Implementation
};
複製程式碼

定義作為引數的Block:

- (void)startWithCompletionHandler: (void(^)(NSData *data, NSError *error))completion;
複製程式碼

這裡的Block有一個NSData引數,一個NSError引數並沒有返回值

typedef void(^EOCCompletionHandler)(NSData *data, NSError *error);
- (void)startWithCompletionHandler:(EOCCompletionHandler)completion;”
複製程式碼

通過typedef定義Block簽名的好處是:如果要某種塊增加引數,那麼只修改定義簽名的那行程式碼即可。


屬性

1.書寫規則
@property、空格、括號、執行緒修飾詞、記憶體修飾詞、讀寫修飾詞、空格、類、物件名稱; 根據不同的場景選擇合適的修飾符。

推薦:

@property (nonatomic, copy, readonly) NSString *name;
@property (nonatomic, strong, readwrite) UIView *headerView;
@property (nonatomic, weak) id<#delegate#> delegate;
複製程式碼

2. Block屬性應該使用copy關鍵字

推薦:

typedef void (^ErrorCodeBlock) (id errorCode,NSString *message);
@property (nonatomic, copy) ErrorCodeBlock errorBlock;

@property (nonatomic, copy) <#returnType#> (^<#Block#>)(<#parType#>);
複製程式碼

3. 形容詞性的BOOL屬性的getter應該加上is字首

推薦:

@property (nonatomic, assign, getter=isEditable) BOOL editable;
複製程式碼

4. 對外儘量使用不可變物件

儘量把對外公佈出來的屬性設定為只讀,在實現檔案內部設為讀寫。具體做法是:

  • 在標頭檔案中,設定物件屬性為readonly
  • 在實現檔案中設定為readwrite

這樣一來,在外部就只能讀取該資料,而不能修改它,使得這個類的例項所持有的資料更加安全。而且,對於集合類的物件,更應該仔細考慮是否可以將其設為可變的。

如果在公開部分只能設定其為只讀屬性,那麼就在非公開部分儲存一個可變型。所以當在外部獲取這個屬性時,獲取的只是內部可變型的一個不可變版本,例如:

在公共API中:

@interface EOCPerson : NSObject

@property (nonatomic, copy, readonly) NSString *firstName;
@property (nonatomic, copy, readonly) NSString *lastName;
@property (nonatomic, strong, readonly) NSSet *friends //向外公開的不可變集合

- (id)initWithFirstName:(NSString*)firstName andLastName:(NSString*)lastName;
- (void)addFriend:(EOCPerson*)person;
- (void)removeFriend:(EOCPerson*)person;

@end

複製程式碼

在這裡,我們將friends屬性設定為不可變的set。然後,提供了來增加和刪除這個set裡的元素的公共介面。

在實現檔案裡:


@interface EOCPerson ()

@property (nonatomic, copy, readwrite) NSString *firstName;
@property (nonatomic, copy, readwrite) NSString *lastName;

@end

@implementation EOCPerson {
     NSMutableSet *_internalFriends;  //實現檔案裡的可變集合
}

- (NSSet*)friends 
{
     return [_internalFriends copy]; //get方法返回的永遠是可變set的不可變型
}

- (void)addFriend:(EOCPerson*)person 
{
    [_internalFriends addObject:person]; //在外部增加集合元素的操作
    //do something when add element
}

- (void)removeFriend:(EOCPerson*)person 
{
    [_internalFriends removeObject:person]; //在外部移除元素的操作
    //do something when remove element
}

- (id)initWithFirstName:(NSString*)firstName andLastName:(NSString*)lastName 
{

     if ((self = [super init])) {
        _firstName = firstName;
        _lastName = lastName;
        _internalFriends = [NSMutableSet new];
    }
 return self;
}

複製程式碼

我們可以看到,在實現檔案裡,儲存一個可變set來記錄外部的增刪操作。

這裡最重要的程式碼是:

- (NSSet*)friends 
{
   return [_internalFriends copy];
}

複製程式碼

這個是friends屬性的獲取方法:它將當前儲存的可變set複製了一不可變的set並返回。因此,外部讀取到的set都將是不可變的版本。


代理方法

1. 代理方法的第一個引數必須為委託者

代理方法必須以委託者作為第一個引數(參考UITableViewDelegate)的方法。其目的是為了區分不同委託著的例項。因為同一個控制器是可以作為多個tableview的代理的。例如:

-  (void)tableView:(UITableView *)tableView 
didSelectRowAtIndexPath:(NSIndexPath *)indexPath
複製程式碼

2.向代理髮送訊息時需要判斷其是否實現該方法

推薦:

if ([self.delegate respondsToSelector:@selector(signUpViewControllerDidPressSignUpButton:)]) { 
 [self.delegate signUpViewControllerDidPressSignUpButton:self]; 
} 
複製程式碼

3. 遵循代理過多的時候,換行對齊顯示 推薦:

@interface ShopViewController () <UIGestureRecognizerDelegate,
                                  HXSClickEventDelegate,
                                  UITableViewDelegate,
                                  UITableViewDataSource>

複製程式碼

4. 代理的方法需要明確必須執行和可不執行

  • @required:必須實現的方法
  • @optional:可選是否實現的方法
@protocol ZOCServiceDelegate <NSObject>
@optional
- (void)generalService:(ZOCGeneralService *)service didRetrieveEntries:(NSArray *)entries; 
@end 

複製程式碼

1. 類的名稱
應該以三個大寫字母為字首;建立子類的時候,應該把代表子類特點的部分放在字首和父類名的中間。

推薦:

//父類
ZOCSalesListViewController

//子類
ZOCDaySalesListViewController
ZOCMonthSalesListViewController
複製程式碼

2. 所有返回類物件和例項物件的方法都應該使用instancetype

將instancetype關鍵字作為返回值的時候,可以讓編譯器進行型別檢查,同時適用於子類的檢查,這樣就保證了返回型別的正確性(一定為當前的類物件或例項物件)

推薦:

- (instancetype)init 
{ 
    self = [super init]; // call the designated initializer 
    if (self) { 
        // Custom initialization 
    } 
    return self; 
} 

複製程式碼
@interface ZOCPerson
+ (instancetype)personWithName:(NSString *)name; 
@end 

複製程式碼

不推薦:

@interface ZOCPerson
+ (id)personWithName:(NSString *)name; 
@end 

複製程式碼

3. 在類的.h檔案中儘量少引用其他標頭檔案

有時,類A需要將類B的例項變數作為它公共API的屬性。這個時候,我們不應該引入類B的標頭檔案,而應該使用向前宣告(forward declaring)使用class關鍵字,並且在A的實現檔案引用B的標頭檔案。

// EOCPerson.h
#import <Foundation/Foundation.h>

@class EOCEmployer;

@interface EOCPerson : NSObject

@property (nonatomic, copy) NSString *firstName;
@property (nonatomic, copy) NSString *lastName;
@property (nonatomic, strong) EOCEmployer *employer;//將EOCEmployer作為屬性

@end

// EOCPerson.m
#import "EOCEmployer.h"

複製程式碼

優點:

  • 不在A的標頭檔案中引入B的標頭檔案,就不會一併引入B的全部內容,這樣就減少了編譯時間。

  • 可以避免迴圈引用:因為如果兩個類在自己的標頭檔案中都引入了對方的標頭檔案,那麼就會導致其中一個類無法被正確編譯。

但是個別的時候,必須在標頭檔案中引入其他類的標頭檔案:

  • 該類繼承於某個類,則應該引入父類的標頭檔案。
  • 該類遵從某個協議,則應該引入該協議的標頭檔案。而且最好將協議單獨放在一個標頭檔案中。

4. 類的佈局

#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 - Network Methods

#pragma mark - Target Methods

#pragma mark - Public Methods

#pragma mark - Private Methods

#pragma mark - UITableViewDataSource  
#pragma mark - UITableViewDelegate  

#pragma mark - Setters and Getters

複製程式碼

可以使用程式碼塊一鍵生成,參考Xcode 快速開發 程式碼塊


相等性的判斷

判斷兩個person類是否相等的合理做法:

-  (BOOL)isEqual:(id)object 
{

    if (self == object) {  
        return YES; //判斷記憶體地址
    } 

    if (![object isKindOfClass:[ZOCPerson class]]) { 
        return NO; //是否為當前類或派生類 
     } 

     return [self isEqualToPerson:(ZOCPerson *)object]; 

}

//自定義的判斷相等性的方法
-  (BOOL)isEqualToPerson:(Person *)person 
{ 
        if (!person) {  
              return NO;
        } 
        BOOL namesMatch = (!self.name && !person.name) || [self.name isEqualToString:person.name]; 
        BOOL birthdaysMatch = (!self.birthday && !person.birthday) || [self.birthday isEqualToDate:person.birthday]; 
        return haveEqualNames && haveEqualBirthdays; 
} 

複製程式碼

圖片命名

1.命名規範:不能有中文、大寫、特殊符號、空白
2.命名格式(推薦): fileType[function]project[pageName]imageName[status]{.png,@2x.png,@3x.png}
萬能公式:類別_功能_模組_頁面_名稱_狀態.png

icon_tab_bookshelf_sel@2x.png
複製程式碼

面試題(風格糾錯)

typedef  enum{
    UserSex_Man,
    UserSex_Woman
}UserSex;
@interface UserModel :NSObject

@property(nonatomic, strong) NSString *name;
@property (assign,nonatomic) int age;
@property (nonatomic,assign) UserSex sex;

-(id)initUserModelWithUserName: (NSString*)name withAge(int age);

-(void)doLogIn;
@end

複製程式碼

參考文獻:

禪與 Objective-C 程式設計藝術
iOS 程式碼規範
看完這個你們團隊的程式碼也很規範
《Effective Objective-C 2.0》

相關文章