編碼規(guī)范

編碼規(guī)范簡單來說就為了保證寫出來的代碼具備三個原則:可復(fù)用、易維護、可擴展. 這其實也是面向?qū)ο蟮幕驹瓌t. 可復(fù)用, 簡單來說就是不要寫重復(fù)的代碼, 有重復(fù)的部分要盡量封裝起來重用.易維護, 就是不要把代碼復(fù)雜化, 不要去寫巨復(fù)雜邏輯的代碼, 而是把復(fù)雜的邏輯代碼拆分開一個個小的模塊, 這也是Do one thing的概念, 每個模塊(或者函數(shù))職責(zé)要單一, 這樣的代碼會易于維護, 也不容易出錯. 可擴展則是要求寫代碼時要考慮后面的擴展需求, 這個屬于架構(gòu)層面的東東, 利用對應(yīng)的設(shè)計模式來保證,本內(nèi)容主要討論第1點。

本章通過命名規(guī)范和編碼規(guī)范來詳細(xì)討論
  • 命名規(guī)范
  • 編碼規(guī)范

1. 命名規(guī)范

主要涉及常量和變量命名、枚舉命名、類及其方法命名,以及分類及其方法命名。

1.1 使用 #define 預(yù)處理定義常量。定義一個 ANIMATION_DURATION 常量來表示 UI 動畫的一個常量時間,這樣代碼中所有使用 ANIMATION_DURATION 的地方在編譯階段都會被替換成 0.3。無類型信息,不便于調(diào)試。不推薦

#define ANIMATION_DURATION    0.3

1.2 使用類型常量,對于局部常量通常以字符 k 開頭,且需要以 static const 修飾。推薦

static const NSTimeInterval kAnimationDuration = 0.3;

1.3 使用類型常量,外部可見,則通常以定義該常量所在類的類名開頭。

// 數(shù)值常量
EOCViewClass.h
extern const NSTimeInterval EOCViewClassAnimationDuration;

EOCViewClass.m
const NSTimeInterval EOCViewClassAnimationDuration = 0.3;


// 字符串常量    
EOCViewClass.h
extern NSString *const EOCViewClassStringConstant;

EOCViewClass.m
NSString *const EOCViewClassStringConstant = @"EOCStringConstant";

1.4 用枚舉表示狀態(tài)、選項、狀態(tài)碼

// 通用枚舉值
typedef NS_ENUM(NSInteger, RoomType) {
    RoomType_Normal,     // 虛擬診室
    RoomType_Exam,       // 考試診室
    RoomType_Talk,       // 討論區(qū)診室
    RoomType_XGuang,     // 能力 X 光
};

1.5 需要以按位或操作來組合枚舉的都應(yīng)使用 NS_OPTIONS 宏來定義。

// 位移相關(guān)操作的枚舉值
typedef NS_OPTIONS(NSUInteger, UIViewAutoresizing) {
    UIViewAutoresizingNone                 = 0,
    UIViewAutoresizingFlexibleLeftMargin   = 1 << 0,
    UIViewAutoresizingFlexibleWidth        = 1 << 1,
    UIViewAutoresizingFlexibleRightMargin  = 1 << 2,
    UIViewAutoresizingFlexibleTopMargin    = 1 << 3,
    UIViewAutoresizingFlexibleHeight       = 1 << 4,
    UIViewAutoresizingFlexibleBottomMargin = 1 << 5
};

// 然后就可以通過組合的方式來使用
self.tableView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;

1.6 變量和對象的命名,給一個對象命名時建議采用 ’修飾+類型‘ 的方式,對于 BOOL 類型,應(yīng)加上 is/has 前綴。私有實例變量前加一個下劃線,如:_myPrivateVarible

@property (nonatomic, strong) UIButton *closeBtn;    // 表示關(guān)閉的button,是 UIButton 類型
@property (nonatomic, strong) UILabel *titleLabel;   // 表示標(biāo)題的label,是 UILabel 類型
@property (nonatomic, assign) BOOL isShowLeft;       // 表示是否顯示Left,是 BOOL 類型

