When KISS and DRY collide [closed]
Want to improve this question? Update the question so it's on-topic for Stack Overflow.
Closed 10 years ago.
Improve this questionI'm an obsessive follower of the DRY and KISS principles but last week I had a case where both seem to contradict each other:
For an application I was doing, I had to implement a loop for times which does the following:
- iterate over the elements of a list of type A
- convert the element of type A to type B and insert them into a list of type B
Here's an example:
for (A a : listOfA) {
listOfB.add(BFactory.convertFromAToB(a));
}
Within the code, I have to do this about 4 times, convert a type (e.g. D, E etc.) into another one. I may not be able to change the types I'm about to convert, as they are 3rd party types which we have to use in out app.
So we have:
for (A a : listOfA) {
listOfB.add(BFactory.convertFromAToB(a));
}
for (C a : listOfC) {
listOfB.add(DFactory.convertFromCToD(c));
}
...
So, to not violate dry, I came up with a generic solution:
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));
}
}
A call looks something like this:
convertAndCopy(listOfA, listOfB, new Function<A, B>() {
A apply(B b) {
return CFactory.convertFromBToC(b);
}
});
Now, while this is better in terms of DRY, I think it vio开发者_运维问答lates KISS, as this solution is much harder to understand than the duplicated for loops.
So, is this DRY vs. KISS? Which one to favor in this context?
EDIT
Just to be clear, the class I'm talking about is an Adapter, which delegates call to a legacy system to our own implementation, converting the legacy into our own types along the way. I have no means of changing the legacy types, nor may I change our types (which are XML-Schema-generated).
Either is fine.
With the loops, you are not really repeating yourself, because the only parts that are repetitive is "syntactic clutter" (and not too much of that in your case). You are not repeating/duplicating "application logic" code.
If you like the "Function" style, maybe make use of the Guava library (which has the Function interface and many helper methods that work with them on collections). That is DRY (because you don't repeat yourself, and re-use code that already exists), and still KISS (because those are well understood patterns).
If you only have to do this 4 times in your whole application, and the conversion is really as trivial as your examples, I would choose writing 4 for loops any time over the generic solution.
Readability suffers a lot from using that generic solution and you don't actually gain anything from it.
General principles like DRY and KISS never work all of the time.
IMO, the answer is to forget the dogma (at least for this problem), and think about what gives you the best / most readable solution.
If the duplicated x 4 code is easier to understand and it is not a maintenance burden (i.e. you don't need to change it a lot), then it is the right solution.
(And Thilo's answer is right too ... IMO)
I think it is not that KISS and DRY contradict each other. I would rather say that Java does not allow you to express simplicity while not repeating yourself.
First of all if you introduce properly named methods to convert from List<A>
to List<B>
and so on instead of repeating the loop all the time it would be DRY while still remaining KISS.
But what I would advice is to look at alternate languages that allow you to take full odvantage of DRY while still promote KISS, e.g. in Scala:
val listOfB = listOfA map convertAtoB
val listOfC = listOfB map convertBtoC
val listOfD = listOfC map convertCtoD
Where convertAtoB
is a function taking an item of type A and returning B:
def convertAtoB(a: A): B = //...
Or you can even chain these map
calls.
You could move the conversion function into CFactory:
convertAndCopy(listOfA, listOfB, CFactory.getConverterFromAToB());
The code is quite readable/simple this way and you promote code reuse (maybe you will need to use the converter object later in another context).
Implementation :
public <S, T> void convertAndCopy(List<A> listofA, List<B> listOfB, Function<A, B> f) {
listOfB.addAll(Collections2.transform(listOfA,f));
}
(using guava Iterators).
I'm not even sure that you should DRY here, you could use directly:
listOfB.addAll(Collections2.transform(listOfA,CFactory.getConverterFromAToB()));
精彩评论