百万分之一
作为一个勤奋的开发人员,您已经为几个需要更好地访问复杂的大量数据存储的客户安装了一个应用程序,它编写良好,而且经过了充分测试。
对每个客户,现场测试阶段都畅通无阻地通过了。您在去银行的路上,心里极少考虑这六个月来的软件审查,这时您的传呼机响了起来。您的一个客户在使用您的软件运行一个报表时,系统崩溃了。
您赶到出事地点,运行了一个随机测试。工作良好。您运行另一个。没出现问题。您又运行了数百个测试。还是没有问题。您又检查了持续六个月运行这个应用程序的其它客户。没有投诉。
您重复运行那个引起问题的报表。崩溃!怎么回事?
破坏者数据错误模式
许多程序需要频繁访问和处理内部储存的数据来执行各种复杂的任务。这种数据可以从内存中的大型结构、数据库或网络上检索得到。
这类程序非常容易遭受损坏的内部数据引起的崩溃。我称这种错误模式为破坏者数据模式,是因为这种数据可以无限期地存在于系统中(很象冷战中的潜伏间谍一样),不引发任何问题,直到访问一段特定的数据时,损坏的数据才象炸弹一样爆炸。
语法原因
假定我们有一个 JDBC 应用程序,它存储了一个名为 Mapping 的数据库表,该表将 String 的名称映射到一系列元素的集合。(请参阅 参考资料,以获取关于 JDBC API 的更多信息。)每个集合中的每个元素都引用另一个表中的一个关键字(该表名为 Properties,包含这些元素的不同已知属性)。
这样说吧,Mapping 和 Properties 表最初都是从一个文本文件中读取的,这个文本文件由外部源( 外部意为不是内部产生的任意数据源)发展而来,而在外部源中,每行都以一个名称开头,后面跟着对应集合的表达,如下所示:
清单 1. 样本,外部源文本文件
In the Mapping file:
apples {macintosh, gala, golden-delicious}
trees {elm, beech, maple, pine, birch}
rocks {quartz, limestone, marble, diamond}
...
In the Properties file:
macintosh {color: red, taste: sour}
gala {color: red, taste: sweet}
diamond {color: clear, rigidity: hard, value: high}
...
可以对 Mapping 和 Properties 表条目进行语法分析并将其传递到一个方法中,此方法会把这些条目插入到一个数据库中。但这种方法存在潜在的缺陷。例如,假定我们已经编写了一个处理 JDBC 兼容数据库的类。遵照 JDBC API,我们可以定义一个 PreparedStatement 对象并使用它把信息传递到数据库中,如下所示:
清单 2. 使用 StreamTokenizer 插入域和区域字符串
...
PreparedStatement insertionStmt =
con.prepareStatement("INSERT INTO MAPPING VALUES(?,?)");
...
public void insertEntry(String domain, String range)
throws SQLException {
insertionStatement.setString(1, domain);
insertionStatement.setString(2, range);
insertionStatement.executeUpdate();
}
以这种方式插入两个 String 合适与否取决于从文本文件中获取 String 的方式。例如,假定一个简单的正则表达式匹配工具被用来将每一行拆分成两个 String :
一个 String 包含第一个 String 之前的全部字符。
一个 String 包含第一个 String 之后的全部字符。