我是DRY和KISS原则的忠实追随者,但上周我有一个案例,两者似乎互相矛盾:
对于我正在做的应用程序,我必须实现一个循环,执行以下操作:
这是一个例子:
for (A a : listOfA) {
listOfB.add(BFactory.convertFromAToB(a));
}
在代码中,我必须做大约4次,将类型(例如D,E等)转换为另一种类型。我可能无法更改我要转换的类型,因为它们是我们必须在app中使用的第三方类型。
所以我们有:
for (A a : listOfA) {
listOfB.add(BFactory.convertFromAToB(a));
}
for (C a : listOfC) {
listOfB.add(DFactory.convertFromCToD(c));
}
...
所以,为了不违反干,我提出了一个通用的解决方案:
private interface Function<S, T> {
T apply(S s);
}
public <S, T> void convertAndCopy(List<S> src, List<T> dst, Function<S, T> f) {
for (S s : src) {
dst.add(f.apply(s));
}
}
电话看起来像这样:
convertAndCopy(listOfA, listOfB, new Function<A, B>() {
A apply(B b) {
return CFactory.convertFromBToC(b);
}
});
现在,虽然这在DRY方面更好,但我认为它违反了KISS,因为这个解决方案比重复的for循环更难理解。
那么,这是DRY vs. KISS吗?在这种背景下哪一个受到青睐?
编辑
为了清楚起见,我正在讨论的类是一个适配器,它将调用遗留系统委托给我们自己的实现,并将遗产转换为我们自己的类型。我无法更改遗留类型,也无法更改我们的类型(XML-Schema生成)。
要么没事。
对于循环,你并没有真正重复自己,因为唯一重复的部分是“语法混乱”(在你的情况下并不是太多)。您没有重复/复制“应用程序逻辑”代码。
如果您喜欢“函数”样式,可以使用Guava库(它具有Function接口和许多在集合上使用它们的辅助方法)。那是DRY(因为你不重复自己,并重新使用已经存在的代码),仍然是KISS(因为那些是很好理解的模式)。
如果您只需要在整个应用程序中执行此操作4次,并且转换实际上与示例一样简单,我会选择在通用解决方案上随时编写4 for循环。
可读性因使用该通用解决方案而受到很大影响,而您实际上并没有从中获得任何东西。
像DRY和KISS这样的一般原则从来都不会起作用。
IMO,答案是忘记教条(至少对于这个问题),并考虑什么为您提供最好/最可读的解决方案。
如果复制的x 4代码更容易理解并且不是维护负担(即您不需要对其进行大量更改),那么这是正确的解决方案。
(Thilo的答案也是对的...... IMO)
我认为不是KISS和DRY相互矛盾。我宁愿说Java不允许你表达简单而不重复自己。
首先,如果你引入正确命名的方法,从List<A>
转换为List<B>
等等,而不是一直重复循环,它仍然是DRY,同时仍然保持KISS。
但我建议的是看看替代语言,这些语言可以让你充分利用DRY,同时仍然可以推广KISS,例如:在斯卡拉:
val listOfB = listOfA map convertAtoB
val listOfC = listOfB map convertBtoC
val listOfD = listOfC map convertCtoD
其中convertAtoB
是一个采用A类项目并返回B的函数:
def convertAtoB(a: A): B = //...
或者你甚至可以链接这些map
电话。
您可以将转换函数移动到CFactory中:
convertAndCopy(listOfA, listOfB, CFactory.getConverterFromAToB());
代码以这种方式可读/简单,您可以促进代码重用(可能稍后需要在另一个上下文中使用转换器对象)。
实施:
public <S, T> void convertAndCopy(List<A> listofA, List<B> listOfB, Function<A, B> f) {
listOfB.addAll(Collections2.transform(listOfA,f));
}
(使用番石榴迭代器)。
我甚至不确定你应该在这里干,你可以直接使用:
listOfB.addAll(Collections2.transform(listOfA,CFactory.getConverterFromAToB()));