Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
книги хакеры / Питер_Гудлиф_Ремесло_программиста_Практика_написания_хорошего_кода.pdf
Скачиваний:
16
Добавлен:
19.04.2024
Размер:
9.23 Mб
Скачать

 

 

 

 

hang

e

 

 

 

 

 

 

 

C

 

E

 

 

 

 

X

 

 

 

 

 

 

-

 

 

 

 

 

d

 

 

F

 

 

 

 

 

 

t

 

 

D

 

 

 

 

 

 

 

i

 

 

 

 

 

 

 

 

 

r

 

P

 

 

 

 

 

NOW!

o

 

 

 

 

 

 

 

 

 

 

 

 

 

BUY

 

 

 

 

 

 

to

 

 

 

 

 

w Click

 

 

 

Главаm

6. Людям свойственно ошибаться

 

 

 

 

w

 

 

 

 

 

 

 

 

 

 

w

 

 

 

 

 

 

 

o

 

 

.

 

 

 

 

 

.c

 

 

 

p

 

 

 

 

g

 

 

 

 

 

df

 

 

n

e

 

 

 

 

 

-xcha

 

 

 

 

 

 

 

 

hang

e

 

 

 

 

 

 

 

C

 

E

 

 

 

 

X

 

 

 

 

 

 

-

 

 

 

 

 

d

 

 

F

 

 

 

 

 

 

t

 

 

D

 

 

 

 

 

 

 

i

 

 

 

 

 

 

 

 

 

r

P

 

 

 

 

 

NOW!

o

 

 

 

 

 

 

 

 

 

 

 

 

BUY

 

 

 

 

 

 

to

 

 

 

 

 

607Click

 

 

 

 

 

m

 

 

 

 

 

 

w

 

 

 

 

 

 

 

o

 

 

w

 

 

 

 

 

 

 

 

w

 

 

 

 

 

 

.c

 

 

.

 

 

 

 

 

 

 

 

p

 

 

 

 

g

 

 

 

 

 

df

 

 

n

e

 

 

 

 

 

-x cha

 

 

 

 

Слабости вашего стиля комментариев обнаружатся во время рецензи% рования.

3.Придерживаются ли ваши коллеги одинакового стандарта написания комментариев, возможно, с небольшими различиями?

a.Чьи комментарии лучше других? Почему? Чьи самые плохие? Есть ли корреляция между качеством их комментариев и качеством ко да в целом?

b.Не думаете ли вы, что если потребовать от членов вашей команды придерживаться определенного стандарта кодирования, то качест во комментариев может вырасти?

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

4.Включаете ли вы в исходные файлы историю их модификации? Если да:

a.Поддерживаете ли вы ее вручную? Зачем, если система управления версиями может автоматически делать это вместо вас? Насколько точно ведется история?

b.Разумна ли такая практика в действительности? Как часто воз никает потребность в таких данных? Каковы преимущества хране ния их в исходном файле по сравнению с иным механизмом?

Людям свойственно неаккуратно вести журналы, даже если у них са% мые лучшие намерения. Это требует большого объема ручной работы, которой пренебрегают, когда времени мало. Следует вести журнал с по% мощью специальных средств и правильно выбирать место для инфор% мации (им, по моему мнению, не должен быть файл исходного текста).

5.Добавляете ли вы свои инициалы или помечаете каким то иным спо собом свои комментарии, которые делаете в чужом коде? Указываете ли вы дату комментирования? Когда и зачем вы это делаете – полезна ли такая практика? Сослужили ли вам когда либо пользу чьи то ини циалы или отметка о дате?

Для некоторых комментариев такая практика полезна. В других слу% чаях это неудобно – приходится пробираться через эти помехи, чтобы добраться до существенного материала.

Лучше всего пользоваться временными комментариями FIXME (испра% вить) или TODO (что сделать), которыми помечается незаконченная ра% бота. В окончательной версии сохранять их, пожалуй, не стоит.

Глава 6. Людям свойственно ошибаться

Вопросы для размышления

1.Эквивалентны ли такие механизмы сообщения об ошибках, как воз вращаемые значения и исключительные ситуации?

 

 

 

 

hang

e

 

 

 

 

 

 

C

 

E

 

 

 

X

 

 

 

 

 

-

 

 

 

 

 

d

 

F

 

 

 

 

 

 

t

 

D

 

 

 

 

 

 

 

i

 

 

 

 

 

 

 

 

r

P

 

 

 

 

 

NOW!

o

 

 

 

 

 

 

 

 

 

 

 

BUY

 

 

 

 

 

to

 

 

 

 

w Click

 

 

 

608m

 

 

 

 

w

 

 

 

 

 

 

 

 

 

w

 

 

 

 

 

 

 

o

 

.

 

 

 

 

 

