最近做项目时偶然翻到自己前不久写的一段代码,里面有一个给字符串加上换行符的函数,定义大致如下:
string StringInsertNewLine(string src, int length)
函数功能很简单:每隔i个字符就给s插入一个换行符。主要是用于在表格中显示备注等信息。里面有一个简单的输入参数检查:
string StringInsertNewLine(string src, int length)
{
if (length == 0)
{
throw new ArgumentException("length必须大于0");
}
if (src == null || src.Length < length)
{
return src;
}
... ...
}
对于src是否为null的检查,似乎已经是写程序的一种惯例了,但记得有同事对这种检查和处理方式提出了疑问,他认为这种检查没有什么必要:难道会有人傻到用下面的方式去调用它吗?
string s = StringInsertNewLine(null, 10)
抑或是
string s = StringInsertNewLine("Hello kitty", -10)
显然,直接用这种方式调用的可能性几乎为0(除非程序员根本没弄清楚这个函数是干什么的)。但是极有可能出现这样的调用情况
string s = StringInsertNewLine(GetString(), GetLength())
我们无法确定GetString()这个函数是否会在某种情况下返回null(事实上极有可能),也不清楚GetLength()是否会返回一个负数。如果不对参数进行检查,在这种情况下可能导致程序崩溃。
这就是“防御式编程”的主题,要对传入参数的险恶性有充分的考虑。关于“防御式编程”的讨论可以参考《程序员修炼之道》和《Code Complete2》,另外还有不少书都重点强调了这一问题。即便如此,在写程序时还是难免偷懒。我们是不是在每个函数中都对参数的有效性作过严密的验证呢?我们是否也都抱过侥幸的心理,对于一些“不可能”的情况轻易放过?虽然我还没有足够的经历来证明这种“懒惰”可能导致的后果,但还应该尽量去避免这样的疏漏。
至于在检查出异常的情况下如何处理,这取决于程序的应用。且不谈“契约”之类的理论,就拿上面的例子来说,在src为null的情况下应该返回null还是抛出异常?把这段代码放在不同的应用中会有不同的结论,如果src是绝对不允许为null的话,抛出异常是一种正确的选择;但在上面的例子中,StringInsertNewLine是用于备注等信息的处理,src为null是一种常见的也是允许的情况,返回一个null应该没有什么问题。“何时应该抛出异常”也是一个很有争议的主题,在《.net 框架程序设计》中也基于.net平台的特性作了一些讨论。