一天一小時的文章撰寫
一天一小時的晨間運動
做你自己,其他人都已經有人做了。
— 作者:Oscar Wilde。
最近發現了窮人與富人之間的最大的差異,除了腦袋思維不同之外,另外最大的不同點就是富人或是成功的人都有一個共通性就是自律。
大多的成功人士或是富人對於自已的時間安排或是每天要做的事情都有一定的規律,且是用一種很變態的方式強迫自己一定要執行這個規律,於是我就給了自己每天要來寫部落格的動力,才有這個部落格出現,期待哪天能變成富人(做夢中…)。
做你自己,其他人都已經有人做了。
— 作者:Oscar Wilde。
最近發現了窮人與富人之間的最大的差異,除了腦袋思維不同之外,另外最大的不同點就是富人或是成功的人都有一個共通性就是自律。
大多的成功人士或是富人對於自已的時間安排或是每天要做的事情都有一定的規律,且是用一種很變態的方式強迫自己一定要執行這個規律,於是我就給了自己每天要來寫部落格的動力,才有這個部落格出現,期待哪天能變成富人(做夢中…)。
這是我之前的一個項目,目前這項目已經結束了,這項目是一套實體的遊戲,在要執行這個遊戲的時候,會要刷取一個會員卡,且為了要防止網路斷線客人就無法玩的問題,因此我們後來決定將客人的客戶編號、客戶名稱、儲值金額與一組申請金鑰的時間,這些資料除了存放到 Server 上之外,我們也會將這資料存放到客戶的會員卡中。
當然存資料進去之前除了卡片本身有設定密碼之外,存進去的資料我們也會使用 RSA 加密,加入那個申請金鑰的時間就是擔心就算是使用 RSA 加密,也會有被破解的一天,尤其是像目前已經出現量子電腦,這個 RSA 加密,在未來能夠被快速破解的日子離現在應該是不遠了,因此我們如果為了要防止這狀況,所以在每張會員卡中還加入了這個申請金鑰的時間,請這個時間會到微秒,這是認證的一個很重要的一環,如果使用者手上的時間與 Server 上紀錄的時間不同時,就會立即視為這張卡片失效,因此就算駭客能破解掉我們的 RSA 加密,他最多是破解掉這張卡片的數據,無法仿造其他卡片的資料,也止血於這張會員卡(好像扯遠了)。
我們當時在選擇會員卡的時候,最後我們是選擇了 Mifare 的卡片,在 Mifare Card 可能大家很陌生,他有另外一個叫法叫做 NFC 卡,這個名稱大家可能就很有感覺了,因為目前手機上應該都有這個功能了,NFC 跟 RFID 高頻的頻率都是一樣的,所以基本上我用 RFID 或是使用 NFC 都是可以讀取 Mifare 的卡片,NFC 跟 RFID 高頻的頻率是 13.56 MHz,差別在於 RFID 高頻感應距離大約有 5 公分,最長有遇過可以到 12 公分的感應距離 (如果要在更長的感應距離,必須要重新去跟 NCC 申請安規),而 NFC 的感應距離只有 1~3 公分左右,所以有一陣子 NFC 我們又稱他為「碰一下」。
在 Mifare 的卡片中有明確的定義了卡片的容量大小,一般有分為 1K 跟 4K 的容量,當然還有更小的例如錢扣,只是沒有用過,他們的差別在於他可以存進去的資料內容,我們當時選定 Mifare 卡片是 1K 的,1K 跟 4K 的容量差別在於:
每個儲存區會有 4 個儲存塊 (Block),每個儲存塊可以存放 16 個 Byte,因此 16 個儲存區 * 4 個儲存塊 * 16 Byte = 1024 Byte;必須要特別注意一點,這些儲存塊中並不是每一塊資料都能被寫入哦,詳細請參考圖1. Mifare 儲存區示意圖

