C#

Запись защитного кода

Разработчики слишком часто получают исключения типа 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);