先吹个牛,我打心眼自认为自己是喜欢对团队项目的代码质量负责的人,对于思考如何写出高质量可读性的代码我是乐此不彼。之前我写过两篇关于代码命名规范和代码编写规范的文章,《iOS架构师之路:iOS开发(OC)中的命名规范》、《iOS架构师之路:IOS项目中的编码规范》,您要是心情很好,就去看看吧,如果低于很好,那不建议您看,怕您心里骂娘,因为现在看,感觉自己写的不太认真,有不少方面可以写的更细致,恩,我决定给自己帖贴金,不能这么说自己:其实这半年小哥我在代码规范方面的知识又见涨不少,所以看以前定制的规范不爽,作为架构师保持谦卑,通过不断学习,不断自我修正,对代码有一点洁癖是该有的气质(潜台词其实我想说我有)。制定项目的代码规范对架构师的重要性,就像要你生个娃一样,责任重大,万一生出来缺胳膊少腿,娶不到姑娘,你以后即使伺候他一辈子,给他当牛做马,他也不一定会念你的好。
论代码规范的重要性
- 1.架构师要为整个项目技术方向的发展负责,所以制定一个良好的代码规范,让开发工程师遵守,有利于项目朝着您预知的方向发展。比如当您向使用AOP技术实现日志功能时,就需要规定一些方法命名。
- 2.一致的代码规范,有利于代码reveiw工作。如果每个工程师写的代码风格不一样,review代码的同事,阅读起来肯定不顺畅。
- 3.要求工程师按照代码规范写出一致的代码,就不怕他跳槽。这行本来就浮躁,流动性大,要是工程师写的代码风格只有他自己能看懂,那玩意他跳槽,新人是很难继续维护这部分代码的,得不偿失。
培养代码洁癖
给大家推荐一本关于代码规范的杰作,第一本:《禅与 Objective-C 编程艺术(Zen and the Art of the Objective-C Craftsmanship 中文翻译)》(简称:Zen),这本书开源社区的大牛,无偿奉献出来的,该书给我们介绍许多写代码的正确姿势,并解释为什么使用这个姿势体验更好。看完这本书应该知道如何写出优雅、高可读性并且可靠的代码了。
我推荐的代码规范
《The Objective-C Style Guide used by The New York Times》(简称:New York,该规范也有中文版),《New York》是我比较喜欢的编码规范风格,它是《Zen》的编码思想一个很好的实践。
关于《Zen》、《New York》代码规范的补充
1.iOS切图文件的命名规范
这部分规范可能是很有经验的设计提供,也有可能是我们开发人员提供,掌握总是没有坏处的。
我们的命名规则的基本思想是把文件名分成三部分,第一部分是图片的逻辑归属分类,第二部分是图片的表现内容,第三部分是图片的内容的类型,有些图片还会有第四部分,表示图片表现的状态。首先有几个规则是:
- 用英文命名,不用拼音
- 每一部分用下划线分隔
- 图片名中两倍图在名字最后要加@2x,三倍图在名字最后要加@3x
万能公式:
2.类的布局
程序布局的目的是显示出程序良好的逻辑结构,提高程序的准确性、连续性、可读性、可维护性。更重要的是,统一的程序布局和编程风格,有助于提高整个项目的开发质量,提高开发效率,降低开发成本。同时,对于普通程序员来说,养成良好的编程习惯有助于提高自己的编程水平,提高编程效率。因此,统一的、良好的程序布局和编程风格不仅仅是个人主观美学上的或是形式上的问题,而且会涉及到产品质量,涉及到个人编程能力的提高,必须引起大家重视。
2.1.文件布局
【规则2-1-1】遵循统一的布局顺序来书写头文件。
说明:以下内容如果某些节不需要,可以忽略。但是其它节要保持该次序。
头文件布局:
文件头
#import (依次为标准库头文件、非标准库头文件)
全局宏
常量定义
全局数据类型
类定义
正例:
/***************************************************************************
* 文件引用
***************************************************************************/
/***************************************************************************
* 类引用
***************************************************************************/
/***************************************************************************
* 宏定义
***************************************************************************/
/***************************************************************************
* 常量
***************************************************************************/
/***************************************************************************
* 类型定义
***************************************************************************/
/ ***************************************************************************
* 类定义
***************************************************************************/
【规则2-1-2】遵循统一的布局顺序来书写实现文件。
说明:以下内容如果某些节不需要,可以忽略。但是其它节要保持该次序。
实现文件布局:
文件头(参见“注释”一节)
#import (依次为标准库头文件、非标准库头文件)
文件内部使用的宏
常量定义
文件内部使用的数据类型
全局变量
本地变量(即静态全局变量)
类的实现
正例:
/***************************************************************************
* 文件引用
***************************************************************************/
/***************************************************************************
* 宏定义
***************************************************************************/
/***************************************************************************
* 常量
***************************************************************************/
/***************************************************************************
* 类型定义
***************************************************************************/
/***************************************************************************
* 全局变量
***************************************************************************/
/***************************************************************************
* 原型
***************************************************************************/
/ ***************************************************************************
* 类特性
***************************************************************************/
/ ***************************************************************************
* 类的实现
***************************************************************************/
2.2类结构布局
使用#pragma mark –来分类方法
#pragma mark – Life Cycle
#pragma mark - Events
#pragma mark – Private Methods
#pragma mark - UITextFieldDelegate
#pragma mark - UITableViewDataSource
#pragma mark - UITableViewDelegate
#pragma mark - Custom Delegates
#pragma mark – Getters and Setters
2.3布局中的空格
每个方法或者功能块之间为了结构清晰,应当有且只有一行空格。
@interface SomeClass:NSObject
@property (noatomic, strong) UIView *aView
- (void)someMethod;
@end
@implementation SomeClass
- (void)setAView:(NSInteger )aview {
}
- (void)someMethod {
}
@end
2.4关于布局中的Private Methods块,正常情况下ViewController里面不应该写
不是delegate方法的,不是event response方法的,不是life cycle方法的,就是private method了。对的,正常情况下ViewController里面一般是不会存在private methods的,这个private methods一般是用于日期换算、图片裁剪啥的这种小功能。这种小功能要么把它写成一个category,要么把他做成一个模块,哪怕这个模块只有一个函数也行。
ViewController基本上是大部分业务的载体,本身代码已经相当复杂,所以跟业务关联不大的东西能不放在ViewController里面就不要放。另外一点,这个private method的功能这时候只是你用得到,但是将来说不定别的地方也会用到,一开始就独立出来,有利于将来的代码复用。
3.属性初始化放哪最好?建议在Getter中初始化
我看到很多APP,甚至我公司的项目,很多开发工程师,初始化属性的位置比较随意,有单独添加一个初始化方法类似setupView的,有在init初始化的,各种情况都有,我其实挺崩溃的,首先初始化方式不一致,其次也这样做非常有可能破坏了每个方法功能的单一性(每个方法只做一件事)。我比较习惯一个对象的"私有"属性写在extension里面,然后这些属性的初始化全部放在getter里面做,在init和dealloc之外,是不会出现任何类似_property这样的写法的。就是这样:
@interface CustomObject()
@property (nonatomic, strong) UILabel *label;
@end
@implementation
#pragma mark - getters and setters
- (UILabel *)label {
if (_label == nil) {
_label = [[UILabel alloc] init];
_label.text = @"1234";
_label.font = [UIFont systemFontOfSize:12];
... ...
}
return _label;
}
@end
#pragma mark - life cycle
- (void)viewDidLoad {
[super viewDidLoad];
[self.view addSubview:self.label];
}
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
self.label.frame = CGRectMake(1, 2, 3, 4);
}
唐巧说他喜欢的做法是用_property这种,然后关于_property
的初始化通过[self setupProperty]
这种做法去做。从刚才上面的代码来看,就是要在viewDidLoad里面多调用一个setup方法而已,然后我推荐的方法就是不用多调一个setup方法,直接走getter。
嗯,怎么说呢,其实两种做法都能完成需求。但是从另一个角度看,苹果之所以选择让[self getProperty]
和self.property
可以互相通用,这种做法已经很明显地表达了苹果的倾向:希望每个property都是通过getter方法来获得。
早在2003年,Allen Holub就发了篇文章《Why getter and setter methods are evil》,自此之后,业界就对此产生了各种争议,虽然是从Java开始说的,但是发展到后面各种语言也参与了进来。然后虽然现在关于这个问题讨论得少了,但是依旧属于没有定论的状态。setter的情况比较复杂,也不是我这一节的重点,我这边还是主要说getter。我们从objc的设计来看,苹果的设计者更加倾向于getter is not evil。
认为getter is evil的原因有非常之多,或大或小,随着争论的进行,大家慢慢就聚焦到这样的一个原因:Getter和Setter提供了一个能让外部修改对象内部数据的方式,这是evil的,正常情况下,一个对象自己私有的变量应该是只有自己关心。
然后我们回到iOS领域来,objc也同样面临了这样的问题,甚至更加严重:objc并没有像Java那么严格的私有概念。但在实际工作中,我们不太会去操作头文件里面没有的变量,这是从规范上就被禁止的。
认为getter is not evil的原因也可以聚焦到一个:高度的封装性。getter事实上是工厂方法,有了getter之后,业务逻辑可以更加专注于调用,而不必担心当前变量是否可用。我们可以想一下,假设一个ViewController有20个subview要加入view中,这20个subview的初始化代码是肯定逃不掉的,放在哪里比较好?放在哪里都比放在addsubview的地方好,我个人认为最好的地方还是放在getter里面,结合单例模式之后,代码会非常整齐,生产的地方和使用的地方得到了很好的区分。
所以放到iOS来说,我还是觉得使用getter会比较好,因为evil的地方在iOS这边基本都避免了,not evil的地方都能享受到,还是不错的。
4.Getters and Setters放在最底部
我之前写代码一直把Getters and Setters 放在implementation的最前面,昨天看大神casatwy说最好放在最后面,我觉得更有道理。控制器可能会有非常多的view属性和其他属性,如果所有的getters and setters放在前面,就会导致在implementation代码顶部有大量的初始化代码,这就导致主要的逻辑代码挪到后面去了,其他人阅读代码是不太方便的。