另外有一件事情要特別注意的是,這些資料要存入或是要讀取出來的時候,都要被轉換成 16 進位的資料才能寫入,因此讀取出來的資料也是 16 進位的資料,也因為空間不大,因此在存放資料時必須要特別注意你要存放的內容格式。
你的一筆資料可以存放到他一個 Block,你也可以把資料串接起來用文字的方式串接後再存放,關鍵是你的要存的資料大小會不會超過,意思是說,我可以將資料這樣分配:
以上兩種方式都有人在用,我個人的建議是,如果你的資料不敏感,你可以直接用第一種分散式儲存方式,再加上 Mifare Card 本身的加密方式便可。但是如果當你的資料的敏感度是很高的,那我個人不建式採用第一種方式,會建議採用第二種或是第三種方式,但是如果是第三種方式就要特別注意 Json 的長度,畢竟它會多出很多的字節出來,而採用這一類集中式的儲存方式時,你可以再將資料進行 RSA 加密,經過 RSA 加密後資料的長度會在 300 ~ 400 個字節左右,再去拆解成每 16 個字節存在一個 Bolck,最終以400個字節來算,我的資料會被拆解分別放到 25 個 Block 中。
資料存放的方式可以有很多種方式儲存,反正最終你要存放的時候必須要轉換成 16 進位便可,因此資料儲存大置放會分成下列這幾種:
上述的幾種資料型態都是可以將資料存入到 Mifare Card 卡,就看項目的需求與類型是要用哪一種方式去進行儲存,只要經過資料規劃,便可以很方便地去使用 Mifare Card 卡,其實你可以將 Mifare Card 卡當作是一個記事本來看,它說穿了就是一個文字儲存器,只是這個儲存器從虛擬的檔案,變成是一個實體,可以被人帶在身上的物品,並且透由特殊的工具,如 RFID / NFC …等只要是射頻為 13.56 MHz 的頻率便可以將資料讀取出來或是寫入進去。
在 Mifare Card 中,其實是有一個小晶片,這個小晶片當中會外接天線,你將 Mifare Card 卡割開之後便可以看到天線了,RFID Reader 當發出讀取命令的時候,會去將數據透由頻率射出的方式向四處散射,而此時當 Mifare Card 這時候剛剛好接近讀卡機時,這時候的卡片天線因為接收到射頻,而射頻中會帶有微量的電壓 (大約2福特左右),這個微量的電壓就可以驅動 Mifare Card 上的小晶片,而晶片這時候會在通訊訊號中,將參雜在其中的資料或是指令獲取出來,進行判斷指令識別、驗證與寫入或讀取數據,並在透由反波將數據夾雜在訊號中反送回去,這便是整個 Mifare Card 讀取的過程。
這邊有一個重點,就是因為它是透過天線去接收訊號,並透過訊號中的微電壓去驅動,因此如果在卡片的前後帶有載體,且載體是具有帶電系數的載體(如鐵、碳纖維… 等)時,這種狀況下很有可能會導致他無法運作,或者是當如果你將一片以上的卡片放在一起去感應時,可能會無法順利地讀取到你要的數據,因為你的反送書據可能會驅動第二張卡片,而第二張卡片會將反送資料當作指令在判讀,進而的視為這是無效指令,因此當你有一片以上的卡片要讀取時,一定要分開讀取,不要疊加在一起讀取。
所謂的客製化特徵 (Custom Attributes) ,是C#中一個可以讓你去自定義你的類別中屬性或是方法的一個特製的屬性,是針對屬性、方法甚至是類別的本身再去外掛的一個屬性,這個屬性你平常建構起來之後,是看不到它,但是它就確實存在你實體化後的物件中,你必須要透過一些特殊的方法去把它取出來。
這感覺有點像是我在進入到捷運站的時候,在捷運站我會拿著一張悠遊卡進站,進站的時候我只需要將悠遊卡拿出來,往卡機一刷就可以自動進行扣款,那個悠遊卡其實就是一張 RFID 高頻的 Mifare 卡,在你手上拿的這張卡片你可以看到它是印有一些圖騰,另外會有一個卡號,還有一些說明…等資訊,你可以理解的是這上面寫的那些資訊就是它的一些相關的屬性,例如有名稱、備註、卡號…等。

但是其實在這張悠遊卡中它是有一些隱藏資訊的,例如說他會存放是否為老人卡、學生卡或是一般成人卡,以及卡裡面的金額,進站點…等等這些資訊 (會友另外一張專門來說明 Mifare 卡),但是這些資訊你看不到,而這些被隱藏起來的資訊就好像我們的Custom Attributes。
因此我們可以針對 Method、Property 都可以去定義 Custom Attributes,要定義 Custom Attributes 首先你必須要先建立一個 Class 來繼承 System.Attribute 這個父類別,一旦你繼承了 System.Attribute 這個父類別之後,你的這個類別就可以當作是 Custom Attributes 來使用,如圖2. 自定義 Custom Attributes 類別

再圖2中我們定義了一個 EAPQueueAttributes 的類別,在這個類別中,我定義了三個自定義屬性,分別是「Index」、「Name」與「IsList」這三個屬性,基本上定義到這裡就已經將我們的客製化特徵定義完畢了,接下來就可以去使用者個 Custom Attributes。
如何使用,其實很簡單,就是在你要自定義的那個類別中 Using 到那個類別的 Namespace,再將你要自定義特徵的屬性上方,定義出你的 Custom Attributes 的名稱,接下來你就可以去設定你的特徵資料值,如圖3. 設定 Custom Attributes 相關資料

使用方式很簡單,如圖3 一樣在你的屬性或是方法上加上一個中跨號 (VB.NET 是 <> 角跨號),並且在中跨號裡面去定義 Custom Attributes 的名稱,接下來就可以在Custom Attributes 名稱的後方直接指定你的特徵內容,定義完畢之後你對他賦予的相關資訊就完成了。
接下來就是我要如何在系統運行的時候,將這些 Custom Attributes 的隱藏資料讀取出來加以運作呢?我們要讀取這些 Custom Attributes 的資料時,同樣的也是要透過 Reflection 的機制來獲取,因此我們在抓取這些資料之前,我們也是一樣要先將我要抓取的物件轉換成 Type 型別,來獲取該類別的資訊,因此可以透過 GetType() 的方法,來抓取到物件對應的類別資訊,如var ClassInfo = BarCodeInfo.GetType();
在早前文章(透過 Reflection 建構通用型 SOAP 入口) 中有介紹到我們透過 GetType() 的方法,可以抓取到該物件的類別資訊,因此我有了類別資訊後,可以再透過 GetProperty() 的方法來獲取到該類別的所有屬性資訊,如var PropertyInfo = BarCodeInfo.GetType().GetProperty([PropertyName]);
或是透過 GetProperties() 來獲取到所有該類別的公開屬性資訊,如var PropertyInfo = BarCodeInfo.GetType().GetProperties();
另外也可以透過早前文章介紹到的 GetMethod() 或是 GetMethods() 來獲取該類別的所有公開方法,如下範例。var MethodInfo = BarCodeInfo.GetType().GetMethod([MethodName]);var MethodInfo = BarCodeInfo.GetType().GetMethods();
當然如果要獲取該類別中所有全域變數的宣告也是有 GetField() 以及 GetFields() 的方法來獲取到該類別的全域變數資訊,如下範例var FieldInfo = BarCodeInfo.GetType().GetField([FieldName]);var FieldInfo = BarCodeInfo.GetType().GetFidles();
而我們如果要獲取 Custom Attributes 資訊時,必須要明確知道你要獲取的 Custom Attributes 是附加在哪個地方上,所以如果你要抓取類別上的 Custom Attributes 就必須要先透過 GetType() 的方法來獲取到類別的資訊;如果你要抓取的是方法上的 Custom Attributes 就要先透過 GetMethod() 的方法來取得到指定類別資訊;依此類推,如果我依據圖3 中所定義的,該類別的 Custom Attributes 都是設定在屬性上,因此我們就必須要先透過 GetProperty() 或是 GetProperties() 的方法,來獲取到類別上屬性的資訊,當獲取到屬性資訊後,可以再透過 GetCustomAttributes() 的方法來獲取到該屬性上所定義的所有客製化特徵資訊,如下範例var AttrInfo = BarCodeInfo.GetType().GetProperty("[PropertyName]").GetCustomAttributes(false);
請注意,這裡是獲取到該屬性、方法、類別或變數的所有 Custom Attributes 資訊,一個方法上面可以擁有多個 Custom Attributes 如圖4. 定義多個 Custom Attributes 資訊