.c

 

 

p

 

 

 

 

g

 

 

 

 

df

 

 

n

e

 

 

 

 

-xcha

 

 

 

 

 

 

 

hang

e

 

 

 

 

 

 

 

C

 

E

 

 

 

 

X

 

 

 

 

 

 

-

 

 

 

 

 

d

 

 

F

 

 

 

 

 

 

t

 

 

D

 

 

 

 

 

 

 

i

 

 

 

 

 

 

 

 

 

r

P

 

 

 

 

 

NOW!

o

 

 

 

 

 

 

 

 

 

 

 

 

BUY

 

 

 

 

 

 

to

 

 

 

 

 

Ответы и обсуждениеClick

 

 

 

 

 

m

 

 

 

 

 

 

w

 

 

 

 

 

 

 

o

 

 

w

 

 

 

 

 

 

 

 

w

 

 

 

 

 

 

.c

 

 

.

 

 

 

 

 

 

 

 

p

 

 

 

 

g

 

 

 

 

 

df

 

 

n

e

 

 

 

 

 

-x cha

 

 

 

 

Возвращаемые значения эквивалентны глобальным переменным со# стояния, поскольку оба механизма могут передавать один и тот же код (хотя на переменную состояния проще не обращать внимания). С помо% щью обоих методов можно написать код, действующий аналогичным образом.1

Исключения – совсем другое дело. В них участвуют новые потоки управления, что сильно отличается от простых кодов причины. Они тесно связаны с исполнительной средой языка и программы. Хотя можно моделировать исключения с помощью собственного кода, рас% пространяющего ошибки, нужно тщательно определить:

Представление ошибок в виде произвольных объектов, а не про% стых целочисленных кодов.

Иерархию классов исключений и возможность перехвата через ба% зовый класс.

Распространение исключений любыми функциями, даже теми, где отсутствуют операторы try, catch или throw.

Именно последний пункт лучше всего демонстрирует, почему эти два метода не эквивалентны. Будучи реализованы на уровне языка, ис% ключения нисколько не навязываются для использования в коде. Са% модельная имитация должна уметь обрабатывать отказ в каждой точ% ке. Каждая функция обязана возвращать код ошибки – даже если сама не может быть причиной сбоя, – чтобы распространять информацию об ошибке дальше. Это требует существенного адаптирования кода.

2.Можете предложить какие нибудь реализации возвращаемых типов кортежей? Не ограничивайтесь каким либо одним языком програм мирования. В чем достоинства и недостатки возвращаемых значений типа кортежа?

В C для каждого возвращаемого типа можно создать struct, связав с кодом причины отказа. Это будет выглядеть примерно так:

/* Объявление возвращаемого типа */ struct return_float

{

int reason_code; float value;

};

/* Функция, использующая его ... */ return_float myFunction() { ... }

Это некрасиво, скучно писать, трудно применять и тяжело читать. Можно воспользоваться шаблонами C++ или генериками Java/C#,

1Тем не менее они не совсем совпадают. В C++ можно возвратить значение типа proxy, для которого в деструкторе определено поведение. Тем самым

механизм кода возврата обретает дополнительные возможности.

 

 

 

 

hang

e

 

 

 

 

 

 

 

C

 

E

 

 

 

 

X

 

 

 

 

 

 

-

 

 

 

 

 

d

 

 

F

 

 

 

 

 

 

t

 

 

D

 

 

 

 

 

 

 

i

 

 

 

 

 

 

 

 

 

r

 

P

 

 

 

 

 

NOW!

o

 

 

 

 

 

 

 

 

 

 

 

 

 

BUY

 

 

 

 

 

 

to

 

 

 

 

 

w Click

 

 

 

Главаm

6. Людям свойственно ошибаться

 

 

 

 

w

 

 

 

 

 

 

 

 

 

 

w

 

 

 

 

 

 

 

o

 

 

.

 

 

 

 

 

.c

 

 

 

p

 

 

 

 

g

 

 

 

 

 

df

 

 

n

e

 

 

 

 

 

-xcha

 

 

 

 

 

 

 

 

hang

e

 

 

 

 

 

 

 

C

 

E

 

 

 

 

X

 

 

 

 

 

 

-

 

 

 

 

 

d

 

 

F

 

 

 

 

 

 

t

 

 

D

 

 

 

 

 

 

 

i

 

 

 

 

 

 

 

 

 

r

P

 

 

 

 

 

NOW!

o

 

 

 

 

 

 

 

 

 

 

 

 

BUY

 

 

 

 

 

 

to

 

 

 

 

 

609Click

 

 

 

 

 

m

 

 

 

 

 

 

w

 

 

 

 

 

 

 

o

 

 

w

 

 

 

 

 

 

 

 

w

 

 

 

 

 

 

.c

 

 

.

 

 

 

 

 

 

 

 

p

 

 

 

 

g

 

 

 

 

 

df

 

 

n

e

 

 

 

 

 

-x cha

 

 

 

 

чтобы автоматически строить данную конструкцию, либо привлечь класс C++ std::pair. В промышленном коде C++ можно встретить оба подхода. Тот и другой – нудные: требуются дополнительные объявле% ния и механизмы, возвращающие эти типы. В некоторых языках, на% пример в Perl, поддерживаются списки произвольных типов – такой способ реализации значительно проще. В функциональных языках то% же есть такая возможность.

Мы уже увидели определенные недостатки такой технологии: она очень докучлива в коде и не доставляет никакого удовольствия при чтении. Кроме того, это не идиоматическая практика кодирования. Возможен выигрыш в производительности, если возвращается не один аргумент, а несколько, но это не очень убедительно, если только вы не работаете на уровне машинного кода. Заметное преимущество состоит в том, что отдельный код успешности операции не смешивается с воз% вращаемыми значениями.

3.В чем различия между реализациями механизма исключительных си туаций в разных языках?

Мы рассмотрим четыре основные реализации: C++, Java, .NET и струк% турированные исключения Win32. Исключения Win32 связаны с функ% ционирующей платформой, остальные – со своими языками. Языки могут быть реализованы с использованием соответствующих средств платформы, на которой происходит работа, или без них.

Во всех случаях применяется идентичный подход: исключение гене% рируется оператором throw и перехватывается потом оператором catch, помещаемым после кода, заключенного в блок try. Все они следуют модели завершения исполнения программы.

В Java, .NET и Win32 есть также конструкция finally. Она содержит код, выполняемый как при нормальном, так и при аварийном выходе из блока try. В это место можно, например, поместить код для уборки и знать, что он всегда выполнится. В C++ можно моделировать finally, но это малоприятное занятие.

Чистые (raw) исключения Win32 (за вычетом поддержки в языке, пре% доставляемой компилятором) не производят уборку при развертыва% нии стека, потому что в ОС нет понятия деструктора. Их нужно приме% нять с осторожностью – они предназначены для обработки ситуаций, более близких к сигналам, чем к ошибкам в логике кода.

Исключения Java (потомки Throwable) и исключения C# (потомки Ex ception) автоматически выполняют обратную трассировку, очень по% лезную при дальнейшей отладке. В .NET CLI позволяет генерировать что угодно, но C# не предоставляет такой возможности (хотя предо% ставляет возможность перехвата). Другие языки .NET могут генериро% вать что угодно.

 

 

 

 

hang

e

 

 

 

 

 

 

C

 

E

 

 

 

X

 

 

 

 

 

-

 

 

 

 

 

d

 

F

 

 

 

 

 

 

t

 

D

 

 

 

 

 

 

 

i

 

 

 

 

 

 

 

 

r

P

 

 

 

 

 

NOW!

o

 

 

 

 

 

 

 

 

 

 

 

BUY

 

 

 

 

 

to

 

 

 

 

w Click

 

 

 

610m

 

 

 

 

w

 

 

 

 

 

 

 

 

 

w

 

 

 

 

 

 

 

o

 

.

 

 

 

 

 

.c

 

 

p

 

 

 

 

g

 

 

 

 

df

 

 

n

e

 

 

 

 

-xcha

 

 

 

 

 

 

 

hang

e

 

 

 

 

 

 

 

C

 

E

 

 

 

 

X

 

 

 

 

 

 

-

 

 

 

 

 

d

 

 

F

 

 

 

 

 

 

t

 

 

D

 

 

 

 

 

 

 

i

 

 

 

 

 

 

 

 

 

r

P

 

 

 

 

 

NOW!

o

 

 

 

 

 

 

 

 

 

 

 

 

BUY

 

 

 

 

 

 

to

 

 

 

 

 

Ответы и обсуждениеClick

 

 

 

 

 

m

 

 

 

 

 

 

w

 

 

 

 

 

 

 

o

 

 

w

 

 

 

 

 

 

 

 

w

 

 

 

 

 

 

.c

 

 

.

 

 

 

 

 

 

 

 

p

 

 

 

 

g

 

 

 

 

 

df

 

 

n

e

 

 

 

 

 

-x cha

 

 

 

 

4.Сигналы – это классический механизм UNIX. Нужны ли они по преж нему, когда мы располагаем современными технологиями типа ис ключительных ситуаций?

Да, они по%прежнему нужны. Сигналы являются частью стандарта ISO C, так что обойтись без них в любом случае не просто. Сигналы ве% дут свое происхождение от реализаций UNIX (до) System%V. Это асин% хронный механизм для сообщения о проблемах/событиях системного уровня. Исключения решают другую проблему, сообщая об ошибках логики кода, которые могут достичь обработчика. Нет смысла генери% ровать исключения для событий типа сигналов, особенно в модели с за% вершением программы – она не обеспечивает асинхронной обработки.

5. Какая структура кода лучше всего подходит для обработки ошибок?

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

6.Как бы вы поступили с ошибками, возникшими в вашем коде обработ ки ошибок?

С ошибками, сообщения о которых поступают из обработчиков собы% тий, следует поступать так же, как с любыми другими ошибками. Од% нако все быстро становится отвратительным, когда обработчики оши% бок вложены в обработчики ошибок, вложенные в обработчики оши% бок. Следите, чтобы этого не происходило, и поищите более толковый способ организации своего кода.

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

Вопросы личного характера

1.Насколько полно реализована обработка ошибок в вашем нынешнем программном проекте? Как это отражается на стабильности про граммы?

Есть прямая связь между надежной обработкой ошибок и стабильно% стью кода. Либо от вашей программы не требуется надежность, либо она должна систематически обнаруживать и обрабатывать все аварий% ные состояния. Если это не является основополагающим принципом философии программы, вы не получите надежной системы.

2.Занимаетесь ли вы обработкой ошибок во время написания кода или она является неприятным отвлечением, которое вы откладываете на более позднее время?

Нелюбовь к обработке ошибок естественна; кому может понравиться постоянно думать о негативных аспектах функционирования про%

 

 

 

 

hang

e

 

 

 

 

 

 

 

C

 

E

 

 

 

 

X

 

 

 

 

 

 

-

 

 

 

 

 

d

 

 

F

 

 

 

 

 

 

t

 

 

D

 

 

 

 

 

 

 

i

 

 

 

 

 

 

 

 

 

r

 

P

 

 

 

 

 

NOW!

o

 

 

 

 

 

 

 

 

 

 

 

 

 

BUY

 

 

 

 

 

 

to

 

 

 

 

 

w Click

 

 

 

Главаm

6. Людям свойственно ошибаться

 

 

 

 

w

 

 

 

 

 

 

 

 

 

 

w

 

 

 

 

 

 

 

o

 

 

.

 

 

 

 

 

.c

 

 

 

p

 

 

 

 

g

 

 

 

 

 

df

 

 

n

e

 

 

 

 

 

-xcha

 

 

 

 

 

 

 

 

hang

e

 

 

 

 

 

 

 

C

 

E

 

 

 

 

X

 

 

 

 

 

 

-

 

 

 

 

 

d

 

 

F

 

 

 

 

 

 

t

 

 

D

 

 

 

 

 

 

 

i

 

 

 

 

 

 

 

 

 

r

P

 

 

 

 

 

NOW!

o

 

 

 

 

 

 

 

 

 

 

 

 

BUY

 

 

 

 

 

 

to

 

 

 

 

 

611Click

 

 

 

 

 

m

 

 

 

 

 

 

w

 

 

 

 

 

 

 

o

 

 

w

 

 

 

 

 

 

 

 

w

 

 

 

 

 

 

.c

 

 

.

 

 

 

 

 

 

 

 

p

 

 

 

 

g

 

 

 

 

 

df

 

 

n

e

 

 

 

 

 

-x cha

 

 

 

 

граммы?1 Однако прислушайтесь к следующему важному совету: не откладывайте это дело в долгий ящик. В противном случае вы обяза% тельно пропустите важные ошибки, которые в один прекрасный день приведут к неожиданному поведению программы. Выработайте у себя привычку думать об ошибках сразу.

3.Возьмите последнюю написанную вами функцию (достаточного раз мера) и тщательно проанализируйте ее код. Найдите все необычные ситуации и условия возможного возникновения сбоев. Все ли они учте ны в коде обработки ошибок?

Теперь предложите проанализировать свой код кому то постороннему. Не стесняйтесь! Нашлись ли другие опасные ситуации? Почему? Как это характеризует код, над которым вы работаете?

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

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

4.Как вам проще справляться со сбойными ситуациями – с помощью

возвращаемых значений или исключений? Вы уверены, что умеете писать код, корректно обрабатывающий исключительные ситуации?

В некоторой степени это зависит от ваших привычек. Исключения до% полняют и расширяют возврат значений. Тот, кто пользуется исклю% чениями, разбирается и в возвращаемых значениях, но обратное верно не всегда. Возвращаемые значения более очевидны, и потому их легче правильно применять.

Если же вы применяете исключения, нужно знать, чего опасаться. На% дежная обработка оказывает влияние на весь ваш код, а не только на те части, где генерируются и перехватываются ошибки. Надежная об% работка исключений представляет собой большую и сложную тему,

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

2К примеру, часто ли в C проверяют ошибки, возвращаемые printf?