- (BOOL)isEqualToString:(NSString *)aString;
- (BOOL)hasPrefix:(NSString *)aString;

// 這是一個成員變量,添加一個下劃線
NSString *_userId;

1.7 方法的命名,應(yīng)該全部使用有意義的單詞組成,且以小寫字母開頭,多單詞組合時,后面的單詞首字母大寫,參數(shù)過多時,推薦每個參數(shù)各占一行。如果是私有方法建議加上 'private'/'p_' 作為前綴,很容易的將其同公共方法區(qū)分開

- (id)initWithAccount:(NSString *)account
             password:(NSString *)psw;
             
- (id)privateMethod;
- (id)p_method;

1.8 類的命名,首字母大寫,之后每個單詞首字母都大寫,使用能夠反映類功能的名詞短語,使用前綴來避免命名空間沖突,如:ABC+類名(ABCUserInfo)

// 這是一個模型
@interface UserModel : JSONModel
// 這是一個視圖
@interface BaseNavView : BaseView
// 這是一個視圖控制器
@interface LoginVC : BaseVC
// 這是一個接口
@interface Login_Post : BaseRestApi

// 添加前綴
// 這是一個模型
@interface TLQUserModel : JSONModel
// 這是一個視圖
@interface TLQBaseNavView : BaseView
// 這是一個視圖控制器
@interface TLQLoginVC : BaseVC
// 這是一個接口,POST 方式
@interface TLQLogin_Post : BaseRestApi
// 這是一個接口,GET 方式
@interface TLQCreatePatient_Get : BaseRestApi

1.9 分類的命名,與類命名相同,此外需添加要擴展的類名和 “+”,如:NSString+JudgeString

// 與字符串操作相關(guān)的分類
// 這是文件名
NSString+JudgeString.h
// 這是類名
@interface NSString (JudgeString)

1.10 協(xié)議的命名,與類命名相同,需添加 "Delegate" 后綴,如:ReplyViewDelegate

// 協(xié)議的命名
@protocol ShaftAdviceViewDelegate;

1.11 圖片命名,使用英文,首字母大寫,之后每個單詞首字母都大寫并添加模塊作為前綴,避免沖突圖片應(yīng)該與類文件一樣,按模塊分組放置

// 這是沙河路徑
<Application_Home>/Documents/
<Application_Home>/Library/Preferences/
<Application_Home>/Library/Caches/
<Application_Home>/tmp/

// 這是用戶頭像路徑
<Application_Home>/Library/Preferences/Icons/(userId).png

// 這是 app 圖片資源, xxx 表示圖片唯一名稱
<Application_Home>/Library/Caches/Icons/(xxx).png

// 這是用戶信息路勁
<Application_Home>/Library/Preferences/Infos/(userId).plist

// 這是數(shù)據(jù)庫路勁
<Application_Home>/Documents/app.sqlite

2. 編碼規(guī)范

  1. 讓別人能讀懂的代碼
  2. 可擴展的代碼
  3. 可測試的代碼

2.1 判斷 nil 或者 YES/NO

// Preferred:
if (someObject) { ... } 
if (!someObject) { ... }

// Not preferred:
if (someObject == YES) { ...} 
if (someObject != nil) { ...}

2.2 條件賦值.

// Preferred:
result = object ? : [self createObject];

// Not preferred:
result = object ? object : [self createObject];

2.3 初始化方法,推薦使用字面量方式.

// Preferred:
NSArray *names = @[@"Brian", @"Matt", @"Chris", @"Alex", @"Steve"];
NSDictionary *productManagers = @{@"iPhone" : @"Kate", @"iPad" : @"Kamal"};
NSNumber *shouldUseLiterals = @YES;
NSNumber *buildingZIPCode = @10018;

// Not Preferred:
NSArray *names = [[NSArray alloc] initWithObjects:@"Brian",@"Matt",@"Chris",@"Alex",@"Steve", nil];
NSDictionary *productManagers = [[NSDictionary alloc] initWithObjectsAndKeys:@"iPhone", @"Kate", @"iPad", @"Kamal", nil];
NSNumber *shouldUseLiterals = [NSNumber numberWithBool:YES];
NSNumber *shouldUseLiterals = [NSNumber numberWithInteger:10018];

2.4 BOOL 賦值,使用三目運算符,更簡潔.

// Preferred:
BOOL isAdult = age > 18;

// Not preferred:
BOOL isAdult;
if (age > 18)
{
    isAdult = YES;
}
else
{
    isAdult = NO;
}

// Preferred:
result= num1 > num2 ? num1:num2;

// Not preferred:
if (num1 > num2) 
{
    result = num1;
}
else
{
    result = num2;
}

2.5 拒絕死值,使用變量,即間接的方式,每次修改的時候容易被遺忘,地方多了修改就麻煩了.

// Preferred:
if (car == Car.Nissan)
// or
const int adultAge = 18; if (age > adultAge) { ... }

// Not preferred:
if (carName == "Nissan")
// or
if (age > 18) { ... }

2.6 嵌套判斷,一旦發(fā)現(xiàn)某個條件不符合,立即返回,條理更清晰.

// Preferred:
if (!user.UserName) return NO;
if (!user.Password) return NO;
if (!user.Email) return NO;

return YES;

// Not preferred:
BOOL isValid = NO;
if (user.UserName)
{
    if (user.Password)
    {
        if (user.Email) isValid = YES;
    }
}
return isValid;

2.7 參數(shù)過多,可以聚合成一個 model 類,代碼更簡潔.

// Preferred:
- (void)registerUser(User *user)
{
     // to do...
}

// Not preferred:
- (void)registerUserName:(NSString *)userName
                password:(NSString *)password 
                   email:(NSString *)email
{
     // to do...
}

2.8 Block 的循環(huán)引用問題,解決方法很簡單, 就是在block體內(nèi)define一個strong的self, 然后執(zhí)行的時候判斷下self是否還在, 如果在就繼續(xù)執(zhí)行下面的操作, 否則return或拋出異常.

// weakSelf 可能被釋放,導(dǎo)致奇怪的錯誤
__weak typeof(self) weakSelf = self;
dispatch_block_t block =  ^{
    [weakSelf doSomething]; // weakSelf != nil
    // preemption, weakSelf turned nil
    [weakSelf doSomethingElse]; // weakSelf == nil
};


// strongSelf 不會被釋放 - 推薦使用
__weak typeof(self) weakSelf = self;
myObj.myBlock =  ^{
    __strong typeof(self) strongSelf = weakSelf;
    if (strongSelf) {
      [strongSelf doSomething]; // strongSelf != nil
      // preemption, strongSelf still not nil
      [strongSelf doSomethingElse]; // strongSelf != nil
    }
    else {
        // Probably nothing...
        return;
    }
};

2.9 建議:#import 頭文件按模塊來分,更直觀.

// Cell
#import "LogInCell.h"
#import "LogOutCell.h"
// Setting
#import "AppPreference.h"
#import "AppDelegate.h"
// VC
#import "LoginVC.h"
#import "UserInfoVC.h"
#import "SettingVC.h"
// Notify
#import "LoginNotify.h"
// View
#import "DSTabbar.h"
#import "InvitationView.h"
// Api
#import "LoginStatus_Get.h"

2.10 建議:@class與#import,如需要繼承類或執(zhí)行協(xié)議,可以在.h中進行#import類或協(xié)議;其他情況下,在.h中聲明用@classs聲明此類即可。這樣可以減少因頭文件依賴引起重復(fù)編譯,提高編譯速度,也解決了兩個類的相互引用問題.

// 在 .h 文件中通過 class 關(guān)鍵字來引用
@class PatientInfo;
@interface CreatePatient_Get : BaseRestApi
@property (nonatomic, strong) PatientInfo *patientInfo;
@end

// 而在 .m 文件中通過 import 關(guān)鍵字來引用
#import "PatientInfo.h"
@implementation CreatePatient_Get
@end

2.11 注釋,可以采用 “ /**/ ” 和 // 兩種注釋符號,涉及多行注釋時,第一種。對于一行代碼的注釋可放在前一行或本行上,不允許放在下一行。顯而易見的代碼不需要加注釋.

