71. Проектируйте и пишите безопасный в отношении ошибок код

71. Проектируйте и пишите безопасный в отношении ошибок код

Резюме

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

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

Желательно дополнительно гарантировать, что конечное состояние либо является исходным состоянием (в результате отката после происшедшей ошибки), либо корректно вычисленным целевым состоянием (если ошибок не было). Это — строгая гарантия безопасности.

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

Обсуждение

Базовая, строгая гарантии и гарантия бессбойности (известная также как гарантия отсутствия исключений) впервые были описаны в [Abrahams96] и получили широкую известность благодаря публикациям [GotW], [Stroustrup00, §E.2] и [Sutter00], посвященным вопросам безопасности исключений. Эти гарантии применимы к обработке любых ошибок, независимо от конкретного использованного метода, так что мы можем воспользоваться ими при описании безопасности обработки ошибок в общем случае. Гарантия бессбойности является строгим подмножеством строгой гарантии, а строгая гарантия, в свою очередь, является строгим подмножеством базовой гарантии.

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

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

Однако в большинстве функций могут произойти сбои. Если ошибка возможна, наиболее безопасным будет гарантировать транзакционное поведение функции: либо функция выполняется успешно и программа переходит из начального корректного состояния в корректное целевое состояние, либо — в случае сбоя — программа остается в том же состоянии, в котором находилась перед вызовом функции, т.е. видимые состояния всех объектов после сбойного вызова оказываются теми же, что и до него (например, значение глобальной целой переменной не может измениться с 42 на 43), и любое действие, которое вызывающий код мог предпринять до сбойного вызова, должно остаться возможным (с тем же смыслом) и после сбойного вызова (например, ни один итератор контейнера не должен стать недействительным; применение оператора ++ к упомянутой глобальной целой переменной даст значение 43, а не 44). Такая гарантия безопасности называется строгой.

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

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

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

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

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

Примеры

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

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

Пример 3. std::vector::insert. Поскольку внутреннее представление vector<T> использует непрерывный блок памяти, вставка элемента в средину требует перемещения ряда имеющихся значений на одну позицию для освобождения места для вставляемого элемента. Перемещение выполняется с использованием копирующего конструктора T::T(const T&) и оператора присваивания T::operator=, и если одна из этих операций может сбоить (генерировать исключение), то единственный способ обеспечить строгую гарантию — это сделать полную копию контейнера, выполнить операцию над копией, а затем обменять оригинал и копию с использованием бессбойной функции vector<T>::swap.

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

Пример 4. Запуск спутника. Рассмотрим функцию f, в которой частью ее работы является запуск спутника, и используемую ею функцию LaunchSatellite, обеспечивающую гарантию не ниже строгой. Если функция f может выполнить всю работу, при которой может произойти сбой, до запуска спутника, то f способна обеспечить строгую гарантию. Но если f должна выполнить некоторые операции, в процессе которых может произойти сбой, уже после запуска спутника, то обеспечение строгой гарантии оказывается невозможным — вернуть запущенный спутник на стартовую площадку уже нельзя. (Такую функцию f следует разделить по крайней мере на две, поскольку одна функция не должна даже пытаться выполнить несколько различных действий такой важности; см. рекомендацию 5.)

Ссылки

[Abrahams96] • [Abrahams01b] • [Alexandrescu03d] • [Josuttis99] §5.11.2 • [Stroustrup00] §14.4.3, §E.2-4, §E.6 • [Sutter00] §8-19, §40-41, §47 • [Sutter02] §17-23 • [Sutter04] §11-13 • [Sutter04b]

Поделитесь на страничке

Следующая глава >

Похожие главы из других книг

О гуманном отношении к людям

Из книги Журнал «Компьютерра» №39 от 25 октября 2005 года автора Журнал «Компьютерра»

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


Пишите, Шура, пишите

Из книги Блоги. Новая сфера влияния автора Попов Антон Валерьевич

Пишите, Шура, пишите Метки: темы блога, автор блога, маркетинг, пользовательский контентХороший маркетинг начинается с хорошего продукта. Блог компании – это тоже своего рода продукт, постоянно изменяемый автором. Складывается он из оформления, юзабилити и содержания.


1.6.17. Правило расширяемости: проектируйте с учетом изменений в будущем, поскольку будущее придет скорее, чем кажется

Из книги Искусство программирования для Unix автора Реймонд Эрик Стивен

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


Совет 38. Проектируйте классы функторов для передачи по значению

Из книги Эффективное использование STL автора Мейерс Скотт

Совет 38. Проектируйте классы функторов для передачи по значению Ни С, ни С++ не позволяют передавать функции в качестве параметров других функций. Вместо этого разрешается передавать указатели на функции. Например, объявление стандартной библиотечной функции qsort


Безопасный сервер WWW

Из книги Основы AS/400 автора Солтис Фрэнк

Безопасный сервер WWW По мере того, как все больше компаний подключают свои AS/400 к WWW (World Wide Web), вопрос сетевой защиты серьезно обостряется. Многие только что рассмотренные нами приемы защиты AS/400 в клиент/серверной среде применимы также и при подключении ее к открытым сетям


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

Из книги Эффективное использование C++. 55 верных способов улучшить структуру и код ваших программ автора Мейерс Скотт

Правило 18: Проектируйте интерфейсы так, что их легко было использовать правильно и трудно – неправильно C++ изобилует интерфейсами. Интерфейсы функций. Интерфейсы классов. Интерфейсы шаблонов. Каждый интерфейс – это средство, посредством которого пользователь


1.6.17. Правило расширяемости: проектируйте с учетом изменений в будущем, поскольку будущее придет скорее, чем кажется

Из книги Искусство программирования для Unix автора Реймонд Эрик Стивен

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


Резюме: безопасный многопоточный код

Из книги Системное программирование в среде Windows автора Харт Джонсон М

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


67. Пишите максимально обобщенный код

Из книги Стандарты программирования на С++. 101 правило и рекомендация автора Александреску Андрей

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


4.2 Об отношении Linux к разделам винчестера

Из книги Руководство по переходу на Ubuntu 10.04 LTS «Lucid Lynx» автора Неворотин Вадим

4.2 Об отношении Linux к разделам винчестера Linux очень забавно работает с различными устройствами и источниками данных. Для каждого такого объекта создаётся специальный файл, через который происходит «общение» этого объекта с системой. В частности, подобные файлы есть для


2.3.4. Безопасный доступ к почте

Из книги Яндекс для всех автора Абрамзон М. Г.

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


Осторожность в отношении автоматического преобразования типов

Из книги Фундаментальные алгоритмы и структуры данных в Delphi автора Бакнелл Джулиан М.

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


4.13.3. Безопасный Интернет

Из книги Linux глазами хакера автора Флёнов Михаил Евгеньевич

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


Самый безопасный способ разработки шахты

Из книги Все секреты Minecraft автора Миллер Меган

Самый безопасный способ разработки шахты Хотите избежать скелетов и пещерных пауков, но не желаете переключаться в режим Мирный (Peaceful)? Поскольку мобы возникают в темноте, контролируйте уровень освещенности. Раскапывайте шахту прямо из своего защищенного дома


Совет 31: Безопасный шопинг

Из книги Выжить в цифровом мире. Иллюстрированные советы от «Лаборатории Касперского» автора Дьяков Михаил

Совет 31: Безопасный шопинг Правильный интернет-магазин непременно использует защищенный протокол HTTPS с сертификатом, подписанным легальным удостоверяющим центром. Убедиться в наличии такового совсем несложно — достаточно посмотреть на адресную строку. Если там