养成良好的编码习惯(iOS)
@Famulei.
强制采用一套严格的技术标准并不是一个好主意,但是标准对于团队组织非常有用,因为标准有助于减少项目中随意出现的诸多分歧。如果你的团队反对采用严格的标准,那么可以考虑一些其他的选择,如灵活的指导原则、一些建议,或者一组能够表现最佳实践的例子。
命名规则
为什么要有规则
-
要求你更多的按规矩行事
-
有助于在项目之间传递知识
-
有助于你在新项目里更快速的学习代码
-
弥补编程语言的不足之处
-
强调相关变量之间的关系
何时采用命名规则
-
当多个程序员合作开发一个项目
-
当你计划把一个程序转交给另一位程序员来修改和维护的时候(这种情况总是会发生)
-
当你所在的组织中的其他程序员评估你写的程序的时候
-
当你写的程序规模太大,以至于无法再脑海里同时了解事情的全貌,而必须分而治之的时候
-
当你写的程序生命周期足够长,长到你可能会把它搁置几个星期或者几个月之后又重新启动有关该程序的工作时
-
当在一个项目中存在一些不常见的术语,并且你希望在编写代码阶段使用标准的术语或者缩写的时候
类名
-
类名应包含一个明确描述该类(或类的对象)是什么或做什么的名词
-
前缀两个字母或三个字母大写(推荐)
-
使用 @private,@protected 显式限定实例变量的访问权限
协议
-
使用weak修饰, 使用assgin修饰可能会造成野指针
-
协议有两个修饰符 @optional 和@required, 默认是@required状态,没有实现@required状态的代理方法会出现黄色警告,没有别的影响
-
@optional 和@required, 在调用代理方法时都需要做个判断,否则会导致崩溃
if ([self.cell.delegate respondsToSelector:@selector(cell:didClickImageAtIndex:withLongPress:)]){
[self.cell.delegate cell:self.cell didClickImageAtIndex:index withLongPress:NO];
}
Variable Names 变量名
你可不能像给狗取名字哪有给变量名,仅仅因为它可爱或者听上去不错。变量和变量名就本质而言是同一食物。变量的好与坏很大程度上取决于它命名的好坏。在给变量命名的时候请谨慎小心。
eg:
v vs headView
mlbl vs nameLabel
VC1 vs registerViewController
从上述的例子比较看出,一个好的变量名是可读的,易记的和具有描述性的。
变量命名时最重要的考虑事项 :
该名字要完全,准确地描述出该变量所代表的事物。通常对变量的描述
就是最佳的变量名。这种名字容易阅读,因为其中并不包含晦涩的缩写,
同时也有歧义,因为它是对该事物的完整描述,不会和其他事物混淆,
因此也容易记忆。
Const 常量
常量使用由全部大写的多个单词组成的说明型名称,每个单词之间用下划线分隔。声明常量时,要使用Const语句以及常量名、它的数据类型和它的值。
下面是一些使用常规命名规范的常量名称示例:
eg:
ACCESS_CONNECTSTRING
YY_EXTERN_C_BEGIN
API_MAX_STRINGBUFFER
常量名(如宏定义、枚举、静态局部变量等)推荐参考Apple开发文档命名。以小写字母k开头.官方说法:k表示Const (谁让人家Apple喜欢呢)
//大家经常看到的
eg:
kMainScreenWidth
kMainScreenHeight
我就是想要使用缩写(遇到任性的宝宝):
-
使用标准的缩写
eg:
msg 是 message, func 是 function
-
确保不要改变变量的含义
-
团队成员可以理解其意义
eg:
Btn 是 Button, Arr 是 Array, Str 是 String, Dic 是 Dictionary
-
缩写要一致
eg :
Number 或者 Num 或者 Numb, Dictionary 或者 Dic 或者Dict
-
避免使用令人误解的名字或缩写
eg:
Navigation 使用 nv (阅读者以为是女性的意思)
-
避免在名字中使用数字
-
避免使用标准类型、变量或者子程序的名字
eg:
id, true, return
-
不要使用与变量含义完全无关的名字
eg:
x1 , y2 , m3 ,z4 ( 臣妾真的猜不到 - - )
枚举常量
- 枚举常量
-
使用枚举来定义一组相关的整数常量
-
枚举常量与其typedef命名遵循函数命名规则。
- 延伸(非命名规则)
C语言风格的enum定义枚举类型
enum {
UIViewAnimationTransitionNone,
UIViewAnimationTransitionFlipFromLeft,
UIViewAnimationTransitionFlipFromRight,
UIViewAnimationTransitionCurlUp,
UIViewAnimationTransitionCurlDown,
} UIViewAnimationTransition;
//位移操作枚举定义
enum {
UIViewAutoresizingNone = 0,
UIViewAutoresizingFlexibleLeftMargin = 1 << 0,
UIViewAutoresizingFlexibleWidth = 1 << 1,
UIViewAutoresizingFlexibleRightMargin = 1 << 2,
UIViewAutoresizingFlexibleTopMargin = 1 << 3,
UIViewAutoresizingFlexibleHeight = 1 << 4,
UIViewAutoresizingFlexibleBottomMargin = 1 << 5
}; typedef NSUInteger UIViewAutoresizing;
枚举值一般是4个字节的int值,在64位系统上是8个字节。iOS6以后苹果引入了两个宏来重新定义这两个枚举类型,将enum定义和typedef合二为一,并且采用不同的宏来从代码角度来区分。
NS_OPTIONS一般用来定义位移相关操作的枚举值。
通用情况,推荐使用NS_ENUM。NS_OPTIONS一般用来定义具有位移操作或特点的情况。例如:
typedef NS_ENUM(NSInteger, UIViewAnimationTransition) {
UIViewAnimationTransitionNone,//默认从0开始
UIViewAnimationTransitionFlipFromLeft,
UIViewAnimationTransitionFlipFromRight,
UIViewAnimationTransitionCurlUp,
UIViewAnimationTransitionCurlDown,
};
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
};
方法命名
-
描述方法所做的事情
-
避免使用无意义、模糊或者标书不清的动词
-
对返回值有所描述
-
不要传递过多参数(建议限制大约在7个以内)
-
把状态、类型、样式或者出错变量放在最后
eg:
- (void)selectItemAtIndexPath:(nullable NSIndexPath *)indexPath animated:(BOOL)animated scrollPosition:(UICollectionViewScrollPosition)scrollPosition;
- (void)appendPartWithInputStream:(NSInputStream *)inputStream name:(NSString *)name fileName:(NSString *)fileName length:(int64_t)length mimeType:(NSString *)mimeType;
- (NSMutableURLRequest *)requestWithMethod:(NSString *)method URLString:(NSString *)URLString parameters:(id)parameters error:(NSError * __autoreleasing *)error;
准确使用对仗词
add/remove begin/end insert/delete
increment/decrement show/hide create/destory
open/close lock/unlock source/target
first/last max/min start/stop
get/put next/previous up/down
get/set old/new high/low
经典命名规则
- 匈牙利命名法。
该命名法是在每个变量名的前面加上若干表示数据类型的字符。
基本原则是:变量名=属性+类型+对象描述。
如i表示int,所有i开头的变量命都表示int类型。
s表示String,所有变量命以s开头的都表示String类型变量。
- 骆驼命名法
正如它的名称所表示的那样,是指混合使用大小写字母来构成变量和函数的名字。驼峰命名法跟帕斯卡命名法相似,只是首字母为小写,如userName。因为看上去像驼峰,因此而得名。
- 帕斯卡命名法 即pascal命名法。做法是首字母大写,如UserName,常用在类的变量命名中。
布局与风格
布局与风格并不影响执行速度、内存使用量等方面程序的布局。编排出色的代码会带来视觉上和思维上的愉悦,这是非程序员的人不能感受到的。如果你做的是合作项目,这块内容还是蛮重要的,尽量统一大家的风格。
好布局有什么用
-
准确表现代码的逻辑结构
-
改善可读性
-
经得起修改
布局技术
- 分组
eg: //Method 分组
#pragma mark - View lifeCycle
#pragma mark - Getter/Setter
#pragma mark - Init methods
#pragma mark - Action methods
#pragma mark - Common methods
#pragma mark - UIActionSheetDelegate
#pragma mark - UIImagePickerControllerDelegate
#pragma mark - UITableViewDelegate Methods
#pragma mark - UITableViewDataSource Methods
#pragma mark - UIScrollViewDelegate Methods
- 空格
eg: //属性
@property (nonatomic, readonly, strong) NSDictionary *statuCodeToMessageDictionary;
-
property 后留空格
-
修饰符“,”后留空格
-
修饰符“()”后留空格
-
所有变量的类名后留空格
-
类型标识符和尖括号内的协议名之间不能有任何空格
-
属性的参数应该按照下面的顺序排列: 原子性->读写->内存管理
(这些修饰符被修改的可能性从高到底应为:内存管理 > 读写权限 >原子操作)
- 如无特别情况,使用nonatomic修饰,atomic带来的互斥锁特别影响性能。
eg: //方法
- (void)setAnimatingWithStateOfTask:(NSURLSessionTask *)task;
1.方法返回值前留空格
2.方法返回值后不留空格
3.左括号和方法名在同行并有空格隔开
4.多个参数的方法换行并以冒号对齐
- 换行
eg://method 推荐写法(不完全为了美观)
- (BOOL)validateResponse:(NSHTTPURLResponse *)response
data:(NSData *)data
error:(NSError *__autoreleasing *)error;
//不推荐写法(参数很多的情况,可读性很差)
- (BOOL)validateResponse:(NSHTTPURLResponse *)response data:(NSData *)data error:(NSError *__autoreleasing *)error;
- 缩进
eg: //常用的字符串(推荐写法)
NSString *string = @"Rose";
//不推荐写法
NSString*string=@"Rose";
- 对齐
eg: //create UILabel 推荐写法 (组织结构明了,易于修改,可维护性强)
UILabel *testLabel = [[UILabel alloc]init];
testLabel.frame = CGRectMake(50, 50, 50, 50);
testLabel.text = @"The cute Rose";
testLabel.textColor = [UIColor yellowColor];
[self.view addSubview:testLabel];
//不推荐 (凌乱, 臃肿,简直不想看)
UILabel*testLabel=[[UILabel alloc]init];
testLabel.frame=CGRectMake(50, 50, 50, 50);
testLabel.text=@"The cute Rose";
testLabel.textColor=[UIColor yellowColor];
[self.view addSubview:testLabel];
- 其他考虑
-
段落之间使用空行
-
单语句代码块的格式要前后统一
-
对于复杂的表达式,将条件分隔放在多行上
-
...
注释
对于精心编写的代码而言,注释不过是美丽衣裳上的小饰物而已。注释的种类:
重复代码
解释代码
代码标记
概述代码
代码意图说明
注释单行或多行
-
该行代码太复杂,因此需要注释
-
该行语句出过错,留个标记
-
不要对单行代码做行尾注释
-
注释的缩进要与相应代码保持一致
-
每行注释至少用一个空行隔开
行尾注释用于数据声明
避免用行尾注释存放维护注记 (记录修改或者错误编号)
用行尾注释标记块尾
注释控制结构
-
应在每个if、case、循环或者代码段前面加上注释
-
应在每个控制结构后加上注释
-
注释赢靠近其说明的代码
-
在声明参数处注释这些参数
eg:
/**
基本地址URL,接口特殊性需要其他baseURL,可以覆盖此方法 默认为空
@return 基本地址的URL
*/
- (NSString *)baseURL;
高质量的子程序
什么是子程序?
子程序是为实现一个特点的目的而编写的一个可被调用的方法(method)或者过程(procedure)。
创建子程序的正当理由
-
避免代码重复
-
支持子类化
-
引入中间、易懂的抽象
-
隐藏顺序
-
隐藏指针操作
-
提高可移植性
-
改善性能
-
确保所有的子程序都很小
-
简化复杂的逻辑判断
创建类的很多理由也是创建子程序的理由
-
隔离复杂度
-
隐藏实现细节
-
限制变化带来的影响
-
隐藏全局数据
-
促成可重用的代码
-
达到特定的重构目的
子程序设计
- 功能上的内聚性
是最强也是最好的一种内聚性,让一个子程序仅执行一项操作。
- 顺序上的内聚性
是指子程序包含有需要按特定书序执行的操作,需要共享数据,而且只有全部执行完毕后才完成一项完整的功能。
- 通信上的内聚性
是指一个子程序中的不用操作使用了同样的数据,但不存在其他任何联系。
- 临时上的内聚性
是指含有一些因为需要同时执行才放到一起的操作的子程序。
- 过程上的内聚性
是指一个子程序的操作是按特定的顺序进行的。
- 逻辑上的内聚性
是指若干操作被放入同一个子程序中,通过传入的控制标志选择执行其中的一项操作。
- 巧合的内聚性(无内聚性或者混乱内聚性)
是指子程序中的各个操作之间没有任何可以看到的关联。
宏定义
C语言提供的三种预处理功能的其中一种,分别为:宏定义、文件包括、条件编译。宏定义和操作符区别是:宏定义制作替换,不做计算,也不做表达式求解。
-
宏命名一般用大写
-
宏定义末尾不加分号
-
使用宏可提高程序的通用性和易读性,减少不一致性减少错误和便于修改
-
把宏表达式整个包含在括号内
eg: // 简单的宏定义
#define STATUS_BAR_HEIGHT 20
eg: // 代码块宏定义
#define ALERT(text) {UIAlertView *alert = [[UIAlertView alloc]initWithTitle:@"提示" message:(text) delegate:nil cancelButtonTitle:@"确定" otherButtonTitles:nil, nil];[alert show];}
断言
断言是开发期间使用的、让程序在运行时进行自检的代码(通常是一个子程序或者宏)。
建立自己的断言机制
例如YYKit里面:
#define YYAssertNotNil(condition, description, ...) NSAssert((condition), (description), ##__VA_ARGS__)
#define YYCAssertNotNil(condition, description, ...) NSCAssert((condition), (description), ##__VA_ARGS__)
#define YYAssertMainThread() NSAssert([NSThread isMainThread], @"This method must be called on the main thread")
#define YYCAssertMainThread() NSCAssert([NSThread isMainThread], @"This method must be called on the main thread")
使用断言的建议
-
用错误处理代码来处理预期会发生的情况,用断言来处理绝不应该发生的状况
-
避免把需要执行的代码放到断言里
-
用断言来注解并验证前条件和后条件
-
对于高健壮性的代码,应该先使用断言再处理错误
异常
异常时把代码中的错误或异常事件传递给调用方代码的一种特殊手段。
-
用异常通知程序的其他部分,发生了不可忽略的错误
-
只在真正例外的情况才抛出异常(仅在其他编码实践方法无法解决的情况下才使用异常)
-
避免在构造函数和析构函数中抛出异常,除非你在同一地方把它们捕获
-
在恰当的抽象层次抛出异常
-
在异常消息中加入关于导致异常发生的全部信息
-
避免使用空的catch语句
-
了解所用函数库可能抛出的异常
-
考虑创建一个集中的异常报告机制
使用@try、catch捕获异常:
@try {
// 可能会出现崩溃的代码
}
@catch (NSException *exception) {
// 捕获到的异常exception
}
@finally {
// 结果处理
}
警告(视团队需求而定)
-
将建议其的警告级别设置为最高级,尽可能不放过任何一个警告,然后修正编译器所报告的全部错误
-
用对待错误的态度来处理警告
-
在项目组范围内使用同一的编译设置
信仰问题
-
缩进风格
-
大括号的摆放位置
-
所用的继承开发环境
-
注释风格
-
效率与可读性的取舍
-
对方法的选择
-
命名习惯
-
对全局变量的使用
”特定的标准”的细节往往没有“存在某个标准”重要。我们都在向一个相对标准的规则靠近。没必要在一些编码风格的细节差异上产生斗争。只要清晰明了,对项目没有损失就好。
个人性格(题外话)
-
人的个性对编程能力有直接影响
-
最有关系的性格:谦虚、求知欲、诚实、创造性和纪律、以及高明的偷懒。
-
很多程序员不愿意朱东海吸收新知识和技术,如何能抽出少量时间阅读和学习编程知识,提升速度很快。
-
好性格与培养正确的习惯关系很大,养成良好的编码习惯,其他自然水到渠成。
网络图片.png
本文参考:
小菲菲总结了份规范文档
http://xuyafei.cn/post/language/cocoadai-ma-feng-ge-zhi-nan-zhi-ming-ming-gui-fan
<<代码大全>>
我们公司正在招聘前端开发工程师和PHP开发工程师,欢迎加入我们的Famulei.com,坐标上海。
养成良好的编码习惯(iOS)