Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Роббинс Д. - Отладка приложений для Microsoft .NET и Microsoft Windows - 2004

.pdf
Скачиваний:
350
Добавлен:
13.08.2013
Размер:
3.3 Mб
Скачать

422 ЧАСТЬ III Мощные средства и методы отладки приложений .NET

щихся Profiling API, у вас сформируется гораздо более глубокое понимание, чем то, что можно составить с одним лишь Profiling.DOC.

ProfilerLib

Прежде чем мы погрузимся в глубины средства профилирования ExceptionMon, хочу посвятить немного времени разговору о ProfilerLib. Как вы, наверное, дога дались из предшествующих разговоров о COM Profiling API, здесь много стерео типного кода. Поскольку я не в восторге от того, чтобы при разработке ПО снова и снова набирать одно и то же, я быстро понял, что мне нужна библиотека для выполнения черной работы, особенно с учетом большого числа методов, поддер живаемых интерфейсом ICorProfilerCallback.

Два примера средств профилирования, поставляемых с Visual Studio .NET, так же придерживаются курса повторного использования COM кода, но их способ написания кода непригляден тем, что смешивает всю инфраструктуру во вспомо гательные структуры данных. Я работал над тем, чтобы исправить это, когда в «MSDN Magazine» за декабрь 2001 года появилась колонка Мэтта Питрека (Matt Pietrek) «Under the Hood». Мэтт взял пример кода средства профилирования и устранил путаницу. Я решил, что это хорошая основа, поэтому взял код Мэтта и улучшил его еще больше, упростив написание средств профилирования.

Процесс настройки для использования ProfilerLib довольно прост. Сначала надо создать DLL проект и настроить его на подключение ProfilerLib.LIB, а в файл STDAFX.H проекта включить ProfileLib.H. В одном из ваших CPP файлов определите следую щие переменные и присвойте им значения, нужные для вашего средства профи лирования:

//Строка GUID средства профилирования wchar_t * G_szProfilerGUID

//CLSID средства профилирования

GUID G_CLSID_PROFILER

//Префикс ProgID средства профилирования wchar_t * G_szProgIDPrefix

//Имя средства профилирования

wchar_t * G_szProfilerName

// Для примера вот значения для ExceptionMon wchar_t * G_szProfilerGUID =

L"{F6F3B5B7 4EEC 48f6 82F3 A9CA97311A1D}" ;

GUID G_CLSID_PROFILER =

{0xf6f3b5b7 , 0x4eec , 0x48f6 ,

{0x82 , 0xf3 , 0xa9 , 0xca , 0x97 , 0x31 , 0x1a , 0x1d } } ;

wchar_t * G_szProgIDPrefix = L"ExceptionMonProfiler" ;

wchar_t * G_szProfilerName = L"ExceptionMon" ;

Объявив уникальные COM значения, добавьте DllMain в ваш CPP файл:

ГЛАВА 10 Мониторинг управляемых исключений

423

 

 

 

 

HINSTANCE G_hInst = NULL ;

 

 

 

extern "C" BOOL WINAPI DllMain ( HINSTANCE

hInstance

,

 

DWORD

dwReason

,

 

LPVOID

 

)

 

{

 

 

 

switch ( dwReason )

 

 

 

{

 

 

 

case DLL_PROCESS_ATTACH: DisableThreadLibraryCalls ( hInstance ) ; G_hInst = hInstance ;

break ; default :

break ;

}

return( TRUE ) ;

}

ProfilerLib содержит базовый класс CBaseProfilerCallback, который реализует методы, необходимые интерфейсу ICorProfilerCallback. В своем средстве профи лирования наследуйте классы обратного вызова от CBaseProfilerCallback и пере определяйте конкретные методы для получения нужных вам уведомлений. Так вы сможете сосредоточиться только на важных элементах, а не на остальных мето дах, которые просто путаются под ногами.

Присвоив имя наследующему классу, реализуйте функцию AllocateProfilerCallback со следующим прототипом. В этой функции создайте наследующий от CBaseProfiler Callback класс и верните его. Код в ProfilerLib.h позаботится об остальном.

ICorProfilerCallback * AllocateProfilerCallback ( ) ;

Наконец, возьмите из ProfilerLib файл EXAMPLE.DEF, скопируйте его в свой проект, переименуйте и замените в нем оператор LIBRARY для правильного выпол нения всех экспортов, необходимых в создании COM DLL.

