以前上学的时候,听到“一个学生在毕业后刚刚开始编程的头几年中,写出的代码多半是垃圾”这样的说法,均不屑一顾。现在工作一年多了,越发感觉自己代码中疏漏处甚多,故近来常做亡羊补牢的重构之举。拿自己4个月前写的数据访问层来说,这个层位于整个系统的最底端,根据传入的sql语句进行查询和更新操作。就拿查询来说吧,我当时觉得很简单,写出来的方法是这样的:

 1//代码段1
 2public DataTable ExecuteQuery(string cmdText)
 3{
 4    if (cmdText == null)
 5        return null;
 6
 7    SqlDataAdapter adapter = new SqlDataAdapter(cmdText, connection);
 8    DataTable table = new DataTable();
 9    try
10    {
11        adapter.Fill(table);
12    }
13    catch (SqlException e)
14    {
15
16        HttpContext.Current.Response.Redirect(string.format("~/ErrorPage?ErrorString={0}", HttpContext.Current.Server.UrlEncode(e.Message)));
17    }
18    finally
19    {
20            connection.Close();
21    }
22
23    return table;
24}

而此方法都是这样被我调用的:

1//代码段2
2string cmd = "select * from MaterialClass where MaterialClassCode = {0}";
3cmd = string.Format(cmd, scope);
4DataTable table = new BitAECSqlExe().ExecuteQuery(cmd);

有心的朋友很容易就能看出,如此的代码是非常脆弱的,根本无法应付查询字符串中出现特殊字符的情况(单引号,百分号等)。程序容错性差不说,还容易遭到sql注入的攻击。另外,对于sql操作可能抛出的异常,这里捕获之后就跳转到出错页的方式也是很糟糕的;因为这段底层代码,除了会被页面调用以外,还会被其他一些不是由客户端请求引发的功能所调用(如部署到服务器上的windows服务,处理复杂任务的专用工作线程等),在这些调用环境里HttpContext.Current是没有意义的。实际上,在这种底层的代码里,只能对异常作必要的处理,至于究竟是跳转到一个友好的错误提示界面,还是写入错误日志,应当交给更高层的代码去决定。
  对这部分的重构其实很简单,那就是在进行查询的时候,对于所有参数都以SqlParameter进行封装。重构中新增了方法PrepareCommand,用来得到我们需要的SqlCommand对象。对ExecuteQuery的重构如下:

 1//代码段3
 2private SqlCommand PrepareCommand(string cmdText, SqlParameter[] cmdParms)
 3{
 4    if (cmdText == null)
 5        throw new ArgumentNullException();
 6
 7    SqlCommand cmd = new SqlCommand(cmdText, connection);
 8    if((cmdParms!=null)&&(cmdParms.Length>0))
 9        foreach (SqlParameter param in cmdParms)
10        {
11            cmd.Parameters.Add(param);
12        }
13
14    return cmd;
15}
16
17public DataTable ExecuteQuery(string cmdText, SqlParameter[] sqlParams)
18{
19    if (cmdText == null)
20        return null;
21
22    SqlDataAdapter adapter = new SqlDataAdapter(this.PrepareCommand(cmdText,sqlParams));
23    DataTable table = new DataTable();
24    try
25    {
26        adapter.Fill(table);
27    }
28    catch (SqlException e)
29    {
30        throw e;
31    }
32    finally
33    {
34        if (connection.State != ConnectionState.Open)
35            connection.Close();
36    }
37
38    return table;
39}

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

1//代码段4
2string cmd = "select * from MaterialClass where MaterialClassCode = @MaterialClassCode and CompanyName = @CompanyName";
3SqlParameter[] SqlParams = 
4
5    new SqlParameter("@MaterialClassCode",MaterialClassCode ),
6    new SqlParameter("@CompanyName",CompanyName )
7}
8DataTable table = new BitAECSqlExe().ExecuteQuery(cmd);

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

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

