Чому нульові посилання уникаються під час викидання винятків?


21

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



2
Деякі мови крапляться на нуль краще, ніж інші. Для "керованого коду", такого як .Net / Java, нульове відмінність - це лише інший тип проблем, тоді як інший нативний код може не вирішувати це так витончено (ви не згадали про конкретну мову). Навіть у керованому світі іноді ви хочете написати незахищений код (вбудований ?, зброя?), А іноді вам хочеться голосно кусати ASAP (тестування одиниць). Обидва типи коду можуть входити в одну бібліотеку - це буде проблемою. Взагалі я думаю, що код, який намагається не нашкодити комп’ютерним почуттям, - це погана ідея. Безперезонна безпека - це все одно
робота

@Job: Це виступає за лінь. Якщо ви знаєте, як обробити виняток, ви поводитесь із ним. Іноді ця обробка може включати викидання іншого винятку, але ви ніколи не повинні дозволяти нульовому винятковій винятковій помилці залишитись без обробки. Колись. Це кошмар кожного програміста; це найкорисніший виняток у всьому дереві. Просто запитайте переповнення стека .
Aaronaught

або кажучи іншим способом - чому б не повернути якийсь код для подання інформації про помилку. Цей аргумент буде лютувати довгі роки.
gbjbaanb

Відповіді:


24

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

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

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

Насправді багато рамок охоплюють обидва поняття; наприклад, у .NET, часто зустрічається шаблон, - це пара методів, приставка яких має слово Try(наприклад, TryGetValue). У Tryвипадку посилання встановлюється за замовчуванням (зазвичай null), а в іншому випадку викидається виняток. Немає нічого поганого в будь-якому підході; обидва використовуються часто в середовищах, які їх підтримують.

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

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


5
Насправді є мови, які не мають нуля або нуля і не потребують "заміни його на щось інше". Незмінні посилання означають, що там може бути щось, а може і не бути. Якщо ви просто вимагаєте від користувача чітко перевірити, чи є щось там, ви вирішили проблему. Дивіться haskell для прикладу реального життя.
Йоганна Ларссон

3
@ErikKronberg, так, "помилка в мільярд доларів" і вся ця нісенітниця, парад людей, котрий вимальовує це і стверджує, що це свіже і захоплююче, ніколи не закінчується, тому попередній потік коментаря був видалений. Ці революційні заміни, які люди ніколи не підводять, завжди є деяким варіантом Нульового Об'єкта, Опції чи Контракту, які фактично не магічно усувають основну логічну помилку, вони просто відкладають або сприяють її відповідно. У всякому разі, це, очевидно, питання про мови програмування , які роблять єnull , так що на насправді, Haskell досить тут недоречний.
Aaronaught

1
Ви серйозно сперечаєтесь з тим, що не вимагати тестування на null так добре, як вимагати його?
Йоганна Ларссон

3
@ErikKronberg: Так, я "серйозно стверджую", що тестування на null не особливо відрізняється від (a) необхідності проектувати кожен рівень програми навколо поведінки Null Object, (b) необхідності узгодження шаблону Опції весь час, або (c) не дозволяючи абоненту сказати "я не знаю" і примусити виняток або збій. Існує причина, чому nullтак довго витримали, і більшість людей, які говорять про інше, схоже, є науковцями з невеликим досвідом проектування додатків у реальному світі з такими обмеженнями, як неповні вимоги чи можлива послідовність.
Aaronaught

3
@Aaronaught: Іноді я хотів би, щоб у коментарях була кнопка знищення. Немає ніяких причин подібного лютувати.
Майкл Шоу

11

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


4
Якщо не обробляти NULL, це буде навмисним незнанням інтерфейсу між вашим кодом і тим, що його повернули. Розробники, які допустили б цю помилку, зробить інших мовою, яка не робить NULL.
Blrfl

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

@Job: Що робити, якщо у вас немає налагоджувача? Ви розумієте, що 99,99% часу, коли ваша програма буде працювати в середовищі випуску? Коли це станеться, ви хочете побажати, щоб ви використовували більш значущий виняток. Ваш додаток все ще може вийти з ладу і дратувати користувача, але принаймні він видасть інформацію про налагодження, яка дозволить вам негайно відстежувати проблему, таким чином, мінімізуючи зазначену роздратування.
Aaronaught

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

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

8

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

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

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


Розглянемо неініціалізований підклас з ідентифікаційним рядком, і всі його методи кидають винятки. Якщо хтось із них коли-небудь з’являється, ви знаєте, що сталося. У вбудованих системах з обмеженою пам'яттю виробнича версія неініціалізованої фабрики може просто повернути нуль.
Джим Балтер

3
@ Джим Балтер: Я думаю, я збентежений, як це справді допоможе на практиці. У будь-якій нетривіальній програмі вам доведеться в якийсь момент розібратися зі значеннями, які можуть бути ініціалізовані. Отже, повинен бути певний спосіб позначити значення за замовчуванням. Таким чином, перед тим, як продовжувати, потрібно перевірити це. Тож замість потенційної аварії ви потенційно працюєте з недійсними даними.
Ед С.

