Ian*_*oyd 38 c# defensive-programming exception-handling
我正在使用一个用于创建数据库连接的小例程:
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;
}
Run Code Online (Sandbox Code Playgroud)
然后我开始研究.NET框架文档,看看各种事物的记录行为是什么,看看我是否可以处理它们.
例如:
ConfigurationManager.ConnectionStrings...
Run Code Online (Sandbox Code Playgroud)
该文件说,打电话的ConnectionStrings抛出一个ConfigurationErrorException如果无法检索集合.在这种情况下,我无法处理此异常,所以我会放手.
下一部分是ConnectionStrings的实际索引,以查找connectionName:
...ConnectionStrings[connectionName];
Run Code Online (Sandbox Code Playgroud)
在这种情况下,ConnectionStrings文档说如果找不到连接名,该属性将返回null.我可以检查是否发生了这种情况,并抛出一个例外,让某人高兴他们给了一个无效的connectionName:
ConnectionStringSettings cs=
ConfigurationManager.ConnectionStrings[connectionName];
if (cs == null)
throw new ArgumentException("Could not find connection string \""+connectionName+"\"");
Run Code Online (Sandbox Code Playgroud)
我重复同样的练习:
DbProviderFactory factory =
DbProviderFactories.GetFactory(cs.ProviderName);
Run Code Online (Sandbox Code Playgroud)
该GetFactory方法对如果指定一家工厂发生了什么没有文档ProviderName找不到.它没有记录返回null,但我仍然可以防御,并检查 null:
DbProviderFactory factory =
DbProviderFactories.GetFactory(cs.ProviderName);
if (factory == null)
throw new Exception("Could not obtain factory for provider \""+cs.ProviderName+"\"");
Run Code Online (Sandbox Code Playgroud)
接下来是DbConnection对象的构造:
DbConnection conn = factory.CreateConnection()
Run Code Online (Sandbox Code Playgroud)
再次,文档没有说明如果它无法创建连接会发生什么,但我再次检查null返回对象:
DbConnection conn = factory.CreateConnection()
if (conn == null)
throw new Exception.Create("Connection factory did not return a connection object");
Run Code Online (Sandbox Code Playgroud)
接下来是设置Connection对象的属性:
conn.ConnectionString = cs.ConnectionString;
Run Code Online (Sandbox Code Playgroud)
文档没有说明如果无法设置连接字符串会发生什么.它会抛出异常吗?它会忽略它吗?与大多数例外一样,如果在尝试设置连接的ConnectionString时出错,那么我无法从中恢复.所以我什么都不做.
最后,打开数据库连接:
conn.Open();
Run Code Online (Sandbox Code Playgroud)
DbConnection 的Open方法是抽象的,因此它取决于从DbConnection下降的任何提供者来决定它们抛出的异常.抽象的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;
}
Run Code Online (Sandbox Code Playgroud)
所以我的四行函数变成了12行,并且需要5分钟的文档查找.最后我确实捕获了一个允许方法返回null的情况.但实际上我所做的就是将访问冲突异常(如果我试图在空引用上调用方法)转换为InvalidArgumentException.
我还捕获了两种可能存在null返回对象的情况; 但我再次只为另一个交易了一个例外.
从积极的方面来说,它确实遇到了两个问题,并解释了异常消息中发生的事情,而不是发生在路上的坏事(即降压停在这里)
但是这值得吗?这有点矫枉过正吗?这防御性节目是否出错了?
Jac*_*esB 31
手动检查配置并抛出异常并不比让框架在缺少配置时抛出异常更好.你只是复制了框架方法中发生的前置条件检查,它使你的代码冗长而没有任何好处.(实际上,您可能通过将所有内容作为基本Exception类来删除信息.框架抛出的异常通常更具体.)
编辑:这个答案似乎有点争议,所以有点详细说明:防御性编程意味着"为意外做好准备"(或"偏执狂"),其中一种方法是进行大量的前提条件检查.在许多情况下,这是一种良好的做法,但是,与所有实践一样,成本应与权益相权衡.
例如,它没有提供任何好处来抛出"无法从工厂获得连接"异常,因为它没有说明为什么无法获取提供者 - 并且如果是,下一行将抛出异常provider为null.因此,前置条件检查的成本(开发时间和代码复杂性)是不合理的.
另一方面,验证连接字符串配置是否存在的检查可能是合理的,因为该异常可以帮助告诉开发人员如何解决问题.无论如何,您将在下一行中获得的null异常不会告诉缺少连接字符串的名称,因此您的前置条件检查确实提供了一些值.例如,如果您的代码是组件的一部分,则该值非常大,因为组件的用户可能不知道组件需要哪些配置.
对防御性编程的不同解释是,您不仅应该检测错误条件,还应该尝试从可能发生的任何错误或异常中恢复.我不相信这是一个好主意.
基本上你应该只处理你可以做些什么的异常.无论如何都无法恢复的异常应该只是向上传递给顶级处理程序.在Web应用程序中,顶级处理程序可能只显示一般错误页面.但是,在大多数情况下,如果数据库处于脱机状态或缺少某些关键配置,则没有太多事情可做.
这种防御性编程有意义的一些情况是,如果您接受用户输入,并且该输入可能导致错误.例如,如果用户提供URL作为输入,并且应用程序尝试从该URL获取某些内容,则检查URL看起来是否正确并处理可能由请求引起的任何异常非常重要.这允许您向用户提供有价值的反馈.
mqp*_*mqp 13
嗯,这取决于你的观众是谁.
如果您正在编写您期望被许多其他人使用的库代码,那些不会与您讨论如何使用它的库代码那么它就不会过度.他们会感激你的努力.
(也就是说,如果你这样做,我建议你定义比System.Exception更好的例外,以便让那些想要捕获一些例外而不是其他例外的人更容易.)
但是如果你只是想自己使用它(或者你和你的伙伴),那么显然它是过度的,并且可能最终会让你的代码不那么可读而伤害你.
我希望我能让我的团队像这样编码.大多数人甚至都没有得到防御性编程的观点.他们做的最好的事情是将整个方法包装在try catch语句中,并让所有异常都由泛型异常块处理!
向你致敬Ian.我能理解你的困境.我自己经历过同样的事情.但你所做的可能会帮助一些开发人员进行几个小时的键盘攻击.
请记住,当您使用.net框架API时,您对它的期望是什么?什么看似自然?对你的代码做同样的事情.
我知道这需要时间.但是质量是有代价的.
PS:你真的不必处理所有错误并抛出自定义异常.请记住,您的方法仅供其他开发人员使用.他们应该能够自己找出常见的框架异常.这不值得麻烦.
您的"之前"示例具有清晰简洁的区别.
如果出现问题,框架最终会抛出异常.如果你无法对异常做任何事情,你也可以让它传播到调用堆栈.
但是,有时候,在框架内部抛出一个异常,实际上并没有说明实际问题是什么.如果您的问题是您没有有效的连接字符串,但框架会抛出"无效使用null"之类的异常,那么有时最好捕获异常并使用更有意义的消息重新抛出它.
我确实检查了很多空对象,因为我需要一个实际的对象来操作,如果对象是空的,抛出的异常将是倾斜的,至少可以说.但是我只检查空对象,如果我知道会发生什么.某些对象工厂不返回null对象; 他们抛出异常,在这些情况下检查null将是无用的.