在圖4 中可以看到這個 Class 中被定義了三個 Custom Attributes 資訊,所以當你透過 GetCustomAttributes() 的方法來獲取 Custom Attributes 時,你會就會獲取到一個 object[] 這個物件陣列中會有三個元素,分別就是圖4中所定義的那三個 Attributes 的物件資訊;請注意,你這時候獲取到的是物件,也就是它是一個已經配置過記憶體的物件,裡面已經有包含了你當初所設定在那些 Custom Attributes 中的資料,獲取到之後便可以直接拿來使用或判斷,如圖5. 獲取 Custom Attributes 範例。

在圖5 中可以看到他首先先宣告並實體化在圖3中所定義的那個 EAP_BarcodeCheck 的物件,接著它透由 GetType() 轉換成類別資訊,再透過 GetProperties() 獲取到所有屬性資訊 (因為圖3 中所有的 ColumnIndexList 是定義在屬性中)。
獲取到所有屬性資訊後,又宣告了一個 ColumnIndexList 的清單物件要來收集所有欄位被定義在Custom Attributes 中的位置,最後我用了一個 Foreach 將所有的屬性逐一取出來,並且透過 GetCustomAttributes() 來抓取到該屬性中所有的 Custom Attributes,並透過 Linq 的方式抓取到屬於EAPQueueAttributes 的 Attribute,如此我便獲取到該 Custom Attributes 中所定義的 Column Name 以及 Column Index,就可以將資料存放到早前選告的 ColumnIndexList 的清單中。
透過這樣的操作,我便可以獲取到類別中 Custom Attributes,並且可以再透過 Reflection 的機制可以在達到更多的操作,後續我會使用這樣的手法,來達到更多我要講解的東西;簡單說一個目前有做過的真實範例:當我收到一筆資料時,我會去定義這筆資料可以透由「@」去做區隔。CheckIn@4523||1||AS4382G||1
如上範例所示@前面的 CheckIn 便是我要去本類別中在 Method 中的 Custom Attributes 的資訊裡,被定義跟 CheckIn 一樣的方法,如下範例:[ATTR_MessageDefinition(Name = "CheckIn")]
private void HandleLotCheckInInfo(object pInputInfo) {
.... .... .... ....
.... .... .... ....
}
[ATTR_MessageDefinition(Name = "CheckOut")]
private void HandleLotCheckOutInfo(object pInputInfo) {
.... .... .... ....
.... .... .... ....
}
而@後面的資料,就是我調用對應的方法時,要帶入的資料,動態調用的方法,請參閱透過 Reflection 建構通用型 SOAP 入口,這裡不多做解釋。
一套系統最重要的部分,除了產品本身,最重要的部分在我認定,就是系統與系統之間的介面,這個介面的定義其實很廣泛,可以分為下面幾個類型:
一般我們對到系統間通訊那怕是 File、TCP、Web Service … 等,都應該要明確的定義每個接口通訊的格式。我們在系統分析之前,在做需求調研階段,應該就會整理出會有哪一些的接口,並且會產出一份接口清單出來,如圖1. 系統接口清單定義表

因此我們在分析階段的時候,第一個要做的事情就是這一份規格,務必要先將這份規格先展開,請這份規格展開之後,必須要發送到所有系統負責人的手上,且須親自與系統負責人解釋清楚,接口定義一定是最重要的,且有時候往往會導致系統能否順利結案的關鍵,所以好好的分析這份規格勢必是在前期最為重要的事情,因此我們需要針對每個要傳送的欄位都比需要有一個明確的定義,如圖2. 系統接口內容定義規格表

從圖二中我們可以看到我們會先定義這個接口的編號、輸送方式、接口說明、啟動時間,並且針對內部細項也需要逐一地去陳列清楚包含資料意義、資料字串長度、資料類型、資料說明…等,都必須要有清楚的說明。
圖3. 這是一種類型的接口資料定義,其實定義方式也是大同小異,主要也是針對要傳遞的內容去定義清楚,且包含了 Request 與 Response 都必須要明確的定義清楚。

最後是一個圖3 的資料範例,這是一個 Json 的資料格式範例,當然它可以是 Json 一樣的它也可以是 XML 主要還是看在定義時,雙方的認定
Request
{
“MESSAGE_ID":"machine.running.notice.process",
“REQ_ID":"REQ_20170321123313213″,
“SITE":"TCI01″,
“FACTORY":"503″,
“LINE_ID":"TCI01-S0503″,
“OPERATIO":"TCI01-S0503-01″,
“STATION":"1″,
“RESOURCE":"50301″,
“SEND_TIME":"2018/06/07 21:18:23″,
“STATUS":"1″
}
Response
{
“RESULT": “1″,
“REQ_ID":"REQ_20170321123313213″,
“MESSAGE": “"
}
在客戶需求收集與界定分析-2的章節有針對性,我們會提供出:
將所有客户的需求經由上述的幾項進行分析完成之後,便可以確定目前要執行的這一套系統,會跟哪一些系統有交集,進行整理之後便可以歸整出整套系統的全貌,並將以繪製可以產出整個系統的架構圖,如圖1. 系統架構示意圖。

