防御式编程:理解断言

理论部分摘自代码大全

在防御式驾驶中要建立这样一种思维,那就是你永远也不能确定另一位司机将要做什么。这样才能确保在其他人做出危险动作时你也不会受到危害。你要承担起保护自己的责任,哪怕是其他司机犯的错误。防御式编程的主要思想是:子程序应该不因传入错误数据而被破坏,哪怕是由其他子程序产生的错误数据。

断言

断言是指在开发期间使用的、让程序在运行时进行自检的代码。断言为真,则表明程序运行正常;断言为假,则意味着它已经在代码中发现意料之外的错误。
断言可以用于在代码中说明各种假定,澄清各种不希望的情形。
断言主要是用于开发和维护阶段。通常,断言只是在开发阶段被编译到目标代码中。在开发阶段,断言可以查清相互矛盾的假定、预料之外的情况以及传给子程序的错误数据等。

iOS中的断言语法

在OC中,断言使用NSAssert函数,是一个宏。基本形式是两个参数,第一个参数是一个bool值:断言的结果,第二个参数是一个描述字符串。断言的结果为false时,第二个参数描述字符串会输出。


防御式编程:理解断言_第1张图片

这里举例一段BlocksKit中的代码:

- (instancetype)initWithBlock:(id)block
{
    NSParameterAssert(block);
    NSMethodSignature *blockSignature = [[self class] typeSignatureForBlock:block];
    NSMethodSignature *methodSignature = [[self class] methodSignatureForBlockSignature:blockSignature];
    NSAssert(methodSignature, @"Incompatible block: %@", block);
    return (self = [self initWithBlock:block methodSignature:methodSignature blockSignature:blockSignature]);
}

断言通常用来判断外部输入的参数是否正确,所以cocoa封装了一个检查参数的断言,与NSAssert相比方便的地方就是自动定义了一个输出字符串。
NSParameterAssert是一个封装了NSAssert的宏,定义如下:


#define NSParameterAssert(condition) NSAssert((condition), @"Invalid parameter not satisfying: %@", @#condition)

在来看上面代码中这句断言的使用:

    NSAssert(methodSignature, @"Incompatible block: %@", block);

这段代码的意图是用传入的block来代替某个对应delegate的方法,所以要检查一下传入的block和这个delegate上对应的方法签名是否一致。如果不一致则中断程序,抛出“Incompatible block”提示信息。

在swift中,断言用assert函数。两个参数和OC中的相同。

public func assert(@autoclosure condition: () -> Bool, @autoclosure _ message: () -> String = default, file: StaticString = #file, line: UInt = #line)

通过声明可以发现,断言中会记录当前文件和行号,但是这里赋了默认值,所以使用assert时可以省略。
这里顺带提下代码规范,有些人有时为了方便会把几句代码写在一行。这样写一个潜在的坏处就是如果发生异常,日志中记录了行号。但是因为这一行有几句代码,增加了判断是由具体哪一句代码产生异常。应该避免将几句代码写在一行里。

断言还有一个经常使用的地方是单元测试。在单元测试中,XCode中封装了几个在测试中经常使用的断言。

这里列举AFN单元测试中用到了XCTAssert的代码,只截取部分带断言代码:

    //常规的断言
    XCTAssert([NSStringFromClass([task class]) isEqualToString:@"__NSCFBackgroundDownloadTask"]);

    
    XCTAssertTrue([[[serializedRequest URL] query] isEqualToString:@"key=value"], @"Query parameters have not been serialized correctly (%@)", [[serializedRequest URL] query]);
    XCTAssertFalse([policy evaluateServerTrust:trust forDomain:nil], @"Policy should not allow server trust because invalid certificaftes are not allowed");

    //为nil时是正确的
    XCTAssertNil(error, @"Error handling status code %@", @(statusCode));
    //不为nil时是正确的
    XCTAssertNotNil(error, @"Did not fail handling status code %@",@(statusCode));

    //前两个表达式结果相等时正确
    XCTAssertEqual(reachable, weakManager.isReachable, @"Expected status to match 'isReachable'");

XCT(Xcode Test)中还有很多类似的断言,有兴趣可以自己查文档,这里就不列举了。

断言使用的原则

用错误处理代码来处理预期会发生的状况,用断言来处理绝不应该发生的状况

错误处理代码用来检查不太可能经常发生的非正常情况,这些情况在写代码时就预料到的,而且在产品代码中也要处理这些情况。而断言是用于检查代码中的bug,如果在发生异常的时候触发了断言,采取的措施就不仅仅是对错误做出恰当的反应,而是应该修改源码并重新编译。可以把断言理解成一种注释,它说明了这个程序的假定。

避免把执行的代码放到断言中

断言的可以在编译器中设置关闭,如果你把一些操作写在断言里,在某些情况下可能编译器会过滤掉这些代码。

    NSAssert([self performAction], @"could't perform);

这样写就很危险,应该这样写:

    Bool performed=[self performAction];
    NSAssert(performed, @"could't perform);

高健壮性的代码,先使用断言再处理错误

对于要求高健壮性的代码,可能项目非常庞大,超长的开发周期和很多的开发人员,也可能出现断言被触发但是没有被注意到,这时应该也处理一下触发断言时的错误。

欢迎关注我的微博:@没故事的卓同学

你可能感兴趣的:(防御式编程:理解断言)