Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
КП - 2 часть - Лекция 2. Исключительные ситуаци...docx
Скачиваний:
0
Добавлен:
01.07.2025
Размер:
342.41 Кб
Скачать

2 Приближение

2.1 Развитие способов обработки ошибок

Ассемблер.

В древние времена ошибки обрабатывались через флаги регистров (на примере открытия файла для чтения). Код выглядел как-то так:

mov ah, 3dh ; хочу открыть файл!

lea dx, fileName + 2; с именем, занесенным в fileName

xor al, al ; для чтения!

int 21h ; ms-dos исполни!

jnc OK_OPEN ; если получилось открыть то перепрыгнуть на метку OK_OPEN

mov ah, 9 ; хочу вывести сообщение

lea dx, errorMessage; с текстом "ничего не получилось"

int 21h ; ms-dos исполни!

jmp EXIT_APP ; перепрыгнуть на метку EXIT_APP

OK_OPEN:

...

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

С.

#include <stdio.h>

#include <errno.h>

#include <string.h>

int main()

{

FILE *ptr_file = fopen("output.txt", "r");

if (!ptr_file)

{

printf("Failed to open file output.txt for reading: %s", strerror(errno));

return 1;

}

// ...

fclose(ptr_file);

return 0;

}

В С обработка ошибок стала намного проще. Функции Windows, работы с файлами и др. через возвращали код успешного выполнения (или устанавливали глобальную переменную errno). Принята была какая-то такая концепция.

  1. если возвращаемое значение – адрес, то когда все плохо, возвращать NULL

  2. во всех остальных случаях возвращать 0 – когда все успешно завершилось и код ошибки, если не все хорошо. Либо устанавливать в глобальную переменную errno код ошибки.

Также язык намного более выразителен, чем ассемблер. И все таки объем кода по обработке ошибок велик, ведь это только обработка одного вызова fopen! А если присутствует декомпозиция, то посмотрите, насколько некомфортным становится обработка ошибок.

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

Метафора: снятие денег в банкомате из двух шагов.

void ВыдачаНаличныхВБанкомате(сколькоДенег)

{

СписатьДеньгиСоСчета(сколькоДенег)

ВыдатьДеньги(сколькоДенег)

}

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

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

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

С++

Какие преимущества это дает?