Программирование для будущего использования интерфейсов

42

Рядом со мной сидит коллега, который разработал такой интерфейс:

public interface IEventGetter {

    public List<FooType> getFooList(String fooName, Date start, Date end)
        throws Exception;
    ....

}

Проблема в том, что сейчас мы не используем этот «конечный» параметр где-либо в нашем коде, он просто есть, потому что нам, возможно, придется использовать его когда-нибудь в будущем.

Мы пытаемся убедить его, что плохая идея - помещать параметры в интерфейсы, которые сейчас бесполезны, но он продолжает настаивать на том, что придется проделать большую работу, если мы когда-нибудь реализуем использование даты окончания позже и потом придется адаптировать весь код.

Теперь мой вопрос: есть ли источники, которые занимаются темой «уважаемых» программистов, с которой мы можем связать его?

Свери
источник
29
«Предсказание очень сложно, особенно в отношении будущего».
Jörg W Mittag
10
Часть проблемы в том, что это не самый лучший интерфейс для начала. Получение Foos и их фильтрация - это две разные проблемы. Этот интерфейс заставляет вас фильтровать список одним конкретным способом; гораздо более общий подход заключается в передаче функции, которая решает, следует ли включать foo. Тем не менее, это элегантно, только если у вас есть доступ к Java 8.
Doval
5
Чтобы противостоять это точка. Просто заставьте этот метод принимать пользовательский тип, который пока содержит только то, что вам нужно, и может в конечном итоге добавить endпараметр к этому объекту и даже установить его по умолчанию, чтобы не нарушать код
Rémi
3
При использовании методов Java 8 по умолчанию нет причин добавлять ненужные аргументы. Метод может быть добавлен позже с реализацией по умолчанию , что простые вызовы Определенная с, скажем, с null. Реализующие классы могут затем переопределять при необходимости.
Борис Паук
1
@Doval Я не знаю, что такое Java, но в .NET вы иногда будете сталкиваться с такими вещами, как недопущение выявления причуд IQueryable(может принимать только определенные выражения) кода вне DAL
Бен Ааронсон,

Ответы:

62

Пригласите его узнать о ЯГНИ . Обоснование части страницы Википедии может быть особенно интересным здесь:

По мнению тех, кто защищает подход YAGNI, соблазн писать код, который не нужен в данный момент, но может возникнуть в будущем, имеет следующие недостатки:

  • Время, затраченное на добавление, тестирование или улучшение необходимой функциональности.
  • Новые функции должны быть отлажены, задокументированы и поддерживаться.
  • Любая новая функция налагает ограничения на то, что может быть сделано в будущем, поэтому ненужная функция может помешать добавлению необходимых функций в будущем.
  • Пока эта функция действительно не нужна, сложно полностью определить, что она должна делать, и протестировать ее. Если новая функция не определена и не протестирована должным образом, она может работать некорректно, даже если в конечном итоге она потребуется.
  • Это приводит к раздуванию кода; программное обеспечение становится больше и сложнее.
  • Если нет спецификаций и какого-либо контроля версий, эта функция может быть неизвестна программистам, которые могут ее использовать.
  • Добавление новой функции может предложить другие новые функции. Если эти новые функции также будут реализованы, это может привести к эффекту снежного кома к ползучести объектов.

Другие возможные аргументы:

  • «80% стоимости срока службы программного обеспечения уходит на техническое обслуживание» . Написание кода как раз вовремя , снижает затраты на техническое обслуживание: один должен поддерживать меньше кода, и может сосредоточиться на коде на самом деле необходимо.

  • Исходный код пишется один раз, но читается десятки раз. Дополнительный аргумент, нигде не используемый, приведет к потере времени на понимание, почему существует аргумент, который не нужен. Учитывая, что это интерфейс с несколькими возможными реализациями, все усложняется.

  • Ожидается, что исходный код самодокументируется. Фактическая подпись вводит в заблуждение, поскольку читатель может подумать, что это endвлияет либо на результат, либо на выполнение метода.

  • Люди, пишущие конкретные реализации этого интерфейса, могут не понимать, что не следует использовать последний аргумент, что приведет к различным подходам:

    1. Мне не нужно end, поэтому я просто проигнорирую его значение,

    2. Мне не нужно end, поэтому я скину исключение, если это не так null,

    3. Мне не нужно end, но я постараюсь как-то им воспользоваться,

    4. Я напишу много кода, который можно будет использовать позже, когда endэто будет необходимо.