如果再將分析後的接口也整合進去之後,便可以得到系統與系統間的通訊關係,並展開整個系統的溝通架構圖,如圖2. 系統溝通架構示意圖。

最後將功能清單與畫面整合之後,整個系統的全貌大致就出現了,加以歸整分類之後,便可以展開系統的功能架構圖出來,如圖3. 功能架構示意圖

上述幾項經過整理之後,便可以整理出客戶需求規格書,也就是藍圖匯報的主要產出。與客戶說明需求時,經由這幾項規格彙整,基本上便可與客戶進行需求的確認。
但是如果要接著往下分析時,可能就不會是這樣便可以完成的,因此如果我們要移交給後續系統分析人員進行處理時,就還需要在往下分析下去,除非接下來接手的人員就是你自己,又或者是說接下來要接手的人員,你已經跟他合作很久,他非常清楚知道他要怎麼做,又或者是你知道接下來接手的人員是一個經驗相當的人員,不然單就從雛形、使用個案與活動圖,要讓分析人員自行轉換成有哪一些類別或是哪一些作業,中間的間距仍舊是有點太大,因此我們可以再幫分析設計的人員再多分析出一個強韌分析圖。
這個強韌分析圖主要是再做一件事情,就是需要將使用個案與接下來系統分析設計人員要做的類別圖或循序圖建立起一個關係,因為要從使用者案直接跳到類別循序圖,中間的間距有點大,因此可以透由這個強韌分析圖 (Robustness Analysis),拉近兩邊的關係,如圖4. 所表示的 (本圖是參考來自中山大學吳仁和教授簡報內容), Use case to sequence diagram is not easy,所以他建議走下面的這一條路,經由 Robustness Analysis 分析之後,再轉去做 Sequence Diagram 會比較清晰,也不會做錯。

在 Robustness Analysis 中他分成了四樣東西,分別是:
另外針對 Robustness Analysis 他也是有一個準則的,他的準則如下,如圖5. Robustness Analysis 準則

有沒有可能會有與準則相抵觸的狀況,答案是有可能,例如說 DBA (Database Administrator), DBA 是一個 Actor,他就有可能會直接還操作 Database,但是面對到這種狀況,基本上我的想法是還是要將他導回到正常的流程定義,這時候 DBA 是 Actor,Database 就會是 Interface 也就是邊界 Boundary,那 Control 就會是 SQL 的 Stored Procedure、Function … 等,Entity 就是他操作的 Table ,主要想表達的是,雖然我們會遇到一些狀況會與準則衝突,但是為了要能與外界接軌,還是要將整個流程引導回正常準則,這樣別人才能一看你的圖便可了解你的圖內容代表的意義。
另外還有一個非常重要的問題是,當我們在做 Robustness Analysis 時,又或者是系統分析設計人員接手到客戶需求規格時,發現到 Control 與 Control 之間有著一條直接的連線時,表示這兩個物件之間有一個緊密的強耦合關係,這裡必須要先說明 Control 控制者這個物件在我們的定義中,會將它視為是一個主要物件,所謂的主要物件是指這個使用者需求的商業邏輯物件的主要處理者,簡單說就是一個商業邏輯物件,當發現兩個商業邏輯物件出現了一個強偶合關係存在時,如圖6 Robustness Analysis 示意圖

這時候我們就知道在這兩個主要物件之間,會存在一個次要的物件,這個次要物件主要就是用來解決物件與物件之間的耦合性,以物件導向的宗旨來看,物件要達到耦合力低內聚力高,我們也經常聽到這句話,但是到底什麼是耦合力低內聚力高,而物件導向中要達到絕對的內聚力高,沒有耦合性那是比較不可能的,除非我們將整套系統都採用外掛的方式執行,但是這樣的系統執行效能上又會變成很大的致命傷,又或者是我們目前這套系統是一個很小型的 Utility,絕對不會有所謂的耦合,這樣就有這可能性。
但回到正題,如果發現關係是必然存在那又要如何建立一個高內聚力,低耦合力呢? 這時就回到我們剛剛談到的那個所謂次要物件上,這個所謂的次要物件基本上就是我們常常聽到的設計模式 (Design Patterns);這是強韌分析圖中第二重要的部分,就是要找出這些物件間的所有耦合,只要在強韌分析圖當中發現有 Control 與 Control 之間存在一個直接的連線關係時,就表示在這一個地方未來將會架設一個 Design Patterns 在這裡,因為其實 Design Patterns 主要的目的就是為了要來解決物件與物件之間的耦合性,Design Patterns 之所以稱之為 Design Patterns,基本上它就是在設計階段的時候,就必須要加入到你的設計中,它是你設計的一個很重要的環節,而不是在你寫程序的時候,忽然靈感一來覺得這裡需要架設一個 Pattern,那裏需要架設一個 Abstract Factory,這邊又可以來一個 Prototype 的方式在任意的添加,這樣的做法基本上我們會稱之為 anti-pattern (反面模式),也就是所謂的因為物件導向而物件導向,又或因為 Design Patterns 而 Design Patterns 的做法。
我們可以看到圖6. Robustness Analysis 示意圖,系統架構師在分析完客戶的需求之後,為了要能夠讓系統分析設計師順利地往下執行下去,最後我們都會產出這樣的一個 Robustness Analysis,來協助後續的系統分析設計作業,並且在做完者樣的強韌分析之後,我們也大致可以產生出一份 Table 清單以及物件清單,有了這份清單,系統分析設計人員在繼續往下分析時,可以更加容易上手。

到這裡便是我們整個在製作藍圖設計時,會產出的相關活動從需求擷取,到需求條列、需求分析、情境分析、雛型設計、流程釐清到最後的分析接軌,這一連串的作業便可以完成了客戶需求規格書的產出,基本上它完成了部分系統分析的內容,但是這部分基本上就很難界定,因此如果客戶需求規劃與系統分析設計是屬於不同的人時,必須要有當責的認知,彼此都要能在跨出一步,相互干涉到彼此的一部份,這樣才能夠更加順利的進行。
在我上一個章節有談到 (請參閱 客戶需求收集與界定分析-1),我們會先將客人描述性的需求轉換成條列式需求,再將條列式需求,分別找出名詞、動詞以及受詞,而在名詞的部分,基本上就會是我們在做 UML 的時候所要找的行為者 (Actor),在上一個章節中看到的範例,基本上我們就能夠找出以下的行為者:

而在條列式需求中找到的受詞,有大部分的可能它就會是 Database、File、System Output…等這類的重要產出,另外也有能會是其他系統、如果發現你的受詞是系統 (注意,這裡說的系統,是指外部系統例如 MES / EAP / WMS / ERP…等,本系統之外的系統一律為外部系統),那基本上就可以知道,在這裡會有一個通信接口,也就是我們常聽到的 Interface。
因此在這個時候就可以先對這些 Interface 做定義並賦予一個唯一的編號例如 PE01、PM01、MP01…等,一般我們會將系統的第一個字做為編號的一碼,例如找到 MES 會發送信息給 PAS 系統我們就會用 MP 開頭,MP 開頭找到的第一個接口我們就會定義為 MP01,但二個接口會定義為 MP02… 依此類推,這時候可以不需要有內容,內容大約都是在系統分析設計的階段才會明確定義的,但是這時候必須要有接口編號出來,如圖2. 系統接口編號定義表

因此我們在界定條列式需求的時候,可以直接用接口編號來取代描述定義,這樣系統分析的人員更可以直接從客戶需求文件中,一目了然的清晰了解到這裏有一個需要特別注意的地方,並且透由這樣的整理,客戶也會感覺到專業,因此便可更加的放心。
剛剛有談到從主詞中可以找到行為者 (Actor)、再受詞中可以找到 Database、Result、Interface… 等相關系統作業的產出,那動詞呢? 從動詞中,主要就是要找到客戶的需求邏輯,也就是客戶需求的使用個案 (Use Case),至於 Use Case 有沒有像 Actor、Table 或 Interface 一樣有一個準則呢? 這部分其實沒有一個標準的定義,Use Case 的大小,其實主要還是靠經驗,但是可以知道的是,一個 Use Case 如果在分解強韌分析圖 (Robustness Diagram) 的時候,沒有辦法切出 Boundary、Controller 與 Entity,那就表示切的 Use Case 是有問題的。
以我們目前對於 Use Case 拆解的定義,其實也是有一套準則,就是當你將需求流程拆到最小的單位時,那他就會是一個 Use Case,所謂的最小單位就是指一個需求流程再拆解下去已經沒有意義時,例如圖3. 使用個案示意圖,以「PPM150 RCCP 粗略產能評估」它的需求就是答交組要去進行RCCP粗略產能評估的作業,來確定客人下達的粗略產能能否滿足,當然在要做這一件事情之前,會有一連串的動作,例如展開工單、展 BOM 表、展 MRP…等這些作業,在業務流程 (注意,是流程哦)上它都會是獨立的,所以它們都會被抓出來作為獨立的 Use Case,但是到了最後要進行 RCCP 產能估算這個流程時,再往下細拆就會變成動作了,它就已經不構成需求流程,因此我們拆到這個階段便可,在往下繼續拆解已經沒有太大的意義,這時候他就會形成我們的 Use Case;

至於什麼樣的流程是最小單位呢? 這個當然要看你的系統來定義啦,就跟物件導向的原理一樣,一棵樹是一個物件、一片葉子是一個物件、一塊木頭也是物件,那到底樹木是物件還是樹葉、樹枝、樹幹又或者是整棵樹是物件? 最終還是要看你的系統是什麼樣的系統,如果我做的系統是森林導覽,那物件的單位就是一棵樹,對於樹葉、樹枝、樹幹它都會是屬性;而如果我做的系統是樹木幹細胞研究系統,那物件的單位就有可能小到非常小,甚至連年輪都會被定義成是物件;因此回到我們討論的主題,流程的最小單位還是要看你的系統是什麼樣的系統,以及業務需求來決定你流程的大小,這部分就還是要看業務顧問的經驗了,沒有一個一定的準則可以依循。
在使用個案圖 (Use Case Diagram) 中,可以看到那條件上有寫到「include」,有時候還能看到「Extends」這是什麼意思?
另外還有一般化 (Generalization),這會比較偏向繼承的概念,在 Use Case 的概念會比較像是標準化的意思,我有一個標準化的流程,有可能在 A、B、C 這三個 Use Case 都會有,這時候我們可以定義一個標準化的 Use Case,讓其他的 Use Case 都 Generalization 這個標準化的 Use Case,這類的 線我比較少用,因為在使用個案中加入這類的線,很容易讓使用者聽得一頭霧水,基本上解釋到最後會讓我有想罵客戶的衝動,所以我這裡基本上是很少用這條的線。
我們設計完 Use Case 之後,針對一個 Use Case 我們應該都要有一個對應的活動圖 (Activity Diagram),如果我們做的是一個網站或是 UI 系統的化,會再多一個雛形來讓使用者更能夠明白我們在說的東西與內容,一般來說要使用者去看 Use Case 那會是一個很痛苦的過程,對於使用者來說,他們也會覺得你是浪費它的時間,因此對於 Use Case 我們會跟使用者說,它是一個讓你知道你要做甚麼的指引,重點還是要來看 Use Case 裡面的商業流程與 UI,Use Case 我們會比較快速的帶過,讓他先有一個映像便可,等到商業流程與 UI 說明完之後,再回過頭來在說明一次 Use Case 這時候的使用者,他會比較有感覺,抵觸或反彈就不會那麼的大。
因此剛剛也有提到一個 Use Case 基本上會有一個對應的活動圖,這個活動圖主要就是要來描述這個 Use Case 的動作,上面有提到,Use Case 的拆分就是拆解到流程的最小單位,在細拆下去就會變成動作,而這個動作就是活動圖中所要描述的內容,他主要是要描述這個最小單位的流程他有什麼樣的商業流程 (Business Process),因此他基本上很像流程圖如圖4. 活動圖示意圖,使用者一般也是會用流程圖的角度去理解他。

