Чи розумно знімати нуль кожного окремого вказівника?


65

На новій роботі я отримував позначення в кодах оглядів такого коду:

PowerManager::PowerManager(IMsgSender* msgSender)
  : msgSender_(msgSender) { }

void PowerManager::SignalShutdown()
{
    msgSender_->sendMsg("shutdown()");
}

Мені сказали, що останній метод повинен читати:

void PowerManager::SignalShutdown()
{
    if (msgSender_) {
        msgSender_->sendMsg("shutdown()");
    }
}

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

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

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

РЕДАКТУВАННЯ : У наведеному вище прикладі msgSender_співавтор необов'язковий. Якщо це колись NULL, це вказує на помилку. Єдина причина, яку він передає в конструктор, - це те, що він PowerManagerможе бути перевірений з макетним IMsgSenderпідкласом.

ПІДСУМОК : На це питання було кілька справді чудових відповідей, дякую всім. Я прийняв цей від @aaronps головним чином завдяки його стислості. Здається, існує досить широка загальна згода, яка:

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

14
Не замислюйтесь ні на хвилину, що помилки - це область молодших програмістів. 95% розробників усіх рівнів досвіду щось піднімають. Решта 5% лежать через зуби.
Blrfl

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

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

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

6
@Rig, я впевнений, що є відповідні випадки мовчазного відмови. Але якщо хтось ставить мовчазні збої як загальну практику (якщо тільки не працювати в царині, де це має сенс), я дійсно не хочу працювати над проектом, в який вони вводять код.
Вінстон Еверт

Відповіді:


66

Це залежить від "контракту":

Якщо PowerManager ОБОВ'ЯЗКОВО має бути дійсний IMsgSender, ніколи не перевіряйте його на нуль, нехай він швидше помре.

Якщо, з іншого боку, це МОЖЕ бути IMsgSender, то вам потрібно перевіряти кожен раз, коли ви користуєтесь, настільки просто.

Остаточний коментар про історію молодшого програміста, проблема насправді у відсутності процедур тестування.


4
+1 за дуже лаконічну відповідь, що в значній мірі підсумовує те, як я почуваюсь: "це залежить ..." У вас також було сміливо сказати "ніколи не перевіряйте на нуль" ( якщо null недійсний). Розміщення assert()в кожному члені функції, що відмежовує вказівник, - це компроміс, який, ймовірно, може заспокоїти багато людей, але навіть це відчуває себе як "спекулятивна параноїя" для мене. Перелік речей, що перевищують мою здатність контролювати, нескінченний, і як тільки я дозволю собі почати переживати про них, він стає справді слизьким схилом. Навчившись довіряти своїм тестам, мої колеги - і мій налагоджувач - допомогли мені краще спати вночі.
ухилення від потоку

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

1
Простий і простий, це правильна відповідь на питання.
Матвій Аскімов

2
Це найближча правильна відповідь, але я не можу погодитися з «ніколи не перевіряти на нуль», завжди перевіряйте наявність недійсних параметрів у точці, коли вони будуть призначені змінній члена.
Джеймс

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

77

Я вважаю, що код повинен читати:

PowerManager::PowerManager(IMsgSender* msgSender)
  : msgSender_(msgSender)
{
    assert(msgSender);
}

void PowerManager::SignalShutdown()
{
    assert(msgSender_);
    msgSender_->sendMsg("shutdown()");
}

Це насправді краще, ніж захищати NULL, оскільки це дає зрозуміти, що функцію ніколи не можна викликати, якщо msgSender_NULL. Це також гарантує, що ви помітите, якщо це станеться.

Спільна "мудрість" ваших колег мовчки ігнорує цю помилку з непередбачуваними результатами.

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

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


