Правильно використовуючи null
Існують різні способи використання null
. Найпоширеніший і семантично правильний спосіб - це використовувати його тоді, коли ви можете мати або не мати одного значення. У цьому випадку значення або дорівнює, null
або є чимось значущим, як запис із бази даних чи щось таке.
У цих ситуаціях ви здебільшого використовуєте його так (у псевдокоді):
if (value is null) {
doSomethingAboutIt();
return;
}
doSomethingUseful(value);
Проблема
І це дуже велика проблема. Проблема полягає в тому, що до моменту виклику doSomethingUseful
значення, можливо, не було перевірено null
! Якщо цього не було, програма, швидше за все, вийде з ладу. І користувач може навіть не бачити жодних гарних повідомлень про помилки, залишаючись з чимось на кшталт "жахлива помилка: потрібне значення, але отримане недійсне!" (після оновлення: хоча Segmentation fault. Core dumped.
в деяких випадках може бути навіть менш інформативна помилка на кшталт або, що ще гірше, відсутність помилок та неправильних маніпуляцій з null)
Забути писати чеки для null
вирішення null
ситуацій - надзвичайно поширена помилка . Ось чому Тоні Хоаре, який вигадав, null
заявив на програмній конференції під назвою QCon London у 2009 році, що він зробив помилку в мільярд доларів у 1965 році:
https://www.infoq.com/presentations/Null-References-The-Billion-Dollar- Помилка-Тоні-Хоаре
Уникнення проблеми
Деякі технології та мови роблять перевірку null
неможливою забути різними способами, зменшуючи кількість помилок.
Наприклад, у Haskell Maybe
замість нулів є монада. Припустимо, DatabaseRecord
це визначений користувачем тип. У Haskell значення типу Maybe DatabaseRecord
може бути рівним Just <somevalue>
або може бути рівним Nothing
. Потім ви можете використовувати його різними способами, але незалежно від того, яким чином ви ним користуєтеся, ви не можете застосувати якусь операцію, Nothing
не знаючи цього.
Наприклад, ця функція називається zeroAsDefault
віддачею x
для Just x
і 0
для Nothing
:
zeroAsDefault :: Maybe Int -> Int
zeroAsDefault mx = case mx of
Nothing -> 0
Just x -> x
Крістіан Хакл каже, що C ++ 17 і Scala мають свої способи. Тож ви можете спробувати дізнатися, чи є у вашій мові щось подібне, і користуйтеся нею.
Нулі все ще знаходять широке застосування
Якщо у вас немає нічого кращого, то використання null
добре. Просто продовжуйте стежити за цим. Оголошення типу у функціях допоможуть вам дещо допомогти.
Також це може здатися не дуже прогресивним, але ви повинні перевірити, чи хочуть ваші колеги використовувати null
чи щось інше. Вони можуть бути консервативними і можуть не захотіти використовувати нові структури даних з якихось причин. Наприклад, підтримка старих версій мови. Такі речі повинні бути задекларовані у стандартах кодування проекту та належним чином обговорені з командою.
На вашу пропозицію
Ви пропонуєте використовувати окреме булеве поле. Але ви все одно повинні це перевірити, і все одно можете забути його перевірити. Тож тут нічого не виграно. Якщо ви навіть можете забути щось інше, як-от оновлення обох значень кожного разу, тоді це ще гірше. Якщо проблема забуття перевірити null
не вирішена, то немає сенсу. Уникнути null
важко, і ви не повинні робити це таким чином, що робить це гірше.
Як не використовувати null
Нарешті, існують загальні способи null
неправильного використання . Одним із таких способів є використання його замість порожніх структур даних, таких як масиви та рядки. Порожній масив - це правильний масив, як і будь-який інший! Майже завжди важливо і корисно для структур даних, які можуть вміщувати кілька значень, бути порожнім, тобто має 0 довжини.
З точки зору алгебри порожній рядок для рядків схожий на 0 для чисел, тобто ідентичність:
a+0=a
concat(str, '')=str
Порожній рядок дозволяє рядкам взагалі стати моноїдом:
https://en.wikipedia.org/wiki/Monoid
Якщо ви цього не отримаєте, це для вас не так важливо.
Тепер давайте розберемося, чому це важливо для програмування на цьому прикладі:
for (element in array) {
doSomething(element);
}
Якщо ми передамо порожній масив, тут код буде добре працювати. Це просто нічого не зробить. Однак якщо ми перейдемо null
сюди, то, швидше за все, ми отримаємо аварію з помилкою на кшталт "не можу пройти через null, вибачте". Ми можемо обернути його, if
але це менш чисто і знову, ви можете забути це перевірити
Як поводитися з нулем
Що doSomethingAboutIt()
слід робити, а особливо, чи варто викидати виняток - ще одне складне питання. Коротше кажучи, це залежить від того, чи null
було прийнятним вхідне значення для даної задачі і що очікується у відповідь. Винятки становлять події, яких не очікували. Я не буду заглиблюватися в цю тему. Ця відповідь вже давно.
std::optional
абоOption
. В інших мовах, можливо, вам доведеться самостійно побудувати відповідний механізм, або ви можете насправді вдатися доnull
чогось подібного, оскільки це більш ідіоматично.