對於這種流程圖,使用者一般會非常的孰悉,他也可以立即告知你,你的流程尚有什麼樣的問題,這時如果搭配上一個系統雛形的畫面,對於使用者來說,他基本上可以有 80% 的機會可以 Get 到你的信息或你想要表達的內容。所以才會說一個 Use Case 必須要搭配一個活動圖與一個 UI 雛形一起描述,如圖5. 系統雛形示意圖

雛型不一定要是系統介面,也可以透過類似 Microsoft Visio / Power Point 來呈現出你的 UI 畫面,如圖6. 非系統畫面的雛型示意圖,重點是要能表達出你的想法,並且將流程結合起來,這樣使用者一下就能明白你的意思,圖6 的畫面是一個我透由 Microsoft Visio 完成的 UI 雛形。

今天收到一個任務是要建立一個可以讓外部系統溝通的 Web Service 接口,但由於要調用的對口Method 數量有太多,因此如果每個都去建立一個 Web Service 接口,那會有多少個要做,因此我們將所有的接口全部統一起來,只留一個 Method 來對所有的服務接口,如圖1. InvokeSer 示意圖中所定義的 InvokeSer。

在 InvokeSer 中有兩個參數分別如下:
Param1 主要定義的是 Service Identify,也就是要調用服務的定義識別,例如 LotCheckIn / LotCheckOut / InsertUser / CreateJob / … …等,這個定義識別只是一個識別,用來讓系統可以識別服務的唯一碼。
Param2 傳入的資料是真正要調用服務的時後,所要帶入的參數資料,一般會是 XML / JSON String / Text …等,有時可能傳遞的 Method 會需要傳送一個以上的參數,這時候會強烈建議無論是一個或是多個參數,都要整合成一個參數值,無論是用 XML 去彙整又或者是用 JSON 去彙整都可以,要將多個參數資料轉換成一個參數值,轉換的方式如下圖2. Process Request Format 表格。

我會將我要傳遞的資料存入到一個 DTO (Data Transform Object)物件中,並且將 DTO 物件轉換成 JSON 或是 XML 的字串,再將這個字串存入到 Condition 的格式中,如下範例:
{
Condition : Json string 範例
"Token":"GIaUgupqZumIQbVAANw7lbeNxBS2j3KTl9sUdS9NV8G0omjfUWM=",
"ConditionType":"S",
"Condition":"{\"factor_id\" : \"bracket\", \"factor_name\" : \"支架\"}"
}
<?xml version="1.0″ encoding="UTF-8″ ?>
Condition : XML string 範例
<Param2>
<Token>GIaUgupqZumIQbVAANw7lbeNxBS2j3KTl9sUdS9NV8G0omjfUWM=</Token>
<ConditionType>S</ConditionType>
<Condition>{“factor_id" : “bracket", “factor_name" : “支架"}</Condition>
</Param2>
在上述的範例中,可以看到我要傳遞的參數有兩個資料,分別是 factor_id 與 factor_name 兩個值,但是我將兩個資料包含在了 Condition 的這個標籤中,並且將所有的資料都彙整成一個 XML 或是 JSON String,做為是 Param 2 的資料值,使用這樣的做法就可以將所有個參數都彙整成一個參數資料;附帶一提 Input 的參數資料可以這樣彙整,相對的如果我的 Out put 資料有一個以上,也可以用這樣的方式傳出。
好了,接下來就是我要如何透過 Param1 的定義,來調用不同的服務,一般人想到可能就是透由 switch 或是 if…else if …else if 如此的串接下去,但如果是這樣的方式,不就每次要新增一個服務時就要重新再加入一次,另外如果有 100 個服務要做,那這個 switch 或是 if…else 就要寫不完,且對於維護上也不容易,因此可以透由一個小方法來完成我的這個定義,就是透由 Reflection 來建構,首先我們要先建立一張資料表,來取代這些 if…else 或是 switch 的定義,表結構如圖3,資料內容如圖4


在圖4的範例中,裡面有一個「service_key」,這個欄位值就是用來儲存服務的識別值,也就是 Param1 的資料,從上述的圖4 的資料中可以看的出來,如果當 Param1 傳入的資料是 「plot.check.in.process」的資料時,基本上就可以對應到第三筆資料,從第三筆資料中就可以找到這個服務是要調用「FW.RMS_ManagerService.Service.lot_check_in_handler」這個類別的服務,也就是圖4 中「class_name」所儲存的資料,而真正要調用的 Method 是 method_name 所定義的 StartRun 的服務,如圖5. lot_check_in_handler 服務

