Kotlin边用边学:利用 require / check / assert 完善接口定义

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边用边学系列》

你可能感兴趣的:(Kotlin边用边学:利用 require / check / assert 完善接口定义)