Но знайте, что ваш коллега может быть прав.

Все предыдущие пункты основаны на том факте, что рефакторинг прост, поэтому добавление аргумента позже не потребует больших усилий. Но это интерфейс, и как интерфейс он может использоваться несколькими командами, участвующими в разработке других частей вашего продукта. Это означает, что изменение интерфейса может быть особенно болезненным, и в этом случае YAGNI здесь не применяется.

Ответ от hjk дает хорошее решение: добавить метод к уже используемому интерфейсу не особенно сложно, но в некоторых случаях он также имеет существенную стоимость:

  • Некоторые фреймворки не поддерживают перегрузки. Например, и если я хорошо помню (поправьте меня, если я ошибаюсь), WCF .NET не поддерживает перегрузки.

  • Если интерфейс имеет много конкретных реализаций, добавление метода к интерфейсу потребует прохождения всех реализаций и добавления метода там же.

Арсений Мурзенко
источник
4
Я думаю, что также важно учитывать вероятность появления этой функции в будущем. Если вы точно знаете, что он будет добавлен, но не сейчас, по какой-либо причине, я бы сказал, что это хороший аргумент, чтобы иметь в виду, поскольку это не просто функция «на всякий случай».
Йерун Ванневел
3
@JeroenVannevel: конечно, но это очень умозрительно. Исходя из моего опыта, большинство функций, которые, как мне казалось, будут реализованы, были либо отменены, либо отложены навсегда. Это включает в себя функции, которые изначально были выделены заинтересованными сторонами как очень важные.
Арсений Мурзенко
26

но он продолжает настаивать на том, что предстоит проделать большую работу, если мы реализуем использование «конечной» даты через некоторое время и затем придется адаптировать весь код.

(немного позже)

public class EventGetter implements IEventGetter {

    private static final Date IMPLIED_END_DATE_ASOF_20140711 = new Date(Long.MAX_VALUE); // ???

    @Override
    public List<FooType> getFooList(String fooName, Date start) throws Exception {
        return getFooList(fooName, start, IMPLIED_END_DATE_ASOF_20140711);
    }

    @Override
    public List<FooType> getFooList(String fooName, Date start, Date end) throws Exception {
        // Final implementation goes here
    }
}

Это все, что вам нужно, метод перегрузки. Дополнительный метод в будущем может быть введен прозрачно, не затрагивая вызовы существующего метода.

ХИК
источник
6
За исключением того, что ваше изменение означает, что он больше не является интерфейсом и его невозможно будет использовать в тех случаях, когда объект уже является производным от объекта и реализует интерфейс. Это тривиальная проблема, если все классы, которые реализуют интерфейс, находятся в одном проекте (преследуйте ошибки компиляции). Если это библиотека, используемая несколькими проектами, добавление поля вызывает замешательство и работу для других людей в отдельные моменты времени в течение следующих x месяцев / лет.
Кьевели
1
Если команда OP (за исключением коллеги) действительно считает, что этот endпараметр сейчас не нужен, и продолжает использовать endметод -less во всех проектах, то обновление интерфейса - это наименьшее их беспокойство, поскольку становится необходимостью обновления классы реализации. Мой ответ нацелен конкретно на часть «Адаптировать весь код», потому что похоже, что коллега думал о том, чтобы обновлять вызывающие методы исходного метода во всех проектах, чтобы ввести третий параметр по умолчанию / фиктивный ,
HJK
18

[Есть] «уважаемые» гуру кодирования, с которыми мы можем связать его [чтобы убедить его]?

Обращения к власти не особенно убедительны; лучше представить аргумент, который действителен независимо от того, кто это сказал.

Вот сокращение до абсурда, которое должно убедить или продемонстрировать, что ваш коллега застрял на том, что он «прав» независимо от чувствительности:


Что вам действительно нужно, так это

getFooList(String fooName, Date start, Date end, Date middle, 
           Date one_third, JulianDate start, JulianDate end,
           KlingonDate start, KlingonDate end)

