Запись защитного кода
Разработчики слишком часто получают исключения типа NullReferenceException г:тому, что они не проверяют допустимость состояния фрагмента кода. Если состоя--^ге недопустимо, произойдет исключение. Фактически один из примеров, приведенных : ^:ьше в этой главе, имеет именно такую ситуацию, которую вы, вероятно, заметили. Вот код, который имеет высокую вероятность исключения.
void TestCallingExample ()
CallingExample els = null; try
els = new CallingExample () ; els .Method () ;
eaten (Exception)
Console.WriteLine("Depth is (" + els.GetDepth() + ")");
Проблема кроется в коде, выделенном полужирным шрифтом. Он подразумевает, что
мгъект els всегда будет допустимым экземпляром класса CallingExample. Мы не ожем
i: зволить себе делать такие предположения. Если исключение произойдет во время соз-
-^ния экземпляра класса CallingExample, объект els будет пуст, и блок catch предотвра-
вп создание экземпляра исключения, а следовательно, и отказ программы. Однако ис-
исъзование метода els . GetDepth () непосредственно после создания преодолеет нашу
iU^ixy, поскольку объект els пустой и создаст исключение NullReferenceException.
: лучший способ написать этот код.
void TestCallingExample () { CallingExample els = null; try I
els = new CallingExample();
els.Method (); )
catch (Exception) {
}
if (els != null)
{
Console.WriteLine("Depth is (" + els.GetDepth() + ")"); }
}
Полужирным шрифтом выделен код защиты программы, который проверяет, не яв¬ляется ли объект els нулевым; если он не пуст, то разрешается обращение к методу els . GetDepth (). Теперь код защищен от исключения. Фактически исключения вполне могут происходить, поскольку вызов метода GetDepth () также может создать исключе¬ние внутренне, но для защиты метода TestCallingExample О мы сделали все, что воз¬можно, с учетом невысокой степени риска у метода GetDepth ().
Пропуск метода TestCallingExample () — это свидетельство того, как прово¬дится обработка. Подразумевается, с точки зрения вызывающей стороны метода TestCallingExample (), что он всегда будет что-нибудь делать. Вызывающая сторона метода TestCallingExample () не имеет никакого способа узнать, что нечто пошло не так, если не будет передано исключение.
Код, оповещающий вас (или пользователя) о проблеме, может быть и благословени¬ем, и проклятием. Благословением, конечно, потому, что он сообщает вам о проблеме. Но код исключения может быть и проклятием, поскольку иногда вы знаете, что нечто может пойти не так, и это нормально, но вы не хотите, чтобы исключение пошло вверх по иерархии программы. В таких случаях исключение необходим» перехватывать, хоть это и усложняет ваш код.
Предположим, например, что вы хотите проанализировать число. Функции анали¬за .NET возвращают результат, если все в порядке, а в противном случае передается исключение. Не будет никакого возвращаемого значения или выходного параметра — только исключение. Но при анализе числа вы знаете, что нечто может пойти не так, как надо, поэтому необходимо написать обработчик исключения. Вот некий исходный код. который анализирует число и имеет обработчик исключения:
int TestGetValue(string buffer) { int retval = 0; try {
retval = int.Parse(buffer); )
catch (FormatException ex) {
Console.WriteLine("Exception (" + ex.Message + ")"); } return retval;
Код в этом примере предполагает, что если вызов метода Parse () не может преоб¬разовать строку в число (из-за неправильного символа или числа), то будет передано исключение. Исключение будет перехвачено и обработано (со свойством исключения essage для выяснения причины проблемы), а затем значение retval будет возвращено :ъ:зывающей стороне. Затем переменная retval инициализируется стандартным зна¬чением 0, которое является вполне допустимым форматом числа и может интерпрети-: ■: заться как результат успешной операции анализа.
Разработчик явно заинтересован. Перехватывая исключение, метрд TestGetValue О говорит: "Я всегда буду возвращать допустимое значение вызывающей стороне". Но ино¬гда допустимые значения не доступны. В случае анализа числа исключение таки пере¬хается. Так, обработка исключения на этом уровне совершенно неправильная вещь, по-::-:: льку мы должны были бы позволить исключению быть перехваченным вызывающей :гороной на более высоком уровне. Но здесь бывает ловушка. Мы действительно хо-гам сообщать вызывающей стороне, что анализ невозможен? Возможно, вызывающая гторона больше заинтересована в том, чтобы было возвращено допустимое значение. -то подобно высказыванию исполнительного директора вашей компании, "Ах, у нас ::-:чились скобки для степлера". Несомненно, скобки очень важны, и, возможно, работа и::мпании пойдет не так гладко, но вы действительно хотите сообщать исполнительному ггректору о таких мелких проблемах?
Разработчики Microsoft знают об этой проблеме с анализом и используют подход, ко-к рый вы можете использовать также. Как уже было сказано в главе 3, "Манипулирование трэками", существует два варианта анализа числа.
• Метод Parse () возвращает допустимое число, если буфер можно проанализиро¬вать, и передает исключение, если его нельзя проанализировать.
• Метод TryParse () возвращает значение true или false, указывающее на возмож¬ность анализа.
Вот как вы могли бы переписать метод TestGetValue () с помощью метода
tryParse ():
cool TestGetValue(string buffer, out int val)
oool retval = false;
if (int.TryParse(buffer, out val))
retval = true;
return retval;
3 измененном примере метод TestGetValue () возвращает значение true или false,
": "ы указать, будет ли анализ числа успешным или нет. Если возвращается значе-
-::е -rue, параметр val укажет на допустимое число; в противном случае параметр val
- z: лжен использоваться.
Зы могли бы заметить, что использование методов Parse () и TryParse () не очень
€.:лщно. Фактически метод TestGetValue () можно уменьшить до одной строки.
:::! TestGetValue(string buffer, out int val) return int.TryParse(buffer, out val);