какие могут быть ошибки в работе программы

Блок 3. Категории программных ошибок

Блок 1. Что представляет собой тестирование

Тестирование программного обеспечения— проверка соответствия между реальным и ожидаемым поведением программы, осуществляемая на конечном наборе тестов, выбранном определенным образом.

Блок 2. Как определить качество ПО (стандарты ISO, критерии качества, метрики)

Понятие «качество ПО» имеет множество трактовок. И тестировщик, и программист дадут свое толкование этого термина.

Если вкратце, то качество программы определяется следующим:

• возможностями, благодаря которым она понравится пользователю;

• недостатками, которые вынуждают пользователя приобрести другую программу.

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

Характеристики качества ПО

Наиболее распространенная модель описания качества ПО представлена в серии стандартов ​ISO 9126 Многоуровневая Модель Качества​ программного обеспечения.

Она описывает внутреннее и внешнее качество программного обеспечения​ по следующим шести критериям:

Наряду с атрибутами качества, стандарт ISO 9126 определяет ​наборы метрик для оценки (получения численного значения) каждого атрибута​:

1. Полнота реализации функций;

2. Корректность реализации функций​;

3. Отношение числа обнаруженных дефектов к прогнозируемому;

4. Отношение числа проведенных тестов к общему их числу​;

5. Отношение числа доступных проектных документов к указанному в их списке;

6. Наглядность и полнота документации​.

Как контролировать качество системы?

Как точно узнать, что программа делает именно то, что нужно, и ничего другого? Как определить, что она достаточно надежна, переносима, удобна в использовании? Ответы на этот вопрос можно получить с помощью процессов верификацииивалидации.

Блок 3. Категории программных ошибок

Не существует ни абсолютного определения ошибок, ни точного критерия наличия их в программе. Можно лишь сказать, насколько программа не справляется со своей задачей — это исключительно субъективная характеристика.

Это узкое определение, более подробную информацию вы получите на лекции, но стоит обратить внимание на то, что ошибка может возникнуть на любой стадии разработки проекта. Будьте бдительны, господа исследователи!

Коварные баги имеют свою категорийность, охватывающую все возможные варианты:

Ошибки пользовательского интерфейса

2. Взаимодействие программы с пользователем

3. Организация программы

4. Пропущенные команды

Процедуры обработки ошибок — это очень важная часть программы. В них тоже часто встречаются ошибки. Кроме того, правильно определив ошибку, программа не всегда выдает о ней достаточно информативное сообщение.

Ошибки, связанные с обработкой граничных условий

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

Начальное и последующие состояния

Бывает, что при выполнении какой-либо функции программы сбой происходит только однажды — при самом первом обращении к этой функции. Такую ошибку невозможно повторить (точнее, для ее повторения нужно установить новую копию программы). Но не стоит думать, что ошибка, проявляющаяся только при первом запуске программы, безвредна: ведь это будет первое, с чем столкнется каждый новый пользователь.

Ошибки управления потоком

Если по логике программы вслед за первым действием должно быть выполнено второе, а она выполняет третье, значит, в ​управлении потоком допущена ошибка. Такие ошибки трудно пропустить:

в худшем случае в работе программы произойдет сбой, а при менее серьезной ошибке она просто «забредет не туда».

Ошибки передачи или интерпретации данных

Один модуль может передавать данные другому или даже другой про грамме. Некоторые данные могут передаваться между модулями множество раз, и на каком-то этапе они могут быть разрушены или неверно интерпретированы. Изменения, внесенные одной из частей программы, могут потеряться или достичь не всех частей системы, где они важны.

Описывается так: предположим, в системе ожидаются два события, А и Б. Первым может произойти любое из них. Но если первым произойдет событие А, выполнение программы продолжится, а если первым наступит событие Б, то в работе программы произойдет сбой. Программист полагал, что первым всегда должно быть событие А, и не ожидал, что Б может выиграть гонки.

Программа может не справляться с повышенными нагрузками. Напри​мер, она может не выдерживать интенсивной и длительной эксплуатации или не справляться со слишком большими объемами данных.

Программы могут посылать устройствам неверные данные, игнорировать их сообщения об ошибках, пытаться использовать устройства, которые заняты или вообще отсутствуют. Даже если нужное устройство просто сломано, программа должна понять это, а не сбоить при попытке к нему обратиться.

