Похоже, что некоторые люди проинтерпретировали заголовок одного моего поста "Чище, элегантнее и неверно", как говорящего об исключениях вообще (см. библиографическую ссылку [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 и транзакций, но вы редко сможете увидеть пример, который бы их использовал)
Кому как. Я всегда пишу сообразно строгой гарантии безопасности. Отнюдь не сложно.
ОтветитьУдалитьНу или мне кажется, что всегда сообразно. Наверняка, бывает, что что-то упускаю. Но это не нарочно.