寫程式宜異中求同

摘要:寫程式宜異中求同

在寫程式時, 如果能將不同的 code 歸納成相同的, 程式碼便可以大幅地縮短, 例如若程式寫成

pageA:
int tax = 1000 * .05; //計算營業稅

pageB:
int total = Convert.ToInt32( txtTotal.Text);
int tax = total * .05; //計算營業稅

通常在寫 pageA,需要計算營業稅時, 您會簡單地寫成  int tax = 1000 * .05; 稍後在寫 pageB 時, 如果也需要計算營業稅, 由於您記得稍早在寫 pageA 時已經寫過了, 所以很直覺地就會將那段程式碼 copy 到 pageB 之後再小小改動(例如將金額由 1000 改為 total  變數)。 程式碼有可能像我舉例的只有一行, 但通常您會 copy 的比較可能是一小段程式碼, 可能是 10 行或 100 行, copy 之後, 再審視這段 code 哪些地方需要修改。

這麼做, 有什麼缺點呢?

  • 系統裡相似的程式碼不斷地出現, 無法有效地縮短程式碼
  • 系統裡若有 5 處的程式碼相似, 您若又需要時, 就需要去尋找哪一份比較適用於新的程式, 再來進行小幅改動, 並做測試, 並無法很快地就達到原本程式就提供的功能, 也由於每次都小幅改動, 因此每份 code 都可能因此發生不同的 bug
  • 若複製多次後, 日後才發現它有 bug, 剛才舉的例子, 若日後您才發現當金額是 130 時, 稅金應該是 6.5, 四捨五入成 7元, 而上述的 code 卻會算成 6元, 此時, 您就需要費心地找出系統裡所有包含這段 code 的的部份, 並將它們一一改正, 更慘的是等您改好後, 發現改好的 code 仍有 bug 時, 您又要全部重改一次, 也由於您每次都是 copy 後再小幅改動, 因此新版的 code 也只能人工一一檢視/修改, 是很花時間的工作

為了迴避這類窘境, 我們可以將您面對的問題試著歸納成相同的事, 例如您可以先寫一支程式來計算稅金, 各程式都去呼叫它

 

class Helper {  
  public static int Tax(int total){
   return (int)(total * 0.05);
  }

}
  

//pageA 可以寫成
int tax = Helper.Tax(1000); //計算營業稅


//pageB 可以寫成
int total = Convert.ToInt32( txtTotal.Text);  
int tax = Help.Tax(total); //計算營業稅

 

日後您發現程式有問題時, 只需要回頭改 Tax(int total) 這支 method 即可, 方便多了, 對吧!! 如果 Tax() method 裡不止一行, 而是100行, 那麼您可以想像整個系統裡, 只會有一份這 100 行的 code, 而不是數份, 對吧

 

我來舉另一個 Business Object 的例子, 如果您要寫維護 Blog 文章的功能, 前後台的程式碼都會需要有列出文章清單, 與取得單篇文章的功能, 那麼您可以寫成

 

class Blog{
public DataTable Select(int pageIndex, int pageSize, string whereCondition){
...//在這裡根據 whereCondition 條件, 傳回多筆的 Blog 文章記錄
}


public BlogInfo Load(int articleID){
...//在這裡傳回單筆 blog 文章的所有欄位值
}


}

 

那麼前後台的網頁就可以經由下方的程式碼來取得 Blog 文章記錄, 不必前後台各自寫一次

Blog obj = new Blog();
DataTable data = obj.Select(0, 20, "");
this.GridView1.DataSource = data;
this.GridView1.DataBind();

或許您會說"現在用 VS.NET 2005/2008 只需要拉一拉 SqlDataSource, 就做好了, 連寫都不必寫", 但您要想的是

  • 如果 VS.NET 2003 沒提供這方式, 難道您就程式碼寫二次嗎?
  • 我在此舉的例子只是剛好可以用 VS.NET 2005 拖拉, 並不表示所有的程式都可以用拉的完成, 我主要想表達的是希望 coding 時,盡量能做到程式碼共用, 好方便維護
  • 若用拉的, 前後台其實仍要拉二次, 而日後若 Database 由SQL Server 換成 Access, Oracle,您整個系統都要重拉
  • 若日後您又被要求寫 windows form, 那麼程式就又要寫一次, 若您當初有寫 Blog class, 那麼在 windows form 裡仍可以叫用它, 不必重寫

 

我再舉一個例子, 若您的系統想方便地支援 Access and SQL Server, 那麼要如何將程式歸納成相同的事呢? 您可以設計以下的 class

abstract class DB : 裡面有ExecuteNonQuery(string sql) method
class AccessDB : 繼承 DB class
class SqlDB : 繼承 DB class

最後再寫一支 class DBFactory : 裡面有 public static DB GetDB(string dbType, string connectionString) 這個 method

將上述的 class 包成一支 dll file , 以後各案子都將它加入參考, 就大功告成了, 日後我要寫程式存取 db 時, 就可以寫成
string dbType = ... //取自 web.config
string connString = ... //取自 web.config
DB db = DBFactory.GetDB(dbType, connString);
db.ExecuteNonQuery("insert ....");

如此一來, 您只需要修改 web.config, 程式就可以支援 Access or SQL Server 了

 

我再舉一個例子, 您可能開發多個網站, 但首頁裡總是需要有一個小區塊用來顯示最新消息, 那麼,要如何將程式歸納成相同的事呢? 您可以先花時間寫好一支顯示最新消息的模組,而新網站需要它的話,就在網頁裡先新增一個 uc_HotNews.ascx 的使用者控制項(因為每個案子需要的外觀都不同,因此您可以利用 ucHotNews.ascx來設計它的版型), 裡面只放一個repeater, 根據客戶或美工想要的外觀來編排它, 最後, 您在首頁裡可以寫成

//Default.aspx.cs

protected void Page_Load(..){

HotNews obj  = new HotNews();

obj.SetLayout("~/UC/ucHotNews.ascx"); //在這裡決定版型

obj.SetConfig("~/Config/HotNews.config"); //請這模組到此 config file 讀取必要的設定,例如dbType, tableName, 呈現筆數,按何欄位排序,... 等設定

this.Controls.Add(obj); //將這 HotNews 顯示在網頁裡

}

如果您才初學程式, 或許會覺得 copy 程式碼實在是簡單明白又快速的方法, 但請您要了解程式通常在寫一次之後, 是要修改多次的,  因此, 好不好維護是比第一次寫得快不快重要得多。

或許您會覺得事先寫出一個最新消息模組, 以符合日後多個專案是很難的事, 並且覺得要將多種不同邏輯放在一支模組裡似乎也將它搞得太複雜了, 但我想表達的是希望您在 coding 時能時時想到如何歸納, 如果能異中求同, 您的 code 就會愈簡潔, 至於撰寫一個這類的模組, 如何方便日後擴充功能, 也是可以利用程式技巧克服的, 不應輕言放棄。

如果您在 coding 時能花時間將看起來不同的事情歸納成相同的事, 相信您撰寫程式的速度會愈來愈快, 品質也會愈來愈好