代碼不規(guī)范,同事兩行淚

任何一個傻瓜都能寫出計算機可以理解的代碼。唯有寫出人類容易理解的代碼,才是優(yōu)秀的程序員。 —— 佚名

本文是筆者結(jié)合公司代碼規(guī)范要求,和之前看的《禪與 Objective-C 編程藝術(shù)》與《Effective Objective-C 2.0》書籍,以及參考相關(guān)博客,總結(jié)出的一套iOS開發(fā)規(guī)范。有不足的地方,歡迎博客下留言。

圖片發(fā)自簡書App

大括號

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

if (!error) {
    return success;
}

- (void)doHomework
{
    if (self.hungry) {
        return;
    }
    //doSomething
}

運算符

1. 一元運算符與變量之間沒有空格:

!aValue
-aValue  //負(fù)號
~aValue  //位非
++iCount
*strSource

2. 二元運算符與變量之間必須有空格

fWidth = 5 + 5;
fLength = fWidth * 2;
for(int i = 0; i < 10; i++)

3.三元運算符
當(dāng)三元運算符的第二個參數(shù)(if 分支)返回和條件語句中已經(jīng)檢查的對象一樣的對象的時候,下面的表達方式更靈巧:

result = object ? : [self createObject];

不推薦:

result = object ? object : [self createObject];

if語句

1. 盡量列出所有的情況,且給出明確的結(jié)果。

推薦:

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

2. 黃金大道
在使用條件語句編程時,代碼的左邊距應(yīng)該是一條“黃金”或者“快樂”的大道,也就是說善于使用return來提前返回不符合的情況。

推薦:

- (void)someMethod {
    if (![someOther boolValue]) {
        return;
    }
    // Do something important
}

3. 復(fù)雜的表達式
條件表達式如果比較復(fù)雜,則需要將他們提取出來賦給一個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. 條件語句體應(yīng)該總是被大括號包圍
盡管有時候你可以不使用大括號(比如,條件語句體只有一行內(nèi)容),但是這樣做會帶來問題隱患。
推薦:

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分支,在將來就無法通過編譯器來檢查新增的枚舉類型了。


函數(shù)

1. 一個函數(shù)的長度盡量限制在50行以內(nèi)
如果一個方法里面的代碼行數(shù)過多,代碼的閱讀體驗極差。

2. 一個函數(shù)只做一件事(單一原則)
每個函數(shù)的職責(zé)都應(yīng)該劃分的很明確(就像類一樣)。

3. 對于有返回值的函數(shù),確保每個分支都有返回值

推薦:

int function()
{
    if(condition1){
        return count1
    }else if(condition2){
        return count2
    }else{
       return defaultCount
    } 
}

4. 外部傳入的參數(shù)需要檢驗參數(shù)的非空、數(shù)據(jù)類型的合法性,參數(shù)錯誤立即返回或斷言

推薦:

void function(param1,param2)
{
      if(!param1){
           return;
      }
      if(!param2){
           return;
      }
     //Do some right thing
}

5. 多個函數(shù)如果有邏輯重復(fù)的代碼,建議將重復(fù)的部分抽取出來,成為獨立的函數(shù)進行調(diào)用

6. 如果方法參數(shù)過多過長,建議多行書寫,每個參數(shù)占用一行,用冒號進行對齊
推薦:

- (void)initWithAge:(NSInteger)age
               name:(NSString *)name
             weight:(CGFloat)weight;

7. 方法名中不應(yīng)使用and,而且簽名要與對應(yīng)的參數(shù)名保持一致

推薦:

- (instancetype)initWithWidth:(CGFloat)width height:(CGFloat)height;

不推薦:

- (instancetype)initWithWidth:(CGFloat)width andHeight:(CGFloat)height;

注釋

1.類的注釋
對于類的注釋寫在當(dāng)前類文件的頂部。

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

/// 刷新按鈕
@property (nonatomic, strong) UIButton *refreshBtn;

