Як я можу знати, якими мають бути багаторазові мої методи? [зачинено]


133

Я маю на увазі власний бізнес вдома, і дружина приходить до мене і каже

Дорога .. Чи можете ви надрукувати на консолі весь день заощадження по всьому світу на 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 Я лише будував своє запитання навколо згаданої цитатою, я не маю нічого проти будь-якої країни, релігії, раси чи будь-чого у світі. Я просто намагаюся зробити крапку.



71
Сюжет, який ви тут робите, я намагався висловити багато разів: загальність - це дорого, і тому треба виправдовуватися конкретними, чіткими перевагами . Але воно йде глибше, ніж це; Мови програмування створені їхніми дизайнерами, щоб зробити деякі види простоти легшими, ніж інші, і це впливає на наш вибір як розробників. Це легко спараметріровать метод значення, і коли це найпростіший інструмент , який Ви маєте на своєму арсеналі, спокуса використовувати його незалежно від того, чи має сенс для користувача.
Ерік Ліпперт

30
Повторне використання - це не те, що ви хочете заради себе. Ми надаємо пріоритет повторному використанню, оскільки маємо переконання, що артефакти коду складаються дорого, тому слід використовувати їх у максимально можливих сценаріях, щоб амортизувати ці витрати за цими сценаріями. Ця думка часто не виправдовується спостереженнями, тому поради щодо розробки для повторного використання часто застосовуються неправильно . Створіть свій код, щоб знизити загальну вартість програми .
Ерік Ліпперт

7
Ваша дружина неетична, щоб витрачати час, брехаючи. Вона попросила відповідь і дала запропонований носій; Згідно з цим договором, як ви отримуєте цей результат, тільки між вами та вами. Крім того, destroyCity(target)спосіб більш неетичний, ніж destroyBagdad()! Який монстр пише програму, щоб витерти місто, не кажучи вже про будь-яке місто світу? Що робити, якщо система була порушена ?! Також, що стосується управління часом та ресурсами (вкладені зусилля) з етикою? Поки усний / письмовий договір був укладений, як було домовлено.
Тезра

25
Я думаю, ви, можливо, занадто багато читаєте в цей жарт. Це жарт про те, як комп'ютерні програмісти приймають погані етичні рішення, оскільки вони надають пріоритет технічним міркуванням над наслідками своєї роботи на людину. Це не є гарною порадою щодо розробки програм.
Ерік Ліпперт

Відповіді:


114

Це черепахи аж донизу.

Або абстракції в цьому випадку.

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

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

Як дізнатися, які функції застосувати?

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

Але як я знати, скільки (і що) параметризувати? Зрештою, вона може сказати .

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

Однак тут застосовується контекст. Я не знаю вашої дружини. Можливо, ви точно оцінили, що вона насправді цього захоче. Але ви все ж повинні підтвердити разом із замовником, що це дійсно те, чого вони хочуть, тому що в іншому випадку ви збираєтеся витратити час на розробку функції, яку ви ніколи не збираєтеся використовувати.

Як я можу знати, яку архітектуру застосувати?

Це хитріше. Клієнт не дбає про внутрішній код, тому ви не можете запитати їх, чи потрібен він. Їх думка з цього приводу здебільшого не має значення.

Однак ви все ще можете підтвердити необхідність цього, задавши правильні запитання замовнику. Замість того, щоб питати про архітектуру, запитайте їх про їхні очікування щодо майбутнього розвитку або розширення бази даних коду. Ви також можете запитати, чи є у поточної мети термін, оскільки, можливо, ви не зможете реалізувати свою фантазійну архітектуру в необхідний термін.

Як я можу дізнатися, коли далі абстрагувати свій код?

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

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

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

Щоб було зрозуміло, я не маю на увазі, що не слід писати код багаторазового використання, коли використовується лише два екземпляри алгоритму. Звичайно, ви можете також абстрагувати це, але правило повинно бути таким, що для трьох примірників ви повинні абстрагуватися.

Знову ж таки, це фактори у ваших очікуваннях. Якщо ви вже знаєте, що вам потрібні три і більше екземплярів, звичайно, ви можете відразу абстрагуватися. Але якщо ви лише здогадуєтесь, що, можливо, ви хочете реалізувати її більше разів, правильність здійснення абстракції повністю покладається на правильність вашої здогадки.
Якщо ви правильно здогадалися, ви заощадили деякий час. Якщо ви неправильно здогадалися, ви витратили частину свого часу та зусиль і, можливо, поставили під загрозу свою архітектуру, щоб реалізувати те, що в кінцевому підсумку не потребує.

Якщо destroyCity(City city)краще destroyBaghdad(), це takeActionOnCity(Action action, City city)навіть краще? Чому / чому ні?

Це дуже залежить від кількох речей:

  • Чи є кілька дій, які можна вжити в будь-якому місті?
  • Чи можна ці дії використовувати взаємозамінно? Тому що якщо дії "знищити" та "відновити" мають зовсім інші страти, то об'єднувати їх в один takeActionOnCityметод немає сенсу .

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

Але вживати дій по містах мені недостатньо, а як щодо takeActionOnGeographicArea(Action action, GeographicalArea GeographicalArea)?

Тут же виникає те саме питання:

  • Чи є у вас випадки використання для географічних регіонів?
  • Чи однакове виконання дії на місто та регіон?
  • Чи можна вжити будь-яких дій у будь-якому регіоні / місті?

Якщо ви можете остаточно відповісти "так" всім трьом, тоді абстракція є гарантованою.


16
Я не можу наголосити на правилі "один, два, багато". Існує нескінченна можливість щось абстрагувати / параметризувати, але корисний підмножина невеликий, часто дорівнює нулю. Точно знати, який варіант має значення, найчастіше можна визначити лише заднім числом. Тож дотримуйтесь негайних вимог * та додайте складності у міру необхідності нових вимог чи заднього огляду. * Іноді ви добре знаєте проблемний простір, тоді може бути добре додати щось, бо знаєте, що вам це потрібно завтра. Але використовуйте цю владу розумно, це також може призвести до розорення.
Крістіан Зауер

