Когда 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()));