软件编码要遵循以下原则:
1.遵循开发流程,在设计的指导下进行代码编写。
2.代码的编写以实现设计的功能和性能为目标,要求正确完成设计要求的功能,达到设计的性能。
3.程序具有良好的程序结构,提高程序的封装性好,减低程序的耦合程度。
4.程序可读性强,易于理解;方便调试和测试,可测试性好。
5.易于使用和维护;良好的修改性、扩充性;可重用性强/移植性好。
6.占用资源少,以低代价完成任务。
7.在不降低程序的可读性的情况下,尽量提高代码的执行效率。
本规范的描述主要以Objective-C语言为例
一 命名
1.排版约定
(a) 名称有多个单词组成时,不要使用特殊字符(标点符号,下划线,破折号等等)作为名称一部分或者分隔符。
(b) 在项目中变量和方法等,都采用“驼峰”命名法。例如: runTheWordsTogether
特殊情况是首单词是广为人知的缩写。如: TIFFRepresentation(NSImage)
(c) 对于函数名和常量名,首字母大写并使用和相关联的类相同的前缀。如:
UIGraphicsGetCurrentContext() //函数
NSCellDisabled //常量
(d) 避免给方法名加下划线前缀来表示为私有方法(给实例变量加下划线前缀来表示私有变量是允许的)。苹果保留了此约定的使用。
(e) 类名、协议名
类名应当包含能清晰指出该类的功能的名词。
▪ 类名,类别名和协议名的首字母大写,使用首字母大写的形式分割单词
▪ 在面向特定应用的代码中,类名应尽量避免使用前缀,每个类都使用相同的前缀影响可读性。
▪ 协议头文件里的协议名不要和类名混淆。如:
LoginDelegate //协议名,加上Delegate
Login //不好。看上去像一个类名。
(f) 方法名
▪ 方法的参数采用“驼峰”命名法
▪ 表示操作对象的方法,用动词开始命名。如:
- (void)invokeWithTarget:(id)target;
▪ 多个参数时,每个参数使用参数标签
- (void)sendAction:(SEL)aSelector
to:(id)anObject
forAllCells:(BOOL)flag 正确
- (void)sendAction:(SEL)aSelector :(id)anObject :(BOOL)flag 错误
▪ 方法名称要清晰的表达出方法的功能。如:
- (id)viewWithTag:(NSInteger)aTag;正确
- (id)taggedView:(int)aTag;错误
▪ 当创建一个比继承的方法参数更多的方法时,在继承的方法后边添
加新的关键字。如:
- (id)initWithFrame:(CGRect)frameRect; //UIView,NSView
- (id)initWithFrame:(CGRect)frameRect
mode:(int)aMode
numberOfRows:(int)rowsHigh// NSMatrix, a subclass
numberOfColumns:(int)colsWide; // of NSView
▪ 不要使用“and”来连接那些作为receiver属性的关键字。
- (int)runModalForDirectory:(NSString *)path
file:(NSString *)name
types:(NSArray *)fileTypes; 正确;
- (int)runModalForDirectory:(NSString *)path
andFile:(NSString *)name
andTypes:(NSArray *)fileTypes; 错误
▪ 方法描述了两个分离的动作,用“and”连接它们;
-(BOOL)opneFile:(NSString *)fulPath
withApplication:(NSString *)appName
andDeactiovation:(BOOL)flag;
▪ 方法名应简短清晰,但不能因简短牺牲清晰
insertObject: atIndex: //好
insert:at: //不好,不够清晰;
removeObjectAtIndex://好
removeObject: //好,因为它移除参数提及的对象;
remove: //不清晰,要移除什么?
▪ 方法名应尽量全拼,即使单词很长。因为你写的缩写不一定是广为人知的,因为开发者有着各自的文化和语言背景。
destinationSelection://好
destSel: //不好
setBackgroundColor; //好
setBkgdColor: //不好
当然,少量的缩写是通常所见和使用了很长历史的可以排除在外。
例如:alloc,alt, app,calc, dealloc, func, horiz, info, init, int, max, min, msg, nib, pboard, rect, Rep, temp, vert等。
还有计算机工业常用的,
如:ASCII, PDF, XML, HTML, URL, RTF, HTTP, TIFF, JPG, PNG, FIG,
LZW, ROM, RGB, CMYK, MIDI, FTP等。
▪ API方法名避免有歧义,即能别理解一种以上的意思。如:
sendPort: //不好,是发送端口号还是要返回一个端口号?
▪ 如果不需要重写getter方法时,getter的方法名和变量名应相 同。不允许使用“get”前缀。如:
- (id) getDelegate; // 禁止
- (id)delegate;// 对头
重写getter时可以使用,例如:
@property(getter=getFontSize)NSInteger fontSize;
▪ 访问器方法根据属性名称:
如果属性为名词,格式为
- (type)noun;
- (void)setNoun:(type)aNoun;
如果属性为形容词,格式为
- (BOOL)isAdjective;
- (void)setAdjective:(BOOL)flag;
如果属性为动词,格式为
- (BOOL)verbObject;
- (void)setVerbObject:(BOOL)flag;
不要把动词用分词的形式转成形容词,例如:
- (void)setAcceptsGlyphInfo:(BOOL)flag; 正确;
- (void)setGlyphInfoAccepted:(BOOL)flag; 错误
可以使用情态动词(can, should, will等)明确意思,但不能用do,does。
只有方法间接返回对象和值的时候使用“get”。只有在多个条目需要被返回的时候才能使用如下命名:
- (void)getLineDash:(float*)pattern
count:(int*)count phase:(float*)phase;
▪ 不同类里做相同功能的方法应该有相同的方法名称,即一致性。
▪ 前缀有规定的格式:包含两到三个大写字母,不能使用下划线或者 “sub prefixes”.标准如:NS,AB,IB等。在面向多应用的代码 中,推荐使用前缀。如:GTMSendMessage命名classes,protocols, functions,constants,typedef structures时使用前缀。命名 methods时不要使用前缀;方法是存在于定义它们 的类所创建的 namespace里。也不要在命名结构体字段时使用前缀。
▪ 如果你是一个大cocoa框架的子类,想绝对确定你的私有方法有一 个不同于其他子类的名字,你可以添加一个自己的前缀给私有方法。 这个前缀尽可能独一的,基于你的公司或工程的缩写格式 “XX_method”.
▪ 本规则仅针对Objective-C代码,C++代码使用C++的编码习惯。
2. 变量名
▪ 变量名应使用容易意会的应用全称,命名使用“驼峰”表示法。
二 格式化代码
1. 指针”*”号的位置,“*”前加空格,紧跟参数名
如: UITextField *mTextField;
2. 每行的长度
每行的长度尽量在100个字符之内,保持美观大方。
3. 方法的声明和定义
(a) 在“-”或者“+”和返回值之间留1个空格,方法名和第一个参数间不留。如:
- (void)doSomethingWithString:(NSString *)theString{
//dosomething
}
(b) 当参数过长时,每个参数占用一行,以冒号对齐。如:
- (void)doSomethingWith:(GTMFoo *)theFoo
rect:(NSRect)theRect
interval:(float)theInterval {
// dosomething
}
(c) 如果方法名比参数名短,每个参数占用一行,至少缩进4个字符,且为垂直 对齐(而非使用冒号对齐)。
如:
- (void)short:(GTMFoo *)theFoo
longKeyword:(NSRect)theRect
evenLongerKeyword:(float)theInterval {
// dosomething
}
5.方法的调用
(a) 调用方法沿用声明方法的习惯。例外:如果给定源文件已经遵从某种习惯,继续遵从那种习惯。
(b) 所有参数应在同一行中,或者每个参数占用一行且使用冒号对齐。如:
[myObject doFooWith:arg1 name:arg2 error:arg3];
或[myObject doFooWith:arg1
name:arg2
error:arg3];
(c) 和方法的声明一样,如果无法使用冒号对齐时,每个参数一行、缩进4个字符、
垂直对其(而非使用冒号对齐)。
如:
[myObj short:arg1
longKeyword:arg2
evenLongerKeyword:arg3];
(d) @public 和@private
▪ @public 和@private使用单独一行,且缩进1个字符
(e) Protocals
▪ 类型标示符、代理名称、尖括号间不留空格。
▪ 该规则同样适用于:类声明、实例变量和方法声明。如:
@interfaceMyProtocoledClass : NSObject<NSWindowDelegate> {
@private
id<MyFancyDelegate> delegate;
}
- (void)setDelegate:(id<MyFancyDelegate>)delegate;
@end
三 Cocoa 和Objective-C特有的规则
1.成员变量
◦成员变量使用@private。如:
@interfaceMyClass : NSObject {
@private
id myInstanceVariable;
}
// public accessors, setter takes ownership
- (id)myInstanceVariable;
- (void)setMyInstanceVariable:(id)theVar;
@end
2. Indentify Designated Initializer
一个精心设计的初始化方法应当完成以下步骤来确保适当的检测和误差传播。
▪ 通过调用super的指定初始器重新分配self;
▪ 检测返回值和nil对比,这可以指出一些发生在父类的初始器里的错误。
▪如果初始化当前类时发生了一个错误,释放掉该类对象并返回nil。
如下所示:
- (id)init{
self = [super init]; //Call a designated initializer here;
if (self != nil){
// Initialize object ...
if (someError){
[self release];
self = nil;
}
}
return self;
}
1. 初始化
▪ 在初始化方法中,不要将变量初始化为“0”或“nil”,那是多余的
▪ 内存中所有的新创建的对象(isa除外)都是0,所以不需要重复初始化为 “0”或“nil”,避免显式的调用+new方法
▪ 禁止直接调用NSObject的类方法+new,也不要在子类中重载它。使用alloc和 init方法
4. #import VS #include
▪ 使用#import引入Ojbective-C和Ojbective-C++头文件,使用,#include引入C和C++头文件
▪ import根框架(root frameworks),而非各单个文件
▪ 虽然有时我们仅需要框架(如Cocoa 或Foundation)的某几个头文件,但引入根文件编译器会运行的更快。因为根框架(rootframeworks),一般会预编译,所以加载会更快。再次强调:使用#import 而非#include 来引入Objective-C框架。如:
#import <Foundation/NSArray.h> // 禁止
#import<Foundation/NSString.h>
...
#import <Foundation/Foundation.h> //正确
5. 创建对象时autorelease 与release 的使用
▪ 一般情况下不用的对象需要release,不建议使用autorelease,在release的时候要严格按照内存管理的方法进行。在以下两种情况下需要使用autorelease :
(a) 方法返回指针类型的对象时需要用到autorelease
- (NSMutableArray *)getBookmarkPreset{
//return object should autorelease
NSMutableArray *bookMarkList = [[[NSMutableArray alloc] init] autorelease];
//do something
return bookMarkList;
}
(b) 方法的逻辑判断比较复杂,有可能提前返回的情况下,建议使用autorelease,虽然这样会稍微有点慢,但这样可以阻止因为提前return 或其他意外情况导致的内存泄露。例如:
MyController* controller = [[[MyController alloc] init] autorelease];
// ... 这里的代码可能会提前return ... [controller release];
6. 先release,再retain
▪ 在为对象赋值时,遵从“先release,再retain”
▪ 在将一个新创建的对象赋给变量时,要先将旧对象release掉,否则会内 存泄露。如:
- (void)setFoo:(GMFoo *)aFoo {
[foo_ release];
foo_ = [aFoo retain];
}
▪ dealloc的顺序要与变量声明的顺序相同,这有利于review代码
▪ NSString的属性的setter使用“copy” ,禁止使用retain,以防止意外的修改了NSString变量的值。如:
- (void)setFoo:(NSString *)aFoo {
[foo_ autorelease];
foo_ = [aFoo copy];
}
或
@property (nonatomic, copy) NSString *aString;
7. 对nil 的检查
▪ 仅在有业务逻辑需求时检查nil,而非为了防止崩溃
8. BOOL陷阱
▪ 将int值转换为BOOL时应特别小心。避免直接和YES比较
▪ Objective-C中,BOOL被定义为unsigned char,这意味着除了YES(1) 和 NO(0)外它还可以是其他值。禁止将int直接转换为BOOL。
▪ 常见的错误包括:将数组的大小、指针值或位运算符的结果转换为 BOOL,因为该BOOL值的结果取决于整型值的最后一 位
▪ 将整型值转换为BOOL的方法:使用三元运算符返回YES / NO,或使用 位运算符(&&,||,!)
▪ BOOL、_Bool和bool之间的转换是安全的,但是BOOL和 Boolean间的转换不是安全的,所以将Boolean看成整型值。
▪ 在Objective-C中,只允许使用BOOL,如:
// 禁止
- (BOOL)isBold{
return [self fontTraits] &NSFontBoldTrait; //&得出来的值是int不是Bool,避免出现返回int。
}
- (BOOL)isValid {
return [self stringValue];
}
// 对头
- (BOOL)isBold {
return ([self fontTraits] &NSFontBoldTrait) ? YES : NO;
}
- (BOOL)isValid {
return [self stringValue] != nil;
}
- (BOOL)isEnabled {
return [self isValid] && [self isBold];
}
▪ 禁止直接将BOOL和YES/NO比较,如:
// 禁止
BOOL great = [foo isGreat];
if (great == YES)
...
// 对头
BOOL great = [foo isGreat];
if (great)
9. 属性
通过使用声明property和synthesize组合来避免显式声明公共实例变 量。如果要显式声明一个实例变量,要么@private或者@protected。如果该 类派生子类,子类需要访问该类数据,使用@protected。
▪ 位置:属性的声明紧随成员变量块之后,中间空一行,无缩进。
▪ 严把权限:对不需要外部修改的属性使用readonly
▪ NSString使用copy而非retain。
▪ 一般使用nonatomic,除非特殊情况下使用atomic ,例如多线程。
四Cocoa Pattern(模式)
1. Delegate Pattern(委托模式)
▪ delegate对象使用assign,禁止使用retain。因为retain会导致循环索引导 致内存泄露, 并且此类型的内存泄露无法被Instrument发现,极难调试
2. Model/View/Controller
▪ Model和View分离。
▪ 不要在与view相关的类中添加过多的业务逻辑代码,这让代码的可重用性很差 。
▪ Controller负责业务逻辑代码,且Controller的代码与view尽量无关
▪ 使用@protocal定义回调APIs,如果并非所有方法都是必须的,可选使用 @optional标示,必选使用 @required标识.
五 其他
1. init方法和dealloc
init和dealloc方法是是最常用的方法,所以将他们放在类实现的开始位置
(a) 使用空格将相同的变量、属性对齐,使用换行分组
(b) 在设定视图frame时,会随屏幕大小变化的视图使用controller.view.frame 的相对数值。例如:
//MyViewController.m
-(void)viewDidLoad{
[superviewDidLoad];
// Do something ...
UIScrollView *mScrollView = [[UIScrollViewalloc] initWithFrame: CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height)];
[self.viewaddSubview:mScrollView];
}
2. 多view视图排版
有多个view同时在一个view上时,其之间的间距用变量来设定;
//MyViewController.m
-(void)viewDidLoad{
[superviewDidLoad];
int i = 0;
UILabel *l1 = [[UILabelalloc ]initWithFrame:CGRectMake(0, i, 100, 30)];
[self.view addSubview:l1];
[l1 release];
i+=50;
UILabel *l2 = [[UILabelalloc ]initWithFrame:CGRectMake(0, i, 100, 30)];
[self.view addSubview:l2];
[l2 release];
i+=60;
UILabel *l3 = [[UILabelalloc ]initWithFrame:CGRectMake(0, i, 100, 30)];
[self.view addSubview:l3];
[l3 release];
// Do something ...
}