Кроме выполнения всей рутины COM, ProfilerLib вносит в CBaseProfilerCallback дополнительные методы, которые облегчат вам жизнь. Некоторые из них я уже упоминал, но есть и другие. Встречая что либо, что, по моему мнению, может быть использовано повторно, я добавляю это в ProfilerLib, так что не забудьте прове рить файлы проектов — вы увидите, что для вас написаны и другие экономящие время подпрограммы.

Как вы могли догадаться из кода, в подкаталоге Tests каталога ProfilerLib есть программа пример DoNothing. Это простейшее средство профилирования, кото рое можно сделать, и оно демонстрирует применение ProfilerLib. Оно обрабаты вает все уведомления, но лишь подает звуковой сигнал при инициализации и выгрузке. Это мой запатентованный метод разработки «Отладка ушами». Кроме того, все другие написанные мною утилиты, которые используют Profiling API, приме няют ProfilerLib в качестве базовых классов, так что вы сможете увидеть более сложные примеры использования. ProfilerLib спасла мне огромное количество времени, и, надеюсь, сэкономит массу времени и вам.

424 ЧАСТЬ III Мощные средства и методы отладки приложений .NET

ExceptionMon

Установив и запустив ProfilerLib, я смог приступить к ExceptionMon. Взглянув на интерфейс ICorProfilerCallback, вы увидите, что к вашим услугам все виды потря сающих обратных вызовов, позволяющие точно узнать, что делается при возник новении исключения. Как будто кто то в Microsoft читал мои мысли! Могло пока заться, что реализация ExceptionMon была совершенно тривиальной. Как всегда, на самом деле оказалось не так.

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

STDMETHOD ( ExceptionThrown ) ( ObjectID thrownObjectId )

;

 

STDMETHOD

(

ExceptionUnwindFinallyEnter ) ( FunctionID functionId ) ;

STDMETHOD

(

ExceptionCatcherEnter ) ( FunctionID

functionId

,

 

 

ObjectID

objectId

 

) ;

С помощью ProfilerLib я быстро набросал первоначальный вариант Exception Mon, записывая вывод в файл журнала в том же каталоге, откуда загружен про цесс ExceptionMon. Первая маленькая проблема, с которой я столкнулся, — как лучше отладить ExceptionMon. Поскольку CLR выполняло всю работу по внесению указанного средства профилирования в адресное пространство, я хотел обеспе чить возможность начать отладку с самого начала. Раз уж мы используем пере менные окружения для запуска средства профилирования, я решил пойти дальше и добавить еще одну — EXPMONBREAK, установка которой заставляла ExceptionMon вызвать DebugBreak, чтобы я смог подключить отладчик.

Хотя средство профилирования можно отлаживать как любую неуправляемую DLL, загруженную в процесс, я предпочитаю вызов DebugBreak, так как средство профилирования будет загружено в Visual Studio .NET, поскольку здесь размеща ется CLR. Можно ограничить загрузку процессов, установив переменную окруже ния EXPMONPROC, чтобы отлаживать только один процесс. Однако для нужд разра ботки и отладки я предпочитаю запускать для тестирования несколько программ. Используя схему EXPMONBREAK, я легко могу подключить несколько отладчиков к нескольким процессам.

Раз я говорю о переменных окружения, следует упомянуть две важнейшие для мониторинга исключений — ASPNET_WP.EXE/W3WP.EXE. По умолчанию Exception Mon не сбрасывает файл вывода на диск, поэтому, чтобы увидеть отчет об исклю чениях, надо остановить ASPNET_WP.EXE/W3WP.EXE. Однако, если установить пе ременную окружения EXPMONFLUSH, все записи сбрасываются на диск немедленно.

Еще одна проблема с записью файлов в том, что ExceptionMon поместит файл журнала в каталог процесса, тогда как стандартная учетная запись ASPNET, веро ятно, не имеет разрешения на создание файлов в %SYSTEMROOT%\Microsoft.NET\ Framework\%FRAMEWORKVERSION%, где располагается ASPNET_WP.EXE. В Windows Server 2003 применяется учетная запись NETWORK SERVICE, а W3WP.EXE распо лагается в %SYSTEMROOT%\System32\inetsrv. Полный путь и имя для файла выво да для ExceptionMon можно указать в переменной окружения EXPMONFILENAME. Оче

ГЛАВА 10 Мониторинг управляемых исключений

425

 

 

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

