2009年1月27日 星期二

T10 Pair Programming的應用變化

常聽到的Pair Programing是老手帶著新手一起工作,一個人寫程式的同時另一個人在旁邊同時學習並提供意見。雖然這樣的開發方式能夠讓兩個人同時深入了解那個功能,但是在同樣的時間裡一次投入兩個人去做一個人就能做完的事卻是絕大多數的公司無法負擔的。

降低人力成本是必要的,訓練新人到能夠獨立作業的程式也很重要,在我的想法裡能夠提供這樣的作法:一個元件交給一位老手帶著新手一起進行,元件內部較困難的交給老手做,其他部分則交給新手。一般來說Flow與Action會是比較困難的部分,老手在設計分解步驟的同時再視狀況把純粹的流程或是簡單的動作交給新手撰寫。

各自開發好負責的部分後進行整合時兩人應該在一起,由老手檢視並指導新手的設計同時表達出每個Interface Method的設計想法。Unit Test需要的測試內容依需求定義與程式內的判斷條件由老手列出測試內容並交給新手測試並修正,藉由這樣的分工老手可以順便review自己所作的流程處理是否適當,新人也可以在測試同時瞭解程式的寫法是否正確。

估計一位老手應該有能力同時帶領兩位新手負責兩個不同的元件,在新手的基礎能力在一定水平的狀況下可以分派較多的東西給他們。如此一來可以加快設計的進度。

2009年1月24日 星期六

T09 動靜分離──Flow與Action的分工

在無法或不想投入足夠時間開發功能的時候,一個Interface Method的實作極可能會成為直接完成功能的型態──所有大大小小要做的步驟都被堆在一個Method,而且沒有完善的例外控制。如果完成功能的技術瓶項繫在少數幾個人手上但是都忙到無法投入太多時間,那麼這個功能的潛在問題也相對較多。

曾經被派去寫一款連接com port的存摺印表機,使用Java Comm API送出控制碼來操作印表機的工作。剛開始都花時間在研讀規格與測試要送出什麼樣的資料才會讓印表機動作,把控制動作做完後臨時被派去另一個專案,接下來的流程處理就轉交給另一位同事處理。在這個案例裡,我負責讓每一個分解動作(Action)能夠運行,另一位同事則負責讓每一個API的流程控制(Flow)趨於完善。

換句話說,把Interface Method視為元件功能的同時,Flow裡要做的就是元件功能的SA,Action裡要做的元件功能的SD。在Flow裡依功能的需要挑選Flow裡的其他方法或是Action裡的方法來組合出流程,此時僅在邏輯上設計所需的動作可以不用管Action裡是如何完成而;Action裡則專注於如何完成該動作與傳回動作的結果,同樣不用管Flow的用法。

在這個觀念之下,Flow除了呼叫其他方法之外應該只包含(且完全包含)屬於該層級的流程控制指令:
  if…else
  for
  while
  do…while
  switch…case…default
  try…catch
  ( ) ? :
只要Action回應的狀態正確且足夠,Component完成後就可以交給Programmer處理Unit Test發現的Flow內部的判斷錯誤──即使programmer不懂Action裡的運作原理。

更進一步地想,“元件功能越多就封裝越多的邏輯規則難以修改”的說法也可以打破。在傳統的寫法下因流程與動作的緊密結合,使得元件的邏輯規則無法修改或只能複製出來改寫內部的程式;前者造成功能不符不同需求的僵化,後者整個覆寫掉原有程式使得程式碼出現重覆與無用的狀況。在這個元件結構下,我們只要繼承原有的Abstract Flow Class就可以在只變化流程的還能重用元件內所有原有的功能,同時保有元件升級時功能與作法依然可以一起升級的便利。

2009年1月20日 星期二

T08 基本Component內部(3)──Flow與Action

Flow與Action是流程控制的部分,必須可以拿到Properties、Model兩者(Flow還要能拿到Action),同時會在方法內遇到問題時丟出Exception。其中Exception要用時直接產生新的,其他的物件則必須仰賴Implementation提供。

在建立Flow與Action時應該同時再傳入Implementation物件,因此定義setImpl()方法。在內部取得Properties、Model與Action等物件時依封裝原則加上getBaseProperties()、getBaseModel()與getBaseAction()等方法,宣告為protected供內部呼叫。方法名稱會加上Base是因為在Component那層還會有自己要使用的getProperties()、getModel()與getAction(),那是對應取得Component層級的方法。

這是基本Component裡的Flow與Action的結構。

基本Component的內部運作非常地重要,因為這關係到所有Component能否正常動作。到此為止基本Component的全部內部關聯如下:

2009年1月19日 星期一

T07 基本Component內部(2)──Properties與Data Model