// 這里是多行注釋
/**
 *  登錄接口
 *
 *  @param account 賬號
 *  @param psw     密碼
 */
- (id)initWithAccount:(NSString *)account
             password:(NSString *)psw;


// 這里是單行注釋,寫在上一行
// 病程ID
@property (nonatomic, strong) NSString *status_id;

// 這里是單行注釋,寫在同一行
@property (nonatomic, strong) NSString *status_id; // 病程ID

2.12 注冊通知時,在視圖控制器銷毀的時候,需要刪除通知.

// 刪除通知
- (void)dealloc {
    [[NSNotificationCenter defaultCenter] removeObserver:self];
    [[LoginNotify sharedInstance] removeLoginObserver:self];
}

2.13 啟動了一個定時器,在控制器銷毀的時候,需要關(guān)閉定時器.

// 關(guān)閉定時器
- (void)viewWillDisappear:(BOOL)animated {
    [super viewWillDisappear:animated];
    if (_timer) {
        dispatch_source_cancel(_timer);
        _timer = nil;
      }
}

2.14 建議:在添加通知的時候,可以把每個通知都包裝成獨立的方法,就可以復(fù)用,而不需要重復(fù)的寫相同的通知。以功能模塊來區(qū)分通知的類型。

// Preferred:

// 與登錄相關(guān)的通知
@interface LoginNotify : NSObject

+ (instancetype)sharedInstance;

#pragma mark - login
- (void)addLoginObserver:(id)target selector:(SEL)selector;
- (void)removeLoginObserver:(id)target;
- (void)postLoginNotify;

#pragma mark - louout
- (void)addLogoutObserver:(id)target selector:(SEL)selector;
- (void)removeLogoutObserver:(id)target;
- (void)postLogoutNotify;

#pragma mark - automatic login
- (void)addAutomaticLoginObserver:(id)target selector:(SEL)selector;
- (void)removeAutomaticLoginObserver:(id)target;
- (void)postAutomaticLoginNotify;

@end

// Not preferred:

[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillShow:) name:UIKeyboardWillShowNotification object:nil];

2.15 建議:對于某些功能,需要依賴其他的功能才能完成整個過程,可以把這些相互依賴的功能包裝成一個獨立的方法,便于復(fù)用。

// 這是登錄,包括郵箱登錄接口、手機登錄接口、用戶信息接口、消息提示接口

// Preferred: 
// 該 LogingHelper 類方法包裝了登錄相關(guān)的邏輯和數(shù)據(jù)的交互
@interface LoginHelper : NSObject

+ (void)loginWithUser:(NSString *)username
             Password:(NSString *)password
           Completion:(void (^)(BOOL success, NSString *errorMsg, Login_Post    *loginPost,UserInfo_Get *userinfoGet))block;

@end

// Not preferred:

Login_Post *loginApi = nil;
if ([username isValidateMobile])
{
    // 手機登錄
    loginApi = [[Login_Post alloc] initWithAccount:username password:password];
}
else
{
    // 郵箱登錄
    loginApi = [[Email_Post alloc] initWithAccount:username password:password];
}

if (llginApi.code != 0)
{
    // 登錄失敗,返回
    return;
}

UserInfo_Get *userInfoApi = [[UserInfo_Get alloc] initWithUserId:loginApi.user_id];
if (userInfoApi.code != 0)
{
    // 獲取用戶信息失敗,返回
    return;
}

// 其他相互依賴的功能....

2.15 建議:在使用第三發(fā)庫的時候,比如 AFNetworking 進行網(wǎng)絡(luò)請求??梢猿橄蟪鲆粋€基類,把網(wǎng)絡(luò)相關(guān)的請求重新封裝,通過繼承的方式,來復(fù)用這些功能。好處就是:需要更新 AFNetworking 庫,或者遷移到其他的網(wǎng)絡(luò)庫,只需要修改基類的方法。各種 Loading、下拉刷新......等

2.15 建議:所有需要重復(fù)使用的代碼,都可以包裝成一個方法

最后編輯于
?著作權(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)容