10
Велика різниця між викликом assert і прикладом коду OP полягає в тому, що assert увімкнено лише в налагодженнях (#define NDEBUG) Коли продукт потрапляє в руки замовника, і налагодження відключається, і ніколи не вбудована комбінація шляхів коду виконується, щоб залишити вказівник нульовий, хоча функція, про яку йдеться, виконується, асдрад не забезпечує захисту.
atk

17
Імовірно, ви б протестували збірку, перш ніж фактично надсилати її замовнику, але так, це можливий ризик. Рішення хоч і прості: або просто постачайте з увімкненими твердженнями (це версія, яку ви протестували в будь-якому випадку), або замініть її на власний макрос, який стверджується в режимі налагодження і просто журнали, журнали + зворотні виходи, виходи, ... у режимі випуску.
Крістоф Простой

7
@James - у 90% випадків ви також не відновитеся після невдалої перевірки NULL. Але в такому випадку код мовчки вийде з ладу, і помилка може з’явитися набагато пізніше - наприклад, ви думали, що ви зберегли файл, але насправді нульова перевірка іноді не спрацьовує, не залишаючи вас мудрішими. Ви не можете оговтатися від усієї ситуації, яка не повинна статися. Ви можете переконатись, що вони не відбудуться, але я не думаю, що у вас є бюджет, якщо ви не напишете код для супутника або атомної електростанції NASA. Вам потрібно мати спосіб сказати: "Я поняття не маю, що відбувається - програма не повинна бути у такому стані".
Maciej Piechotka

6
@James Я не згоден з метанням. Це робить його схожим на те, що насправді може статися. Твердження стосуються речей, які повинні бути неможливими. Для справжніх помилок у коді іншими словами. Винятки становлять випадки, коли трапляються несподівані, але можливі речі. Винятки становлять речі, з яких можна було б впоратись та відновитись. Логічні помилки в коді, з якого ви не можете відновити, і не слід намагатися.
Крістоф Простой

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

30

Якщо msgSender ніколи не повинен бути null, слід поставити nullчек лише в конструкторі. Це справедливо і для будь-яких інших входів у клас - поставте перевірку на цілісність у точці входу до «модуля» - класу, функції тощо.

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

Крім того - якщо ви впевнені, аргумент ніколи не може бути недійсним, розгляньте використання посилань, залежно від використання класу. В іншому випадку розгляньте можливість використання std::unique_ptrабо std::shared_ptrзамість необробленого вказівника.


11

Ні, нерозумно перевіряти кожну відмітку покажчика на наявність вказівника NULL.

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

Якщо я зіткнувся з такою ситуацією, як ваша, я б поставив два питання:

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

9

Цей приклад, здається, стосується більше часу життя об'єкта, ніж того, чи є вхідним параметром нульовий †. Так як ви говорите , що PowerManagerнеобхідно завжди мати дійсний IMsgSender, передаючи аргумент покажчиком (дозволяючи тим самим можливість нульового покажчика) вражає мене як дизайн вада ††.

У таких ситуаціях я вважаю за краще змінити інтерфейс, щоб вимоги абонента виконувалися мовою:

PowerManager::PowerManager(const IMsgSender& msgSender)
  : msgSender_(msgSender) {}

void PowerManager::SignalShutdown() {
    msgSender_->sendMsg("shutdown()");
}

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

Ви також можете написати те ж саме, використовуючи смарт-покажчик (через boost або c ++ 11), щоб явно змусити IMsgSenderжити довше, ніж PowerManager:

PowerManager::PowerManager(std::shared_ptr<IMsgSender> msgSender) 
  : msgSender_(msgSender) {}

void PowerManager::SignalShutdown() {
    // Here, we own a smart pointer to IMsgSender, so even if the caller
    // destroys the original pointer, we still have a valid copy
    msgSender_->sendMsg("shutdown()");
}

Цей спосіб є кращим, якщо можливо, IMsgSenderчас життя не може бути гарантовано більшим, ніж PowerManager'(тобто x = new IMsgSender(); p = new PowerManager(*x);).

† Що стосується покажчиків: невпинна перевірка нуля робить код важче читати і не покращує стабільність (покращує зовнішній вигляд стабільності, що набагато гірше).

Десь хтось отримав адресу для пам’яті для зберігання IMsgSender. Це відповідальність за те, щоб переконатися, що розподіл вдалося (перевірка значень повернення бібліотеки або належна обробка std::bad_allocвинятків), щоб не обходити недійсні покажчики.

Оскільки пристрій PowerManagerне є власником IMsgSender(це просто запозичити його на деякий час), воно не несе відповідальності за виділення або знищення цієї пам'яті. Це ще одна причина, чому я віддаю перевагу посиланням.

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


1
Насправді немає нічого поганого в тому, щоб іноді використовувати сирі вказівники. Std :: shared_ptr - це не срібна куля, і використання її у списку аргументів - це ліки для неохайної інженерії, хоча я розумію, що вважати, що це не можна використовувати, коли це можливо. Використовуйте java, якщо ви відчуваєте це.
Джеймс

2
Я не сказав, що сирі вказівники погані! Вони безумовно мають своє місце. Однак це C + + , посилання (і тепер загальні вказівники) є частиною мови чомусь. Використовуйте їх, вони спричинить вам успіх.
Сет

Мені подобається ідея розумного вказівника, але це не варіант у цьому конкретному концерті. +1 для рекомендування використання посилань (як @Max та декілька інших). Іноді неможливо ввести посилання, однак через проблеми залежності від курки та яєць, тобто якщо об'єкт, який інакше був би кандидатом для ін'єкції, повинен мати вказівник (або посилання) на його власника, який вводиться в нього . Іноді це може вказувати на щільно з’єднану конструкцію; але це часто прийнятно, як у відносинах між об'єктом та його ітератором, де ніколи не має сенсу використовувати останній без першого.
ухилення від потоку

8

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

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

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

Редагувати: якщо розмірковувати над історією молодшого програміста, звучить так, що сталося те, що приватний член був встановлений на нуль, коли цього ніколи не слід було допускати. У них виникли проблеми з неправильним написанням і намагаються виправити це шляхом перевірки після читання. Це назад. На той момент, коли ви його виявите, помилка вже сталася. Прийнятне рішення для перегляду коду / компілятора для цього не є умовами захисту, а натомість getters та setters або const members.


7

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

void PowerManager::SignalShutdown()
{
    if (!msgSender_)
    {
       throw SignalException("Shut down failed because message sender is not set.");
    }

    msgSender_->sendMsg("shutdown()");
}

Запропоноване "виправлення" іншими членами вашої команди порушує принцип мертвих програм Tell No Lies . Помилок насправді важко знайти як є. Метод, який мовчки змінює свою поведінку на основі попередньої проблеми, не лише ускладнює пошук першої помилки, але й додає 2-й власний помилку.

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

Будь-який з цих підходів дозволить уникнути мовчазних збоїв:

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

  2. Киньте виняток, якщо він є нульовим.


5

Я погоджуюся з тим, що затримати нуль у конструкторі. Далі, якщо член оголошено в заголовку як:

IMsgSender* const msgSender_;

Тоді вказівник не може бути змінений після ініціалізації, тож якщо він був гарний при будівництві, він буде нормальним протягом життя об'єкта, який його містить. (Об'єкт вказав на буде НЕ бути константними.)


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

