面试被问到公司编码规范问题,感觉有很多东西,但是不知道该怎么说出来,今天突然找到 李明杰 老师的一份编码规范。重新又看一遍觉得受益良多。
关于命名的一般性的原则
- 最少字符,就是要尽量的减少命名对象的长度,尽量选择字符少的名词
- 名符其实,命名应该能直观的描述被命名对象是什么或者做什么
- 避免歧义,尽量不要采用多义词,也不要使用命名组合之后产生多义的方式
- 上下文一致,比如谓词的统一性,如果都是集合类,那么使用Remove表示删除操作,那么所有上下文就应该都用这个Remove,而不要再用Delete
- 少用缩写,除非是很常见的缩写或者项目中定义好的缩写,否则不要使用缩写
Xcode配置
- 由于空格缩进相比Tab缩进,所敲键盘次数过多,也没有Tab缩进直观,所以这里使用Tab缩进而非空格缩进,并且缩进宽度为2个字符。Xcode通过Preferences->Text Editing->Indentation,Prefer indent using选择Tabs,Tab width输入2,Indent width输入2来进行设置;
- 工程名及代码文件名
- 所有命名都只能采用英文字符,下划线,加号;
- 所有命名采用Pascal命名方式;
- 头文件的命名方式很重要,我们可以根据其命名知晓头文件的内容;
- 扩展类的文件命名,原类文件名+类别,类似GTMNSString+Parsing.h
文件扩展名
.h C/C++/Objective-C header file
.m Objective-C implementation file
.mm Objective-C++ implementation file
.cc Pure C++ implementation file
.c C implementation file
声明孤立的类或协议:
将孤立的类或协议声明放置在单独的头文件中,该头文件名称与类或协议同名。
声明相关联的类或协议:
将相关联的声明(类,类别及协议) 放置在一个头文件中,该头文件名称与主要的类/类别/协议的名字相同。
NsString.h NSString 和 NSMutableString 类
NsLock.h NSLocking 协议和 NSLock, NSConditionLock, NSRecursiveLock 类
为已有框架中的某个类扩展 API:
如果要在一个框架中声明属于另一个框架某个类的范畴类的方法,该头文件的命名形式为:原类名+“Additions”。如 Application Kit 中的 NSBundleAdditions.h。
相关联的函数与数据类型:将相联的函数,常量,结构体以及其他数据类型放置到一个头文件中,并以合适的名字命名。如 Application Kit 中的 NSGraphics.h。
工程中的group与本地文件夹要一一对应
类及协议
采用Pascal命名方式
- 类名应包含一个明确描述该类(或类的对象)是什么或做什么的名词。类名要有合适的前缀(请参考“前缀”一节)。Foundation 及 Application Kit 有很多这样例子,如:NSString, NSData, NSScanner, NSApplication, NSButton 以及 NSEvent;
- 协议应该根据对方法的行为分组方式来命名,大多数协议仅组合一组相关的方法,而不关联任何类,这种协议的命名应该使用动名词(ing),以不与类名混淆;
NsLocking 好的做法
NsLock 糟糕的做法,看起来像类名 - 有些协议组合一些彼此无关的方法(这样做是避免创建多个独立的小协议)。这样的协议倾向于与某个类关联在一起,该类是协议的主要体现者。在这种情形,我们约定协议的名称与该类同名。NSObject 协议就是这样一个例子。这个协议组合一组彼此无关的方法,有用于查询对象在其类层次中位置的方法,有使之能调用特殊方法的方法以及用于增减引用计数的方法。由于 NSObject 是这些方法的主要体现者,所以我们用类的名称命名这个协议;
采用Camel命名方式
- 避免歧义,如displayName,是返回一个displayName还是执行这个操作,会产生歧义,如果是执行这个操作,应该采用showName,避免采用可以是形容词的display
- 表示对象行为的方法,名称以动词开头,名称中不要出现 do 或 does,因为这些助动词没什么实际意义。也不要在动词前使用副词或形容词修饰
Objective-C
- (void) invokeWithTarget:(id)target:
- (void) selectTabViewItem:(NSTableViewItem *)tableViewItem
get属性方法不要带get前缀,也不要带其它前缀
Objective-C
- (NSSize) cellSize; 对
- (NSSize) calcCellSize; 错
- (NSSize) getCellSize; 错
参数要用描述该参数的标签命名
Objective-C
- (void) sendAction:(SEL)aSelector to:(id)anObject forAllCells:(BOOL)flag; 对
- (void) sendAction:(SEL)aSelector :(id)anObject :(BOOL)flag; 错
第一个参数,方法名要能描述该参数
Objective-C
- (id) viewWithTag:(int)aTag; 对
- (id) taggedView:(int)aTag; 错
不要使用 and 来连接用属性作参数标签,虽然上面的例子中使用 add 看起来也不错,但当你方法有太多参数关键字时就有问题。
Objective-C
- (int) runModalForDirectory:(NSString *)path file:(NSString *)name types:(NSArray *)fileTypes; 对
- (int) runModalForDirectory:(NSString *)path addFile:(NSString *)name addTypes:(NSArray *)fileTypes; 错
如果方法描述两种独立的行为,使用 and 来串接它们
Objective-C
- (BOOL) openFile:(NSString *)fullPath withApplication:(NSString *)appName andDeactivate:(BOOL)flag; NSWorkspace
方法的参数个数尽量保持四个以内,如果需要超过四个的参数,应该考虑重构方法或者抽象参数为类型
方法的实现需要进行参数有效性检查;
在方法声明中,在(-/+)符号与返回值之间留1个空格。此外,参数与前面的冒号不留空,方法段与之间留1个空格
方法间保持一个空行
方法内的空行用于区分不同的功能代码,但是如果方法中又太多的功能区块那么需要考虑重构代码
不要使用冒号对齐含实现块的方法,因为Xcode的缩进会使代码更难读
推荐:
Objective-C
// blocks are easily readable
[UIView animateWithDuration:1.0 animations:^{
// something
} completion:^(BOOL finished) {
// something
}];
不推荐:
Objective-C
// colon-aligning makes the block indentation hard to read
[UIView animateWithDuration:1.0
animations:^{
// something
}
completion:^(BOOL finished) {
// something
}];
访问方法
如果属性是用名词描述的,则命名格式为
Objective-C
- (void) setNoun:(type)aNoun;
- (type) noun;
例如:
Objective-C
- (void) setgColor:(NSColor *)aColor;
- (NSColor *) color;
如果属性是用形容词描述的,则命名格式为:
Objective-C
- (void) setAdjective:(BOOL)flag;
- (BOOL) isAdjective;
例如:
Objective-C
- (void) setEditable:(BOOL)flag;
- (BOOL) isEditable;
如果属性是用动词描述的,则命名格式为:(动词要用现在时时态)
Objective-C
- (void) setVerbObject:(BOOL)flag;
- (BOOL) verbObject;
例如:
Objective-C
- (void) setShowAlpha:(BOOL)flag;
- (BOOL) showsAlpha;
不要使用动词的过去分词形式作形容词使用
Objective-C
- (void) setAcceptsGlyphInfo:(BOOL)flag; 对
- (BOOL) acceptsGlyphInfo; 对
- (void) setGlyphInfoAccepted:(BOOL)flag; 错
- (BOOL) glyphInfoAccepted; 错
可以使用情态动词(can, should, will 等)来提高清晰性,但不要使用 do 或 does
Objective-C
- (void) setCanHide:(BOOL)flag; 对
- (BOOL) canHide; 对
- (void) setShouldCloseDocument:(BOOL)flag; 对
- (void) shouldCloseDocument; 对
- (void) setDoseAcceptGlyphInfo:(BOOL)flag; 错
- (BOOL) doseAcceptGlyphInfo; 错
方法调用时,如果参数太多,则遵照以下方式
Objective-C
[myObject doFooWith:arg1
name:arg2
error:arg3];
禁用以下方式
Objective-C
[myObject doFooWith:arg1 name:arg2 // some lines with >1 arg
error:arg3];
[myObject doFooWith:arg1
name:arg2 error:arg3];
[myObject doFooWith:arg1
name:arg2 // aligning keywords instead of colons
error:arg3];
如果无法使用冒号对齐,则如下缩进两个空格
Objective-C
[myObj short:arg1
longKeyword:arg2
evenLongerKeyword:arg3];
委托方法 委托方法是那些在特定事件发生时可被对象调用,并声明在对象的委托类中的方法。它们有独特的命名约定,这些命名约定同样也适用于对象的数据源方法。
名称以标示发送消息的对象的类名开头,省略类名的前缀并小写类第一个字符
Objective-C
- (BOOL) tableView:(NSTableView *)tableView shouldSelectRow:(int)row;
- (BOOL)application:(NSApplication *)sender openFile:(NSString *)filename;
冒号紧跟在类名之后(随后的那个参数表示委派的对象)。该规则不适用于只有一个 sender 参数的方法
Objective-C
- (BOOL) applicationOpenUntitledFile:(NSApplication *)sender;
上面的那条规则也不适用于响应通知的方法。在这种情况下,方法的唯一参数表示通知对象
Objective-C
- (void) windowDidChangeScreen:(NSNotification *)notification;
用于通知委托对象操作即将发生或已经发生的方法名中要使用 did 或 will
Objective-C
- (void) browserDidScroll:(NSBrowser *)sender;
- (NSUndoManager *) windowWillReturnUndoManager:(NSWindow *)window;
用于询问委托对象可否执行某操作的方法名中可使用 did 或 will,但最好使用 should
Objective-C
- (BOOL) windowShouldClose:(id)sender;
方法参数
不要在参数名中使用 pointer 或 ptr,让参数的类型来说明它是指针
避免使用 one, two,...,作为参数名,更不要用1,2,...
避免为节省几个字符而缩写
采用以下参数标签与参数组合的惯例
Objective-C
...action:(SEL)aSelector
...alignment:(int)mode
...atIndex:(int)index
...content:(NSRect)aRect
...doubleValue:(double)aDouble
...floatValue:(float)aFloat
...font:(NSFont *)fontObj
...frame:(NSRect)frameRect
...intValue:(int)anInt
...keyEquivalent:(NSString *0charCode
...length:(int)numBytes
...point:(NSPoint)aPoint
...stringValue:(NSString *)aString
...tag:(int)anInt
...target:(id)anObject
...title:(NSString *)aString
常量及枚举
- 避免使用k-style命名常量
- 通常不实用宏来定义常量,整数常量用枚举,浮点常量使用const
- 使用大写字母来定义预处理编译宏,类似的 #ifdef DEBUG
- 编译器定义的宏名收尾都有双下划线,如 _ MACH _
- 优先使用全局常量而非宏,应使用static方式声明常量
- 定义一般枚举使用NS_ENUM,命名方式为:前缀+Pascal命名,具体为什么可参考nshipster
Objective-C
typedef NS_ENUM(NSInteger, UITableViewCellStyle) {
UITableViewCellStyleDefault,
UITableViewCellStyleValue1,
UITableViewCellStyleValue2,
UITableViewCellStyleSubtitle
};
定义位与枚举使用NS_OPTION,命名方式为:前缀+Pascal命名+Mask
Objective-C
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
};
const常量命名方式:前缀+Pascal命名
属性和变量
- 数据成员保持最小公开原则
- 在需要公开一个数据成员时将其声明为属性,反之声明为实例变量
- 在不需要改变一个属性时,添加@readonly,需要时添加@readwrite
- 属性采用Camel命名方式
- 实例变量的命名方式同属性,不过首尾都加下划线,加尾部下划线可区分属性对应的实例变量
- 局部变量的命名方式同属性,禁用下划线
- 定义NSString时,如果不是明确的想要引用,则应该添加@copy,大多数情况下我们是把NSString当成值类型使用的
- @public,@protected,@private缩进一个空格
使用如下方式
Objective-C
view.backgroundColor = [UIColor orangeColor];
[UIApplication sharedApplication].delegate;
而不是
Objective-C
[view setBackgroundColor:[UIColor orangeColor]];
UIApplication.sharedApplication.delegate;
最好使用auto-synthesis。但是,如果有必要,@synthesize和@dynamic应各自在实现的新行定义
当使用属性,实例变量时应该使用self.来访问,这意味着所有的属性将很容易区分,因为它们都使用 self. 开头声明指针时星号应该靠近变量名
import和include
用#import 导入Objective-C或Objective-C++头文件,用#include 导入C或C++头文件;
导入框架根的头文件而不是分别导入框架头文件,
Objective-C
#import <Foundation/Foundation.h> // good
#import <Foundation/NSArray.h> // avoid
#import <Foundation/NSString.h>
...
前缀
前缀有规定的格式。它由两到三个大写字符组成,不能使用下划线与子前缀
前缀 Cocoa 框架
NS Foundation
NS Application Kit
AB Address Book
IB Interface Builder
命名 class,protocol,structure,函数,常量时使用前缀;
命名成员方法时不使用前缀,因为方法已经在它所在类的命名空间中;
同理,命名结构体字段时也不使用前缀
Blocks VS Selectors VS Protocols
- Protocol做为只作为接口使用,类似C#和Java中的interface,剥离老版本Objective-C中使用Protocol作为委托实现方式的功能。
- 不建议在protocol中使用@optional,因为如果只将protocol作为接口使用,那么抽象好了,接口自然不需要@optional
- block使得代码能更好的整合,尽可能的去使用block
- 有了block,除了编译期无法确定需要使用NSSelectorFromString之外,可能真的不需要Selector了。
- protocol对象声明使用weak,禁止使用retain。因为retain会导致循环索引导致内存泄露
- 回调的最佳方式是使用代理模式,而不是使用Notification;
- block语法
Objective-C
returnType (^blockName)(parameterTypes) = ^returnType(parameters) {...};
@property (nonatomic, copy) returnType (^blockName)(parameterTypes);
- (void)someMethodThatTakesABlock:(returnType (^)(parameterTypes))blockName;
[someObject someMethodThatTakesABlock:^returnType (parameters) {...}];
typedef returnType (^TypeName)(parameterTypes);
TypeName blockName = ^returnType(parameters) {...};
NSException
同样的如C++中一样,强烈不建议使用异常
如果非要使用,代码格式如下
Objective-C
@try {
foo();
}
@catch (NSException *ex) {
bar(ex);
}
@finally {
baz();
}
异常由具有如下形式的全局 NSString 对象标识: [Prefix] + UniquePartOfName + Exception UniquePartOfName 部分是有连续的首字符大写的单词组成。例如:
NSColorListIOException NSColorListNotEditableException NSDraggingException NSFontUnavailableException NSIllegalSelectorException
初始化函数
- 返回值必须为instancetype,不要再使用id了
- 避免多次调用父类的初始化函数
- 使用通用的、约定俗成的alloc和init的方式创建实例,而不是使用new方法
- 在创建NSString,NSDictionary,NSArray和NSNumber等对象实例时,应使用Literals字面量。需要注意的是,不应将nil传给NSArray和NSDictionary字面量,否则会引起程序崩溃,使用
Objective-C
NSArray *names = @[@"Brian", @"Matt", @"Chris", @"Alex", @"Steve"];
NSDictionary *productManagers = @{@"iPhone" : @"Kate", @"iPad" : @"Kamal"};
NSNumber *shouldUseLiterals = @YES;
NSNumber *buildingZIPCode = @10018;
而不是
Objective-C
NSArray *names = [NSArray arrayWithObjects:@"Brian", @"Matt", @"Chris", @"Alex", @"Steve", nil];
NSDictionary *productManagers = [NSDictionary dictionaryWithObjectsAndKeys:@"Kate", @"iPhone", @"Kamal", @"iPad", nil];
NSNumber *shouldUseLiterals = [NSNumber numberWithBool:YES];
NSNumber *buildingZIPCode = [NSNumber numberWithInteger:10018];
在初始化函数中,访问数据成员应该直接访问实例变量而不是属性
pragma
- 使用#pragma将同一个protocol的方法归类
- 使用#pragma将同一个父类的重写方法归类
- 使用#pragma将一组相关联的方法归类
Objective-C
@implementation ViewController
- (instancetype)init {
...
}
#pragma mark - UIViewController
- (void)viewDidLoad {
...
}
#pragma mark - IBAction
- (IBAction)cancel:(id)sender {
...
}
#pragma mark - UITableViewDataSource
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
...
}
#pragma mark - UITableViewDelegate
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
...
}
nil / Nil / NULL / NSNull
Objective-C中默认所有的指针指向nil
nil最显著的行为是,它虽然为零,仍然可以有消息发送给它,结果返回0
Objective-C
// 举个例子,这个表达...
if (name != nil && [name isEqualToString:@"Steve"]) { ... }
// …可以被简化为:
if ([name isEqualToString:@"steve"]) { ... }
不需要显示初始化为nil,更不要初始化为其它几个关键词
需要知道如下
Symbol Value Meaning
NULL (void *)0 literal null value for C pointers
nil (id)0 literal null value for Objective-C objects
Nil (Class)0 literal null value for Objective-C classes
NSNull [NSNull null] singleton object used to represent null
注释
- 不推荐处处都是注释,不推荐写大块注释,好的代码应该自解释。
- 关键的逻辑、跳转、判断,以及复杂的算法,非常有必要使用注释。
- 多个注释应尽量左对齐。
- 所有注释需要保持更新,不需要的注释需要删除
- 安装VVDocument,使用其格式对方法进行注释
- 在switch块中,如果一个case不添加break,请明确添加注释
表达式格式
方法大括号和其它大括号(比如for、if、while、switch等等)应在语句的同一行开始,而在新的一行关闭;
Objective-C
if (user.isHappy) {
// Do something
} else {
// Do something else
}
不要省略掉花括号,因为即使当前只有一行代码,可能后续其它人会添加代码,如果遗忘补上花括号会产生非预期结果
Objective-C
// good
if (!error) {
return success;
}
// bad
if (!error)
return success;
// bad
if (!error) return success;
- 单目运算符与操作数间不要有空格
- 多目运算符与操作数保持一个空格
- 三目运算符不要嵌套
- 强制类型转换、参数之间不要有空格
Booleans
- Objective-C用BOOL来编码真值。它是signed char的typedef,并且用宏YES和NO来相应的表示真和假。
- 在Objective-C中,当遇到处理真值的参数,属性和实例变量时,使用类型BOOL。当分配字面值时,使用宏YES和NO。
- 如果BOOL属性的名称表示为一个形容词,该属性可以省略“is”字头,但需要为get方法按照常规指定名称
Objective-C
@property (assign, getter=isEditable) BOOL editable;
判断真假时不要与字面值比较
Objective-C
// good
if (someObject) {
}
if (![anotherObject boolValue]) {
}
// bad
if (someObject == nil) {}
if ([anotherObject boolValue] == NO) {}
if (isAwesome == YES) {} // Never do this.
if (isAwesome == true) {} // Never do this.
Objective-C中的所有真值类型和数值:
Name Typedef Header True Value False Value
BOOL signed char objc.h YES NO
bool _Bool (int) stdbool.h true false
boolean unsigned char MacTypes.h TRUE FALSE
NSNumber __NSCFBoolean Foundation.h @(YES) @(NO)
CFBooleanRef struct CoreFoundation.h kCFBooleanTrue kCFBooleanFalse
文档结构管理
- 建立Libraries文件夹,放所有第三方库。
- 建立Utilities文件夹,放自己封装的类。
- 建立Constants.h 文件,专门放常量定义和一些全局的枚举。
- 每个功能块放一个Group(实际的文件夹),其中按MVC分成Model,View,Controller子文件夹来管理。
- 程序资源文件放入Supporting Files 文件夹,可在其下建立 图片,音频,视频等子文件夹,分别管理不同种类的资源,若有多个功能共用的,可以建立Common文件夹。
网友评论