如果不想要這麼麻煩,其實還有另外一個方式就是透過客製化特徵 Customized Attributes 或是介面樣板 Interface 一樣可以達到這個作用 (針對客製化特徵 Customized Attributes 會有另外章節來專門說明這個工具的用法,這裡不詳述),但是主要我是為了要便於管理,因此選擇採用 Database 來集中化管理。
OK,完成了以上的配置,那我究竟要如何去透過資料庫動態的去調用呢?看到這裡我想有人心裡已經有答案,其實說穿了就是反向注入的模型來達到這樣的機制,這機制要怎麼做呢?首先我會先建立一個類別是專門用來處理這個服務委派,我這裡稱他為「InvokeServiceHandler」。
特別說明一下,針對類別,我的命名方式喜歡以模擬人物化的方式來命名,例如這個服務就是「服務委派作業處理者」,因此在這裡我就已經賦予他一個責任是專責用來處理服務委派的作業,其他非服務委派的功能我便不會將它放入到這個類別中,這樣的作法是為了強逼自己要將物件進行單一原則,一個物件只讓它處理一個作業概念(Concept)
在這個「InvokeServiceHandler」的類別中,我加入了一個專門用來處理反向注入的方法是 StartRun ,這個方法中會要求帶入兩個參數,也就是要傳入 Param1 / Param2 的參數,呼叫方式可以參考圖1. InvokeSer 示意圖
這個方法第一步基本上就是要抓取 Database 中「service_key」與 pKey 相吻合的資料,獲取到該資訊後,便可依據該資訊建立起對應的物件,如圖6. 動態調用類別

以圖6所示,首先我們會先看到一個下方程序
var AssemblyTemp = Assembly.GetExecutingAssembly();
這一段主要是用來獲取 Assembly,所謂 Assembly 就是編譯出來的那顆 dll 或是 .exe 檔案,那一個檔案就是所謂的 Assembly,一般我們如果要取得 Assembly 獲取方式主要是會指定我要獲取的檔案路徑,在讓程序動態將 Assembly 檔案讀取出來進而獲取到我們要的 Assembly;但是我們這次所做的服務,目前會調用的都是存在同一個 Assembly 檔案中的處理者類別,因此就不需要如此麻煩的去動態載入 dll 檔案,而是可以透由 Assembly.GetExecutingAssembly() 這個方法來獲取到當前這個類別所屬的 Assembly 物件。
接下來有看到一段程序如下
AssemblyTemp.GetTypes().SingleOrDefault
這一段程序可以透由 Assembly.GetTypes() 的方法去抓取到這個 Assembly 中所有的型別,Class 類別基本上就是屬於一種型別,因此透由 Assembly.GetTypes() 的方法,可以獲取到所有該 Assembly 下的所有類別資訊,並且透由 LINQ 的方式抓取到 Namespace + Class 名稱與數據表中「class_name」相符合的類別 (關於「class_name」請參考圖3或圖4的資料),透過這方法便可以獲取到對應服務處理類別的型別物件,注意,此時獲取到的只是類別,並沒有 Instance,也就是說它還沒有被 new 起來,因此如果現在直接去使用它,就會獲得一個 NullReferenceException 的錯誤。
沒有 Instance 的類別基本上便無法使用,但是在這樣的一個環節中,我們無法直接使用強行別的建構方式來實體化這個類別,進而轉換成物件,因此就出現了下面這一段程序
Activator.CreateInstance(CurrentType, null);
這一段程序主要是動態產生出一個實體化之後的物件類別,因此你需要指定你要建構的類別型別,並且如果該類別在建構子的地方是必須要帶入參數時,也必須要將建構子所需要帶入的資料傳入進去,如此便可以動態的將這個類別實體化,變成一個有 Instance 的物件。
獲取到被實體化的物件之後,基本上這個物件就是一個已經有配置記憶體的物件,只是被 Boxing 成一個 object 型別的實體,也就是說,如果你知道這個物件是什麼,你可以透過強行別轉換的方式強制將這個記憶體在轉換過去,如下列所示,可以直接將這個實體物件轉換成已知的物件或是介面
(IMachineInfo)Activator.CreateInstance(CurrentType, null);
但是目前我們的類別配置是在數據庫中,因此無法有一個唯一性的類別或介面,因此我們可以不需要進行轉換,但問題來了,如果不轉換的時候,我要如何去調用我要執行的 Method 呢?
這時後,我們還要再度的利用到 Reflection 的機制,我們可以透過物件的 GetType() 的方法,將物件再萃取成類別的型別,透過型別物件可以提供出很多的方法與屬性其中就有一個 GetMethod() 的方法可以讓你指定你要獲取的方法名稱,如下列範例
ClassInstance.GetType().GetMethod(MappingInfo.method_name);
在上述的案例中,可以看到我透過 GetType() 的方式將 ClassInsteance 萃取出類別型別,再透過 GetMethod() 的方法,可以抓取出指定 Method 的型別,注意這裡也只是一個 Method 的型別,因此他不是一個具有實體物件的方法,所以他是無法被操作的。
如果我想要操作這個 Method 的話,就需要透過一些比較特別的方法去呼叫,這方法就是 Invoke,它是一個類似委派的呼叫,如下列範例所示
var ParamInfo = new object[] { pCondition };
return (string)MethodType.Invoke(ClassInstance, ParamInfo);
透過 Invoke 的方法,來執行我本身這個 Method,因此帶入的第一個參數需要具備有兩個條件
第二個參數是調用 Method 的時候要傳入的參數,以剛剛「StartRun(string pCondition)」的範例,它有一個參數是 pCondition,資料型別為 string,因此在透過 Invoke 呼叫的時候,就需要將這個參數的資料帶入,但是有可能你要調用的方法要傳入的參數不只一個時,你要怎麼做呢?
因此第二個參數所要帶入的資料是一個物件陣列 object[],你需要宣告一個物件陣列的物件,並依據要傳遞參數的順序依據存入到對應物件陣列 Index 的位置,如上述的範例 ParamInfo 所演示的範例一樣,最後再將 ParamInfo 資料作為第二個參數傳入到 Invoke 方法中。
透過 Invoke 調用 Method 時,如果該 Method 有回傳值時,也會在調用完 Invoke 後回傳回來,如此便可以獲取到你動態調用的方法回傳值。
接下來我們可以來做一個類別是可以讓 Invoke 調用的類別,在圖4中有一個 service_key 的資料為「material.on.machine.process」,它配置的類別是「FW.RMS_ManagerService.Service. material_on_machine_handler」,執行的方法為「StartRun」,於是我實作了這個類別與方法,很單純的回覆一個 「Hello this is test!!」的字串,並且要確定它是能夠接收到傳遞的參數,因此後面再加上傳遞進來的參數,如圖7. 實作material_on_machine_handler

