Найкращий спосіб обробляти нулі на Java? [зачинено]


21

У мене є код, який виходить з ладу через NullPointerException. Викликається метод на об'єкті, де об'єкт не існує.

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

Які ваші думки?


5
Чому б вам не «виправити причину нуля»? Чи можете ви навести приклад, чому це не єдиний розумний вибір? Ясна річ, щось має відштовхувати вас від очевидного. Що це? Чому не виправити першопричину вашого першого вибору?
S.Lott

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

2
@Shaun F: Якщо код зламаний, він порушений. Я не розумію, як можливо для коду створювати несподівані нулі і не виправлятись. Зрозуміло, що там відбуваються якісь "німі управлінські" чи "політичні" речі. Або щось, що дозволяє помилковому коду бути якось прийнятним. Чи можете ви пояснити, як помилковий код не виправляється? Яке політичне підґрунтя спонукає вас до цього питання?
S.Lott

1
"вразливий до подальшого створення даних, який мав нулі" Що це навіть означає? Якщо "я можу виправити процедуру створення даних", більше немає вразливості. Я не можу зрозуміти, що може означати це "пізніше створення даних, яке містило нулі"? Програмне забезпечення баггі?
S.Lott

1
@ S.Lott, інкапсуляція та одна відповідальність. Весь ваш код не "знає", що робить ваш інший код. Якщо клас приймає Froobinators, він робить якомога менше припущень щодо того, хто створив Froobinator та як. Він походить від "зовнішнього" коду, або він просто приходить з іншої частини бази коду. Я не кажу, що ви не повинні виправляти помилковий код; але пошукайте "оборонне програмування", і ви знайдете те, про що йдеться в ОП.
Пол Дрейпер

Відповіді:


45

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


1
Це дійсно так просто.
biziclop

3
У Гуави є кілька дуже приємних помічницьких методів, які роблять нульову перевірку простою Precondition.checkNotNull(...). Див stackoverflow.com/questions/3022319 / ...

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

Thorbjørn: Отже, це замінює випадкову NullPointerException навмисною NullPointerException? У деяких випадках проведення ранньої перевірки може бути корисним або навіть необхідним; але я побоююсь безлічі безглуздих перевірок нульової панелі, які додають невеликої вартості та просто збільшують програму.
користувач281377

6
Якщо null не є розумним вхідним параметром для вашого методу, але виклики методу, ймовірно, випадково передадуть null (особливо, якщо метод є загальнодоступним), ви можете також захотіти, щоб ваш метод кинув, IllegalArgumentExceptionколи дано null. Це сигналізує абонентам методу, що помилка знаходиться в їх коді (а не в самому методі).
Брайан

20

Не використовуйте null, використовуйте Необов’язково

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

Неможливо сказати, що може бути, nullа що не може бути.

Java 8 надає набагато краще картина: Optional.

І приклад від Oracle:

String version = "UNKNOWN";
if(computer != null) {
  Soundcard soundcard = computer.getSoundcard();
  if(soundcard != null) {
    USB usb = soundcard.getUSB();
    if(usb != null) {
      version = usb.getVersion();
    }
  }
}

Якщо кожен із них може повернути успішне значення або не може повернути його, ви можете змінити API на Optionals:

String name = computer.flatMap(Computer::getSoundcard)
    .flatMap(Soundcard::getUSB)
    .map(USB::getVersion)
    .orElse("UNKNOWN");

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

Якщо ви не використовуєте Java 8, ви можете подивитися com.google.common.base.Optionalв Google Guava.

Гарне пояснення команди Guava: https://github.com/google/guava/wiki/UsingAndAvoidingNullExplained

Більш загальне пояснення недоліків до нуля, із прикладами з декількох мов: https://www.lucidchart.com/techblog/2015/08/31/the-worst-mistake-of-computer-science/


@Nonnull, @Nullable

Java 8 додає ці примітки, щоб допомогти інструментам перевірки коду, таким як проблеми з виявленням IDE. Вони досить обмежені у своїй ефективності.


Перевірте, коли це має сенс

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

З іншого боку, якщо це nullможна було б використати і щось означати, то обов'язково його використовуйте.


Зрештою, ви, очевидно, не можете видалити nullз Java. Я настійно рекомендую замінити Optionalабстракцію, коли це можливо, і перевірити nullті інші часи, що ви можете зробити щось розумне в цьому.


2
Зазвичай відповіді на чотирирічні запитання видаляються через низьку якість. Добре розмовляючи про те, як Java 8 (яка тоді ще не існувала) може вирішити проблему.

але не має значення для початкового питання, звичайно. І що говорити, аргумент справді необов’язковий?
jwenting