Бывает, что старые ошибки вдруг всплывают снова из-за того, что про​грамма скомпонована с устаревшей версией одной из подпрограмм. Поэтому версии всех составляющих проекта обязательно должны централизованно контролироваться.

Документация считается частью программного продукта. И если она плохо написана, пользователь может подумать, что и сама программа требует значительных доработок.

Если программист допускает по полторы ошибки на каждую строку программного кода, то сколько их допускает тестировщик на каждый тест? Обнаружение ошибок, допущенных тестировщиками, — дело обычное. Конечно, если таких ошибок будет слишком много, вы быстро потеряете доверие остальных членов команды. Но нужно иметь в виду, что иногда ошибки тестировщика отражают проблемы пользовательского интерфейса: если программа заставляет пользователя делать ошибки, значит, с ней что- то не так. Конечно, многие ошибки тестирования вызваны просто неверными тестовыми данными.

И в заключение – в качестве бонуса на лекции вас ожидает тест для оценки себя как тестировщика 🙂

Источник

Программист и ошибки — актуально во все времена

Годы бегут, компьютеры становятся мощнее, листинги программ длиннее, а программисты всё ещё допускают те же самые ошибки (или же сталкиваются с ними)… Предлагаю разобраться с основными типами ошибок и причинами, по которым они происходят

Чтобы максимально раскрыть смысл фразы «актуально во все времена«, в качестве иллюстрирующих примеров будут приведены сведения времён старой доброй DOS 🙂, поэтому материал рекомендуется к прочтению любителям ностальгии

Какие же бывают типы ошибок?

Тип №1. Ошибки в программном комплексе, допущенные при разработке и не обнаруженные при его тестировании

• В «Справочнике Microsoft Works» и интерактивной помощи пакета интегрированной обработки информации Works 2.0 функция ЕСЛИ описана как
ЕСЛИ (Условие, ЗначениеЛожь, ЗначениеИстина)
Однако в действительности работа данной функции должна иметь следующий вид:
ЕСЛИ (Условие, ЗначениеИстина, ЗначениеЛожь)

В «Руководстве пользователя Microsoft Works для Windows» пакета Works 3.0 эта ошибка исправлена 🙂

• В русифицированном варианте Norton Utilities (версия 7.0, фирма Symantec) в утилите форматирования sformat при задании опции:
Системные файлы: [Не ставить…]
при форматировании выдаётся сообщение:
Системные файлы: Ставить
и наоборот, при задании опции:
Системные файлы: [Ставить…]
при форматировании выдаётся сообщение:
Системные файлы: Не ставить

• Неудача при запуске первого американского спутника к Венере случилась, вероятнее всего, из-за ошибки в программе – вместо требуемой в операторе запятой программист поставил точку. Вот как был записан этот оператор:
DO 50 I = 12.525
На самом же деле он должен был выглядеть следующим образом:
DO 50 I = 12,525
В программе на Фортране IV требовался цикл, а программист поставил точку, а в результате получилось присваивание значения 12,525 неявной переменной DO50I (пробелы в Фортране игнорируются) [ Спасибо за этот ценный комментарий-поправку хабраюзеру rexxer2 ]

• Потеря связи с космической станцией «Фобос-1» (СССР) произошла из-за ошибочной команды, переданной с Земли на бортовой компьютер.

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

К данному типу относятся ошибки в алгоритмах, когда алгоритм неверный или создан на основе неправильных представлений о действительности:

• Одна из первых компьютерных систем противовоздушной обороны США (60-е годы) в первое же дежурство подняла тревогу, приняв восходящую из-за горизонта Луну за вражескую ракету, поскольку этот «объект» приближался к территории США и не подавал сигналов, что он «свой» 🙂

Тип №2. Ошибки, возникающие при вводе в компьютер неверных данных

Весьма популярные ошибки, предотвращение которых известно под названием «защита от дурака»

• В 1983 году произошло наводнение в юго-западной части США. Причина заключалась в том, что в компьютер были введены неверные данные о погоде, в результате чего он дал ошибочный сигнал шлюзам, перекрывающим реку Колорадо.

• Ещё один печальный пример: в восьмидесятые годы прошлого века в Антарктиде разбился самолёт с туристами на борту, поскольку в управляющую полётом систему были заложены неверные координаты аэропорта взлёта и система ошибочно рассчитала высоту полёта над горами

