Когда KISS и DRY сталкиваются
Я одержимый приверженец принципов DRY и KISS, но на прошлой неделе у меня было случай, когда оба кажутся противоречащими друг другу:
Для приложения, которое я делал, мне пришлось реализовать цикл для времени, который делает следующее:
- итерация по элементам списка типа A
- преобразуйте элемент типа A в тип B и вставьте его в список типа B
Вот пример:
for (A a : listOfA) {
listOfB.add(BFactory.convertFromAToB(a));
}
Внутри кода я должен сделать это примерно 4 раза, преобразовать тип (например, D, E и т.д.) в другой. Я не могу изменить типы, которые я собираюсь преобразовать, поскольку они являются сторонними типами, которые мы должны использовать в приложении.
Итак, мы имеем:
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);
}
});
Теперь, хотя это лучше с точки зрения СУХОЙ, я думаю, что это нарушает KISS, поскольку это решение гораздо труднее понять, чем дублирование для циклов.
Итак, это DRY против KISS? Какая из них выгодна в этом контексте?
ИЗМЕНИТЬ
Просто, чтобы быть ясным, класс, о котором я говорю, является адаптером, который делегирует вызов устаревшей системе для нашей собственной реализации, превращая наследие в наши собственные типы на этом пути. У меня нет средств для изменения устаревших типов, и я не могу изменить наши типы (которые генерируются XML-Schema).
Ответы
Ответ 1
Либо отлично.
С помощью циклов вы на самом деле не повторяетесь, потому что единственными частями, которые повторяются, является "синтаксический беспорядок" (и не слишком много этого в вашем случае). Вы не повторяете/дублируете код "логики приложения".
Если вам нравится стиль "Function", возможно, используйте библиотеку Guava (которая имеет интерфейс Function и многие вспомогательные методы, которые работают с ними в коллекциях). Это DRY (потому что вы не повторяете себя и повторно используете уже существующий код), и все еще KISS (потому что это хорошо понятые шаблоны).
Ответ 2
Если вам нужно сделать это всего 4 раза в своем приложении, и преобразование действительно так же тривиально, как и ваши примеры, я бы выбрал запись 4 для циклов в любое время по родовому решению.
Чтение сильно страдает от использования этого общего решения, и вы на самом деле ничего не получаете от него.
Ответ 3
Общие принципы, такие как DRY и KISS, никогда не работают постоянно.
ИМО, ответ - забыть догму (по крайней мере, для этой проблемы) и подумать о том, что дает вам лучшее/наиболее читаемое решение.
Если дублированный код х 4 легче понять и он не является бременем для обслуживания (т.е. Вам не нужно много его менять), то это правильное решение.
(И ответ Тило тоже прав... ИМО)
Ответ 4
Я думаю, что это не значит, что KISS и DRY противоречат друг другу. Я бы сказал, что Java не позволяет вам выразить простоту, не повторяя себя.
Прежде всего, если вы вводите правильно названные методы для преобразования из List<A>
в List<B>
и т.д. вместо того, чтобы повторять цикл все время, это было бы СУХОЙ, оставаясь еще KISS.
Но я бы посоветовал посмотреть на альтернативные языки, которые позволят вам полностью использовать DRY, пока продвигают KISS, например. в Scala:
val listOfB = listOfA map convertAtoB
val listOfC = listOfB map convertBtoC
val listOfD = listOfC map convertCtoD
Где convertAtoB
- это функция, берущая элемент типа A и возвращающий B:
def convertAtoB(a: A): B = //...
Или вы можете даже объединить эти вызовы map
.
Ответ 5
Вы можете переместить функцию преобразования в 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()));