您的位置:首页 > 其它

一次程序调试带来的教训

2007-07-30 00:07 232 查看
周五的时候在写一个小程序,它的输入是电话号码,输出是该号码的区号。区号表用Access数据库存储。
解析算法的最初思路是:把整张区号表做成一个状态机,电话号码逐字符输入。这样效率应该比较高,但是要手工构造那个状态机就有点麻烦。“先让程序跑起来,然后让它快起来”——稍微简化一下,先用号码的头三位(为什么用前三位?那是因为国内区号最短就是三位)做模糊查询,过滤掉明显不匹配的区号,然后再用剩下的区号对号码进行最长子串匹配。嗯,就是这个主意。

数据库设计是酱紫滴:
RegionCode数据表

字段名类型及约束字段含义
City文本,非空区号对应的主要城市
Code文本,主键区号
GetCodeList查询

SELECT Code.city, Code.code
2FROM Code
3WHERE Code.code like ([(@prefix] & "*")
4ORDER BY len(code) DESC , code;

DataBaseManager封装了GetCodeList查询

public class DataBaseManager
2 {
3 public static void EnsureDatabaseAvailable ()
4
6 private static OleDbConnection GetConnection ()
7
9 public static CodeList GetCodeList(string prefix)
10 {
11 string statement= "GetCodeList";
12 OleDbDataAdapter adapter = new OleDbDataAdapter (statement, GetConnection ());
13 OleDbCommand command = adapter.SelectCommand;
14 command.CommandType = CommandType.StoredProcedure;
15 command.Parameters.Add("@Code", prefix);
16 OleDbParameter parameter = command.Parameters.Add(new OleDbParameter("@Code", OleDbType.VarWChar, 255));
17 parameter.Value = prefix;
18 DataTable table = new DataTable("Code");
19 adapter.Fill(table);
20 return new CodeList(table);
21 }
22 }

查询结果被传递给CodeList类,它封装了对结果集的遍历(这个类还会有其他方法,例如提供区号和城市之间的映射查询)

public class CodeList : IEnumerable
2[TestFixture]
2 public class RegionCodeParserTestCases
3SELECT Code.city, Code.code
2FROM Code
3WHERE ((left(Code.code,len([@prefix]))=[@prefix]))
4ORDER BY len(code) DESC , code;
NUnit再测,还是Red bar……

OK,再退一步,我现在已经完全不信任Access的查询了,我自己拼SQL,参数传递我也一样不信任,直接把参数拼进SQL串去:

public static CodeList GetCodeList(string prefix)
2 {
3 const string statement =
4 "SELECT Code.city, Code.code FROM Code" +
5 " WHERE ((left(Code.code,len([@prefix]))=[@prefix])) " +
6 "ORDER BY len(code) DESC , code;";
7 statement = statement.Replace("@prefix", prefix);
8 statement = statement.Replace("@prefix", prefix);
9 OleDbDataAdapter adapter = new OleDbDataAdapter (statement, (OleDbConnection)connection);
10 OleDbCommand command = adapter.SelectCommand;
11 command.CommandType = CommandType.StoredProcedure;
12 command.Parameters.Add("@Code", prefix);
13 OleDbParameter parameter = command.Parameters.Add(new OleDbParameter("@Code", OleDbType.VarWChar, 255));
14 parameter.Value = prefix;
15 DataTable table = new DataTable("Code");
16 adapter.Fill(table);
17 return new CodeList(table);
18 }

再测,依然Red bar……
别急,擦擦汗,验证一下SQL串的正确性——拼完串的地方加个断点->添加快速监视->粘出拼好的SQL,放在Access里面执行——见鬼了,查询结果完全没问题:

citycode
020广州市
为啥程序都查不到呢?难道Access对Ado.net的支持差到这种地步吗?
最后一招,关门放狗……googling到一些文字,说Access不支持命名参数传递,只能严格按照参数出现的位置顺序传递实参。可是,我用SQL串拼得完全不需要参数呀?而且GetCodeList只有一个参数,完全不存在顺序问题啊?

光阴似箭,日月如梭,半天时间已经报销了,还是毫无进展。休息一下先,到走廊上溜达溜达……

再次回到座位上,老老实实从头看一遍程序。突然一行代码从眼前闪过:

internal CodeList(DataTable table)
2 {
3 foreach (DataRow row in table.Rows)
4 {
5 mList.Add(row[0]);
6 }
7 }

这里我偷了个懒,用索引代替字段类型,这是一个隐含的假设:结果集的第一个字段是区号。查一下SQL,OMG!第一个字段是city,我违反了自己的诺言。天哪!一个业余程序员才会犯的错误居然花了我半天宝贵的时间?!

痛定思痛,痛何如哉?总结教训,以为殷鉴:
1、在怀疑别人是否出了问题之前,先确定自己是正确的。
2、每个单元测试用例的范围应该尽量小,以便快速定位问题。
3、尽量避免“按约定编程”,这会给程序加入容易使人忽略的假设,难以发现和定位。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: