禅与 Objective-C 编程艺术
Posted iOS开发by唐巧
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了禅与 Objective-C 编程艺术相关的知识,希望对你有一定的参考价值。
前言
这是一本开源的电子书,最近经过林翔宇、庞博两位译者翻译成了中文。我读完后,深感每一个ios开发都应该学习一下本文。文章采用了CC共享协议,所以我转载部分内容到这里,希望大家有时间都去把完整的原文读一遍。
以下是摘抄的部分内容
条件语句
为了避免错误,条件语句体应该总是被大括号包围,即使可以不这样做(比如,条件语句体只有一行内容)。可能的错误是:多加了第二行,并且误以为它是 if 语句体里面的。此外,更危险的是,如果把 if 语句体里的一行注释掉了,之后的一行代码会成为 if 语句里的代码。
推荐:
if (!error) {
return success;
}
不推荐:
if (!error)
return success;
或者
if (!error) return success;
在 2014年2月 苹果的 SSL/TLS 实现里面发现了知名的 错误。
代码在这里:
static OSStatus
SSLVerifySignedServerKeyExchange(SSLContext *ctx, bool isRsa, SSLBuffer signedParams,
uint8_t *signature, UInt16 signatureLen)
{
OSStatus err;
...
if ((err = SSLHashSHA1.update(&hashCtx, &serverRandom)) != 0)
goto fail;
if ((err = SSLHashSHA1.update(&hashCtx, &signedParams)) != 0)
goto fail;
goto fail;
if ((err = SSLHashSHA1.final(&hashCtx, &hashOut)) != 0)
goto fail;
...
fail:
SSLFreeBuffer(&signedHashes);
SSLFreeBuffer(&hashCtx);
return err;
}
显而易见,这里有没有括号包围的2行连续的 goto fail;
。我们当然不希望写出上面的代码导致错误。
此外,在其他条件语句里面也应该按照这种风格统一,这样更便于检查。
尤达表达式
不要使用有尤达表达式。尤达表达式是指,拿一个常量去和变量比较而不是拿变量去和常量比较。它就像是在表达 “蓝色是不是天空的颜色” 或者 “高个是不是这个男人的属性” 而不是 “天空是不是蓝的” 或者 “这个男人是不是高个子的”
(译者注:名字起源于星球大战中尤达大师的讲话方式,总是用倒装的语序)
推荐:
if ([myValue isEqual:@42]) { ...
不推荐:
if ([@42 isEqual:myValue]) { ...
nil 和 BOOL 检查
类似于 Yoda 表达式,nil 检查的方式也是存在争议的。一些 notous 库像这样检查对象是否为 nil:
if (nil == myValue) { ...
或许有人会提出这是错的,因为在 nil 作为一个常量的情况下,这样做就像 Yoda 表达式了。 但是一些程序员这么做的原因是为了避免调试的困难,看下面的代码:
if (myValue == nil) { ...
如果程序员敲错成这样:
if (myValue = nil) { ...
这是合法的语句,但是即使你是一个丰富经验的程序员,即使盯着眼睛瞧上好多遍也很难调试出错误。但是如果把 nil 放在左边,因为它不能被赋值,所以就不会发生这样的错误。 如果程序员这样做,他/她就可以轻松检查出可能的原因,比一遍遍检查敲下的代码要好很多。
为了避免这些奇怪的问题,可以用感叹号来作为运算符。因为 nil 是 解释到 NO,所以没必要在条件语句里面把它和其他值比较。同时,不要直接把它和 YES
比较,因为 YES
的定义是 1, 而 BOOL
是 8 bit的,实际上是 char 类型。
推荐:
if (someObject) { ...
if (![someObject boolValue]) { ...
if (!someObject) { ...
不推荐:
if (someObject == YES) { ... // Wrong
if (myRawValue == YES) { ... // Never do this.
if ([someObject boolValue] == NO) { ...
同时这样也能提高一致性,以及提升可读性。
黄金大道
当编写条件语句的时候,左边的代码间距应该是一个“黄金”或者“快乐”的大道。 这是说,不要嵌套 if
语句。多个 return 语句是 OK 的。这样可以避免 Cyclomatic 复杂性,并且让代码更加容易阅读。因为你的方法的重要部分没有嵌套在分支上,你可以很清楚地找到相关的代码。
推荐:
- (void)someMethod {
if (![someOther boolValue]) {
return;
}
//Do something important
}
不推荐:
- (void)someMethod {
if ([someOther boolValue]) {
//Do something important
}
}
复杂的表达式
当你有一个复杂的 if 子句的时候,你应该把它们提取出来赋给一个 BOOL 变量,这样可以让逻辑更清楚,而且让每个子句的意义体现出来。
BOOL nameContainsSwift = [sessionName containsString:@"Swift"];
BOOL isCurrentYear = [sessionDateCompontents year] == 2014;
BOOL isSwiftSession = nameContainsSwift && isCurrentYear;
if (isSwiftSession) {
// Do something very cool
}
三元运算符
三元运算符 ? 应该只用在它能让代码更加清楚的地方。 一个条件语句的所有的变量应该是已经被求值了的。类似 if 语句,计算多个条件子句通常会让语句更加难以理解。或者可以把它们重构到实例变量里面。
推荐:
result = a > b ? x : y;
不推荐:
result = a > b ? x = c > d ? c : d : y;
当三元运算符的第二个参数(if 分支)返回和条件语句中已经检查的对象一样的对象的时候,下面的表达方式更灵巧:
推荐:
result = object ? : [self createObject];
不推荐:
result = object ? object : [self createObject];
错误处理
当方法返回一个错误参数的引用的时候,检查返回值,而不是错误的变量。
推荐:
NSError *error = nil;
if (![self trySomethingWithError:&error]) {
// Handle Error
}
此外,一些苹果的 API 在成功的情况下会对 error 参数(如果它非 NULL)写入垃圾值(garbage values),所以如果检查 error 的值可能导致错误 (甚至崩溃)。
Case语句
除非编译器强制要求,括号在 case 语句里面是不必要的。但是当一个 case 包含了多行语句的时候,需要加上括号。
switch (condition) {
case 1:
// ...
break;
case 2: {
// ...
// Multi-line example using braces
break;
}
case 3:
// ...
break;
default:
// ...
break;
}
有时候可以使用 fall-through 在不同的 case 里面执行同一段代码。一个 fall-through 是指移除 case 语句的 “break” 然后让下面的 case 继续执行。
switch (condition) {
case 1:
case 2:
// code executed for values 1 and 2
break;
default:
// ...
break;
}
当在 switch 语句里面使用一个可枚举的变量的时候,default
是不必要的。比如:
switch (menuType) {
case ZOCEnumNone:
// ...
break;
case ZOCEnumValue1:
// ...
break;
case ZOCEnumValue2:
// ...
break;
}
此外,为了避免使用默认的 case,如果新的值加入到 enum,程序员会马上收到一个 warning 通知
Enumeration value 'ZOCEnumValue3' not handled in switch.(枚举类型 'ZOCEnumValue3' 没有被 switch 处理)
枚举类型
当使用 enum
的时候,建议使用新的固定的基础类型定义,因它有更强大的的类型检查和代码补全。 SDK 现在有一个 宏来鼓励和促进使用固定类型定义 - NS_ENUM()
例子:
typedef NS_ENUM(NSUInteger, ZOCMachineState) {
ZOCMachineStateNone,
ZOCMachineStateIdle,
ZOCMachineStateRunning,
ZOCMachineStatePaused
};
命名
通用的约定
尽可能遵守 Apple 的命名约定,尤其是和 () 相关的地方。
推荐使用长的、描述性的方法和变量名
推荐:
UIButton *settingsButton;
不推荐:
UIButton *setBut;
常量
常量应该使用驼峰命名法,并且为了清楚,应该用相关的类名作为前缀。
推荐:
static const NSTimeInterval ZOCSignInViewControllerFadeOutAnimationDuration = 0.4;
不推荐:
static const NSTimeInterval fadeOutTime = 0.4;
常量应该尽量使用 in-line 的字符串字面值或者数字,这样便于经常用到的时候复用,并且可以快速修改而避免查找和替换。 常量应该用 static
声明,不要使用 #define
,除非它就是明确作为一个宏来用的。
推荐:
static NSString * const ZOCCacheControllerDidClearCacheNotification = @"ZOCCacheControllerDidClearCacheNotification";
static const CGFloat ZOCImageThumbnailHeight = 50.0f;
不推荐:
#define CompanyName @"Apple Inc."
#define magicNumber 42
常量应该在 interface 文件中这样被声明:
extern NSString *const ZOCCacheControllerDidClearCacheNotification;
并且应该在实现文件中实现它的定义。
你只需要为公开的常量添加命名空间前缀。即使私有常量在实现文件中可能以不同的模式使用,你也不需要坚持这个规则了。
方法
对于方法签名,在方法类型 (-
/+
符号)后应该要有一个空格。方法段之间也应该有一个空格(来符合 Apple 的规范)。在参数名称之前总是应该有一个描述性的关键词。
使用“and”命名的时候应当更加谨慎。它不应该用作阐明有多个参数,比如下面的initWithWidth:height:
例子:
推荐:
- (void)setExampleText:(NSString *)text image:(UIImage *)image;
- (void)sendAction:(SEL)aSelector to:(id)anObject forAllCells:(BOOL)flag;
- (id)viewWithTag:(NSInteger)tag;
- (instancetype)initWithWidth:(CGFloat)width height:(CGFloat)height;
不推荐:
- (void)setT:(NSString *)text i:(UIImage *)image;
- (void)sendAction:(SEL)aSelector :(id)anObject :(BOOL)flag;
- (id)taggedView:(NSInteger)tag;
- (instancetype)initWithWidth:(CGFloat)width andHeight:(CGFloat)height;
- (instancetype)initWith:(int)width and:(int)height; // Never do this.
点击“阅读原文”,查看完整的原文。
以上是关于禅与 Objective-C 编程艺术的主要内容,如果未能解决你的问题,请参考以下文章