Шляхи уникнення/зменшення болю поверненого значення після кожного виклику функції?

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

У всякому разі, коли я пишу C-код, дуже часто він виглядає так:

int retval;
...
retval = my_function(arg1, arg2);
if (retval != SUCCESS_VALUE) { do_something(); }

retval = my_other_function(arg1, arg2);
if (retval != SUCCESS_VALUE) { do_something_else(); }

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

Деякі ідеї:

  • Спробуйте жити за допомогою assert() (але це не для коду продукту, який не може дозволити собі просто померти).
  • Завершити функцію дзвінків за допомогою макросу або функцію перевірки зворотного значення, наприклад, ensure_success (my_function (args) або ensure_success (my_other_function (args), my_error_handler, error_handler_args) .

Чи існує якась інша практика у цьому питанні, я б вважала за краще?

Edit:

  • Так, я пишу C-код. Я поважаю вашу думку, що я маю намагатися уникати написання книги в цілому, але це насправді не конструктивне. Це не питання мовної війни, будь ласка, не робіть цього.
  • Я не прошу думок про те, що найкраще зробити, я просто хочу додаткові можливості. (Я виберу один, який мені подобається, інші можуть вибирати щось інше.)
8
@sbi так, я думаю, ми всі згодні з тим, що у С є "проблеми" :)
додано Автор Martin James, джерело
Тим часом ніхто не приєднався до мене в моєму розумному голосуванні, щоб закрити це як насамперед думку (яка передувала мовній війні).
додано Автор Jim Balter, джерело
@sbi Я не збираюся потрапити в ще одну дурну дискусію. Якщо у вас є фактична відповідь, перейдіть вперед і опублікуйте її.
додано Автор Jim Balter, джерело
@ sbi Підходи до цієї проблеми різноманітні та індивідуальні ... це дуже питання думки, як її вирішити.
додано Автор Jim Balter, джерело
@ Джим: Будь ласка, скажіть мені про область в програмуванні, де підходи не різноманітні та індивідуальні, і де це певною мірою не є питанням, яке можна вибрати.
додано Автор sbi, джерело
@ Джим: Я не робив, тому що це не на думку. (Якщо ви думаєте, що "написання такого коду засмоктує" є думкою, вам потрібно отримати освіту. :) )
додано Автор sbi, джерело
Для обробки мови без виключень це дуже важливо для того, щоб не пропустити помилку.
додано Автор sbi, джерело
assert не для обробки помилок
додано Автор Cat Plus Plus, джерело
@JimBalter я зараз зробив.
додано Автор user529758, джерело

7 Відповіді

Ви зіткнулися з тим, що при багатьох рішеннях проблем багато питань може статися не так.

Обробка цієї "помилки" -ситуацій є частиною вирішення проблеми.

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

Отже, (і) розробка/написання коду, необхідного для усунення несправностей, є важливою умовою успіху.


Що стосується практичного пораду, то, розглядаючи обробку помилок як частину коду в цілому, для обробки помилок ті ж правила застосовуються як до кожної іншої рядки коду:

  • легко зрозуміти
  • достатньо строгий, щоб бути ефективним
  • достатньо гнучкий для розширення
  • не вдалося зберегти (надмірне згадати в цьому контексті)
  • систематичний/послідовний (з точки зору шаблонів, найменування, компонування)
  • задокументовано

Можливі мовні конструкції для використання:

  • breaks out of local contexts
  • (goto)*
  • (longjmp & friends to "simulate" exceptions)*
  • cleanly defined (again systematic/consistent) function declarations and return codes

* Використовується з дисципліною структурованим способом.

3
додано
@alk Навіть ручна сторінка longjmp говорить, що це погано, його слід уникати. Зрілість кодера має зовсім не що інше , що пов'язано з тим, що вся ідея за нею є поганою.
додано Автор Bartek Banachewicz, джерело
longjmp і goto можуть бути використані, якщо це зрілий і дисциплінований кодер .. ;-)
додано Автор alk, джерело
@ sbi: див. вище, pls.
додано Автор alk, джерело
@CatPlusPlus Якщо ви називаєте себе майстром, ви зможете це впоратись ... - але ви маєте рацію, мені це не подобається. Занадто стрес Але я не міг опиратися, згадавши це, послухавши свою війну вище ... ;-)
додано Автор alk, джерело
longjmp викличе хаос з кодом очищення.
додано Автор sbi, джерело
@BartekBanachewicz: я не погоджуюсь - longjmp() заповнює порожнечу між кодами стану/ errno і exit() / abort() , і стає особливо корисним, якщо задіяні зворотні виклики
додано Автор Christoph, джерело
Використання longjmp вимагає проблем.
додано Автор Cat Plus Plus, джерело

Спробуйте подолати цю проблему з певною науковою цікавістю. Є багато людей, які стверджують, що підхід C до обробки помилок призводить до того, що програмісти більше усвідомлюють стан помилок, приділяють більше уваги помилкам і де/як їх слід трактувати. Просто візьміть до уваги таку вправу (якщо трохи втомлює) поінформованості, наприклад, медитації :)

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

Check out this article on C error handling mantra: http://tratt.net/laurie/tech_articles/articles/how_can_c_programs_be_so_reliable

Один загальний відповідь на ваше загальне питання: спробуйте зробити функції максимально малими, щоб ви могли безпосередньо повернути з них помилки. Цей підхід хороший на всіх мовах. Решта - це вправи в структуруванні коду.

3
додано
Або депресії.
додано Автор Bartek Banachewicz, джерело
Lemme наводить статтю, яку ви опублікували: (...) треба обробляти всі можливі шляхи помилки. Це надзвичайно болісно (...)
додано Автор Bartek Banachewicz, джерело
@BartekBanachewicz Немає болю, ніякого прибутку: психологія на сьогоднішній день. Ru/blog/memory-medic/201106/& hellip;
додано Автор ctn, джерело
Щоб бути ясним, я згоден з висновком статті: "Я не рекомендую використовувати C, якщо не багато чого було приділено рішення". Я хотів би додати: C не виробляє надійне програмне забезпечення, він виробляє надійних програмістів.
додано Автор ctn, джерело
@BartekBanachewicz Це, дійсно, альтернатива.
додано Автор ctn, джерело

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

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

int func1 (int arg1, int arg2)
{
  return arg1 == arg2;
}

int func12 (int arg1, int arg2)
{
  return arg1 - arg2;
}

і деяка функція обробки помилок:

void err_handler_func1 (int err_code)
{
  if(err_code != 0)
  {
    halt_and_catch_fire();
  }
}

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

typedef void(*func_t)(int, int);
typedef void(*err_handler_t)(int);

typedef enum
{
  FUNC1,
  FUNC2,
  ...
  FUNC_N//not a function name, but the number of items in the enum
} func_name_t;

const func_t function [FUNC_N] =
{
  func1,
  func2,
  ...
};

const err_handler_t err_handler [FUNC_N] = 
{
  err_handler_func,
  err_handler_func,
  ...
}

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

void execute_function (int func_n, int arg1, int arg2)
{
  err_handler[func_n]( function[func_n](arg1, arg2 );
}

execute_function (FUNC1, 1, 2);
execute_function (FUNC2, 2, 2);

і так далі.

2
додано

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

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

Приблизно, ви можете розділити умови помилки на смертельні та не фатальні.

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

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

З фатальних є ті, які припиняють програму, тобто ви просто надрукуєте повідомлення про помилку та exit() з ненульовим статусом.

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

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

Трохи варіативної макро магії дозволяє придумати хороший синтаксис для принаймні деяких з цих випадків, наприклад

_Bool do_stuff(int i);
do_stuff(i) || panic("failed to do stuff with %i", i);
2
додано

In rare but happy situations you can exploit the short-circuiting nature of && and ||:

if (my_function1(arg1, arg2) == SUCCESS_VALUE 
 && my_function2(arg3) == SUCCESS_VALUE)
{
  /* Proceed */
}
else
{
  /* Cry and die. */
}

Якщо ви повинні розрізняти помилку в функціях 1 і 2, це може не так добре працювати.

Естетично, не супер-красиво, але це обробка помилок зрештою :)

0
додано
Гаразд, це опція, але я намагаюся не вводити виклик в тест if; Я вважаю, що це має дещо оманливий ефект.
додано Автор einpoklum, джерело
Отже, чи можете ви розкрити свою відповідь, щоб запропонувати підхід, оснований на goto ?
додано Автор einpoklum, джерело
На жаль, це виключає багато можливостей. Ти можеш це макрофікувати. Також я отримаю знімок за цей коментар, але іноді також допомагає gooto .
додано Автор Skurmedel, джерело
@ Джим: lol "modern"
додано Автор Puppy, джерело
@DeadMG Ви, можливо, пропустили попередню (модифіковану) дискусію тут.
додано Автор Jim Balter, джерело
Ніхто не повинен заперечувати проти того, що ви згадуєте про перехід ... це сучасна особливість сучасних структурованих мов збірки, таких як C.
додано Автор Jim Balter, джерело

Якщо було б вирішення цього в С, то виключення не були б винайдені. У вас є тільки два варіанти:

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

Хоча, якщо ви використовуєте Visual C, ви можете використовувати SEH як винятки в C. Це не дуже добре, але працює.

0
додано
Я не дуже шанувальник цього, але Go має дисциплінований підхід до цієї проблеми, яка не ґрунтується на виключеннях (за винятком деяких дуже обмежених форм).
додано Автор Jim Balter, джерело