Вы никогда не знаете, когда вам придется интернационализироваться для клингона, поэтому вам лучше позаботиться об этом сейчас, потому что для его модернизации потребуется много работы, а клингоны не известны своим терпением.

MSW
источник
22
You never know when you'll have to internationalize for Klingon, Вот почему вы должны просто передать a Calendarи позволить клиентскому коду решить, хочет ли он отправить a GregorianCalendarили a KlingonCalendar(держу пари, некоторые уже сделали). Duh. :-p
SJuan76
3
Что о StarDate sdStartи StarDate sdEnd? Мы не можем забыть о Федерации в конце концов ...
WernerCD
Все они, очевидно, являются подклассами или могут быть преобразованы в Date!
Даркхогг
Если бы это было так просто .
Msw
@msw, я не уверен, что он просил «обратиться к власти», когда он попросил ссылки на «уважаемых гуру кодирования». Как вы говорите, ему нужен «аргумент, который действителен независимо от того, кто это сказал». Люди типа "гуру", как правило, знают много таких. Также вы забыли HebrewDate startи HebrewDate end.
Trysis
12

С точки зрения разработки программного обеспечения, я считаю, что правильное решение для такого рода проблем заключается в модели построения. Это определенно ссылка авторов «гуру» для вашего коллеги http://en.wikipedia.org/wiki/Builder_pattern .

В шаблоне компоновщика пользователь создает объект, который содержит параметры. Этот контейнер параметров затем будет передан в метод. Это позаботится о любом расширении и перегрузке параметров, которые понадобятся вашему коллеге в будущем, в то же время сделав все это очень стабильным, когда необходимо внести изменения.

Вашим примером станет:

public interface IEventGetter {
    public List<FooType> getFooList(ISearchFooRequest req) {
        throws Exception;
    ....
    }
}

public interface ISearchFooRequest {
        public String getName();
        public Date getStart();
        public Date getEnd();
        public int getOffset();
        ...
    }
}

public class SearchFooRequest implements ISearchFooRequest {

    public static SearchFooRequest buildDefaultRequest(String name, Date start) {
        ...
    }

    public String getName() {...}
    public Date getStart() {...}
    ...
    public void setEnd(Date end) {...}
    public void setOffset(int offset) {...}
    ...
}
InformedA
источник
3
Это хороший общий подход, но в этом случае, кажется, просто подтолкнуть проблему от IEventGetterк ISearchFootRequest. Вместо этого я бы предложил что-то вроде public IFooFilterчлена, public bool include(FooType item)который возвращает, включить или нет элемент. Тогда отдельные реализации интерфейса могут решить, как они будут выполнять эту фильтрацию
Бен Ааронсон,
@Ben Aaronson Я просто редактирую код, чтобы показать, как создается объект параметра запроса
InformedA
Здесь нет необходимости в компоновщике (и, честно говоря, в общем случае чрезмерно / чрезмерно рекомендуемый шаблон); нет никаких сложных правил относительно того, как параметры должны быть установлены, поэтому объект параметра вполне подойдет, если есть обоснованные сомнения относительно сложности или частого изменения параметров метода. И я согласен с @BenAaronson, хотя смысл использования объекта параметров заключается не в том, чтобы сразу включать ненужные параметры, а в том, чтобы их было очень легко добавить позже (даже если существует несколько реализаций интерфейса).
Аарона
7

Вам это не нужно сейчас, так что не добавляйте его. Если вам это понадобится позже, расширьте интерфейс:

public interface IEventGetter {

    public List<FooType> getFooList(String fooName, Date start)
         throws Exception;
    ....

}

public interface IBoundedEventGetter extends IEventGetter {

    public List<FooType> getFooList(String fooName, Date start, Date end)
        throws Exception;
    ....

}
ToddR
источник
+1 за то, что не изменили существующий (и, вероятно, уже протестированный / доставленный) интерфейс.
Себастьян Годеле
4

