Параллельное и распределенное программирование на С++
Шрифт:
Рис. 7.3. Архитектура упрощенного компонента обработки ошибок
Компонент 1 на рис. 7.3— это простой компонент отображения (map), который содержит список номеров ошибок и их описания. Компонент 2 содержит объект, который преобразует номера ошибок в адреса переходов, функций или подсистем. По номеру ошибки компонент 2 определяет направление перехода. Компонент 3 преобразует номера ошибок в иерархическую структуру отчетов и логику отчетов. Иерархическая структура отчетов содержит данные о том, кого (или что) необходимо уведомить об ошибке. Логика отчетов определяет, что должно включать это уведомление. Компонент 4
Надежность ПО: простой план
Напомним, что мы различаем ошибочные и неудобные (нежелательные) условия. Неудобные или нежелательные условия должны обрабатываться обычной программной логикой. Ошибки (дефекты) требуют специального программирования. В книге Страуструпа Язык программирования С++ (1997) автор приводит четыре основных альтернативных действия, которые может предпринять программа при обнаружении ошибки. По мнению Страуструпа, программа, выявив проблему, которую невозможно обработатьлогически, должна реализовать один из следующих вариантов поведения.
• Вариант1. Завершить программу.
• Вариант 2. Возвратить значение, обозначающее «ошибку».
• Вариант 3. Возвратить значение, обозначающее нормальное завершение, и оставить программу в состоянии с необработанной ошибкой.
• Вариант 4. Вызвать функцию, предназначенную для вызова в случае ошибки.
Эти четыре альтернативы можно «примерить» к отношениям типа «изготовитель-потребитель». Изготовитель — это обычно некоторый участок програм м ного кода, который реализует библиотечную функцию, класс, библиотеку классов или оболочку приложения. В качестве потребителя можно представить участок программного кода, который вызывает библиотечную функцию, класс, библиотеку классов или оболочку приложения. Потребитель делает запрос. Изготовитель при попытке выполнить запрос обнаруживает ошибку, и его дальнейшее поведение должно быть направлено на реализацию одного из перечисленных выше четырех альтернативных вариантов. Однако проблема состоит в том, что ни один из них не универсален.
Очевидно, что завершать программу при каждом обнаружении ошибки попросту неприемлемо. Здесь мы согласны со Страуструпом. В таких случалх следует поступать более изобретательно. Что касается варианта 2, то примитивный возврат значения ошибки действительно может помочь в некоторых ситуациях, но далеко не во всех. Не каждое возвращаемое значение может интерпретироваться как успешное или неудачное. Например, если значение, возвращаемое некоторой функцией, имеет вещественный тип, и область определения функции включает как отрицательные, так и положительные значения, то какое тогда значение функции можно использовать для представления ошибки? Другими словами, это не всегда возможно. С нашей точки зрения, вариант 3 также неприемлем. Ведь если «изготовитель» возвращает значение, обозначающее нормальное завершение, «потребитель» продолжит работу, предположив, что его запрос был выполнен, а это может вызвать еще большие проблемы. Осталось рассмотреть вариант 4. Он требует более внимательного подхода при обсуждении обработки как ошибок, так и исключительных ситуаций.
План А: модель возобновления, план Б: модель завершения
При обнаружении ошибки или исключительной ситуации существует два основных плана для реализации варианта4. Первый план состоит в попытке скорректировать условия, которые вызвали сбой, а затем возобновить выполнение с точки, в которой была обнаружена ошибка или исключительнал ситуация. Этот подход называетс я
Использование объектов отображения для обработки ошибок
Компонент отображения (map) можно использовать как составную часть стратегии обработки ошибок или обработки исключений. Назначение отображения — связать один элемент с другим. Например, отображение можно использовать для связи номеров ошибок с их описаниями:
//.. .
map<int,string> ErrorTable;
ErrorTable[123] = «Деление на нуль»;
ErrorTable[4556] = «Отсутствие тонального вызова»;
//. . .
Здесь число 123 связано с описанием «Деление на нуль». Тогда при выполнении инструкции
cout « ErrorTable[123] « endl;
в объект выходного потока cout будет записана строка «Деление на нуль».
Помимо отображения встроенных типов данных, можно также отображать (т.е. находить соответствие) определенные пользователем объекты, содержащие данные встроенных типов. Вместо того, чтобы некоторое отображение просто возвращало описательное сообщение для каждого номера ошибки, можно позаботиться о том, чтобы оно возвращало объект с соответствующим номером ошибки. Этот объект может иметь методы, предназначенные для коррекции ошибок, составления отчетов об ошибках и их регистрации (записи ошибок в системный журнал). Например, предположим, что у нас есть следующий определенный пользователем объект:
defect_response:
class defect_response{
protected: //. . .
int DefectNo;
string Explanation;
public:
bool operator<(defect_response &X);
virtual int doSomething(void); string explanation(void);
//...
};
Теперь мы можем внести в отображение объекты типа defect_response:
//...
map<int,defect_reponse *> ErrorTable;
defect_response * Response;
Response = new defect_response;
ErrorTable[123] = Response;
//...
Этот код связывает объект отклика (на ошибку) с номером ошибки 123. Благодаря полиморфизму объект отображения может содержать указатели на любой объект типа defect_response или любой объект, который выведен из него. Предположим, что у нас есть следующий класс:
class exception_response : public defect_response{
//.. . public:
int doSomething(void)
//...
};
Этот класс exception_response является потомко м класса defect_response, поэтому мы можем внести в объект ErrorTable указатели на тип exception_re sponse.
//...
map<int,defect_reponse *> ErrorTable;
defect_response * Response;
exception_response *Response2;
Response = new defect_response;
Respone2 = new exception_response;
ErrorTable[123] = Response; // Хранит объект типа
// defect_response.
ErrorTable[456] = Response2; // Хранит объект типа
// exception_response.
//...
Это определение означает, что объект типа ErrorTable может связывать с соответствующим но м ером ошибки различные объекты (с различными описаниями и характеристиками). Следовательно, при вызове метода doSomething объект ProblemSolver будет выполнять различные наборы инструкций: