前言
制定风格指南主要的目的是统一团队的代码风格与样式,提高工作效率与阅读性还有维护性;
这篇文章虽然是OC风格指南,但是有些风格是所有编程语言通用的,另外它还参考了阿里巴巴Java代码规范,想不到吧。
原则
优化阅读体验,而非写代码的体验
代码库通常具有较长的生命周期,并且花在阅读代码上的时间也远多于编写代码的时间。所以我们应该明确一个目标:去优化别人阅读、维护我们代码时的体验,而不是优化写代码时的体验(例如随便使用缩写进行编码,代码量变少了,但是增加了阅读和维护的难度
)。
适当的规范限制和标准不是消灭代码内容的创造性、优雅性,而是限制过度个性化,以一种普遍认可的方式一起做事,进而提高工作效率,降低沟通成本。提高稳定性。
与上下文尽量保持一致
尽量保持代码库中的风格样式一致。在整个代码库中始终保持一种风格可以让工程师更专注于其他(更重要的)问题。一致性还可以实现更好的自动化。
如果风格指南中没有说明,那么就按照代码库之前的风格样式或者参考 Apple SDK
的风格样式。
命名风格
通用命名风格
[必须] 清晰和简洁(
好的名称应该是能自我描述的
),简洁的原则是为了保持更好的清晰度和可读性,记住 不要本末倒置。正例:
// 移除一个对象。
1. removeObject:
// 使用指定字符串替换出现过的字符串。
2. stringByReplacingOccurrencesOfString: withString:
反例:
// 要移除什么?
1. remove:
// 要将什么替换成什么?是全部都替换还是只替换第一个匹配的位置?
2. replace("1", "2")[必须] 一致性:命名应该和上下文乃至全局保持一致性,相同类型或者具有相同作用方法的名称应该相同或类似。
正例:
1. NSDictionary、NSArray、NSSet这些类中名叫 count 的方法所体现的作用都是一样的。
2. 系统的一些代理方法和通知的名称也会刻意保持一致,
例如 `UIApplicationDelegate`协议中的 `applicationDidBecomeActive` 方法名
就和 `UIApplicationDidBecomeActiveNotification`通知的名称是一致的。[必须] 禁止自我指涉:命名不要自我指涉(通知、掩码或常量等除外)。
正例:
NSString
反例:
NSStringObject[必须] 禁止过度缩写和自创缩写,一些通用缩写名除外(例如ATM、GPS、max、min等),具体请参考 可接受的缩写词列表。
Tips:
阅读者可能来自不同的地方接受不同的教育和不同的文化,
他们不一定会明白你写的缩写的意思。
正例:
setBackgroundColor
反例:
setBgColor[必须] 禁止使用无意义的拼音,国际通用名、地名、人名除外。
正例:
Beijing、Alibaba
反例:
// 打折
DaZhePromotion[必须] 代码和注释中避免使用任何语言的种族歧视性词语。
正例:
secondary、main
反例:
slave、master[必须] 禁止以new、alloc、copy、mutableCopy等关键字作为名称的开始部分。
[必须] 由于 OC 没有命名空间的概念,所以全局名称(
类名、协议名、全局常量名、全局变量名、函数名、typedef名称
)必须添加前缀,前缀由3个及以上字符组成且全部大写。Tips:
由于系统保留任意两个字符作为前缀的使用权(NS、UI、CG、CF、CA、WK、MK、CI、NC等等),
为了避免和系统命名冲突,所以前缀至少由3个字符组成。
正例:
ZTYLoginViewController
反例:
ZTLoginViewController[必须] 如果某个全局名称(
例如函数名、通知名、协议方法名
)和某个类有所关联,那么请使用相关类名作为其前缀,否则请使用通用前缀。正例:
UIApplicationDidBecomeActiveNotification
MAGUserLoginSuccessNotification[必须] 命名风格统一使用 驼峰命名方式 ,局部变量名等特殊名称可以不遵守。
正例:
totalRemain
反例:
total_remain[必须] 成员变量名称必须以_作为开始部分。
正例:
_nameString
反例:
nameString[建议] 在给常量或变量命名时,请将表示类型的名词放在词尾,以提高辨识度。
正例:
nameLabel、nameString
反例:
name[建议] 如果模块、接口、类、方法使用了某种模式,在命名时尽量体现出具体模式。
正例:
OrderFactory、LoginProxy[建议] 建议给临时变量名称添加前缀,以提高辨识度(
特殊情况除外,例如for循环里面的i
)。正例:
// t表示temp
t_label
反例:
label
类名风格
[必须] 一个完整的类名由 前缀+名称+类型 3个部分组成。
正例:
MAGLoginViewControler
前缀:MAG
名称:Login
类型:ViewControler
分类命名风格
[必须] 分类命名风格和类名类似,由 前缀+名称 2个部分组成。
正例:
UIView (MAGAdd)
反例:
UIView (Add)[必须] 分类中的方法名必须要添加前缀,防止覆盖系统或3方库的私有方法,前缀需要保持唯一性(
例如mp_
)。正例:
mp_substringFromString
反例:
substringFromString[建议] 分类中建议不要声明属性,尽量挪到主类中声明。
Tips:
尽管从技术上来讲可以在分类中声明属性,但是这么做需要格外小心,
因为它很容易出现内存上或其他一些问题,而且出现问题很难排查。[建议] 如果一个类比较复杂,建议使用分类重新组织结构和代码(
可以参考系统的UIView
)。
枚举命名风格
[必须] 枚举名称的前缀应该和 typedef 的名称保持一致。
正例:
typedef NS_ENUM(NSInteger, MGradientChangeDirection) {
MGradientChangeDirectionLevel,
};
反例:
typedef NS_ENUM(NSInteger, MGradientChangeDirection) {
level,
};
方法命名风格
[必须] 方法名称的开头一般以小写字母开始,特殊单词除外(
例如HTTP、URL
)。[必须] 方法名称禁止直接使用_作为开始部分。
Tips:
由于系统的私有方法通常以_作为开始部分,这么做可以避免不小心覆盖系统私有方法。[必须] 私有方法必须添加前缀,前缀需要保持唯一性(
例如mp_
)。Tips:
给私有方法添加前缀有如下好处:
1. 提高辨识度,提高代码可读性。
2. 避免不小心覆盖系统或框架的私有方法。[必须] 如果方法返回某个属性值,那么请直接使用属性名作为方法名。
正例:
- (CGSize)cellSize;
反例:
- (CGSize)getCellSize;[必须] 方法的每个参数前必须添加有效关键字。
正例:
- (void)sendAction:(SEL)aSelector
toObject:(id)anObject
forAllCells:(BOOL)flag;
反例:
- (void)sendAction:(SEL)aSelector
:(id)anObject
:(BOOL)flag;[必须] 如果某个方法是由通知触发的,那么请使用
Notification
关键字作为名称后缀。正例:
appDidBecomeActiveNotification
反例:
appDidBecomeActive[建议] 尽量不要使用“and”连接接收者属性,尽管and读起来还算顺口,但随着你创建的方法参数的增加,这将会带来一系列的问题。
正例:
- (int)runModalForDirectory:(NSString *)path
file:(NSString *)name
types:(NSArray *)fileTypes;
反例:
- (int)runModalForDirectory:(NSString *)path
andFile:(NSString *)name
andTypes:(NSArray *)fileTypes;[建议] 如果方法描述了两个独立的动作,则可以使用
and
连接。正例:
- (BOOL)openFile:(NSString *)fullPath
withApplication:(NSString *)appName
andDeactivate:(BOOL)flag;[建议] 尽量不要使用
get
作为方法名称的开始部分,除非这个方法间接返回对象或值。正例:
- (UIColor *)backgroundColor;
- (void)getLineDash:(float *)pattern count:(int *)count phase:(float *)phase;
反例:
- (UIColor *)getBackgroundColor;
协议命名风格
[必须] 协议中的方法名以触发消息的对象名开头,省略类名前缀并首字母小写,如果它没有关联任何类则可以忽略这个规则。
正例:
- (BOOL)tableView:(NSTableView *)tableView
shouldSelectRow:(int)row;[必须] 除非协议方法只有一个参数,否则冒号需紧跟在类名后面。
正例:
1. - (BOOL)tableView:(NSTableView *)tableView
shouldSelectRow:(int)row;
2. - (BOOL)applicationOpenUntitledFile:(NSApplication *)sender;
通知命名风格
[必须] 一个完整的通知名称由 关联的类名/通用前缀+名称+Notification 3个部分组成。
正例:
UIApplicationDidBecomeActiveNotification
关联的类:UIApplication
通知名称:DidBecomeActive
固定后缀:Notification
常量命名风格
[必须] 如果常量局限于某个 编译单元(
通常指某个类的实现文件内
) 之内,通常在前面加小写字母k作为前缀,若常量在全局可见,通常以类名作为前缀,然后采用首字母大写驼峰命名方式。正例:
// 局部可见
const CGFloat kAnimationDuration = 2.0;
// 全局可见
const CGFloat UIActivityIndicatorViewAnimationDuration = 2.0;[必须] const如果修饰的是基本数据类型,则放在最左侧,如果修饰的是对象,则放在变量名前面。
正例:
const NSInteger age
NSString * const name
异常类命名风格
- [必须] 一个完整的异常类名称由 前缀+名称+Exception 3个部分组成。
文件命名风格
[必须] 文件名全部小写,使用_连接不同的模块,模块中的单词可以使用驼峰命名。
[必须] 文件名称由 所属模块+描述 2个部分组成。
正例:
public_back@2x.png
注释风格
[必须] 注释请遵守 Doxygen 风格。
一个好的例子:
/**
* @brief 关于这个方法的一个简短说明。
* @discussion 一段详细的描述。
* @warning 一些警告信息。
* @note 描述一些需要注意的事情。
* @param obj1 参数1的说明。
* @return 返回值的说明。
* @code
* 示例代码:
* id temp = [self testFunction:@"111"];
* @endcode
*
* @par 分割线,上面是一些常用的注释语法,下面是不常用的注释语法。
*
* @todo 即将要做的事情。
* @bug 可能存在的BUG,或者对缺陷的说明。
* @since 说明从什么版本、什么时间加入此代码。
* @exception 对可能存在的异常的解释。
* @pre 用来说明执行方法所需要的前提条件。
* @post 用来说明执行方法之后的使用条件。
* @author 作者名称。
* @remark 一些评论信息。
* @copyright 版权信息。
* @version 当前的工程版本。
* @par 一个新的名称
*
* 开始一个新段落。
*/
- (id)testFunction:(NSString *)obj1 {
return nil;
}效果图
[必须] 注释的作用是用于解释那些复杂不容易理解的逻辑,以及需要注意的地方,而不是告诉别人这个方法的作用,作用应该在名称中体现出来,提供一个合理的名称比使用晦涩的名称然后试图通过注释来解释它们要好的多。
[必须] 如果修改了实现细节,请记得修改注释。
[必须] 注释不要写的太冗长或太简短,这样都不利于别人快速理解。
[必须] 注释的双斜线和内容之间有且仅有一个空格。
正例:
// 这是示例注释,请注意在双斜线后有一个空格。[必须] 注释中的语句也需要添加适量的标点符号和空格帮助别人理解。
[必须] 对于代码注释需要谨慎,代码被注释一般有2种可能,1. 后续会恢复此段代码逻辑; 2. 永久不用;对于第1种情况需添加相应注释,如果没有注释信息难以知晓注释动机,后者建议直接删除。如果有需要可以通过代码仓库查阅历史代码。
[必须] 如果某个方法是有问题的,可以使用特殊注释来提醒别人和自己。
正例:
// MARK: - 方法集
// TODO: 等待实现
// FIXME: 有bug,需要修改
// !!!: 逻辑混乱,需要完善
// ???: 具体干什么用的?
- (void)testFunction;[必须] 行尾注解和代码保持2个空格,如果后续行有多个注释,将它们排列起来通常会更具可读性。
正例:
[self doSomethingWithALongName]; // Two spaces before the comment.
[self doSomethingShort]; // More spacing to align the comment.[建议] 别给糟糕的代码加注释,重构它。
Tips:
注释不能美化糟糕的代码。当企图使用注释前,先考虑是否可以通过调整结构,命名等操作,消除写注释的必要。[建议] 如果一个类比较复杂或者有需要注意的地方,那么请在声明它的地方加上注释帮助别人快速理解,如果有必要可以使用 Monodraw 工具绘制ASCII图形提高可读性,如下图所示。
monodraw示例图(这张图很清楚的向别人表达这个类的作用)
编码风格
[必须] 不要增加多余空格来使上下代码的等号对齐。
正例:
int a1 = 1;
long a2 = 3;
NSString *a3 = @"";
反例:
int a1 = 1;
long a2 = 3;
NSString *a3 = @"";[必须] 逗号(,)后面、二元运算符的左右应该添加1个空格,小括号左右不要有空格。
正例:
kColorRGB(255, 255, 255);
int a = 3 + 4;
反例:
kColorRGB(255,255,255)
int a = 3+4;[建议] 尽量使用 if return 代替 if else,if 嵌套最好不超过5层。
正例:
if (x == 1) {
……
return;
}
if (x == 2) {
……
return;
}
反例:
if (x == 1) {
……
} else if (x == 2) {
……
}[建议] 尽量避免采用取反逻辑运算符,因为取反逻辑不利于快速理解。
正例:
if (array == nil) {
……
}
反例:
if (!array) {
……
}
结构风格
[必须] 文件内部使用的常量、静态变量在@interface之前声明,如果没有@interface就在@implementation前声明。
[必须] 同一类型的属性声明放在一块显示,中间用一行空格区分,不同类型的声明用2行空格隔开,属性声明的开始和末尾都要添加一行空格。
正例:
@interface MineViewController ()
@property (nonatomic, weak) UIView *headView;
@property (nonatomic, weak) UITableView *tableView;
@property (nonatomic, copy) NSArray *dataSourceArray;
@end[必须] 不同作用的方法按照顺序进行排序,生命周期相关的方法 > 公开方法 > 私有方法 > 继承方法 > 通知方法 > 协议方法 > getter/setter方法,简单的说就是越重要越常用的方法越靠前。
#pragma mark - LifeCycle(生命周期相关的代码放在最上面)
- (void)dealloc {}
- (void)viewDidLoad {}
- (void)viewWillAppear:(BOOL)animated {}
#pragma mark - Public(公开方法)
// code...
// 下空两行
#pragma mark - Private(私有方法)
#pragma mark - Override(需要覆盖父类的方法)
#pragma mark - Notification(通知方法)
#pragma mark - Delegate(Delegate需要实现的方法)
#pragma mark - Getter/Setter[必须] 方法的声明顺序应该为类方法 > 初始化方法 > 实例方法。
[建议] 不同逻辑、不同语义、不同业务的代码之间插入一个空行分隔开以提升可读性。
正例:
[self createSubviews];
[self createTableview];
[self netRequest];[建议] 头文件包含顺序应该是系统头文件 > SDK头文件 > 其他依赖的头文件,用一个空行分隔逻辑上不同的头文件,在每个组中,包含的内容建议按首字母顺序排列。
正例:
#import "ProjectX/BazViewController.h"
#import <Foundation/Foundation.h>
#include <unistd.h>
#include <vector>
#include "base/basictypes.h"
#include "base/integral_types.h"
#include "util/math/mathutil.h"
#import "ProjectX/BazModel.h"
#import "Shared/Util/Foo.h"
一个好的头文件例子:
#import <UIKit/UIKit.h> |
- 使用 @class 向前声明
WXYZ_ADModel
而不是用#import
包含。 - @interface 之前有关于类的说明注释,帮助别人在使用这个类时快速了解这个类的作用以及需要注意的地方。
- 字典使用了泛型声明字典包含的类型,并且在注释中详细的说明了字典包含的内容。
- 属性和第一个方法之间有2个空格来区分它们没有关联性。
- 每个属性和返回值都有是否为空的状态,例如
+ createADViewWithType
方法使用nullable
表明了返回值可能为空的状态,并且在注释中说明了什么情况下会返回空对象。 - 遵守了类方法 > 初始化方法 > 实例方法的规则。
结语
- 可以转载,但是请注明来源。