Первоначальная версия ExceptionMon работала прекрасно, так как получала идентификаторы функции и объекта и могла просто вызывать соответствующие методы в интерфейсе ICorProfilerInfo для получения маркеров (tokens) класса и функции, чтобы найти имена в метаданных. Код из листинга 10 1 — CBaseProfiler Callback::GetClassAndMethodFromFunctionId — демонстрирует все, что нужно сделать для получения имен классов и методов по идентификатору функции.

Внутрипроцессная отладка и ExceptionMon

Доведя базовую версию до рабочего состояния, я подумал, что полезно было бы добавить обзор стека на момент инициации исключения. Тогда вы смогли бы понять, как сложилась такая ситуация, и взглянуть на условия. В документации для Profiling API я заметил, что ICorProfilerInfo::SetEventMask можно передать бито вый параметр COR_PRF_ENABLE_INPROC_DEBUGGING, чтобы включить внутрипроцессную отладку.

При внутрипроцессной отладке Profiling API передает уведомления о событи ях, но вам потребуется способ получения более подробной информации, чем та, что можно получить через интерфейс ICorProfilerInfo. Поскольку в Microsoft уже разработали прекрасный «продвинутый» отладочный API, работающий рука об руку с CLR, идея состояла в том, чтобы предоставить ограниченную версию отладоч ного API, способного выполнять такие задачи, как контроль значений перемен ных в реальном времени и просмотр стека.

Полностью отладочный API описывается в DebugRef.DOC из того же каталога, что и документы по Profiling API и API метаданных. Как и все документы в катало ге Tools Developers Guide, DebugRef.DOC пространен в описании интерфейсов, методов и значений параметров и вполне конкретен в применении. Каталог Samples содержит рабочий отладчик, составляющий примерно 98% исходного кода реаль ного CORDBG, но сам код иногда трудно прослеживать, хотя в конечном счете он раскрывает свои секреты.

Читая документацию по отладочному API, уделите особое внимание тому, ка кие методы вызываются из внутрипроцессной отладки. Если под именем метода есть зеленый текст «Not Implemented In Process», использовать его нельзя. Вы уви дите, что большинство методов, которые нельзя использовать, относится к уста новке точек прерывания и изменению значений. Поскольку главная причина проведения внутрипроцессной отладки — простой сбор информации, все важные элементы полностью доступны.

Первый этап в применении внутрипроцессной отладки с Profiling API — уста новка флага COR_PRF_ENABLE_INPROC_DEBUGGING при вызове ICorProfilerInfo::SetEventMask. Интересно, что его простая установка вызывает два побочных эффекта. Первый состоит в том, что, раз вы потребовали внутрипроцессную отладку, профилируе мое приложение будет выполняться медленнее. Это потому, что CLR не будет ис пользовать прекомпилированный (precompiled) код, скомпилированный с помо щью NGEN.EXE, заставляя этот код пройти JIT компиляцию, как в обычных усло виях. Возможно, вы не используете NGEN.EXE, но его весьма интенсивно приме няет .NET Framework, так что здесь будут потери.

426 ЧАСТЬ III Мощные средства и методы отладки приложений .NET

Если вы запускали NGEN.EXE, то могли заметить параметр командной строки /PROF, добавляющий к создаваемому коду информацию профилирования. Хоть это и может показаться хорошим решением, пока Profiling API не поддерживает его, так что использовать его нельзя. Я все таки считаю, что замедление кода окупает ся преимуществами.

Вторая проблема, с которой вам придется столкнуться, не документирована и в первый раз совершенно сбила меня с толку. Метод ICorProfilerCallback::Excep tionThrown получает идентификатор объекта, который описывает сгенерирован ный класс. В моей первой реализации, не использующей внутрипроцессную от ладку, я всегда получал идентификатор, способный передать CBaseProfilerCall back::GetClassAndMethodFromFunctionId. Простое добавление флага COR_PRF_ENAB LE_INPROC_DEBUGGING к ICorProfilerInfo::SetEventMask даже без действительного при менения API внутрипроцессной отладки меняет что то изнутри, так что в каче стве идентификатора объекта передается только 0. Хоть API внутрипроцессной отладки и содержит методы для получения информации, было весьма неприятно выяснять, что случилось с идентификатором объекта просто из за установки флага!

