Похоже, что некоторые люди проинтерпретировали заголовок одного моего поста "Чище, элегантнее и неверно", как говорящего об исключениях вообще (см. библиографическую ссылку [35]; заметьте, что цитирующий даже изменил название моего поста!).
Этот заголовок был ссылкой только на конкретный кусок кода, который я скопировал из книжки, где автор утверждал, что представленный кусок кода "чище и элегантнее". Я лишь указал, что этот фрагмент кода не только чище и элегантнее, но и просто неверен.
Вы можете писать корректный код с исключениями.
Заметьте, это тяжело.
С другой стороны, только потому, что что-то является тяжёлым, не значит, что его не нужно делать.
Вот разбивка:
Очень просто | Тяжело | Действительно тяжело |
---|---|---|
Писать плохой код с кодами возврата Писать плохой код на исключениях | Писать хороший код с кодами возврата | Писать хороший код на исключениях |
Писать хороший код с кодами возврата тяжело, потому что вы должны проверять результат выполнения каждой функции (проверять каждый код ошибки) и думать, что вам делать, если там произойдёт ошибка.
А писать хороший код на исключениях - очень тяжело, поскольку вы должны проверять каждую строчку кода (даже каждое выражение) и думать о том, какие исключения могут там возникнуть и как вы будете их обрабатывать.
Но это нормально. Как я уже сказал: просто потому, что что-то делается непросто, не означает, что это не нужно делать. Написать драйвер устройства тяжело - но люди делают это - и это хорошо.
Но взгляните на такую табличку:
Очень просто | Тяжело | Действительно тяжело | ||||||
---|---|---|---|---|---|---|---|---|
|
|
|
function ComputeChecksum(const AFile: String; out AResult: DWORD): Boolean; var h, hfm: THandle; pv: Pointer; dwHeaderSum: DWord; begin h := CreateFile(PChar(AFile), GENERIC_READ, FILE_SHARE_READ, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0); hfm := CreateFileMapping(h, 0, PAGE_READ, 0, 0, 0); pv := MapViewOfFile(hfm, FILE_MAP_READ, 0, 0, 0); CheckSumMappedFile(pv, GetFileSize(h, nil), dwHeaderSum, AResult); UnmapViewOfFile(pv); CloseHandle(hfm); CloseHandle(h); Result := True; end;Очевидно, что этот код плох. Нет проверок ни одного кода ошибок. Это тот тип кода, что вы можете написать в спешке, подразумевая, что вы потом вернётесь к нему и доработаете. И легко заметить, что этот код требует доработки, прежде чем он будет готов к выходу в свет.
А вот другой вариант:
function ComputeChecksum(const AFile: String; out AResult: DWORD): Boolean; var h, hfm: THandle; pv: Pointer; dwHeaderSum: DWord; begin Result := False; h := CreateFile(PChar(AFile), GENERIC_READ, FILE_SHARE_READ, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0); if h <> INVALID_HANDLE_VALUE then begin hfm := CreateFileMapping(h, 0, PAGE_READ, 0, 0, 0); if hfm then begin pv := MapViewOfFile(hfm, FILE_MAP_READ, 0, 0, 0); if pv then begin if CheckSumMappedFile(pv, GetFileSize(h, nil), dwHeaderSum, pdwResult) then fRc := True; UnmapViewOfFile(pv); end; CloseHandle(hfm); end; CloseHandle(h); end; end;Этот код всё ещё неверен, но он явно пытается выглядеть правильно. Это то, что я называю "не плохо".
Теперь пример кода на исключениях, который вы могли бы написать в спешке:
function CreateNotifyIcon: TNotifyIcon; begin Result := TNotifyIcon.Create; Result.Text := 'Бла-бла-бла'; Result.Visible := True; Result.Icon := TIcon.Create('cool.ico'); end;(это пример, взятый из реальной программы в статье про иконки области уведомлений, с небольшими изменениями для маскировки источника)
А вот как код мог бы выглядеть после исправления:
function CreateNotifyIcon: TNotifyIcon; begin Result := TNotifyIcon.Create; Result.Text := 'Бла-бла-бла'; Result.Icon := TIcon.Create('cool.ico'); Result.Visible := True; end;Тонкая разница, не так ли?
Легко заметить разницу между плохим кодом на кодах ошибок и неплохим кодом на кодах ошибок: не плохой код на кодах ошибок проверяет коды ошибок. Плохой код - нет. Ну, тяжело сказать, были ли правильно обработаны коды ошибок, но по крайней мере вы увидите разницу между плохим кодом и кодом, который не так плох (он может не быть отличным, но хотя бы неплох).
С другой стороны, невероятно трудно увидеть разницу между плохим и неплохим кодом, основанным на исключениях.
Соответственно, когда я пишу код на исключениях, у меня нет выбора написать плохой код и потом улучшить его позже. Если бы я сделал это, я не смог бы потом найти этот плохой код, поскольку он выглядит практически идентичным к неплохому коду.
Смысл не в том, что исключения плохи. Я хочу сказать, что исключения слишком сложны, чтобы я мог их использовать (и, похоже, так же сложны и для авторов той книги, хотя они пытаются учить вас программировать с исключениями!)
(Да, есть модели программирования типа RAII и транзакций, но вы редко сможете увидеть пример, который бы их использовал)
Кому как. Я всегда пишу сообразно строгой гарантии безопасности. Отнюдь не сложно.
ОтветитьУдалитьНу или мне кажется, что всегда сообразно. Наверняка, бывает, что что-то упускаю. Но это не нарочно.