我正与一个小小的程序,用于创建一个数据库连接:

之前

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;
}

然后我就开始寻找到的.净框架文件,看看什么的 记录了 行为的各种各样的事情都和看到,如果我可以处理它们。

例如:

ConfigurationManager.ConnectionStrings...

文档 说那叫 ConnectionStrings 抛出了一个 ConfigurationErrorException 如果它不能检索所收集的。在这种情况下,有什么我可以做些什么来处理这个例外,所以我将让它去。


接下来的部分是实际索引的 ConnectionStrings 找到 connectionName:

...ConnectionStrings[connectionName];

在这一实例 ConnectionStrings文件 说,财产将返回 如果连名字也不会被发现。我可以检查这种情况发生,并把一个异常让人高了,他们给了一个无效的connectionName:

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

我重复同样的演习:

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

GetFactory 方法没有任何文件上会发生什么,如果一家工厂的规定 ProviderName 不能被发现。这不是记录回返 null, 但我仍然可以防御性, 检查 空:

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

接下来是建筑的对象对象:

DbConnection conn = factory.CreateConnection()

再一次的 文档 并不是说会发生什么,如果它不能创建一个连接,但同样我可以检查一无返回的对象:

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

接下来是设置一个酒店连接的对象:

conn.ConnectionString = cs.ConnectionString;

医生不是说会发生什么,如果它不能设定连串。它不会扔的一个例外?它不会忽视它吗?如同大多数例外,如果有一个错误的话,试图设置的。一方面,有什么我可以做到恢复。所以我会做什么。


最后,开放该数据库连接:

conn.Open();

开放的方法 的对象是抽象的,所以它是无论提供者是降,从对象的决定的例外情况,他们扔掉。也没有的指导的抽象开放的方法的文件有什么我可以期望发生,如果有一个错误。如果有一个错误的连接,我知道我不能处理它-我得让它泡了那里的呼叫者可以显示一些UI要用户,并让他们再试一次。


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分钟的文件的查找。在结束我没有赶上的一种情况的一个方法是允许返回空。但在实践中我所做的就是转换一个访问违例外(如果我试图呼叫的方法上的一个空的参照)进入一个 InvalidArgumentException.

我还要抓两个可能的情况下,有可能是 返回的物体;但同样我只是交易的一个例外的另一种。

在积极的方面,它没有赶上的两个问题,并且解释发生了什么异常的信息,而不是糟糕的事情发生了时(即巴克停止这里的)

但是它值得吗?这是矫枉过正的?这是防御性的编程出了错?

有帮助吗?

解决方案

手动检查配置和抛出异常并不比只是让框架抛出异常,如果配置缺少更好。你只是复制前提检查其框架内的方法发生了,无论如何,它使你的代码冗长而没有益处。 (其实你可能的删除的投掷一切为Exception基类的信息。由框架抛出的异常通常是更具体的。)

编辑:这个答案似乎是有些争议,所以有点阐述:防御性编程的意思是“准备意外”(或“偏执”)和做到这一点的方法之一就是让许多先决条件检查。在许多情况下,这是很好的做法,但与所有的做法成本应针对利益进行权衡。

例如它没有提供任何好处,以抛出一个“未能从工厂连接”的例外,因为它并没有说明的为什么的无法获得供应商任何东西 - 和非常如果提供者为null下一行,无论如何都会抛出异常。这样的前提条件检查(在开发时间和代码复杂度)的成本是没有道理的。

在另一方面检查,以验证连接字符串配置存在的可能的是合理的,因为除了可以帮助告诉开发人员如何解决这个问题。空的异常,你会在下一行获得死活不告诉缺少连接字符串的名字,所以你的先决条件检查并提供一些价值。如果你的代码,例如是组件的一部分,该值是相当大的,因为该组件可能不知道哪些配置组件的用户需要。

防御式编程的一个不同的解释是,你不应该只检测错误条件,你也应该尝试从可能出现的任何错误或异常恢复。我不相信这是一般的好主意。

基本上你应该只处理异常,你可以的的一些事情。你不能从反正恢复异常,应该只向上传递到顶级处理器。在Web应用程序的顶层处理程序可能只是显示了一个一般性的错误页面。但并不是真正意义上在大多数情况下做的,如果数据库是脱机或一些重要的配置丢失。

某些情况下,那种防御式编程的是有意义的,是如果你接受用户输入,并将输入可能会导致错误。例如,如果用户提供了一个URL作为输入,并且应用程序试图从该URL获取的东西,那么你检查URL看起来是正确的,而你处理可能导致从请求的任何异常是非常重要的。这使您可以向用户提供有价值的反馈。

其他提示

嗯,这取决于你的观众。

如果你写的库码的预期使用的其他很多人,谁也不会跟你说话有关如何使用它,然后就没有矫枉过正。他们会欣赏你的努力。

(这就是说,如果你那样做,我建议你更好的定义的例外情况不仅仅是系统。异常,以使人们更容易谁想要抓你的一些例外,但不是其他人。)

但是如果你只是要自己使用(或者你和你的好友),那么显然这是矫枉过正,而且很可能伤害你通过你的代码不太可读性。

我希望我能得到我的球队像这样的代码。大多数人甚至不获得防御式编程的地步。他们做的最好的是包装在一个try catch语句的整体方法和让所有的异常,由通用的异常块来处理!

脱帽向你伊恩。我能理解你的困境。我经历过同样的自己。但你也可能帮助一些开发人员几个小时的键盘抨击。

记住,当你正在使用.NET框架API,你能指望什么了呢?什么似乎很自然?做相同的代码。

我知道这需要时间。但随后质量是有代价的。

PS:你真的没有来处理所有的错误,并引发自定义异常。请记住,你的方法将只用于其他开发人员。他们应该能够找出共同框架例外自己。这是不值得的麻烦。

您“之前”例子有被清楚和简明的区别。

如果事情是错,一个异常将会最终被抛出框架。如果你不能做的例外话,你还不如让它沿着调用堆栈。

,有很多次,但是当一个例外是,真的没有实际的问题是什么揭示框架深处泛起。如果你的问题是,你没有一个有效的连接字符串,但框架抛出类似的异常“无效使用空的”,那么有时它是更好地捕获异常,并有一条消息,是更有意义的重新抛出。

我做检查空对象很多,因为我需要一个实际的对象进行操作,如果对象为空时引发的异常将是斜的,至少可以这样说。但我只检查空的对象,如果我知道这是会发生什么。一些对象工厂没有返回null对象;它们抛出异常,而是和检查空将是在这些情况下无用。

我不认为我会写任何空引用检查逻辑的 - 至少,没有办法的办法,你已经做到了

我的程序,您可以通过应用程序配置文件的配置设置检查所有的这些设置在启动时。我通常建立一个静态类包含的设置和引用类的属性(而不是ConfigurationManager)在应用程序中的其他地方。有两个原因。

首先,如果应用程序没有正确配置,它是行不通的。我宁愿此刻的程序读取配置文件比在未来的某个时刻,当我尝试创建一个数据库连接知道这一点。

二,检查配置的有效性应该不会真的是依赖于配置的对象的关注。有没有在使自己在任何地方插入整个检查你的代码,如果你已经执行这些检查在前面没有任何意义。 (也有例外,当然 - 例如,长期运行,你需要能够在程序运行时更改配置,并将这些变化体现在程序的行为的应用程序,在这样的程序,您需要转到ConfigurationManager每当你需要的设置。)

我不会做空引用的GetFactoryCreateConnection呼叫检查,无论是。你会如何编写测试用例行使该代码?你不能,因为你不知道如何让这些方法返回null - 你甚至不知道它的可能的使这些方法返回null。所以,你已经更换一个问题 - 你的程序可以抛出一个NullReferenceException,你不明白的条件下 - 用另一种更显著一个:那些同样神秘的条件下,你的程序将运行,你有没有经过测试的代码

我的大拇指的规则是:

  

不抓住如果所述消息   抛出的异常是相关的   呼叫者。

因此,NullReferenceException异常没有相关的消息,我会检查它是否为空,并抛出一个异常,一个更好的消息。 ConfigurationErrorException是相关的,所以我不抓住它。

唯一的例外是,如果的getConnection的“合同”并不在配置文件中检索必要的连接字符串。

如果它的getConnection应该有一个自定义异常这不能不说的连接无法检索的合同的话,那么你可以在你的自定义异常包裹ConfigurationErrorException。

在其他的解决办法是指定的getConnection不能丢(但可以返回null),那么你添加一个“的ExceptionHandler”你的课。