Тип 3. Компьютерные вирусы, «вмешивающиеся» в работу компьютера и выполняемую им программу.

• Летом 1988 года в Мичиганском госпитале компьютерный вирус инфицировал три компьютера, которые обрабатывали информацию о пациентах. Вирус перемешал фамилии пациентов в базе данных. В результате данного «вмешательства» диагностические сведения одних пациентов оказались приписанными другим пациентам.

Тип 4. Выход из строя элементов компьютера и обслуживающих его систем

Тут, в принципе, всё обстоит точно так же, как и 20 лет назад: в процессе эксплуатации компьютерной системы возможно физическое повреждение накопителя, выход из строя блока питания, отключение электроэнергии, колебание напряжения в электрической сети и др. Результатом такого рода неисправностей может быть полная потеря информации, хранящейся на жёстком диске, частичная или полная потеря информации в файлах баз данных, нарушение работы систем, управляемых компьютером и многое другое. Для предотвращения ошибок данного типа используют системы, в которых одновременно работают несколько компьютеров, дублирующих друг друга, в компьютеры устанавливают два и более параллельно работающих накопителя (вспоминаем RAID-массивы), аппаратуру подключают к источникам бесперебойного питания, которые обеспечивают его работу при отключении электроэнергии или колебаниях напряжения электрической сети и т.д. и т.п.

Тип №5. Выход из строя или сбои в работе измерительных приборов и датчиков, используемых при управлении какими-либо техническими системами и технологическими процессами

• В июле 1985 года произошло преждевременное отключение компьютера одного из основных двигателей американского космического корабля «Челленджер» (Шаттл), едва не закончившееся катастрофой. Положение спас командир корабля, сумевший на двух работающих основных двигателях и двух менее мощных двигателях для маневрирования вывести «Челленджер» на орбиту. Причина же заключалась в том, что один из трёх бортовых компьютеров, управляющих двигателями (на каждый двигатель по компьютеру), был «обманут» вышедшим из строя датчиком, измеряющим температуру газа в двигателе. Для устранения подобных неполадок в будущем на следующих космических кораблях серии Шаттл были установлены датчики изменённой конструкции.

• При запуске французской ракеты нового поколения «Ариан-5» примерно на 37-й секунде полёта компьютер, находившийся на борту ракеты, получил от датчиков системы управления неверную информацию о пространственной ориентации ракеты. Исходя из этой информации, компьютер начал корректировать траекторию полёта для того, чтобы компенсировать не существующую на самом деле погрешность. Ракета стала отклоняться от курса, что привело к возрастанию нагрузок на её корпус. В результате чрезмерных нагрузок верхняя часть ракеты отвалилась, и по команде с земли ракета была взорвана.

Тип 6. «Злая воля человека», носителем которой чаще всего выступает либо программист, либо оператор

Программист, создавая программу, может специально внести в неё ошибку 🙂. Другим вариантом проявления «злой воли программиста» является включение в программу «логической бомбы», срабатывающей, например, после определённого числа запусков программы, определённых значениях входных данных и др. Оператор, обслуживающий компьютер, может сознательно ввести в компьютер неверные данные, которые и будут обработаны компьютером, выдавая неверные выходные данные в соответствии с принципом «мусор на входе – мусор на выходе».

• Сборочный конвейер волжского автомобильного завода в городе Тольятти работает под управлением АСУ, которая обеспечивает своевременное поступление деталей на конвейер со складов и из цехов вспомогательных производств. Для выполнения этой задачи информационно-управляющая система хранит информацию о тысячах узлов и деталей, из которых собирается автомобиль, о запасах деталей на складах, об их движении по транспортным линиям и т. д. На основе этой информации АСУ самостоятельно управляет автоматизированными складами, транспортными конвейерами, а также рядом других устройств.
Программист, разрабатывавший программное обеспечение для управления главным конвейером Волжского автозавода, сознательно внёс в программу «логическую бомбу» в знак протеста против низкой зарплаты. Через некоторое время эта «логическая бомба» сработала, и главный конвейер остановился на несколько дней. Ущерб от остановки составил 1 миллион рублей (в ценах 80-х годов), этот ущерб был несопоставим с зарплатой всех программистов ВАЗа, вместе взятых, а программист был дисквалифицирован и переведён в рабочие.

Подводим итоги

Надеюсь, данный материал окажется полезным. Желаю всем делать поменьше ошибок, ведь это особенно актуально в нынешние кризисные времена :^)