5
@jwenting Це повністю стосується оригінального питання. Відповідь «Який найкращий спосіб забити цвях викруткою» - це «Використовуйте молоток замість викрутки».
Daenyth

@jwenting, я думаю, що я це висвітлював, але для наочності: це аргумент необов'язковий, я зазвичай не перевіряв би на null. У вас буде 100 рядків нульової перевірки та 40 рядків речовини. Перевірте наявність null на більш публічних інтерфейсах або коли null насправді має сенс (тобто аргумент необов’язковий).
Пол Дрейпер

4
@ J-Boss, як, на Java, у вас не було б перевіреного NullPointerException? Це NullPointerExceptionможе статися буквально кожен раз, коли ви викликаєте метод екземпляра на Java. У вас був би throws NullPointerExceptionмайже кожен метод коли-небудь.
Пол Дрейпер

8

Існує кілька способів вирішити це, пересипаючи свій код if (obj != null) {}не є ідеальним, він безладний, він додає шуму під час читання коду пізніше під час циклу технічного обслуговування і схильний до помилок, тому що легко забути зробити це обгортання пластини котла.

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

Що є нульовим

У кожному визначенні та випадку Null представляє абсолютний брак даних. Нулі в базах даних представляють відсутність значення для цього стовпця. нуль String- це не те саме, що порожній String, нульовий int- це не те саме, що ZERO, в теорії. На практиці "це залежить". Empty Stringможе зробити гарну Null Objectреалізацію для класу String, оскільки Integerце залежить від логіки бізнесу.

Альтернативи:

  1. Null ObjectВізерунок. Створіть примірник свого об'єкта, який представляє nullстан, і ініціалізуйте всі посилання на цей тип із посиланням на Nullреалізацію. Це корисно для простих об'єктів типу значень, які не мають великої кількості посилань на інші об'єкти, які також можуть бути nullі, як очікується, вважатимуться nullдійсним станом.

  2. Використовуйте інструменти, орієнтовані на аспекти, для плетіння методів із Null Checkerаспектом, який запобігає недійсності параметрів. Це для випадків, коли nullє помилка.

  3. Використовуйте assert()не набагато краще, if (obj != null){}але менш шум.

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

1 є ідеальним рішенням, коли вхідні дані, як відомо, мають nullбути замінені на якесь значення за замовчуванням, так що споживачі вище за течією не повинні мати справу з усіма кодовими кодовами, що перевіряють нуль. Перевірка відомих значень за замовчуванням також буде виразнішою.

2, 3 і 4 - просто зручні альтернативні генератори винятків, які слід замінити NullPointerExceptionчимось більш інформативним, що завжди є і вдосконаленням.

Наприкінці

nullу Java майже у всіх випадках логічна помилка. Ви завжди повинні прагнути усунути першопричину NullPointerExceptions. Ви повинні прагнути не використовувати nullумови як логіку бізнесу. if (x == null) { i = someDefault; }просто зробіть початкове призначення цього об'єкта за замовчуванням.


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

@ Richard: якщо nullце несподівано, то це справжня помилка, тоді все повинно припинитися.

Нуль в базах даних та в коді програмування обробляється по-різному в одному важливому аспекті: У програмуванні null == null(навіть у PHP), але в базах даних, null != nullоскільки в базах даних воно являє собою невідоме значення, а не "нічого". Дві невідомі необов’язково рівні, а два нотації рівні.
Саймон Форсберг

8

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

Перегляньте лекцію Google Tech Talvers: "Чистий код говорить - не шукайте речей!" він говорить про це близько 24 хвилини

http://www.youtube.com/watch?v=RlfLCWKxHJ0&list=PL693EFD059797C21E

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

Також, коли ви створюєте передумову існування якогось об'єкта, такого як

class House(Door door){

    .. null check here and throw exception if Door is NULL
    this.door = door
}

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

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

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

Ви завжди повинні віддавати перевагу програмам, які працюють, тому що у вас є кілька тестів, які доказують, що це працює, а не НАДІЙНА, вона працює просто тому, що у вас є ціла купа попередніх нульових перевірок всюди


4

tl; dr - ДОБРО перевірити на несподівані nulls, але BAD для програми, щоб спробувати зробити їх хорошими.

Деталі

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

Правило №1:

Javadoc для методу, який дозволяє nullпараметр або повертає nullзначення, повинен чітко це документувати і пояснювати, що nullозначає.

Правило №2:

Метод API не повинен визначатися як прийняття чи повернення, nullякщо немає вагомих причин для цього.

Враховуючи чітку специфікацію "контракту" методу по відношенню nullдо помилки програмування, це передача або повернення місця, nullде ви не повинні.

Правило №3:

Додаток не повинен намагатися "виправити" помилки програмування.

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

