代碼不規範,同事兩行淚

任何一個傻瓜都能寫出計算機能夠理解的代碼。惟有寫出人類容易理解的代碼,纔是優秀的程序員。 —— 佚名git

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

圖片發自簡書App

大括號

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

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];
複製代碼

不推薦:bash

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

if語句

1. 儘可能列出全部的狀況,且給出明確的結果。session

推薦:app

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

複製代碼

2. 黃金大道
在使用條件語句編程時,代碼的左邊距應該是一條「黃金」或者「快樂」的大道,也就是說善於使用return來提早返回不符合的狀況。ide

推薦:

- (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》

相關文章
相關標籤/搜索