这篇规范作为我们iOS团队的代码规范,并且还会根据项目的实践和研究的深入做不定时更新。
这篇规范一共分为三个部分:
- 上线规范:介绍上线打包前该检查的相关项。
- 日常开发规范:介绍日常发过程中应该遵守的规范。
- 通用规范:不限于iOS的通用性代码规范。
- iOS规范:适用于iOS代码规范。
上线规范
1.检查Build Configuation
是否是Release
模式
2.检查请求地址会否为正式地址
3.检查Bundle Identifier
是否为正式
4.检查版本号
是否为最新版本号
5.检查相关证书
描述文件等
是否为正式
日常开发规范
1.禁止使用xib
2.无用文件定期清理
3.方法名
属性名
加备注
4.头文件
引用分功能模块划分
5.尽量使用MVVM
模式,避免类的内容过于臃肿
通用规范
关于大括号
- 控制语句(if,for,while,switch)中,大括号开始与行尾
- 函数中,大括号要开始于行首
推荐写法
//控制语句
while(someCondition){
}
//函数
void function(param1,param2)
{
}
运算符
- 运算符与变量之间的间隔
1.1 一元运算符与变量之间没有空格:
!bValue
~iValue
++iCount
*strSource
&fSum
1.2 二元运算符与变量之间必须有空格
fWidth = 5 + 5;
fLength = fWidth * 2;
fHeight = fWidth + fLength;
for(int i = 0; i < 10; i++)
- 多个不同的运算符同时存在时应该使用括号来明确优先级
在多个不同的运算符同时存在的时候应该合理使用括号,不要盲目依赖操作符优先级。
因为有的时候不能保证阅读你代码的人就一定能了解你写的算式里面所有操作符的优先级。
来看一下这个算式:2 << 2 + 1 * 3 - 4
这里的<<是移位操作直观上却很容易认为它的优先级很高,所以就把这个算式误认为:(2 << 2) + 1 * 3 - 4
但事实上,它的优先级是比加减法还要低的,所以该算式应该等同于:2 << 2 + 1 * 3 - 4。
所以在以后写这种复杂一点的算式的时候,尽量多加一点括号,避免让其他人误解(甚至是自己)。
变量
- 一个变量有且只有一个功能,尽量不要把一个变量用作多种用途
- 变量在使用前应初始化,防止未初始化的变量被引用
- 局部变量应该尽量接近使用它的地方
推荐写法
func someFunction()
{
let index = ...;
//Do something With index
...
...
let count = ...;
//Do something With count
}
if语句
- 必须列出所有分支(穷举所有的情况),而且每个分支都必须给出明确的结果。
推荐写法
var hintStr;
if (count < 3) {
hintStr = "Good";
} else {
hintStr = "";
}
- 不要使用过多的分支,要善于使用return来提前返回错误的情况
推荐写法
- (void)someMethod
{
if (!goodCondition) {
return;
}
//Do something
}
比较典型的例子
-(id)initWithDictionary:(NSDictionary*)dict error:(NSError)err
{
//方法1. 参数为nil
if (!dict) {
if (err) *err = [JSONModelError errorInputIsNil];
return nil;
}
//方法2. 参数不是nil,但也不是字典
if (![dict isKindOfClass:[NSDictionary class]]) {
if (err) *err = [JSONModelError errorInvalidDataWithMessage:@"Attempt to initialize JSONModel object using initWithDictionary:error: but the dictionary parameter was not an 'NSDictionary'."];
return nil;
}
//方法3. 初始化
self = [self init];
if (!self) {
//初始化失败
if (err) *err = [JSONModelError errorModelIsInvalid];
return nil;
}
//方法4. 检查用户定义的模型里的属性集合是否大于传入的字典里的key集合(如果大于,则返回NO)
if (![self __doesDictionary:dict matchModelWithKeyMapper:self.__keyMapper error:err]) {
return nil;
}
//方法5. 核心方法:字典的key与模型的属性的映射
if (![self __importDictionary:dict withKeyMapper:self.__keyMapper validation:YES error:err]) {
return nil;
}
//方法6. 可以重写[self validate:err]方法并返回NO,让用户自定义错误并阻拦model的返回
if (![self validate:err]) {
return nil;
}
//方法7. 终于通过了!成功返回model
return self;
}
以看到,在这里,首先判断出各种错误的情况然后提前返回,把最正确的情况放到最后返回。
- 条件表达式如果很长,则需要将他们提取出来赋给一个BOOL值
推荐写法
let nameContainsSwift = sessionName.hasPrefix("Swift")
let isCurrentYear = sessionDateCompontents.year == 2014
let isSwiftSession = nameContainsSwift && isCurrentYear
if (isSwiftSession) {
// Do something
}
- 条件语句的判断应该是变量在左,常量在右
推荐写法
if ( count == 6) {
}
或者
if ( object == nil) {
}
或者
if ( !object ) {
}
- 每个分支的实现代码都必须被大括号包围
推荐写法
if (!error) {
return success;
}
不推荐写法
if (!error)
return success;
或者
if (!error) return success;
- 条件过多,过长的时候应该换行
推荐写法
if (condition1() &&
condition2() &&
condition3() &&
condition4()) {
// Do something
}
不推荐写法
if (condition1() && condition2() && condition3() && condition4()) {
// Do something
}
for语句
- 不可在for循环内修改循环变量,防止for循环失去控制。
for (int index = 0; index < 10; index++){
...
logicToChange(index)
}
Switch语句
- 每个分支都必须用大括号括起来
switch (integer) {
case 1: {
// ...
}
break;
case 2: {
// ...
break;
}
case 3: {
// ...
break;
}
default:{
// ...
break;
}
}
- 使用枚举类型时,不能有default分支, 除了使用枚举类型以外,都必须有default分支
RWTLeftMenuTopItemType menuType = RWTLeftMenuTopItemMain;
switch (menuType) {
case RWTLeftMenuTopItemMain: {
// ...
break;
}
case RWTLeftMenuTopItemShows: {
// ...
break;
}
case RWTLeftMenuTopItemSchedule: {
// ...
break;
}
}
在Switch语句使用枚举类型的时候,如果使用了default分支,在将来就无法通过编译器来检查新增的枚举类型了。
函数
- 一个函数的长度必须限制在50行以内
通常来说,在阅读一个函数的时候,如果视需要跨过很长的垂直距离会非常影响代码的阅读体验。如果需要来回滚动眼球或代码才能看全一个方法,就会很影响思维的连贯性,对阅读代码的速度造成比较大的影响。最好的情况是在不滚动眼球或代码的情况下一眼就能将该方法的全部代码映入眼帘。 - 一个函数只做一件事(单一原则)
每个函数的职责都应该划分的很明确(就像类一样)。
推荐写法
dataConfiguration()
viewConfiguration()
- 对于有返回值的函数(方法),每一个分支都必须有返回值
推荐写法
int function()
{
if(condition1){
return count1
}else if(condition2){
return count2
}else{
return defaultCount
}
}
- 对输入参数的正确性和有效性进行检查,参数错误立即返回
推荐写法
void function(param1,param2)
{
if(param1 is unavailable){
return;
}
if(param2 is unavailable){
return;
}
//Do some right thing
}
- 如果在不同的函数内部有相同的功能,应该把相同的功能抽取出来单独作为另一个函数
原来的调用:
void logic() {
a();
b();
if (logic1 condition) {
c();
} else {
d();
}
}
将a,b函数抽取出来作为单独的函数
void basicConfig()
{
a();
b();
}
void logic1()
{
basicConfig();
c();
}
void logic2()
{
basicConfig();
d();
}
- 将函数内部比较复杂的逻辑提取出来作为单独的函数
一个函数内的不清晰(逻辑判断比较多,行数较多)的那片代码,往往可以被提取出去,构成一个新的函数,然后在原来的地方调用它这样你就可以使用有意义的函数名来代替注释,增加程序的可读性。
举一个发送邮件的例子:
openEmailSite();
login();
writeTitle(title);
writeContent(content);
writeReceiver(receiver);
addAttachment(attachment);
send();
中间的部分稍微长一些,我们可以将它们提取出来:
void writeEmail(title, content,receiver,attachment)
{
writeTitle(title);
writeContent(content);
writeReceiver(receiver);
addAttachment(attachment);
}
然后再看一下原来的代码:
openEmailSite();
login();
writeEmail(title, content,receiver,attachment)
send();
- 避免使用全局变量,类成员(class member)来传递信息,尽量使用局部变量和参数。
在一个类里面,经常会有传递某些变量的情况。而如果需要传递的变量是某个全局变量或者属性的时候,有些朋友不喜欢将它们作为参数,而是在方法内部就直接访问了:
class A {
var x;
func updateX()
{
...
x = ...;
}
func printX()
{
updateX();
print(x);
}
}
我们可以看到,在printX方法里面,updateX和print方法之间并没有值的传递,乍一看我们可能不知道x从哪里来的,导致程序的可读性降低了。
而如果你使用局部变量而不是类成员来传递信息,那么这两个函数就不需要依赖于某一个类,而且更加容易理解,不易出错:
func updateX() -> String
{
x = ...;
return x;
}
func printX()
{
String x = updateX();
print(x);
}
注释
优秀的代码大部分是可以自描述的,我们完全可以用程代码本身来表达它到底在干什么,而不需要注释的辅助。
但并不是说一定不能写注释,有以下三种情况比较适合写注释:
公共接口(注释要告诉阅读代码的人,当前类能实现什么功能)。
涉及到比较深层专业知识的代码(注释要体现出实现原理和思想)。
容易产生歧义的代码(但是严格来说,容易让人产生歧义的代码是不允许存在的)。
除了上述这三种情况,如果别人只能依靠注释才能读懂你的代码的时候,就要反思代码出现了什么问题。
最后,对于注释的内容,相对于“做了什么”,更应该说明“为什么这么做”。
Code Review
换行、注释、方法长度、代码重复等这些是通过机器检查出来的问题,是无需通过人来做的。
而且除了审查需求的实现的程度,bug是否无处藏身以外,更应该关注代码的设计。比如类与类之间的耦合程度,设计的可扩展性,复用性,是否可以将某些方法抽出来作为接口等等。
iOS规范
变量
- 变量名必须使用驼峰格式
类,协议使用大驼峰:
HomePageViewController.h
对象等局部变量使用小驼峰:
NSString *personName = @"";
NSUInteger totalCount = 0;
- 变量的名称必须同时包含功能与类型
UIButton *addBtn //添加按钮
UILabel *nameLbl //名字标签
NSString *addressStr//地址字符串
- 系统常用类作实例变量声明时加入后缀
图片
常量
- 常量以相关类名作为前缀
推荐这样写:
static const NSTimeInterval ZOCSignInViewControllerFadeOutAnimationDuration = 0.4;
不推荐这样写:
static const NSTimeInterval fadeOutTime = 0.4;
- 对外公开某个常量:
如果我们需要发送通知,那么就需要在不同的地方拿到通知的“频道”字符串(通知的名称),那么显然这个字符串是不能被轻易更改,而且可以在不同的地方获取。这个时候就需要定义一个外界可见的字符串常量。
推荐这样写:
//头文件
extern NSString *const ZOCCacheControllerDidClearCacheNotification;
//实现文件
static NSString * const ZOCCacheControllerDidClearCacheNotification = @"ZOCCacheControllerDidClearCacheNotification";
static const CGFloat ZOCImageThumbnailHeight = 50.0f;
不推荐这样写:
#define CompanyName @"Apple Inc."
#define magicNumber 42
宏
- 宏、常量名都要使用大写字母,用下划线‘_’分割单词。
#define URL_GAIN_QUOTE_LIST @"/v1/quote/list"
#define URL_UPDATE_QUOTE_LIST @"/v1/quote/update"
#define URL_LOGIN @"/v1/user/login”
- 宏定义中如果包含表达式或变量,表达式和变量必须用小括号括起来。
#define MY_MIN(A, B) ((A)>(B)?(B):(A))
参考文献:详情请阅读
总结
编码规范,没有固定的版本,也没有明确的对错。但是对于完善的开发团队,明确的规范,可以为后期的代码维护,新人学习带来很多好处。还有就是规范,可以将开发维护过程中,前人犯过的错误,进行总结,写入内部编码规范内,起到避免重复犯错的作用。
网友评论