Правило №4:

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

Якщо у вашому коді є помилки, які призводять до безлічі NPE, найважче може з’ясувати, звідки ці nullзначення взялися. Один із способів полегшити діагностику - написати свій код, щоб nullвиявити його якнайшвидше. Часто ви можете це зробити спільно з іншими чеками; напр

public setName(String name) {
    // This also detects `null` as an (intended) side-effect
    if (name.length() == 0) {
        throw new IllegalArgumentException("empty name");
    }
}

(Очевидно, є випадки, коли правила 3 ​​і 4 повинні бути загартовані реальністю. Наприклад (правило 3) деякі види програм повинні намагатися продовжувати після виявлення, ймовірно, помилок програмування. І (правило 4) може бути занадто багато перевірки на наявність поганих параметрів. вплив на ефективність.)


3

Я б рекомендував закріплювати метод як захисний. Наприклад:

String go(String s){  
    return s.toString();  
}

Повинно бути більше по лінії цього:

String go(String s){  
    if(s == null){  
       return "";  
    }     
    return s.toString();  
}

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


10
Це має жахливий побічний ефект у тому, що абонент (програміст, який кодує проти цього API) може виробити звичку передавати null як параметр для отримання поведінки за замовчуванням.
Горан Йович

2
Якщо це Java, вам взагалі не слід користуватися new String().
biziclop

1
@biziclop насправді це набагато важливіше, ніж більшість усвідомлює.
Woot4Moo

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

1
@ Woot4Moo Отже, напишіть власний код, який викидає виняток. Важливо - повідомити абоненту, що передані аргументи невірні, і повідомити їх якомога швидше. Мовчазне виправлення nulls - це найгірший можливий варіант, гірший, ніж кидання NPE.
Андрес Ф.

2

Наступні загальні правила щодо нуля мені дуже допомогли:

  1. Якщо дані надходять поза вашим контролем, систематично перевіряйте наявність нулів і дійте належним чином. Це означає або викинути виняток, який має сенс для функції (перевірено або невірно, просто переконайтесь, що ім'я виключення точно повідомляє, що відбувається.) Але НІКОЛИ не допускайте, щоб у вашій системі було втрачено значення, яке потенційно може нести сюрпризи.

  2. Якщо Null входить у область відповідних значень для вашої моделі даних, поводьтеся з цим відповідним чином.

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

  4. Мабуть, найважливіше з усіх ... Тести, тести та ще раз тест. Під час тестування свого коду не перевіряйте його як кодер, протестуйте його як нацистського психопата-домінатрикса і намагайтеся уявити всілякі способи катування з пекла цього коду.

Це має тенденцію до параноїдальної сторони щодо нуля, що часто призводить до фасадів та проксі-серверів, які інтерфейсують системи із зовнішнім світом та суворо контрольованих значень всередині з великою надмірністю. Зовнішній світ тут означає майже все, що я не кодував. Це несе витрати на виконання, але поки що мені рідко доводилося оптимізувати це, створюючи "нульові безпечні розділи" коду. Треба сказати, хоча я в основному створюю тривалі системи для охорони здоров’я, і останнє, що мені хочеться, - це підсистема інтерфейсу, що переносить вашу алергію на йод до сканування КТ через несподіваний нульовий покажчик, тому що хтось інший в іншій системі ніколи не розумів, що імена можуть містять апострофи або символи, такі як 但 耒耨。

все одно .... мої 2 копійки


1

Я б запропонував використовувати функцію Option / Some / None з функціональних мов. Я не фахівець з Java, але інтенсивно використовую власну реалізацію цього шаблону у своєму проекті C #, і я впевнений, що він може бути перетворений у світ Java.

Ідея цього шаблону полягає в тому, що: якщо логічно виникнути ситуація, коли існує можливість відсутності значення (наприклад, при відтворенні з бази даних за id), ви надаєте об'єкт типу Option [T], де T - можливе значення . I випадок відсутності об'єкта значення класу None [T] не повертається, у разі якщо повернення існування значення - об'єкта Some [T], що містить значення.

У цьому випадку ви повинні вирішити можливість відсутності значення, і якщо ви зробите перевірку коду, ви зможете легко знайти місце неправильного поводження. Щоб отримати натхнення від реалізації мови C #, зверніться до мого сховища Bitbucket https://bitbucket.org/mikegirkin/optionsomenone

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


0

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

Як завжди, це залежить від випадку, як і у більшості речей у житті :)



0
  1. Не використовуйте тип "Опціон", якщо це справді не є обов'язковим, часто вихід краще обробляти як виняток, якщо ви справді не очікували нуля як опції, а не тому, що ви регулярно пишете баггі-код.

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

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

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

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

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

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.