Чтобы задействовать отладочные интерфейсы, надо вызвать метод ICorProfiler Info::BeginInprocDebugging для запуска процесса получения соответствующего ин терфейса. Как часть этого вызова передается указатель DWORD на файл «cookie» контекста. Этот файл следует сохранить, чтобы передать его методу ICorProfiler Info::EndInProcDebugging, который надо вызвать, чтобы указать на окончание внут рипроцессной отладки. Второй шаг — получение соответствующего отладочно го интерфейса. Если вас интересует только текущий поток, вызовите метод ICorPro filerInfo::GetInprocInspectionIThisThread, чтобы получить интерфейс IUnknown, че рез который можно запросить интерфейс ICorDebugThread. Чтобы провести обще процессную отладку, вызовите ICorProfilerInfo::GetInprocInspectionInterface и за просите через возвращенный IUnknown интерфейс ICorDebug. Лично я не понимаю, почему два метода ICorProfilerInfo не могут просто возвращать соответствующие интерфейсы.

Получив отладочный интерфейс, вы готовы к обращению к отладочному API за необходимой информацией. В моем случае я хотел получить последнее исклю чение в потоке, так что все, что мне надо было сделать, это вызвать метод ICorDebug Thread::GetCurrentException, чтобы получить интерфейс ICorDebugValue, описываю щий последнее инициированное исключение. Странно, что каждый раз при вы зове метода ICorDebugThread::GetCurrentException происходил сбой, так что я и вправду начал волноваться удастся ли заставить ExceptionMon работать!

Проштудировав документацию на профилирующий и отладочный API, я обна ружил предложение, говорящее, что для выполнения любых операций со стеком во внутрипроцессной отладке надо вызвать ICorDebugThread::EnumerateChains. От ладочный API использует концепцию стековых цепочек (stack chains) чтобы свя зать информацию об управляемом и неуправляемом стеках, составляющую пол ную информацию о стеке. Я не видел, чтобы вызов ICorDebugThread::GetCurrentException был как то связан со стеком, но решил, что стоит попробовать вызвать ICorDebug Thread::EnumerateChains, прежде чем делать что то еще. Хотя это не документиро вано (по крайней мере, неявно), я выяснил, что для работы с отладочным API, надо сначала вызвать ICorDebugThread::EnumerateChains, иначе большинство методов не

ГЛАВА 10 Мониторинг управляемых исключений

427

 

 

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

Листинг 10-2. BeginInprocDebugging

HRESULT CExceptionMon ::

 

 

BeginInprocDebugging ( LPDWORD

pdwProfContext

,

ICorDebugThread **

pICorDebugThread

,

ICorDebugChainEnum ** pICorDebugChainEnum )

{

// Сообщаем Profiling API о необходимости внутрипроцессной отладки.

HRESULT hr = m_pICorProfilerInfo >

 

BeginInprocDebugging ( TRUE

,

pdwProfContext );

ASSERT ( SUCCEEDED ( hr ) ) ; if ( SUCCEEDED ( hr ) )

{

IUnknown * pIUnknown = NULL ;

// Запрашиваем у Profiling API интерфейс IUnknown, // от которого можно получить IcorDebugThread.

hr = m_pICorProfilerInfo >

GetInprocInspectionIThisThread ( &pIUnknown ) ;

ASSERT ( SUCCEEDED ( hr ) ) ;

 

if ( SUCCEEDED ( hr ) )

 

{

 

hr = pIUnknown >

 

QueryInterface ( __uuidof ( ICorDebugThread ) ,

 

(void**)pICorDebugThread

) ;

ASSERT ( SUCCEEDED ( hr ) ) ;

 

// В любом случае IUnknown мне больше не нужен.

 

pIUnknown >Release ( ) ;

 

// Я делаю это в ходе обычной работы потому, // что, если прежде всего из ICorDebugThread

// не вызвать ICorDebugThread::EnumerateChains, // многие другие методы не сработают.

if ( SUCCEEDED ( hr ) )

{

hr = (*pICorDebugThread) >

EnumerateChains ( pICorDebugChainEnum ) ;

ASSERT ( SUCCEEDED ( hr ) ) ;

if ( FAILED ( hr ) )

{

(*pICorDebugThread) >Release ( ) ;

}

}

}

}

return ( hr ) ;

}

428 ЧАСТЬ III Мощные средства и методы отладки приложений .NET

