Как я узнаю, насколько многоразовыми должны быть мои методы? [закрыто]

133

Я занимаюсь своими делами дома, и моя жена приходит ко мне и говорит

Дорогая .. Можешь ли ты распечатать все консоли Day Light Savings по всему миру на 2018 год в консоли? Мне нужно кое-что проверить.

И я очень счастлив, потому что именно этого я ждал всю свою жизнь с опытом Java и придумал:

import java.time.*;
import java.util.Set;

class App {
    void dayLightSavings() {
        final Set<String> availableZoneIds = ZoneId.getAvailableZoneIds();
        availableZoneIds.forEach(
            zoneId -> {
                LocalDateTime dateTime = LocalDateTime.of(
                    LocalDate.of(2018, 1, 1), 
                    LocalTime.of(0, 0, 0)
                );
                ZonedDateTime now = ZonedDateTime.of(dateTime, ZoneId.of(zoneId));
                while (2018 == now.getYear()) {
                    int hour = now.getHour();
                    now = now.plusHours(1);
                    if (now.getHour() == hour) {
                        System.out.println(now);
                    }
                }
            }
        );
    }
}

Но затем она говорит, что просто проверяет меня, был ли я инженером-программистом с этической точки зрения, и говорит, что, похоже, я не с тех пор (взято отсюда ) ..

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

И я, как, хорошо, хорошо, вы меня поймали. Пройдите любой год, когда захотите, вот и вы:

import java.time.*;
import java.util.Set;

