原文链接:https://testing.googleblog.com/2018/07/code-health-make-interfaces-hard-to.html
原文标题:Code Health: Make Interfaces Hard to Misuse
发表时间:美国时间 2018-07-25
发布渠道:Google Testing Blog
原文作者:Marek Kiszkis
我们不希望在自己提交的代码里面出现错误,但是如果是接口调用者出现的错误呢?一个好的接口设计应当是使调用者很容易调用成功并且不容易犯错。不要将维护类所需的不变量的责任推给调用方。
找找这段代码可能引起的问题
class Vector {
explicit Vector(int num_slots); // Creates an empty vector with `num_slots` slots.
int RemainingSlots() const; // Returns the number of currently remaining slots.
void AddSlots(int num_slots); // Adds `num_slots` more slots to the vector.
// Adds a new element at the end of the vector. Caller must ensure that RemainingSlots()
// returns at least 1 before calling this, otherwise caller should call AddSlots().
void Insert(int value);
}
如果调用方忘记调用AddSlots() 方法,在调用Insert() 的时候可能触发未定义的行为。接口把复杂性推到调用方身上,让调用方暴露给实现细节。
由于维护插槽和类的调用方可见行为无关,所以不要把他们暴露在接口中;根据需要在Insert() 中增加插槽,使调用方不会触发未定义行为。
class Vector {
explicit Vector(int num_slots);
// Adds a new element at the end of the vector. If necessary,
// allocates new slots to ensure that there is enough storage
// for the new value.
void Insert(int value);
}
代码在编译器强制执行的约定比运行检查时强制执行的约定通常要好的多,最糟糕的是只在文档中,需要调用方自觉遵循的约定。
有些体现接口会被误用的例子:
- 要求调用者调用一个初始化函数(替代方式:公开返回完全初始化的对象的工厂方法)。
- 要求调用者进行自定义清理(替代方式:使用特定于语言的构造,以确保在对象超出范围时自动清理))。
- 允许创建不需要参数的对象的代码路径(例如:无ID用户)。
- 只允许参数的一些值是有效的,特别是如果可以使用一个更合适的类型(如倾向Duration timeout,而不是int timeout_in_millis)。
有一个十全十美的接口不太现实,在某些情况下一些需求不会在接口中实现,依赖静态分析或者文档还是必要的。(例如,一个回调函数必须是线程安全的)。
不需要强制执行的代码就不要执行了,避免过于防御性的代码。例如,函数参数的广泛验证会增加复杂性并降低性能。