Подтвердить что ты не робот

Как защищать меня?

i работал с небольшой подпрограммой, которая используется для создания подключения к базе данных:

Перед

public DbConnection GetConnection(String connectionName)
{
   ConnectionStringSettings cs= ConfigurationManager.ConnectionStrings[connectionName];
   DbProviderFactory factory = DbProviderFactories.GetFactory(cs.ProviderName);
   DbConnection conn = factory.CreateConnection();
   conn.ConnectionString = cs.ConnectionString;
   conn.Open();

   return conn;
}

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

Например:

ConfigurationManager.ConnectionStrings...

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


Следующая часть - это фактическое индексирование ConnectionStrings, чтобы найти connectionName:

...ConnectionStrings[connectionName];

В этом случае ConnectionStrings documentation говорит, что свойство вернет null, если имя соединения не найдено, я могу проверить это и выбросить исключение, чтобы кто-то высоко сказал, что они дали недопустимое имя соединения:

ConnectionStringSettings cs= 
      ConfigurationManager.ConnectionStrings[connectionName];
if (cs == null)
   throw new ArgumentException("Could not find connection string \""+connectionName+"\"");

Я повторяю одно и то же упражнение:

DbProviderFactory factory = 
      DbProviderFactories.GetFactory(cs.ProviderName);

В методе GetFactory отсутствует документация о том, что произойдет, если не удалось найти factory для указанного ProviderName. Он не документировал возврат null, но я все еще могу защищаться и проверять на null:

DbProviderFactory factory = 
      DbProviderFactories.GetFactory(cs.ProviderName);
if (factory == null) 
   throw new Exception("Could not obtain factory for provider \""+cs.ProviderName+"\"");

Далее - построение объекта DbConnection:

DbConnection conn = factory.CreateConnection()

Опять документация не говорит, что произойдет, если не сможет создать соединение, но снова я могу проверить нулевой объект возврата

DbConnection conn = factory.CreateConnection()
if (conn == null) 
   throw new Exception.Create("Connection factory did not return a connection object");

Далее устанавливается свойство объекта Connection:

conn.ConnectionString = cs.ConnectionString;

Документы не говорят, что произойдет, если он не может установить строку подключения. Вызывает ли это исключение? Он игнорирует это? Как и в случае с большинством исключений, если при попытке установить ConnectionString для соединения произошла ошибка, я ничего не могу сделать для ее восстановления. Поэтому я ничего не буду делать.


И, наконец, открытие соединения с базой данных:

conn.Open();

Открытый метод DbConnection является абстрактным, поэтому он зависит от того, какой провайдер спускается с DbConnection, чтобы решить, какие исключения они выбрасывают. Кроме того, в документации по открытым методам Open нет руководства по поводу того, что я могу ожидать, если есть ошибка. Если произошла ошибка подключения, я знаю, что я не могу справиться с этим - я должен позволить этому пузыриться там, где вызывающий может показать некоторый пользовательский интерфейс пользователю, и позволить им попробовать еще раз.


После

public DbConnection GetConnection(String connectionName)
{
   //Get the connection string info from web.config
   ConnectionStringSettings cs= ConfigurationManager.ConnectionStrings[connectionName];

   //documented to return null if it couldn't be found
    if (cs == null)
       throw new ArgumentException("Could not find connection string \""+connectionName+"\"");

   //Get the factory for the given provider (e.g. "System.Data.SqlClient")
   DbProviderFactory factory = DbProviderFactories.GetFactory(cs.ProviderName);

   //Undefined behaviour if GetFactory couldn't find a provider.
   //Defensive test for null factory anyway
   if (factory == null)
      throw new Exception("Could not obtain factory for provider \""+cs.ProviderName+"\"");

   //Have the factory give us the right connection object
   DbConnection conn = factory.CreateConnection();

   //Undefined behaviour if CreateConnection failed
   //Defensive test for null connection anyway
   if (conn == null)
      throw new Exception("Could not obtain connection from factory");

   //Knowing the connection string, open the connection
   conn.ConnectionString = cs.ConnectionString;
   conn.Open()

   return conn;
}

Резюме

Итак, моя четырехлинейная функция, стала 12 строк и потребовала 5 минут поиска документации. В конце концов я поймал один случай, когда методу разрешено возвращать null. Но на практике все, что я сделал, это преобразование исключения нарушения доступа (если я пытаюсь вызвать методы на нулевой ссылке) в InvalidArgumentException.

Я также поймаю два возможных случая, когда могут быть null объекты возврата; но опять я только торгую одно исключение для другого.

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

Но стоит ли это? Это избыток? Это защитное программирование пошло не так?

4b9b3361

Ответ 1

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