2
> "Я не знаю, де я це прочитав [..]". Можливо, ви читали Кодування жаху: Правило трьох .
Руна

10
"Правило один, два, багато" насправді існує для того, щоб не допустити побудови неправильної абстракції шляхом сліпого застосування DRY. Вся справа в тому, що два фрагменти коду можуть виглядати майже точно так само, тому захоплювати абстрагувати відмінності подалі; але на початку ви не знаєте, які частини коду є стабільними, а які - ні; окрім того, може виявитись, що вони насправді мають розвиватися незалежно (різні зміни форм, різні набори обов'язків). В будь-якому випадку неправильна абстракція працює проти вас і перешкоджає.
Філіп Мілованович

4
Очікування на більш ніж двох прикладах "тієї самої логіки" дозволяє вам бути кращим суддею того, що слід абстрагувати, і як (і насправді йдеться про управління залежностями / з'єднання між кодом з різними моделями змін).
Філіп Мілованович

1
@kukis: Реалістичну лінію слід намалювати на 2 (відповідно до коментаря Балдріка): нуль-один-багато (як це стосується відносин із базами даних). Однак це відкриває двері до марних зразків, які шукають поведінки. Дві речі можуть бути невиразно схожими, але це не означає, що вони насправді однакові. Однак, коли третя інстанція потрапляє у бійку, яка нагадує як першу, так і другу, ви можете зробити більш точне судження про те, що їх схожість справді є повторною схемою використання. Отже, лінія здорового глузду намальована в 3, яка враховує помилку людини під час виявлення "шаблонів" між двома примірниками.
Flater

44

Практика

Це Software Engineering SE, але створення програмного забезпечення - це набагато більше мистецтва, ніж інженерія. Немає універсального алгоритму, який слід слідкувати чи вимірювати, щоб визначити, наскільки достатньо використання. Як і з чим завгодно, чим більше практикуєш розробляти програми, тим краще ти будеш на ній. У вас з’явиться краще відчуття того, що «достатньо», тому що ви побачите, що піде не так і як воно піде не так, коли параметризуєте занадто багато або занадто мало.

Зараз це не дуже корисно , а як щодо деяких вказівок?

Погляньте на своє запитання. Існує багато "вона могла сказати" і "я могла". Дуже багато тверджень, які теоретизують про якусь майбутню потребу. Люди пильнують передбачення майбутнього. А ви (швидше за все) людина. Основна проблема дизайну програмного забезпечення намагається пояснити майбутнє, яке ви не знаєте.

Керівництво 1: Вам це не потрібно

Серйозно. Просто перестань. Частіше за все ця уявлена ​​проблема в майбутньому не з’являється - і вона точно не з’явиться так, як ви її уявляли.

Настанова 2: Вартість / вигода

Класно, ця маленька програма потребувала у вас декількох годин, щоб написати, можливо? Так що , якщо ваша дружина дійсно повернеться і попросити ті речі? Найгірший випадок, ви витрачаєте ще кілька годин, обробляючи разом іншу програму. У цьому випадку не так вже й багато часу, щоб зробити цю програму гнучкішою. І це не додасть великої швидкості виконання або використання пам'яті. Але нетривіальні програми мають різні відповіді. У різних сценаріях є різні відповіді. В якийсь момент витрати, очевидно, не варті користі навіть при недосконалих майбутніх навичках розповідати.

Керівництво 3: Зосередження уваги на константах

Подивіться назад на питання. У вашому оригінальному коді багато постійних вводів. 2018, 1. Постійні вставки, постійні струни ... Вони, найімовірніше, потрібні для того, щоб не бути постійними . Що ще краще, їм потрібно лише небагато часу, щоб параметризувати (або принаймні визначити як фактичні константи). Але ще одна річ, на яку слід насторожитися - це постійна поведінка . TheSystem.out.printlnнаприклад. Таке припущення про використання, як правило, є чимось, що змінюється в майбутньому, і, як правило, дуже дорого виправить. Мало того, але подібний IO робить цю функцію нечистою (разом із вилученням часового поясу дещо). Параметризуючи те, що поведінка може зробити функцію більш чистою, що призводить до підвищення гнучкості та тестабельності. Великі переваги з мінімальними витратами (особливо якщо ви робите перевантаження, яке використовується System.outза замовчуванням).


1
Це просто настанова, 1-х добре, але ти дивишся на них і йдеш "чи це колись зміниться?" І println можна було б налаштувати за допомогою функції вищого порядку - хоча Java для них не чудова.
Теластин

5
@KorayTugay: якби програма справді була для вашої дружини, яка приїхала вдома, YAGNI сказав би вам, що ваша початкова версія ідеальна, і вам більше не потрібно вкладати час для введення констант або параметрів. ЯГНІ потребує контексту - ваша програма є викидним рішенням, або програма міграції працює лише кілька місяців, чи це частина величезної ERP-системи, призначеної для використання та підтримки протягом декількох десятиліть?
Док Браун

8
@KorayTugay: Відокремлення вводу / виводу від обчислень - це основна методика структурування програми. Відокремлене генерування даних від фільтрації даних від перетворення даних від споживання даних від представлення даних. Ви повинні вивчити деякі функціональні програми, тоді ви побачите це більш чітко. У функціональному програмуванні досить часто генерувати нескінченну кількість даних, відфільтрувати лише ті, що вас цікавлять, перетворити дані у потрібний вам формат, побудувати з нього рядок і надрукувати цю рядок у 5 різних функціях, по одному на кожен крок.
Йорг W Міттаг

3
З іншого боку, рішуче слідування YAGNI призводить до необхідності постійного рефактора: "Якщо використовуватись без постійного рефакторингу, це може призвести до неорганізованого коду та масової переробки, відомої як технічна заборгованість". Отже, хоча YAGNI - це взагалі хороша річ, на неї покладається велика відповідальність за перегляд та переоцінку коду, що не те, що кожен розробник / компанія бажає робити.
Flater

4
@Telastyn: Я пропоную розширити питання на те, "чи це ніколи не зміниться, і чи є намір коду тривіально читабельним, не називаючи константу ?" Навіть для значень, які ніколи не змінюються, може бути доречним назвати їх лише для того, щоб речі читали.
Flater

27

По-перше: жоден розробник програмного забезпечення, орієнтованого на безпеку, не запише метод DestroyCity, не передаючи маркер авторизації з будь-якої причини.

Я теж можу написати що-небудь як імператив, який має очевидну мудрість, не застосовуючи її в іншому контексті. Чому потрібно авторизувати рядкове з'єднання?

По-друге: весь код при його виконанні повинен бути повністю вказаний .

Не має значення, чи було рішення жорстко закодовано на місці, чи перенесено на інший шар. У якийсь момент є фрагмент коду на якійсь мові, який знає і те, що потрібно знищити, і як його прокласти.

Це може бути в одному об’єктному файлі destroyCity(xyz), а може бути у файлі конфігурації: destroy {"city": "XYZ"}"або це може бути серія клацань та натискань клавіш в інтерфейсі користувача.

По-третє:

Дорога .. Чи можете ви надрукувати на консолі весь день заощадження по всьому світу на 2018 рік? Мені потрібно щось перевірити.

дуже різний набір вимог до:

вона хоче пройти користувальницький форматник рядків, ... цікавиться лише певними місячними періодами, ... [і] зацікавлена ​​у певні години ...

Тепер другий набір вимог, очевидно, передбачає більш гнучкий інструмент. Він має більш широку цільову аудиторію та більш широку сферу застосування. Небезпека тут полягає в тому, що найбільш гнучка програма у світі насправді є компілятором машинного коду. Це буквально програма настільки загальна, що вона може побудувати все що завгодно, щоб зробити комп'ютер будь-яким, що вам потрібно (в межах обмежень його апаратного забезпечення).

Взагалі кажучи, люди, які потребують програмного забезпечення, не хочуть чогось загального; вони хочуть чогось конкретного. Даючи більше варіантів, ви насправді ускладнюєте їхнє життя. Якби вони хотіли такої складності, вони замість цього використовували б компілятор, не запитуючи вас.

Ваша дружина запитувала функціональність і недооцінювала свої вимоги до вас. У цьому випадку це здавалося цілеспрямовано, і взагалі це тому, що вони не знають нічого кращого. Інакше вони би просто використали компілятор. Тому перша проблема полягає в тому, що ви не запитували більше деталей про те, що саме вона хотіла зробити. Чи хотіла вона керувати цим кілька років? Чи хотіла вона цього у файлі CSV? Ви не дізналися, які рішення вона хотіла прийняти сама, і що вона просила, щоб ви зрозуміли і вирішили за неї. Після того, як ви зрозуміли, які рішення потрібно відкласти, ви можете зрозуміти, як передавати ці рішення за допомогою параметрів (та інших способів, що настроюються).

При цьому, більшість клієнтів пропускають спілкування, припускають або не знають певних деталей (ака. Рішень), які вони дійсно хотіли б зробити самі, або що вони насправді не хотіли приймати (але це звучить приголомшливо). Ось чому такі важливі методи роботи, як PDSA (план-розробка-вивчення-дія). Ви запланували роботу відповідно до вимог, а потім розробили набір рішень (код). Тепер прийшов час вивчити це самостійно чи разом із клієнтом та дізнаватися про нові речі, і вони інформують ваше мислення просування вперед. Нарешті, дійте над своєю новою інформацією - оновіть вимоги, вдосконаліть процес, отримайте нові інструменти тощо ... Потім почніть планувати заново. Це дозволило б виявити будь-які приховані вимоги з часом і довести прогрес для багатьох клієнтів.

Нарешті. Ваш час важливий ; це дуже реально і дуже скінчено. Кожне рішення, яке ви приймаєте, тягне за собою багато інших прихованих рішень, і саме про це розробляє програмне забезпечення. Затримка рішення як аргументу може зробити поточну функцію більш простою, але вона робить десь ще складнішою. Чи є це рішення релевантним у тому іншому місці? Чи більш актуально тут? Чиє рішення насправді прийняти? Ви вирішуєте це; це кодування. Якщо ви повторюєте набори рішень часто, то кодифікація їх всередині якоїсь абстракції є дуже реальною. Тут є корисна перспектива XKCD . І це актуально на рівні системи, будь то функція, модуль, програма тощо.

Порада на початку передбачає, що рішення, які ваша функція не має права приймати, слід сприймати як аргумент. Проблема полягає в тому, що DestroyBaghdadфункція насправді може бути тією функцією, яка має це право.


+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" або щось подібне

У вас немає рядка формату, тому додавання його - запит на особливості, і ви повинні направити свою дружину на екземпляр своєї родини Джира. Він може бути внесений до відставання та визначити його пріоритетним на засіданні відповідного засідання комітету з управління проектами.


1
Ви (звертаючись до ОП), звичайно, не повинні додавати рядок формату, оскільки ви не повинні нічого друкувати. Єдине, що стосується цього коду, який абсолютно перешкоджає його повторному використанню - це друк. Він повинен повернути зони або карту зон, коли вони відключаються DST. (Не дивлячись на те, чому вона тільки визначає , коли йдуть від переходу на літній час , а не на ДСТ, я не розумію , це , здається, не відповідає постановка задачі ..)
David Conrad

вимога - друк до консолі. ви можете зв'язати тугий зчіп, пропустивши у вихідний потік як параметр, як я підказую
Ewan

1
Тим не менш, якщо ви хочете, щоб код був багаторазовим, не слід друкувати на консолі. Напишіть метод, який повертає результати, а потім запишіть абонента, який їх отримує, та друкує. Це також робить його перевіреним. Якщо ви хочете, щоб він давав вихід, я не передавав би вихідний потік, я передавав би споживача.
Девід Конрад

наш потік - споживач
Ewan


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()
            );
        });
    }
}

