維's profileIT : 是工作還是嗜好?PhotosBlogListsMore Tools Help

Blog


    August 10

    我以前的痛苦,你也擁有一樣的痛苦嗎?

    記得筆者在以前使用Delphi/C++/Java開發軟體時,經常需要撰寫許多的輸入驗證碼,例如當使用者輸入身分證字號,或是輸入數值時,都需要根據企業邏輯來檢查使用者輸入的資料是否正確,通常許多的驗證邏輯程式碼幾乎都是一樣的,但是對於不同的程式語言,不同的開發環境都可能需要不斷重寫。更麻煩的是,由於一個團隊擁有許多的開發人員,因此不同的開發人員可能會使用不同的程式碼,不同的驗證邏輯,並且分散在整個應用程式不同的地方進行資料驗證的工作。這造成了許多的困擾,例如當驗證邏輯需要修改或是更正時,可能忘記改某些地方,或是由於開發人員撰寫的方式不同,而讓一些臭蟲因此而出現。雖然後來許多的開發人員把這些驗證程式碼封裝成函式庫統一讓開發人員使用,或是封裝成元件讓開發人員使用,例如現在.NET/Java都提供了一些簡單,常用的Validation元件,但是驗證程式碼分散在整個應用程式不同的地方的問題仍然無法得到解決,而由這個現象延伸出的問題也一樣存在。那麼我們有什麼好方法可以解決嗎?

    在回答之前也許我們應該想想這些驗證程式碼的目的到底是什麼?在這些驗證程式碼中除了一般檢查使用者輸入的字元,格式,長度或是語意之外,最重要的驗證工作應該是使用者的輸入應該符合應用程式的企業邏輯規則,不是嗎?

    因此如果我們能夠把這些驗證程式碼進行的工作定義在企業邏輯模型之中,那麼不管日後開發人員使用什麼程式語言,或是在應用程式的什麼地方,開發人員只需要在需要進行驗證程式碼的工作時從企業邏輯模型中最出定義好的驗證邏輯,再根據這些驗證邏輯來驗證使用者輸入的資料。如此一來一旦驗證邏輯有改變或是更新,我們只需要更新定義在企業邏輯模型之中的驗證邏輯,那麼由於整個應用程式都是從企業邏輯模型中取得驗證邏輯,那麼我們不就解決了可能忘記更改一些分散在不同地方的問題了嗎? 更進一步的想想,如果我們能夠使用一段通用的程式碼來檢查企業邏輯規則,而不需要對每一個不同的使用者輸入撰寫不同的程式碼來檢查,那麼不是更棒嗎(如果讀者真的開發過這樣的應用程式驗證邏輯,就知道筆者說的痛苦,以及難以維護的程式碼了)?

    問題是,能夠有方法解決這長久以來的痛苦嗎?

    也許有可能,讓我們繼續往下看。

    UML中,開發人員可以使用OCL來定義類別/屬性的驗證邏輯或是約束條件,之後開發人員便可以在程式碼中藉由EcoSpace取得這些約束條件(Constraints),並且使用來驗證異動的物件是否符合這些約束條件,一旦符合約束條件才能夠更新回資料來源之中。在ECO架框中支援了這樣的機制,ECO藉由提供開發人員使用OCLECO類別/ECO屬性定義約束條件,以及在ECO架框中提供服務讓開發人員能夠在程式碼中存取這些約束條件並且執行約束條件以便驗證物件。

    在您瞭解了上面討論的觀念之後,接下來的內容中將深入的說明如何在企業邏輯模型中使用OCL來定義約束條件,並且使用來驗證物件。

    在企業邏輯模型中定義約束條件

    開發人員可以在ECO的類別設計家中藉由物件檢視器設定類別或是屬性的Constraints特性來定義約束條件。使用Constraints特性定義約束條件時開發人員必須瞭解下面的觀念:

    n          Constraints特性可以定義任何數目的約束條件

    n          約束條件是使用OCL來撰寫的

    n          開發人員使用Constraints特性定義約束條件後,如果使用者違反了約束條件那麼仍然可以更新物件回資料來源。因此檢查物件是否違反約束條件是開發人員的責任,開發人員必須確定物件在更新回資料來源之前沒有違反約束條件。

    現在讓我們以圖1的範例模型來說明如何定義約束條件以及如何在程式碼中檢查企業邏輯模型的約束條件。

     

    1範例模型

     

    如下圖所示,我們可以點選Joiner類別,並且在物件檢視器中定義Constraints特性:

    2 ECO設計家中為類別/屬性定義約束條件

     

    開發人員可以直接在Constraints特性中輸入OCL或是點選Constraints特性旁的按鈕啟動Constraints編輯器,點選Constraints編輯器下方的Add按鈕加入約束條件。下面的圖形加入了兩個約束條件,每一個約束條件必須輸入約束條件的名稱以及約束條件的OCL敘述,例如NameNotEmpty約束條件規定了Name屬性不可為空白,同樣的EMailNotEmpty也是類似的規定。

     

    3 啟動約束條件編輯器為Joiner類別定義約束條件

     

    最後,範例企業邏輯模型也為DevCoSeminarMaxCount屬性定義了最多參加人數的約束條件必須小於120:

     

    4 啟動約束條件編輯器為DevCoSeminar類別定義約束條件

     

    定義完Constraints的約束條件後,讓我們再解釋一下封裝約束條件的IConstraint介面。

    IConstraint介面

    ECO架框中企業邏輯模型中的約束條件是由IConstraint介面封裝,定義的,開發人員藉由ECO架框服務介面存取到IConstraint介面,再藉由執行IConstraint介面定義的約束條件來驗證物件是否符合企業邏輯。下面是IConstraint介面的定義:

        public interface IConstraint: IModelElement

        {

            IExpression Body { get; }

            IEcoConstraint EcoConstraint { get; }

        }

    IConstraint介面有兩個唯讀屬性,其中的Body即代表封裝的約束條件,而EcoConstraint則是專屬於ECO的額外資訊。Body屬性型態是IExpression介面,而IExpression介面的定義如下:

        public interface IExpression

        {

            string Language { get; }

            string Body { get; }

        }

    IExpression介面也有兩個唯讀屬性,其中的Language屬性會回傳約束條件使用的語言,通常都是”OCL”,代表是由OCL撰寫的約束條件。而Body屬性則是約束條件本身。例如前面的『NameNotEmpty』約束條件,它的IConstraint. Body. Body屬性便是self.Name.Length > 0

    IEcoConstraint介面也定義了兩個唯讀屬性,IsAutoGenerated屬性回傳這個約束條件是否是自動產生的,而Description則是約束條件的敘述文字:

        public interface IEcoConstraint

        {

            bool IsAutoGenerated { get; }

            string Description { get; }

        }

    因此在ECO程式碼中,要根據約束條件來驗證物件,它的執行步驟如下:

    n          ECO企業邏輯物件中取得所有的約束條件的IConstraint介面物件

    n          一一的使用IOclService執行這些約束條件並且判斷約束條件是否符合

    n          如果ECO企業邏輯物件符合約束條件才能夠更新回資料來源

    n          如果ECO企業邏輯物件不符合約束條件,那麼就通知使用者進行後續的處理

    瞭解了IConstraint介面以及處理約束條件的步驟之後,接下來我們就可以使用範例和程式碼來展示如何使用約束條件了。

    在程式碼中驗證物件的約束條件

    要如何使用程式碼來檢查定義在企業邏輯模型中的約束條件呢? 其實非常的簡單的,本書在第4章已經介紹了ECO的企業邏輯模型的靜態通用機制,因此我們可以使用下面的步驟來完成檢查企業邏輯模型中的約束條件:

    n          在更新異動物件回資料來源之前,藉由使用IDirtyListService服務介面取得使用者所有異動的物件

    n          藉由ECO的企業邏輯模型的靜態通用機制取得定義在企業邏輯模型中的約束條件

    n          藉由IOclService服務介面一一的執行約束條件並且檢查是否違反約束條件

    n          如果沒有違反任何的約束條件就更新物件回資料來源中

    n          如果違反約束條件的話,保留違反約束條件的物件,要求使用者進一步的處理

    瞭解了進行約束條件檢查的步驟之後,讓我們看一些實際的程式碼來驗證上面的步驟。

    在下面的片段程式碼中是準備呼叫EcoSpaceUpdateDatabase方法把使用者異動的物件更新回資料來源中,但是在這之前,它先呼叫了DoCheckObjectConstraints方法以便確定所有異動的物件是符合企業邏輯模型中的約束條件,否則DoCheckObjectConstraints會產生一個例外錯誤。

    001    procedure TWinForm.Button2_Click1(sender: System.Object; e: System.EventArgs);

    002    begin

    003      try

    004        DoCheckObjectConstraints;

    005        EcoSpace.UpdateDatabase;

    006      except on E: Exception do

    007        MessageBox.Show ('物件違反約束條件' + e.Message +'無法更新回資料庫',

    008                          MessageBoxButtons.OKCancel, MessageBoxIcon.Asterisk);

    009      end;

    end;

    DoCheckObjectConstraints方法中先在007行取得IDirtyListService介面,接著進入010行的for迴圈一一的取出每一個異動的物件並且呼叫DoHandleDirtyObject來檢查每一個異動物件的約束條件是否符合。

    001    procedure TWinForm.DoCheckObjectConstraints;

    002    var

    003      dol : IDirtyListService;

    004      Iobj : IObject;

    005    begin

    //藉由IdirtyListServices取得異動的物件

    007      dol := EcoSpace.DirtyListService;

    008      if (dol.HasDirtyObjects) then

    009      begin

    010        for Iobj in dol.AllDirtyObjects do

    011        begin

    012          DoHandleDirtyObject(Iobj);

    013        end;

    014      end;

    015    end;

    最後的DoHandleDirtyObject方法在008行取得IOclService介面,在009行進入for迴圈,藉由IObject 介面的UmlType.Constraints取得定義在這個物件類別中的所有約束條件,接著在012行呼叫IOclServiceEvaluate方法來執行/評量約束條件。如果有任何物件違反了任何的約束條件就產生一個例外錯誤。

    001    procedure TWinForm.DoHandleDirtyObject(Iobj : IObject);

    002    var

    003      IOcl : IOclService;

    004      ICnt : IConstraint;

    005      iCount: Integer;

    006      bResult : boolean;

    007    begin

    008      IOcl := EcoSpace.OclService;

    009      for iCount := 0 to Iobj.UmlType.Constraints.Count - 1 do

    010      begin

    011        ICnt := Iobj.UmlType.Constraints.Item[iCount];

    012        bResult := boolean(IOcl.Evaluate(Iobj as IElement,iCnt.Body.Body).AsObject);

    013        if (not bResult) then

    014          raise Exception.Create(ICnt.Name);

    015      end;

    016    end;

    現在如果我們執行上面的範例程式碼並且搭配圖3和圖4定義的約束條件,那麼我們可以看到當執行下面的範例程式並且在DevCoSeminar物件的MaxCount屬性中輸入超過120的數值:

     

    5 修改DevCoSeminar物件的MaxCount屬性值

     

    那麼在更新物件回資料來源之前就會看到下面的例外錯誤,代表上面的程式碼果然檢查出了物件違反了約束條件。

    6 程式碼檢查出使用者輸入的資料違反了約束條件

     

    如果我們接著又如下圖對Joiner類別進行異動並且試著把EMail屬性清為空白:

     

    7 程式碼檢查出使用者輸入的資料違反了約束條件

     

    那麼在更新物件回資料來源之前我們又可以看到範例程式檢查出Joiner物件違反了EmailNotEmpty的約束條件。

     從上面的程式碼看到了什麼? 如果您有注意的話會發現只需要使用相同的一份程式碼,在一個程序/函式中就可以檢查任何物件的任何約束條件。這和以前許多開發人員需要使用不同的程式碼,在不同的地方檢查不同的使用者輸入資料是完全不一樣的,這可以讓程式碼更容易維護,也不容易產生臭蟲,或是因為疏失而造成的錯誤。

    如果我們進一步的結合.NET/開發工具提供的Validation元件和約束條件,那麼我相信大多數的程式碼驗證工作都可以順利,有效率的完成。

    您注意到,感覺到了使用模型和約束條件的好處了嗎?

    Comments (7)

    Please wait...
    Sorry, the comment you entered is too long. Please shorten it.
    You didn't enter anything. Please try again.
    Sorry, we can't add your comment right now. Please try again later.
    To add a comment, you need permission from your parent. Ask for permission
    Your parent has turned off comments.
    Sorry, we can't delete your comment right now. Please try again later.
    You've exceeded the maximum number of comments that can be left in one day. Please try again in 24 hours.
    Your account has had the ability to leave comments disabled because our systems indicate that you may be spamming other users. If you believe that your account has been disabled in error please contact Windows Live support.
    Complete the security check below to finish leaving your comment.
    The characters you type in the security check must match the characters in the picture or audio.

    To add a comment, sign in with your Windows Live ID (if you use Hotmail, Messenger, or Xbox LIVE, you have a Windows Live ID). Sign in


    Don't have a Windows Live ID? Sign up

    名字wrote:
    一個根本的問題,如果我使用Procedure Oriented的觀念在Object Oriented語言上(也許是Delphi)設計程式,所Compile出來的執行檔,跟Object Oriented觀念在Object Oriented語言上設計程式所Compile出來的執行檔有何差異,我知道這根Performance有關,但我比較想暸一下OO Compiler比較細部的資訊,望李維大哥,肯指點一二或介紹幾本書.Orz
    Aug. 21
    James 縹緲wrote:
    >Ah, 可惜我目前日常的工作很多都是在回答安裝, 註冊等的問題.  愈做愈沒興趣了
    這樣就不太妙了,希望 DevCo 確定花落誰家後,可以盡早安定下來,還有最重要的是幫台灣 DevCo 多擴充幾個人力啦!
    Aug. 17
    維 李wrote:
    >像哪些基本使用介绍的文章,应该找DevCo的新人写,
    我也算是DevCo的新人啊,
     
    >Gordon作为专业人士,没必要在功能简介,安装上浪费时间.
    Ah, 可惜我目前日常的工作很多都是在回答安裝, 註冊等的問題.  愈做愈沒興趣了.
    Aug. 17
    Picture of Anonymous
    (没有名称) wrote:

     终于又见Gordon开始写有技术深度的文章了,上一篇已经记不起是什么时候了.像哪些基本使用介绍的文章,应该找DevCo的新人写,Gordon作为专业人士,没必要在功能简介,安装上浪费时间.

    Aug. 16
    Picture of Anonymous
    (没有名称) wrote:
    确实不错,希望大师的 ECO 书早日出版
    Aug. 14
    Picture of Anonymous
    (no name) wrote:
    談以往或未來的痛苦,還不如談現在的痛苦,那就是使用了Delphi 2005/2006後的痛苦,我希望不管是Borland或DevCo能把Delphi盡快改好一點,讓使用Delphi.net的這些設計者能在穩定與有效率(至少不用常常一段時間把記憶體用盡後,要重新開關一次)的開發工具中,準時完成專案,讓憔悴的心靈得以安然入睡。
    Aug. 14
    Picture of Anonymous
    思坦 wrote:
    很好的IDEA ,這個IDEA 不錯,可以減少BUG.很創新的應用。
    可以考慮加入ECO BOOK 中,並寫得更詳細些。感恩!
    Aug. 10

    Trackbacks (1)

    The trackback URL for this entry is:
    http://gordonliwei.spaces.live.com/blog/cns!CCE1F10BD8108687!1096.trak
    Weblogs that reference this entry