对数据访问层的重构(及重构中Perl的应用)

  以前上学的时候,听到“一个学生在毕业后刚刚开始编程的头几年中,写出的代码多半是垃圾”这样的说法,均不屑一顾。现在工作一年多了,越发感觉自己代码中疏漏处甚多,故近来常做亡羊补牢的重构之举。拿自己4个月前写的数据访问层来说,这个层位于整个系统的最底端,根据传入的sql语句进行查询和更新操作。就拿查询来说吧,我当时觉得很简单,写出来的方法是这样的:
 1 None.gif // 代码段1
 2 None.gif public  DataTable ExecuteQuery( string  cmdText)
 3 ExpandedBlockStart.gifContractedBlock.gif dot.gif {
 4InBlock.gif    if (cmdText == null)
 5InBlock.gif        return null;
 6InBlock.gif
 7InBlock.gif    SqlDataAdapter adapter = new SqlDataAdapter(cmdText, connection);
 8InBlock.gif    DataTable table = new DataTable();
 9InBlock.gif    try
10ExpandedSubBlockStart.gifContractedSubBlock.gif    dot.gif{
11InBlock.gif        adapter.Fill(table);
12ExpandedSubBlockEnd.gif    }

13InBlock.gif    catch (SqlException e)
14ExpandedSubBlockStart.gifContractedSubBlock.gif    dot.gif{
15InBlock.gif
16InBlock.gif        HttpContext.Current.Response.Redirect(string.format("~/ErrorPage?ErrorString={0}", HttpContext.Current.Server.UrlEncode(e.Message)));
17ExpandedSubBlockEnd.gif    }

18InBlock.gif    finally
19ExpandedSubBlockStart.gifContractedSubBlock.gif    dot.gif{
20InBlock.gif            connection.Close();
21ExpandedSubBlockEnd.gif    }

22InBlock.gif
23InBlock.gif    return table;
24ExpandedBlockEnd.gif}

而此方法都是这样被我调用的:
1 None.gif // 代码段2
2 None.gif string  cmd  =   " select * from MaterialClass where MaterialClassCode = {0} " ;
3 None.gifcmd  =   string .Format(cmd, scope);
4 None.gifDataTable table  =   new  BitAECSqlExe().ExecuteQuery(cmd);

有心的朋友很容易就能看出,如此的代码是非常脆弱的,根本无法应付查询字符串中出现特殊字符的情况(单引号,百分号等)。程序容错性差不说,还容易遭到sql注入的攻击。另外,对于sql操作可能抛出的异常,这里捕获之后就跳转到出错页的方式也是很糟糕的;因为这段底层代码,除了会被页面调用以外,还会被其他一些不是由客户端请求引发的功能所调用(如部署到服务器上的windows服务,处理复杂任务的专用工作线程等),在这些调用环境里HttpContext.Current是没有意义的。实际上,在这种底层的代码里,只能对异常作必要的处理,至于究竟是跳转到一个友好的错误提示界面,还是写入错误日志,应当交给更高层的代码去决定。
  对这部分的重构其实很简单,那就是在进行查询的时候,对于所有参数都以SqlParameter进行封装。重构中新增了方法PrepareCommand,用来得到我们需要的SqlCommand对象。对ExecuteQuery的重构如下:
 1 None.gif // 代码段3
 2 None.gif private  SqlCommand PrepareCommand( string  cmdText, SqlParameter[] cmdParms)
 3 ExpandedBlockStart.gifContractedBlock.gif dot.gif {
 4InBlock.gif    if (cmdText == null)
 5InBlock.gif        throw new ArgumentNullException();
 6InBlock.gif
 7InBlock.gif    SqlCommand cmd = new SqlCommand(cmdText, connection);
 8InBlock.gif    if((cmdParms!=null)&&(cmdParms.Length>0))
 9InBlock.gif        foreach (SqlParameter param in cmdParms)
10ExpandedSubBlockStart.gifContractedSubBlock.gif        dot.gif{
11InBlock.gif            cmd.Parameters.Add(param);
12ExpandedSubBlockEnd.gif        }

13InBlock.gif
14InBlock.gif    return cmd;
15ExpandedBlockEnd.gif}