Власне, об'єктно-орієнтоване програмування було винайдено спеціально як рішення проблеми складності / гнучкості парадигми імперативу, оскільки просто немає хорошої відповіді (щоб кожен міг погодитись або дійти самостійно, як би не було), як оптимально вкажіть імперативні функції та процедури в фразі.


Це дуже детальна і продумана відповідь, але, на жаль, я вважаю, що це не вистачає позначки щодо того, про що насправді просила ОП. Він не просив уроку з передового досвіду ООП, щоб вирішити його чудовий приклад, він запитував про критерії, де ми вирішуємо вкладення часу у рішення проти узагальнення.
maple_shaft

@maple_shaft Можливо, я пропустив позначку, але, думаю, і у вас є. ОП не запитує про вкладення часу проти узагальнення. Він запитує: "Як мені знати, якими мають бути багаторазові мої методи?" Він продовжує задавати в тілі свого питання: "Якщо зруйнувати місто (місто міста) краще, ніж знищити Багдад (), чи прийняття" Акція "(" Дія "," Місто міста ") ще краще? Чому / чому ні?" Я зробив обґрунтування альтернативного підходу до інженерних рішень, який, на мою думку, вирішує проблему з'ясування того, як загальноприйняти методи та наводив приклади на підтвердження моєї претензії. Вибачте, що вам це не сподобалось.
Ступорман

