Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
книги хакеры / Питер_Гудлиф_Ремесло_программиста_Практика_написания_хорошего_кода.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

ошибок

 

 

 

 

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

 

 

 

 

 

147Click

 

 

 

 

 

m

 

 

 

 

 

 

w

 

 

 

 

 

 

 

o

 

 

w

 

 

 

 

 

 

 

 

w

 

 

 

 

 

 

.c

 

 

.

 

 

 

 

 

 

 

 

p

 

 

 

 

g

 

 

 

 

 

df

 

 

n

e

 

 

 

 

 

-x cha

 

 

 

 

Пересылка ошибки

Если возникает отказ в вызываемой функции, то вы, возможно, не сможете продолжить работу, но и не будете знать, как поступить. Единственный выход – сделать за собой уборку и передать сообще% ние об ошибке на более высокий уровень. Возможны варианты. Есть два способа пересылки ошибки:

Экспортировать ту информацию об ошибке, которую сами полу% чили (возвратить тот же код ошибки или передать дальше ис% ключение).

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

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

Последствия для кода

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

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

void nastyErrorHandling()

{

if (operationOne())

{

... какие то действия ...

if (operationTwo())

{

... какие то другие действия ...

if (operationThree())

{

... еще действия ...

}

}

}

}

 

 

 

 

hang

e

 

 

 

 

 

 

C

 

E

 

 

 

X

 

 

 

 

 

-

 

 

 

 

 

d

 

F

 

 

 

 

 

 

t

 

D

 

 

 

 

 

 

 

i

 

 

 

 

 

 

 

 

r

P

 

 

 

 

 

NOW!

o

 

 

 

 

 

 

 

 

 

 

 

BUY

 

 

 

 

 

to

 

 

 

 

w Click

 

 

 

148m

 

 

 

 

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

 

 

 

 

 

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

 

 

 

 

 

m

 

 

 

 

 

 

w

 

 

 

 

 

 

 

o

 

 

w

 

 

 

 

 

 

 

 

w

 

 

 

 

 

 

.c

 

 

.

 

 

 

 

 

 

 

 

p

 

 

 

 

g

 

 

 

 

 

df

 

 

n

e

 

 

 

 

 

-x cha

 

 

 

 

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

Можно ли избежать этих проблем? Да, есть несколько возможностей. В первом варианте мы избавимся от вложенности и сделаем структуру более плоской. Семантически код останется эквивалентным, но допол# нительно увеличится сложность, что связано с появлением новой пе% ременной состояния ok, управляющей потоком выполнения:

void flattenedErrorHandling()

{

bool ok = operationOne(); if (ok)

{

... какие то действия ...

ok = operationTwo();

}

if (ok)

{

... какие то другие действия ...

ok = operationThree();

}

if (ok)

{

... еще действия ...

}

if (!ok)

{

... уборка при возникновении ошибок ...

}

}

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

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

Если в каждой операции нашего примера выделяется какой%то объ%

ем памяти, в каждой точке преждевременного выхода придется осво%

 

 

 

 

hang

e

 

 

 

 

 

 

 

C

 

E

 

 

 

 

X

 

 

 

 

 

 

-

 

 

 

 

 

d

 

 

F

 

 

 

 

 

 

t

 

 

D

 

 

 

 

 

 

 

i

 

 

 

 

 

 

 

 

 

r

 

P

 

 

 

 

 

NOW!

o

 

 

 

 

 

 

 

 

 

 

 

 

 

BUY

 

 

 

 

 

 

to

 

 

 

 

 

w Click

 

 

 

Обработкаm

ошибок

 

 

 

 

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

 

 

 

 

 

149Click

 

 

 

 

 

m

 

 

 

 

 

 

w

 

 

 

 

 

 

 

o

 

 

w

 

 

 

 

 

 

 

 

w

 

 

 

 

 

 

.c

 

 

.

 

 

 

 

 

 

 

 

p

 

 

 

 

g

 

 

 

 

 

df

 

 

n

e

 

 

 

 

 

-x cha

 

 

 

 

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

Сочиняем сообщения об ошибках

Ваш код неизбежно столкнется с ошибками, исправлять кото% рые должен пользователь. Без человека тут не справиться: не бу% дет же ваш код вставлять дискету или включать принтер! (Если он это сможет, вы станете богатым человеком.)

Если вы собираетесь пожаловаться пользователю, нужно учесть несколько обстоятельств:

Пользователи мыслят иначе, чем программисты, поэтому представлять информацию нужно в том виде, который им по% нятен. Показывая оставшееся на диске пространство, вы мо% жете напечатать: на диске свободно 10K. Но если свободного мес% та нет вообще, ноль может быть неправильно интерпретирован как OK – к недоумению пользователя, который не может сохра% нить файл, когда программа сообщает ему, что все в порядке.

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

Не показывайте не имеющие смысла коды ошибок. Ни один пользователь не поймет, что ему делать, встретив «код ошибки 707E». Однако такие коды полезно указывать в качестве «до% полнительной информации» – их можно сообщить в службу технической поддержки или поискать в Интернете.

Делайте различие между серьезными ошибками и простыми предупреждениями. Пишите об этом в тексте сообщения (можно ставить префикс Error:) и помещайте соответствую% щие значки в окна сообщений.

Задавайте вопросы (даже простые «Продолжить: Yes/No?»), только если пользователю должны быть совершенно понятны последствия его выбора. Сделайте пояснения, если это необ% ходимо, чтобы было ясно, к чему приводит каждый ответ.

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

 

 

 

 

hang

e

 

 

 

 

 

 

C

 

E

 

 

 

X

 

 

 

 

 

-

 

 

 

 

 

d

 

F

 

 

 

 

 

 

t

 

D

 

 

 

 

 

 

 

i

 

 

 

 

 

 

 

 

r

P

 

 

 

 

 

NOW!

o

 

 

 

 

 

 

 

 

 

 

 

BUY

 

 

 

 

 

to

 

 

 

 

w Click

 

 

 

150m

 

 

 

 

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

 

 

 

 

 

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

 

 

 

 

 

m

 

 

 

 

 

 

w

 

 

 

 

 

 

 

o

 

 

w

 

 

 

 

 

 

 

 

w

 

 

 

 

 

 

.c

 

 

.

 

 

 

 

 

 

 

 

p

 

 

 

 

g

 

 

 

 

 

df

 

 

n

e

 

 

 

 

 

-x cha

 

 

 

 

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

Если не следовать безусловно и неукоснительно принципу наличия в функциях единственных точек входа и выхода (SESE), то подойдет следующий пример, устраняющий зависимость от отдельной перемен% ной, управляющей ходом выполнения кода.1 Однако мы снова теряем код уборки. Код так прост, что фактические намерения хорошо видны:

void shortCircuitErrorHandling()

{

if (!operationOne()) return;

... какие то действия ...

if (!operationTwo()) return;

... еще какие то действия ...

if (!operationThree()) return;

... еще действия ...

}

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

void gotoHell()

{

if (!operationOne()) goto error;

... какие то действия ...

if (!operationTwo()) goto error;

... еще какие то действия ...

if (!operationThree()) goto error;

... еще действия ...

return;

error:

... уборка после ошибок ...

}

В C++ можно избежать этого уродливого кода с помощью технологии

Resource Acquisition Is Initialization (RAII) типа умных указателей (Stroustrup 97). Дополнительное достоинство – безопасная обработка

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

продумывать последствия.