3.方法注釋
對于.h文件中方法的注釋,通過快捷鍵command+option+/快速注釋;
對于.m文件中方法的注釋,在方法的上邊添加//,注釋符和注釋內(nèi)容需要間隔一個空格。例如

// load network data

4.功能注釋
版本迭代中,在同事寫的代碼基礎(chǔ)上開發(fā),一定要寫上版本功能注釋,方便詢問具體功能。推薦

// 這是一個新加的功能 v5.20.0 by minjing.lin

變量

1. 變量名必須使用駝峰格式
類,協(xié)議使用大駝峰:

HomePageViewController.h
<HeaderViewDelegate>

對象等局部變量使用小駝峰:

NSString *personName = @"";
NSUInteger totalCount = 0;

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

UIButton *addBtn 
UILabel *nameLbl 
NSString *addressStr

3. 系統(tǒng)常用類作實例變量聲明時加入后綴

類型 后綴
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. 常量以相關(guān)類名作為前綴

推薦:

static const NSTimeInterval ZOCSignInViewControllerFadeOutAnimationDuration = 0.4;

不推薦:

static const NSTimeInterval fadeOutTime = 0.4;

2. 建議使用類型常量,不建議使用#define預(yù)處理命令

首先比較一下這兩種聲明常量的區(qū)別:

  • 預(yù)處理命令:簡單的文本替換,不包括類型信息,并且可被任意修改。
  • 類型常量:包括類型信息,并且可以設(shè)置其使用范圍,而且不可被修改。

推薦:

static const CGFloat ZOCImageThumbnailHeight = 50.0f;

不推薦:

#define CompanyName @"Apple Inc." 
#define magicNumber 42 

3. 對外公開某個常量

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

推薦:

//.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))

枚舉

當(dāng)使用 enum 的時候,建議使用新的固定的基礎(chǔ)類型定義,因為它有更強大的類型檢查和代碼補全。

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. 取下標(biāo)的時候要判斷是否越界。

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


字面量語法

盡量使用字面量值來創(chuàng)建 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類型創(chuàng)建typedef
如果我們需要重復(fù)創(chuàng)建某種block(相同參數(shù),返回值)的變量,我們就可以通過typedef來給某一種塊定義屬于它自己的新類型。

例如:

int (^variableName)(BOOL flag, int value) =^(BOOL flag, int value)
{
     // Implementation
     return someInt;
}

這個Block有一個bool參數(shù)和一個int參數(shù),并返回int類型。我們可以給它定義類型:

typedef int(^EOCSomeBlock)(BOOL flag, int value);

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

EOCSomeBlock block = ^(BOOL flag, int value){
     // Implementation
};

定義作為參數(shù)的Block:

- (void)startWithCompletionHandler: (void(^)(NSData *data, NSError *error))completion;

這里的Block有一個NSData參數(shù),一個NSError參數(shù)并沒有返回值

typedef void(^EOCCompletionHandler)(NSData *data, NSError *error);
- (void)startWithCompletionHandler:(EOCCompletionHandler)completion;”

通過typedef定義Block簽名的好處是:如果要某種塊增加參數(shù),那么只修改定義簽名的那行代碼即可。


屬性

1.書寫規(guī)則
@property、空格、括號、線程修飾詞、內(nèi)存修飾詞、讀寫修飾詞、空格、類、對象名稱;
根據(jù)不同的場景選擇合適的修飾符。

推薦:

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

2. Block屬性應(yīng)該使用copy關(guān)鍵字

推薦:

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

@property (nonatomic, copy) <#returnType#> (^<#Block#>)(<#parType#>);

3. 形容詞性的BOOL屬性的getter應(yīng)該加上is前綴

推薦:

@property (nonatomic, assign, getter=isEditable) BOOL editable;

4. 對外盡量使用不可變對象

盡量把對外公布出來的屬性設(shè)置為只讀,在實現(xiàn)文件內(nèi)部設(shè)為讀寫。具體做法是:

  • 在頭文件中,設(shè)置對象屬性為readonly
  • 在實現(xiàn)文件中設(shè)置為readwrite。