Properties是所有參數的集合,在基本Component的運作裡需要Flow、Action與Model的實作Class定義,在這裡定義三個參數名稱來放置,同時要有取得這三個參數值的三個不同方法(應記得為什麼要分開設計而不是只經由getProperty(String name)吧?)。這些參數名稱與取得方法必須定義在BasePropertiesInterface並在BasePropertiesAbstract實作。

  public final static String CLASS_MODEL = "classModel";
  public final static String CLASS_FLOW = "classFlow";
  public final static String CLASS_ACTION = "classAction";

  public final String getModelClassName();
  public final String getFlowClassName();
  public final String getActionName();

根據這裡提供的Class設定值,我們還必須補寫Implementation裡的三個建立方法:createModel()、createFlow()與createAction()。三種類別的建立都是使用Class.forName()因此還要再抽取出一個protected final createClass()的共用方法。對這幾個建立方法的控制不建議太完美,因為這些應該完全沒有問題,測試時出現Exception剛好可以快速知道哪裡出現問題。

這是基本Component裡的Properties的結構。

程式的目的是為了處理資料,Model就是被程式處理的單元。但是在生成時還是會需要參數內容作變化不同的行為,所以仍舊定義setProperties()與getProperties()。

這是基本Component裡的Model的結構。

2009年1月18日 星期日

T06 基本Component內部(1)──Implementation

Implementation是Component內部的根,其他種類的Class都可以在這裡取到(除了Exception,因為它只在有錯誤時另行產生),因此我們需要定義出對應的屬性來保存他們,定義的語法如下。採用protected是要讓所有Component實作時都可以拿到物件來操作。

  protected BaseFlowInterface flow = null;
  protected BaseActionInterface action = null;
  protected BaseModelInterface model = null;
  protected BasePropertiesInterface properties = null;

Implementation也是Component Interface的入口,一切實作的機制會由此開始運作,參數也應該由此傳入。為了避免參數檔在內部讀取的綁死現象同時允許在任何時機更換參數物件,宣告setProperties(BasePropertiesInterface)並開放在BaseInterface中,讓外部程式依時機呼叫。

Properties已定義從外部傳進來,其他的Model、Flow與Action被設計為從Properties讀入定義值後自行生成物件。在此定義三個final的private方法createModel()、createFlow()與createAction()並放置在initialObject()的時候呼叫,並在disposeObject()的時候逆向清空。(properties雖是由外部設定,但因為它可能會有值還是下個清空動作比較安全)

  private final void createModel();
  private final void createFlow();
  private final void createAction();

這三個內部物件也同時提供了三個對應取得方法:getModel()、getFlow()與getAction(),加上getProperties()形成內部相互存取的橋樑。前三個方法定義在BaseImplInterface提供內部使用,最後一個定義在BaseInterface作為Component的必須方法。

另外基於Interface Method的唯一出口與入口要求,準備兩個protected方法在Interface Method的進入與結束時固定呼叫。在此時還不知進出之間需要做什麼事,宣告為Abstract Method留待製作系統根本Component時再依專案需求定義。

  protected abstract void beforeInvoke();
  protected abstract void afterInvoke();

這是基本Component裡的Implementation的結構。

2009年1月14日 星期三

T05 根本的建立(2)──定義基本Component結構

在我的定義裡,所有的功能都會被定義在一個Interface裡,而功能的實作會被Component內六種不同意義的Class合作完成。為了提供所有Interface與Class的通用繼承,必須創建出基本Component的結構。

基本Component提供給外部操作的Interface命名為BaseInterface,內部的六種Interface分別為BaseImplInterface、BaseFlowInterface、BaseActionInterface、BaseModelInterface、BaseProperties與BaseExceptionInterface,繼承的關係如下面的class diagram。



BaseInterface的實作由六個內部Interface分工,不需要定義對應的Class;內部的六個Interface則需要定義六個對應的Abstract Class,同樣必須提供基本建構方法與實作ObjectInterface的方法(記得Model、Properties、Exception三類由於繼承基本類型,必須呼叫super.initialObject()與super.disposeObject())。下面是基本Component的最後class diagram。


元件庫現在的結構圖:

2009年1月9日 星期五

T04 把Project的內容匯入Rose Model

設計的最終都是要產出程式碼,意見的分歧在於要先畫出設計圖再根據設計圖產生程式碼?或者是直接把設計的想法變成程式碼?以前已經說過後者的作法雖然快速卻也遺留了許多後遺症,但依之前的經驗也得知畫設計圖真的會多花很多不必要的時間,而且程式與設計圖之間的雙向同步有一定的落差也是現在的瓶頸。我的解決方案是用程式碼定義出Interface與Class的內容後,再同步到Rose裡組合出需要的圖表。

首先先設定匯入的位置,這只需要設定一次。選擇 Tools > Java/J2EE > Project Specification。

