2008年9月28日 星期日

[Design-Pattern] Open-closed Principle(OCP)

Robert C. Martin 曾經說道 "Software entities should be open for extension, but closed for modification."

在設計一個模組時,應該使該模組之後可以在不被修改的前提下被擴充。i.e.不必修改程式碼的情況下使模組改變行為。

這樣的假設之下,乍看之下是不可能的,但如果借用 interface 以及 abstract class 的幫助之下,這樣的情況就有可能會實現。利用 interface 及 abstract class 定義出 method 的 signature 來作為系統中的抽象層,因此這些signature 制定了可能的擴充,i.e.在設計系統之初,就必須為系統的可擴充性加以構想,這也就使得系統中的抽象層不必再修改,i.e.關閉修改之窗。

藉由 implement 抽象層的 object 來實現具體行為,並藉由改變實體行為的模式,來達到擴充的功能,i.e.開啟擴充之窗。

如此就可以滿足"Open-closed Principle",並且具有兩大優點:

1.藉由擴充來位系統提供新的行為,以滿足要求。

2.關閉修改模組可以使系統擁有原有的穩定性。

系統在初期設計時的確很難去思考到 OCP,因為一開始系統在分析時遇到的 class 數量可能不是很多,但是隨著系統的逐步增長, class 的數量可能就會越來越多,當 class 之間的關係越來越複雜的情況下,如果不遵守 OCP,最後系統就會是一團無法擴充與維護的垃圾,因為系統中的 class 之間有著極高的 coupling 與極低的 cohesion,這會造成牽一 class 而動全系統的悲劇。

所以在系統成長到一定程度時,programmer 就要開始思考 class 之間的關聯是否會太過於緊密而失去彈性。

[Object-Oriented] Interface

在JAVA中,interface的使用對於有學過JAVA的人來說,我想一定是不陌生的。但是,確實該如何好好的使用interface的強大功能,我想沒有經驗的programmer應該是陌生的!

有相當多的書籍中,皆有提到關於Polymorphism的強大之處,但是想要真正使用到,對於沒有經驗的人來說,有點困難之處。

Polymorphism與interface之間的關連,就像是Mechanism與Policy一樣。Polymorphism是一種Mechanism,而interface就是一種實現Polymorphism的Policy。

舉個簡單的例子,假設我們要採用"防盜"的機制(Mechanism),那我們可以選擇的策略(Policy)就有很多種,我們可以選擇加裝更好的鎖來達到防盜的目的,或是我們可以請保全公司來加裝保全系統等,都是可以達到我們當初所設定的目標。所以 Mechanism 是一種目標,而 Policy 則是達成目標所可以選擇的方法。

回想起在某一本書中看到,interface定義一種data type,某個class implements interface就會擁有該種data type。然而,interface的使用,不僅僅可以讓程式變得簡潔有力,並且達到限制基本功能的能力,還可以增加系統的彈性。就像對於學生與老師之間的關連,老師接受教導學生,但是學生本身卻有上千種不同特質,所以我們可以利用interface來定義所有的學生,並且要求他們具有基本的能力,諸如:選課、翹課等。

所有想要被老師所教導的各種學生,都必須實做出學生interface所約束的基本能力,i.e.選課與翹課。老師接受一個實做學生interface的object,就可以對他的基本能力做呼叫,這樣就算是一種Polymorphism。

也就是,見人說人話,見鬼說鬼話。

interface使得系統中的Extensibility, Flexibility, Pluggability 獲得保證

2008年9月12日 星期五

[Agile Method] Pair-Programming

Pair-programming 顧名思義就是 pair-thinking 或是 pair-doing something

在台灣的公司,幾乎看不到這樣的情形,因為台灣的文化與思想都是一個人獨立思考、獨立作業。

但是,pair-programming 確實是有他的好處,當自己真的去實踐才會有體會!

在我的碩士生涯中,雖然只有短短的兩年,不過我已經自己接下兩個 CASE,一方面是為了賺取生活費,但是目的卻是在於學習。

CASE 通常都是一個人在自行開發的,我也不例外。我在實驗室中被分配到開發工業研究技術院(Industrial Technology Research Institute of Taiwan,以下簡稱 ITRI)的計畫,不過很特別的是我的指導教授 - 陳振炎教授,他特別提到說,我與我的開發夥伴 - Brian 必須一起作同一件事,也就是實行 pair-programming。