Добившись от ICorDebugThread::GetCurrentException возврата правильного зна чения, я решил что я уже у цели, поскольку оставалось лишь получить имя класса из ICorDebugValue. Увы, просматривая соответствующие интерфейсы — ICorDebug GenericValue, ICorDebugHeapValue, ICorDebugObjectValue, ICorDebugReferenceValue и Icor DebugValue, я понял, что придется еще многое сделать, так как только ICorDebugObject Value содержал метод GetClass, необходимый для получения интерфейса класса, который предоставил бы имя. Это означало, что мне придется поработать, чтобы преобразовать исходный ICorDebugValue от ICorDebugThread::GetCurrentException в ICorDebugObjectValue. Проще всего мне показать вам код, выполняющий всю рабо ту (листинг 10 3). Как видите, нужно разыменовать (dereference) объект и запро сить интерфейс ICorDebugObjectValue.

Листинг 10-3. GetClassNameFromValueInterface

HRESULT CExceptionMon ::

 

 

GetClassNameFromValueInterface ( ICorDebugValue * pICorDebugValue

,

LPTSTR

szBuffer

,

UINT

uiBuffLen

)

{

HRESULT hr = S_FALSE ;

ICorDebugObjectValue * pObjVal = NULL ;

ICorDebugReferenceValue * pRefVal = NULL ;

//Получаем ссылку на это значение. Так должны поступать исключения.

//Если получить ICorDebugReferenceValue не удалось, значит,

//это тип ICorDebugGenericValue. Я ничего не могу сделать

//с ICorDebugGenericValue, так как мне нужно имя класса.

hr = pICorDebugValue >

QueryInterface ( __uuidof ( ICorDebugReferenceValue ),

(void**)&pRefVal

);

if ( SUCCEEDED ( hr ) )

 

{

 

// Разыменовываем значение.

 

ICorDebugValue * pDeRef ;

 

hr = pRefVal >Dereference ( &pDeRef ) ;

 

if ( SUCCEEDED ( hr ) )

 

{

 

// Разыменовав, я могу запросить объектное значение.

 

hr = pDeRef >

 

QueryInterface ( __uuidof ( ICorDebugObjectValue

),

(void**)&pObjVal

);

// Разыменование мне больше не нужно.

 

pDeRef >Release ( ) ;

 

}

 

// Ссылка мне больше не нужна.

 

ГЛАВА 10 Мониторинг управляемых исключений

429

 

 

pRefVal >Release ( ) ;

}

ASSERT ( SUCCEEDED ( hr ) ) ; if ( SUCCEEDED ( hr ) )

{

// Получаем интерфейс класса для этого объекта. ICorDebugClass * pClass ;

hr = pObjVal >GetClass ( &pClass ) ;

// Объектная ссылка мне больше не нужна. pObjVal >Release ( ) ;

ASSERT ( SUCCEEDED ( hr ) ) ; if ( ( SUCCEEDED ( hr ) ) )

{

// Получаем маркер синонима типа для класса. mdTypeDef ClassDef ;

hr = pClass >GetToken ( &ClassDef ) ;

ASSERT ( SUCCEEDED ( hr ) ) ; if ( SUCCEEDED ( hr ) )

{

//Для просмотра маркера класса мне нужен модуль,

//чтобы запросить интерфейс метаданных. ICorDebugModule * pMod ;

hr = pClass >GetModule ( &pMod ) ;

ASSERT ( SUCCEEDED ( hr ) ) ; if ( SUCCEEDED ( hr ) )

{

// Получаем метаданные.

IMetaDataImport * pIMetaDataImport = NULL ;

hr = pMod >

GetMetaDataInterface ( IID_IMetaDataImport ,

(IUnknown**)&pIMetaDataImport ) ;

ASSERT ( SUCCEEDED ( hr ) ) ; if ( SUCCEEDED ( hr ) )

{

// Наконец, получаем имя класса. ULONG ulCopiedChars ;

hr = pIMetaDataImport >

 

GetTypeDefProps ( ClassDef

,

szBuffer

,

uiBuffLen

,

&ulCopiedChars ,

см. след. стр.

430

ЧАСТЬ III Мощные средства и методы отладки приложений .NET

 

 

 

 

 

 

 

 

 

 

 

 

NULL

,

 

 

 

NULL

) ;

 

 

 

ASSERT ( ulCopiedChars < uiBuffLen ) ;

 

 

 

 

if ( ulCopiedChars == uiBuffLen )

 

 

 

 

{

 

 

 

 

hr = S_FALSE ;

 

 

 

 

}

 

 

 

 

pIMetaDataImport >Release ( ) ;

 

 

 

 

}

 

 

 

 

pMod >Release ( ) ;

 

 

 

 

}

 

 

 

 

}

 

 

 

 

pClass >Release ( ) ;

 

 

 

 

}

 

 

 

 

}

 

 

 

 

return ( hr ) ;

 

 

 

}

 

 

 

 

 

 

 

 

