четверг, 11 февраля 2010 г.

Чище, элегантнее и тяжелее опознать

Это перевод Cleaner, more elegant, and harder to recognize. Автор: Реймонд Чен.

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

1 комментарий:

  1. Кому как. Я всегда пишу сообразно строгой гарантии безопасности. Отнюдь не сложно.
    Ну или мне кажется, что всегда сообразно. Наверняка, бывает, что что-то упускаю. Но это не нарочно.

    ОтветитьУдалить

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

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

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

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

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