Изменить: этот ответ кажется несколько спорным, поэтому немного уточнить: Оборонительное программирование означает "подготовиться к неожиданностям" (или "быть параноидальным" ), и один из способов сделать это - сделать много предварительных проверок, Во многих случаях это хорошая практика, однако, как и во всех практиках, стоимость должна быть сопоставлена ​​с преимуществами.

Например, он не дает никаких преимуществ, чтобы исключить исключение "Не удалось получить соединение из factory", поскольку в нем ничего не говорится о том, почему провайдер не может быть получен - и следующая строка будет бросать исключение в любом случае, если поставщик имеет значение NULL. Таким образом, стоимость проверки предварительного условия (во время разработки и сложности кода) не оправдана.

С другой стороны, проверка, подтверждающая наличие конфигурации строки подключения, может быть оправдана, поскольку это исключение может помочь разработчику решить проблему. Исключение null, которое вы получите в следующей строке, в любом случае не указывает имя отсутствующей строки подключения, поэтому ваша проверка на предварительное условие действительно дает некоторое значение. Если ваш код является частью компонента, например, значение довольно велико, так как пользователь компонента может не знать, какие конфигурации требуется компоненту.

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

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

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

Ответ 2

Ну, это зависит от вашей аудитории.

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

(Тем не менее, если вы это делаете, я предлагаю вам определить лучшие исключения, чем просто System.Exception, чтобы облегчить людям, которые хотят поймать некоторые из ваших исключений, но не другие).

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

Ответ 3

Жаль, что я не смог заставить мою команду написать такой код. Большинство людей даже не находят оборонительного программирования. Лучшее, что они делают, это обернуть весь метод в инструкции try catch и разрешить все исключения с помощью общего блока исключений!

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

Помните, что когда вы используете .net framework api, что вы ожидаете от него? Что кажется естественным? Сделайте то же самое с вашим кодом.

Я знаю, что это требует времени. Но тогда качество стоит дорого.

PS: Вам действительно не нужно обрабатывать все ошибки и бросать настраиваемое исключение. Помните, что ваш метод будет использоваться только другими разработчиками. Они должны иметь возможность самостоятельно определять общие рамки. Это не стоит проблем.

Ответ 4

В вашем примере "before" есть четкое и четкое различие.

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

Однако есть моменты, когда исключение выбрасывается глубоко внутри рамки, которая действительно не проливает свет на то, что представляет собой настоящая проблема. Если ваша проблема заключается в том, что у вас нет допустимой строки подключения, но в фреймворке генерируется исключение, например "недопустимое использование null", то иногда лучше поймать исключение и переустановить его с более значимым сообщением.

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

Ответ 5

Я не думаю, что я бы написал любую логику проверки нулевой ссылки - по крайней мере, не так, как вы это сделали.

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

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

Во-вторых, проверка правильности конфигурации не должна быть проблемой для объектов, которые полагаются на конфигурацию. Нет смысла вводить проверки внутри повсюду в вашем коде, если вы уже выполнили эти проверки спереди. (Есть исключения из этого, конечно же, например, долгосрочные приложения, в которых вам нужно иметь возможность изменять конфигурацию во время работы программы и отражать эти изменения в поведении программы, в таких программах вам нужно будет перейдите к ConfigurationManager, когда вам нужна настройка.)

Я бы не выполнял проверку нулевой ссылки на вызовы GetFactory и CreateConnection. Как бы вы могли написать тестовый пример для реализации этого кода? Вы не можете, потому что вы не знаете, как заставить эти методы возвращать null - вы даже не знаете, что можно заставить эти методы возвращать null. Таким образом, вы заменили одну проблему - ваша программа может выбросить NullReferenceException в условиях, которые вы не понимаете, - с другой, более важной: при тех же самых таинственных условиях ваша программа будет запускать код, который вы не тестировали.

Ответ 6

Отсутствует ваша документация по методу.; -)

Каждый метод имеет некоторые определенные параметры ввода и вывода и определенное результирующее поведение. В вашем случае что-то вроде: "Возвращает действительное открытое соединение в случае успеха, else возвращает null (или выбрасывает исключение XXX, как вам нравится). Помня об этом, вы можете теперь решить, как защищать программу.

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

  • Но если вас просто интересует открытый DBConnection или просто null (или какое-то определенное пользователем исключение) при сбое, то просто оберните все в try/catch и верните null (или некоторое исключение) при ошибке, и объект else.

Итак, я бы сказал, это зависит от поведения метода и ожидаемого результата.

Ответ 7

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

Еще одна причина быстро устранить эти ошибки состоит в том, что они часто включают в свои сообщения некоторые данные базы данных, такие как "Нет такой таблицы: ACCOUNTS_BLOCKED" или "User key invalid: 234234". Если это распространяется на конечного пользователя, это плохо по нескольким причинам:

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

Ответ 8

Мое правило:

не поймать, если сообщение брошенное исключение имеет отношение к вызывающий абонент.

Таким образом, NullReferenceException не имеет релевантного сообщения, я бы проверял, является ли оно null и генерирует исключение с лучшим сообщением. ConfigurationErrorException релевантно, поэтому я не поймаю его.

Единственное исключение - если "контракт" GetConnection не извлекает строку подключения обязательно в файле конфигурации.

Если в этом случае GetConnection ДОЛЖЕН иметь контракт с настраиваемым исключением, который говорит, что соединение не может быть восстановлено, вы можете обернуть ConfigurationErrorException в своем обычном исключении.

Другим решением является указание, что GetConnection не может выбрасывать (но может возвращать значение null), а затем добавлять в класс класс "ExceptionHandler".

Ответ 9

Я бы закодировал его точно так же, как ваша первая попытка.

Однако пользователь этой функции защитит объект соединения с помощью блока USING.

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

Ответ 10

Измененная версия не добавляет большого значения, если приложение имеет обработчик AppDomain.UnexpectedException, который сбрасывает цепочку exception.InnerException и все трассировки стека в какой-либо файл журнала (или даже лучше, захватывает мини-накопитель), а затем вызовите Environment.FailFast.

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

Обратите внимание, что предпочтительнее обрабатывать AppDomain.UnexpectedException и вызывать Environment.FailFast вместо верхнего уровня try/catch (Exception x), потому что с последним исходная причина проблемы, вероятно, будет затенена дальнейшими исключениями.

Это связано с тем, что если вы поймаете исключение, любые открытые блоки finally будут выполняться и, вероятно, будут вызывать больше исключений, что скроет исходное исключение (или, что еще хуже, они будут удалять файлы в попытке вернуть некоторое состояние, возможно, неправильные файлы, возможно, даже важные файлы). Вы никогда не должны улавливать исключение, которое указывает на недопустимое состояние программы, которое вы не знаете, как обрабатывать - даже в верхнем уровне main функции try/catch. Обработка AppDomain.UnexpectedException и вызов Environment.FailFast - это другой (и более желательный) чайник рыбы, потому что он останавливает блокировку finally, и если вы пытаетесь остановить свою программу и занести в журнал полезную информацию, не нанося дальнейший урон, вы определенно не хотите запускать блоки finally.

Ответ 11

Не забудьте проверить OutOfMemoryExceptions... вы знаете, что может.

Ответ 12

Изменения Iain выглядят разумно для меня.

Если я использую систему, и я использую ее неправильно, я хочу получить максимальную информацию о неправильном использовании. Например. если я забыл вставить некоторые значения в конфигурацию перед вызовом метода, я хочу исключение InvalidOperationException с сообщением, описывающим мою ошибку, а не исключение KeyNotFoundException/NullReferenceException.

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

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

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

Ответ 13

В моих глазах ваш образец "после" не очень защищен. Потому что защита была бы проверкой частей под вашим контролем, что было бы аргументом connectionName (проверьте значение null или пустое, и выведите ArgumentNullException).

Ответ 14

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

Что-то вроде этого (отредактировано в редакторе SO, поэтому синтаксис/компилятор не проверяет. Могу не компилировать; -))

private string GetConnectionString(String connectionName)
{

   //Get the connection string info from web.config
   ConnectionStringSettings cs= ConfigurationManager.ConnectionStrings[connectionName];

   //documented to return null if it couldn't be found
   if (cs == null)
       throw new ArgumentException("Could not find connection string \""+connectionName+"\"");
   return cs;
}

private DbProviderFactory GetFactory(String ProviderName)
{
   //Get the factory for the given provider (e.g. "System.Data.SqlClient")
   DbProviderFactory factory = DbProviderFactories.GetFactory(ProviderName);

   //Undefined behaviour if GetFactory couldn't find a provider.
   //Defensive test for null factory anyway
   if (factory == null)
      throw new Exception("Could not obtain factory for provider \""+ProviderName+"\"");
   return factory;
}

public DbConnection GetConnection(String connectionName)
{
   //Get the connection string info from web.config
   ConnectionStringSettings cs = GetConnectionString(connectionName);

   //Get the factory for the given provider (e.g. "System.Data.SqlClient")
   DbProviderFactory factory = GetFactory(cs.ProviderName);


   //Have the factory give us the right connection object
   DbConnection conn = factory.CreateConnection();

   //Undefined behaviour if CreateConnection failed
   //Defensive test for null connection anyway
   if (conn == null)
      throw new Exception("Could not obtain connection from factory");

   //Knowing the connection string, open the connection
   conn.ConnectionString = cs.ConnectionString;
   conn.Open()

   return conn;
}

PS: Это не полный рефактор, только первые два блока.