Продуктивность вместо надежности
Я начал заниматься программированием в 1975 году. Написав изрядное количество
программ на языке BASIC, я заинтересовался аппаратным обеспечением и перешел
на язык ассемблера. Еще через некоторое время я переключился на язык C, дающий
доступ к аппаратному обеспечению на более высоком уровне абстракции и облег-
чающий программирование. Я писал код для операционных систем, для платформ,
для библиотек. И всегда старался сделать свой код по возможности компактным
и быстрым, потому что для хорошей работы приложения качественным должно
быть не только само приложение, но и используемые им операционная система
и библиотеки.
Также внимательно я относился к восстановлению после ошибок. Выделяя память
(при помощи оператора
new
в C++ или методов
malloc
,
HeapAlloc
,
VirtualAlloc
и т. п.), я всегда проверял возвращаемое значение, чтобы убедиться, что памяти
действительно хватает. При неудовлетворительном результате запроса я програм-
мировал обходной путь, гарантируя, что состояние остальной части программы
516
Глава.20 .Исключения.и.управление.состоянием
останется незатронутым и что вызывающая сторона получит сообщение об ошибке
и сможет принять меры по ее исправлению.
По какой-то необъяснимой причине при написании кода .NET Framework
многие программисты не практикуют подобный подход. Вероятность столкнуться
с нехваткой памяти существует всегда, но я практически никогда не вижу блок
catch
с кодом восстановления после исключения
OutOfMemoryException
. Более
того, мне встречались разработчики, утверждавшие, что CLR не позволяет про-
граммам перехватывать это исключение. Разумеется, это неправда. На самом деле
при выполнении управляемого кода возможны различные ошибки, но я еще не
сталкивался с разработчиками, которые писали бы код для восстановления после
потенциальных сбоев. В этом разделе мы поговорим как раз о таких сбоях. А также
о том, почему считается допустимым игнорировать такие ситуации. Кроме того,
я опишу несколько проблем, которые могут возникнуть, если не обращать внимания
на эти сбои, и предложу пути решения.
Объектно-ориентированное программирование позволяет добиться от раз-
работчиков высокой продуктивности. Изрядная заслуга тут принадлежит компо-
зиционным удобствам, облегчающим написание, чтение и редактирование кода.
Например, рассмотрим строку:
Boolean f = "Jeff".Substring(1, 1).ToUpper().EndsWith("E");
Включая в программу такую инструкцию, разработчик делает важное допущение
о том, что ее выполнение пройдет без ошибок. Однако ошибки вполне возможны, и
нам нужен способ борьбы с ними. Для этого и существуют конструкторы и механиз-
мы обработки исключений, являющиеся альтернативой методам из Win32 и COM,
возвращающим значение
true
или
false
в зависимости от результата своей работы.
Продуктивность разработки достигается не только благодаря композицион-
ности кода, но и благодаря некоторым возможностям компиляторов. В частности,
компилятор способен неявно:
вставлять в вызываемый метод необязательнее параметры;
упаковывать экземпляры значимого типа;
создавать и инициализировать массивы параметров;
связываться с членами динамических переменных и выражений;
связываться с методами расширения;
связываться с перегруженными операторами и вызывать их;
создавать делегаты;
автоматически определять тип при вызове обобщенных методов, объявлении
локальных переменных и использовании лямбда-выражений;
определять и создавать классы замыканий (closure) для лямбда-выражений
и итераторов;
определять, создавать и инициализировать анонимные типы и их экземпляры;
517
Продуктивность.вместо.надежности
писать код поддержки LINQ (Language Integrated Queries).
Да и CLR делает многое для облегчения жизни программистов. К примеру, CLR
умеет неявно:
вызывать виртуальные и интерфейсные методы;
загружать сборки и JIT-компилируемые методы, которые могут стать ис-
точником исключений
FileLoadException
,
BadImageFormatException
,
Invalid ProgramException
,
FieldAccessException
,
MethodAccessException
,
MissingFieldException
,
MissingMethodException
и
VerificationException
;
пересекать границы домена приложений для доступа к объектам типа, произ-
водного от
MarshalByRefObject
, которые могут стать источником исключения
AppDomainUnloadedException
;
сериализовать и десериализовать объекты при пересечении границ домена
приложений;
заставлять поток генерировать исключение
ThreadAbortException
при вызове
методов
Thread.Abort
и
AppDomain.Unload
;
вызывать методы
Finalize
, чтобы сборщик мусора до освобождения памяти
объекта выполнил завершающие операции;
создавать типы в куче загрузчика при работе с обобщенными типами;
вызывать статический конструктор типа, который может стать источником ис-
ключения
TypeInitializationException
;
генерировать различные исключения, в том числе
OutOfMemoryException
,
DivideByZeroException
,
NullReferenceException
,
RuntimeWrappedException
,
TargetInvocationException
,
OverflowException
,
NotFiniteNumberException
,
ArrayTypeMismatchException
,
DataMisalignedException
,
IndexOutOfRange-
Exception
,
InvalidCastException
,
RankException
,
SecurityException
и многие
другие.
И, разумеется, .NET Framework поставляется с обширной библиотекой клас-
сов, содержащих десятки тысяч типов, каждый из которых поддерживает общую,
многократно используемую функциональность. Многие из этих типов предна-
значены для создания веб-приложений, веб-служб, приложений с расширенным
пользовательским интерфейсом, приложений для работы с системой безопасности,
для управления изображениями, для распознавания речи — этот список можно
продолжать бесконечно. И любая часть кода этих приложений может стать источ-
ником ошибки. В следующих версиях могут появиться новые типы исключений,
наследующие от уже существующих. И ваши блоки
catch
начнут перехватывать
исключения, о которых раньше и не подозревали.
Все это вместе — объектно-ориентированное программирование, средства ком-
пилятора, функциональность CLR и грандиозная библиотека классов — делают
518
Do'stlaringiz bilan baham: |