好了,萬事皆備,只欠東風,所以我們來踢這最後一腳,我們將它執行起來測試一下,Run 起來之後,就可以看到如圖8. InvokeSer 執行畫面,以及圖9. InvokeSer WSDL 畫面


好了我們嘗試執行 InvokeSer 的方法,點選 InvokeSer 會進入到執行畫面,如圖10. InvokeSer 作業畫面

我在Param1中輸入了「material.on.machine.process」,然後再 Param2 中輸入了「哈喽,我是許小史」,沒有意外點選完叫用後,它會出現
Hello this is test!!
哈喽,我是許小史
如圖11. 叫用 InvokeSer 服務

這樣就完成了我們通用型的 SOAP 服務,透過這個服務,我們日後就不需要再因為要加入新的 WSDL 方法而又要不斷的調整我們的 Controller,只需要到數據庫中,加入一筆對應要作業的資料便可完成了。
在接到項目之後,一般來說會先進行所謂的啟動會議 (一般外面業界會稱為 Kick-off Meeting),在Kick-off Meeting結束之後就會進行一連串的客戶需求訪談 (有些地方稱為需求調研),因此在需求調研完畢後,會可以收集到一堆客戶的需求單,接下來我們就必須要針對這些需求單去進行客戶需求的分析與設計,將客戶的需求轉換到系統作業畫面中,並將分析後的結果集合成冊之後,與客人確認是否跟客戶所提出來的有落差,如果有落差的時候,會再重新調整,如此反覆確認直到所有的需求都確認,這便是一般再需求調研階段我們會執行的作業。
至於客戶的需求要如何分析呢? 有沒有甚麼工具可以協助分析呢? 是有的,如果在網路上搜尋後就可以找到一大堆,我這裡介紹我比較常用的方法,是屬於物件導向的分析方法。基本上會有幾個步驟:
基本上上述幾項完成之後,便可以完成我們的客戶需求分析,以下我將會針對這些作業到底我們要如何來去進行,首先是「條列式客戶需求」
條列式客戶需求
客戶的需求一般來說都是屬於比較分散式也沒有邏輯的,因此我們常常需要去誘導客戶的需求,讓他能夠變得清晰,且是可以被解析,也可以被拆解的階段;
一般來看我們會收到的客戶需求大致會跟下面狀況一樣:
客戶在月底前會先下達 Forecast 預估產量,來評估未來產能狀況,有些客戶並不會明確指定料號或是 Model,而只會告知下月xxx、xxx …等大約多少的量(ex. 下月2代馬達約4000萬片),確認是否可以承接,之類的。
答交組必須要到 ERP 確認目前的訂單量,並且分析是否可以接受這個Forecast 預估產量,並且要回復給客戶是否可以承接該下個月的產量
目前存在ERP中的料號可分為「原物料號」、「虛擬料號」、「半成品料號」以及「成品料號」;當客戶下達 Forecast 預估產量時,只會下達成品料號或是成品 Model 甚至是成品「產品說明」。
在答交組接收到所下達的「產品說明」後,需轉換為正式的成品料號,因此需要有一個紀錄表來記錄目前常用的與現有的成品料號,讓答交組可以在確定完客戶下達的Model之後,亦可選定要預估的實際料號
一般我們會收到的需求單就如同上述這類的「需求描述」,注意,他是一段需求描述,可能沒有辦法有具體的說明,因此我們在接收到這樣的需求時,我們可能需要將這個需求變成條列式的需求列表,做法大致就是再需求描述中去找到一個段落,而所謂的一個段落其實就是能夠找到一段描述的主詞、動詞以及受詞這三樣,只要能夠找出這三項,基本上就算是能夠組合成一個條列式的需求,例如以範例一個說明:
客戶在月底前會先打電話下達 Forecast 預估產量,來評估未來產能狀況,有些客戶並不會明確指定料號或是 Model,而只會告知下月xxx、xxx …等大約多少的量(ex. 下月2代馬達約4000萬片),確認是否可以承接。
答交組必須要到 ERP 確認目前的訂單量,並且分析是否可以接受這個Forecast 預估產量,並且要回復給客戶是否可以承接該下個月的產量

圖1 是針對條列式需求的示意圖,基本上將客戶的需求全部分析之後,並將客戶的需求進行條列式的分析,便可以整理出上述的條列式資料。
條列式需求主要是要透由系統性的方式將客戶的需求描述,變成是一個實際可以被執行的需求,透由這一個工具(方法,我喜歡稱他為工具),便可以清晰地整理出客人的需求出來。
姓名:許玄儒,小名:許小史,來自台灣高雄,喜好游泳、跑步。
就職:上海昊聲電子信息技術(股)有限公司
擔任:資深業務顧問、自動化系統架構師
專業經歷

代表性客戶:
最近發現了窮人與富人之間的最大的差異,除了腦袋思維不同之外,另外最大的不同點就是富人或是成功的人都有一個共通性就是自律,大多的成功人士或是富人對於自已的時間安排或是每天要做的事情都有一定的規律,且是用一種很變態的方式強迫自己一定要執行這個規律,於是我就給了自己每天要來寫部落格的動力,才有這個部落格出現。