我很多时候都参与API的设计/实现,我面临着这个难题.
我是information hiding的坚定支持者,并尝试使用各种技术,包括但不限于内部类,私有方法,程序包私有限定符等.
这些技术的问题在于它们倾向于防止良好的可测试性.尽管可以解决其中的某些技术(例如,通过将一个类放入相同的程序包来实现程序包私有性),但其他一些则为not so easy to tackle,并且需要反射魔术或其他技巧.
让我们看一个具体的例子:
public class Foo {
SomeType attr1;
SomeType attr2;
SomeType attr3;
public void someMethod() {
// calculate x, y and z
SomethingThatExpectsMyInterface something = ...;
something.submit(new InnerFoo(x, y, z));
}
private class InnerFoo implements MyInterface {
private final SomeType arg1;
private final SomeType arg2;
private final SomeType arg3;
InnerFoo(SomeType arg1, SomeType arg2, SomeType arg3) {
this.arg1 = arg1;
this.arg2 = arg2;
this.arg3 = arg3;
}
@Override
private void methodOfMyInterface() {
//has access to attr1, attr2, attr3, arg1, arg2, arg3
}
}
}
有充分的理由不公开InnerFoo-没有其他类,库也不能访问它,因为它没有定义任何公共合同,并且作者故意不希望它可供访问.但是,要使其成为100%TDD犹太洁食且无任何反射技巧就可以访问,应将InnerFoo重构如下:
private class OuterFoo implements MyInterface {
private final SomeType arg1;
private final SomeType arg2;
private final SomeType arg3;
private final SomeType attr1;
private final SomeType attr2;
private final SomeType attr3;
OuterFoo(SomeType arg1, SomeType arg2, SomeType arg3, SomeType attr1, SomeType attr2, SomeType attr3) {
this.arg1 = arg1;
this.arg2 = arg2;
this.arg3 = arg3;
this.attr1 = attr1;
this.attr2 = attr2;
this.attr3 = attr3;
}
@Override
private void methodOfMyInterface() {
//can be unit tested without reflection magic
}
}
该示例仅涉及3个attrs,但是拥有5-6个参数是非常合理的,然后OuterFoo构造函数将必须接受8-10个参数!在顶部添加getter,您已经有100行完全没有用的代码(也需要getter来获取这些attrs进行测试).是的,我可以通过提供一种构建器模式来改善这种情况,但是我认为这不仅过度设计,而且还违反了TDD本身的目的!
解决此问题的另一种方法是公开Foo类的受保护方法,在FooTest中对其进行扩展并获取所需的数据.同样,我认为这也是一种不好的方法,因为受保护的方法的确定义了合同,并且通过公开该合同,我现在已经对其进行隐式签名.
不要误会我的意思.我喜欢编写可测试的代码.我喜欢简洁,简洁的API,短代码块,可读性等.但是,我不喜欢在信息隐藏方面做出任何牺牲,因为这更容易进行单元测试.
有人能对此提出任何想法吗(总体而言,特别是特别如此)?对于给定的示例,还有其他更好的解决方案吗?