Як слід повідомляти про помилки в наукових бібліотеках?


11

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

  1. Повернути код помилки з результатом, повернутим аргументом вказівника. Це те, що робить PETSc.
  2. Повернення помилок за вартовим значенням. Наприклад, malloc повертає NULL, якщо він не міг виділити пам'ять, sqrtповерне NaN, якщо ви перейдете в негативному числі і т. Д. Цей підхід використовується в багатьох функціях libc.
  3. Киньте винятки. Використовується в угоді.II, Трилінос тощо.
  4. Повернути тип варіанту; наприклад, функція C ++, яка повертає об'єкт типу, Resultякщо він працює правильно і використовує тип, Errorщоб описати, як він не вдався std::variant<Error, Result>.
  5. Використовуйте ствердження та збої. Використовується в p4est та деяких частинах igraph.

Проблеми з кожним підходом:

  1. Перевірка кожної помилки вводить багато зайвого коду. Значення, в яких буде зберігатися результат, завжди повинні бути оголошені спочатку, вводячи безліч тимчасових змінних, які можуть бути використані лише один раз. Цей підхід пояснює, яка помилка виникла, але важко визначити, чому або, для глибокого стека викликів, де.
  2. Випадок помилки легко ігнорувати. Крім того, багато функцій навіть не можуть мати значущої вартості, якщо весь діапазон типів виводу є правдоподібним результатом. Багато з тих же проблем, що і №1.
  3. Можливо лише в C ++, Python тощо, а не в C або Fortran. Можна імітувати на C за допомогою чаклуна setjmp / longjmp або libunwind .
  4. Можливо лише в C ++, Rust, OCaml тощо, а не в C або Fortran. Можна імітувати на C за допомогою макро-чаклунства.
  5. Можливо, найбільш інформативний. Але якщо ви скористаєтесь таким підходом до, скажімо, бібліотеки С, для якої ви потім пишете обгортку Python, дурна помилка, як передача індексу поза межами масиву, призведе до збою інтерпретатора Python.

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

Ще один розгляд полягає в тому, які види помилок підлягають відновленню чи ні. Вихід з ладу не може бути відновлений, і, у будь-якому випадку, вбивця поза пам'яті ОС дістанеться до нього, перш ніж це зробити. Індекс поза межами розміру масиву також не підлягає відновленню. Для мене як користувача найприємніше, що може зробити бібліотека - це збій з інформативним повідомленням про помилку. З іншого боку, невдача, скажімо, ітеративного лінійного розв'язувача для конвергенції могла бути відшкодована за допомогою вирішувача прямої факторизації.

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

В інший бік, я думаю, що підхід №5 може бути значно вдосконалений для бібліотеки С, якщо він визначає глобальний покажчик функції обробника тверджень як частина публічного API. Обробник тверджень за замовчуванням повідомляє про номер файлу / рядка та збої. Прив'язки C ++ для цієї бібліотеки визначали б новий обробник тверджень, який замість цього видає виняток C ++. Аналогічно, прив'язки Python визначали б оброблювач тверджень, який використовує API CPython для викидання винятку Python. Але я не знаю жодного прикладу, який би використовував такий підхід.


Інша увага - це ефективність роботи. Як ці різні методи впливають на швидкість роботи програмного забезпечення? Чи варто використовувати різні поводження з помилками у "контрольних" частинах коду (наприклад, обробка вхідних файлів) порівняно з обчислювально дорогими "двигунами"?
LedHead

Зауважте, що найкраща відповідь буде відрізнятися залежно від мови.
chrylis

Відповіді:


10

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

По-перше, є два види помилок: помилки, з яких можна відновити, і помилки, з яких неможливо відновити.

  • Перший, наприклад, якщо вхідний файл неможливо прочитати - наприклад, якщо ви читаєте інформацію з такого файлу, $HOME/.dealiiякий може бути або не може існувати. Функція читання повинна просто повернутися до функції виклику, щоб остання зрозуміла, що робити. Можливо також, що ресурс наразі недоступний, але може бути знову за хвилину (віддалена змонтована файлова система).

  • Останнє, наприклад, якщо ви намагаєтесь додати вектор розміром 10 до вектора розміром 20: Спробуйте, як би не було, нічого з цим не можна зробити - у коді є помилка, яка призвела до момент, коли ми намагалися зробити додавання.

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

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

  • У першому випадку виникла виняткова ситуація, яку можна було б впорати. Незважаючи на те, що C і Fortran не мали можливості це висловити, усі розумні мови, які з'явилися пізніше, включили способи в мовний стандарт для вирішення таких "виняткових" повернень, забезпечуючи, ну, "винятки". Використовуйте ці - ось для чого вони там; вони також розроблені таким чином, що ви не можете забути їх ігнорувати (якщо це зробити, виняток просто поширюється на один рівень вище).

Іншими словами, я виступаю за те, що я тут виступаю (і що угода. II) - це суміш ваших стратегій 3 і 5, залежно від контексту. Це правда, що 3 не працює на таких мовах, як C або Fortran - і в цьому випадку можна стверджувати, що це вагомий привід просто не використовувати мови, які ускладнюють вираження того, що ви хочете робити.

x), але оскільки оцінювач потрібно викликати неодноразово, він повинен не просто вийти з ладу, а просто кинути виняток. У таких випадках, навіть якщо передача негативного значення не підлягає відновленню, слід викинути виняток, а не переривати програму. Я не погодився з такою позицією пару років тому, але змінив свою думку після того, як керівні принципи щодо програмного забезпечення спільноти xSDK закодували вимогу, що програми ніколи не мають збоїв (або, принаймні, повинні мати спосіб переходу з аварії на виняток - так що угоди. Тепер у мене є можливість зробити Assertвикид виключенням замість дзвінка abort().)


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

@cdalitz: Це недолік дизайну C ++, що ви можете кидати об'єкти будь-якого типу. Але будь-яке розумне програмне забезпечення (виключається Trilinos) лише викидає винятки, які є похідними std::exception, і їх можна спіймати шляхом посилання, не знаючи похідного типу.
Вольфганг Бангерт

1
Але я категорично не згоден із поверненням коду помилки з причин, викладених у початковому запитанні: (i) Коди помилок занадто часто ігноруються, і як наслідок помилки взагалі не обробляються; (ii) у багатьох випадках просто немає виняткових значень, які можна розумно повернути, враховуючи, що тип повернення функції фіксований; (iii) функції мають різні типи повернення, і вам доведеться визначати в кожному конкретному випадку окремо, що таке "виняткове" значення, яке представляє помилку.
Вольфганг Бангерт

WB написав (вибачте, фокус '@' чомусь не працює, а StackExchage чомусь видаляється ім'я користувача: "Коди помилок ігноруються занадто часто". Це ще більше стосується вилучення винятків: не багато розробників програмного забезпечення переживають проблеми з розбиттям кожного виклику функції в блоці спробу / лову. Але це здебільшого справа смаку: доки в документації чітко зазначено, чи є і які винятки кидає функція, я можу впоратися з нею. Але знову можна сказати: обов'язок писати документацію занадто часто ігнорується ;-)
cdalitz

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