Я чув деякі голоси, які говорять про те, що перевірка повернутого нульового значення методами - це погана конструкція. Я хотів би почути деякі причини цього.
псевдокод:
variable x = object.method()
if (x is null) do something
Я чув деякі голоси, які говорять про те, що перевірка повернутого нульового значення методами - це погана конструкція. Я хотів би почути деякі причини цього.
псевдокод:
variable x = object.method()
if (x is null) do something
Відповіді:
Обґрунтування того, що не повертати нуль, полягає в тому, що вам не потрібно перевіряти його, а значить, вашому коду не потрібно йти іншим шляхом, заснованим на поверненому значенні. Ви можете перевірити Null Object Pattern, який надає додаткову інформацію про це.
Наприклад, якщо я мав би визначити метод на Java, який повертає колекцію, я, як правило, вважаю за краще повернути порожню колекцію (тобто Collections.emptyList()
), а не нульову, оскільки це означає, що мій код клієнта чистіший; напр
Collection<? extends Item> c = getItems(); // Will never return null.
for (Item item : c) { // Will not enter the loop if c is empty.
// Process item.
}
... що чистіше, ніж:
Collection<? extends Item> c = getItems(); // Could potentially return null.
// Two possible code paths now so harder to test.
if (c != null) {
for (Item item : c) {
// Process item.
}
}
null
є "контейнером", який містить нуль або один покажчик на об'єкти. (Ось, безумовно, це чітко моделюється Haskell's Maybe
у тих випадках, і це все краще для цього.)
Ось причина.
У « Чистому коді» Роберта Мартіна він пише, що повернення нуля - це поганий дизайн, коли замість цього ви можете повернути, скажімо, порожній масив. Оскільки очікуваний результат - це масив, чому б і ні? Це дозволить вам повторити результат без додаткових умов. Якщо це ціле число, можливо, 0 буде достатньо, якщо це хеш, порожній хеш. тощо.
Передумова полягає в тому, щоб не змушувати код виклику негайно обробляти проблеми. Код виклику може не захотіти стосуватися себе. Ось чому також у багатьох випадках винятки кращі, ніж нульові.
Хороші способи повернення нуля:
Погане використання: намагання замінити або приховати виняткові ситуації, такі як:
У цих випадках кидання виключення є більш адекватним, оскільки:
Однак винятки не слід використовувати для обробки нормальних умов роботи програми, таких як:
boolean login(String,String)
здається прекрасним, і такAuthenticationContext createAuthContext(String,String) throws AuthenticationException
Так, повернення NULL - це жахливий дизайн в об'єктно-орієнтованому світі. Коротше кажучи, використання NULL призводить до:
Перегляньте цю публікацію в блозі, щоб отримати детальне пояснення: http://www.yegor256.com/2014/05/13/why-null-is-bad.html . Більше в моїй книзі « Елегантні об’єкти» , розділ 4.1.
bool TryGetBlah(out blah)
або , FirstOrNull()
або MatchOrFallback(T fallbackValue)
.
isEmpty()
), і лише якщо це правда, тоді зателефонуйте методу. Люди заперечують проти другого, говорять, що це слабша продуктивність - але, як каже філософія Unix, "цінність людського часу за машинним часом" (тобто тривіально повільніша продуктивність витрачає менше часу, ніж розробники налагоджувального коду, що дає помилкові помилки).
Хто каже, що це поганий дизайн?
Перевірка нулів є звичайною практикою, навіть заохочується, інакше ви ризикуєте NullReferenceExceptions скрізь. Краще виправити помилку витончено, ніж викидати винятки, коли цього не потрібно.
Виходячи з того, що ви сказали до цього часу, я думаю, що інформації недостатньо.
Повернення нуля з методу CreateWidget () здається поганим.
Повернення нуля з методу FindFooInBar () здається нормальним.
Create...
повертає новий екземпляр або кидає ; Get...
повертає очікуваний наявний екземпляр або кидає ; GetOrCreate...
повертає наявний екземпляр або новий екземпляр, якщо його немає, або кидає ; Find...
повертає наявний екземпляр, якщо він існує, абоnull
. Для запитів колекції - Get...
завжди повертає колекцію, яка порожня, якщо не знайдено відповідних елементів.
Його винахідник каже, що це помилка на мільярд доларів!
Це залежить від мови, якою ви користуєтесь. Якщо ви використовуєте мову на зразок C #, де ідіоматичним способом вказівки на відсутність значення є повернення null, то повернення null - це хороший дизайн, якщо у вас немає значення. Крім того, у таких мовах, як Haskell, які ідіоматично використовують монаду "Maybe" для цього випадку, повернення null було б поганим дизайном (якби це було можливо).
null
мов, як C # і Java, часто перевантажується і надає певного значення в домені. Якщо ви шукаєте null
ключове слово в мовних специфікаціях, воно просто означає "недійсний покажчик". Це, ймовірно, не означає нічого в жодній проблемній області.
null
чи "не ініціалізовано"null
Якщо ви прочитаєте всі відповіді, стає зрозумілим, відповідь на це питання залежить від виду методу.
По-перше, коли відбувається щось виняткове (IOproblem тощо), логічно виключаються винятки. Коли саме щось виняткове, мабуть, щось на іншу тему ..
Всякий раз, коли очікується, що метод не дасть результатів, існують дві категорії:
Доки у нас є офіційний спосіб позначити, що функція може або не може повернути нуль, я намагаюся скласти умову іменування для позначення цього.
Так само, як у вас є умова " Спробуйте щось" () для методів, які, як очікується, вийдуть з ладу, я часто називаю свої методи " Щось безпечним" () коли метод повертає нейтральний результат замість null.
З назвою я ще не в порядку, але нічого кращого не міг придумати. Тому я зараз з цим біжу.
У мене є конвенція в цій галузі, яка мені добре служила
Для запитів з одним елементом:
Create...
повертає новий екземпляр або кидаєGet...
повертає очікуваний наявний екземпляр або кидаєGetOrCreate...
повертає наявний екземпляр, або новий екземпляр, якщо його немає, або кидаєFind...
повертає наявний екземпляр, якщо він існує, абоnull
Для запитів колекції:
Get...
завжди повертає колекцію, яка порожня, якщо не знайдено відповідних [1] елементів[1] задано деякі критерії, явні або неявні, вказані у назві функції або як параметри.
Get
коли очікую, що він буде там, так що якщо його немає, то це помилка, і я кидаю - мені ніколи не потрібно перевіряти значення повернення. Я використовую, Find
якщо я дійсно не знаю, є він чи ні - тоді мені потрібно перевірити повернене значення.
Винятки становлять виключення за інших обставин.
Якщо ваша функція призначена для пошуку атрибута, асоційованого з даним об'єктом, а у цього об’єкта немає такого атрибута, можливо, буде доречно повернути null. Якщо об'єкта не існує, кидання винятку може бути більш доречним. Якщо функція призначена для повернення списку атрибутів, а повернути їх немає, повернення порожнього списку має сенс - ви повертаєте всі нульові атрибути.
Добре повернути нуль, якщо це зробити певним чином:
public String getEmployeeName(int id){ ..}
У такому випадку має значення повернути null, якщо ідентичний номер не відповідає існуючому об'єкту, оскільки він дозволяє відрізнити випадок, коли не було знайдено відповідності від дійсної помилки.
Люди можуть вважати це поганим, оскільки його можна зловживати як "особливе" значення повернення, яке вказує на стан помилки, що не так добре, трохи схоже на повернення кодів помилок з функції, але заплутане, оскільки користувач повинен перевірити повернення на null, а не ловити відповідні винятки, наприклад
public Integer getId(...){
try{ ... ; return id; }
catch(Exception e){ return null;}
}
Це не обов'язково поганий дизайн - як і для багатьох дизайнерських рішень, це залежить.
Якщо результат методу - це те, що не дало б хорошого результату при нормальному використанні, повернення нуля нормально:
object x = GetObjectFromCache(); // return null if it's not in the cache
Якщо дійсно завжди має бути ненульовий результат, тоді може бути краще винести виняток:
try {
Controller c = GetController(); // the controller object is central to
// the application. If we don't get one,
// we're fubar
// it's likely that it's OK to not have the try/catch since you won't
// be able to really handle the problem here
}
catch /* ... */ {
}
Optional<>
Для певних сценаріїв ви хочете помітити помилку, як тільки це станеться.
Перевірка на наявність NULL та не затвердження (для помилок програміста) чи викидання (для помилок користувача чи абонента) у випадку відмови може означати, що пізніші збої важче відстежити, оскільки оригінальний непарний випадок не знайдено.
Крім того, ігнорування помилок може призвести до подвигів безпеки. Можливо, нікчемність виникла з того, що буфер був перезаписаний тощо. Тепер ви не зриваєтесь, а це означає, що експлуататор має шанс виконати ваш код.
Які альтернативи ви бачите поверненню нуля?
Я бачу два випадки:
У цьому випадку ми могли б: Повернути нуль або викинути (перевірений) виняток (або, можливо, створити елемент і повернути його)
У цьому випадку ми можемо повернути Null, повернути порожній список або викинути Виняток.
Я вважаю, що null return може бути менш хорошим, ніж альтернативи, оскільки це вимагає від клієнта пам'ятати, щоб перевірити наявність null, програмісти забули та код
x = find();
x.getField(); // bang null pointer exception
У Java, кидаючи перевірений виняток, RecordNotFoundException, дозволяє компілятору нагадати клієнтові розібратися у справі.
Я вважаю, що пошук, який повертає порожні списки, може бути досить зручним - просто заповнити дисплей усім вмістом списку, ой він порожній, код "просто працює".
Іноді повернення NULL - це правильна річ, але конкретно, коли ти маєш справу з послідовностями різного роду (масиви, списки, рядки, що-що-у тебе), можливо, краще повернути послідовність нульової довжини, як це призводить до скорочення і, сподіваємось, більш зрозумілого коду, не вимагаючи при цьому набагато більше запису з боку програми-реалізатора API.
Змусити їх викликати інший метод після факту, щоб з'ясувати, чи був попередній дзвінок нульовим. ;-) Гей, це було досить добре для JDBC
Основна ідея цього потоку - програмувати обороно. Тобто код проти несподіваного. Є масив різних відповідей:
Адамський пропонує переглянути Нульовий шаблон об'єкта, за що за цю пропозицію проголосували відповіді.
Michael Valenty також пропонує конвенцію про іменування, щоб повідомити розробнику, що можна очікувати. ZeroConcept пропонує правильне використання , якщо це причина NULL. І інші.
Якщо ми зробимо «правило», що ми завжди хочемо робити оборонне програмування, то можемо побачити, що ці пропозиції є дійсними.
Але у нас є 2 сценарії розвитку.
Класи "авторські" розробником: Автор
Класи, "спожиті" іншим (можливо) розробником: розробником
Незалежно від того, повертає клас NULL для методів із значенням повернення чи ні, розробнику потрібно перевірити, чи об'єкт дійсний.
Якщо розробник не може цього зробити, то клас / метод не є детермінованим. Тобто, якщо «виклик методу» для отримання об’єкта не робить те, що він «рекламує» (наприклад, getE Employee), він порушив договір.
Як автор класу, я завжди хочу бути таким же добрим і захисним (і детермінованим) при створенні методу.
Отже, враховуючи, що або NULL, або NULL OBJECT (наприклад, якщо (співробітник як NullEposleee.ISVALID)) потрібно перевірити, і це може трапитися з колекцією співробітників, тоді кращий підхід є об'єктом нульового об'єкта.
Але мені також подобається пропозиція Майкла Валентина щодо іменування методу, який ОБОВ'ЯЗКОВО повертає нуль, наприклад getE zaposeeOrNull.
Автор, який кидає виняток, знімає вибір розробника для перевірки дійсності об'єкта, що дуже погано для колекції об'єктів, і змушує розробника в обробці винятків при розгалуженні їх споживчого коду.
Як розробник, що споживає клас, сподіваюся, що автор надає мені можливість уникати або програмувати нульову ситуацію, з якою можуть зіткнутися їх клас / методи.
Тож як розробник я програмував би захист проти NULL з методу. Якщо автор дав мені договір, який завжди повертає об'єкт (NULL OBJECT завжди робить), і у цього об'єкта є метод / властивість, за допомогою якого перевіряти дійсність об'єкта, я б використовував цей метод / властивість для продовження використання об'єкта , інакше об'єкт недійсний, і я не можу ним користуватися.
Підсумок полягає в тому, що автор класу / методів повинен забезпечити механізми, які розробник може використовувати в своєму захисному програмуванні. Тобто більш чіткий намір методу.
Розробник завжди повинен використовувати оборонне програмування для перевірки дійсності об'єктів, повернених з іншого класу / методу.
з повагою
GregJF
Іншими варіантами цього є: повернення деякого значення, яке вказує на успіх чи ні (або тип помилки), але якщо вам просто потрібне булеве значення, яке вказуватиме на успіх / невдачу, повернення нуля для відмови, а об'єкт для успіху не буде бути менш правильним, то повертаючи true / false та отримуючи об'єкт через параметр.
Тож я особисто не бачу нічого поганого у поверненні нуля як ознаки того, що щось пішло не так, і перевіряючи це пізніше (щоб насправді знати, чи вдалося вам чи ні). Крім того, сліпо думаючи, що ваш метод не поверне NULL, а потім базувати на ньому свій код, може призвести до інших, часом важко знайти помилок (хоча в більшості випадків це просто розбиває вашу систему :), як ви посилаєтесь на 0x00000000 рано чи пізно).
Інший підхід міг би використовувати виняток для позначення невдач, але тут - насправді є набагато більше голосів, які говорять, що це практика BAD (оскільки використання винятків може бути зручним, але має багато недоліків).
Навмисні нульові функції можуть виникати під час розробки складних програм, і, як і мертвий код, такі випадки вказують на серйозні недоліки в програмних структурах.
Нульова функція або метод часто використовується як поведінка за замовчуванням відновлюваної функції або методу перезапису в рамках об'єкта.
Ну, це, звичайно, залежить від мети методу ... Іноді кращим вибором було б кинути виняток. Все залежить від конкретного випадку.
Якщо код щось подібний:
command = get_something_to_do()
if command: # if not Null
command.execute()
Якщо у вас є манекенний об’єкт, метод Execute () нічого не робить, і ви повертаєтесь, що замість Null у відповідних випадках вам не потрібно перевіряти Null справа, а можете просто робити:
get_something_to_do().execute()
Отже, тут проблема полягає не в тому, щоб перевірити наявність NULL проти винятку, а замість того, щоб абонент повинен по-різному (будь-яким способом) обробляти спеціальні випадки чи ні.
Для мого використання мені потрібно було повернути Map з методу, а потім шукати конкретний ключ. Але якщо я поверну порожню карту, вона призведе до NullPointerException, і тоді вона не буде набагато інакше повертати нуль замість порожньої карти. Але з Java8 ми могли використовувати опціонально . Вище сказане - саме та причина, що була введена Факультативна концепція.
День,
Повернення NULL, коли ви не можете створити новий об'єкт, є стандартною практикою для багатьох API.
Чому чорт це поганий дизайн, я поняття не маю.
Редагувати: Це стосується мов, де у вас немає винятків, як, наприклад, C, де це було багато років.
HTH
"Авахапі,