@maple_shaft Чесно кажучи, тільки ОП може визначитися, якщо моя відповідь була відповідна на його питання, оскільки решта з нас могли боротися з війнами, захищаючи наші інтерпретації своїх намірів, і все це може бути однаково помилковим.
Ступорман

@maple_shaft Я додав вступ, щоб спробувати уточнити, як це стосується питання, і дав чітке розмежування відповіді та прикладу реалізації. Це краще?
Ступорман

1
Чесно кажучи, якщо ви застосуєте всі ці принципи, відповідь прийде природно, буде гладкою та масово читаною. Плюс, змінний без особливої ​​суєти. Я не знаю, хто ти, але я б хотів, щоб тебе було більше! Я продовжую видобувати реактивний код для гідного OO, і це ЗАВЖДИ наполовину розміру, більш читабельний, більш керований і все ще має нарізання / розбивання / картографування. Я думаю, що React призначений для людей, які не розуміють «основних» понять, які ви тільки що перерахували.
Stephen J

3

Досвід , знання домену та огляди коду.

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


З досвідом ви почнете розпізнавати шаблони в написаних доменних неспецифічних методах (і класах). І якщо вас взагалі цікавить код DRY, ви відчуватимете погані почуття, коли ви збираєтесь написати метод, який інстинктивно знає, що ви будете писати варіанти в майбутньому. Отже, ви інтуїтивно напишете замість цього параметризований найменш загальний знаменник.