所謂的 pair-programming 就是我跟 Brian 要一起寫同一支程式,但不是各寫各的,是要一起寫,所以就是一個人負責 key-in codes 而另一個人負責檢查邏輯。現在市面的不論是付費或是免費的 IDE 都已經具備有程式 syntax 的檢查,但是邏輯的檢查卻是只能靠 programmer 自己去解決,而 pair-programming 就是在解決一個思考漏洞的問題。

實際上這很有趣,因為我們從來沒有這樣的經驗,不過我卻也深深地體會到,一個人的能力真的很有限!因為我跟 Brian 之間的實力在伯仲之間,所以我們的思考與想法都很接近,但是人與人之間一定會存有衝突!那怎麼辦?好在我們兩在進入碩士前就彼此認識,所以在溝通上我們都盡量以雙方意見的結合為最終的結果,所以一直到現在我們都很順利的在開發。

反觀我所接的 CASE,因為只有我一個人開發一個部份,所以所有的問題我只能自己想辦法自己解決,除非我真的想不出來,我才會去請教朋友。

但是問題出現在實際面上,一個企業不可能僱用兩個人去作同一件事,這對於台灣的企業來說是不符合成本的,所以現階段要讓 pair-programming 在台灣實現事很有困難的!

2008年7月29日 星期二

[Struts] StrutsTestCase for JUnit

Introduction

對於寫過test case 的 programmer 來說,JUnit 應該是不陌生,JUnit 是被大家公認用來執行java測試最好的framework,但是對於像Struts這種以MVC為宗旨的framework,我們又該如何去執行測試呢?StrutsTestCase for JUnit[1] 就是一個用來達成我們目的的solution。從字面上就可以了解,StrutsTestCase其實就是將JUnit延伸到Struts framework可以使用的library。所以,大致上對於測試的方式是大同小異的,不過對於StrutsTestCase[1]來說,還是有一些不一樣之處。

下圖一是官方網站的簡介,此網站對於StrutsTestCase[1]有很詳細的說明。

clip_image003

圖一 官方網站的說明

圖一中的紅圈處就可以下載該library。點選後就會進入SourceForge.net的網站,如下圖二。

clip_image005

圖二 SourceForge.net的下載點

接個點選Download字樣,進入如下圖三的畫面。

clip_image007

圖三 SourceForge.net的下載點(2)

畫面中請下載您所需要的版本,通常是選擇下載最新版本為主,圖中最後一個載點是在檔名後方加上 -src代表是原始碼的版本,若您有需要訂做自己適用的StrutsTestCase,您也可以下載原始碼版本。

下載完的壓縮檔案解開後的資料夾中會包含一個strutstest-x.jar(其中x會依照您所下載的版本不同而有所改變)。若是一般的 Java Application 要使用,請設定 classpath 位置;若是需要在 Web Server 上面的 Web Application 使用,請放置該 Web Application 的 WEB-INF / lib 之下。

Example

首先假設您有一個action如下:

public class LoginAction extends Action {

    public ActionForward excute(...) {

        String username = ((LoginForm)form).getUsername();

        String password = ((LoginForm)form).getPassword();

        String role = “user”;

        if (username.equals(“adm”) && password.equals(“test”)) {

            role = “adm”;

        }

        request.getSession().setAttribute(“role”,role);

        request.getSession().setAttribute(“username”,username);

    }

}

上述程式碼中,我們假設您已經有一個LoginForm class。上述的action您在struts-config.xml中設定如下:

<action path=”/login” type=”LoginAction” />

接下來您就可以根據您的action來開發strutstestcases測試,開發strutstestcase步驟如下:

(1) 您必須建立新的class,我們命名為LoginActionTest並且extends MockStrutsTestCase:

public class LoginActionTest extends MockStrutsTestCase {

}

(2) 如果您是在 eclipse 環境下執行測試,請您務必加入以下的程式碼:

public class LoginActionTest extends MockStrutsTestCase {

    public void setUp() {

        super.setUp();

        setContextDirectory(new File(“WebContent”)); //註一

        this.setConfigFile(“/WEB-INF/struts-config.xml”);

    }

}