@evadeflow Я подумав, що "я згоден з усіма іншими", відповів на основну частину питання. Ура, хоча! ;-)
Grimm The Opiner

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

@evadeflow Люди відвідують Questins лише для представника, тепер на нього відповіли, що ніхто більше не перегляне це. ;-) Я вискакував лише тому, що це було питання про покажчики, і я стежу за кривавою бригадою "Використовуй розумні покажчики на все завжди".
Grimm The Opiner

3

Це відверто небезпечно!

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

// Pre: vertex should never be null.
void transform_vertex(Vertex* vertex, ...)
{
    // Inserted by my "wise" co-worker.
    if (!vertex)
        return;
    ...
}

Я одного разу спробував зняти таку перевірку передумови в такій функції та замінивши її на, assertщоб побачити, що буде.

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

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

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

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

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

Чи доцільно для стандарту кодування вимагати, щоб кожен вказівник, відмінений у функції, спочатку перевірявся на NULL - навіть приватні члени даних?

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

assertце шлях сюди. Порушення передумов не повинно залишатися непоміченим, інакше закон Мерфі може легко спричинити.


1
"assert" - це шлях, але пам’ятайте, що в будь-якій сучасній системі кожен доступ до пам'яті через вказівник має вбудовану в апаратну частину нулеву вказівку. Тож у "assert (p! = NULL); return * p;" навіть затвердження здебільшого безглуздо.
gnasher729

@ gnasher729 А, це дуже вдалий момент, але я схильний розглядати assertяк механізм документації для встановлення того, що є передумовами, а не просто спосіб змусити аборт. Але мені також подобається характер відмови у твердженні, який показує вам, який саме рядок коду спричинив збій, а умову затвердження не вдалося ("документування збоїв"). Може стати в нагоді, якщо ми в кінцевому підсумку скористаємося налагодженням налагодження поза налагоджувачем і потрапимо поза охороною.

@ gnasher729 Або я, можливо, неправильно зрозумів її частину. Чи показують ці сучасні системи, який рядок вихідного коду, в якому твердження не вдалося? Я, як правило, уявляю, що вони втратили інформацію в цей момент і, можливо, просто показують segfault / порушення доступу, але я якось далекий від "сучасного" - все ще вперто в Windows 7 протягом більшої частини моєї розробки.

Наприклад, у MacOS X та iOS, якщо ваша програма виходить з ладу через нульовий доступ вказівника під час розробки, відладчик підкаже точний рядок коду, де це відбувається. Є ще один дивний аспект: компілятор спробує надати вам попередження, якщо ви зробите щось підозріле. Якщо ви передасте вказівник на функцію та отримаєте доступ до неї, компілятор не попередить вас про те, що вказівник може бути NULL, оскільки це трапляється так часто, ви будете заглушені у попередженнях. Однак якщо ви робите порівняння, якщо (p == NULL) ... тоді компілятор припускає, що є хороший шанс, що p - NULL,
gnasher729