你的方法的文件丢失。;-)

每个方法都有一些定义的输入和输出参数和一个定义得到的行为。在你的情况是这样的:"返回的有效开放式连接在成功的情况下,他返回null(或引发XXXException,你等等)。保持这种行为记住,你现在可以决定如何防御性的,你应该程序。

  • 如果你的方法应该获得详细的信息为什么和什么失败,那么它像你一样和检查并抓住所有的一切和返回的适当信息。

  • 但是,如果你只是有兴趣在一个开放的对象,或只是 (或者某些用户限定的例外)上失败,那只是包装一切都在尝试/抓住和返回 (或一些例外)上的错误,并对象的人。

所以,我要说,它取决于方法的行为和预期产出。

在一般情况下,数据库特定的例外情况应该被抓,并重新抛出更多的东西的一般情况下,像(假定的) DataAccessFailure 例外。更高级别的代码在大多数情况下并不需要知道你是读取数据库中的数据。

另一个原因是陷阱这些错误是,他们往往包括一些数据库的详细信息,在他们的消息,如"没有这样的表:ACCOUNTS_BLOCKED"或"用户密无效:234234".如果这种传播到最终用户,这是在几个方面:

  1. 令人困惑。
  2. 潜在的安全违规行为。
  3. 令人尴尬的图像你的公司(法想象客户读书的一个错误信息,与原文法).

我会编码它完全像你的第一次尝试。

但是,该函数的用户将保护连接对象与使用块。

我真的不喜欢翻译喜欢你的其他版本的异常做,因为它使得它发现非常困难,为什么它打破了(数据库下来?没有权限读取配置文件,等等?)。

在修正后的版本并没有增加多少价值,只要应用程序具有转储AppDomain.UnexpectedException链和所有堆栈跟踪到某种类型的日志文件的exception.InnerException处理器(甚至更好,捕获小型转储),然后调用Environment.FailFast

从这些信息中,这将是相当简单查明哪里出了问题,而无需复杂与其他错误检查方法的代码。

请注意,优选的是,处理AppDomain.UnexpectedException并调用代替具有顶层Environment.FailFast因为后者,问题的最初原因可能会被进一步例外遮蔽try/catch (Exception x)

这是因为,如果你捕获异常,任何打开finally块将执行,并可能会抛出更多的异常,这将隐藏原始异常(或更糟的是,他们将删除的文件,试图恢复一些状态,可能是错误的文件,甚至重要的文件)。你不应该捕捉异常,这表明你不知道如何处理无效程序的状态 - 即使是在顶级main功能try/catch块。处理AppDomain.UnexpectedException并呼吁Environment.FailFast是鱼的不同(更可取的)壶,因为它停止finally阻止运行,如果你想阻止你的程序并没有做进一步的损害登录一些有用的信息,你绝对不要运行finally块。

不要忘记检查OutOfMemoryExceptions ......你知道,它的可能发生。

伊恩的变化看明智的给我。

如果我使用的系统和我不当使用它,我要上滥用最多的信息。例如。如果我忘记了调用一个方法之前插入一些值的配置,我想用一个消息一个InvalidOperationException详细说明我的错误,而不是一个KeyNotFoundException / NullReferenceException异常。

这是所有关于上下文IMO。我已经看到了一些相当令人费解的异常信息在我的时间,但其他时间从框架来默认的例外是完全正常的。

在一般情况下,我认为这是最好宁可谨慎的一面,尤其是当你写的东西,在很大程度上被其他人使用或通常在调用图,其中错误是很难诊断深。

我总是试图要记住,作为一段代码或系统的开发者,我在一个更好的位置来诊断不是谁是在运用它的人的失败。它有时在检查代码+自定义异常消息可以累计节省的调试时间小时的几行(也使自己的生活更容易,因为你没有得到解决拉到别人的机器上调试他们的问题)的情况下。

在我的眼里,你的“后”样品是不是真的防守。因为防守将是检查你的控制下的部件,这将是自变量connectionName(检查空或为空,并抛出ArgumentNullException)。

为什么你添加了所有的防御性编程后不分割你的方法是什么?你有一堆其需要单独的方法不同的逻辑块。为什么?因为这样你封装属于一起的逻辑,并且产生的公共方法只是导线以正确的方式的那些块。

像这样(在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:这不是一个完整的重构,只做了前两个块

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top