爲什麼要做代碼規範?
目前的項目大多都是由一個團隊來完成,如果沒有統一的代碼規範,那麼每個人的代碼必定會風格迥異,在工作中肯定會有多個人同時開發同一模塊的情況,即使是分工十分明晰的,等到整合代碼、CodeReView、工作接力等情況時問題就會顯現出來。統一的風格使得代碼可讀性大大提高了,人們看到任何一段代碼都不用去浪費更多的時間去琢磨。規範不是對開發的制約,而確實是有助於提高開發效率的。,規範的代碼在團隊的合作開發中是非常有益而且必要的。
代碼規範能帶來什麼好處?
1.規範的代碼可以促進團隊合作
在文章開篇的時候就已經提及了:統一的風格使得代碼可讀性提高,在同事之間看到任何一段代碼都不用去浪費更多的時間去琢磨,可以高效的完成開發工作。
2.規範的代碼可以減少bug處理
有規範的對參數進行輸入輸出,有規範的異常處理,沒規範的日誌處理等等,bug不但可以有效減少,查找bug也變得輕而易舉。
3.規範的代碼可以降低維護成本
開發過程中的代碼質量直接影響着維護的成本,可讀性高的代碼維護成本必然會大大降低。 而且,維護工作不僅僅是讀懂原有代碼,還需要在原有代碼基礎上作出修改,因此,統一的風格有利於長期的維護。
4.規範的代碼有助於代碼審查
代碼審查可以及時糾正一些錯誤,對開發人員的代碼規範作出監督。團隊的代碼審查同時也是一個很好的學習機會,對成員的進步也是很有益的,同時代碼審查也有助於代碼規範的實施。
5.養成代碼規範的習慣,有助於自身的成長
有很多時候去看自己曾經寫得代碼是不是沒有頭緒呢?尤其是出現bug的時候需要逐行的debug?我們應該做的就是規範開發,減少自己出現的錯誤,規範開發最大的受益人其實是自己。
下面開始我們的正題:代碼規範
《iOS技術部門代碼規範1.0範本》
本文整合了谷歌、raywenderlich.com、58到家、NetStars(日)公司在開發過程中關於iOS端(Objective-C語言)的代碼規範,適用於中小型團隊(0-30人協作)。
核心原則 | 意義 |
---|---|
原則一:代碼應該簡潔易懂,邏輯清晰 | 軟件是需要人來維護的,不要過分追求技巧,降低程序的可讀性。 |
原則二:面向變化編程,而不是面向需求編程 | 不能僅僅爲了當前的需求,寫出擴展性強,易修改的程序纔是負責任的做法,對自己負責,對公司負責。 |
原則三:先保證程序的正確性,防止過度工程 | 過度工程(over-engineering):在正確可用的代碼寫出之前就過度地考慮擴展,重用的問題,使得工程過度複雜。 |
引用改編自 https://www.jianshu.com/p/d7e87107073c
目錄:
- 註釋的寫法
- 命名與規範
- 分類的方法規範
- 代碼組織結構規範
- 條件語句規範
- 補充規範
註釋的寫法
首先我們就從大家最關心的“代碼註釋”作爲切入,雖然寫起來很痛苦,但註釋是保證代碼可讀性的關鍵。下面的規則給出了你應該什麼時候、在哪進行註釋。註釋很重要,好的代碼應該能自成文檔。與其給類型及變量起一個晦澀難懂的名字,再爲它寫註釋,不如直接起一個有意義的名字。當你寫註釋的時候,記得你是在給你的聽衆寫,即下一個需要閱讀你所寫代碼的貢獻者。大方一點,下一個讀代碼的人可能就是你😁
1.Class 類註釋
每個接口、類別以及協議應輔以註釋,以描述它的目的及與整個項目的關係。如下:
/**
委託代理,用來處理關於app的啓動和關閉過程中的xxx、xxx事件,被app的xxx控制器擁有。
*/
@interface MyAppDelegate : NSObject {
...
}
@end
2.Property 屬性註釋
/**
標識是起始地還是目的地
*/
@property (nonatomic, copy) NSString *userName;
/**
地址信息模型
*/
@property (nonatomic, copy) UserInfoModel *userInfoModel;
3.Method 方法聲明註釋
/**
登錄倒計時文本
@param timerLabel 倒計時Label對象
@param timeText 倒計時文本數據
*/
- (void)timerLabel:(UILabel *)timerLabel timeText:(NSString *)timeText;
4.Block 代碼塊註釋
/**
驗證搜索成功失敗模塊
@param searchResponse 搜索返回結果
@param error 錯誤信息
@return 如果返回 YES 代表搜索成功, 返回 NO 代表搜索失敗
*/
typedef BOOL(^SearchResultBlock)(AMapPOISearchResponse *searchResponse, NSError *error);
5.NSEnum 枚舉註釋
/**
(參考YYText)
YYTextVerticalAlignmentTop: 頂部對齊
YYTextVerticalAlignmentCenter: 居中對齊
YYTextVerticalAlignmentBottom: 底部對齊
*/
typedef NS_ENUM(NSInteger, YYTextVerticalAlignment) {
YYTextVerticalAlignmentTop = 0, ///< Top alignment.
YYTextVerticalAlignmentCenter = 1, ///< Center alignment.
YYTextVerticalAlignmentBottom = 2, ///< Bottom alignment.
};
6.局部變量註釋
@interface SomeViewController () {
SomeModel * someModel; // 用來存儲信息
NSString * someStr; // 用來檢測的字段
UIView * someView; // 用來顯示信息的視圖
}
7.Method 方法實現註釋①
/** 地址選擇確認返回上一頁 */
- (void)doSomeThing:(id)someObject{
/**
判斷內容和結果
*/
if (條件判斷1) {
if (內部條件) {
do...
}else{
do...
}
}
/**
判斷內容和結果
*/
if (條件判斷2) {
do...
return;
}
/**
[回調數據][目標接受頁面]
tips:當block嵌套太多的時候,對於讀代碼的人來說非常困惑,因爲往往可能追了3,4個block進去之後,被以下這點代碼傳出去了,所以,在使用block的時候,花點時間註明回調的數據以及目標頁面方便自己也方便別人
*/
_someBlock(xxx);
}
8.Method 方法實現註釋②
// tips:對於一個不常用的第三方、底層類庫等代碼,可以簡單的把每一個過程都寫一下,這樣的話易讀性很強
- (void)createCAEmitterLayer{
// 1.創建CAEmitterLayer
CAEmitterLayer *emitterLayer = [CAEmitterLayer layer];
// 2.設置CAEmitterLayer的屬性(最主要的是前四個)
// 發射源的形狀 是枚舉類型
emitterLayer.emitterShape = kCAEmitterLayerLine;
// 發射模式 枚舉類型
emitterLayer.emitterMode = kCAEmitterLayerSurface;
// 發射源的size 決定了發射源的大小,如果做了傾斜或者便宜屏幕寬度是不夠的,那時候就需要自定義
emitterLayer.emitterSize = self.view.frame.size;
// 發射源的位置
emitterLayer.emitterPosition = CGPointMake(self.view.bounds.size.width * 0.5, -10);
// 渲染模式 枚舉類型 (⭐️這個渲染模式表達效果會很不好,不常用,kCAEmitterLayerAdditive,可以讓重疊的部分高亮)
emitterLayer.renderMode = kCAEmitterLayerAdditive;
// 3.添加到目標視圖的layer上
[self.view.layer addSublayer:emitterLayer];
}
9.Method 方法實現註釋③
/**
登錄請求驗證
@param userId 用戶名
@param password 密碼
@param complete 執行完畢的block
*/
- (void)loginRequestWithUserId:(NSString *)userId password:(NSString *)password complete:(void (^)(CheckLogon *result))complete{
}
命名的規範
1.通用變量命名
保持使用駝峯命名法,建議的寫法如下:
@property (nonatomic, copy) NSArray *childPaths;
反例:
@property (nonatomic, copy) NSArray *childpaths;
2.宏命名
不帶參數情況下,全部大寫,單詞間用 _ 分隔,建議寫法如下:
#define THIS_IS_A_TICKET @"THIS_IS_A_TICKET"
不帶參數情況下,避免和類自己的參數衝突可以以【小寫】字母k或者自定義字母作爲開頭,後面遵循駝峯命名,建議寫法如下:
#define kScreenWidth [UIScreen mainScreen].bounds.size.width
帶參數的情況下,遵循駝峯命名,建議寫法如下:
#define getImageUrl(url) [NSURL URLWithString:[NSString stringWithFormat:@"%@%@",kBaseUrl,url]]
反例:
#define homepageadbannerheight 85
3.控件命名
控件的命名需要注意突出後綴,建議寫法如下:
UIView *userInfoView;
反例:
UIView *userInfo;
關鍵是讓人通過命名就能知道其目的或者用法,一般控件的命名都是接上所生命類的後綴,示例情況如下:
UI控件名 | 自定義名 |
---|---|
UIView | someView |
UViewController | someViewController |
UIButton | someButton |
UILabel | someLabel |
UITableView | someTableView |
4.類的命名①
一般在自定義類的時候需要注意命名規範,首字母大寫每個單詞首字母大寫(大駝峯命名法),建議寫法如下:
@interface UserInfoManager : NSObject
5.類的命名②
一般在不同業務線上會有不同分支的首頁、子頁面,很多時候會有重複的信息,比如兩個模塊都有HomePage,這時候就需要在前面加上前綴,建議寫法如下:
/**
活動頁面
*/
@interface ActivityHomeViewController : UIViewController
反例:
@interface HomeViewController : UIViewController
分類的方法規範
我們在開發過程中爲了更好的使用系統方法,一般會使用分類來添加方法,來滿足開發需要,分類的作用就是在不修改原有類的基礎上,爲一個類擴展方法,最主要的是可以給系統類擴展我們自己定義的方法。爲了避免和系統方法或者別的同事所做的分類方法的衝突,一般都會在方法面前添加小寫字母來做區別,建議寫法如下:
@interface UIView (TouchBlock)
- (void)tb_touchView:(void (^)(void))block;
@end
反例:
@interface UIView (TouchBlock)
- (void)touchView:(void (^)(void))block;
@end
代碼組織結構規範
1. #import 文件的順序
在開發過程中,使用#import來導入頭文件是必不可少的工序,但有的功能或者業務模塊需要導入的文件就會非常多,看着會很彆扭,所以指定一套方案來改善這一現狀是很有必要的,建議順序如下:
// 系統庫
#import <UIKit/UIKit.h>
#import <Foundation/Foundation.h>
// 第三方庫
#import <Masonry.h>
// 自定義類
#import "MyButton.h"
#import "UserInfoModel.h"
2.控制器中方法分區
一般在控制器中,會有控制器生命週期相關方法以及不同的業務代碼,可以使用#pragma mark來進行簡單的方法分區,建議寫法如圖:
可以稍做優化,區分爲幾個模塊,示例如下:
#pragma mark - Life Cycle 生命週期
#pragma mark - Request 網絡請求
#pragma mark - Delegate 代理實現
#pragma mark - Event Response 事件響應
#pragma mark - Methods 方法
根據每個人的喜好不一致可以按照自己的想法來寫,這樣在別人梳理文件的時候思路會比較清晰
條件語句規範
1.{}括號的規範
一般會出現以下幾種情況:
// 1
if (YES) {
Do();
}
// 2
if (YES) DO();
// 3
if (YES)
Do();
// 4
if (YES)
{
Do();
}
以上第一種方式是蘋果主推的,經過調研,方法2、3、4雖然在寫法上會有所簡化,但在風格上看來還是有所欠缺,所以更多的建議使用第一種。
2.減少邏輯嵌套
一般在判斷的時候可以使用最簡單的方法來做最簡單的事情,使用return可以減少複雜度,提高代碼可讀性,建議寫法如下:
- (void)someMethod {
if(![someThing]) {
return;
}
// continue do something
}
反例:
- (void)someMethod {
if([someThing]) {
// continue do something
}else{
return;
}
}
3.複雜表達式
常用寫法:
if ([self getUserInfoModel] == nil && ![self userLogin]){
[self showLoginVC];
}
一般會有很長的表達式或者多個待判斷的值,在這種情況下,建議以下寫法:
BOOL userInfoModelIsNil = [self getUserInfoModel] == nil;
BOOL userIsLogin = ![self userLogin];
BOOL showLoginVC = userModelIsNil && userIsLogin;
if (showLoginVC) {
[self showLoginVC];
}
第一種寫法大家都是很常用的,表達很簡潔,但是從閱讀代碼和調試代碼的角度看,推薦第二種,因爲每個條件和句子的意義很明顯的就能看出來。
補充規範
1.如果一行有非常多的參數,更好的方式是將每個參數單獨拆成一行。如果使用多行,將每個參數前的冒號對齊,建議寫法如下:
- (void)doSomethingWithUserName:(NSString *)name
address:(NSString *)address
doorNumber:(float)number {
...
}
當第一個關鍵字比其它的短時,保證下一行至少有 4 個空格的縮進。這樣可以使關鍵字垂直對齊,而不是使用冒號對齊:
- (void)getA:(AClass *)A
longKeywordB:(BClass *)B
evenLongerKeywordC:(CClass *)C {
...
}
2.常量是容易重複被使用和無需通過查找和代替就能快速修改值。常量應該使用static來聲明而不是使用#define,除非顯式地使用宏。建議寫法如下:
static NSString * const XXXAboutViewControllerCompanyName = @"XXXCompanyName";
static CGFloat const XXXImageThumbnailHeight = 50.0;
反例:
#define CompanyName @"XXXCompanyName"
#define ImagethumbnailHeight 50
3.布爾值的判斷書寫,Objective-C使用YES和NO。因爲true和false應該只在CoreFoundation,C或C++代碼使用。既然nil解析成NO,所以沒有必要在條件語句比較。不要拿某樣東西直接與YES比較,因爲YES被定義爲1和一個BOOL能被設置爲8位。這是爲了在不同文件保持一致性和在視覺上更加簡潔而考慮。建議書寫方式如下:
if (someObject) {
}
if (![anotherObject boolValue]) {
}
反例:
if (someObject == nil) {
}
if ([anotherObject boolValue] == NO) {
}
if (isAwesome == YES) {
}
if (isAwesome == true) {
}
4.三元操作符的表達,當需要提高代碼的清晰性和簡潔性時,三元操作符?:纔會使用。單個條件求值常常需要它。多個條件求值時,如果使用if語句或重構成實例變量時,代碼會更加易讀。一般來說,最好使用三元操作符是在根據條件來賦值的情況下。Non-boolean的變量與某東西比較,加上括號()會提高可讀性。如果被比較的變量是boolean類型,那麼就不需要括號。建議寫法如下:
NSInteger value = 5;
result = (value != 0) ? x : y;
BOOL isHorizontal = YES;
result = isHorizontal ? x : y;
反例:
result = a > b ? x = c > d ? c : d : y;
5.在使用 Init方法 和 類構造方法 時需要注意:
Init方法應該遵循Apple生成代碼模板的命名規則。返回類型應該使用instancetype而不是id
- (instancetype)init {
self = [super init];
if (self) {
// ...
}
return self;
}
當類構造方法被使用時,它應該返回類型是instancetype 而不是id。這樣確保編譯器正確地推斷結果類型。
@interface Airplane
+ (instancetype)airplaneWithType:(RWTAirplaneType)type;
@end