对数据访问层的重构(及重构中Perl的应用)的更多相关文章

  1. 使用MediatR重构单体应用中的事件发布/订阅

    标题:使用MediatR重构单体应用中的事件发布/订阅 作者:Lamond Lu 地址:https://www.cnblogs.com/lwqlun/p/10640280.html 源代码:https ...

  2. 【重构】AndroidStudio中代码重构菜单Refactor功能详解

    代码重构几乎是每个程序员在软件开发中必须要不断去做的事情,以此来不断提高代码的质量.Android Stido(以下简称AS)以其强大的功能,成为当下Android开发工程师最受欢迎的开发工具,也是A ...

  3. .NET重构—单元测试重构

    .NET重构—单元测试重构 阅读目录: 1.开篇介绍 2.单元测试.测试用例代码重复问题(大量使用重复的Mock对象及测试数据) 2.1.单元测试的继承体系(利用超类来减少Mock对象的使用) 2.1 ...

  4. 框架重构:测试中的DateTime.Now

    存在的问题 DateTime.Now是C#语言中获取计算机的当前时间的代码: 但是,在对使用了DateTime.Now的方法进行测试时,由于计算机时间的实时性,期望值一直在变化.如:计算年龄. pub ...

  5. 重构网页过程中的小tips

    1.display为inline-block的元素可以使用virtical-align:middle来使得元素垂直居中对齐 2.在一些按钮标签或者mark标签中,如果文本内容确定不会改变长度的话,可以 ...

  6. 关于ruby重构的过程中去除不必要的format

    (文章是从我的个人主页上粘贴过来的,大家也可以访问我的主页 www.iwangzheng.com) #这段话可以由下面的话替代56     respond_to do |format|57       ...

  7. 通过反射获取SSM的controller层的注解以及注解中的value值

    package com.reflection.test; import java.lang.annotation.Annotation; import java.lang.reflect.Invoca ...

  8. C++对象模型6--对象模型对数据访问的影响

    如何访问成员? 前面介绍了C++对象模型,下面介绍C++对象模型的对访问成员的影响.其实清楚了C++对象模型,就清楚了成员访问机制.下面分别针对数据成员和函数成员是如何访问到的,给出一个大致介绍. 对 ...

  9. 阶段3 1.Mybatis_06.使用Mybatis完成DAO层的开发_6 Mybatis中使用Dao实现类的执行过程分析-增删改方法

    从测试类入手,断点调试 找到实现类,进入到insert方法里面 这里是SqlSession的接口里面的方法. 我们需要找SqlSession的实现类. DefaultSqlSession 里面有两个i ...

随机推荐

  1. 【iOS开发】多线程下NSOperation、NSBlockOperation、NSInvocationOperation、NSOperationQueue的使用

    http://blog.csdn.net/crycheng/article/details/21799611 本篇文章主要介绍下多线程下NSOperation.NSBlockOperation.NSI ...

  2. web相关基础知识3

    一 .浮动布局   ★元素浮动之后不占据原来的位置,脱离标准流 ★浮动的盒子在一行上显示 ★行内元素浮动之后转换为行内块元素.(不推荐使用,会脱离标准流,转行内元素最好使用display: inlin ...

  3. 【SSH】——spring的控制反转和依赖注入

    spring是一个轻量级的容器框架,主要是为了使企业的开发变得简单.高效.无论是从大小还是开销来讲,他都可以算是轻量级的,也是非侵入性的. 下图是spring的框架示意图,说到spring,就不得不提 ...

  4. [C/C++] “箭头(->)”和“点号(.)”的区别

    转自:http://blog.csdn.net/gyymen/article/details/4962873 首先介绍一下C++中的结构.对于一个结构: struct MyStruct { int m ...

  5. (转)String,StringBuffer与StringBuilder的区别??

    String 字符串常量StringBuffer 字符串变量(线程安全)StringBuilder 字符串变量(非线程安全) 简要的说, String 类型和 StringBuffer 类型的主要性能 ...

  6. python 的sets list dictionary

    http://blog.csdn.net/joseph_happy/article/details/6717412 http://blog.csdn.net/joseph_happy/article/ ...

  7. oracle 导入导出语句

    imp USERID/PSD@SID file='D:\1.dmp' full=y statistics=none exp USERID/PSD@SID file='D:\1.dmp' tables= ...

  8. Event loop的macro task和micro task

    macrotask在一些文章中也被直接称为task. 一个宿主环境只有一个事件循环,但可以有多个任务队列.宏任务队列(macro task)与微任务队列(micro task)就是其中之二. 每次事件 ...

  9. [学习笔记]Tarjan&&欧拉回路

    本篇并不适合初学者阅读. SCC: 1.Tarjan缩点:x回溯前,dfn[x]==low[x]则缩点. 注意: ①sta,in[]标记. ②缩点之后连边可能有重边. 2.应用: SCC应用范围还是很 ...

  10. 实际上ECMAScript中并没有对类的定义

    首先,我们用一个经典例子来简单阐述一下ECMAScript中的继承机制. 在几何学上,实质上几何形状只有两种,即椭圆形(是圆形的)和多边形(具有一定数量的边).圆是椭圆的一种,它只有一个焦点.三角形. ...