1
Ви також знаєте, що сталося, коли ви отримуєте nullяк повернене значення: Інформація, яку ви запитували, не існує. Немає різниці. У будь-якому випадку, абонент повинен перевірити повернене значення, якщо йому фактично потрібно використовувати цю інформацію для певної мети. Незалежно від того, чи це валідація null, нульового об'єкта чи монади, це не має жодної практичної різниці.
Aaronaught

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

1
Я пояснив практичну відмінність від класу Uninitialized двічі - він визначає походження елемента null, що дає змогу точно визначити помилку при виникненні перенавантаження нуля. Що стосується мов, розроблених навколо нульових парадигм, вони уникають проблеми під час руху - вони надають альтернативні способи програмування, щоб уникнути неініціалізованих змінних; що може здатися важким для розуміння, якщо ви не знайомі з ними. Структуроване програмування, яке також уникає великого класу помилок, також колись вважалося "академічним".
Джим Балтер

5

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

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

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

Ця проблема особливо присутня в C або C ++ (наприклад) через "важку" помилку, яку вона викликає (збій програми, негайне, без елегантного відновлення).

В інших мовах завжди виникає питання, як з ними поводитися.

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

У Haskell ви повинні прямо вказати дію для нульового випадку, і тому програмісти Haskell глузують над своїми колегами, бо вони зрозуміли це правильно (правда?).

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

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


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

2

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

(Однак ви можете залишити повідомлення про помилку у файлі журналу.)

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

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


2

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

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

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

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

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


1

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

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

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

Більш висока складність зазвичай означає більше помилок.

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

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


1

Характерно для C ++, але там відсутні нульові посилання , тому що нульова семантика в C ++ пов'язана з типами вказівників. Цілком розумно функцію відкритого файлу відмовити і повернути нульовий покажчик; насправді fopen()функція робить саме це.


Дійсно, якщо ви знайдете нульове посилання в C ++, це тому, що ваша програма вже зламана .
Kaz Dragon

1

Це залежить від мови.

Наприклад, Objective-C дозволяє без проблем надсилати повідомлення нульовому (нульовому) об'єкту. Заклик до нуля також повертає нуль і вважається мовною особливістю.

Мені особисто це подобається, оскільки ти можеш покладатися на таку поведінку і уникати всіх цих заплутаних вкладених if(obj == null)конструкцій.

Наприклад:

if (myObject != nil && [myObject doSomething])
{
    ...
}

Можна скоротити до:

if ([myObject doSomething])
{
    ...
}

Коротше кажучи, це робить ваш код більш читабельним.


0

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


А також назвіть його: GetAddressMaybeабо GetSpouseNameOrNull.
rwong

-1

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

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


2
Нульова посилання може «виникати» з великої кількості причин, дуже мало з них стосується того, що було пропущено. Можливо, ви також його плутаєте з нульовим посиланням на виняток .
Aaronaught

-1

Нульові посилання часто дуже корисні: Наприклад, елемент у зв'язаному списку може мати спадкоємця або не мати наступника. Використання nullдля "не наступника" - цілком природно. Або людина може мати подружжя чи ні - використання nullдля "людина не має подружжя" - цілком природно, набагато природніше, ніж наявність у когось "не має подружжя" - особливого значення, на яке Person.Spouseміг би посилатися член.

Але: Багато значень не є необов'язковими. У звичайній програмі OOP, я б сказав, що більше половини посилань не може бути nullпісля ініціалізації, або програма вийде з ладу. В іншому випадку код повинен бути прокреслений if (x != null)чеками. То чому б кожне посилання за замовчуванням повинно бути нульовим? Це дійсно повинно бути навпаки: змінні повинні бути ненульовими за замовчуванням, і ви повинні чітко сказати "о, і це значення може бути nullі".


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

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

-1

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


Ні, я не маю на увазі виключення з нульовою посиланням. У всіх мовах, які я знаю, неініціалізовані, але оголошені змінні є певною формою нуля.
davidk01

Але ваше запитання було не про неініціалізовані змінні. І є мови, які не використовують null для неініціалізованих змінних, а натомість використовують об’єкти обгортки, які необов'язково можуть містити значення, наприклад, Scala та Haskell.
Джим Балтер

1
"... але замість цього використовуйте об'єкти обгортки, які необов'язково можуть містити значення ." Що явно не схоже на нульовий тип .
Aaronaught

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

1
Якщо у вас є виняткове значення "Відсутнє", то саме те саме, що виняткове "нульове" значення - якщо у вас є ті ж інструменти, з якими можна поводитися. Це суттєве "якщо". Для вирішення цього випадку вам потрібна додаткова складність. У Haskell відповідність шаблонів та система типів надають спосіб допомогти керувати цією складністю. Однак є й інші інструменти для управління складністю іншими мовами. Винятки є одним із таких інструментів.
Стів314
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.