一次程序调试带来的教训
2007-07-30 00:07
232 查看
周五的时候在写一个小程序,它的输入是电话号码,输出是该号码的区号。区号表用Access数据库存储。
解析算法的最初思路是:把整张区号表做成一个状态机,电话号码逐字符输入。这样效率应该比较高,但是要手工构造那个状态机就有点麻烦。“先让程序跑起来,然后让它快起来”——稍微简化一下,先用号码的头三位(为什么用前三位?那是因为国内区号最短就是三位)做模糊查询,过滤掉明显不匹配的区号,然后再用剩下的区号对号码进行最长子串匹配。嗯,就是这个主意。
数据库设计是酱紫滴:
RegionCode数据表
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里面执行——见鬼了,查询结果完全没问题:
为啥程序都查不到呢?难道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、尽量避免“按约定编程”,这会给程序加入容易使人忽略的假设,难以发现和定位。
解析算法的最初思路是:把整张区号表做成一个状态机,电话号码逐字符输入。这样效率应该比较高,但是要手工构造那个状态机就有点麻烦。“先让程序跑起来,然后让它快起来”——稍微简化一下,先用号码的头三位(为什么用前三位?那是因为国内区号最短就是三位)做模糊查询,过滤掉明显不匹配的区号,然后再用剩下的区号对号码进行最长子串匹配。嗯,就是这个主意。
数据库设计是酱紫滴:
RegionCode数据表
字段名 | 类型及约束 | 字段含义 |
City | 文本,非空 | 区号对应的主要城市 |
Code | 文本,主键 | 区号 |
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里面执行——见鬼了,查询结果完全没问题:
city | code |
020 | 广州市 |
最后一招,关门放狗……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、尽量避免“按约定编程”,这会给程序加入容易使人忽略的假设,难以发现和定位。
相关文章推荐
- 如果一个程序跑10000次只失败一次,你会怎么调试?
- [转]如果一个程序跑10000次只失败一次,你会怎么调试?
- 如果一个程序跑10000次只失败一次,你会怎么调试?
- 如果一个程序跑10000次只失败一次,调试方法
- 一次有教益的程序崩溃调试 (下)
- 无法到达的快递地址 一次偷懒表设计带来的惨痛教训
- 一次程序调试小记
- 程序跑10000次才失败一次怎么调试?
- 一次程序调试小记
- 记录一次并未读源码调试程序的经历
- 如果一个程序跑10000次只失败一次,你会怎么调试?
- 一次有教益的程序崩溃调试 (上)
- 一次有教益的程序崩溃调试 (中)
- 如果一个程序跑10000次只失败一次,你会怎么调试?
- 一次工作中程序bug的调试总结
- 如果一个程序跑10000次只失败一次,你会怎么调试?
- 如果一个程序跑10000次只失败一次,你会怎么调试
- 如果一个程序跑10000次只失败一次,你会怎么调试?
- vs2008调试XSLT程序
- 编写和调试Shader程序(1)