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

 

 

 

 

 

135Click

 

 

 

 

 

m

 

 

 

 

 

 

w

 

 

 

 

 

 

 

o

 

 

w

 

 

 

 

 

 

 

 

w

 

 

 

 

 

 

.c

 

 

.

 

 

 

 

 

 

 

 

p

 

 

 

 

g

 

 

 

 

 

df

 

 

n

e

 

 

 

 

 

-x cha

 

 

 

 

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

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

Исключительные обстоятельства

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

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

Обработка ошибок – дело серьезное. От нее зависит стабильность вашего кода.

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

Генерировать ошибку, если возникают какие%то неприятности

Обнаруживать все сообщения об ошибках

Обрабатывать их надлежащим образом

Передавать ошибку дальше, если не можем обработать ее сами

Работать с ошибками тяжело. Возникшая ошибка часто не связана с тем, чем вы занимались в данный момент (большинство из них отно% сятся к категории «непредвиденных обстоятельств»). Кроме того, об% рабатывать их скучно – хотелось бы сосредоточиться на том, что долж# на делать программа, а не на неприятностях, с которыми она может столкнуться. Однако без надлежащей обработки ошибок программа оказывается хрупкой – построенной на песке, а не на каменном осно% вании. При первых же порывах ветра она рухнет.

Механизмы сообщения об ошибках

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

 

 

 

 

hang

e

 

 

 

 

 

 

C

 

E

 

 

 

X

 

 

 

 

 

-

 

 

 

 

 

d

 

F

 

 

 

 

 

 

t

 

D

 

 

 

 

 

 

 

i

 

 

 

 

 

 

 

 

r

P

 

 

 

 

 

NOW!

o

 

 

 

 

 

 

 

 

 

 

 

BUY

 

 

 

 

 

to

 

 

 

 

w Click

 

 

 

136m

 

 

 

 

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

 

 

 

 

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

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

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

Без обработки ошибок

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

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

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

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

Возвращаемые значения

Следующий по простоте механизм – возврат функцией значений, соот% ветствующих ее успешному/неудачному завершению. Возвращаемое булево значение дает простой ответ «да»/»нет». Более сложный под% ход состоит в нумерации всех возможных состояний и возврате соот% ветствующего кода. Одно из значений означает успех; остальные пред% ставляют различные случаи аварийного завершения. Коды заверше% ния могут быть общими для всего кода проекта, и тогда ваша функция

 

 

 

 

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

 

 

 

 

 

137Click

 

 

 

 

 

m

 

 

 

 

 

 

w

 

 

 

 

 

 

 

o

 

 

w

 

 

 

 

 

 

 

 

w

 

 

 

 

 

 

.c

 

 

.

 

 

 

 

 

 

 

 

p

 

 

 

 

g

 

 

 

 

 

df

 

 

n

e

 

 

 

 

 

-x cha

 

 

 

 

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

Такой подход хорош, если функции не возвращают данных; получе% ние же кодов ошибок наряду с данными вносит некоторую путаницу. Если функция int count() обходит связанный список и возвращает ко% личество элементов в нем, каким образом она может сообщить о повре% ждении структуры списка? Есть три способа:

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

Передать код ошибки через параметр функции. В C++ и .NET этот параметр передается по ссылке. В C доступ к переменной следует организовать с помощью указателей. Данный способ некрасив и не% интуитивен; нет синтаксических средств, чтобы отличить возвра% щаемое значение от параметра.

Можно также зарезервировать некоторый диапазон возвращаемых значений для кодов ошибок. Например, в функции подсчета все от% рицательные числа могут трактоваться как сигнал об ошибке; в ка% честве результата они все равно не имеют смысла. Отрицательные числа часто используются таким образом. Возвращаемым значени% ям типа указателя может быть присвоено специальное недопусти% мое значение, которым обычно является ноль (или NULL). В Java и C# можно возвращать нулевую ссылку на объект.

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

Переменные, содержащие состояние ошибки

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

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

1Если пользоваться типом unsigned int, то количество доступных значений

увеличивается вдвое за счет знакового разряда по сравнению с signed int.

 

 

 

 

hang

e

 

 

 

 

 

 

C

 

E

 

 

 

X

 

 

 

 

 

-

 

 

 

 

 

d

 

F

 

 

 

 

 

 

t

 

D

 

 

 

 

 

 

 

i

 

 

 

 

 

 

 

 

r

P

 

 

 

 

 

NOW!

o

 

 

 

 

 

 

 

 

 

 

 

BUY

 

 

 

 

 

to

 

 

 

 

w Click

 

 

 

138m

 

 

 

 

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

 

 

 

 

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

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

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

Исключения

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

Есть две модели функционирования, отличающиеся ходом событий после обработки исключительной ситуации:

Модель с прекращением исполнения

Выполнение продолжается далее после обработчика, перехватив% шего исключительную ситуацию. Такая модель существует в C++,

.NET и Java.

Модель с возобновлением

Выполнение возобновляется в том месте, где была сгенерирована исключительная ситуация.

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

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

 

 

 

 

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

 

 

 

 

 

139Click

 

 

 

 

 

m

 

 

 

 

 

 

w

 

 

 

 

 

 

 

o

 

 

w

 

 

 

 

 

 

 

 

w

 

 

 

 

 

 

.c

 

 

.

 

 

 

 

 

 

 

 

p

 

 

 

 

g

 

 

 

 

 

df

 

 

n

e

 

 

 

 

 

-x cha

 

 

 

 

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

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

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

Агитпоход за безопасность исключений

Прочный код должен хорошо «держать» исключительные си# туации. Он должен работать корректно (какой смысл в это вкла% дывается, обсуждается ниже), независимо от того, какие возника% ют исключительные ситуации. Это не должно зависеть от того, перехватывает ли сам код какие%либо исключительные ситуации.

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

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

ных ситуаций.

 

 

 

 

hang

e

 

 

 

 

 

 

C

 

E

 

 

 

X

 

 

 

 

 

-

 

 

 

 

 

d

 

F

 

 

 

 

 

 

t

 

D

 

 

 

 

 

 

 

i

 

 

 

 

 

 

 

 

r

P

 

 

 

 

 

NOW!

o

 

 

 

 

 

 

 

 

 

 

 

BUY

 

 

 

 

 

to

 

 

 

 

w Click

 

 

 

140m

 

 

 

 

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

 

 

 

 

Есть несколько уровней защиты от исключительных ситуаций. Они характеризуются теми гарантиями, которые даются вызы% вающему коду. Вот эти гарантии:

Базовые гарантии

Если в функции возникают исключительные ситуации (в ре% зультате выполняемых действий или вызова других функ% ций), они не приводят к утечке ресурсов. Код сохраняет рабо% тоспособность (т. е. им можно корректно пользоваться), но со% стояние его может оказаться неопределенным. Пример: функ% ция%член должна добавить в контейнер 10 элементов, но ее работа прервана исключительной ситуацией. Контейнер со% храняется в рабочем состоянии; при этом может оказаться, что в него не было добавлено ни одного объекта, либо добавле% ны все 10, либо только половина всех объектов.

Сильные гарантии

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

Гарантии невозбуждения исключительных ситуаций

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

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

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

1Так, по крайней мере, происходит в C++ и Java. В C# деструктором назван ~X(), что глупо, поскольку это лишь замаскированный фина% лизатор. Генерация исключения в деструкторе C# приводит к дру% гим последствиям.