Key Takeaways(划重点):
- require对外、check对内,组成了协议的前置条件
- assert是协议的后置条件
接触过Design by contract或OCL(Object Constraint Language)或平时设计比较严谨的同学应该知道,一个良好的接口设计/文档其实是应该包括了接口的前置条件(即满足什么条件才可以调用这个接口)和后置条件的(执行完毕这个接口后,哪些是真)。在2000年初期MDA (Model-driven Architecture)还比较红火的时候,很多模型师、架构师都会在接口中追加此类定义。这个追加行为,除了本身有利于源代码的输出(是的,当时MDA的口号其实就是以后不需要码农,只要模型师的),确实可以让接口的定义更完整、清晰,显得专业味十足。
现在MDA虽然不怎么再提到,但其科普的前置/后置条件还是一定程度帮助了软件业的完善。Kotlin作为一个比较现代的语言,在汲取了多类语言和设计概念后,很多原先其他语言需要特定实现(或重复发明轮子)的事情,在Kotlin的标准库就自带了,譬如require / check / assert 对于前置/后置条件的支持。
先看下三者的定义:
-
require(Boolean) throw
IllegalArgumentException
-
check(Boolean) throw
IllegalStateException
-
assert(Boolean) throw
AssertionError
其实对应着看到各自的输出,应该能猜测到一些东西。譬如
-
IllegalArgumentException
: 传入的参数有问题 -
IllegalStateException
:自身状态不对 -
AssertionError
:和预估的不一样 (在后置条件的维基百科中其实就是这么定义的)Postconditions are sometimes tested using assertions within the code itself
所以总结下来,大概就是这么回事了:
- require负责检查输入的参数,如果有问题,抛出
IllegalArgumentException
- check负责检查自身是否万事俱备可以执行了,如果不是,抛出
IllegalStateException
- require + check就是在做前置条件的检查,通过了才可以执行真正的程序逻辑
- assert负责确保程序执行完毕后的结果/内部状态是否符合预期,如果不是,抛出
AssertionError
一个完整应用了这几个检查的代码大概如下(一个方法用于单次执行指定的sql语句,每次执行连接数据库并在执行完毕后释放连接(老土的demo,没有连接池-_-)):
fun execute(sql: String) : Unit {
// 输入参数的检查
require(!sql.isNullOrBlank()) {
"被执行的sql语句不能为空"
}
// 自身状态检查
check(!this.host.isNullOrBlank()) {
"sql server未指定"
}
/*
* conn = ...
* conn.execute(sql)
* conn.disconnect()
*/
// 执行完毕后状态检查
assert(!conn.isConnected) {
"每次执行完毕后都需要释放连接"
}
}
上面的require和check的顺序,没有一定的谁先谁后,这个纯粹看个人风格/习惯。不过如果涉及到某些执行/检查比较费资源时,还是让不费资源的优先执行为上。
Kotlin标准库的这几个函数,虽小却清晰的用代码来定义了契约,讲究协作的今天,还是挺需要的。
希望这篇博文能对你有所帮助,喜欢的话点个赞吧!
更多Kotlin的实用技巧,请参考《Kotlin边用边学系列》