Чи має бути метод прощати аргументи, які передаються? [зачинено]


21

Припустимо, у нас є метод, foo(String bar)який працює лише на рядках, що відповідають певним критеріям; наприклад, він повинен бути малим, не повинен бути порожнім або мати лише пробіл і повинен відповідати шаблону [a-z0-9-_./@]+. У документації до методу зазначено ці критерії.

Якщо метод відхиляє будь-які відхилення від цього критерію, чи метод повинен прощати деякі критерії? Наприклад, якщо початковий метод є

public void foo(String bar) {
    if (bar == null) {
        throw new IllegalArgumentException("bar must not be null");
    }
    if (!bar.matches(BAR_PATTERN_STRING)) {
        throw new IllegalArgumentException("bar must match pattern: " + BAR_PATTERN_STRING);
    }
    this.bar = bar;
}

І друге метод прощення - це

public void foo(String bar) {
    if (bar == null) {
        throw new IllegalArgumentException("bar must not be null");
    }
    if (!bar.matches(BAR_PATTERN_STRING)) {
        bar = bar.toLowerCase().trim().replaceAll(" ", "_");
        if (!bar.matches(BAR_PATTERN_STRING) {
            throw new IllegalArgumentException("bar must match pattern: " + BAR_PATTERN_STRING);
        }
    }
    this.bar = bar;
}

Чи слід змінити документацію на те, що вона буде перетворена та встановлена ​​на перетворене значення, якщо це можливо, або метод повинен бути максимально простим та відхиляти будь-які відхилення? У цьому випадку barможе бути встановлений користувачем програми.

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


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

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

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

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

2
@Boggin Я також хотів би вказати на "Принцип надійності", який переглянули . Складність виникає, коли потрібно розширити реалізацію, і прощаюча реалізація призводить до неоднозначного випадку з розширеною реалізацією.

Відповіді:


47

Ваш метод повинен робити те, що говорить, що робить.

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

Однак, якщо визначена логіка не є зручною для користувачів, її, можливо, слід вдосконалити.


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

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

20

Є кілька моментів:

  1. Ваша реалізація повинна виконувати те, що зазначено в задокументованому договорі, і більше нічого не повинна робити.
  2. Простота важлива як для контракту, так і для виконання, хоча більше для першого.
  3. Спроба виправити помилкове введення додає складності, контрінтуїтивно не лише контракту та виконання, але й використання.
  4. Помилки слід виявляти рано, лише якщо це покращує налагоджуваність і не надто сильно загрожує ефективності.
    Пам’ятайте, що існують твердження про налагодження для діагностики логічних помилок у режимі налагодження, що здебільшого знімає будь-які проблеми щодо продуктивності.
  5. Ефективність, наскільки це дозволяє час та гроші, не заважаючи простоті, завжди є метою.

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


Експеримент у реальному житті, коли нечіткий і вседозволений введення - це HTML.
Це призвело до того, що кожен робив це трохи по-іншому, і специфікація, тепер це задокументовано, є мамантом, насиченим особливими випадками.
Дивіться закон Постеля (" Будьте консервативними в тому, що ви робите, будьте ліберальні у тому, що ви приймаєте від інших ") і критику, який торкається цього ( Або набагато краще, про який мені повідомив MichaelT ).


Ще один критичний твір автора sendmail: Принцип надійності переглянуто

15

Поведінка методу має бути чіткою, інтуїтивно зрозумілою, передбачуваною та простою. Загалом, ми повинні дуже вагатися, щоб зробити додаткову обробку на вхід абонента. Такі здогадки про те, що призначений абонент незмінно мають багато крайніх випадків, які призводять до небажаної поведінки. Розглянемо операцію так само просто, як приєднання шляху до файлу. Багато (або, можливо, навіть більшість) функцій, що приєднуються до файлового шляху, мовчки відкинуть будь-які попередні шляхи, якщо один із об'єднаних шляхів виявиться вкоріненим! Наприклад, /abc/xyzприєднавшись до /evil, результат буде справедливим /evil. Це майже ніколи не те, що я маю намір, коли я приєднуюся до файлових шляхів, але оскільки немає інтерфейсу, який би веде себе таким чином, я змушений або мати помилки, або написати додатковий код, що охоплює ці випадки.

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

  • Сирий функціонал, без попередньої обробки.
  • Крок попередньої обробки сам по собі .
  • Поєднання необробленої функціональності та попередньої обробки.

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

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


2
+1 для передбачуваного І ще +1 (я бажаю) для простого. Я набагато скоріше ви допоможете мені помітити і виправити мої помилки, ніж намагатись їх приховати.
Джон М Гант

4

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

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

З іншого боку, якби дані надходили, скажімо, з файлів властивостей, зібраних технічними людьми, які повинні розуміти це "Fred Mertz" != "FredMertz", я був би схильний посилити відповідність і заощадити витрати на розробку.

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


3

Ви згадуєте деякий контекст, з якого випливає це питання.

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

Якщо ви хочете перетворити дані, що надходять із бази даних користувачів, дещо прощаючим способом, поставте цю функціональність в окремий метод перетворення та документуйте функціональність, пов'язаний з нею .

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

Великий акцент тут ясність і документ , що код робить .


-1
  1. Ви можете назвати метод відповідно до дії, наприклад doSomething (), takeBackUp ().
  2. Щоб полегшити технічне обслуговування, ви можете зберігати загальні договори та перевірку дій на різних процедурах. Зателефонуйте їм відповідно до випадків використання.
  3. Оборонне програмування: ваша процедура обробляє широкий спектр вхідних даних, у тому числі (Мінімум речей, що стосуються випадків використання, має бути покрито в будь-якому випадку)
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.