目前的项目大多都是由一个团队来完成,如果没有统一的代码规范,那么每个人的代码必定会风格迥异,在工作中肯定会有多个人同时开发同一模块的情况,即使是分工十分明晰的,等到整合代码、CodeReView、工作接力等情况时问题就会显现出来。统一的风格使得代码可读性大大提高了,人们看到任何一段代码都不用去浪费更多的时间去琢磨。规范不是对开发的制约,而确实是有助于提高开发效率的。,规范的代码在团队的合作开发中是非常有益而且必要的。
在文章开篇的时候就已经提及了:统一的风格使得代码可读性提高,在同事之间看到任何一段代码都不用去浪费更多的时间去琢磨,可以高效的完成开发工作。
有规范的对参数进行输入输出,有规范的异常处理,没规范的日志处理等等,bug不但可以有效减少,查找bug也变得轻而易举。
开发过程中的代码质量直接影响着维护的成本,可读性高的代码维护成本必然会大大降低。 而且,维护工作不仅仅是读懂原有代码,还需要在原有代码基础上作出修改,因此,统一的风格有利于长期的维护。
代码审查可以及时纠正一些错误,对开发人员的代码规范作出监督。团队的代码审查同时也是一个很好的学习机会,对成员的进步也是很有益的,同时代码审查也有助于代码规范的实施。
有很多时候去看自己曾经写得代码是不是没有头绪呢?尤其是出现bug的时候需要逐行的debug?我们应该做的就是规范开发,减少自己出现的错误,规范开发最大的受益人其实是自己。
下面开始我们的正题:代码规范
本文整合了谷歌、raywenderlich.com、58到家、NetStars(日)公司在开发过程中关于iOS端(Objective-C语言)的代码规范,适用于中小型团队(0-30人协作)。
核心原则 | 意义 |
---|---|
原则一:代码应该简洁易懂,逻辑清晰 | 软件是需要人来维护的,不要过分追求技巧,降低程序的可读性。 |
原则二:面向变化编程,而不是面向需求编程 | 不能仅仅为了当前的需求,写出扩展性强,易修改的程序才是负责任的做法,对自己负责,对公司负责。 |
原则三:先保证程序的正确性,防止过度工程 | 过度工程(over-engineering):在正确可用的代码写出之前就过度地考虑扩展,重用的问题,使得工程过度复杂。 |
引用改编自 https://www.jianshu.com/p/d7e87107073c
首先我们就从大家最关心的“代码注释”作为切入,虽然写起来很痛苦,但注释是保证代码可读性的关键。下面的规则给出了你应该什么时候、在哪进行注释。注释很重要,好的代码应该能自成文档。与其给类型及变量起一个晦涩难懂的名字,再为它写注释,不如直接起一个有意义的名字。当你写注释的时候,记得你是在给你的听众写,即下一个需要阅读你所写代码的贡献者。大方一点,下一个读代码的人可能就是你?
每个接口、类别以及协议应辅以注释,以描述它的目的及与整个项目的关系。如下:
/**
委托代理,用来处理关于app的启动和关闭过程中的xxx、xxx事件,被app的xxx控制器拥有。
*/
@interface MyAppDelegate : NSObject {
...
}
@end
/**
标识是起始地还是目的地
*/
@property (nonatomic, copy) NSString *userName;
/**
地址信息模型
*/
@property (nonatomic, copy) UserInfoModel *userInfoModel;
/**
登录倒计时文本
@param timerLabel 倒计时Label对象
@param timeText 倒计时文本数据
*/
- (void)timerLabel:(UILabel *)timerLabel timeText:(NSString *)timeText;
/**
验证搜索成功失败模块
@param searchResponse 搜索返回结果
@param error 错误信息
@return 如果返回 YES 代表搜索成功, 返回 NO 代表搜索失败
*/
typedef BOOL(^SearchResultBlock)(AMapPOISearchResponse *searchResponse, NSError *error);
/**
(参考YYText)
YYTextVerticalAlignmentTop: 顶部对齐
YYTextVerticalAlignmentCenter: 居中对齐
YYTextVerticalAlignmentBottom: 底部对齐
*/
typedef NS_ENUM(NSInteger, YYTextVerticalAlignment) {
YYTextVerticalAlignmentTop = 0, ///< Top alignment.
YYTextVerticalAlignmentCenter = 1, ///< Center alignment.
YYTextVerticalAlignmentBottom = 2, ///< Bottom alignment.
};
@interface SomeViewController () {
SomeModel * someModel; // 用来存储信息
NSString * someStr; // 用来检测的字段
UIView * someView; // 用来显示信息的视图
}
/** 地址选择确认返回上一页 */
- (void)doSomeThing:(id)someObject{
/**
判断内容和结果
*/
if (条件判断1) {
if (内部条件) {
do...
}else{
do...
}
}
/**
判断内容和结果
*/
if (条件判断2) {
do...
return;
}
/**
[回调数据][目标接受页面]
tips:当block嵌套太多的时候,对于读代码的人来说非常困惑,因为往往可能追了3,4个block进去之后,被以下这点代码传出去了,所以,在使用block的时候,花点时间注明回调的数据以及目标页面方便自己也方便别人
*/
_someBlock(xxx);
}
// 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];
}
/**
登录请求验证
@param userId 用户名
@param password 密码
@param complete 执行完毕的block
*/
- (void)loginRequestWithUserId:(NSString *)userId password:(NSString *)password complete:(void (^)(CheckLogon *result))complete{
}
保持使用驼峰命名法,建议的写法如下:
@property (nonatomic, copy) NSArray *childPaths;
反例:
@property (nonatomic, copy) NSArray *childpaths;
不带参数情况下,全部大写,单词间用 _ 分隔,建议写法如下:
#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
控件的命名需要注意突出后缀,建议写法如下:
UIView *userInfoView;
反例:
UIView *userInfo;
关键是让人通过命名就能知道其目的或者用法,一般控件的命名都是接上所生命类的后缀,示例情况如下:
UI控件名 | 自定义名 |
---|---|
UIView | someView |
UViewController | someViewController |
UIButton | someButton |
UILabel | someLabel |
UITableView | someTableView |
一般在自定义类的时候需要注意命名规范,首字母大写每个单词首字母大写(大驼峰命名法),建议写法如下:
@interface UserInfoManager : NSObject
一般在不同业务线上会有不同分支的首页、子页面,很多时候会有重复的信息,比如两个模块都有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
在开发过程中,使用#import来导入头文件是必不可少的工序,但有的功能或者业务模块需要导入的文件就会非常多,看着会很别扭,所以指定一套方案来改善这一现状是很有必要的,建议顺序如下:
// 系统库
#import
#import
// 第三方库
#import
// 自定义类
#import "MyButton.h"
#import "UserInfoModel.h"
一般在控制器中,会有控制器生命周期相关方法以及不同的业务代码,可以使用#pragma mark来进行简单的方法分区,建议写法如图:
可以稍做优化,区分为几个模块,示例如下:
#pragma mark - Life Cycle 生命周期
#pragma mark - Request 网络请求
#pragma mark - Delegate 代理实现
#pragma mark - Event Response 事件响应
#pragma mark - Methods 方法
根据每个人的喜好不一致可以按照自己的想法来写,这样在别人梳理文件的时候思路会比较清晰
一般会出现以下几种情况:
// 1
if (YES) {
Do();
}
// 2
if (YES) DO();
// 3
if (YES)
Do();
// 4
if (YES)
{
Do();
}
以上第一种方式是苹果主推的,经过调研,方法2、3、4虽然在写法上会有所简化,但在风格上看来还是有所欠缺,所以更多的建议使用第一种。
一般在判断的时候可以使用最简单的方法来做最简单的事情,使用return可以减少复杂度,提高代码可读性,建议写法如下:
- (void)someMethod {
if(![someThing]) {
return;
}
// continue do something
}
反例:
- (void)someMethod {
if([someThing]) {
// continue do something
}else{
return;
}
}
常用写法:
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