按右上角的按鈕新增一個class path後再按右邊的按鈕選擇匯入的來源再按確定就完成了。

每次更新過程式碼後,都需要執行以下的匯入動作來保持Model與程式碼的同步。選擇 Tools > Java/J2EE > Reverse Engineer…。

選擇剛才加入的class path後按Add Recursive,再依序按Select All、Reverse、Done即完成。匯入的程式碼會放置在Logical View裡。

找到程式碼存放的Package後在上面按右鍵 New > Class Diagram,就可以開始繪製Class Diagram。

2009年1月8日 星期四

T03 根本的建立(1)──定義根本的Class

在萬物都還沒有開始的渾沌,總需要有一個開始作為世界演化的基礎,這便是ObjectInterface存在的理由。往後所有的Interface都必須繼承Obejct Interface,這是為了讓所有的物件都符合我對物件的基本要求:生成與消滅。在這個Interface裡宣告了兩個方法:initialObject()與disposeObject()。

程式執行的目標是為了要存取資料,因此接著需要的是一個最基本的資料物件,定義對應的ModelInterface(記得要繼承ObjectInterface)並賦予兩個最基本的方法:getValue()與setValue()。另外定義一個Abstract Class作為介面的對應,此時只是為了定義Model的存在,因此不讓它繼承任何物件但是要提供基本的Constructor並且實作ObjectInterface的要求。

註:依照以前的定義,這個Abstract Class應該叫作AbstractModel,但是實作時發現這樣命名會在檔案多的時候很難搜尋,因此往後會把Abstract放在後面,所以名稱被定為ModelAbstract。

元件的結構裡還需要Properties與Exception兩種基本類別,仿照Model的作法產生PropertiesInterface、PropertiesAbstract(繼承Properties)與ExceptionInterface與ExceptionAbstract(繼承Exception)。

這些就是我的程式世界裡所有物件最基本型態的對應內容,其class diagram如下:



元件庫現在的結構圖:

2009年1月2日 星期五

T02 元件設計的產出元素

對設計的人來說,SA所產出的Interface Method就是SD的需求規格,將所有的Interface Method完成即是工作的目標。

對於每一個Interface都應該準備一個Component來負責其實作,在大多數的情況下專案完全把Component視為一個不用理會內部的黑盒子,只要外表上執行起來的功能正常即可。最常見的設計結果就是每個Component都對應為一個Class,所有的設計內容都堆放在一起;這時的結構非常地簡單。(圖上)

在我的想法裡,每個Component內部都應該依程式碼特性分為六個部分(Implementation、Flow、Action、Model、Properties、Exception),而且分開定義在不同的Interface並放置在不同的Class裡,這時與最快速的作法比較起來是一個Class對應十二個Interface與Class的差別。(圖中)

再加上貫徹同一作用的資料或程式只能存在一份的要求,每個Class都必須貫徹使用Abstract Class來放置通用程式的方法,如此一來就是一個Class對應十八個Interface、Abstract Class與Class的巨大差別。(圖下)

就算有一堆特別的好處,縱使是我也不想把一個Class可以達成的功能弄成十八個檔案,因為結構的複雜化需要更多的時間來佈置,而且執行的效能可能也是極大的問題。因此必須提供出對應的作法與工具程式來證明並不會多浪費時間,才具有基本的誘因讓人願意這麼做。

接下來,就是以實際的作法來印證我的全部想法的過程。

2009年1月1日 星期四

T01 專案分析的產出元素

根據至今為止的個人經驗,匯集系統分析與設計時會產出的全部元素由上而下條列於這張圖。

對使用者的訪談的時候最主要的目標是收集所有的Use Case。Use Case的內容構成整個系統的邊界,只要完成所有的Use Case並通過測試,就可以說完成了系統。在其上的Subsystem、Package都僅是為了將Use Case分門別類存放的靜態編制,即使不做也不影響系統的完成,但是分開存放能夠再以Subsystem或Package為單位切割Use Case,侷限出更小的範圍作影響的追溯。

想完成一個Use Case的最低要求是找出達成該目標的所有分解動作(Activity),並將之收集起來,收集完所有Use Case的Activity後的集合便是系統所應具有的全部動作。在進入SD階段之前“應該”作完收集全部Activity的工作。在需求不再改變的情形下,Use Case與Activity就是SA階段該有的產出。

為了方便串連SA與SD,我讓每個Use Case流程與Activity動作都對應到一個Interface Method,所有的Interface Method就是SD要完成的所有事項。Interface Method依功能分類後放置在對應的Interface裡,Interface再依不同功能與層級的要求,另外再定義對應層級的Package來分層放置。

到Interface(Interface Method)為止是系統分析階段應該要有的所有產出元素。