(Цей досвід також може інстинктивно переноситися на деякі об'єкти та методи вашого домену.)

З доменними знаннями ви матимете уявлення про те, які бізнес-концепції тісно пов'язані, які поняття мають змінні, які досить статичні тощо.

З оглядом коду недостатні та параметризації будуть швидше зафіксовані до того, як він стане виробничим кодом, оскільки ваші колеги (сподіваємось) матимуть унікальний досвід та перспективи, як щодо домену, так і кодування в цілому.


Зважаючи на це, нові розробники зазвичай не мають цих Spidey Senses або досвідченої групи однолітків, щоб відразу спертися на них. І навіть досвідчені розробники отримують перевагу від базової дисципліни, щоб провести їх через нові вимоги - або через туманні дні в мозку. Отже, ось що я запропонував би для початку :

  • Почніть з наївної реалізації, з мінімальної параметризації.
    (Включіть будь-які параметри, які ви вже знаєте що вам знадобляться, очевидно ...)
  • Видаліть магічні числа та рядки, перемістивши їх у конфігурації та / або параметри
  • Фактор "великих" методів переходить на більш дрібні, добре названі методи
  • Рефакторні надмірно методи (якщо це зручно) у загальний знаменник, параметризуючи відмінності.

Ці дії не обов'язково проводяться у зазначеному порядку. Якщо ви сідаєте, щоб написати метод, який ви вже знаєте, що він є надмірним із існуючим методом , перескочіть прямо на рефакторинг, якщо це зручно.(Якщо для рефактора не буде потрібно значно більше часу, ніж було б просто написати, протестувати та підтримувати два методи.)

Але, крім простого досвіду тощо, я раджу досить мінімалістичний код DRY-ing. Відтворити очевидні порушення не важко. І, якщо ви занадто завзято , ви можете отримати "надмірно сухий" код, який навіть важче читати, розуміти та підтримувати, ніж еквівалент "WET".


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-sensses, огляди коду .. Але на що відповідь is takeActionOnCity(Action action, City city) better? null?
Корай Тугай

1
@svidgen Зрозуміло, ще один спосіб абстрагувати це без зайвих зусиль - інвертувати залежність - запропонувати функції повернути список міст, а не робити будь-які дії над ними (наприклад, "println" у вихідному коді). Це можна абстрагувати далі, якщо необхідно, але саме ця зміна сама по собі піклується про половину доданих вимог до оригінального питання - і замість однієї нечистої функції, яка може робити всілякі погані речі, у вас просто функція який повертає список, і абонент робить погані речі.
Луань

2

Та сама відповідь, що і щодо якості, зручності використання, технічної заборгованості тощо:

Настільки ж багаторазові, як ви, користувач, 1 вони повинні бути

Це, в основному, виклик судження - чи витрати на проектування та підтримку абстракції будуть погашені витратами (= час та зусилля), це допоможе вам заощадити.

  • Зауважте фразу "внизу лінії": тут є механік виплат, тому це залежатиме від того, наскільки ви будете працювати з цим кодом далі. Наприклад:
    • Це разовий проект, чи він буде поступово вдосконалюватися протягом тривалого часу?
    • Ви впевнені у своєму дизайні, чи вам, ймовірно, доведеться брати брухт чи іншим чином кардинально змінити його для наступного проекту / рубіжу (наприклад, спробувати іншу рамку)?
  • Прогнозована вигода також залежить від вашої здатності передбачати майбутнє (зміни в додатку). Іноді ви обгрунтовано можете побачити місця, на які збирається ваш додаток. Більше разів ви думаєте, що можете, але насправді не можете. Основні правила тут - це принцип YAGNI і правило три - обидва акцентують увагу на відпрацьовуванні того, що ви знаєте, зараз.

1 Це конструкція коду, тож ви в цьому випадку "користувач" - користувач вихідного коду


1

Існує чіткий процес, за яким ви можете слідувати:

  • Напишіть невдалий тест для однієї функції, яка сама по собі є "річчю" (тобто, не якийсь довільний розкол функції, де ні половина насправді не має сенсу).
  • Напишіть абсолютний мінімальний код, щоб він пройшов зелений колір, а не рядок більше.
  • Промийте і повторіть.
  • (Refactor невпинно при необхідності, що повинно бути легким через велике покриття тесту.)

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

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

Я вважаю приклад вашого міста дивним; Я б, швидше за все, ніколи не твердо кодував назву міста. Це так очевидно, що додаткові міста будуть включені пізніше, чим би ви не займалися. Але іншим прикладом можуть бути кольори. У деяких випадках жорстке кодування "червоним" або "зеленим" було б можливим. Наприклад, світлофори - це настільки всюдисущий колір, що ви можете просто відійти від нього (і ви завжди можете рефактор). Різниця полягає в тому, що "червоний" і "зелений" мають універсальне, "твердо кодоване" значення у нашому світі, це малоймовірно, що він коли-небудь зміниться, і альтернативи насправді також немає.

Ваш перший метод економії світла просто порушений. Незважаючи на те, що він відповідає специфікаціям, жорсткий кодекс 2018 року є особливо поганим, оскільки а) це не зазначено в технічному "договорі" (у назві методу в даному випадку), і б) скоро буде застарілим, тому поломка включено з першості. Для речей, пов’язаних із часом / датою, дуже рідко має сенс жорстко кодувати певне значення, оскільки час йде з часом. Але крім цього, все інше підходить для обговорення. Якщо ви даєте йому простий рік, а потім завжди підраховуєте повний рік, продовжуйте. Більшість перерахованих вами речей (форматування, вибір меншого діапазону тощо) кричить про те, що ваш метод робить занадто багато, і натомість він, мабуть, повинен повернути список / масив значень, щоб абонент міг сам форматувати / фільтрувати.

Але врешті-решт, більшість із цього - це думка, смак, досвід та особисті упередження, тому не варто занадто хвилюватися з цього приводу.


Щодо другого до останнього абзацу - подивіться спочатку "вимоги", тобто ті, на яких ґрунтувався перший метод. Він визначає 2018 рік, тому код технічно правильний (і, ймовірно, відповідає вашому підходу, орієнтованому на функції).
dwizum

@dwizum, це правильно щодо вимог, але там назва методу вводить в оману. У 2019 році будь-який програміст, просто дивлячись на ім'я методу, припустив би, що він робить все, що завгодно (можливо, поверне значення для поточного року), а не 2018 рік ... Я додам речення до відповіді, щоб зрозуміти, що я мав на увазі.
AnoE

1

Я прийшов до думки, що існує два різновиди коду для багаторазового використання:

  • Код, який можна використовувати повторно, тому що це така основна, основна річ.
  • Код, який можна використовувати багаторазово, оскільки він має параметри, переопределення та гачки для повсюди.

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

Ці речі є настільки загальними, дуже абстрактними способами, що їх застосовують повсюдно у щоденному програмуванні. Якщо ви виявите, що пишете код спеціального призначення, це було б простіше якби він був більш абстрактним / загальним (наприклад, якщо у нас є "список замовлень клієнтів", ми можемо викинути "замовлення клієнта", щоб отримати "список" ) тоді може бути гарною ідеєю витягнути це. Навіть якщо він не буде повторно використаний, він дозволяє нам відключати непов'язані функціональні можливості.

Другий різновид - це те, де у нас є якийсь конкретний код, який вирішує справжню проблему, але це робиться, приймаючи цілу купу рішень. Ми можемо зробити це більш загальним / багаторазовим, використовуючи «м'яке кодування» цих рішень, наприклад, перетворивши їх у параметри, ускладнивши реалізацію та випікання в більш конкретних деталях (тобто знання того, які гачки ми могли б захотіти для відміни). Здається, ваш приклад такого роду. Проблема такого роду повторного використання полягає в тому, що ми можемо в кінцевому підсумку намагатися відгадати випадки використання інших людей або наших майбутніх самих себе. Врешті-решт у нас може бути стільки параметрів, що наш код не є корисним, не кажучи вже про багаторазове використання! Іншими словами, для дзвінка потрібно більше зусиль, ніж просто написання власної версії. Тут важливим є YAGNI (вам це вже не потрібно). Багато разів такі спроби "багаторазового використання" коду в кінцевому підсумку не використовуються повторно, оскільки це може бути непорівнянним із тими випадками використання, які є більш принциповими, ніж можуть враховувати параметри, або ті потенційні користувачі, скоріше, скочують свої власні (чорт, подивіться, стандарти та бібліотеки, автори яких вказують слово "Просте", щоб відрізнити їх від попередників!).

