標籤:blog http io os 使用 ar for 檔案 資料
以前上學的時候,聽到“一個學生在畢業後剛剛開始編程的頭幾年中,寫出的代碼多半是垃圾”這樣的說法,均不屑一顧。現在工作一年多了,越發感覺自己代碼中疏漏處甚多,故近來常做亡羊補牢的重構之舉。拿自己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,它們之間的差異就是我很希望自己能通過Regex跨越的鴻溝(一個一個手工去改的話實在太累了,一百多處啊,呵呵)。需要注意的是,查詢語句中可能出現的參數個數是不定的,每個檔案中可能包含的方法調用的數量也是不定的。本想寫一個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的應用)