тому що чому б ви його протестували інакше, тому він буде попереджати з того часу, якщо ви використовуєте його без тестування. Якщо ви використовуєте власний макрос, схожий на затвердження, вам потрібно бути трохи розумнішим, щоб записати його таким чином, щоб "my_assert (p! = NULL," Це нульовий покажчик, дурний! "); * P = 1;" не дає вам попередження.
gnasher729

2

Наприклад, Objective-C розглядає кожен виклик методу на nilоб'єкті як неоперативний, який оцінює нульове значення. Є певні переваги такого дизайнерського рішення в Objective-C з причин, запропонованих у вашому питанні. Теоретична концепція забезпечення нульового захисту кожного виклику методу має певні заслуги, якщо вона широко оприлюднена та послідовно застосовується.

Однак, код живе в екосистемі, а не у вакуумі. Поведінка, що охороняє нуль, була б неідіоматичною та дивною у C ++, а тому слід вважати шкідливою. Підсумовуючи, ніхто так не пише код C ++, тому не робіть цього! Однак, як зустрічний приклад, зауважте, що дзвінки на free()або deleteна NULLC та C ++ гарантовано не мають змоги.


У вашому прикладі, мабуть, варто було б розмістити твердження в конструкторі, що msgSenderє недійсним. Якщо конструктор називається метод на msgSenderвідразу ж, щось не таке твердження не буде необхідності, оскільки вона розбила б прямо там в будь-якому випадку. Однак, оскільки він просто зберігається msgSender для подальшого використання, з огляду на стек стеження того, SignalShutdown()як з'явилося значення , було б очевидно NULL, тому твердження в конструкторі значно спростить налагодження.

Ще краще, що конструктор повинен прийняти const IMsgSender&посилання, чого не може бути NULL.


1

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

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

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

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

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


+1 за те, що мав мужність підтримувати (здавалося б, непопулярну позицію). Я був би розчарований, якби ніхто не захищав моїх колег з цього приводу (вони дуже розумні люди, які випускають якісний продукт вже багато років). Мені також подобається думка, що додатки повинні "вийти з ладу в тестовому середовищі ... і вийти з ладу у виробництві". Я не переконаний, що NULLце зробити так, щоб доручити "перекидати " чеки, але я вдячний почути думку від когось поза моїм робочим місцем, який в основному каже: "А-а. Це не здається занадто необгрунтованим".
ухилення від потоку

Мене дратує, коли я бачу людей, які тестують на NULL після malloc (). Це говорить мені, що вони не розуміють, як сучасні ОС управляють пам'яттю.
Прорік Крістоф

2
Я здогадуюсь, що @KristofProvost говорить про ОС, які використовують overcommit, для яких malloc завжди досягає успіху (але ОС може пізніше вбити процес, якщо насправді недостатньо пам'яті). Однак це не є вагомою причиною пропускати нульові перевірки на malloc: overcommit не є універсальним для будь-яких платформ, і навіть якщо це ввімкнено, можуть бути й інші причини виходу з ладу malloc (наприклад, в Linux, обмеження простору адресного простору процесу, встановлене з ulimit ).
Іван Варфоломій

1
Що сказав Джон. Я справді говорив про надмірну комісію. Overcommit увімкнено за замовчуванням у Linux (саме це і оплачує мої рахунки). Я не знаю, але підозрюю, що якщо це так само в Windows. У більшості випадків ви все одно закінчитесь збоями, якщо тільки ви не готові написати багато коду, який ніколи не буде перевірений на обробку помилок, які майже напевно ніколи не трапляться ...
Kristof Provost

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

1

Використання вказівника замість посилання скаже мені, що msgSenderце лише варіант, і тут нульова перевірка була б правильною. Фрагмент коду занадто повільний, щоб вирішити це. Можливо, є інші елементи, PowerManagerякі є цінними (або перевіреними) ...

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


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

@evadeflow: Гаразд, зізнаюся, це залежить від розміру класу та видимості членів вказівника (на які неможливо зробити посилання), чи перевіряю я перед перенаправленням. У випадках, коли клас малий і простий, а покажчик (и) приватні, я не ...
Wolf

0

Це залежить від того, що ви хочете статися.

Ви писали, що працюєте над «пристроєм побутової електроніки». Якщо яким - то чином, помилка , вводиться шляхом установки msgSender_на NULL, ви хочете

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

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

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

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

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