16 None.gif
17 None.gif public  DataTable ExecuteQuery( string  cmdText, SqlParameter[] sqlParams)
18 ExpandedBlockStart.gifContractedBlock.gif dot.gif {
19InBlock.gif    if (cmdText == null)
20InBlock.gif        return null;
21InBlock.gif
22InBlock.gif    SqlDataAdapter adapter = new SqlDataAdapter(this.PrepareCommand(cmdText,sqlParams));
23InBlock.gif    DataTable table = new DataTable();
24InBlock.gif    try
25ExpandedSubBlockStart.gifContractedSubBlock.gif    dot.gif{
26InBlock.gif        adapter.Fill(table);
27ExpandedSubBlockEnd.gif    }

28InBlock.gif    catch (SqlException e)
29ExpandedSubBlockStart.gifContractedSubBlock.gif    dot.gif{
30InBlock.gif        throw e;
31ExpandedSubBlockEnd.gif    }

32InBlock.gif    finally
33ExpandedSubBlockStart.gifContractedSubBlock.gif    dot.gif{
34InBlock.gif        if (connection.State != ConnectionState.Open)
35InBlock.gif            connection.Close();
36ExpandedSubBlockEnd.gif    }

37InBlock.gif
38InBlock.gif    return table;
39ExpandedBlockEnd.gif}

  上面重构的是ExecuteQuery方法的定义部分,这其实只是重构的开始,因为现有代码中充满了类似代码段2那样的方法调用,都需要改成如下的形式:

1 None.gif // 代码段4
2 None.gif string  cmd  =   " select * from MaterialClass where MaterialClassCode = @MaterialClassCode and CompanyName = @CompanyName " ;
3 None.gifSqlParameter[] SqlParams  =  
4 ExpandedBlockStart.gifContractedBlock.gif dot.gif
5InBlock.gif    new SqlParameter("@MaterialClassCode",MaterialClassCode ),
6InBlock.gif    new SqlParameter("@CompanyName",CompanyName )
7ExpandedBlockEnd.gif}

8 None.gifDataTable table  =   new  BitAECSqlExe().ExecuteQuery(cmd);

   比照一下上面贴出的代码段2和代码段4,它们之间的差异就是我很希望自己能通过正则表达式跨越的鸿沟(一个一个手工去改的话实在太累了,一百多处啊,呵呵)。需要注意的是,查询语句中可能出现的参数个数是不定的,每个文件中可能包含的方法调用的数量也是不定的。本想写一个C#的console小程序来完成这个批量操作,但又总觉得杀鸡用牛刀,后来写了一个简单的perl脚本,利用perl强大的文本处理能力,很容易就实现了这个功能。脚本代码如下:

 1 None.gif use  FileHandle;
 2 None.gif use  File :: Find;
 3 None.gif use  strict;
 4 None.gif
 5 None.gif # 全局变量
 6 None.gif my   $directory   =   " E:/GEA52_SVN/GEA52/Web " ;
 7 None.gif # my $directory = "E:/Temp/Perl";
 8 None.gif
 9 None.giffind( \& editFile ,   $directory );
10 None.gif
11 None.gif # 使用这个方法来修改每一个代码文件
12 None.gif#xingyk 20070702
13 None.gif sub  editFile() 
14 None.gif{
15 None.gif     if  (  - f and  /. cs ?/  ) 
16 None.gif    {
17 None.gif         my   $file   =   $_ ;
18 None.gif         open  FILE ,   $file ;
19 None.gif         my   @lines   =   < FILE > ;
20 None.gif         close  FILE;
21 None.gif        
22 None.gif         my   @maArr ;
23 None.gif         for   my   $line  (  @lines  ) 
24 None.gif        {
25 None.gif             @maArr   =   $line   =~   /\ s + ( \ w + ) \ s *= { \ d} / g;
26 None.gif            
27 None.gif             # 先替换原有查询字符串
28 None.gif              $line   =~  s /\ s + ( \ w + ) \ s *= { \ d} /  $ 1 =\ @$ 1   / g;
29 None.gif             # 接下来添加SqlParameter数组的默认构造器
30 None.gif              my   $sqlParam   =   " \n\tSqlParameter[] sqlParams = {  " ;
31 None.gif             if ( @maArr > 0 )
32 None.gif            {
33 None.gif                 for ( @maArr )
34 None.gif                {
35 None.gif                     $sqlParam   =   $sqlParam . " \t\tnew SqlParameter(\ " @ " .$_. " \ " " . $_ . " ),\n " ;
36 None.gif                }
37 None.gif                 $line   =   $line . $sqlParam . " \t\t\t};\n " ;
38 None.gif            }
39 None.gif            
40 None.gif        }
41 None.gif
42 None.gif         open  FILE ,   " >$file " ;
43 None.gif         print  FILE  @lines ;
44 None.gif         close  FILE;
45 None.gif    }
46 None.gif}


 

转载于:https://www.cnblogs.com/xingyukun/archive/2007/07/03/804755.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值