這樣一來,在外部就只能讀取該數(shù)據(jù),而不能修改它,使得這個類的實例所持有的數(shù)據(jù)更加安全。而且,對于集合類的對象,更應(yīng)該仔細(xì)考慮是否可以將其設(shè)為可變的。

如果在公開部分只能設(shè)置其為只讀屬性,那么就在非公開部分存儲一個可變型。所以當(dāng)在外部獲取這個屬性時,獲取的只是內(nèi)部可變型的一個不可變版本,例如:

在公共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屬性設(shè)置為不可變的set。然后,提供了來增加和刪除這個set里的元素的公共接口。

在實現(xiàn)文件里:


@interface EOCPerson ()

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

@end

@implementation EOCPerson {
     NSMutableSet *_internalFriends;  //實現(xiàn)文件里的可變集合
}

- (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;
}

我們可以看到,在實現(xiàn)文件里,保存一個可變set來記錄外部的增刪操作。

這里最重要的代碼是:

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

這個是friends屬性的獲取方法:它將當(dāng)前保存的可變set復(fù)制了一不可變的set并返回。因此,外部讀取到的set都將是不可變的版本。


代理方法

1. 代理方法的第一個參數(shù)必須為委托者

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

-  (void)tableView:(UITableView *)tableView 
didSelectRowAtIndexPath:(NSIndexPath *)indexPath

2.向代理發(fā)送消息時需要判斷其是否實現(xiàn)該方法

推薦:

if ([self.delegate respondsToSelector:@selector(signUpViewControllerDidPressSignUpButton:)]) { 
 [self.delegate signUpViewControllerDidPressSignUpButton:self]; 
} 

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

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

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

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


1. 類的名稱
應(yīng)該以三個大寫字母為前綴;創(chuàng)建子類的時候,應(yīng)該把代表子類特點的部分放在前綴和父類名的中間。

推薦:

//父類
ZOCSalesListViewController

//子類
ZOCDaySalesListViewController
ZOCMonthSalesListViewController

2. 所有返回類對象和實例對象的方法都應(yīng)該使用instancetype

將instancetype關(guān)鍵字作為返回值的時候,可以讓編譯器進行類型檢查,同時適用于子類的檢查,這樣就保證了返回類型的正確性(一定為當(dāng)前的類對象或?qū)嵗龑ο螅?/p>

推薦:

- (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的屬性。這個時候,我們不應(yīng)該引入類B的頭文件,而應(yīng)該使用向前聲明(forward declaring)使用class關(guān)鍵字,并且在A的實現(xiàn)文件引用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"

優(yōu)點:

  • 不在A的頭文件中引入B的頭文件,就不會一并引入B的全部內(nèi)容,這樣就減少了編譯時間。

  • 可以避免循環(huán)引用:因為如果兩個類在自己的頭文件中都引入了對方的頭文件,那么就會導(dǎo)致其中一個類無法被正確編譯。

但是個別的時候,必須在頭文件中引入其他類的頭文件:

  • 該類繼承于某個類,則應(yīng)該引入父類的頭文件。
  • 該類遵從某個協(xié)議,則應(yīng)該引入該協(xié)議的頭文件。而且最好將協(xié)議單獨放在一個頭文件中。

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 快速開發(fā) 代碼塊


相等性的判斷

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

-  (BOOL)isEqual:(id)object 
{

    if (self == object) {  
        return YES; //判斷內(nèi)存地址
    } 

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

     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.命名規(guī)范:不能有中文、大寫、特殊符號、空白
2.命名格式(推薦):
fileType[function]project[pageName]imageName[status]{.png,@2x.png,@3x.png}
萬能公式:類別_功能_模塊_頁面_名稱_狀態(tài).png

icon_tab_bookshelf_sel@2x.png

面試題(風(fēng)格糾錯)

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 編程藝術(shù)
iOS 代碼規(guī)范
看完這個你們團隊的代碼也很規(guī)范
《Effective Objective-C 2.0》

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容