iT邦幫忙

DAY 2
13

如何提升系統設計品質 - 技術與工具以.NET為例系列 第 2

[如何提升系統品質-Day2]重構– UI, Business logic, Data access概念分開

程式寫的好與寫的不好,其實一直很難去定義出來。雖然可以透過一些Quality attribute以及相關的KPI來決定所謂的品質,但卻很難去解釋怎麼樣的寫法,會比較好維護,是比較好的架構。

而提升系統品質一個很重要的過程:重構。

基本上說到重構,一開始就該帶入測試,所以,在這邊我還是得強調,重構得先透過測試來保護,才能證明自己重構完的程式,仍如同原本預期的結果運作。至於測試的部分,會在後續的文章中,慢慢地帶出來觀念、步驟、使用的工具,以及導入測試可能會碰到的困難。

[如何提升系統品質]系列文章連結
範例
我們切入點,先用個很單純的例子,輸入帳號、密碼,然後驗證。

先舉出原型版的寫法,也就是最常見的糾結版:

上面是常在forum或一般業界沒有規定系統架構下,很常見到的程式寫法。(其實已經算很OK了,至少命名OK,且還使用parameter來防止SQL injection)。

大家可以看到,按了Verify的按鈕之後,把所有要處理的程式寫在一起,包括了Business logic, Data access, 以及UI的呈現控制。
概念上就像是下圖:

壞處:不管是DB table欄位的調整,connection string的調整,business logic的調整,UI的調整,這個方法都會被影響到,這一支.aspx.cs都要修改,而且同樣的Data access以及同樣的Business logic,在其他地方要用,就得再重寫一次。

重構
步驟一:
我們先將Data access以及Business logic,跟根據資料回傳結果以及商業邏輯判斷後UI呈現的部分選起來,按滑鼠右鍵,選『重構』,『擷取方法』,然後給它個有意義的名字,代表要處理的事情。


這樣重構完的結果,其實只是把一堆事情封裝成一個方法而已,這樣並沒有代表太大的意義。

步驟二:
我們接著要把UI呈現控制的部分,從VerifyPasswordById的方法中抽離出來,並微調一下我們的方法,改為回傳一個我們透過Enum自訂的狀態。讓UI的呈現控制,根據這個狀態來決定怎麼處理呈現的控制。如此一來,Business logic與Data access的部分,可以與UI處理隔離開來。這代表著,UI要怎麼處理,UI未來要怎麼變化,都跟Business logic與Data access無關了。


如此一來,我們的程式架構就會變成:

步驟三:
既然我們的UI已經可以獨立開來,未來修改UI都不會影響到Business logic跟Data Access的部分,那我們接著就用同樣的方式把Business logic與Data access隔開吧。

我們一樣微調一下重構後的新方法,改回傳DataTable,如此就不會影響到我們原本的Business logic的程式。


重構的第一版,我們將UI, Business logic, Data access的程式獨立開來,這樣子可以達到最基本的關注點分離,只要input參數、output type沒變,各自內容怎麼改變,對彼此都不會有任何影響

/// <summary>
/// 驗證密碼是否OK
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
/// <history>
/// 1.使用parameter防止SQL injection,但所有的UI呈現、商業邏輯、資料存取相關的程式,都寫在Button的Click裡面。
/// 2.將Business logic與Data Access,透過重構->擷取方法,抽成一個method,微調成return 驗證後的狀態,讓UI呈現與Buiness logic分開。
/// </history>
protected void Verify_Click(object sender, EventArgs e)
{
    string id = this.Id.Text;
    string password = this.Password.Text;

    var status = VerifyPasswordById(id, password);

    string result = string.Empty;
    switch (status)
    {

        case VerifyStatus.Passed:
        case VerifyStatus.Failed:
            result = status.ToString();
            break;
        case VerifyStatus.NoExist:
            result = "帳號或密碼輸入錯誤";
            break;
        case VerifyStatus.None:
        default:
            break;
    }
    this.Result.Text = result;
}

private enum VerifyStatus
{
    None = 0,
    Passed,
    Failed,
    NoExist
}

/// <summary>
/// 
/// </summary>
/// <param name="id"></param>
/// <param name="password"></param>
/// <returns></returns>
/// <history>
/// 1.Business logic獨立完成,只與Data access的QueryPasswordById()有相依性。Business logic並不知道,也不用知道被什麼UI呼叫。
/// </history>
private VerifyStatus VerifyPasswordById(string id, string password)
{
    DataTable dt = QueryPasswordById(id, password);
    if (dt.Rows.Count > 0)
    {
        if (password == dt.Rows[0]["Password"].ToString())
        {                
            return VerifyStatus.Passed;
        }
        else
        {                
            return VerifyStatus.Failed;
        }
    }
    else
    {            
        return VerifyStatus.NoExist;
    }
}

/// <summary>
/// 
/// </summary>
/// <param name="id"></param>
/// <param name="password"></param>
/// <returns></returns>
/// <history>
/// 1.Data access獨立完成,Data access並不知道,也不用知道被什麼Business logic呼叫
/// </history>
private DataTable QueryPasswordById(string id, string password)
{
    DataTable dt = new DataTable();

    string connectionString = @"myConnectionString";
    using (SqlConnection cn = new SqlConnection(connectionString))
    {
        cn.Open();
        string sqlStatement = @"Select Password From SomeTable Where ID=@id ";
        SqlCommand sqlCommand = new SqlCommand(sqlStatement, cn);
        sqlCommand.Parameters.AddWithValue("@id", id);
        SqlDataAdapter adapter = new SqlDataAdapter(sqlCommand);

        adapter.Fill(dt);
    }
    return dt;
}

結論
如果您以前撰寫的程式,要維護前人撰寫的程式,還在原型的糾結版時,請至少依據這樣簡單的方式,先把程式的思緒釐清出來,光可維護性就可以提升很多。當然,這還只是第一版,很多讀者的能力早就不知道在第幾版了,敬請期待後面我們一路的重構下去,會變成什麼模樣吧。

[註]密碼的驗證,基本上不會長的像這樣,而是將input的密碼加上hash處理,去跟DB已經hash過的密碼值比對。不過這邊我就只是簡單的示意,到更後面的例子,怎麼驗證這件事,就更不重要了。


上一篇
[如何提升系統品質-Day1]命名的重要性
下一篇
[如何提升系統品質-Day3]重構– DRY & Top-Down思考方式(1)
系列文
如何提升系統設計品質 - 技術與工具以.NET為例30
0
kradark
iT邦好手 1 級 ‧ 2011-10-12 00:04:53

這樣後人的確容易看多了,
如果大大code看不清楚的,可以按ctrl加滾輪向上。

0
食夢黑貘
iT邦研究生 4 級 ‧ 2011-10-17 14:49:28

事實上現在只有在專案是這樣做, 但若一套用在 Custom Based 的網站, 很多登入面的邏輯又會差很多...

就是91 iT邦研究生 4 級 ‧ 2011-10-17 19:55:41 檢舉

就得用到後面的interface介紹。

0
sl1015
iT邦新手 5 級 ‧ 2012-07-08 05:03:55

請問目前的圖片都變成dotblogs的logo 可以回復一下原本的內容嗎

就是91 iT邦研究生 4 級 ‧ 2012-09-03 16:05:13 檢舉

抱歉,可能得請您參考一下http://www.dotblogs.com.tw/hatelove/archive/2011/05/11/asp.net-refactoring-separate-ui-service-data.access.aspx
看來是圖檔不給外連了,抱歉。

我要留言

立即登入留言