Google代码健康:让接口难被误用
本文源于Google公司的马桶上的健康代码,作者: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)
。
具有万无一失的接口并不总是可行的。在某些情况下,必须依靠静态分析或文档,因为某些要求无法在接口中表示出来(例如,回调函数必须是线程安全的)。
不要执行无需执行的操作-避免使用过度防御的代码。例如,对功能参数的广泛验证会增加复杂性并降低性能。
- 版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 3.0 许可协议。转载请注明出处!