为什么要有代码规范?
对于团队,如果代码风格不统一,阅读或修改同事的代码会非常困难,造成潜在的风险。
对于个人,代码规范是对自身编码习惯的一种监督,如果没有这种监督,有时候因为偷懒,会写出难看的代码,时间长了自己都看不懂。这样对于代码的维护性是不利的。
代码规范的内容?
代码规范包含的范围十分广泛。从一个变量的命名到一个类的设计,我觉得都属于代码规范的范畴。从实践的角度,可以把代码规范分成两个部分:
第一部分是规则,即一定要这么做。这里面没有对错,但需要统一。包含变量的命名、函数的命名、模块的组织、代码块的组织、宏、枚举、常量的声明、函数的粒度。
第二部分是风格,即一种模式化的代码设计结构。我们实现某个功能时,往往不止一种实现方式。每一种实现方式没有绝对的高低之分,不同角度的解读,就会有不同的偏好。所以这个层面上的代码规范,只能求同存异。但是不管怎样,每个人必须要有一致性的风格。就像不同的小区可以有不同的风格,但同一个小区只能有一种设计风格。风格包含代码设计中的抽象概念,比如接口、继承等等。
为什么要有代码规范?
对于团队,如果代码风格不统一,阅读或修改同事的代码会非常困难,造成潜在的风险。
对于个人,代码规范是对自身编码习惯的一种监督,如果没有这种监督,有时候因为偷懒,会写出难看的代码,时间长了自己都看不懂。这样对于代码的维护性是不利的。
代码规范的内容?
代码规范包含的范围十分广泛。从一个变量的命名到一个类的设计,我觉得都属于代码规范的范畴。从实践的角度,可以把代码规范分成两个部分:
第一部分是规则,即一定要这么做。这里面没有对错,但需要统一。包含变量的命名、函数的命名、模块的组织、代码块的组织、宏、枚举、常量的声明、函数的粒度。
第二部分是风格,即一种模式化的代码设计结构。我们实现某个功能时,往往不止一种实现方式。每一种实现方式没有绝对的高低之分,不同角度的解读,就会有不同的偏好。所以这个层面上的代码规范,只能求同存异。但是不管怎样,每个人必须要有一致性的风格。就像不同的小区可以有不同的风格,但同一个小区只能有一种设计风格。风格包含代码设计中的抽象概念,比如接口、继承等等。
代码的规则
规则如同法律,尽管从心底里很多人不认可,但每个人都必须遵守。看起来大家都受了束缚,但是对整个社会以及每一个人都是利大于弊。
变量命名
变量命名历史上产生过很多方法,比较著名的有匈牙利命名法、驼峰命名法、下划线命名法。
- 匈牙利命名法:szUserName
- 驼峰命名法:userName
- 下划线命名法:user_name
用Objective-C开发,我们就参考苹果的官方命名方式即可,苹果采用的是驼峰命名法。
苹果的变量命名方式常常被其它语言的开发者所吐槽,因为它的名有时候是在太长了!!!下面是3个最长的Objective-C属性名:
1 | automaticallyEnablesStillImageStabilizationWhenAvailable |
变量声明最重要的是清晰其次才考虑长短,如果为了简短而含糊不清,是不可取的。
拿现有代码举例
变量含义 | Not Good | Good |
---|---|---|
昵称输入框 | tfNickName | nickNameTextField |
密码ImageView | imvPass | passwordImageView |
我的搜索条 | mysearchBar | mySearchBar |
类变量
前面加下划线,和系统框架保持一致
1 | @interface UIViewController : UIResponder <NSCoding, UIAppearanceContainer> |
现有的例子
1 | @interface CouponListTableViewCell : UITableViewCell |
类变量与属性
属性是Objective-C的一种高级语法,属性可以完全替换类变量,并且提供更强大的访问控制(strong、weak、nonatomic、readonly…),而且属性也只需要用一行就能搞定,所以为什么还要用类变量呢?
属性声明后不用写@synthesize,且会自动生成类变量。
例外
- 一些局部变量允许用最简单的方式命名:i、j、temp
- 一些常用的缩写可以不用驼峰:RMB,OBD
函数命名
Objective-C中的函数不同于其他语言,函数名是按参数隔开的。初看起来是有点怪,但是习惯了就会发现这样和命名方式可读性极好,就像一句话一样。所以当你的函数名不能流利的读出来的话,那么它的命名肯定就有问题。
还是先举系统的例子
1 | - (void)willAnimateFirstHalfOfRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation |
这里面值得注意的几点:
- 最左边的(+、-)和返回类型的左括号之间有一个空格,返回类型的右括号与第一个参数之间没有空格;
- 函数名中每一段描述都以小写字母开头,描述的写法要考虑清晰明确,参考变量命名;
- 如果函数名过长,应该换行,按冒号对齐;
- 从描述中可以看出对应参数的意义。
再看看我们自己的命名
最后一个参数描述Selector首字母大写了
1
2
3+ (UIButton *)createButton:(MUButtonItemAttribute *)attr
target:(id)target
Selector:(SEL)sel;有一些多余的空格
1
2+ (AppDelegate *) appDelegate;
- (void)hideTabbar :(BOOL)status;加减号和括号之间要有空格
1
- (void)pushEvent:(NSDictionary*)dictionary target:(id)target;
参数名和参数要对应,含义清晰
1
2
3
4
5- (void)setNavBackButton:(NSString *)string;
- (id)init:(NSString*)nickName phoneNumber:(NSString *)phoneNumber;
- (void)setTextFieldBackGroud:(CGRect)rect;
- (id)init:(NSInteger)flag;
- (void)requestHealthRecord:(NSString *)typeStr;函数名过长要换行
1
2
3
4
5- (id)initWithStyle:(UITableViewCellStyle)style
reuseIdentifier:(NSString *)reuseIdentifier
containingTableView:(UITableView *)containingTableView
leftUtilityButtons:(NSArray *)leftUtilityButtons
rightUtilityButtons:(NSArray *)rightUtilityButtons;命名风格混乱
1
2
3
4
5
6+ (NSInteger)GetServerBack:(NSString *)serverName
path_Param:(NSMutableDictionary*)path_Param
query_Param:(NSMutableDictionary*)query_Param
body_Param:(NSMutableDictionary*)body_Param
method:(NSInteger)method
returnValue:(NSMutableDictionary*)returnValue;参数之间用and、with,多此一举
1
2
3
4- (id)initWithFrame:(CGRect)frame
andImageView:(UIImageView*)imageView
withImageFrame:(CGRect)imageInitFrame
withImageUrl:(NSURL*)imageUrl;
命名很重要!!!
良好的命名是写出高质量代码的前提,命名的时候除了符合以上的规范,还需要找到合适的单词来表达含义,不要为了一时之快用拼音,也千万不要出现数字。
代码模块组织
这个代码模块的组织是指文件级别的,即我们应该如何划分包。
现有的包结构是有些问题的,导致我们找某个功能的实现文件比较麻烦。
文件的命名
我们自己创建的文件,应该有统一的前缀。比如HJMainViewController,这也符合系统的命名规范。Objective-C没有namespace,所以这样做的好处是当你引用了第三方的代码时,防止重名。而且当我们自己的代码越来越大时,内部也会出现冲突。
一个文件一个类
好处:
- 减少单个文件的长度
- 定位文件更方便
- 便于重用
代码块的组织
作为一个程序员,大部分时间实在看代码而不是在写。所以代码的可读性严重影响到我们的工作效率。
当你看到超过1000行的实现文件时,你还有看下去的欲望吗?
所以千万不要写出超过1000行的代码,有的话也得重构。
当代码控制到1000行以内后,只是具备了可读的基本条件。我们的屏幕一般只能一次显示50行代码,从头看到尾就是20屏,常常会看了后面忘了前面。所以我们要把一个文件里面的所有函数有秩序的组织起来,让人不用重头看到尾,就能很快定位到想看的地方。
这里提供一个小办法:用#pragma mark宏根据功能区分开:
1 | pragma mark - Life Circle |
宏、枚举、常量的声明
宏的命名方式参考变量的命名方式,大写,用下划线分开1
2
枚举也有很多写法,这里推荐系统的方式
typedef NS_ENUM(NSInteger, NSTextAlignment)
{
NSTextAlignmentLeft = 0, // Visually left aligned
NSTextAlignmentRight = 1, // Visually right aligned
NSTextAlignmentCenter = 2, // Visually centered
NSTextAlignmentJustified = 3, // Fully-justified. The last line in a paragraph is natural-aligned.
NSTextAlignmentNatural = 4, // Indicates the default alignment for script
}1
常量以k开头,系统命名风格
extern NSString const kAPNetworkDidSetupNotification; // 建立连接
extern NSString const kAPNetworkDidCloseNotification; // 关闭连接
extern NSString const kAPNetworkDidRegisterNotification; // 注册成功
extern NSString const kAPNetworkDidLoginNotification; // 登录成功1
如果是数值常量,用宏的话,也可以用k开头
#define kDistancePoint 10.0f // 圆点、标志和正文的间距1
2## 函数的粒度
知道我们现在最长的函数有多少行吗?655行。
- (void)getShopDetail
1
2
3
4
5
6
7
8
一个函数最好不超过一屏(50行),千万不能超过两屏(100行)。
因为从设计的角度来说,一个函数只需要干一件事,所以50行一般是够的,超长的函数往往干了很多事情,或者干了一件很大的事(拆成多件小事,交给子函数去干)。
## 其它
- **Log**:不要使用NSLog,提交了会因想到别人,而且最后打包删起来麻烦。需要找一个Log工具,MyNSLog功能不够。
- **代码中尽量不要出现数字**:据上下文推测出来的,还是计算的出为好(现在界面里面很多地方确实要写死数字,适配iPhone6会很困难)
_contentBkg = [[UIView alloc] initWithFrame:CGRectMake(0, 1 / [UIScreen mainScreen].scale, 320, 68 - 1)];1
- **避免复杂的表达式**:
if (tfPassword.text!=nil &&
![tfPassword.text isEqualToString:@""] &&
tfRequestNumber.text!=nil &&
![tfRequestNumber.text isEqualToString:@""]
)
{
;
}
1 | - **一个变量不在多个函数中出现,不要作为类变量:** |
MBProgressHUD* hudProgress;
int result;1
2- **每个类的.h开头的地方要有注释,说明这个类是干嘛的**
-
/**
- SDImageCache maintains a memory cache and an optional disk cache. Disk cache write operations are performed
- asynchronous so it doesn’t add unnecessary latency to the UI.
*/
@interface SDImageCache : NSObject1
- **if后面的语句哪怕只有一行,都加括号**
if ([key rangeOfString:@”热”].location != NSNotFound)
{
titleLabel.text = @”热门城市”;
}
else
{
titleLabel.text = key;
}1
2
3
4
5
6
7
# 代码的风格
风格如同习俗,不同地区有不同的习俗。你觉得入乡随俗好,就入乡随俗,这样避免了一些无谓的麻烦;但是你若坚持原有的习俗,社会也是能接受的,毕竟这些东西都是发展变化的,没有固定的模式,只有更好的模式。
## 接口
这里的接口指的是一个类公开的一组方法,良好设计的类是高内聚低耦合的,所以接口必须是简洁的。
@interface PaySucceedViewController : LCBaseViewController
@property (nonatomic,retain) NSString *isSuccess;
@property (nonatomic,retain) NSString *shopName;
@property (nonatomic,retain) NSString *detailinfo;
@property (nonatomic,retain) NSString *amount;
@property (nonatomic,retain) NSString *omsOrderId;
/*
- 支付订单类型
* - @since
*/
@property (nonatomic) ORDER_CATEGORY mCategory;
@end`
上面的例子是一个ViewController,但是它暴露了太多属性,属性很零碎,传值的时候要一个一个传。其实这里属性应该属于一个叫订单Model的对象,把这些属性封装在OrderModel中,复用性和可维护性都会好很多。(但是现在代码整体结构没有Model层,这里需要很大的重构)
继承
继承不是为了少写代码,它的目的是抽象,顺便少写了代码。
举NavigationBar的例子,用宏可以实现,用基类也可以实现,但基类抽象出了一个共有的接口,以后发生共性的变化够可以通过修改基类来实现,你可以在基类里加一组方法,而宏做不到。
其它
代码规范的路还长着,先走出第一步吧!