PS: сведения предоставляются по данным из этих источников, множество важных дополнений и опровержений представлены ниже в комментариях, особенно от хабраюзера scoon. Однако оригинальный текст не изменяю

Источник

Откуда берутся ошибки в ПО?

Почему бывает так, что программы работают неправильно? Все очень просто – они создаются и используются людьми. Если пользователь допустит ошибку, то это может привести к проблеме в работе программы – она используется неправильно, значит, может повести себя не так, как ожидалось.

Ошибка (error) – это действие человека, которое порождает неправильный результат.

Однако программы разрабатываются и создаются людьми, которые также могут допускать (и допускают) ошибки. Это значит, что недостатки есть и в самом программном обеспечении. Они называются дефектами или багами (оба обозначения равносильны). Здесь важно помнить, что программное обеспечение – нечто большее, чем просто код.

Дефект, Баг (Defect, Bug) – недостаток компонента или системы, который может привести к отказу определенной функциональности. Дефект, обнаруженный во время исполнения программы, может вызвать отказ отдельного компонента или всей системы.

При исполнении кода программы дефекты, заложенные еще во время его написания, могут проявиться: программа может не делать того, что должна или наоборот делать то, чего не должна – происходит сбой.

какие могут быть ошибки в работе программы. Смотреть фото какие могут быть ошибки в работе программы. Смотреть картинку какие могут быть ошибки в работе программы. Картинка про какие могут быть ошибки в работе программы. Фото какие могут быть ошибки в работе программы

Сбой (failure) – несоответствие фактического результата (actual result) работы компонента или системы ожидаемому результату (expected result).

Сбой в работе программы может являться индикатором наличия в ней дефекта.

Таким образом, баг существует при одновременном выполнении трех условий:

Важно понимать, что не все баги становятся причиной сбоев – некоторые из них могут никак себя не проявлять и оставаться незамеченными (или проявляться только при очень специфических обстоятельствах).

Причиной сбоев могут быть не только дефекты, но также и условия окружающей среды: например, радиация, электромагнитные поля или загрязнение также могут влиять на работу как программного, так и аппаратного обеспечения.

Всего существует несколько источников дефектов и, соответственно, сбоев:

Дефекты могут возникать на разных уровнях, и от того, будут ли они исправлены и когда, будет напрямую зависеть качество системы.

Качество (Quality) – степень, в которой совокупность присущих характеристик соответствует требованиям.

Качество программного обеспечения (Software Quality) – это совокупность характеристик программного обеспечения, отражающих его способность удовлетворять установленные и предполагаемые потребности.

Требование (Requirement) – потребность или ожидание, которое установлено. Обычно предполагается или является обязательным.

какие могут быть ошибки в работе программы. Смотреть фото какие могут быть ошибки в работе программы. Смотреть картинку какие могут быть ошибки в работе программы. Картинка про какие могут быть ошибки в работе программы. Фото какие могут быть ошибки в работе программы

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

Во втором случае ошибки были допущены уже при кодировании, что привело к появлению дефектов в готовом продукте. Но на этом уровне баги достаточно легко обнаружить и исправить, поскольку мы видим несоответствие требованиям.

Третий вариант хуже – здесь ошибки были допущены на этапе проектирования системы. Заметить это можно лишь проведя тщательную сверку со спецификацией. Исправить такие дефекты тоже непросто – нужно заново перерабатывать дизайн продукта.

В четвертом случае дефекты были заложены еще на этапе формирования требований; вся дальнейшая разработка и даже тестирование пошли по изначально неправильному пути. Во время тестирования мы не найдем багов – программа пройдет все тесты, но может быть забракована заказчиком.

Условно можно выделить пять причин появления дефектов в программном коде.

Источник

Выбор правильной стратегии обработки ошибок (части 1 и 2)

какие могут быть ошибки в работе программы. Смотреть фото какие могут быть ошибки в работе программы. Смотреть картинку какие могут быть ошибки в работе программы. Картинка про какие могут быть ошибки в работе программы. Фото какие могут быть ошибки в работе программы

Виды ошибок

Пользовательские ошибки

Сделаю очень громкое заявление: такие ошибки — на самом деле не ошибки.

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

Поэтому не имеет смысла применять к пользовательским ошибкам какие-либо стратегии обработки. Вводимые данные нужно как можно скорее проверять, чтобы ошибок не возникало.