註一:其中WebContent要根據您在eclipse中建立的資料夾而定,依照我所設定的資料夾如下圖四,所以我們採用WebContent:

clip_image013

圖四 Eclipse專案的目錄結構

(3) 接著我們就要開始來正式的撰寫我們的test case,所有的test case method都必須以test開頭,例如:testLogin。

public class LoginActionTest extends MockStrutsTestCase {

    public void setUp() {

        super.setUp();

        setContextDirectory(new File(“WebContent”));

        this.setConfigFile(“/WEB-INF/struts-config.xml”);

    }

    public void testLogin() {}

}

(4) 有了test case method,我們就要開始撰寫內容了。首先,我們要先告知strutstestcase我們要測試的路徑,也就是瀏覽器如何存取該action的方法,不過,我們只需要設定在struts-config.xml中的路徑即可,無須增加IP位址,因為MockStrutsTestCase是屬於Mock Test測試(也就是非in-container測試,所謂的container就是由tomcat這類的server來啟動,所以in-container就是會啟動tomcat server來進行測試,Mock Test就是另一種不用啟動tomcat server的離線測試),先前我們已經將LoginAction class在struts-config.xml中設定的路徑為/login,所以我們可以透過MockStrutsTestCase所提供的setRequestPathInfo() method來設定action的存取位置。

public class LoginActionTest extends MockStrutsTestCase {

    public void setUp() {

        super.setUp();

        setContextDirectory(new File(“WebContent”));

        this.setConfigFile(“/WEB-INF/struts-config.xml”);

    }

    public void testLogin() {

        super.setRequestPathInfo(“/login”);

    }

}

NOTE: 呼叫superclass的methods時,可以使用super關鍵字,本人建議所有呼叫superclass的methods時,都加上super關鍵字,以方便未來觀看程式碼時就可以一目了然,另外,若是呼叫同一個method時,請您也加上this來方便日後的維護。這並沒有硬性的規定,所以的關鍵字都不加上去,程式碼依舊可以執行,這只是習慣性的問題而已。

(5) 再來就是告知該action我們要傳入的資料,此資料就是模擬使用者會傳入哪些資料給action。切記,雖然我們的action是接收一個ActionForm物件,不過在MockStrutsTestCase中是用類似URL GET method(../login.do?username=adm&…)的方式給予。我們並不能給予ActionForm物件。所以我們要呼叫MockStrutsTestCase的addRequestParameter() method,並將我們要給予的參數名稱與參數值。

public class LoginActionTest extends MockStrutsTestCase {

    public void setUp() {

        super.setUp();

        setContextDirectory(new File(“WebContent”));

        this.setConfigFile(“/WEB-INF/struts-config.xml”);

    }

    public void testLogin() {

        super.setRequestPathInfo(“/login”);

        super.addRequestParameter(“username”,”adm”);

        super.addRequestParameter(“password”,”test”);

    }

}

(6) 設定好路徑與參數後,我們就要告知MockStrutsTestCase,該測試可以啟動了,所以我們就呼叫他的actionPerform() method來正式的執行該action。

public class LoginActionTest extends MockStrutsTestCase {

    public void setUp() {

        super.setUp();

        setContextDirectory(new File(“WebContent”));

        this.setConfigFile(“/WEB-INF/struts-config.xml”);

    }

    public void testLogin() {

        super.setRequestPathInfo(“/login”);

        super.addRequestParameter(“username”,”adm”);

        super.addRequestParameter(“password”,”test”);

        super.actionPerform();

    }

}

(7) Action執行完後,我們就可以針對action執行後的結果作測試,因為我們的action是將結果儲存在session中,所以我們就要從session中取出結果,並且呼叫JUnit的測試method來檢驗結果。

public class LoginActionTest extends MockStrutsTestCase {

    public void setUp() {

        super.setUp();

        setContextDirectory(new File(“WebContent”));

        this.setConfigFile(“/WEB-INF/struts-config.xml”);

    }

    public void testLogin() {

        super.setRequestPathInfo(“/login”);

        super.addRequestParameter(“username”,”adm”);

        super.addRequestParameter(“password”,”test”);

        super.actionPerform();

        String role = super.getSession().getAttribute(“role”);

        super.assertEquals(role,”adm”);

    }

}

NOTE:若action是將結果儲存在request中,您就要改呼叫super.getRequest() method來進行測試。

References

[1] StrutsTestCase官方網站 http://strutstestcase.sourceforge.net/

2008年6月5日 星期四

[SQL] 搜尋金額的範圍內容

資料庫真是一項好用的東西,尤其是精通 SQL 語法的話,對於 programmer 來說,真是如虎添翼阿~~

回想起大學時代修資料庫系統的課,老師第一堂課就說:所有的程式,資料的運算如果能用 SQL 解決,就一定要用 SQL 解決,不然,你花大錢買他幹嘛!!如果只是用資料庫來存資料,取資料都只有簡單的 CRUD (Create,Replace,Update and Delete),那跟直存成檔案有啥差別!!

這句話讓我深深的體會在這次的 CASE 中。因為我這次接的 CASE 是關於撥款的系統,很多資料的運算要處哩,如果全部都讓我寫程式自己作,那效能一定會是最令人詬病的!!

回到我們的主題,光看這主題一定不能了解我要解的問題??

precondition : the database is MS SQL Server 2K

那我們就來描述一下我所遇到的問題:

如果使用者給你的 table 長這樣

Table A

range ....

------------------------------------------

0~100 ...

101~200...

...

但是使用者希望他是輸入 51,然後你就要把第一筆的資料撈給他!!

剛開始我就想,哇!! 好難~~但是,問題就是要解,那要怎樣解勒??

一步步來,首先,我們要先試試看能不能把 0~100 這種字串透過 SQL 來拆解,透過線上的文件中我們可以由 String 資料型態的相關 function 值中發現一個 function - CHARINDEX(pattern, str)

於是我們就寫下

SELECT CHARINDEX('~','0~100')

結果的確是我們要的結果,CHARINDEX function 是用來將特定的 pattern 來給與 str 中的位置,由上面的測試結果為 2 就可以知道這 function 對我們是有用的~~

接著就是要取出部分的字串,這項要求很熟悉,

沒錯要用 SUBSTRING(str,begin,len),於是我們就用它來先取出左邊的範圍吧!!

DECLARE @x AS VARCHAR

SET @x='0~100'

SELECT SUBSTRING(@x, 1, CHARINDEX('~', @x)-1)

上面的作法是為了不要每次都打 '0~100' ,所以宣告一個變數來儲存~~

上面的 SQL 語法得到的結果會是 0 ,恭喜~~我們成功了左邊的範圍。

那右邊的範圍可以如法炮製嗎?? 好像還差一個東西:我們要如何知道字串的長度勒??

答案是 LEN(str) function

所以我們來整合一下吧~~

DECLARE @x AS VARCHAR

SET @x='0~100'

SELECT SUBSTRING(@x, 1, CHARINDEX('~', @x)-1) AS leftRange,

         SUBSTRING(@x, CHARINDEX('~', @x)+1, LEN(@x)) AS rightRange

完成了一半囉!!

接下來就是要將剛剛的 SQL 語法套用到我們的問題中。

SELECT SUBSTRING(range, 1, CHARINDEX('~', range)-1) AS leftRange,

SUBSTRING(range,CHARINDEX('~',range)+1,LEN(range)) AS rightRange

FROM A

結果就是

leftRange    rightRange

-----------------------------------

0                100

101             200

...

的確這樣我們就可以拆解出範圍囉~~

如果們把這樣的結果當作 subquery,對這樣的結果作進一步的包裝,那我們就可以將使用者的要求達成囉~~ 下面假這使用者輸入的內容為 @input

DECLARE @input AS VARCHAR

SET @input='51'

SELECT T.*

FROM (

  SELECT *, SUBSTRING(range, 1, CHARINDEX('~', range)-1) AS leftRange, SUBSTRING(range,CHARINDEX('~',range)+1,LEN(range)) AS rightRange  FROM A

) AS T WHERE @input BETWEEN T.leftRange AND T.rightRange

問題得到解決了~~

回到一開始,如果這樣的問題要用程式來解決的話,勢必要花掉很多的時間,所以能用 SQL 幫我們運算,何樂而不為呢~~

以上的經驗分享之~~