Имя класса для исключения получено — оставалось просмотреть стек. Я уже получил интерфейс ICorDebugChainEnum, так что просмотр стека сводился к следо ванию алгоритму, описанному в файле DebugRef.DOC. Единственное, что интересно в просмотре стека: нельзя просмотреть неуправляемый стек с помощью отладоч ного API. Чтобы проверить, является ли цепочка управляемой, вызовите ICorDebug Chain::IsManaged.

Для меня ExceptionMon оказался бесценным помощником в слежении за ис ключениями, которые генерируют мои приложения. Я совершенно доволен вы водом в текстовый файл, но вам может прийти мысль добавить возможность вы вода информации через GUI, чтобы видеть исключения почти в реальном време ни. Это несложно, и это прекрасный способ изучить программирование Windows Forms!

Использование исключений в .NET

Теперь, когда ExceptionMon следит за вашими исключениями, хочу поговорить о применении исключений в .NET. То, что в .NET исключения встроены внутрь, — определенно повод для ликования. Для тех, кто перешел из C++ Win32, исключе ния C++ казались прекрасной идеей, но их реализация оставляла желать много лучшего. Поскольку .NET обладает ясной и целостной манерой обработки исклю чений библиотеки классов .NET Framework (FCL), разработка в .NET становится гораздо проще.

Я готов был написать отдельную главу по обработке исключений, но мой кол лега Джеффри Рихтер (Jeffrey Richter) уже проделал замечательную работу в сво ей книге «Applied Microsoft .NET Framework Programming» (издательство Microsoft Press, 2002 год) и «Applied Microsoft .NET Framework Programming in Microsoft Visual Basic .NET» (Microsoft Press, 2003). Его главы по обработке исключений (глава 18 в обеих книгах) следует прочесть всем, кто занимается разработкой в .NET. Но я хочу особо подчеркнуть некоторые аспекты использования и создания собствен ных исключений в программах.

ГЛАВА 10 Мониторинг управляемых исключений

431

 

 

Первое: исключения для исключительных событий. Мы все слышали это, но я обнаружил, что у многих разработчиков проблемы с определением. Мое опреде ление состоит в том, что исключение следует инициировать, только когда встре чается ошибка или непредвиденные условия. Одной из виденных мною у разра ботчиков ошибок было использование исключений вместо оператора switch…case. (Я правда это видел!) Инициируйте исключения, только когда что то не так. Не возвращайте общие коды состояния с помощью исключений.

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

Я говорил об ущербе производительности, потому что, несмотря на свободу исключений в .NET, внутри они реализуются через SEH. Если хотите это прове рить, отладьте приложение .NET, используя отладку в неуправляемом режиме (native mode–only debugging), — вы увидите те же первые случаи исключения при ини циации вашего исключения. Это подразумевает переход в режим ядра при каж дом исключении. Идея, повторяю, в том, чтобы инициировать исключения при ошибках, а не в нормальном ходе выполнения программы.

Серьезнейшая проблема с исключениями состоит в том, что трудно узнать, что перехватывать при использовании FCL. Как говорит Джеффри в главе об исклю чениях (правило, которое вы, вероятно, затвердили), перехватывайте только те исключения, что подходят используемым объектам. Каждый метод и свойство в документации FCL содержит раздел Exceptions. Когда я применяю каждое свой ство или метод, то всегда дважды сверяюсь со справкой (к счастью, справка по F1 достаточно «поумнела», чтобы открывать правильный раздел) и проверяю все инициируемые исключения, дабы убедиться, что я перехватываю лишь те, что инициируются согласно документации. Следите за перехватом исключений, что бы избежать неожиданностей.

Microsoft в C# использует те же документирующие комментарии, что и для создания справочной документации MSDN, и, как я говорил в главе 9, почти та кую же документацию можно создавать с помощью прекрасного инструмента Ndoc (его можно скачать по адресу http://ndoc.sourceforge.net). Чтобы облегчить жизнь тем, кто использует ваши объекты, заполняйте тэги <exception></exception> и ука зывайте все исключения, инициируемые в вашем коде. Кроме того, неплохо бы дважды проверить все выполняемые вами FCL вызовы и указать, какие исключе ния могут быть инициированы в этих методах, чтобы предоставить полный от

Соседние файлы в предмете Программирование на C++