Ни один принцип разработки не является абсолютным, поэтому, хотя я в основном согласен с другими ответами, я решил сыграть адвоката дьявола и обсудить некоторые условия, при которых я рассмотрю вопрос о принятии решения вашего коллеги:

  • Если это общедоступный API, и вы ожидаете, что эта функция будет полезна сторонним разработчикам, даже если вы не используете ее для внутреннего использования.
  • Если он имеет значительные непосредственные выгоды, а не только будущие. Если подразумеваемая дата окончания равна Now(), то добавление параметра удаляет побочный эффект, который имеет преимущества для кэширования и модульного тестирования. Возможно, это допускает более простую реализацию. Или, может быть, это более соответствует другим API в вашем коде.
  • Если ваша культура развития имеет проблемную историю. Если из-за процессов или территориальности слишком сложно изменить что-то центральное, например интерфейс, то, что я видел раньше, это люди, использующие обходные пути на стороне клиента, а не меняющие интерфейс, тогда вы пытаетесь сохранить дюжину специальных дат окончания фильтры вместо одного. Если такого рода вещи часто случаются в вашей компании, имеет смысл приложить немного больше усилий для проверки будущего. Не поймите меня неправильно, лучше изменить процессы разработки, но обычно это легче сказать, чем сделать.

Тем не менее, по моему опыту, самым большим следствием для YAGNI является YDKWFYN: вы не знаете, какая форма вам понадобится (да, я только что сделал эту аббревиатуру). Даже если какой-либо параметр ограничения может быть относительно предсказуемым, он может принимать форму ограничения страницы или количества дней или логического значения, определяющего использование даты окончания из таблицы пользовательских настроек, или любого количества вещей.

Поскольку у вас еще нет требований, у вас нет возможности узнать, какой тип должен иметь этот параметр. У вас часто бывает неловкий интерфейс, который не совсем подходит под ваши требования, или вам все равно придется его менять.

Карл Билефельдт
источник
2

Недостаточно информации для ответа на этот вопрос. Это зависит от того, что на getFooListсамом деле делает, и как это делает.

Вот очевидный пример метода, который должен поддерживать дополнительный параметр, используется или нет.

void CapitalizeSubstring (String capitalize_me, int start_index);

Реализация метода, который работает с коллекцией, в которой вы можете указать начало, но не конец коллекции, часто глупо.

Вы действительно должны взглянуть на саму проблему и спросить, является ли параметр бессмысленным в контексте всего интерфейса, и действительно, какую нагрузку накладывает дополнительный параметр.

QuestionC
источник
1

Боюсь, у твоего коллеги может быть очень веское замечание. Хотя его решение на самом деле не самое лучшее.

Из его предложенного интерфейса ясно, что

public List<FooType> getFooList(String fooName, Date start, Date end) throws Exception;

возвращает экземпляры, найденные в течение определенного промежутка времени. Если клиенты в настоящее время не используют конечный параметр, это не меняет того факта, что они ожидают экземпляры, найденные в течение определенного промежутка времени. Это просто означает, что в настоящее время все клиенты используют открытые интервалы (от начала до вечности)

Таким образом, лучший интерфейс будет:

public List<FooType> getFooList(String fooName, Interval interval) throws Exception;

Если вы предоставляете интервал статическим фабричным методом:

public static Interval startingAt(Date start) { return new Interval(start, null); }

тогда клиенты даже не будут чувствовать необходимость указывать время окончания.

В то же время ваш интерфейс более правильно передает то, что он делает, поскольку getFooList(String, Date)не сообщает, что это имеет дело с интервалом.

Обратите внимание, что мое предложение вытекает из того, что метод делает в настоящее время, а не из того, что он должен или мог бы сделать в будущем, и как таковой принцип YAGNI (который действительно очень актуален) здесь не применим.

Боумор
источник
0

Добавление неиспользуемого параметра сбивает с толку. Люди могут вызывать этот метод, предполагая, что эта функция будет работать.

Я бы не добавил это. Позднее добавить его с помощью рефакторинга и механического исправления сайтов вызовов. На статически типизированном языке это легко сделать. Не нужно охотно добавлять его.

Если есть причина , чтобы этот параметр можно предотвратить путаницу: Добавить Assert в реализации этого интерфейса для обеспечения , что этот параметр передается в качестве значения по умолчанию. Если кто-то случайно использует этот параметр, по крайней мере, он немедленно заметит во время тестирования, что эта функция не реализована. Это устраняет риск попадания ошибки в производство.

USR
источник