Если в качестве стратегии обработки исправимых ошибок вы предпочитаете исключения, то будьте осторожны: исключения предназначены только для исключительных ситуаций, к которым не относится большинство случаев ввода пользователями неверных данных. По сути, это даже норма, по мнению многих приложений. Используйте исключения только тогда, когда пользовательские ошибки обнаруживаются в глубине стека вызовов, вероятно, внешнего кода, когда они возникают редко или проявляются очень жёстко. В противном случае лучше сообщать об ошибках с помощью кодов возврата.

Системные ошибки

Обычно системные ошибки нельзя предсказать. Более того, они недетерминистские и могут возникать в программах, которые до этого работали без нареканий. В отличие от пользовательских ошибок, зависящих исключительно от вводимых данных, системные ошибки — настоящие ошибки.

Но как их обрабатывать, как исправимые или неисправимые?

Это зависит от обстоятельств.

Многие считают, что ошибка нехватки памяти — неисправимая. Зачастую не хватает памяти даже для обработки этой ошибки! И тогда приходится просто сразу же прерывать выполнение.

Но падение программы из-за того, что ОС не может выделить сокет, — это не слишком дружелюбное поведение. Так что лучше бросить исключение и позволить catch аккуратно закрыть программу.

Но бросание исключения — не всегда правильный выбор.

Кто-то даже скажет, что он всегда неправильный.

Если вы хотите повторить операцию после её сбоя, то обёртывание функции в try-catch в цикле — медленное решение. Правильный выбор — возврат кода ошибки и цикличное исполнение, пока не будет возвращено правильное значение.

Если вы создаёте вызов API только для себя, то просто выберите подходящий для своей ситуации путь и следуйте ему. Но если вы пишете библиотеку, то не знаете, чего хотят пользователи. Дальше мы разберём подходящую стратегию для этого случая. Для потенциально неисправимых ошибок подойдёт «обработчик ошибок», а при других ошибках необходимо предоставить два варианта развития событий.

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

Программистские ошибки

Это худший вид ошибок. Для их обработки я стараюсь сделать так, чтобы мои ошибки были связаны только с вызовами функций, то есть с плохими параметрами. Прочие типы программистских ошибок могут быть пойманы только в runtime, с помощью отладочных макросов (assertion macros), раскиданных по коду.

При работе с плохими параметрами есть две стратегии: дать им определённое или неопределённое поведение.

Если исходное требование для функции — запрет на передачу ей плохих параметров, то, если их передать, это считается неопределённым поведением и должно проверяться не самой функцией, а оператором вызова (caller). Функция должна делать только отладочное подтверждение (debug assertion).

С другой стороны, если отсутствие плохих параметров не является частью исходных требований, а документация определяет, что функция будет бросать bad_parameter_exception при передаче ей плохого параметра, то передача — это хорошо определённое поведение (бросание исключения или любая другая стратегия обработки исправимых ошибок), и функция всегда должна это проверять.

Примечание: необязательно бросать исключение, чтобы получилось определённое поведение. Пока это не упомянуто в исходных условиях для функции, это считается определённым. Всё, что прописано в исходных условиях, не должно проверяться функцией, это неопределённое поведение.

Когда нужно проверять только с помощью отладочных подтверждений, а когда — постоянно?

К сожалению, однозначного рецепта нет, решение зависит от конкретной ситуации. У меня есть лишь одно проверенное правило, которому я следую при разработке API. Оно основано на наблюдении, что проверять исходные условия должен вызывающий, а не вызываемый. А значит, условие должно быть «проверяемым» для вызывающего. Также условие «проверяемое», если можно легко выполнить операцию, при которой значение параметра всегда будет правильным. Если для параметра это возможно, то это получается исходное условие, а значит, проверяется только посредством отладочного подтверждения (а если слишком дорого, то вообще не проверяется).

Хотя в ряде случаев это может быть ошибкой.

Об иерархии std::exception

Если в качестве стратегии обработки исправимых ошибок вы выбрали исключения, то рекомендуется создать новый класс и наследовать его от одного из классов исключений стандартной библиотеки.

Я предлагаю наследовать только от одного из этих четырёх классов:

Подведём итоги

Есть два пути обработки ошибок:

Есть три основных источника ошибок, каждый требует особого подхода:

Гибкие методики обработки ошибок в C++

Иногда что-то не работает. Пользователи вводят данные в недопустимом формате, файл не обнаруживается, сетевое соединение сбоит, в системе кончается память. Всё это ошибки, и их надо обрабатывать.

Это относительно легко сделать в высокоуровневых функциях. Вы точно знаете, почему что-то пошло не так, и можете обработать это соответствующим образом. Но в случае с низкоуровневыми функциями всё не так просто. Они не знают, что пошло не так, они знают лишь о самом факте сбоя и должны сообщить об этом тому, кто их вызвал.

В C++ есть два основных подхода: коды возврата ошибок и исключения. Сегодня широко распространено использование исключений. Но некоторые не могут / думают, что не могут / не хотят их использовать — по разным причинам.

Я не буду принимать чью-либо сторону. Вместо этого я опишу методики, которые удовлетворят сторонников обоих подходов. Особенно методики пригодятся разработчикам библиотек.

Проблема

Я работаю над проектом foonathan/memory. Это решение предоставляет различные классы выделения памяти (allocator classes), так что в качестве примера рассмотрим структуру функции выделения.

Это приводит нас к ситуации, когда чередуются нормальные и ошибочные ветви кода. Исключения в таком случае выглядят более подходящим решением. Благодаря им вы сможете обрабатывать ошибки только тогда, когда вам это нужно, а в противном случае — достаточно тихо передать их обратно вызывающему.

Это можно расценить как недостаток.

Но в подобных ситуациях исключения имеют также очень большое преимущество: функция выделения памяти либо возвращает валидную память, либо вообще ничего не возвращает. Это функция «всё или ничего», возвращаемое значение всегда будет валидным. Это полезное следствие согласно принципу Скотта Майера «Make interfaces hard to use incorrectly and easy to use correctly».

Учитывая вышесказанное, можно утверждать, что вам следует использовать исключения в качестве механизма обработки ошибок. Этого мнения придерживается большинство разработчиков на С++, включая и меня. Но проект, которым я занимаюсь, — это библиотека, предоставляющая средства выделения памяти, и предназначена она для приложений, работающих в реальном времени. Для большинства разработчиков подобных приложений (особенно для игроделов) само использование исключений — исключение.

Чтобы уважить эту группу разработчиков, моей библиотеке лучше обойтись без исключений. Но мне и многим другим они нравятся за элегантность и простоту обработки ошибок, так что ради других разработчиков моей библиотеке лучше использовать исключения.

Идеальное решение: возможность включать и отключать исключения по желанию. Но, учитывая природу исключений, нельзя просто менять их местами с кодами ошибок, поскольку у нас не будет внутреннего кода проверки на ошибки — весь внутренний код опирается на предположение о прозрачности исключений. И даже если бы внутри можно было использовать коды ошибок и преобразовывать их в исключения, это лишило бы нас большинства преимуществ последних.

К счастью, я могу определить, что вы делаете, когда обнаруживаете ошибку нехватки памяти: чаще всего вы журналируете это событие и прерываете программу, поскольку она не может корректно работать без памяти. В таких ситуациях исключения — просто способ передачи контроля другой части кода, которая журналирует и прерывает программу. Но есть старый и эффективный способ передачи контроля: указатель функции (function pointer), то есть функция-обработчик (handler function).

Если у вас включены исключения, то вы просто их бросаете. В противном случае вызываете функцию-обработчика и затем прерываете программу. Это предотвратит бесполезную работу функции-обработчика, та позволит программе продолжить выполняться в обычном режиме. Если не прервать, то произойдёт нарушение обязательного постусловия функции: всегда возвращать валидный указатель. Ведь на выполнении этого условия может быть построена работа другого кода, да и вообще это нормальное поведение.

Я называю такой подход обработкой исключений и придерживаюсь его при работе с памятью.

Решение 1: обработчик исключений

Сначала дополним класс и добавим функции для настройки и, возможно, запрашивания функции-обработчика. Я предлагаю делать это так же, как стандартная библиотека обрабатывает std::new_handler :

Поскольку это входит в область видимости класса исключений, вам не нужно именовать каким-то особым образом. Отлично, нам же легче.

Если исключения включены, то для удаления обработчика можно использовать условное компилирование (conditional compilation). Если хотите, то также напишите обычный подмешанный класс (mixin class), дающий требуемую функциональность.