Ця друга форма "повторного використання" в основному повинна здійснюватися за потребою. Звичайно, ви можете вставити деякі "очевидні" параметри там, але не починайте намагатися передбачати майбутнє. ЯГНІ.


Чи можемо ми сказати, що ви погоджуєтесь, що мій перший прийом був добре, коли навіть рік був жорстким? Або якби ви спочатку виконували цю вимогу, ви зробили б рік параметром у своєму першому прийнятті?
Корай Тугай

Ваше перше прийняття було чудово, оскільки вимога була одноразовим сценарієм, щоб "перевірити щось". Це провалює її "етичний" тест, але вона не дає тесту "без догми". "Вона може сказати ..." придумує вимоги, які вам не потрібні.
Варбо

Ми не можемо сказати, яке «зруйнувати місто» «краще» без додаткової інформації: destroyBaghdadце одноразовий сценарій (або, принаймні, безвідмовний). Можливо, зруйнування будь-якого міста буде вдосконаленням, але що робити, якщо destroyBaghdadпрацює, затопивши Тигр? Це може бути багаторазовим для Мосула та Басри, але не для Мекки чи Атланти.
Варбо

Я бачу, так що ви не згодні з Натаніелем Боренштейном, власником цитати. Я намагаюся зрозуміти повільно, я думаю, читаючи всі ці відповіді та дискусії.
Корай Тугай

1
Мені подобається ця диференціація. Це не завжди зрозуміло, і завжди є "випадки на кордоні". Але в цілому я також прихильник "будівельних блоків" (часто у формі staticметодів), які суто функціональні та низькорівневі, і на відміну від цього, прийняти рішення про "налаштування параметрів і гаків", як правило, це те, де ви повинні побудувати деякі структури, які просять бути виправданими.
Marco13

1

Уже є багато відмінних і вичерпних відповідей. Деякі з них заглиблюються в конкретні деталі, викладають певні точки зору на методології розробки програмного забезпечення взагалі, а деякі з них, безумовно, містять суперечливі елементи чи "думки".

У відповіді Варбо вже вказували різні види повторного використання. А саме, чи то багаторазове використання, тому що це фундаментальний будівельний блок, чи то щось багаторазове, тому що воно певним чином "загальне". Посилаючись на останнє, є щось, що я б вважав якоюсь мірою для повторного використання:

Чи може один метод наслідувати інший.

Стосовно прикладу з питання: Уявіть, що метод

void dayLightSavings()

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

publicvoid dayLightSavings()

Це можна реалізувати так, як ви показали у своїй відповіді. Тепер хтось хоче параметризувати це роком. Тож ви можете додати метод

publicvoid dayLightSavings(int year)

і змінити оригінальну реалізацію на просто

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

Наступні "запити на функції" та узагальнення відповідають такому ж шаблону. Тож якщо і лише за наявності попиту на найбільш загальну форму, ви можете її реалізувати, знаючи, що ця найзагальніша форма дозволяє здійснити тривіальні реалізації більш конкретних:

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

Якби ви передбачили майбутні розширення та запити на функції та мали певний час у вашому розпорядженні і хотіли провести нудні вихідні з (потенційно марними) узагальненнями, ви могли б почати з самого загального з самого початку. Але лише як приватний метод. Поки ви відкрили лише простий метод, який запитував клієнт як публічний метод, ви в безпеці.

tl; dr :

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


dayLightSavings()дзвонити dayLightSavings(2018)мені не здається гарною ідеєю.
Корай Тугай

@KorayTugay Коли початковий запит полягає в тому, що він повинен надрукувати "економію денного світла на 2018 рік", тоді це добре. Насправді, це є саме метод , який ви реалізуєте спочатку. Якщо він повинен надрукувати "економію денного світла за поточний рік , то, звичайно, ви б зателефонували dayLightSavings(computeCurrentYear());...
Marco13,

0

Добре правило: ваш метод повинен бути таким же багаторазовим, як… багаторазовий.

Якщо ви очікуєте, що ви будете викликати свій метод лише в одному місці, він повинен мати лише параметри, відомі веб-сайту виклику та недоступні для цього методу.

Якщо у вас більше абонентів, ви можете вводити нові параметри до тих пір, поки інші абоненти можуть передавати ці параметри; інакше вам потрібен новий метод.

Оскільки кількість абонентів може зрости з часом, вам потрібно бути готовим до рефакторингу чи перевантаження. У багатьох випадках це означає, що ви повинні почувати себе безпечно вибирати вираз і запускати дію «параметр вилучення» вашої IDE.


0

Ультракоротка відповідь: Чим менше з'єднання чи залежності від іншого коду, ваш загальний модуль має тим більше його використання.

Ваш приклад залежить лише від цього

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

тому теоретично це може бути багаторазово використане.

На практиці я не думаю, що у вас коли-небудь буде другий шафа, котрий потребує цього коду, тому дотримуючись принципу yagni, я б не зробив його для багаторазового використання, якщо не більше 3-х різних проектів, які потребують цього коду.

Інші аспекти повторного використання - це простота використання та документація, що є основою тестової розробки . Це корисно, якщо у вас є простий тестовий блок, який демонструє / документує просте використання вашого загального модуля як приклад кодування для користувачів вашої бібліотеки.


0

Це гарна можливість викласти правило, яке я вигадав нещодавно:

Бути хорошим програмістом означає вміти передбачати майбутнє.

Звичайно, це категорично неможливо! Зрештою, ви ніколи не знаєте напевно, які узагальнення вам будуть корисні згодом, які пов’язані завдання ви хочете виконати, які нові функції бажають ваші користувачі та c. Але досвід іноді дає вам приблизне уявлення про те, що може стати у нагоді.

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

Тож якщо ви вважаєте, що узагальнення дуже ймовірно, що це потрібно, це часто варто зробити (якщо це не додасть багато роботи чи складності); але якщо це здається набагато менш ймовірним, то, мабуть, це не так (якщо тільки це не дуже легко та / або спрощує код).

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


0

По-перше, найкраща відповідь на тему "Як я знаю, якими мають бути багаторазові мої методи?" - це "досвід". Зробіть це кілька тисяч разів, і ви, як правило, отримаєте правильну відповідь. Але як тизер, я можу дати вам останню рядок цієї відповіді: Ваш клієнт скаже вам, скільки гнучкості та скільки шарів узагальнення ви повинні прагнути.

У багатьох з цих відповідей є конкретні поради. Мені хотілося дати щось більш загальне ... бо іронія - це надто весело, щоб передати!

Як зазначалось у деяких відповідях, загальність є дорогою. Однак насправді це не так. Не завжди. Розуміння витрат є важливим для гри у багаторазове використання.

Я зосереджуюсь на тому, щоб розставити речі за шкалою від "незворотного" до "зворотного". Це гладка шкала. Єдина справді незворотна річ - «витрачений на проект час». Ви ніколи не отримаєте цих ресурсів. Трохи менш оберненими можуть бути "золоті наручники", такі як API Windows. Застарілі функції залишаються в цьому API протягом десятиліть, оскільки бізнес-модель Microsoft вимагає цього. Якщо у вас є клієнти, відносини яких були б остаточно пошкоджені, скасувавши якусь функцію API, це слід вважати незворотним. Дивлячись на інший кінець шкали, у вас є такі речі, як код прототипу. Якщо вам не подобається куди йде, ви можете просто викинути його. Трохи менш оберненими можуть бути API внутрішнього використання. Їх можна буде відновити, не турбуючи клієнта,час (самий незворотний ресурс з усіх!)

Тому поставте їх на шкалу. Тепер ви можете застосувати евристику: чим більше щось обернено, тим більше ви можете використовувати це для майбутньої діяльності. Якщо щось незворотне, використовуйте його лише для конкретних завдань, орієнтованих на клієнтів. Тому ви бачите такі принципи, як екстремальне програмування, які пропонують робити тільки те, що вимагає клієнт, і нічого більше. Ці принципи гарні в тому, щоб ви не робили те, про що шкодуєте.

Такі речі, як принцип DRY, пропонують спосіб перемістити цей баланс. Якщо ви повторюєте себе, це можливість створити те, що в основному є внутрішнім 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 рік.) Дякуємо за ваш час та відповідь btw.
Корай Тугай

@KorayTugay Не знаючи додаткових деталей, я б сказав, що жорстке кодування було правильним підходом. У вас не було можливості дізнатися, чи буде вам потрібен майбутній код DLS, і будь-яке уявлення про те, який запит вона може подати далі. І якщо ваш клієнт намагається перевірити вас, вони отримують те, що отримують = D
Корт Аммон

0

жоден етично навчений програмний інженер ніколи не погодиться написати процедуру DestroyBaghdad. Натомість основна професійна етика вимагає від нього написати процедуру DestroyCity, якій Багдад може бути заданий як параметр.

Це щось відоме в просунутих інженерних колах програмного забезпечення як "жарт". Жарти не повинні бути такими, які ми називаємо "справжніми", хоча, щоб бути смішними, вони, як правило, повинні натякати на щось правдиве.

У цьому конкретному випадку "жарт" не є "правдою". Робота, пов'язана з написанням загальної процедури для знищення будь-якого міста, - це, можна сміливо припустити, на порядок більше того, що потрібно для знищення одного конкретного містамісто. В іншому випадку кожен, хто знищив одне або жменю міст (біблійний Джошуа, скажемо так, чи президент Трумен), може тривільно узагальнити те, що вони зробили, і зможе зруйнувати абсолютно будь-яке місто за бажанням. Це фактично не так. Методи, якими ті двоє людей знаменито застосовували для знищення невеликої кількості конкретних міст, не обов'язково діятимуть у будь-якому місті в будь-який час. Іншому місту, стіни якого мали різну резонансну частоту або висотна оборона повітря була значно кращою, потребували б незначних або кардинальних змін підходу (труба з різними звуками або ракета).

Це також призводить до підтримання коду від змін у часі: зараз існує досить багато міст, котрі не підпадають під жоден із цих підходів, завдяки сучасним методам будівництва та всюдисущому радіолокації.

Розробка та тестування цілком загального засобу, який знищить будь-яке місто, перш ніж погодитися знищити лише одне місто, - це відчайдушно неефективний підхід. Жоден етично підготовлений програмний інженер не намагатиметься узагальнити проблему в такій мірі, що вимагає на замовлення на більшу кількість роботи, ніж їх роботодавець / клієнт насправді повинен платити, без продемонстрованої вимоги.

То що правда? Іноді додавання загальності тривіальне. Чи повинні ми завжди додавати спільності, коли це тривіально? Я б все-таки заперечував «ні, не завжди» через довгострокове обслуговування. Припустимо, що під час написання письма всі міста в основному однакові, тому я продовжую роботу з DestroyCity. Після того, як я написав це, разом із тестами інтеграції, які (завдяки кінцево переліченому простору входів) переглядають кожне відоме місто та переконуються, що функція працює на кожному (не впевнено, як це працює. Напевно, називає City.clone () та знищує клон? Так чи інакше).

