Чи слід перевіряти кожну маленьку помилку в С?


60

Як хороший програміст, слід написати надійні коди, які будуть обробляти кожен результат його програми. Однак майже всі функції з бібліотеки С повернуть 0 або -1 або NULL, коли є помилка.

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

if(fprintf(stderr, "%s", errMsg) < 0){
    perror("An error occurred while displaying the previous error.");
    exit(1);
}

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


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

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

1
Проблема здебільшого є методологічною: ви не пишете перевірку помилок, як правило, коли ви все ще з'ясовуєте, що ви маєте реалізувати, і ви не хочете додавати перевірку помилок прямо зараз, тому що хочете отримати "щасливий шлях" прямо спочатку. Але ви все одно повинні перевірити наявність malloc та co. Замість того, щоб пропускати крок перевірки помилок, ви повинні надати пріоритет своїм діям кодування і нехай перевірка помилок є неявним етапом постійного рефакторингу у вашому списку TODO, застосовується щоразу, коли вас влаштовує ваш код. Додавання схвальних коментарів "/ * ПРОВЕРИТИ * /" може допомогти.
coredump

7
Я не можу повірити, що ніхто не згадується errno! Якщо ви не знайомі, хоча це правда, що "майже всі функції з бібліотеки С повернуть 0 або -1 або NULLколи з’явиться помилка", вони також встановлюють глобальну errnoзмінну , до якої можна отримати доступ, використовуючи, #include <errno.h>а потім просто читаючи значення errno. Так, наприклад, якщо open(2) повертається -1, ви можете перевірити, чи errno == EACCESне вказуватиметься на помилку дозволу, або ENOENTщо вказує на те, що запитуваний файл не існує.
wchargin

1
@ErikEidt C не підтримує try/ catch, хоча ви могли б імітувати його стрибками.
щогла

Відповіді:


29

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

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

У С у вас немає такої розкоші. Існує декілька способів вирішення помилок, деякі з яких є мовними / бібліотечними особливостями, деякі з них - кодуваннями.

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

Ігнорувати певні помилки? Може бути. Наприклад, розумно припустити, що запис на стандартний вихід не вийде з ладу. Якщо це не вдається, як би ви сказали користувачеві в будь-якому випадку? Так, корисно ігнорувати певні помилки або захисно кодувати, щоб їх запобігти. Наприклад, перевірте на нуль перед поділом.

Існують способи вирішити всі або, принаймні, більшість помилок:

  1. Ви можете використовувати стрибки, схожі на gotos, для обробки помилок . У той час як спірне питання серед професіоналів програмного забезпечення, для них є дійсне використання, особливо вбудований і критичний для продуктивності код (наприклад, ядро ​​Linux).
  1. Каскадні ifс:

    if (!<something>) {
      printf("oh no 1!");
      return;
    }
    if (!<something else>) {
      printf("oh no 2!");
      return;
    }
  2. Перевірте першу умову, наприклад, відкриття або створення файлу, а потім припускайте, що наступні операції будуть успішними.

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


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

4
@Damien_The_Unbeliever - тим більше, що stdoutйого можна переспрямувати у файл або навіть не існувати залежно від системи. stdoutце не потік "запису в консоль", це зазвичай так , але це не повинно бути.
Давор Ждрало

3
@JAB Ви надсилаєте повідомлення на stdout... очевидно :-)
TripeHound

7
@JAB: Ви можете вийти з EX_IOERR або близько того, якщо це було доречно.
Джон Маршалл

6
Що б ви не робили, не просто продовжуйте бігати так, ніби нічого не сталося! Якщо команда dump_database > backups/db_dump.txtне зможе записати на стандартний висновок в будь-який момент, я не хотів би, щоб воно продовжувалося і виходило успішно. (Не те, що резервні копії баз даних є, але точка все ще стоїть)
Ben S

15

Однак майже всі функції з бібліотеки С повернуть 0 або -1 або NULL, коли є помилка.

Так, але ви знаєте, яку функцію ви викликали, чи не так?

Насправді у вас є багато інформації, яку ви могли б ввести у повідомлення про помилку. Ви знаєте, яка функція викликалася, ім'я функції, яка її викликала, які параметри були передані, і значення повернення функції. Це багато інформації для дуже інформативного повідомлення про помилку.

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


5
Ця відповідь змусила мене посміхнутися, бо це правда, але не відповідає на питання.
RubberDuck

Як це не відповідає на запитання?
Роберт Харві

2
одна з причин, я думаю, що C (та інші мови) змішали булеві значення 1 і 0 - це та сама причина, що будь-яка гідна функція, що повертає код помилки, повертає "0" без помилок. але тоді ти повинен сказати if (!myfunc()) {/*proceed*/}. 0 не було помилок, і все, що не є нульовим, було деякою своєрідною помилкою, і кожна помилка свого роду мала свій ненульовий код. так, як сказав мій друг, це "є лише одна Істина, але багато неправди". "0" має бути "істинним" у if()тесті, і все, що не має нуля, повинно бути "хибним".
Роберт Брістоу-Джонсон

1
ні, роблячи це по-своєму, існує лише одна можлива негативна код помилки. і багато можливих кодів no_error.
Роберт Брістоу-Джонсон

1
@ robertbristow-johnson: Потім прочитайте це як "Помилки не було". if(function()) { // do something }читається як "якщо функція виконується без помилок, то роби щось". або, ще краще, "якщо функція успішно виконується, роби щось". Серйозно, тих, хто розуміє конвенцію, це не бентежить. Повернення false(або 0), коли виникає помилка , не дозволить використовувати коди помилок. Нуль означає значення false на C, тому що так працює математика. Дивіться програмісти.stackexchange.com/
Роберт Харві

11

TLDR; вам майже ніколи не слід ігнорувати помилки.

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

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

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

Однак як розробник C це також ваша робота - зробити код простим у обслуговуванні. Тому іноді ви можете сміливо ігнорувати помилки задля ясності, якщо (і лише тоді) вони не становлять ніякої загрози для загальної поведінки програми. .

Це та сама проблема, як це робити:

try
{
    run();
} catch (Exception) {
    // comments expected here!!!
}

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


Я погоджуюся, що ремонтопридатність - це дійсно важливий аспект, і цей параграф справді відповідав на моє запитання. Дякую за детальну відповідь.
Дерек 朕 會 功夫

Я думаю, що gazillions програм, які ніколи не турбуються перевіряти свої дзвінки malloc та все ж ніколи не виходять з ладу, є вагомою причиною лінуватися, коли настільна програма використовує лише кілька МБ пам'яті.
whatsisname

5
@whatsisname: Те, що вони не виходять з ладу, не означає, що вони мовчки не псують дані. malloc()це одна з функцій, на яку ви точно хочете перевірити значення повернення!
TMN

1
@TMN: Якщо malloc вийшов з ладу, програма негайно поділиться на сегменти та завершиться, вона не продовжуватиме робити роботи. Вся справа в ризику і що підходить, а не «майже ніколи».
whatsisname

2
@Alex C не бракує гарного поводження з помилками. Якщо ви хочете винятків, ви можете їх використовувати. Стандартна бібліотека, яка не використовує винятку, є навмисним вибором (винятки змушують автоматизовувати управління пам'яттю, а часто не потрібно цього коду низького рівня).
мартінкунев

9

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

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

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

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


6
  • Різні вимоги безпеки вимагають різного рівня коректності. В програмному забезпеченні авіації або автомобільного контролю всі повернені значення перевіряються, пор. MISRA.FUNC.UNUSEDRET . Швидкий доказ концепції, яка ніколи не залишає вашу машину, напевно, ні.
  • Кодування коштує часу. Занадто багато нерелевантних перевірок програмного забезпечення, що не належить до безпечної безпеки, краще витратити зусилля в іншому місці. Але де солодке місце між якістю та вартістю? Це залежить від інструментів налагодження та складності програмного забезпечення.
  • Поводження з помилками може затьмарити потік управління та ввести нові помилки. Мені дуже подобається Річард "мережеві" функції обгортки Стівенса, які принаймні повідомляють про помилки.
  • Поводження з помилками рідко може бути проблемою продуктивності. Але більшість викликів бібліотеки C займатимуть так довго, що вартість перевірки повернутого значення буде незмірно невеликою.

3

Трохи абстрактного взяти на себе питання. І це не обов'язково для мови С.

Для більш великих програм у вас буде шар абстракції; можливо, частина двигуна, бібліотека або в рамках. Цей шар не буде піклуватися про погоду ви отримаєте достовірні дані або результат буде значення по замовчуванням деякий: 0, -1, і nullт.д.

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

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

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

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


0

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


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

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