class App {
    void dayLightSavings(int year) {
        final Set<String> availableZoneIds = ZoneId.getAvailableZoneIds();
        availableZoneIds.forEach(
            zoneId -> {
                LocalDateTime dateTime = LocalDateTime.of(
                    LocalDate.of(year, 1, 1), 
                    LocalTime.of(0, 0, 0)
                );
                ZonedDateTime now = ZonedDateTime.of(dateTime, ZoneId.of(zoneId));
                while (year == now.getYear()) {
                    // rest is same..

Но как мне узнать, сколько (и что) параметризировать? В конце концов, она могла бы сказать ..

  • она хочет передать пользовательский форматировщик строки, возможно, ей не нравится формат, в котором я уже печатаю: 2018-10-28T02:00+01:00[Arctic/Longyearbyen]

void dayLightSavings(int year, DateTimeFormatter dtf)

  • она интересуется только определенными месячными периодами

void dayLightSavings(int year, DateTimeFormatter dtf, int monthStart, int monthEnd)

  • она интересуется определенными часовыми периодами

void dayLightSavings(int year, DateTimeFormatter dtf, int monthStart, int monthEnd, int hourStart, int hourend)

Если вы ищете конкретный вопрос:

Если destroyCity(City city)лучше чем destroyBaghdad(), то takeActionOnCity(Action action, City city)еще лучше? Почему, почему нет?

В конце концов, я могу назвать первым его Action.DESTROYтогда Action.REBUILD, не так ли?

Но для меня недостаточно действий в городах, как насчет takeActionOnGeographicArea(Action action, GeographicalArea GeographicalArea)? Ведь я не хочу звонить

takeActionOnCity(Action.DESTORY, City.BAGHDAD);

тогда

takeActionOnCity(Action.DESTORY, City.ERBIL);

и так далее, когда я могу сделать:

takeActionOnGeographicArea(Action.DESTORY, Country.IRAQ);

ps я только построил свой вопрос вокруг цитаты, которую я упомянул, я ничего не имею против любой страны, религии, расы или чего-либо в мире. Я просто пытаюсь сделать точку.

Корай Тугай
источник
5
Возможный дубликат практического правила затрат и экономии при повторном использовании кода
коммент
71
Вы пытаетесь выразить то, что вы высказываете здесь много раз: общность стоит дорого, и поэтому должна быть обоснована конкретными, ясными преимуществами . Но это идет глубже, чем это; Языки программирования создаются их дизайнерами, чтобы облегчить некоторые типы общности, чем другие, и это влияет на наш выбор как разработчиков. Это легко спараметрировать метод значения, и когда это самый простой инструмент , который Вы имеете в своем арсенале, соблазн использовать его независимо от того, имеет ли смысл для пользователя.
Эрик Липперт
30
Повторное использование - это не то, чего вы хотите ради самого себя. Мы отдаем приоритет повторному использованию, потому что мы считаем, что артефакты кода дороги в создании и поэтому должны использоваться в максимально возможном количестве сценариев, чтобы амортизировать эти затраты в этих сценариях. Это убеждение часто не оправдывается наблюдениями, и поэтому рекомендации по проектированию для повторного использования часто используются неправильно . Создайте свой код, чтобы снизить общую стоимость приложения .
Эрик Липперт
7
Твоя жена неэтична, потому что тратит время, лгая тебе. Она попросила ответить и предложила медиум; Согласно этому контракту, то, как вы получаете этот результат, зависит только от вас самих. Кроме того, destroyCity(target)это более неэтично, чем destroyBagdad()! Какой монстр пишет программу по уничтожению города, не говоря уже о любом городе в мире? Что если система была взломана ?! Кроме того, какое отношение время / ресурсы (вложенные усилия) имеют к этике? Пока устный / письменный договор был завершен в соответствии с согласованным.
Тезра
25
Я думаю, вы слишком много читаете в эту шутку. Это шутка о том, как компьютерные программисты принимают плохие этические решения, потому что они отдают предпочтение техническим соображениям, а не влиянию их работы на людей. Это не предназначено, чтобы быть хорошим советом о разработке программы.
Эрик Липперт

Ответы:

114

Это черепахи все время вниз.

Или абстракции в этом случае.

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

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

Как я знаю, какие функции реализовать?

Причина, по которой я упоминаю выше, состоит в том, что вы уже попали в эту ловушку:

Но как мне узнать, сколько (и что) параметризировать? В конце концов, она могла бы сказать .

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

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

Как я знаю, какую архитектуру реализовать?

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

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

Как я знаю, когда абстрагировать мой код дальше?

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

введите описание изображения здесь XKCD # 764

Другими словами, когда определенный алгоритм / шаблон используется в третий раз, его следует абстрагировать, чтобы его можно было многократно использовать (= можно использовать много раз).

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

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

Если destroyCity(City city)лучше чем destroyBaghdad(), то takeActionOnCity(Action action, City city)еще лучше? Почему, почему нет?

Это очень зависит от нескольких вещей:

  • Есть ли несколько действий, которые можно предпринять в любом городе?
  • Могут ли эти действия использоваться взаимозаменяемо? Потому что, если действия «destroy» и «rebuild» выполняются совершенно по-разному, нет смысла объединять их в одном takeActionOnCityметоде.

Также имейте в виду, что если вы рекурсивно абстрагируете это, вы в конечном итоге получите метод, который настолько абстрактен, что это не что иное, как контейнер для запуска другого метода, что означает, что вы сделали свой метод нерелевантным и бессмысленным.
Если все takeActionOnCity(Action action, City city)тело вашего метода в конечном итоге является не чем иным action.TakeOn(city);, вы должны задаться вопросом, takeActionOnCityдействительно ли у метода есть цель или это не просто дополнительный слой, который ничего не добавляет.

Но для меня недостаточно действий в городах, как насчет takeActionOnGeographicArea(Action action, GeographicalArea GeographicalArea)?

Тот же вопрос всплывает здесь:

  • У вас есть вариант использования для географических регионов?
  • Одинаково ли выполнение действия по городу и области?
  • Можно ли предпринять какие-либо действия в каком-либо регионе / городе?

Если вы можете окончательно ответить «да» на все три вопроса, тогда абстракция оправдана.

Flater
источник
16
Я не могу подчеркнуть правило «один, два, много». Есть бесконечные возможности для абстрагирования / параметризации чего-либо, но полезное подмножество мало, часто ноль. Точное знание того, какой вариант имеет значение, чаще всего можно определить только ретроспективно. Поэтому придерживайтесь непосредственных требований * и добавляйте сложность по мере необходимости в соответствии с новыми требованиями или задним числом. * Иногда вы хорошо знаете проблемную область, тогда может быть неплохо добавить что-то, потому что вы знаете, что вам это нужно завтра. Но используйте эту силу с умом, это также может привести к гибели.
Кристиан Зауэр
2
> «Я не знаю, где я это прочитал [..]». Возможно, вы читали « Ужасы кодирования: правило трех» .
руна
10
«Правило« один, два, много »действительно существует, чтобы помешать вам построить неправильную абстракцию, применяя DRY вслепую. Дело в том, что две части кода могут начать выглядеть почти одинаково, поэтому заманчиво абстрагироваться от различий; но с самого начала вы не знаете, какие части кода стабильны, а какие нет; кроме того, может оказаться, что они действительно должны развиваться независимо (разные модели изменений, разные наборы обязанностей). В любом случае, неправильная абстракция работает против вас и мешает.
Филипп Милованович
4
Ожидание более двух примеров «одной и той же логики» позволяет вам лучше судить о том, что следует абстрагировать и как (и на самом деле речь идет об управлении зависимостями / связью между кодом с различными шаблонами изменений).
Филипп Милованович
1
@kukis: реалистичная линия должна быть проведена в 2 (согласно комментарию Балдрикка): ноль-один-много (как в случае отношений с базой данных). Тем не менее, это открывает путь к ненужному поведению в поиске шаблонов. Две вещи могут смутно выглядеть одинаково, но это не значит, что они на самом деле одинаковы. Однако, когда третий экземпляр вступает в схватку, которая похожа как на первый, так и на второй экземпляр, вы можете сделать более точное суждение, что их сходство действительно является шаблоном многократного использования. Таким образом, линия здравого смысла проведена в 3, что учитывает человеческую ошибку при определении «шаблонов» только между двумя экземплярами.
Флейтер
44

практика

Это Software Engineering SE, но создание программного обеспечения - это больше искусство, чем разработка. Не существует универсального алгоритма, который можно использовать для измерения, чтобы определить, насколько многократного использования достаточно. Как и во всем, чем больше вы будете практиковаться в разработке программ, тем лучше вы будете в этом разбираться. Вы получите лучшее представление о том, что «достаточно», потому что вы увидите, что идет не так и как это происходит, когда вы параметрируете слишком много или слишком мало.

Это не очень полезно , в настоящее время , хотя, так как о некоторых руководящих принципах?

Оглянись на свой вопрос. Там много "она могла бы сказать" и "я мог". Много утверждений, теоретизирующих о некоторой будущей потребности. Люди дерьмо предсказывают будущее. А ты (скорее всего) человек. Подавляющая проблема разработки программного обеспечения - попытаться объяснить будущее, которого вы не знаете.

Принцип 1: Вам это не нужно

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

Руководящий принцип 2: Стоимость / Выгода

Круто, эта маленькая программа заняла у вас несколько часов, чтобы написать? Так что , если ваша жена действительно вернется и попросить те вещи? В худшем случае, вы тратите еще несколько часов, чтобы собрать другую программу для этого. В этом случае не так уж много времени, чтобы сделать эту программу более гибкой. И это не собирается сильно увеличивать скорость выполнения или использование памяти. Но нетривиальные программы имеют разные ответы. Разные сценарии имеют разные ответы. В какой-то момент затраты явно не стоят выгоды даже при несовершенных навыках рассказывания в будущем.

Руководящий принцип 3: Сосредоточьтесь на константах

Оглянись на вопрос. В вашем исходном коде много постоянных целых. 2018, 1. Постоянные целые, постоянные строки ... Скорее всего, они должны быть непостоянными . Более того, они параметризуются (или, по крайней мере, определяют как фактические константы) лишь немного времени. Но другой вещью, которой следует опасаться, является постоянное поведение . System.out.printlnнапример. Подобные предположения об использовании имеют тенденцию изменяться в будущем и исправляются очень дорого. Не только это, но и такой ввод-вывод делает функцию нечистой (наряду с извлечением часового пояса). Параметризация такого поведения может сделать функцию более чистой, что приведет к повышению гибкости и тестируемости. Большие преимущества с минимальными затратами (особенно если вы делаете перегрузку, которая использует System.outпо умолчанию).

Telastyn
источник
1
Это всего лишь ориентир, с единицами все в порядке, но вы смотрите на них и говорите: «Это когда-нибудь изменится?» Нет. И println может быть параметризован с помощью функции более высокого порядка - хотя Java не так хороша в этом.
Теластин
5
@KorayTugay: если программа действительно предназначена для вашей жены, приходящей домой, YAGNI скажет вам, что ваша первоначальная версия идеальна, и вам не нужно больше тратить время на введение констант или параметров. YAGNI нужен контекст - является ли ваша программа одноразовым решением или программой миграции, выполняемой всего несколько месяцев, или она является частью огромной ERP-системы, предназначенной для использования и обслуживания в течение нескольких десятилетий?
Док Браун
8
@KorayTugay: отделение ввода-вывода от вычислений является фундаментальным методом структурирования программ. Отдельная генерация данных от фильтрации данных от преобразования данных от потребления данных от представления данных. Вы должны изучить некоторые функциональные программы, тогда вы увидите это более четко. В функциональном программировании достаточно часто генерировать бесконечное количество данных, отфильтровывать только те данные, которые вам интересны, преобразовывать данные в нужный вам формат, создавать из них строку и печатать эту строку в 5 различных функциях. по одному на каждый шаг.
Йорг Миттаг,
3
Как замечание, строгое соблюдение YAGNI приводит к необходимости постоянного рефакторинга: «Использование без постоянного рефакторинга может привести к неорганизованному коду и массивной переработке, известной как технический долг». Поэтому, несмотря на то, что YAGNI - это хорошая вещь в целом, он несет большую ответственность за пересмотр и переоценку кода, а это не то, что хочет делать каждый разработчик / компания.
Флатер
4
@Telastyn: Я предлагаю расширить вопрос: «Это никогда не изменится, и является ли намерение кода тривиально читаемым без именования константы ?». Даже для значений, которые никогда не меняются, может быть уместно назвать их просто для того, чтобы вещи были читабельными.
Флейтер
27

Во-первых: ни один разработчик программного обеспечения, ориентированный на безопасность, не написал бы метод DestroyCity без передачи авторизационного токена по любой причине.

Я тоже могу написать что-либо в качестве императива, обладающего очевидной мудростью, но не применимого в другом контексте. Почему необходимо авторизовать конкатенацию строк?

Во-вторых: весь код при выполнении должен быть полностью указан .

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

Это может быть в том же объектном файле destroyCity(xyz), и это может быть в файле конфигурации: destroy {"city": "XYZ"}"или это может быть серия щелчков и нажатий клавиш в пользовательском интерфейсе.

В-третьих:

Дорогая .. Можешь ли ты распечатать все консоли Day Light Savings по всему миру на 2018 год в консоли? Мне нужно кое-что проверить.

это совсем другой набор требований к:

она хочет передать пользовательский формататор строк, ... заинтересованный только определенными месячными периодами, ... [и] заинтересованный определенными часовыми периодами ...

Теперь второй набор требований, очевидно, создает более гибкий инструмент. У него более широкая целевая аудитория и более широкая сфера применения. Опасность здесь заключается в том, что наиболее гибким приложением в мире на самом деле является компилятор для машинного кода. Это буквально такая универсальная программа, что она может создать что угодно, чтобы сделать компьютер таким, каким он вам нужен (в рамках ограничений его аппаратного обеспечения).

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

Ваша жена спрашивала о функциональности и недостаточно указала свои требования к вам. В данном случае это было нарочно, и в целом это потому, что они не знают ничего лучшего. В противном случае они бы просто использовали компилятор. Итак, первая проблема в том, что вы не спрашивали более подробную информацию о том, что именно она хотела сделать. Она хотела запустить это в течение нескольких разных лет? Она хотела это в файле CSV? Вы не узнали, какие решения она хотела бы принять сама, и что она просила вас выяснить и решить за нее. Как только вы выяснили, какие решения необходимо отложить, вы можете выяснить, как передавать эти решения с помощью параметров (и других настраиваемых средств).

При этом большинство клиентов не сообщают, предполагают или не знают определенных деталей (то есть решений), которые они действительно хотели бы сделать сами или которые они действительно не хотели делать (но это звучит потрясающе). Вот почему такие методы работы, как PDSA (планировать-развивать-изучать-действовать), так важны. Вы спланировали работу в соответствии с требованиями, а затем разработали набор решений (код). Теперь пришло время изучить это, самостоятельно или с вашим клиентом и узнать что-то новое, и это поможет вашему мышлению двигаться вперед. Наконец, действуйте в соответствии с вашими новыми идеями - обновите требования, уточните процесс, получите новые инструменты и т. Д. Затем начните планирование снова. Это выявило бы какие-то скрытые требования со временем и доказало бы успехи многих клиентов.

В заключение. Ваше время важно ; это очень реально и очень конечно. Каждое решение, которое вы принимаете, влечет за собой множество других скрытых решений, и в этом суть разработки программного обеспечения. Задержка решения в качестве аргумента может сделать текущую функцию более простой, но она усложняет другое. Это решение актуально в этом другом месте? Это более актуально здесь? Чье это решение на самом деле? Вы решаете это; это кодирование. Если вы часто повторяете наборы решений, есть реальная выгода в их кодификации внутри некоторой абстракции. XKCD имеет полезную перспективу здесь. И это актуально на уровне системы, будь то функция, модуль, программа и т. Д.

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

Kain0_0
источник
+1 люблю часть про компилятор!
Ли
4

Здесь много длинных ответов, но, честно говоря, я думаю, что это очень просто

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

так в твоей функции

class App {
    void dayLightSavings() {
        final Set<String> availableZoneIds = ZoneId.getAvailableZoneIds();
        availableZoneIds.forEach(zoneId -> {
            LocalDateTime dateTime = LocalDateTime.of(LocalDate.of(2018, 1, 1), LocalTime.of(0, 0, 0));
            ZonedDateTime now = ZonedDateTime.of(dateTime, ZoneId.of(zoneId));
            while (2018 == now.getYear()) {
                int hour = now.getHour();
                now = now.plusHours(1);
                if (now.getHour() == hour) {
                    System.out.println(now);
                }
            }
        });
    }
}

У вас есть:

The zoneIds
2018, 1, 1
System.out

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

У вас нет строки форматирования, поэтому добавление ее - это запрос функции, и вам следует отослать свою жену в экземпляр семейства Jira. Это может быть отложено и расставлено по приоритетам на соответствующем заседании комитета по управлению проектом.

Ewan
источник
1
Вы (обращаясь к OP), конечно, не должны добавлять строку формата, так как вам не нужно ничего печатать. Одна вещь в этом коде, которая абсолютно предотвращает его повторное использование, это то, что он печатает. Он должен вернуть зоны или карту зон, когда они выходят из летнего времени. (Несмотря на то, почему она только определяет , когда идут от перехода на летнее время , а не на ДСТ, я не понимаю , это , кажется, не соответствует постановка задачи..)
David Conrad
Требуется распечатать на консоль. Вы можете смягчить тесную связь, передавая выходной поток в качестве параметра, как я предлагаю
Ewan
1
Тем не менее, если вы хотите, чтобы код можно было использовать повторно, вы не должны печатать на консоль. Напишите метод, который возвращает результаты, а затем напишите вызывающую программу, которая получает их и печатает. Это также делает его тестируемым. Если вы хотите, чтобы он выводил данные, я бы не передавал поток вывода, я бы передавал Consumer.
Дэвид Конрад
наш поток - это потребитель
Ewan
Нет, OutputStream не является Потребителем .
Дэвид Конрад
4

Короче говоря, не проектируйте свое программное обеспечение для повторного использования, потому что конечному пользователю нет дела до возможности повторного использования ваших функций. Вместо этого, инженер для понимания дизайна - легко ли мой код для кого-то другого или мое будущее забывчивое я для понимания? - и гибкость дизайна- когда мне неизбежно придется исправлять ошибки, добавлять функции или иным образом изменять функциональность, насколько мой код будет противостоять изменениям? Единственное, о чем заботится ваш клиент, - это как быстро вы сможете ответить, когда она сообщит об ошибке или попросит внести изменения. Когда вы задаете эти вопросы о своем дизайне, это, как правило, приводит к тому, что код можно использовать повторно, но этот подход позволяет вам сосредоточиться на том, чтобы избежать реальных проблем, с которыми вы столкнетесь в течение жизни этого кода, чтобы вы могли лучше обслуживать конечного пользователя, а не преследовать высокий, непрактичный «инженерные» идеалы, чтобы радовать шею бороды.

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


Объекты, а не процедуры

Вместо того чтобы использовать классы, такие как модули старой школы, с набором подпрограмм, которые вы вызываете для выполнения того, что должно делать ваше программное обеспечение, рассмотрите возможность моделирования домена как объектов, которые взаимодействуют и взаимодействуют для выполнения поставленной задачи. Методы в объектно-ориентированной парадигме были изначально созданы для того, чтобы быть сигналами между объектами, чтобы они Object1могли сказать, Object2что делать, что бы то ни было, и, возможно, получить ответный сигнал. Это связано с тем, что объектно-ориентированная парадигма по своей сути предназначена для моделирования ваших доменных объектов и их взаимодействий, а не для причудливого способа организации тех же самых старых функций и процедур императивной парадигмы. В случае сvoid destroyBaghdadНапример, вместо того, чтобы пытаться написать не зависящий от контекста универсальный метод для обработки разрушения Багдада или любой другой вещи (которая может быстро стать сложной, трудной для понимания и ломкой), каждая вещь, которая может быть уничтожена, должна отвечать за понимание того, как уничтожить себя. Например, у вас есть интерфейс, который описывает поведение вещей, которые могут быть уничтожены:

interface Destroyable {
    void destroy();
}

Тогда у вас есть город, который реализует этот интерфейс:

class City implements Destroyable {
    @Override
    public void destroy() {
        ...code that destroys the city
    }
}

Ничто из того, что требует уничтожения экземпляра City, никогда не будет заботиться о том, как это происходит, поэтому нет никаких оснований для того, чтобы этот код существовал где-либо за пределами City::destroy, и, действительно, глубокое знание внутренней работы Cityвне себя было бы тесной связью, которая уменьшает гибкость, так как вы должны учитывать эти внешние элементы, если вам когда-либо потребуется изменить поведение City. Это истинная цель инкапсуляции. Думайте об этом, как будто у каждого объекта есть свой собственный API, который должен позволять вам делать с ним все, что вам нужно, чтобы вы могли беспокоиться о выполнении ваших запросов.

Делегация, а не «Контроль»

Теперь, является ли ваш реализующий класс Cityили Baghdadзависит от того, насколько универсальным оказывается процесс разрушения города. По всей вероятности, Cityволя будет состоять из более мелких частей, которые нужно будет уничтожить по отдельности, чтобы выполнить полное разрушение города, поэтому в этом случае каждая из этих частей также будет реализована Destroyable, и каждая из них получит указание Cityуничтожить Сами же так же кто-то извне просил себя Cityуничтожить.

interface Part extends Destroyable {
    ...part-specific methods
}

class Building implements Part {
    ...part-specific methods
    @Override
    public void destroy() {
       ...code to destroy a building
    }
}

class Street implements Part {
    ...part-specific methods
    @Override
    public void destroy() {
        ...code to destroy a building
    }
}

class City implements Destroyable {
    public List<Part> parts() {...}

    @Override
    public void destroy() {
        parts().forEach(Destroyable::destroy);            
    }
}

Если вы хотите по-настоящему сойти с ума и реализовать идею о Bombтом, что объект отбрасывается на локацию и уничтожает все в пределах определенного радиуса, это может выглядеть примерно так:

class Bomb {
    private final Integer radius;

    public Bomb(final Integer radius) {
        this.radius = radius;
    }

    public void drop(final Grid grid, final Coordinate target) {
        new ObjectsByRadius(
            grid,
            target,
            this.radius
        ).forEach(Destroyable::destroy);
    }
}

ObjectsByRadiusпредставляет набор объектов, который рассчитывается для Bombвходных данных, потому что Bombне имеет значения, как выполняется этот расчет, если он может работать с объектами. Кстати, это можно многократно использовать, но главная цель - изолировать вычисления от процессов отбрасывания Bombи разрушения объектов, чтобы вы могли понять каждую часть и то, как они сочетаются друг с другом, и изменить поведение отдельной части, не изменяя весь алгоритм ,

Взаимодействия, а не алгоритмы

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

Я бы дал вам кое-что для вашего оригинального примера, но я, честно говоря, не могу понять, что значит «печатать… Дневные сбережения». Что я могу сказать об этой категории проблем, так это то, что каждый раз, когда вы выполняете вычисление, результат которого можно отформатировать несколькими способами, мой предпочтительный способ разбить это так:

interface Result {
    String print();
}

class Caclulation {
    private final Parameter paramater1;

    private final Parameter parameter2;

    public Calculation(final Parameter parameter1, final Parameter parameter2) {
        this.parameter1 = parameter1;
        this.parameter2 = parameter2;
    }

    public Result calculate() {
        ...calculate the result
    }
}

class FormattedResult {
    private final Result result;

    public FormattedResult(final Result result) {
        this.result = result;
    }

    @Override
    public String print() {
        ...interact with this.result to format it and return the formatted String
    }
}

Поскольку в вашем примере используются классы из библиотеки Java, которые не поддерживают этот дизайн, вы можете просто использовать API ZonedDateTimeнапрямую. Идея здесь заключается в том, что каждый расчет заключен в свой собственный объект. Он не делает никаких предположений о том, сколько раз он должен выполняться или как он должен отформатировать результат. Это исключительно связано с выполнением простейшей формы расчета. Это облегчает понимание и гибкость в изменениях. Точно так же, Resultон связан исключительно с инкапсуляцией результата вычисления, а FormattedResultисключительно с взаимодействием с ним Resultдля его форматирования в соответствии с определенными нами правилами. Таким образом,мы можем найти идеальное количество аргументов для каждого из наших методов, поскольку каждый из них имеет четко определенную задачу . Также гораздо проще изменить движение вперед, если интерфейсы не меняются (что они вряд ли будут делать, если вы должным образом минимизировали ответственность ваших объектов). Нашmain()метод может выглядеть так:

class App {
    public static void main(String[] args) {
        final List<Set<Paramater>> parameters = ...instantiated from args
        parameters.forEach(set -> {
            System.out.println(
                new FormattedResult(
                    new Calculation(
                        set.get(0),
                        set.get(1)
                    ).calculate()
                ).print()
            );
        });
    }
}

Фактически, объектно-ориентированное программирование было придумано специально как решение проблемы сложности / гибкости парадигмы императива, потому что просто нет хорошего ответа (что каждый может договориться или прийти независимо, так или иначе), как оптимально указать императивные функции и процедуры в рамках идиомы.

Stuporman
источник
Это очень подробный и продуманный ответ, но, к сожалению, я думаю, что он не соответствует тому, о чем действительно просил ФП. Он не просил урок о хороших методах ООП для решения своего показательного примера, он спрашивал о критериях, по которым мы решаем инвестировать время в решение по сравнению с обобщением.
maple_shaft
@maple_shaft Может быть, я пропустил отметку, но я думаю, что вы тоже. ОП не спрашивает об инвестициях времени против обобщения. Он спрашивает: «Как я узнаю, насколько многоразовыми должны быть мои методы?» Далее он продолжает задавать вопросы в основной части своего вопроса: «Если destroyCity (Город-город) лучше, чем destroyBaghdad (), то takeActionOnCity (Действие-действие, Город-город) еще лучше? Почему / почему нет?» Я обосновал альтернативный подход к инженерным решениям, который, как мне кажется, решает проблему выяснения того, насколько универсальны методы, и предоставил примеры для поддержки моего утверждения. Мне жаль, что тебе не понравилось.
Ступорман
@maple_shaft Честно говоря, только ОП может определить, был ли мой ответ актуален для его вопроса, поскольку остальные из нас могли вести войны, отстаивая наши интерпретации его намерений, и все они могут быть в равной степени ошибочными.
Ступорман
@maple_shaft Я добавил вступление, чтобы попытаться прояснить, как оно относится к вопросу, и дал четкое разграничение между ответом и примером реализации. Это лучше?
Ступорман
1
Честно говоря, если вы примените все эти принципы, ответ будет естественным, гладким и хорошо читаемым. Плюс, изменчивый без большой суеты. Я не знаю, кто вы, но мне бы хотелось, чтобы вас было больше! Я продолжаю копировать Reactive код для приличного ОО, и он ВСЕГДА наполовину меньше, более читабелен, более управляем, и все еще имеет многопоточность / разбиение / отображение. Я думаю, что React для людей, которые не понимают "основные" концепции, которые вы только что перечислили.
Стивен Дж
3

Опыт , знание предметной области и обзоры кода.

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


С помощью опыта вы начнете распознавать шаблоны в не зависящих от домена методах (и классах), которые вы пишете. И, если вас вообще интересует DRY-код, вы будете испытывать дурные чувства, когда будете писать метод, который, как вы инстинктивно знаете , будут писать варианты в будущем. Таким образом, вместо этого вы интуитивно напишите параметризованный наименьший общий знаменатель.

(Этот опыт может инстинктивно переноситься на некоторые ваши доменные объекты и методы тоже.)

С Знанием предметной области вы будете иметь представление о том, какие бизнес-концепции тесно связаны, какие концепции имеют переменные, которые довольно статичны и т. Д.

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


Тем не менее, новые разработчики, как правило, не будут иметь эти Spidey Senses или опытную группу сверстников, чтобы опираться сразу. И даже опытные разработчики извлекают выгоду из базовой дисциплины, чтобы направлять их через новые требования - или через туманные дни. Итак, вот что я бы предложил для начала :

  • Начните с наивной реализации, с минимальной параметризацией.
    (Включите любые параметры , которые вы уже знаете , вам нужно, очевидно , ...)
  • Удалите магические числа и строки, перенеся их в конфиги и / или параметры
  • Разложите «большие» методы на более мелкие, хорошо названные методы
  • Преобразуйте методы с высокой степенью избыточности (если это удобно) в общий знаменатель, параметризуя различия.

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

Но помимо большого опыта и т. Д., Я советую довольно минималистичный СУХОЙ код. Нетрудно рефакторировать очевидные нарушения. И, если вы слишком усердны , вы можете получить «чрезмерно сухой» код, который еще сложнее читать, понимать и поддерживать, чем «мокрый» эквивалент.

svidgen
источник
2
Так что нет правильного ответа If destoryCity(City city) is better than destoryBaghdad(), is takeActionOnCity(Action action, City city) even better?? Это вопрос да / нет, но у него нет ответа, верно? Или первоначальное предположение неверно, destroyCity(City)не обязательно должно быть лучше, и это на самом деле зависит ? Значит, это не значит, что я не являюсь инженером -программистом, не прошедшим этическую подготовку, потому что на первом месте я реализовал напрямую без каких-либо параметров? Я имею в виду, каков ответ на конкретный вопрос, который я задаю?
Корай Тугай
Ваш вопрос как бы задает несколько вопросов. Ответ на заглавный вопрос: «опыт, знание предметной области, обзоры кода… и… не бойтесь рефакторинга». Ответ на любой конкретный вопрос: «Являются ли эти параметры правильными для метода ABC?» - это вопрос ... «Я не знаю. Почему вы спрашиваете? это. Исправить это. " ... Я мог бы отослать вас к «POAP» для дальнейшего руководства: вам нужно понять, почему вы делаете то, что делаете!
svidgen
Я имею в виду ... давайте даже отступим от destroyBaghdad()метода. Какой контекст? Это видеоигра, в которой конец игры приводит к разрушению Багдада ??? Если это так ... destroyBaghdad()может быть вполне разумное имя метода / подпись ...
svidgen
1
Так что вы не согласны с приведенной цитатой в моем вопросе, верно? It should be noted that no ethically-trained software engineer would ever consent to write a DestroyBaghdad procedure.Если бы вы были в комнате с Натаниэлем Боренштейном, вы бы поспорили с ним, что это на самом деле зависит, а его утверждение неверно? Я имею в виду, что это прекрасно, что многие люди отвечают, тратят свое время и энергию, но я не вижу ни одного конкретного ответа нигде. Spidey-чувства, обзоры кода .. Но каков ответ is takeActionOnCity(Action action, City city) better? null?
Корай Тугай
1
@svidgen Конечно, еще один способ абстрагировать это без каких-либо дополнительных усилий - инвертировать зависимость - заставить функцию возвращать список городов, вместо того чтобы выполнять какие-либо действия с ними (например, «println» в исходном коде). При необходимости это можно абстрагировать дальше, но только это одно изменение само по себе решает примерно половину добавленных требований в исходном вопросе - и вместо одной нечистой функции, которая может делать все виды плохих вещей, у вас просто есть функция это возвращает список, и вызывающий делает плохие вещи.
Луаан
2

Тот же ответ, что и с качеством, удобством использования, техническим долгом и т. Д.

Как многоразовые , как вам, пользователю, 1 нужно им быть

Это в основном суждение - будет ли стоимость проектирования и обслуживания абстракции возмещена стоимостью (= время и усилия), которая спасет вас в будущем.

  • Обратите внимание на фразу «вниз по линии»: здесь есть механизм выплаты, так что это будет зависеть от того, сколько вы будете работать с этим кодом в дальнейшем. Например:
    • Это единовременный проект или он будет постепенно улучшаться в течение долгого времени?
    • Уверены ли вы в своем дизайне, или вам, скорее всего, придется отказаться от него или иным образом радикально изменить его для следующего проекта / этапа (например, попробовать другую структуру)?
  • Прогнозируемая выгода также зависит от вашей способности предсказать будущее (изменения в приложении). Иногда вы можете разумно увидеть место (места), в котором будет работать ваше приложение. Больше раз вы думаете, что можете, но на самом деле не можете. Эмпирические правила здесь - это принцип ЯГНИ и правило трех - оба подчеркивают необходимость отработки того, что вы знаете сейчас.

1 Это кодовая конструкция, поэтому вы «пользователь» в данном случае - пользователь исходного кода

ivan_pozdeev
источник
1

Есть четкий процесс, которому вы можете следовать:

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

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

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

Я нахожу пример вашего города странным; Скорее всего, я никогда не буду жестко задавать название города. Это так очевидно, что дополнительные города будут включены позже, что бы вы ни делали. Но другим примером могут быть цвета. В некоторых случаях возможно жесткое кодирование «красного» или «зеленого». Например, светофор такой повсеместный цвет, что вы можете просто сойти с него (и вы всегда можете рефакторинг). Разница в том, что «красный» и «зеленый» имеют универсальное, «жестко закодированное» значение в нашем мире, невероятно маловероятно, что оно когда-либо изменится, и альтернативы на самом деле тоже нет.

Ваш первый метод перехода на летнее время просто сломан. Несмотря на то, что он соответствует спецификациям, жестко заданный 2018 год особенно плох, поскольку а) он не упоминается в техническом «контракте» (в данном случае в названии метода) и б) он скоро устареет, поэтому поломка включен с самого начала. Для вещей, которые связаны со временем / датой, очень редко имеет смысл жестко задавать конкретное значение, поскольку время течет. Но кроме этого все остальное подлежит обсуждению. Если вы даете ему простой год, а затем всегда рассчитываете полный год, продолжайте. Большинство из перечисленных вами вещей (форматирование, выбор меньшего диапазона и т. Д.) Кричит о том, что ваш метод делает слишком много, и вместо этого он, вероятно, должен вернуть список / массив значений, чтобы вызывающая сторона могла самостоятельно выполнить форматирование / фильтрацию.

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

Anoe
источник
Относительно вашего второго до последнего параграфа - посмотрите на «изначально» заданные требования, то есть те, на которых был основан первый метод. Он определяет 2018, поэтому код технически корректен (и, вероятно, будет соответствовать вашему подходу, основанному на функциях).
августа
@dwizum, это правильно в отношении требований, но там имя метода вводит в заблуждение. В 2019 году любой программист, просто смотрящий на имя метода, предположил бы, что он делает что угодно (возможно, возвращает значения для текущего года), а не 2018 ... Я добавлю предложение к ответу, чтобы пояснить, что я имел в виду.
AnoE
1

Я пришел к мнению, что существует два вида кода многократного использования:

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

Первый вид повторного использования часто является хорошей идеей. Это относится к таким вещам, как списки, хеш-карты, хранилища ключей / значений, сопоставления строк (например, регулярное выражение, глобус, ...), кортежи, унификация, деревья поиска (сначала глубина, сначала ширина, итеративное углубление, ...) комбинаторы синтаксического анализатора, кэши / памятные записки, устройства чтения / записи форматов данных (s-выражения, XML, JSON, protobuf, ...), очереди задач и т. д.

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

Второй тип - это когда у нас есть конкретный код, который решает реальную проблему, но делает это путем принятия целого ряда решений. Мы можем сделать его более общим / многократно используемым путем «мягкого кодирования» этих решений, например, превращая их в параметры, усложняя реализацию и выполняя даже более конкретные детали (т. Е. Знание, какие хуки нам могут потребоваться для переопределений). Ваш пример, кажется, такого рода. Проблема такого рода повторного использования заключается в том, что мы можем попытаться угадать варианты использования других людей или наши будущие "я". В конечном итоге мы можем получить столько параметров, что наш код не будет использоватьсяне говоря уже о многоразовости! Другими словами, при вызове требуется больше усилий, чем просто написание собственной версии. Вот где важен YAGNI (он тебе не нужен). Во многих случаях такие попытки «многократно используемого» кода заканчиваются тем, что его не используют повторно, поскольку он может быть несовместим с этими вариантами использования более фундаментально, чем могут учитывать параметры, или эти потенциальные пользователи скорее свернут свои собственные (чёрт, посмотрите на все стандарты и библиотеки там, чьи авторы имеют префикс со словом «простой», чтобы отличать их от предшественников!).

Эта вторая форма «повторного использования» должна в основном выполняться по мере необходимости. Конечно, вы можете вставить некоторые «очевидные» параметры, но не пытайтесь предсказать будущее. YAGNI.

Warbo
источник
Можем ли мы сказать, что вы согласны с тем, что мой первый дубль был в порядке, хотя даже год был жестко закодирован? Или, если бы вы изначально выполняли это требование, вы бы сделали год параметром в своем первом поступлении?
Корай Тугай
Ваш первый дубль был в порядке, так как требовалось одноразовый скрипт для «проверки чего-либо». Это не проходит ее «этический» тест, но она не проходит тест «без догмы». «Она может сказать…» - это выдумывать требования, которые вам не понадобятся.
Warbo
Мы не можем сказать, какой «разрушить город» «лучше» без дополнительной информации: destroyBaghdadэто одноразовый сценарий (или, по крайней мере, он идемпотентный). Может быть, уничтожение любого города было бы улучшением, но что, если он destroyBaghdadработает, затопив Тигр? Это может быть повторно использовано для Мосула и Басры, но не для Мекки или Атланты.
Warbo
Я вижу, что вы не согласны с Натаниэлем Боренштейном, владельцем цитаты. Я пытаюсь понять медленно, я думаю, читая все эти ответы и обсуждения.
Корай Тугай
1
Мне нравится эта дифференциация. Это не всегда понятно, и всегда есть «пограничные случаи». Но в целом, я также фанат «строительных блоков» (часто в форме staticметодов), которые являются чисто функциональными и низкоуровневыми, и, в отличие от этого, решение о «настройке параметров и хуков» обычно заключается в том, где Вы должны построить некоторые структуры, которые требуют оправдания.
Marco13
1

Там уже есть много отличных и сложных ответов. Некоторые из них углубляются в конкретные детали, излагают определенные точки зрения на методологии разработки программного обеспечения в целом, а некоторые из них, безусловно, содержат в себе спорные элементы или «мнения».

Ответ на Warbo уже отмечали различные виды повторного использования. А именно, является ли что-то повторно используемым, потому что оно является фундаментальным строительным блоком, или является ли что-то повторно используемым, потому что это «универсально» в некотором роде. Ссылаясь на последнее, есть кое-что, что я бы посчитал своего рода мерой для повторного использования:

Может ли один метод подражать другому.

Что касается примера из вопроса: представьте, что метод

void dayLightSavings()

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

publicvoid dayLightSavings()

Это может быть реализовано, как вы показали в своем ответе. Теперь кто-то хочет параметризовать его по году. Таким образом, вы можете добавить метод

publicvoid dayLightSavings(int year)

и изменить исходную реализацию, чтобы просто быть

public void dayLightSavings() {
    dayLightSavings(2018);
}

Следующие «запросы возможностей» и обобщения следуют той же схеме. Поэтому, если и только если есть потребность в самой общей форме, вы можете реализовать ее, зная, что эта самая общая форма допускает тривиальные реализации более конкретных:

public void dayLightSavings() {
    dayLightSavings(2018, 0, 12, 0, 12, new DateTimeFormatter(...));
}

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

тл; др :

Вопрос на самом деле не столько в том, «насколько многоразовым должен быть метод». Вопрос в том, насколько уязвима эта возможность повторного использования и как выглядит API. Создание надежного API, способного выдержать испытание временем (даже если в дальнейшем возникнут дополнительные требования), является искусством и ремеслом, и тема слишком сложна, чтобы ее здесь охватить. Для начала ознакомьтесь с этой презентацией Джошуа Блоха или вики-книгой по дизайну API .

Marco13
источник
dayLightSavings()звонить dayLightSavings(2018)мне не кажется хорошей идеей.
Корай Тугай
@KorayTugay Когда первоначальный запрос состоит в том, что он должен напечатать «летнее время на 2018 год», то это нормально. На самом деле, это является именно метод , который вы реализуете изначально. Если он должен напечатать «летнее время на текущий год , то, конечно, вы бы позвонили dayLightSavings(computeCurrentYear());. ...
Marco13
0

Хорошее эмпирическое правило: ваш метод должен быть как многоразовым, так и ... многоразовым.

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

Если у вас есть больше абонентов, вы можете ввести новые параметры, если другие абоненты могут передавать эти параметры; в противном случае вам нужен новый метод.

Поскольку количество абонентов может со временем расти, вам необходимо быть готовым к рефакторингу или перегрузке. Во многих случаях это означает, что вы должны чувствовать себя в безопасности, выбирая выражение и выполняя действие «extract parameter» вашей IDE.

Karol
источник
0

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

Ваш пример зависит только от

import java.time.*;
import java.util.Set;

так что в теории это может быть многоразовым.

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

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

k3b
источник
0

Это хорошая возможность сформулировать правило, которое я недавно придумал:

Быть хорошим программистом означает быть способным предсказывать будущее.

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

Другими факторами, с которыми вы должны уравновешивать это, является то, сколько дополнительного времени и усилий потребуется, и насколько сложнее это сделает ваш код. Иногда вам везет, и решение более общей проблемы на самом деле проще! (По крайней мере, концептуально, если не в объеме кода.) Но чаще это сложность, а также затраты времени и усилий.

Поэтому, если вы считаете, что обобщение, скорее всего, понадобится, его часто стоит делать (если только это не добавляет много работы или сложности); но если это кажется гораздо менее вероятным, то, вероятно, это не так (если только это не очень просто и / или не упрощает код).

(Для недавнего примера: на прошлой неделе мне дали спецификацию для действий, которые система должна предпринять ровно через 2 дня после того, как что-то истекло. Поэтому, конечно, я сделал двухдневный период параметром. На этой неделе бизнесмены были в восторге, так как они собирались просить об этом улучшении! Мне повезло: это было легкое изменение, и я догадался, что его вполне можно было бы найти. Часто это сложнее судить. Но все же стоит попытаться предсказать, и опыт часто является хорошим руководством .)

gidds
источник
0

Во-первых, лучший ответ на вопрос «Как я узнаю, какими должны быть мои методы многократного использования?» - это «опыт». Сделайте это несколько тысяч раз, и вы, как правило, получите правильный ответ. Но в качестве тизера я могу дать вам последнее Строка этого ответа: ваш клиент скажет вам, сколько гибкости и сколько уровней обобщения вы должны искать.

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

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

Я сосредотачиваюсь на том, чтобы поставить вещи в масштабе от «необратимого» до «обратимого». Это гладкая шкала. Единственная действительно необратимая вещь - это «время, потраченное на проект». Вы никогда не получите эти ресурсы обратно. Чуть менее обратимыми могут быть ситуации «золотых наручников», такие как Windows API. Устаревшие функции остаются в этом API в течение десятилетий, потому что бизнес-модель Microsoft требует этого. Если у вас есть клиенты, чьи отношения будут навсегда повреждены из-за удаления какой-либо функции API, это следует рассматривать как необратимое. Глядя на другой конец шкалы, у вас есть такие вещи, как прототип кода. Если вам не нравится, куда он идет, вы можете просто выбросить его. Чуть менее обратимыми могут быть API внутреннего использования. Они могут быть реорганизованы, не беспокоя клиентавремя (самый необратимый ресурс из всех!)

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

Такие вещи, как принцип СУХОЙ, предлагают способ изменить этот баланс. Если вы обнаружите, что повторяетесь, это возможность создать внутренний API. Ни один клиент не видит это, поэтому вы всегда можете изменить его. Если у вас есть этот внутренний API, теперь вы можете начать играть с вещами, ориентированными на будущее. Как ты думаешь, сколько заданий по часовому поясу тебе даст твоя жена? Есть ли у вас другие клиенты, которые могли бы хотеть задачи на основе часового пояса? Ваша гибкость здесь определяется конкретными требованиями ваших нынешних клиентов и поддерживает потенциальные будущие потребности будущих клиентов.

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

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

Поэтому, когда вы делаете этот трюк обобщения / сушки, всегда держите руку на пульсе своего клиента . Как вы думаете, что ваша жена собирается попросить дальше? Вы ставите себя в положение для удовлетворения этих потребностей? Если у вас есть умение, клиент эффективно расскажет вам о своих будущих потребностях. Если у вас нет ловкости, большинство из нас просто полагаются на догадки! (особенно с супругами!) Некоторым клиентам нужна большая гибкость, и они будут готовы принять на себя дополнительные расходы, связанные с разработкой всех этих уровней, поскольку они напрямую извлекают выгоду из гибкости этих уровней. У других заказчиков довольно постоянные требования, и они предпочитают, чтобы разработка была более прямой. Ваш клиент скажет вам, сколько гибкости и сколько уровней обобщения вы должны искать.

Корт Аммон
источник
Ну, должны быть другие люди, которые сделали это 10000 раз, так почему я должен делать это 10000 раз и получать опыт, когда я могу учиться у других? Потому что ответ будет отличаться для каждого человека, поэтому ответы опытных не относятся ко мне? И Your customer will tell you how much flexibility and how many layers of generalization you should seek.что это за мир?
Корай Тугай
@KorayTugay Это деловой мир. Если ваши клиенты не говорят вам, что делать, значит, вы недостаточно внимательно слушаете. Конечно, они не всегда говорят вам на словах, но они говорят вам другими способами. Опыт помогает вам слушать их более тонкие сообщения. Если у вас еще нет навыков, найдите в своей компании кого-то, кто умеет слушать эти тонкие намеки клиентов и просить у них указания. У кого-то будет такой навык, даже если он генеральный директор или маркетинг.
Корт Аммон
В вашем конкретном случае, если вы не смогли вынести мусор, потому что вы были слишком заняты написанием обобщенной версии этой проблемы часового пояса вместо того, чтобы взламывать конкретное решение, как бы вы себя чувствовали?
Корт Аммон
Таким образом, вы согласны с тем, что мой первый подход был правильным: жесткое кодирование 2018 года в первом дубле вместо параметризации года? (Кстати, это не совсем прислушивается к моему клиенту, я думаю, это пример мусора. Это значит знать вашего клиента. Даже если получить поддержку от Oracle, нет тонких сообщений для прослушивания, когда она говорит, что мне нужен список дневного света изменения на 2018.) Спасибо за ваше время и ответ, кстати.
Корай Тугай
@KorayTugay Не зная каких-либо дополнительных деталей, я бы сказал, что жесткое кодирование было правильным подходом. У вас не было возможности узнать, понадобится ли вам в будущем код DLS, и не было никакого представления о том, какой запрос она может дать в следующий раз. И если ваш клиент пытается проверить вас, он получает то, что получает = D
Cort Ammon
0

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

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

В данном конкретном случае «шутка» не является «правдой». Можно с уверенностью предположить, что работа, связанная с написанием общей процедуры уничтожения любого города, на несколько порядков больше, чем требуется для уничтожения одного конкретного города.город. В противном случае любой, кто разрушил один или несколько городов (библейский Иисус Навин, или, скажем, президент Трумэн), мог тривиально обобщить то, что он сделал, и был способен уничтожить абсолютно любой город по своему желанию. Это на самом деле не так. Методы, которые эти два человека, как известно, использовали для уничтожения небольшого числа конкретных городов, не обязательно будут работать в любой момент в любом городе. Другой город, чьи стены имели различную резонансную частоту или высотные средства воздушной обороны были лучше, потребовал бы незначительных или фундаментальных изменений в подходе (труба или ракета различной высоты).

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

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

Так что правда? Иногда добавление общности тривиально. Должны ли мы всегда добавлять общность, когда это тривиально? Я бы по-прежнему утверждал «нет, не всегда» из-за проблемы с длительным обслуживанием. Предположим, что на момент написания статьи все города в основном одинаковы, поэтому я продолжу с DestroyCity. Как только я написал это, наряду с интеграционными тестами, которые (из-за конечно-перечислимого пространства входных данных) выполняют итерацию по каждому известному городу и проверяют, работает ли функция на каждом (не знаю, как это работает. Вероятно, вызывает City.clone () и уничтожить клона? Во всяком случае).

На практике эта функция используется исключительно для уничтожения Багдада, предположим, что кто-то строит новый город, устойчивый к моим методам (это глубоко под землей или что-то в этом роде). Теперь у меня провал интеграционного теста для варианта использования , которого даже не существует , и прежде чем я смогу продолжить свою кампанию террора против ни в чем не повинного гражданского населения Ирака, я должен выяснить, как уничтожить Субтерранию. Неважно, является ли это этичным или нет, это глупо и трата моего безумного времени .

Итак, вы действительно хотите функцию, которая может выводить летнее время для любого года, просто для вывода данных за 2018 год? Возможно, но это, безусловно, потребует небольшого количества дополнительных усилий, просто собирая тестовые наборы. Может потребоваться много усилий, чтобы получить лучшую базу данных часовых поясов, чем та, которая у вас есть на самом деле. Так, например, в 1908 году в городе Порт-Артур, Онтарио, период DST начался 1 июля. Это в базе данных часовых поясов вашей ОС? Мысль нет, поэтому ваша обобщенная функция неверна . Нет ничего особенно этичного в написании кода, который дает обещания, которые он не может выполнить.

Итак, с соответствующими предостережениями легко написать функцию, которая выполняет временные зоны в течение ряда лет, скажем, с 1970 года по настоящее время. Но так же легко взять функцию, которую вы на самом деле написали, и обобщить ее для параметризации года. Так что сейчас нет ничего более этичного / разумного обобщать сейчас, это делать то, что вы делали, а затем обобщать, если и когда вам это нужно.

Однако, если бы вы знали, почему ваша жена хотела проверить этот список летнего времени, у вас было бы информированное мнение о том, может ли она снова задать тот же вопрос в 2019 году, и если да, то сможете ли вы выйти из этого цикла, дав ей функцию, которую она может вызвать, без необходимости перекомпилировать ее. После того, как вы сделали этот анализ, ответ на вопрос «следует ли это обобщать на последние годы» может быть «да». Но вы создаете для себя еще одну проблему, которая заключается в том, что данные о часовых поясах в будущем носят только предварительный характер, и поэтому, если она запустит их на 2019 год сегодня, она может или не может понять, что это питает ее предположение. Таким образом, вам все еще нужно написать кучу документации, которая не понадобится для менее общей функции («данные поступают из базы данных часовых поясов, бла-бла, вот ссылка, чтобы увидеть их политику по продвижению обновлений, бла-бла»). Если вы отказываетесь от особого случая, то все время, пока вы это делаете, она не может выполнить задачу, для которой ей нужны данные за 2018 год, из-за какой-то ерунды о 2019 году, о которой она даже не заботится.

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

Стив Джессоп
источник
0

Это простая линия для рисования, потому что повторное использование, как это определено астронавтами архитектуры, является бредом.

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

Абстракции и соглашения требуют документирования и обучения. Пожалуйста, прекратите создавать новые только ради этого. (Я смотрю на тебя , JavaScript люди!)

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

Ваш работодатель рад заплатить за все это? Я собираюсь сказать нет.

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

Давайте посмотрим на реальный пример, с которым мне только что пришлось иметь дело: HTMLRenderer. Он не масштабировался должным образом, когда я пытался выполнить рендеринг в контекст устройства принтера. Мне потребовался целый день, чтобы обнаружить, что по умолчанию он использует GDI (который не масштабируется), а не GDI + (который делает, но не сглаживает), потому что мне пришлось пройти через шесть уровней косвенного обращения в двух сборках, прежде чем я нашел код, который сделал что-нибудь .

В этом случае я прощу автора. Абстракция на самом деле необходимо , так как это является основой код , который нацелен на пять очень разных целей рендеринга: WinForms, WPF, DotNet Core, Mono и PDFsharp.

Но это только подчеркивает мою точку зрения: вы почти наверняка не делаете что-то чрезвычайно сложное (рендеринг HTML с таблицами стилей) для нескольких платформ с заявленной целью высокой производительности на всех платформах.

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

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

Противоположным аргументом является то, что этот вид абстракции поддерживает разработку, основанную на тестировании, но я думаю, что TDD также является бредом, поскольку предполагает, что бизнес имеет четкое, полное и правильное понимание своих требований. TDD отлично подходит для НАСА и управляющего программного обеспечения для медицинского оборудования и автомобилей с автоматическим управлением, но слишком дорог для всех остальных.


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

Питер Воне
источник
Хотя я согласен с тем, что вы сказали об абстракциях и усилиях по обучению, и особенно когда это направлено на мерзость, называемую «JavaScript», я категорически не согласен с первым предложением. Повторное использование может происходить на многих уровнях, оно может зайти слишком далеко , и одноразовый код может быть написан одноразовыми программистами. Но есть люди , которые достаточно идеалистические , по крайней мере цели в повторно используемом коде. Жаль вас, если вы не видите преимуществ, которые это может иметь.
Marco13
@ Marco13 - Так как ваше возражение обоснованно, я расширю суть.
Питер Воне
-3

Если вы используете хотя бы java 8, вы должны написать класс WorldTimeZones, чтобы обеспечить то, что по сути представляется коллекцией часовых поясов.

Затем добавьте метод фильтра (фильтр предиката) в класс WorldTimeZones. Это позволяет вызывающей стороне фильтровать все, что они хотят, передавая лямбда-выражение в качестве параметра.

По сути, метод одиночного фильтра поддерживает фильтрацию всего, что содержится в значении, передаваемом предикату.

Кроме того, добавьте метод stream () в ваш класс WorldTimeZones, который при вызове создает поток часовых поясов. Затем вызывающий абонент может фильтровать, отображать и уменьшать по своему усмотрению, не записывая никаких специализаций.

Родни П. Барбати
источник
3
Это хорошие идеи обобщения, однако этот ответ совершенно не соответствует тому, что задают в вопросе. Вопрос не в том, как лучше всего обобщить решение, а в том, где мы проводим черту в обобщениях, и как мы этически взвешиваем эти соображения.
maple_shaft
Поэтому я говорю, что вы должны взвесить их по количеству поддерживаемых ими вариантов использования, измененных сложностью создания. Метод, который поддерживает один вариант использования, не так полезен, как метод, который поддерживает 20 вариантов использования. С другой стороны, если все, что вам известно, это один вариант использования, и для его написания требуется 5 минут - сделайте это. Часто методы кодирования, которые поддерживают определенные виды использования, информируют вас о способе обобщения.
Родни П. Барбати