На практиці функція використовується виключно для того, щоб знищити Багдад, припустимо, хтось будує нове місто, стійке до моїх прийомів (це глибоко під землею чи щось таке). Тепер у мене невдача інтеграційного тесту для випадків використання , який навіть насправді не існує , і перш ніж я можу продовжувати кампанію терору проти невинних цивільних осіб Іраку, я мушу розібратися, як знищити Субтрарію. Неважливо, це етична чи ні, це німий і марний час мого відродження .

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

Добре, тому за допомогою відповідних застережень легко написати функцію, яка виконує часові пояси протягом ряду років, скажімо, 1970 - сьогодні. Але так само просто взяти функцію, яку ви насправді написали, та узагальнити її для параметризації року. Тож насправді не є більш етичним / розумним узагальнювати зараз, це робити те, що ви зробили, а потім узагальнювати, якщо і коли це потрібно.

Однак, якби ви знали, чому ваша дружина хоче перевірити цей список DST, ви б мали усвідомлену думку про те, чи вона, можливо, знову задасть це запитання в 2019 році, і якщо так, чи можете ви вийняти себе з цієї петлі, надавши її функція, яку вона може викликати, не потребуючи її перекомпілювати. Після того як ви зробили цей аналіз, відповідь на питання "чи слід це узагальнити на останні роки", може бути "так". Але ви створюєте ще одну проблему для себе - це те, що дані часового поясу в майбутньому є лише тимчасовими, і тому, якщо вона розпочне їх на 2019 рік сьогодні, вона може чи не може усвідомити, що це її найкраща здогадка. Тож вам ще доведеться написати купу документації, яка б не потрібна для менш загальної функції ("дані надходять із бази даних часових поясів. Якщо ви відмовляєтесь від особливої ​​справи, то весь час, коли ви це робите, вона не може взятися за завдання, для якого їй потрібні дані про 2018 рік, через якусь нісенітницю про 2019 рік вона навіть не хвилює.

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


0

Цю лінію можна легко провести, оскільки повторна зручність використання, як визначено космонавтами архітектури, - це боллокінг.

Майже весь код, створений розробниками додатків, надзвичайно доменний. Це не 1980 рік. Майже все, що варто турбувати, вже знаходиться в рамках.

Абстракції та конвенції вимагають документації та навчальних зусиль. Будь ласка, перестаньте створювати нові лише заради цього. (Я дивлюся на вас , JavaScript!)

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

Ваш роботодавець із задоволенням платить за все це? Я скажу ні.

Абстракція - це ціна, яку ми платимо за гнучкість. Це робить наш код складнішим і складнішим для розуміння. Якщо гнучкість не відповідає справжній і теперішній потребі, просто не варто, тому що YAGNI.

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

У цьому випадку я пробачу автору. Абстракція насправді необхідно , тому що це є основою код , який націлений на п'ять дуже різних цілей рендеринга: WinForms, WPF, DotNet Core, Mono і PDFsharp.

Але це лише підкреслює мою думку: ви майже точно не є робите чогось надзвичайно складного (HTML-рендерінг із таблицями стилів), орієнтованого на декілька платформ із заявленою метою високої продуктивності на всіх платформах.

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

Все це непряме рішення вирішує проблему, якої у вас немає, і робить ваш код набагато важчим для читання, що значно збільшує витрати на технічне обслуговування і є величезною послугою для роботодавця. На щастя, люди, які повинні скаржитися на це, не в змозі зрозуміти, що ви їм робите.

Контр-аргумент полягає в тому, що такий вид абстракції підтримує тестові розробки, але я вважаю, що TDD теж є корисними, оскільки передбачає, що бізнес має чітке, повне і правильне розуміння його вимог. TDD чудово підходить для NASA та програмного забезпечення для управління медичним обладнанням та автомобілями, що керують автотранспортом, але дуже дорогим для всіх інших.


До речі, передбачити всю економію дня в світі неможливо . Зокрема, в Ізраїлі проходить близько 40 переходів щороку, які скачуть всюди, тому що ми не можемо, щоб люди молилися в неправильний час, і бог не економив денне світло.


Хоча я погоджуюся з тим, що ви сказали про абстракції та зусилля в навчанні, і особливо коли це спрямоване на гидоту, яку називають "JavaScript", я рішуче не погоджуюся з першим реченням. Повторне використання може відбуватися на багатьох рівнях, воно може зайти занадто далеко вниз , а код викидання може бути записаний програмованими програмістами. Але є люди, які досить ідеалістичні, щоб принаймні націлитись на код багаторазового використання. Шкода за вас, якщо ви не бачите переваг, які це може мати.
Marco13

@ Marco13 - Оскільки ваше заперечення є розумним, я розширю питання.
Пітер Вон

-3

Якщо ви використовуєте принаймні java 8, ви б написали клас WorldTimeZones, щоб вказати, що по суті представляє собою набір часових поясів.

Потім додайте метод фільтра (Predicate filter) до класу WorldTimeZones. Це забезпечує можливість абоненту фільтрувати все, що вони хочуть, передаючи лямбда-вираз як параметр.

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

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


3
Це хороші узагальнюючі ідеї, однак ця відповідь повністю не вистачає позначки на запитання. Питання полягає не в тому, як найкраще узагальнити рішення, а в тому, де ми проводимо межу узагальненнях і як ми зважуємо ці міркування етично.
maple_shaft

Тому я кажу, що ви повинні зважити їх за кількістю випадків використання, які вони підтримують, модифікованими за складністю створення. Метод, який підтримує один випадок використання, не такий цінний, як метод, який підтримує 20 випадків використання. З іншого боку, якщо все, що ви знаєте, це один випадок використання, і для цього потрібно 5 хвилин для кодування - займіться цим. Часто методи кодування, що підтримують конкретні способи використання, інформують вас про спосіб узагальнення.
Родні П. Барбаті
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.