суббота, 13 февраля 2010 г.

Как MS-DOS сообщала о кодах ошибок?

Это перевод How did MS-DOS report error codes?. Автор: Реймонд Чен.

Вызовы функций MS-DOS (ах, этот int 21h) обычно сообщали об ошибке установкой флага CF и записью кода ошибки в регистр AX. Эти коды ошибок выглядят ужасно знакомыми сегодня: это те же самые коды ошибок, что использует Windows. Все эти малозначные коды типа ERROR_FILE_NOT_FOUND ведут свою историю ещё из MS-DOS (и, вероятно, даже глубже в прошлое).

Коды ошибок являются одной из главных проблем совместимости, потому что вы не можете просто добавить новые коды без ломания существующих программ. Например, стал широко известным тот факт, что "Единственными возможными кодами ошибок, возвращаемыми OpenFile, могли быть 3 (path not found), 4 (too many open files) и 5 (access denied)". Если бы MS-DOS попробовала вернуть код ошибки не из этого списка, программы бы вылетали, потому что они использовали возвращаемое значение как индекс в таблице (массиве) без проверки диапазона. Возврат новой ошибки типа 32 (sharing violation) означало бы, что эти программы переводили бы управление на мусорный адрес и вылетали.

Ещё о совместимости кодов ошибок - в другой раз.

Когда заходит речь о добавлении новых кодов ошибок, совместимость диктует, чтобы вы не меняли коды, уже возвращаемые функциями. Поэтому, если происходит новый тип ошибки (например, a sharing violation), то выбирается один из предыдущих "хорошо-известных" кодов ошибок, который больше всего похож по смыслу на новый код (для "sharing violation" наилучшим совпадением будет, вероятно, "access denied"). Программы, которые были в курсе про новые коды ошибок, могли вызвать новую функцию "получить расширенный код ошибки", которая возвращала один из новых кодов ошибок (в нашем случае - 32 для sharing violation).

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

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

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

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

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

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

Во-первых, из-за наследования вы спокойно можете расширять пространство ошибок: вводя новые классы, дочерние к уже существующим классам исключений. Вы не поломаете при этом никакой код, поскольку он как обрабатывал исключения определённых классов, так и продолжит это делать - теперь уже включая и ваши новые классы (и всё благодаря наследованию). Например, если код реагирует на ELoadConfigError, то вы можете ввести новое исключение вида ELoadConfigFileReadingError = class(ELoadConfigError) - и старый код по-прежнему будет корректно реагировать на все ошибки. А те, кто в курсе про новые классы (ELoadConfigFileReadingError), смогут реагировать только на них. Таким образом все стороны оказываются удовлетворены.

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

Комментариев нет:

Отправить комментарий

Можно использовать некоторые HTML-теги, например:

<b>Жирный</b>
<i>Курсив</i>
<a href="http://www.example.com/">Ссылка</a>

Вам необязательно регистрироваться для комментирования - для этого просто выберите из списка "Анонимный" (для анонимного комментария) или "Имя/URL" (для указания вашего имени и ссылки на сайт). Все прочие варианты потребуют от вас входа в вашу учётку (поддерживается OpenID).

Пожалуйста, по возможности используйте "Имя/URL" вместо "Анонимный". URL можно просто не указывать.

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