Конструктор исключений элегантен: он вызывает текущую функцию-обработчика, передавая ей требуемые аргументы из своих параметров. А затем комбинирует с последующим макросом throw :

Когда я работаю с памятью, то при включённых исключениях у меня также включён и обработчик, который вызывается при бросании исключения.

Так что при этой методике вам ещё будет доступна определённая степень кастомизации, даже если вы отключите исключения. Конечно, замена неполноценная, мы только журналируем и прерываем работу программы, без дальнейшего продолжения. Но в ряде случаев, в том числе при исчерпании памяти, это вполне пригодное решение.

А если я хочу продолжить работу после бросания исключения?

Методика с обработчиком исключений не позволяет этого сделать в связи с постусловием кода. Как же тогда продолжить работу?

Ответ прост — никак. По крайней мере, это нельзя сделать так же просто, как в других случаях. Нельзя просто так вернуть код ошибки вместо исключения, если функция на это не рассчитана.
Есть только одно решение: сделать две функции. Одна возвращает код ошибки, а вторая бросает исключения. Клиенты, которым нужны исключения, будут использовать второй вариант, остальные — первый.

Извините, что говорю такие очевидные вещи, но ради полноты изложения я должен был об этом сказать.

Для примера снова возьмём функцию выделения памяти. В этом случае я использую такие функции:

Не делайте этого в обратной последовательности, иначе вам придётся ловить исключение, а это дорого. Также это не даст нам скомпилировать код без включённой поддержки исключений. Если сделаете, как показано, то можете просто стереть другую перегрузку (overload) с помощью условного компилирования.

Но даже если у вас включена поддержка исключений, клиенту всё равно может понадобиться вторая версия. Например, когда нужно выделить наибольший возможный объём памяти, как в нашем примере. Будет проще и быстрее вызывать в цикле и проверять по условию, чем ловить исключение.

Решение 2: предоставить две перегрузки

Если недостаточно обработчика исключений, то нужно предоставить две перегрузки. Одна использует код возврата, а вторая бросает исключение.

Если рассматриваемая функция не имеет возвращаемого значения, то можете её использовать для кода ошибки. В противном случае вам придётся возвращать недопустимое значение для сигнализирования об ошибке — как nullptr в вышеприведённом примере, — а также установить выходной параметр для кода ошибки, если хотите предоставить вызывающему дополнительную информацию.

Если возвращаемое значение не содержит недопустимое значение для обозначения сбоя, то по мере возможности используйте std::optional или что-то похожее.

Перегрузка исключения (exception overload) может — и должна — быть реализована в рамках версии с кодом ошибки, как это показано выше. Если компилируете без исключений, сотрите перегрузку с помощью условного компилирования.

std::system_error

Подобная система идеально подходит для работы с кодами ошибок в С++ 11.

Эту или подобную систему должны использовать все низкоуровневые функции, являющиеся закрытыми обёртками ОС-функций. Это хорошая — хотя и сложная — альтернатива службе кодов ошибок, предоставляемой операционной системой.

Да, и мне ещё нужно добавить подобное в функции виртуальной памяти. На сегодняшний день они не предоставляют коды ошибок.

std::expected

Выше упоминалось о проблеме, когда у вас нет возвращаемого значения, содержащего недопустимое значение, которое можно использовать для сигнализирования об ошибке. Более того, выходной параметр — не лучший способ получения кода ошибки.

А глобальные переменные вообще не вариант!

Заключение

Если вы создаёте библиотеки, то иногда приходится обеспечивать максимальную гибкость использования. Под этим подразумевается и разнообразие средств обработки ошибок: иногда требуются коды возврата, иногда — исключения.

Одна из возможных стратегий — улаживание этих противоречий с помощью обработчика исключений. Просто удостоверьтесь, что когда нужно, то вызывается callback, а не бросается исключение. Это замена для критических ошибок, которая в любом случае будет журналироваться перед прерыванием работы программы. Как таковой этот способ не универсален, вы не можете переключаться в одной программе между двумя версиями. Это лишь обходное решение при отключённой поддержке исключений.

Более гибкий подход — просто предоставить две перегрузки, одну с исключениями, а вторую без. Это даст пользователям максимальную свободу, они смогут выбирать ту версию, что лучше подходит в их ситуации. Недостаток этого подхода: вам придётся больше потрудиться при создании библиотеки.

Источник

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *