2008年12月25日 星期四

S25 理想元件結構的自動化產出

我最終的理想是將所有產出的程式碼視為一種Model,用方法論約束以形成為固定類型的結構,再撰寫(或找到適合的)應用工具導出到各種方向的結果。這裡記錄的是自己現在認為“應該且可以”具有與可以實現的同步轉出功能。

分析的最小單位應該是Method。每個Method在以規定的方法撰寫並註記合格的註解時,就可以將所有Method視為能用同樣方式存取的Model,依其特性與原則必定可以設計出工具程式自動取得以下的資訊:

◎所有的傳入參數
◎所有的傳回可能值
◎所有的使用資料、參數(常數)與例外
◎所有的方法呼叫(包括同一個Class與其他的)
◎所有使用常數與呼叫方法所屬的Class(使用關聯)
◎使用XML定義流程處理、依設定檔deploy出攤平的程式碼(執行速度變慢才會做)
◎程式的完整流程(搭配輸出工具可以產出流程圖)
◎所有判斷指令內依賴的條件清單(每個條件都附上所有可能的傳回值)
◎所有的變更歷史清單(包含日期)與改變的程式
◎Commit時產生所有需要填寫的變更註解


Method是最基本的單位,往上推展依序是Class、Component Package、Package……到最後就會達到Use Case Package。從最底層精確地產出每一個單位的完整記錄,往上產出時就能拿到百分之百正確的資訊,到最後產生一個關聯與影響的龐大結構,我們可以選取任一個方法作為起點,就可以拿到自己使用到哪些方法(與所屬Class)與哪些方法使用自己完整追溯結果。其實還不只如此,我們甚至可以在任何一個指定層次取得之下的所有資訊匯整出文件。

一直以來額外花時間製作程式文件與修改後同步文件的工作都是程式開發者頭痛的問題,多花一倍時間把原來設計程式時的想法再寫一次,文件或程式修改後還得要同步另一份的資訊。文件與程式相互依賴的關係是如此之多,只要有任何一個沒有更新為對的資訊就會造成兩者各做各的,完全失去原有的意義。

“完整規定程式設計產出的結構、要求在該寫註解的地方填上必要資料“,這是我想做出來的程式設計方法論!

2008年12月24日 星期三

S24 沒人想做的事裡有值得學習的道理

進社會找到了第一份工作,當時使用的作業系統與程式語言是全國唯一使用的,基於自身經驗不足想說有個穩定的工作再說。公司接著陸續應徵了許多人要做與我相同的工作,但是大部份的人在發現使用的是這種冷門技術後都很快消失,很快地除了主管之外就屬我最資深。

公司是製作電話語音系統的,在第一年裡我慢慢摸索後建立了一些好用的函式庫,第二年主管將我調到連線的部門去學習撰寫Gateway的技術。工作了兩年多後因故離開那間公司,在新公司裡綜合了前面兩年的經驗,以不同的作業系統與程式語言用相同的概念製作了另一個語音傳真系統。

2006年主管推派我當公司的福委,在幹部選舉的會議裡被拱為主委,起初對這種吃力不討好的職務頗為反感,不過從另一個角度發現有人出錢讓自己模擬公司的運作也是不錯的事。經歷幾個事件後倒也瞭解了行事曆、事件流程與資源規畫的道理,反而在OO的分析方面有了不同的體認。

同年公司製作的自有平台產品後續需要有人維護,在所有能夠接手的人選裡由於我之前沒有在專案裡奮鬥,因此選擇了我。接到問題後想辦法重現、確認問題發生處、無法解決的、詢問原作者、修改後程式比對、確認並撰寫影響報告、另外加上補齊一些需要的文件,這些事情其實都是工程師們很不願意做的事情。

這段時間其實做得很痛苦,因為要在說明極少的情況下去看懂沒有任何一行是自己所寫的系統,任誰都沒法輕鬆愉快的,但是我卻在不知不覺中累積下不同的經驗。做一個動作時去明白為什麼要做、做的時候缺乏什麼資訊、那些資訊可以在什麼時候由什麼人產出、動作做完之後可能產生什麼影響、影響的範圍在哪裡,腦中累積很多很多之後就開始思索要怎麼做才能改善現有的狀況,有了初步想法之後就開始在這個部落格裡記錄。

2008年12月23日 星期二

S23 專案問題產生原因之可能破解

專案時程給得過於緊迫會有這幾個後遺症:功能急著想完成忽略所有可能的例外、SA與SD收集分解動作的不完全、動作難以抽取或分派形成過多的重覆程式碼,最終造成難以調整的複雜結構。根據以上問題我採用的應對方式是從後面開始解決起。

首先是適合調整的軟體架構,依之前提到的軟體結構設計出放置程式的Class,確保每一個分解動作的程式都是分離且容易移動的。其次是找出適合SA與SD使用的工具軟體,在分析設計每一個Use Case的同時還能以整個系統的角度來思考與放置。最後則是具有大量元件的函式庫,分析設計出來的分解動作在以前若已經依理想的方式包裝成元件,那麼應該有理想的解法同時包覆著許多例外狀況的處理而能夠直接引用,考慮的重心就可以移到多個Use Case共用時產生的各種條件變化。

公司在數個專案間有共用的Jar File,在其中一個專案上線後根據其他專案遇到的問題陸續修改了其中一個。後來在上線專案那邊找到同一個Jar File的重大改變,但是我只敢改在原來的檔案裡而無法直接更新為最新的版本,因為我無法保證沒有side effect。主管說多測試後就可以用,我回答說就算投入一百個人測試,只要沒測到全部的流程就有可能出事,全部的流程卻又是眼前無法提供出來的。

就像以前提到的紅豆與綠豆的例子,在不分類的狀況下全丟在一起是很快完成的,但是未來遇到想分開的情況時會花更多時間。剛開始只求功能的完成,只要豆子的總數對怎麼放都無謂,但是在專案到尾聲時任何一個變動都可能牽一髮而動全身;如何拿出可以掌握任何改變的關聯與影響的追溯結果,正是維持品質的關鍵因素。

2008年12月22日 星期一

S22 專案的痛苦在投入時間的不足

常聽人說專案給的時間都很緊迫所以沒法把設計作好,有次我就反問同事說如果現在有個專案給的時間非常充足,那會怎麼做來作好設計呢?他想了一下暫時沒有答案,不過我倒開始思考壓迫時程之後會造成設計上的什麼問題。

兩點之間最短的距離是一直線,完成功能最快的方式就是只直接寫出達成所需的全部動作,其他狀況就依經驗想得到的就做,所以此時完成的程式根本只夠做出達到目標的動作,沒空細想可能的變化。這樣先天不足的設計,若加上寫的人想偷懶只先寫一部分其他等測到再寫的後天不良,在動作層次不分、判斷條件不足的情況下如果測得出來問題就算幸運,沒測出來等客戶測到或是上線才爆發,那可只有一個慘字能形容。

客戶需求變更的管理是讓系統穩定的因素,但是時程過趕的狀況下,SA與SD沒有完全收集到Use Case應做的全部動作,未來發現缺少動作時就得再補上。一般無關緊要的動作直接補就沒事還沒話說,就怕缺少有關鍵性影響的動作與根據之前資訊所作的設計有所衝突,此時每加上一個就像一次重大的需求變更(或是需要抽取與分派的狀況卻無法重構而硬塞許多重覆的程式碼)。在這種狀況下作出來的設計怎麼可能有穩定的品質呢?

有次與總經理談話的時候他說:管理階層解決問題才是眼前重要的事。但是眼前的問題卻是之前專案模式所殘留下來的後遺症,再加上人設計程式時被測試抓到一項才承認一項的惰性,因而形成令人詬病的品質問題,到頭來需要投入更多額外的人力去找出問題。麻煩的是找到問題後即使知道怎麼改也會擔心影響其他大部分已經正常的功能而無法從根本改起,進而造就疊床架屋式的結構。

2008年12月21日 星期日

S21 做人的方法(15)──有關聯必有影響

物與物之間都是單獨的個體,初始時相互之間是沒有關聯的;物之間的關聯可以想作為有事發生才讓彼此產生關聯、互有影響。兩個物之間可以建立的關聯種類要看兩者之間能夠發生哪些事件來決定。人與人之間會有下面幾個事件與關聯的發生:生育(父子)、交往(情侶)、結婚(配偶)、看不順眼(仇家)、認同(朋友)……等等。

清楚與其他人與物之間的關聯、用對的態度面對接觸到的所有人與物是保持和諧的基礎。用錯誤的態度強欲建立起不自然的關係、在不應該做的時候依舊做了某件事、在該做某件事的時候沒做或未做好,就會使得事物之間的關聯失序而形成混亂。像是偷摘公園裡的花朵、將垃圾隨意丟棄、遇到親友視而不見……。

單純的事件失序時不見得會出問題,在很多時候問題都在於環環相扣的事件影響,像滾雪球般地造成連瑣效應而無可挽回。社會新聞裡偶而會看見原本兩個人在路上擦肩而過,因互看不順眼互罵進而互毆,甚至打電話邀集人馬示威,最後由於支援的朋友裡有人不慎擦槍走火而致人於死。觀看這類事件時人們難免會想:如果一開始擦撞時能夠忍一口氣的話就不會有這種後果。

任何的果都有因,從眼前的果探究形成的原因比較容易,但是在因一呈現時就能分析其利弊與影響而找出最理想作法的話,很多後來的事都可以消弭於無形。人生的經驗就在於收集世界上人事物的因果關係資訊,當某個人事物出現在眼前時,快速地分析不同的處理場景會造成哪些不同的結果影響,再從中抉擇出最有利於自己(有能力的話要同時有利於別人)的作法。

平時大量地收集資訊、條理化儲存資訊建立腦中的資料庫,遇到事件時快速分析並列舉出關聯資訊,最後判斷出一個結果最優化的決策。無論在什麼樣的場景,這個簡單的道理卻適用在整個人生。

2008年12月20日 星期六

S20 只要……就好了?!

這句話如果成立那真的很好,但是說這句話的時候到底是否評估過真正的關聯與影響?或者評估的內容有誤差?抑或僅是安慰可能受傷害的一方?就真的得審慎判斷了。

學生時代時若有同學請你抽煙同時還說:擔心什麼,只要不被教官看到就好了。半夜搭計程車闖了紅燈,司機說:半夜沒有人車,只要沒有警察就沒關係。若以會不會抓到角度看待自己所做的事情,社會秩序就會永遠這麼混亂,個人的生活同樣也會雜亂無章。

公司在其他客戶那邊有前一代的產品,客戶一直希望可以升級到現在的產品,但是由於在設計上的變更而無法直接相容升級。為了符合客戶的期待,有人提出一個簡單的方式:比對兩種產品,把相同的抽取出來放在一起,不相同的整理出相容的界面提出,然後不同的實作各自放在不同的地方,只要依此原則弄成三個專案就可以符合客戶需要。

主管問我的意見時我說:要滿足客戶的需求這樣最快沒錯,但是未來程式有變動時就很慘。一個改變要先研究出應該放在哪裡、程式改好後放回去可能得測試三遍、版本變更清單也要分三處處理,如果提出這種作法的人願意來做維護我就沒話說,我是絕對不接手這種東西的。主管說會再考慮看看,不過後來客戶也沒接受這種作法;一旦設計沒法相容在一起,幾乎是沒可能再合併的。

這個句型在顧前不顧後的案例發生時,至少還可以滿足眼前的需要;有時一聽就感覺有漏洞存在時才更令人無力,那時都會直接反問要是狀況之外的例外發生時該怎麼辦?想要能夠提出“只要……就好了”的說法,務必要對問題的所有發生條件與關聯影響都胸有成竹後再講出來,如此方能應付所有提問的可能狀況不致漏洞百出。

2008年12月19日 星期五

S19 讓流程中蘊藏彈性的思考

即使在軟體架構上使用了最靈活的設計,但是由於架構僅是靜態佈置的框架,實作的功能劇本仍然靠流程設計來達成;功能流程的設計的正確性與成熟度就得看是誰來設計的。如果動態的流程設計僵化而難以變動,不管放到多麼有彈性的軟體結構裡都完全不會有絲毫改善。

在流程設計過程中面對所有的動作時,可以思考是否符合這幾個原則:

整合與拆解:做一件事所需的動作拆解的步驟單位大小。切得太大步驟難以再細分、切得太小控制會太瑣碎,最理想的還是依現有層級的最適大小。分解步驟的恰當將會使得流程更容易設計,較不容易有盲點,也容易作不同方式的組合。

抽取與分派:某些不適合自己處理的步驟交由其他負責單位進行,將工作分派出去會讓自身的工作內容單純易管理;不過遇到別人的工作內容與自身的類似時就要考慮將工作抽取到同一個地方進行,以免造成多頭馬車的現象。

單一與重覆:一件事即使可以處理妥當,有時會需要處理集合內的多數相同事情;相對地也有可能將循環處理的事切分出可以單獨來做的。設計時若遇到有迴圈的地方,都盡可能把迴圈中的動作拉出為可以獨立呼叫的方法。

拆解與變動基本有三種不同的方向,但是其根本的原則還是在於把過程分解為一個個獨立無依賴關係的分解動作,再將各個動作放置到它應該存在的結構位置。只是憑個人去設計所有流程動作總會存在死角,藉由其他人的review與回饋才更能補齊各種可能的思緒。

2008年12月18日 星期四

S18 Review──問題來自過於相信自己

之前支援的專案在十一月下旬出了嚴重問題:執行交易後會保持交易執行中的狀態,無法恢復為等待執行。一開始沒有頭緒,花了很多時間後確認是UI被卡住,又再花了很多時間才知道是自己之前寫的一個API裡發生了無法離開的while迴圈。

那個API原來的用途是找出輸入元件前或後的下一個可輸入元件,尋找的流程是遇到下一個可輸入元件就傳回。設計之初認為再怎麼樣循環找回到自己一定會有結束,經過幾個月來的使用也沒有問題,卻沒想到在特別的操作狀況下會有欄位全部變成不能輸入的狀況,因此造成跑不出來的無窮迴圈,加上在AWT Event執行緒呼叫而使得其後排隊的UI動作全部不會運作。

檢討的最後客戶詢問我們:系統陸續出現這麼多問題的根本原因是什麼?我依據這次問題的經驗回答道:是因為對自己設計的執行流程過於自信。根據提出需求規格的人撰寫對應的方法時,依分解動作堆疊的同時心裡在意的是完成功能,相對於例外的處理則以自己的思維加以管控;然而例外的形成組合種類卻不是一時間可以完整條列的,因而寫出的程式邏輯間就形成潛在的漏洞。

程式能不能完成功能只是找出一條可以走到目的地的路,但是如何在動作的順序間條列出所有可能造成例外的情況,卻得一步一步審慎思考才有機會列舉更多。一個人思考或許列出95%以上,但是只要出現沒想到的狀況,整個系統就可能爆發出嚴重的問題,因此至少需要再有其他人的Review來儘可能協助補齊邏輯上的漏洞。其重要性比起找出程式更理想的寫法真的是大多了。

為人處世時亦是如此,光憑自己的直覺反應可能會太過衝動、粗心或鑽牛角尖,縱使三思過後也可能因為個人特性依然存在著盲點。在重大決定前與親朋好友稍作討論,取聽其他人的不同意見加以綜合應能找出一條更好的路。這幾年在想不透的人生問題上有這樣的朋友可以聽取我的疑問,提供我適切的回答,雖然說最後還是要自己想通才會有改變,但是有其他人幫忙檢視總是提供了更全面的資訊。

2008年12月17日 星期三

S17 做事的方法(15)──適當時機下的適當反應

人與人之間的關係是一種關聯,在發生某些事件的時候我們會與他人產生互動。一個事件的發生是由於人想達成什麼目的或是周遭發生了什麼事,每個關係人再根據事件產生時的連帶資訊,經由本身的判斷流程決定應怎麼回應。掌握好互動時機的發生、互動對象的確認與互動內容的選擇將會影響結果。

小朋友有時會問為什麼遇到親戚或鄰居時一定要打招呼?我說與他們見面是一種特殊的事件,在那個時機的最佳反應就是與對方打招呼。當然我們可以不打招呼,不過要去承擔應打招呼而不打時對方對自己可能產生的不好想法。在聽到內容不確定的消息時不要像傳聲筒般直接傳開,務必要根據現有資訊來研判其真偽,在確定內容真實之前試著完全封鎖消息不要再對任何人提起,這正是“謠言止於智者”所要告誡的。

公司有位主管在系統出問題時就會開始聯絡可能可以解決問題的人,這樣的反應動作是正確的。但是他在聯絡的同時不管正在處理的人已經看出問題都要求再跟原作者溝通,而且在工程師們正在討論時常會來問“問題在哪裡?要怎麼去改?”,感覺上我們付出了時間來處理額外的溝通與溝通時額外的資訊往來。

在不適當的時機作了多餘的溝通可能令人感覺麻煩,在應做事的時機沒有反應也可能造成困擾;即使在適當的時機作了反應,反應的內容太少可能不足以作出正確的判斷,反應的內容太多也可能形成資訊混淆而無法在第一時間找到重點。面對事件時的反應時機與資訊與接下來的處理動作有關聯,接下來的處理動作又與互動的對象有影響;在“牽一髮動全身”的關聯影響下,每一個細節都應該三思而行以避免決策的錯誤。

2008年12月16日 星期二

S16 做人的方法(14)──往好的方向解讀

前陣子寄了一封描述貧窮鄉下的投影片給朋友們,其中一位回覆說轉寄者的心態很可議,因為她看過很多人轉寄許多類似郵件卻一邊揮霍金錢的。我沒法想像可以單純表述世界上還有那樣地方的動作,為何在旁人眼裡卻會用不同方向來解讀而變調。這不禁讓我想到政論節目,每次總坐了一些人去“解讀”最近敵方陣營的行為,把為什麼要這麼做、這麼做有什麼好壞處以最有利於敵方且最不利於我方的方式講得頭頭是道。

比起運動場上拼鬥得你死我活後,雙方人員接受訪問時都讚揚對手的好表現的同時,為什麼政治上都只講對方的壞話,從來沒見過某方提出一個好方案時,另一方認同那的確是有利於全民的舉動呢?就算有雙方都發現某件事是應該去做的,在某方提出一個口號希望大家配合的同時,另一方又換個名字做一樣的動作還說對方有問題。

就像力的作用一般,人們的能力與資源如果用在指責對方,另一邊肯定會返回不小於原來指責的反彈;如果用在推動一個難以推行的好法案,另一方的認同將可以加速它的運行。一點點的“認同”會讓付出的心力有完全不同的結果,但是短期之內看來不可能出現這樣的政治人物。目前雙方又不乏許多根深蒂固的支持者,完全不管某個事件其真正的意義與影響直接就以陣營之間的利益去評斷,因此在很多地方(包括網路)都存在著爭論。在彼此相互拉址的狀況下,又怎可能一同向前進步?

另外,錢是以個人能力賺的,本來就有權力支配金錢的用途。收入要100%個人使用,或者是留下夠用的10%後把90%捐出去都是基於個人的意願,不應加以責難。我們只能說捐出百分比較多的人具有更多的同理心,在感同身受的胸懷下希望藉一己之力讓更多人獲取生活進步的機會。

2008年12月15日 星期一

S15 簡單的幽默(6)──切換為不同的視角

除了針對事物本身的特性作文章之外,還可以經由切換不同的角度來突顯既定事實被變調後的幽默。

有位業務在Facebook的塗鴉牆上留言:近年來收入越來越少,算命又說沒有偏財運,該怎麼辦才好?我建議說:如果沒有偏財運的話,不如將現在的工作當作兼差而把買彩券作為正職,應該就能改善。另一位單身同事也留言:最近手頭很緊卻有好朋友結婚,應該如何處理?我回答道:趕快在好朋友結婚前百日之內先結婚,這樣不僅可以省掉紅包錢還可以存下一小筆積蓄。

原先的事實都指定在一個限定的範圍裡,我們可以試著在它的上面或是平等的地方(通常是上面)找出能夠將之硬拗為截然不同意義的視角;前提當然是原來的事實不變而且完全合乎後來從另一視角所設計的劇本裡。第一個例子裡錢賺得少就用命運裡的財運來涵括,使之成為財運的子集合;第二個例子就設計讓主角先結婚,就可以順理成章地以習俗避開沒錢還要包紅包的事件。

工作忙時有些同事會感到頭痛,聊天時會談要怎麼樣可以有效地讓頭不會痛?總有人開玩笑地說:那就讓我用力跺你的腳吧,只要你的腳比頭還痛自然就會因注意腳痛而忘卻頭痛。男生積極邀約女生下班後搭他的便車回家,女生婉拒時可以試著說:搭我的便車回到巷口下車時,如果有鄰居看到就會感覺妳時常被不同的男生送回家會很有成就感喔。

在開玩笑的同時還要留意一個原則:“好的說法要用在對方身上,壞的譬喻得拿自己打比方”。儘量找好的說法來作範例當然可以避免無謂的不好觀感,倘使笑點一定要用不雅的方式才能呈現的話,就儘量用“如果是我的話……”作為開頭來陳述。適當地犧牲自己才會更有機會帶給別人歡樂,這是我所得到的經驗。

2008年12月14日 星期日

S14 簡單的幽默(5)──誇張聯想來龍去脈

與前面幾個類型比起來,這種作法是相對較難的部分,這是因為之前都是依現有資訊限定範圍作發揮,而此時必須以特定目的為基準,快速搜尋腦中的關聯記憶並決定好著重的論點。想要到達這個程度,腦袋裡堆積的記憶資料庫與指定題目的快速索引都是不可或缺的重要能力。

古代的說客是很明顯的例子,孔明過江東舌戰群儒在一群人輪番提問之下逐一找出反駁的道理令眾人無言,他當然對各國的立場與出路都作過一番分析,再以他的智慧作出即席的反應。這個部份在新聞上也很常見,政治人物在做錯事被發現的場合也都會找出一堆理由來合理化他的錯誤。

運用這樣原理在平時聊天,誇張化地導出一個現在的已知結果也是一種幽默;殺雞用牛刀、殺雞取卵的想法都帶有這樣的意味。另一個方向則是從現在的已知結果往後延伸其影響;杯弓蛇影也具有這樣的意味存在。想像出的所有原因或是所有的影響都必須具有一個特性:要能合理地同時滿足選定結果的所有限制條件,不然僅是牽強地符合的話也不過又是冷笑話而已。

曾看過一篇好笑的文章,它鎖定的標題是“一位計程車司機如何消化掉加油送的面紙”。內容敍述一位計程車司機每天因加油帶回家很多面紙,累積了幾年後只好租間倉庫來存放越來越多的面紙,司機太太說除了清潔用途之外即使每天都行房事來消耗還是用不完,所以現在也都送面紙給親朋好友並建議他們多行房事。司機說如果再這樣送下去,他每天開車的精神都很不好會擔心出車禍,所以希望加油站送點別的東西。

如果覺得不好笑的話,純粹是因為幽默的方向不同而已,不用太在意。那篇文章是以“加油送的面紙”鎖定一個方向引申,再加上使用“加油為什麼會是送面紙?”作為標題的話則是發展來源;一個既定標題的來龍去脈都有可能在上面大作文章,就看找出來的方向是否與標題有絕大多數的吻合。

2008年12月13日 星期六

S13 簡單的幽默(4)──提供遮蔽過的資訊

同事聊天時談到教育小孩的話題,問我會不會打小孩?我說會,而且是用“球棒”打。同事們都嚇了一跳。我補充說那只是遊戲用的“空心塑膠球棒”,打起來會痛但是不會受傷,大家才恍然大悟。先從一個正式的回答裡找出最能一鳴驚人的片段,有時真的可以造成令人哭笑不得的效果。

聊天的話題難免會碰到星座,每當問我是什麼星座時我總是回答“變態的雙魚”,對方通常會問:你是說雙魚很變態嗎?我說:不是,變態的是我。因為我的個性大半與星座書裡描述的雙魚座個性不同。這種方式其實很像演講或作文時使用的破題法,只是取用的內容是具有笑果的那種。

被遮蔽的資訊也可以是主詞,在對方認為你描述的內容是某種物件時,答案卻根本是另一種物件。十歲的小明高興地跑進大人的聚會要讓大家猜謎,他出題道:什麼東西是橢圓形、週邊有毛、而且還經常濕濕的?大人們臉色都變了責罵他怎麼可以猜這種東西?小明委屈地說答案是眼睛啊!大人們才安慰他說誤會他了,沒事。

有時直接坦承自己心裡想的就是對方所說的,也會教人不知該怎麼辦才好。佛洛依德的夢解析大多都是以性為說法,像是夢到蛇代表什麼、夢到球棒代表什麼等等(我沒記);某人有次聊到這個,我問他說我昨晚就直接夢到性器官,請問這代表什麼意義?旁人笑說:那代表你很色。

隔了一會兒,小明又跑進那群大人的聚會要大家猜謎,這次的謎題是:什麼東西是橢圓形、週邊有毛、而且還經常濕濕的?大人們說:咦?這不是剛才猜過了嗎?是眼睛啊!小明笑著說:不對,這次的答案是你們剛才想的那個。一對男女朋友躺在沙灘上作日光浴,女問男說:你心裡在想什麼?男的笑答:我想的跟妳想的一樣。女的立即給男的一巴掌:不要臉!

面對遮蔽的資訊時,每個人習慣用自己的思維去補齊缺失的部份以求一個完整的答案,在接續的過程中如果與正解有差異就會產生幽默的效果。但也要小心會有人根據這個原理來提問,進而探索旁人的內心想法。(我就是這一類型的人)

2008年12月12日 星期五

S12 簡單的幽默(3)──著眼於不同的特性

公司對因專案而長期駐點在客戶端的同事會有定期的拜訪,招待一頓午餐時順便宣導一下公司近期政策並聽取同仁的意見。

這次人事主管提出公司樓上將要設置健身房,詢問大家是否會在下班之餘到公司使用。同仁普遍的意見都說專案實在太忙,沒辦法再花時間到公司健身;人事主管說要是專案經理每週固定一天讓大家準時下班的話,大家回答說要是準時下班就會回家休息而不會去。看起來要多花一段時間才能健身並不會是理想的選擇。

這時我提議說,我們每個專案裡其實都有很多電腦,或許可以讓專案的同仁效法陶侃搬磚的精神,每天早上一來先把電腦搬到樓上裝起來用(那個專案在四樓,而且只能走樓梯),下班時再搬到一樓放好。當時每個人當然是無言以對啦,不過根據“健身”這個需求,與其他類似需求作關聯與搜尋後,我鎖定了古代陶侃的例子作為話題的衍伸。

朋友聊天時說她前幾天騎機車被車子從後面撞倒,一時之間根本搞不清楚是誰撞她的,我開玩笑地跟她說那妳以後騎車必須騎快一點,她問為什麼,我說這樣一來至少車禍時妳可以看清楚妳撞到了誰。或者像這個笑話裡的描述,妻問:我新燙的頭髮看起來會不會很醜?夫答:妳的醜跟頭髮完全沒有關係。

在一件事中找出不重要的特性作文章雖然可以有牛頭不對馬嘴的幽默,但在實際生活運用時要特別留意對方的感受。如果在對方迫切需要正確的回饋時,卻發現你言不及義地越扯越遠,那時就有可能會有意料之外的反應。(像我就會觀察對方是否開始左顧右盼地像在找球棒,那時就該切回正題了……)

2008年12月11日 星期四

S11 簡單的幽默(2)──只滿足需求的結論

跳脫置換字詞與句型的小框架,根據整個描述的真實意義還有可以變化的地方,那就是定義出該段話裡所有的需求,再判斷是否有某個需求可以找出符合但是很誇張的結論。

有位同事花了不少錢購買一個具有濾水功能的水壺,平時都裝大型濾水器的溫水過濾飲用。某天大型濾水器停水,同事說即使是過濾茶水間水龍頭出來的水他就可以喝,我好奇地問他:那要是裝廁所洗手台上的水可以喝嗎?他說他不敢,不過小時候我倒是曾經對嘴直接喝過啦。(要再誇張的話可以問能否裝馬桶裡的水,不過幽默的內容要在好笑與不傷人的狀況下取得平衡)

有位同事中午都會出去步行半個小時以上,有一天中午外面下起雨來,他擔心地說今天沒辦法運動,根據他“想運動來消耗熱量”的需求,其他同事就開始幫他出點子。首先建議可以上下樓梯來運動,他說那對腳的負擔太大;改到一樓大廳的空地踱步兜圈子,他說繞圈的運動量不夠;那麼就加強為兜圈子時抱著一張椅子,他說別人會把他當精神病患看。

聊了一會兒後忽然發現雨變小了,同事還是穿上輕便雨衣出去步行。不過經由這次的事件倒是可以發現原來只描述一個主要功能的Use Case,可以在不斷地提問與答覆之間慢慢地摸索出功能的邊界限制,也更有利於作出一個趨近於完全符合客戶需求的東西。

小題大作地完成別人話中隱含的需求是這種幽默方式的重點。更進階的此種幽默,是讓找出來的結論不管是做或不做都有笑點;要達成的要件是原先的需求應該是要恰好的中庸,但是建議結論裡的做與不做卻是太少與太多。

2008年12月10日 星期三

S10 簡單的幽默(1)──相同表面相異意義

在生活中除了達成目標的成就喜悅外,好笑的事同樣也可以帶來歡笑。工程人員通常給人的印象都是孤僻且沉悶的,但是運用Interface的基本思維卻是可以造就一些幽默出來。雖然Inteface型的幽默有時會很冷,不過出人意表的聯想也能夠有令人擊掌叫好的產出。

當一句話由旁人口中說出時,就可以開始快速檢視其中的字詞有沒有可以抽出成介面,然後把那個字詞換成介面相同但意義迥異的另一個字詞。最典型的代表就是同音異義字,常看綜藝節目的人應該可以從主持人與來賓的對話裡發現很多這種用法。

再往上一層可以針對一個句子作聯想,把第一句的語意抽取出來,然後承接語意與句型卻在下一段展現出乎意料的轉變以製造笑料。這裡有幾個範例:“跟網友見面時要小心,以免對方因為你太醜而把你揍得半死”、“半夜不要照鏡子,否則會嚇到自己”、“慎防吃到不潔的東西搞壞腸胃,尤其不要吃自己煮的菜”。

雖說這種幽默需要快速的聯想力找出許多待置換的內容再選擇最好玩的,但是在文字學習方面也有很多這樣的考題,像是一個句子裡某個字寫上注音要求填上國字 (字音)、或是在一段話裡找出錯別字訂正(字形),這些都是在小學國語考試時經常出現的題型。心裡對於那個注音或字形(Interface)能列出多少單字(Class),關係著是否能夠解答那個問題。

以上是用字詞的抽換與句型的接續的基本型幽默。(現在很多腦筋急轉彎都是運用這個方式)

2008年12月9日 星期二

S09 生活中的事件追溯

生活裡與事件記錄有關的典型是日記與報紙,兩者都是記錄一天內所發生的事件。在寫日記與看報紙時面對的都是今天的內容,我們會很容易就把注意力放在當天的範圍,而忽略了事件與事件之間關聯。

媒體在發生轟動的社會新聞時會在初期的幾天不斷地重覆報導,鉅細靡遺地記載了很多的相關內幕,但是事件熱潮過後的追蹤報導常常少得可憐只是說個結果;如果媒體後來沒有提起,讀者根本不會想起事件曾經是什麼內容。對於類似的新聞事件,我們可以比對它們的形成原因、當事人想法與作法、還有後續產生的影響的異同以獲取其經驗值。

寫日記時亦是如此,在記錄一天的事件時如果沒有連帶地回顧自己之前面對類似事件時的處理與心情,又怎麼查覺出自己的行為與心境是否更加成熟?人的成長部分是經由回顧與反省來學習最佳的處理方式,這需要比對差異的部份才能得知前後的不同並作適當的改進。

當著眼於一件特定事件時,瞭解其前因後果是必然的動作;研究輸入給當事人的所有資訊,分析當事人根據什麼判斷才作出那樣的決定是一件很有趣的事。當觀察的事件數量多的時候,記憶並歸納眾人的思維與決定,可以慢慢地對於人性有更多的認識。倘使搭配一些名家深入說明的文章,有時可以更快速地推算出結論來。

另外,事與物是相互有緊密關連的:做事時會使用到物或是留下物,物的存在也需要事加以處理。因多做事減少的物(像是大量獵殺動物、濫墾森林)或是因少做事增加的物(不帶走自己拿去的垃圾、有毒廢棄物不依規定回收而亂丟),都可能對個人、社會甚至地球造成難以回復的負面影響。

2008年12月8日 星期一

S08 生活中的物品關聯

一般來說物品存放時會根據其特性分開保管,像是浴室用品放在浴室、寢具放在房間;有些時候會依存取習慣來放置,是書桌上同時會放著平時常用的所有物品、廚房流理台上放著等待處理的食材。有的時候除了一般的擺放處之外還會有放置備份品的倉儲空間。以上是物品存放的場所分別。

物品的存放數量與使用類型大致有以下幾種:一是數量少且取用量也少,像梳子、筆、傘等;二是數量多且取用量也多,像米、綠豆等;三是數量多但取用量少,而且需要識別取用的物品,像圖書、光碟片等。前兩種由於沒有識別物件的特性,取用時依序找過所有可能放置的地方找到就用,但是圖書與光碟片這兩種就必定得找到心裡想要的那一個才能滿足需求。

以光碟片來說,量很少時應是放在一本收集簿或布丁桶裡,需要時逐一檢視到找到為止。當數量越來越多時,收集簿或布丁桶的數量自然增多,等到找一片光碟需要太長的時間就會重新整理,把同類型的光碟片放在同一個收集簿或布丁桶減少尋找時間。數量再多到兩三千片時,每一種類型的數量也多到找起來很辛苦時,就會希望還有更好的儲存方式。

後來我找到有光碟櫃這樣的產品,用最小的空間吊掛著三百個棉套可放六百片光碟片,每個棉套位置都定義一個編號在放光碟時記錄其放置編號,最後把所有光碟片與放置編號收集起來製作成索引檔,未來要拿光碟片時只要參考索引檔便可按圖索驥一次拿到正確的結果。快速在大量的同質物品中找到可識別的唯一一個是這種管理方式的優點,但是放置時需要再額外建立對應的關聯是連帶的缺點,而且管理一旦失聯要再重建又得花很大的功夫。

根據物品使用與存放的特性有不同的管理方式,也有不同的優缺點。選擇某種物品的最佳存放方式也是一項決策。

2008年12月7日 星期日

S07 做事的方法(14)──自己做事的痕跡

“凡走過必留下痕跡”,做過的事也必然會留下相對的證據。對於自己曾經做過的事,不想被別人發現的就會儘量抹滅證據,在證據無法消除時就佈置假證據作為煙幕;希望日後可以回溯的就會留下詳細的記錄以便各方面的查詢;至於沒什麼重要性的自然完全不會在意有沒有留下記錄。

同樣的事在不同的情境下會有不同的重要性,因而對應不同的處理方式。就以“小便”這件事來說,在日常生活裡大家想小便就去廁所,只需要知道小便的分解動作與廁所的位置即可;但是在住院的時候,某些情況下醫生會要求記錄次數與數量作為診療的參考資料;至於臨時在路邊小便時,通常會儘量遮掩不被發現並在事後掩埋痕跡。(現在於路邊小便時不遮飾且不掩埋的人越來越多,這個現象不在本次討論範圍)

小時候考試曾有這樣的是非題:深夜路上沒有其他人車時為了節省時間可以闖紅燈,每個人為了理想的考試結果都會填否,但是在實際的生活裡呢?不需要考試結果時還是有人真的為了節省時間而闖了紅燈或是其他的違規。這代表了什麼樣的含意呢? 

當我們做一件事時,可以先想想做的原因、做的結果與做的影響作為判斷的依據,但是最重要的是選擇了什麼樣的標準來檢驗自己的行為?人民在路上是否會違規要看有沒有交警存在,官員是否收賄要看有沒有被狗仔跟拍的可能;檢驗如果只是為了做給檢視的對象看,觀念根本上與做黑心產品無異。

一個人的良好道德依賴時時用內心的良知來檢驗言行慢慢建立而成的,外在的檢驗對象大多只存在於一時,而內心的標準卻無時無地影響我們的作為。為了應付外在的檢驗一時變好,卻在無人發現時言行卑劣,如此表裡不一的行為總會有被比對出來的時候,進而造成對自己的重大負面影響。

2008年12月6日 星期六

S06 見微知著──觀察處理資源的作法

過了將近一個月,我再度回到曾經貼紙條提醒大家容器可以回收的那個專案,當初我列印的告示還是貼在相同的地方,而那些容器的蹤跡也還是同樣出現在資源回收桶與垃圾桶裡。對此我並不再有特別的動作,只是感覺到觀察這樣的小地方的確可以看出一個人的作法。

對於因應自己需求產生的物品,理應由自己將之放置到它該去的地方,對於連這樣的小事都無法盡責的人,我會將之歸類到不負責任的群組裡。一年半前在對公司新人講課時,曾有部門同事進來旁聽,在離去時我觀察到他們沒有把自己放在座椅下的空飲料杯帶出去;當時我故意留下來想對新人說明沒有清理好自己應清理的垃圾時造成的影響,但卻也發現已經有人主動地把那些紙杯都處理掉了。

偶而會有人將飲料空杯直接放在我的車上,方便處理的時候我會帶走,但要是不方便時我就會直接將之放在地上。對於丟垃圾的人來說,他不想負責拿去丟,又覺得直接丟地方違背自己的道德觀,所以就採用間接的作法:把丟垃圾的責任推到不相干的人身上。對於不相干被波及的人來說,當然可以好心地幫那人擦屁股式地做好,也可以順著他的想法預設自己的車不在那裡而形成垃圾直接落地的結果。

我是從來不亂丟垃圾的,即使是再小的紙屑都會先收進口袋裡等想到時再拿出來丟;我也同樣地告誡家人與朋友,應該做好自己該做的事。現在在路上如果看到垃圾時,不管是走路或騎車,只要不會影響別人就會立即撿拾起來另外再丟到垃圾桶去;雖然以往也像路人那樣視而不見,不過在瞭解事物的道理後,反倒願意多花一點心力讓事物返回正軌以減少額外的影響。

2008年12月5日 星期五

S05 做人的方法(13)──推算對方的想法

人就像一個系統,心裡的想法也像是黑盒子般不易得知。路遙知馬力、日久見人心,描述的是時日一久就可以累積足夠的經驗值去明白一個人的內心;當然我們也可以藉由觀察其言行與詢問一些設計過的問題較快速地測試一下,然而卻也得提防對方反過來灌輸假象的可能。

要明白一個人的個性,大致來說是知道該人在面對某一種特定狀況下的決定是什麼。人在面對狀況時,參考的各種變因或多或少有點不同,思考的模式也都有些差異。在敵對時知道對手的個性與想法,就有制敵機先的可能;在合作時知道同伴的個性與想法,就有發揮所長的好處;在帶人時知道下屬的個性與想法,就可以將人放在最適當的位置。

雖然日久可見人心,但是能夠越快瞭解一個人總是比較有利的。最快速的方式就是用相人術,觀察對方的面相、言談與舉止,利用前人累積的觀察經驗立即知道對方時常作什麼樣的想法;最簡單的方式可以使用占星術,利用生日資訊簡單地得到其特性作為初步的認知;最沒辦法的方式,就是利用時間慢慢累積對方做事的方法來反推其個性與想法。

有時在感覺對方可能是什麼樣的人時,我會隨時提出幾個小問題再根據答案來分析,因為就像開車時會反應出駕駛者的直覺想法,突如其來的單刀直入式問題也會令人不知所措而直接回答。就像是系統在面對尋常的測試方式時很可能過關,但是忽然改變測試的方法與內容時說不定就會發現內部與期望不同的地方。

不著痕跡地觀察言行、不經意地詢問意料外的問題,對於一個人我們可以這麼地搜集他所表露出來的各種記錄,再藉由易地而處地思維收集對方身邊所有可能的變因,進而推斷出其內心想法。

2008年12月4日 星期四

S04 追查事件表露的跡象

車輛對我們來說就像是一個黑盒子,它會有什麼樣的行為與反應都仰賴內部(駕駛者)的控制,我們沒法得知內部的行為模式,只能憑它表露的跡象來評估它接下來會可能會發生什麼動作。根據事件所有已知資訊去推論當事人的想法,在生活中是時常可見的事──尤其是在談話性政論節目裡。

在談話性節目裡,常可見到評論者根據已知的事實加以衍伸與推論,雖然說來頭頭是道,但是有些從根本上完全是斷章取義,只選擇對自己有利的部分加以說明。由於評論者刻意隱蔽了某些資訊,倘使群眾沒有查覺到而只聽信他說出來的部分,就會被引導到刻意安排的邏輯裡而深信不移。有些人則深信某公眾人物的個人魅力,會自動忽略或扭曲解讀對他不利的事件;這樣得到的結果自然是有失公平的。

事件發生後,總需要聽取雙方不同的說法,加以收集整理後套用邏輯來篩選不合的條件,剩下合理的狀況才有可能隱藏著正解。但是由於當事人與媒體都有可能隱藏正確的資訊或是增加不正確的煙霧,更添增了推理的重要性。

推理是嘗試著找出在各種條件限制下,唯一的一種符合所有條件的答案。當有複數的答案存在時,不是重新檢視現有的答案是否推算錯誤,就是再依據現況找出其他的限制條件繼續篩選,直到符合的答案只有一個為止。有時也需要留意裡面夾雜有假的限制條件,屆時得額外花費精力判斷出哪些是假的風聲而已。

對於自己沒法明瞭內部的黑盒子,感受氣氛的不尋常、針對可疑處測試,大膽假設並小心求證是很理想的作法。

註:“大膽假設,小心求證;認真做事,嚴肅做人”是胡適送字畫給梁實秋時題在上頭的對聯。

2008年12月3日 星期三

S03 駕駛者的個性

有位朋友說過,瞭解一個人個性最好的方式就是觀察他開車的方式,因為開車時的反應都是人依眼前資訊直覺反應的結果。這句話提供了我在上下班途中一個很好的打發時間方式。

不打燈就變換車道的人很明顯地比較自我;跟車跟得很近的人比較自信;車子開得很慢的人比較封閉保守;車子排氣聲大、機車排氣管上翹的人比較自私而不顧他人感受;快速變換車道求快的人比較躁進;無視交通規則(像闖紅燈)而繼續的人比較沒有法治觀念……。馬路上就如同人生一般,存在著各式各樣的人。

有時看到年輕人騎機車載著女友橫衝直撞,不時有超速、闖紅燈的現象,不禁會聯想坐在後座的女人嫁給那樣的人之後會有什麼樣的生活?有時被前面騎車上翹的排氣噴到時也會想,不知道前面的人同樣被別人的排氣噴到時會不會有快感?有時被忽然切入自己行進路線的車嚇到時,也會用同樣的方式切回到對方的前面,不知道他有什麼樣的感受?

在一個忽然下起雨的日子,騎機車的我因為雨衣沒弄好想靠邊整理,但是靠邊時忘記先打方向燈而使用一個從右邊超車的年輕人因嚇到而對我罵了一句;一時間心情不好很想快速追上去超他的車,但是轉念一想是自己沒有做好才會造成後面的一連串事件,因而降回原來的速度繼續行駛。就在兩個路口後就看到一位女騎士因前面機車忽然切出而剎車滑倒的事故,我扶起她的機車推到路旁見她沒事後坐回車上,不禁回想了一下這五分鐘內所發生的一切,彷彿如同一種啟示。

在行車中觀察人生的百態同時反省自己的行為,是不是也算是一種修行?

2008年12月2日 星期二

S02 偵測路上人車的流向

路上總有許多車與行人遍佈,要能平安地到達目的地,必須密切注意路上所有物件,務必不要讓自己與他們產生任何的時間與空間的“同時交集”。宣導交通安全時會提醒我們要注意什麼什麼的,但是行車安全總歸只有一句話:去感受所有人車的流向!

在路上要避免只將注意力集中在一點上,因為重心放置的輕重不同會造成反應上快慢不一。我習慣將視線放在約二至四輛車左右的距離前,且只固定在那個位置而不是物件上;這樣做的目的是為了以眼睛的餘光儘量地收集所有可能有例外行進路線的人車,以期快速地做出反應。

同事朋友們公認台北的馬路上要數計程車與公車最為危險,因為他們最常在無法預期的狀況切到不同車道而造成問題。其實計程車與公車的特別行為也是有跡象可尋的:公車除了依路線的左右轉外只有靠站的動作,觀察更前面的地方是否有公車站將會是避免與公車發生碰撞的關鍵;空計程車會在路邊疑似有想搭車的人時靠邊,同樣把注意力放一點在路旁的人就可以避免;有人搭的計程車風險較小,但是也要提防乘客忽然間要求的轉變與車門的開啟,這是較困難的部分。

在路上的累積經驗夠多時,去分析各種車輛的行為並找出原因,就可以在路上判斷該原因是否存在來決定前車是否會有變換車道的動作。當然也會有例外,一次在兩線的慢車道上,我以時速50公里跟在一部靠左邊轎車的右後方,他在某個巷口忽然邊打右轉燈邊右轉,來不及剎車的我只好跟著他的轉彎弧度右轉而避免事故。另一次是以時速70公里通過一個十字路口,結果有部轎車從右方路口闖紅燈右轉然後停車並立即開啟駕駛座的車門,我下意識地繞過車門然後停在不遠的前面。

由於前車的突然變化,造成我的行車路線也跟著立即改變。有時在新聞上會看見駕車者因閃避前車的改變反而遭後方車輛追撞的後果。這兩次能夠化險夷的關鍵在於我隨時使用照後鏡觀察左右兩邊的後方沒有其他車輛,所以才能夠保證自己的變化不至於造成後車的影響。

隨時觀察是否有人車可能影響自己,同時注意自己是否可能影響其他人車,會是行車安全必須的重要保證。(在收集資訊時若感覺可能有風險則不要涉險,因為保守而不躁進只會浪費一點時間,但是判斷錯誤卻會造成一生的遺憾)

2008年12月1日 星期一

S01 生活中的推理無所不在

每個人都有自己的生活。在生活裡會遇到許許多多的人事物,雖然絕大部分都是閃過即逝的,但是若用心觀察其中一個相遇的人事物,總是會有不同的收獲。

對於物,我們可以去觀察它的外觀與特性、瞭解如何使用。像是認識各種動植物、記憶城市與道路等地理位置、能夠操作不同的科技產品都是我們可以做到的。多去觀察與瞭解各種物件能夠增加我們心中的資料庫,也會是旁人評斷你認識東西多寡的依據。

對於事,可以分為兩種:一種是發生的事,像是新聞事件與歷史事件,這類與自己沒有切身關係的事,使用對待物的方式加以記憶相關資訊即可;另一種是達到特定目的的事,像是釣魚這樣,需要透過學習與經歷才能夠做好的事,這是擴展我們人生經驗的唯一途徑。事與物的記錄,就像是個人腦中的百科全書建置,記憶得越多越正確就越可以快速地舉一反三。

對於人,如果是跟自己相關性較高的也是要記住他的資訊,接著要明白他的個性,再來可以判斷這個人對於自己要達成某件事是否有影響。在路上擦肩而過的人,隨時的記憶有可能在重要的案件提供關鍵的線索;觀察其動作,或許可以得知他正在做的事會有什麼樣的結果。

觀察、記憶、組織、關聯,四個必要的動作可以讓周遭所有的人、事、物的資訊得以留存,同時在未來需要的時候從腦海中取用;但是付出的代價是要耗費腦中的資源來達成這個功能。有些人願意作這種犧牲而致力於資訊的收集,也有些人因為避免麻煩而忽略不做;聰明的你會怎麼決定呢?

2008年10月25日 星期六

R25 程式設計中得到的快樂

最近認真想了一下程式設計到底有什麼快樂?聽取一部分同事的想法是說,從無到有創造出自己想做的產出、或者是花費心力到最後克服一個難做的功能。的確,第一層的快樂是產出結果,做出自己想做的東西並看著別人樂意去使用。(只是在專案裡由於時間的壓迫與客戶的緊逼,痛苦早就多於快樂)

後來與同事們一起在專案裡奮鬥的同時,看著他們不管在SA或SD上都投入很多時間做重覆性很高的工作,後來便開始設計小工具將來處理重覆性的工作。當小工具確切地符合同事的需要,看著他們用很少時間就完成原本要很久的工作,這是第二層的快樂。(以前開發的小工具也是基於這樣的想法所創作的──包括彩券對獎程式 ^^)

現在則是著眼在所有人都能夠設計出其他人一看就懂的系統或元件,同時藉由工具的輔佐讓開發的時間不會超過太多,卻可以有效減少以後維護與改變的影響。努力地把想法轉換為文章,就是希望所有人(至少是我所屬開發團隊裡的所有人)都有同樣的設計思維,把觀念應用在各種不同的領域上。這是第三層的快樂。

2005/10在上SUN OO-226課程之後,我曾經問講師一個問題:如何讓那些將設計重心放在快速開發的人接受OOAD的觀念?講師想了一會兒後回答那是沒有辦法的。2008/04大陸某大公司的資深工程師也問我類似的問題,我們簡短討論之後認為設計是有很多面向的,有人只看到快速的好處而我們也沒法說服他們去兼顧其他的角度。

從開始寫部落格至今,我為了讓全部的想法與作法能夠接續,在所有原先無法連接起來的斷點花了很多時間思索其原因結果並決定作法,同時藉著專案的機會加以初步驗證。或許沒有看過太多理論的書籍不會有對某些理論先入為主的包袱,才能靜靜地感受事物本質的流向。如今面對同樣的問題,我會淡淡地回答一句:讓你創造出來的全部事物在它該在的位置上做它應做的事,這是我心中回歸的設計本質;如此才不會讓那些事物因為趨向於某一目的而造成難以整理的混亂。

我想,這個簡單的道理應該適用在人生裡的任何一個角落吧。

2008年10月24日 星期五

R24 程式設計想法的差異

在某些電影的逃命場景裡,主角敏捷地利用周遭的地形地物脫離敵人的追殺,我們在感嘆主角的反應過人之際,是否也同樣地注意到他對可利用的一切事先所下的苦功?要能快速地使用手邊的資源,勢必要先瞭解該物件的特性與用法,才能依照心中的想法組合為可達成目標的使用順序。

坊間有很多學會XX語言程式設計之類的書籍,對於初次接觸程式語言的新手,那的確可以快速學會該語言的特性與用法。不過那樣的書主要是以學會並使用XX語言為主,裡面並不會說明如何去條理化達成功能的思緒;也就是說它提供的是準備被我們使用的資源,怎麼去用端看設計者的心裡怎麼想。

心中對於完成功能所作的系統佈置就像是Model,以某種程式語言根據心中想法做出的產出就像是View;在想法正確的前提下依功能流程動作在各種語言下所找到的最佳化寫法,是程式寫作上應作的努力。在特定專業領域裡累積的分析設計經驗,也應該能夠在各種不同的平台與語言下落實,其原因是達成功能的動作都可以在各種平台與語言找到對應的程式寫法。

在程式設計的領域裡,精通某種程式語言到出神入化的境界並能對各種需求做出對的結果,充其量只是很會使用該程式語言,如何系統化地佈置達成功能的全部想法以滿足各種不同面向的需求才是成功的關鍵。在需求不變時達成功能並不困難,在需要各種不同的分析統計或是變更時作最小的改變同時得知影響的範圍,我認為這才是設計的目的。

另外,文件記錄的應該是屬於心中想法的Model,而不應該是程式語言的呼叫方法。程式語言的函式庫有文件詳盡地說明各個方法,但是達成功能的分解動作與流程是深入分析過的人才有的結論,這是沒有地方可以找到的。記錄下達成目標的流程動作並對應到為此而宣告的方法與使用到的資源,才是這個系統裡最重要的資產。

2008年10月23日 星期四

R23 設計在於管理(4)──便利統計分析

得知每一個使用關聯的影響後,應該要把系統所有元件的範圍與方法的影響都記錄下來,作為各種不同用途的統計分析之用。製作一個Model的責任,是在於將之塑造為從各個不同面向都能得到對應的統計分析結果。

統計交易Log的初期,客戶想要知道的是每個Client執行每一支交易的時間是多少?送出到傳回的時間是多少?是否有執行時間過長的交易?其實在Log裡所有可得到的資訊有很多,像是交易開始的日期時間、使用者代號、工作站代號、交易代號與交易執行時間、交易送出到傳回的時間等等,如果只根據現有需求設計了工作站代號、交易執行時間、交易送出到傳回的時間三者關聯的統計報表,那麼在遇到客戶又想知道各個Client某天曾經執行過的交易代號時,應該會當場傻眼吧。

在這份Log裡很明白地具有六個特性,雖然客戶一開始只想要三個,但是設計時僅“滿足客戶現有需求”而沒有包含全部特性,那麼在未來客戶需要更多的屬性時該怪客戶要的跟原來不一樣嗎?倘使已經得知物件全部有那麼多的特性,在設計裡本來就“應該”包容全部以應付未來可能的改變;有時會因為特性實在與現有需求多出太多,那麼在取捨的時候就應有覺悟可能會產生要用到捨棄部份的風險。

公司定義的專案目標是以“滿足客戶現有需求”為方針。在揭示這個方向的同時我曾詢問要“如何讓專案的產出更有彈性地符合可能的改變?”,很可惜地並沒有人可以明確地定義,甚至還有人認為沒有必要。我依然固執地朝這個方向努力……。

2008年10月22日 星期三

R22 做人的方法(12)──易地而處

從關聯的兩邊往另一個方向看過去,是完全不同的風景。在有使用關聯的兩個物件之間,一個會是使用者,另一個則是被使用者;使用者全盤瞭解操作的劇本與時機,被使用者只能被動地應要求而行動。

人與人的相處同樣是由某些事所形成的關聯所建立的。像是客戶向公司訂購某個系統,公司就找我們組成專案團隊進駐開發;客戶告知我們關於需求的想法,而我們努力回饋分析設計的結果並形成產出。有的時候卻難免會形成開發團隊常會怪客戶需求一直變更,客戶也怪作出來的系統不穩定或品質不好這樣的循環。

想像客戶對於一個交易畫面的需求原本是三個人工輸入的欄位,在測試的時候發現第三個欄位可以從某個地方取得,因而希望作個小的需求變更改為自動取得。有些開發者會覺得任何需求變更都會造成多做一些事,感覺困擾而拒絕這個要求。如果自動取得的動作需要改變太多或是重要的地方,那麼為了避免風險自然應該反對;但若是這個改變只是引用現在系統現用的API或作點包裝,著眼在未來所有使用者節省的時間,則是應該接受。

面對的變化需要付出多少額外的代價,同時可以獲取什麼樣的利益是一般人作抉擇時的判斷,然而大多數的人僅以自己的角度來評估。一件事的發生絕大多數都像關聯一樣是雙方面的,懂得這層道理的人知道要從雙方的觀點來抓取最適合的平衡點,以最多、最有效的結果來評斷。同時會去關心對方想法的人,通常也會獲得對方釋出更多的善意。

關聯就是人與人之間的相處,人際關係是否和諧得看當事人是否能夠從對方的角度來看待事物。

2008年10月21日 星期二

R21 設計在於管理(3)──快速追溯關聯

在設計上能夠界定使用關聯的範圍時,接下來要做的是定義每個關聯的影響深度,從這裡可以得知改變的影響。影響會分為上下兩層,對下的使用改變主要是看流程中增減了哪些動作,對上則是看有哪些動作呼叫了自己。其中往上的追溯最為重要,因為自己改變就有可能令上層使用自己的方式有所不同。

程式開發工具裡都會有追溯的功能,不管是使用上的追溯或是繼承上的追溯都有,只要按一下熱鍵就能將指定Class、Method或Data有關聯的地方全部都列示出來。許多人看準了這個功能的好用,因而完全不在設計上作追溯的管理僅在需要的時候才利用這個功能,同時號稱這樣找出來的是完全正確的結果,不像追溯表那樣可能有無法同步的風險。

這樣的想法在單一的關聯上是正確的,但是根據許多人的經驗:使用上找使用關聯時往上找三層就受不了,繼承關聯在五層後會開始混淆,如此等比級數的發散光是找一個就可能吃不消,更何況系統裡的總數不知道有多少。每次系統上出了較嚴重的問題時,客戶就會要求在找出問題的同時,必須報告改變部分所作的改變會影響哪些功能。我也曾經為了一個重大的改變花了整個下午去定位並分析並向上追溯,搞到頭昏腦脹之後也沒法肯定那些是否為完全正確的結果。

越大型的專案,對於使用上的關聯管理起來越為複雜。沒有對的方法與工具,管理起來都是吃力不討好;沒有正確的觀念作為前導,同時也會忽略使用關聯的重要性。等到臨時需要改變,追溯出來的結果沒有涵蓋到執行時發生錯誤的地方,自然會被視為沒有品質可言。

與每層都追溯使用方法的方式比起來,我所想用的分層追溯應是相對簡單的作法。以每個介面作為切割點,根據現有的實作記錄往下使用的介面方法(超理想作法是由程式碼轉出使用關聯),等到需要的時候可立即追溯到上一層的介面使用;如果我的想法證明可以落實的話,應該可以立即追溯到最上層的所有使用功能。

2008年10月20日 星期一

R20 做事的方法(13)──分合之間的變化

想像一下我們進入一間小吃店點了一個粽子,然後觀察前後的物件變化。老板會拿一個碟子作為容器,放入粽子後加上醬料並擺上一個叉子送到桌上,我們食用之後消耗掉粽子與醬料,碟子與叉子則被收進廚房堆在一起等待清洗。雖然只是平常不過的動作,卻蘊含了物件間分與合的變化。

在起始的情況下物件是根據類別存放的,碟子、叉子、粽子與醬料都各自放成一堆,在事件發生(客人需要粽子)時才各取一個聚合為一份餐點。食用後的那份碟子與叉子會被收集到廚房,那裡有整間店全部待洗的餐具,等到清洗後才又回到各自存放的初始狀態。

從存放區的角度看,所有種類的物件都各自在一個集合裡;從客人的角度看,眼前是所有物件各取一份的集合;從廚房的角度來看,是所有的碟子與叉子都會堆放在一個集合裡。物件永遠是同樣的東西,但是從不同的使用面來查看時,組合起來的結果卻是大不相同。

此時如果用“該有的都有了,多的不要用就好”的想法來看:沒事在存放區多準備大盤子的物件、給客人時沒事多給一隻西餐刀、吃剩的粽子不先倒掉而放到廚房的洗碗槽,的確不會影響到真正要做的事。但是在準備時多放置一組大盤子與西餐刀,勢必需要耗費資源去處理;客戶沒事看到吃粽子還附西餐刀,感覺一定很怪;洗碗槽裡同時丟進了殘渣清洗起來勢必得多花一點功夫。如果店裡僱用了外國的幫傭,說不定會以為這些本來就是這樣呢。

想要Working Smart,秘訣在於快速定義出哪些物件是需要的,同時有哪些事是必須處理的,然後規劃出最快速且正確地執行所有動作並且準備好全部物件。省卻動作的時間卻留下多餘的物件必然在未來會付出代價,因為動作只是一瞬間的事但是物件卻永遠存在,而且遺留下來的物件會造成必須做更多的動作!

2008年10月19日 星期日

R19 設計在於管理(2)──應付改變能力

在設計時曾說過系統要能應付不同需要的話,必須可以自由地在不同的面向作乾淨的切割。這種結果也必須精確地先定義每一個可切割的層次,再精確地定義每一個元件與函式放在應該存放的那個小格子裡。

業務曾問我目前維護的系統是否可以獨立出某個特定模組出來販售,我很直接地回答不行。否定的原因主要有兩個:一是層次與元件定義的不明確、另一是元件使用資源的模糊。層次與元件沒有理想定義造成想取出一些元件時,使用關聯的混亂迫使一堆不必要的元件也必須被選用;使用資源沒有界定範圍會讓元件取出時,無法得知實際使用到的資料到底有什麼。

改變是無所不在的,曾有同事說過讓客戶需求定案而不輕易改變會是讓系統品質提升的好方法,我反問他不久前才說過的經驗:要是架構師在最底層的功能就選錯了實作的技術的話呢?客戶時常改變需求沒錯,但是在不違背設計精神的前提下是允許加減功能劇本的動作的。然而更重要的是要讓自己的設計作好應付各種變化的可能。

精確的定位與關聯的最小化是必要的努力,事物與元件一對一的對應也是不可忽視的重點。唯有在設計時致力於這兩者的實現,才有機會讓設計的元件像積木一般靈活地分解組合與替換,卻不會花費過多的時間與造成過多的影響。

2008年10月18日 星期六

R18 設計在於管理(1)──管理源自精確

設計的目的在於管理系統外的需求與系統內的資源,在任何需求有變更或是程式有問題時,確認系統會動到的地方後可以快速正確地定位出影響的範圍。要能達到完整的系統管理,必須具有精確的設計與關聯的記錄;如果缺乏這兩個要素,在無法侷限影響範圍的同時,side effect只要發生在預估範圍之外就是代表品質的不穩定。

在各種類型使用關聯上,用人者與被用者全部都被明確定義且詳盡收集的話,系統的管理將會變得容易很多;就像是把所有的資料放到關聯式資料庫存放一般,在各種不同方面的需要都可以快速地取得想要的結果。這些都仰賴專案的成員精確地記錄下點點滴滴,再使用適當的工具存放與搜尋才能達到的成果。

今天在一個團隊裡如果聽到“我改一下很快就好”、“那個沒弄又沒有關係”之類的話語,可以斷定未來產出的系統絕對是亳無品質可言的,因為快與忽略是造成資料與關聯漏失的主要原因。想像一下一個專案裡要做的全部事與物,假設一共有一萬件大大小小的事情要做,此時漏失了一個沒有被記下,那麼要花多少功夫才能在未來出大紕漏時回溯出是由於這邊少了一件事呢?

製作一個中大型的系統並不是簡單的事,即使只是一個元件也包含許多待做的動作,缺少幾個事物的記錄時或許還可以依靠相關的部分推理出事實;可是缺漏的一多任誰再也沒那種精神再去回溯出來。如果回首看待自己曾經做過的產出有那種似曾相識但是又很糢糊的感覺,那麼肯定是沒有作好記錄管理的結果。

2008年10月17日 星期五

R17 工程首重精確(4)──注重問題根本

雜亂無章的系統很容易在品質方面出現暇疵,不小心寫錯的地方如果也不小心沒有被測試出來,等到系統到客戶手中後才不小心地蹦出來,除了要勞民傷財地追溯影響並修正測試外,客戶對於系統的品質也會越來越不信任。

系統的測試沒有辦法使用80/20原則,保證80%重要且常用的功能是沒有用的,因為只要有任何一個功能在上線後出問題,客戶就會把廠商直接叫過去嚴重的甚至退版。系統的測試應該是精確且完整的,然而在隨性開發的專案裡根本沒法定義出應測試的範圍,僅能含糊地說作好測試系統自然不會出問題。

“完整”必須經由精確地計算得到,不是用推估的方式。鯀不知水性,在治水時硬用全面圍堵想求滴水不漏,結果大量建立的堤防反被大水沖毀;舜明白全面圍堵的困難也知道無法平息洪水,因而根據水性計算出在最少的地方施工而達成目標。瞭解系統內部設計的根本才容易從根本解決問題,而不是根據外部現象硬加程式在表面把錯的修成對的。從表面修補時注重的只是滿足結果的產出,每一次掩飾都是讓系統隱藏一顆不定時的炸彈。

有時會發生罕見又難解的問題,因為找不到真正的發生原因且客戶一時間沒法重現而暫時擱置。在專案裡有幾次這樣的現象,但是到最後一定會迸發出來;有位同事在遇到兩年前的舊問題在上線版本復發時就曾感嘆地說,該來的總是會來,躲得掉一時不代表躲得掉永遠。

問題都應找到根本再加以解決,暫時其他因素苟且放過後,終有一天會回饋到自己身上的。

2008年10月16日 星期四

R16 程式碼的MVC

系統上的MVC設計被很廣泛地接受,將功能所需的資料、流程與顯示分開存放,在絕大多數的狀況下可以僅異動或更換其中一塊而保持另外兩塊不受影響。將資料與程式依特性放置在不同的元件中能夠有這樣的好處,那麼在元件的黑盒子裡如果運用這個想法會不會有同樣的好處呢?

這兩年來我維護了許多其他同事所寫的元件,那些程式全部都是依個人風格所寫,由於開發時我沒有參與,接手時也沒有足夠的說明與文件,因此我相當清楚維護的瓶頸在哪些地方。問過一位喜歡看設計書籍的同事,他說沒看過有人像我這樣推動程式碼內容依MVC擺放的論點,因此我也沒有足夠證據證明它的好處,只能說之前的團隊在換不同成員處理一個元件時真的極易上手。

曾經與公司一位經理說過,我的設計方式可以具有變動彈性、方便維護、容易除錯的特點,但由於要定義架構元件的明確層次與複雜化程式內部的擺放,會使得開發需要的人力再多出一倍左右。那位經理反問我,要怎麼去說服上面同意給兩倍的人力來做出功能完全相同的產出?重心如果放在“那個階段可以產出什麼”的話,我完全無法說服任何人。

別人程式的難懂在於達成功能的所有流程與動作是什麼?使用到的變數放在哪裡?還有不知道某個區塊的程式到底想達成什麼功能?看過一些文章教人如何去抽絲剝繭、循序漸進地去看懂其他高手的程式碼,曾經歷此道的我可以斷然地說那是徒勞無功的。想法轉換為程式的變化如果沒有任何說明,就像一個小型的謎題一樣;你能想像在一個上百萬行的專案程式碼裡,有多少大大小小的“謎題”存在嗎?

猜燈謎時不見得每個參與者都能猜出答案,有的人花很長時間絞盡腦汁還是想不出來。但是卻有一個簡單的方法可讓所有人立即明白答案與原因──那就是讓出謎的人直接將答案告訴每一個人。

2008年10月15日 星期三

R15 工程首重精確(3)──建立通用原則

在介面的定義中我會分為兩個大層:一層是系統分析(SA)定義的,一層是系統設計(SD)定義的。介面的方法是遵循的是完成功能的逐步分解動作所設定,在分析設計時應儘量保持使用的層次與意義的完整。

從介面的存在所切分的兩層來看是截然不同的世界:從使用介面的角度來看,只需要知道介面方法提供了什麼功能、要怎麼使用;從實作元件的角度來看,則僅要努力達成規定的結果而不用管上層如何使用。在一般的文章裡大多都教我們將介面下的實作元作視為黑盒子,不用花太多心思在裡面,但是在撰寫與維護那些黑盒子時總不能跟經手的人說那本來就是黑盒子吧?

如何讓大家快速知道其他人的設計理念是增進團隊產能的捷徑,在目前各自為政的設計之下,每個人做出來的元件都有不同的風格,現在連看懂都很困難更何況是維護。在無法通盤瞭解別人所寫元件的同時,只能依據自己的想法另寫新程式碼來異動而盡量不去動看不明白的地方。

專案裡需要一個記錄用的序號產生功能,設計者為了讓多台電腦同時取用都能正常,寫了100行左右無任何說明的程式碼,但是接手的人看不懂為何要這麼多行而另外使用簡潔的寫法。最後在多伺服器同時取時果然出現了同號問題。這該算是誰的錯誤呢?

物件的擺放如果可以規定擺放與存取的規則,設計的流程如果可以規定出寫作與放置的標準,那麼所有人在第一次拿到別人所做的元件時都能夠立即曉得怎麼開始。黑盒子裡的好壞左右了使用它的程式成敗,如果專案裡的元件都沒有人可以弄懂別人負責的部分,這怎麼可能有團體效率可言呢?

精確的資料存放與步驟流程,就如同大公司貫徹的SOP一樣是維持品質與效率的最大訣竅。

2008年10月14日 星期二

R14 工程首重精確(2)──依意義作設計

每一個元件、每一個方法、每一行程式都應該依需求或設計原有的意義安排,一方面是為了精確地做到應做的動作,另一方面則是為了讓所有看到程式碼的人立即看得出關聯。

某天同事提出一個設計問題:傳入一個一定是偶數的整數,要作出+1-2+3-4+5-6……(數量與傳入的整數相同)。我的作法是採用迴圈依需求逐一地照算式運算,不過同事說最佳的設計是將算式化成(+1-2)+(3-4)+(5-6)……最後成為-1 * (n / 2)的精簡計算。當然用後者的方式是最佳化的設計,但如果沒有任何說明時其他的人根本不知道原來的需求是什麼;如果想法經過轉換則應該註明變化的經過才不致於無人理解。

在系統的設計中我們時常需要取得User ID這個變數,但是在執行交易的時候可能用數種不同的方法都能取得(例如從最底層的Map直接取得,或是用包裝好的API取得),這時應該規定在不同的層次時使用固定的對應拿法。動作的設計也應該先釐清所屬層次定義對應的方法,同時在動作的組合時一樣放在應該放的地方。

try {
 Thread.sleep(1000);
 MyAPI();
} catch (Exception e) {
}
同事在寫這段程式的時候,我建議他把MyAPI()搬出到最後。雖然sleep()在我們的經驗裡從來沒有拋出過例外,感覺上寫在外面或是裡頭對於執行完全沒有影響,但是在意義上是應該寫在外面的。同事同意了我的說法而作了修改。

系統的建立就是這樣,當想法逐步變成產出的同時,一層一層地精確鋪設出該有、該做的輸出,這是良好設計的初步。

2008年10月13日 星期一

R13 工程首重精確(1)──最佳化與範圍

我覺得精確才是工程的意義所在。為了達到某個目的而設計了一組最佳化的動作,精確且快速地達成應該要做的事;而在執行該組最佳化的動作時,所有可能使用到的資源也需精確地定義。

開發系統的時候都會使用Library,這些Jar File都有使用的時機點。在Client與Server架構下,有時沒有明確地定義而把許多檔案都一鼓腦兒地放在一個資料夾裡,說是整個資料夾都使用就保證系統一定可以動。但是客戶會感覺Client的資源過於龐大,多了很多不知道有沒有用的Jar File,不刪覺得需要瘦身但是又怕誤刪了一個造成系統出錯。(正確的作法應把Jar File放置在三個資料夾:Client、Server與C+S共用)

根據功能需求上的意義定義出應該做的對應動作方法應該也要是精確的。如果一個動作沒有清楚的定義,那表示該動作內的一組程式碼或更內部的API會露出在更上層,同時也意味著其他功能中要做那個動作時很可能沒有固定的使用方式。沒法精確地定義與定位使用的動作,就會造成從不同方向去看待時會有更大的機會發生錯誤。

定義出功能使用的程式與資源,也有利於未來想要抽出功能到其他地方使用時能夠精確地取出應用的區塊。倘使只為了完成系統的全部功能而設計,那麼往後根本無法定位出指定功能所必須的範圍。這也正是現在許多專案系統裡很普遍的結果。

2008年10月12日 星期日

R12 Log File的意義與應用

雖然說測試應用程式的好處在於可以便利地偵錯,但是上線的系統極可能不能作除錯動作甚至沒法快速更換除錯用程式的。在沒法看到程式內部狀況的時候,只能依靠程式的輸出來偵測內部的運作狀態;一般都會採用Log File作為這種記錄的機制。

病人與黑盒子元件我們只能根據先天已經存在的症狀來檢核,比較起來自行開發的系統在未來的除錯上有較多的優勢,因為我們可以自行決定哪些要記錄、哪些不要。雖然在考量要記錄什麼資訊時會基於執行數量的多寡與重要性等等因素加以取捨,但是必要的資訊還是應該記錄下來,以避免某些錯誤太難重現而錯先修正的先機。

Log最基礎的想法在於表達出程式曾經經過這裡,在必須的經過點留下記錄可以證明是否執行到應執行的程式;在重要的分歧判斷前留下Log則是為了記錄判斷條件在當時取用的資訊;執行時需要的資訊在初產出與取用前記錄下其內容。對於單一交易的記錄大致以這三個目的為主,執行時發現過多或不足再根據實際需要加以調整。(有時為了不同的需要,也會將應付不同目的的記錄分開放到不同的Log File裡)

如同醫生診斷病人時是以偵測當事人的病況加以治療而日後會提出某些疾病的統計分析,系統的Log除了對於單次交易的記錄之外,還應該考慮到同類型以及所有交易的統計所需。根據這樣的需要,我們必須定義交易起訖記錄以及內部重要步驟的起訖記錄,與其記錄的同時應該帶有的資訊(通常會考慮使用者、工作站、交易代號、開始時間、執行時間為最基本的記錄資訊);而所有記錄的結果應該另外撰寫讀取Log的程式加以分析並統計,才能快速且精確地計算出所需的報表。

先有使用者想處理的資訊存在,然後有因處理使用者資訊而存在的程式;在處理程式執行的同時會有靜態的記錄,再有處理靜態記錄的Log分析程式;對於Log分析程式產出的單一型式報表(例:使用者當日執行各交易次數統計),還會需要另一個報表統計程式產出各個處所或是整間公司的統計報表。資訊需要程式、程式產出資訊,資訊與程式就這麼循環地緊密相依著……。

2008年10月11日 星期六

R11 醫生的診療vs系統的除錯

醫術所謂的”望聞問切”四個字,現在看起來與系統發生問題時所要做的動作似乎相同。(暫時讓病人=系統)先看看系統表現出來的現象,聽取使用者的描述與想法,接著詢問其中還不夠明白的關鍵之處,最後從最可能出錯的地方對症下藥。

就我觀察的醫生診療過程差不多就是這樣,先觀察表面的症狀看符合哪些疾病的特徵,如有與數種疾病的表徵相同就再作更進一步的檢驗直到只剩唯一符合,再來就針對那種疾病施予對應的治療,過一段時間後再觀察病人是否已經好轉並決定接下來的處理。醫治的行為就在取得狀態與處理動作的循環中進行,直到病人痊癒為止;系統的除錯同樣也需要這樣的循環。

但是兩者還是有很大的差異。系統除錯時可以設定中斷點觀察內部程式的動作,可以立即修改程式再重新執行,必要時還可以隨便改動變數的值嘗試看看有什麼改變;診治病人時雖可以有內部的診視,但是有些也沒辦法太細致(懷疑某段血管硬化時總不能先切開來看看吧?),琢磨病情時也不能先下個可能的藥看看(萬一投錯藥掛點的話呢?)。

由於這樣的特性,我們可以在程式設計的領域看到不是相關科系的人同樣有所成就,但是醫生只有少量非相關科系的人能夠生存。另外,程式高手與名醫的相同點在於只要取得相對較少的資訊(甚至只需要對談)就可以判定問題的癥結,這是需要天份將大量的經驗在心中分析化與關聯化後才能達到的領域。

2008年10月10日 星期五

R10 與高階主管們的討論

在六月中旬,公司安排了一場我對高階主管們的報告。原本雖是要檢驗近兩年來我的工作成果與未來工作展望的,但我捨棄了制式的報告而選擇提出近來看到的專案問題(列在前幾篇)與自己想努力的方向(在前一章節)製作成投影片。一個小時的說明下來,高階主管們頗能認同我看到的現象。

最後邀請了一位某大公司的資深人員聽取我的說法並提供意見,他說了一堆不過我聽到的重點只有兩個:一個是我使用的某些專有名詞與其意義不符,另一個則是他在台灣看過不下十個方法論至今還沒有一個成功的。

我必須承認我所使用的名詞不正確,因為我的經驗都來自於工作上的體驗而非書本的知識;我明白了一個道理後只是試著用我認為適當的方式來描述,在Java World裡的幾篇回覆也有人提出類似的論點。不過宇宙的偉大並不是因為它叫宇宙,豬的懶惰並不是因為牠叫做豬,而是它或牠本來就存在;經驗中對的道理並不會因為叫錯名字而變成不對的,那是一直存在的,除非旁人只因名稱不對就不屑一顧。

令我反感的是第二個意見。幾乎當場就反問:如果經歷過那麼多方法論都沒成功,怎不直接對主管們說乾脆不要做了?不過幸好我還懂得一點做人而沒說。不過後來就再也沒看過那個人了。

時光匆匆地過去四個月,從那次會議之後我就轉為支援解決國內專案的問題而時常忙到半夜才回家,大陸的合作開發也暫時停止。到現在為止,還沒有看到任何的轉變……。

2008年10月9日 星期四

R09 做人的方法(11)──適合做黑心產品的想法

這幾年時常看到大陸許多黑心產品的新聞。這些黑心產品都具有相同的特色:從外表上看起來與正常的產品無異,但是內部所使用的都是劣質的替代品。同事們聊天談到這個話題,我搭腔道:其實製作黑心產品的人很適合來做專案,因為只要讓系統能通過客戶的檢驗即可,等到客戶使用另外的方式來測試時,才會發現裡面是亂做的。

沒錯,讓系統在表面上可以通過使用者的測試,正是驗收的關鍵;也由於客戶通過驗收與否事關專案的成敗,使得非常多的人僅把這個目標作為開發系統終極方向。功能的劇本內有些程式有誤造成表現出來的不對,就加程式把不對的部分補做成對的;有些該關起來的視窗因某些判斷不對而保持開著,就加程式判斷若開著就硬把它關掉。沒有找出分歧出錯誤的岔路卻硬加多餘的程式將表面補成對的,如同將眼前漏水的洞硬補起來後,終將發現水還會從其它的地方漏出來。

在龐大的系統裡要找出真正的錯誤點的確很難,也難怪很多人都求快速地在外面補正錯誤,不過卻也因為這種想法造成系統疊床架屋又難以理解。(在寫Log分析程式時我也曾偷懶直接刪除一些轉出後有少欄位的記錄,而沒有去找出為什麼會有漏掉欄位的資訊)以同樣的思維應用在生活的事物上,就會出現許許多多表面只應付檢驗,骨子裡卻失去本意的替代性作法。

凡事如果只看自己的需要,卻沒去注意萬物來去的流向與意義,終將只能做出半調子的成果來。

註:客戶將幾個專案的廠商人員集中在一個樓層,我一直發現垃圾筒裡有很多可以回收的紙製容器與塑膠容器,前幾天終於受不了貼張紙條提醒那些在清洗後是可以回收的,這幾天觀察是有些改善,但是還是有很多直接丟進垃圾筒的可回收容器。如果人的思考方式已經定型下來,要改變實在是太難了!

2008年10月8日 星期三

R08 支援專案所見(5)──遇例外就時間失控

在專案裡除了開發功能與除錯外,最耗費時間的莫過於臨時有狀況發生時要去追查相關資訊作進一步分析了。曾經有的經驗是:系統上線後大致都正常,但是在某段時間裡交易執行的時間是正常時的五倍到十倍。

客戶非常在意頻寬的使用與執行的效率,因此要求我們清查出問題那幾天裡每個交易執行與連線所花的時間;Log File一如平時所見,整個系統有四個伺服器,每個伺服器一天有50 MB以上的資料,而且還要加上數個Client的Log File。記錄的查看很容易,只要找出範圍再逐一過濾;但是客戶要的是全部資料耶!全部看完那禮拜就不用做別的事了。

我當然沒有去看內容,而寫程式判別Log File內特殊的關鍵字取出我所要的資訊存到CSV類型的檔案裡,再藉由排序的功能找出最耗時間交易的全部資訊再作分析。這個處理程式我寫了約三個工作天,但好處是未來任何時候的記錄檔拿回來後,只需要十分鐘就可以取得所有交易的內容作排序與解讀。

時間的失控主要是因為原先放置的東西沒有管理。如果今天的Log File內容不是有順序的邏輯,那麼不可能寫出轉換的工具程式;如果寫工具程式時沒有考慮Log File內交易記錄的全部屬性,那麼在日後客戶另外要求要看指定Client的所有執行交易代號統計時,還得另外再花時間撰寫。

例外,是臨時需要的資訊是原先的設計沒有規畫進去的,臨時為了解決那些例外的需要得花非常多的時間去處理。降低例外的影響有兩個原則:擴大一開始的設計範圍儘可能容納更多考慮得到的例外;另外就是保持設計的彈性,在例外發生時可以快速地將之包含進來卻沒有太大的風險。

2008年10月7日 星期二

R07 支援專案所見(4)──疊床架屋解決問題

原來的功能模組已經完成得差不多時,如果遇到一些與原來流程不盡相同的改變,要是因之前沒有良好設計而擔心融入新的考量時會有解不完的問題,那麼通常都會找出相異的地方後使用if else指令另繞出一條不同的路來放新的流程動作。這便是疊床架屋型的設計。

這種設計法很快而且風險很小,只要條件找對就可以成功。但是一塊一塊固化的程式以及一個一個的if夾雜在一塊,要維護與變更比較多層內的程式可就恐怖了,因為一旦拉扯到重心所在的物件時,恐怕整個系統倒得比什麼都快。還有即使是if else的條件判斷也應該規畫好放置的地方以及共用的考量,才不會因信手拈來而雜亂無章。

很可惜只要專案時程一壓迫,變動都會被只求快速地完成而無暇兼顧佈置,這也正是許多系統令人痛苦的地方。有時在增加新功能元件的時候,主管都會要求要好好地依理想設計,但是元件本身可以,想要把元件處理的部分“好好地”融合到原來的系統裡卻太困難了。

這時我會舉一個例子給主管:有兩個罐子各標示著要放紅豆與綠豆,但是原先的人放的時候不嚴謹,兩邊都各混到一些錯誤的豆子,後來的人再嚴格地把紅豆與綠豆分類放好,這對那兩個罐子而言還是混亂的,並沒有多大用處啊!最後整個系統還是要再度重整的,因為從一開始就沒有做好應有的設計,後面再怎麼做好效果都是有限甚至是做白工。

2008年10月6日 星期一

R06 支援專案所見(3)──流程與動作的綑綁

專案裡看到的程式幾乎都是這種類型居多,在撰寫局部功能的時候在一個方法裡放置了所有要做的動作;當然包括了分析上的行為與細微的API全集中在一起。這是程式寫作時直覺的思考方式,把功能要做的事依其順序寫下來。

這樣設計對於實作一個功能是正確的,反正到最後跑出來的過程與結果是正確的,但是對於變動來說卻是很困難的。首先得先定位出分析意義上的動作步驟範圍,思考上的一個步驟可能由多行程式碼所達成,而那些程式碼是緊密而不可分的;其次,順序變動時還要去思考其他同樣呼叫此方法的功能,是否會因此而造成錯誤的狀況;再者,在其它方法裡若發現同樣目的的程式碼時,是否要抽取出來也難抉擇,因為是否抽得乾淨與是否涵蓋所有使用的功能都不易確認。

進一步的設計是把依序寫下的動作分門別類地佈置與封裝以形成方便重用的元件,可惜的是現在經手的程式有很多從底層就已經開始混淆,這在專案上想覆寫時也會遇到明明只要改一點小地方,卻非得整個方法內容剪貼出來修改而完全棄置原來的程式。長久下來就造成專案產出程式裡的盤根錯節與過多的重覆程式碼,無論是誰都難以再度釐清。

直覺的設計寫法在極小型的專案裡還可以應付,因為需要記憶的部分不多;但是在中大型的專案裡思考內容很多而且由多人開發,那光憑少數人的記憶是完全無法應付管理與追溯的要求的。

2008年10月5日 星期日

R05 做事的方法(12)──切換Task會浪費資源

現在的電腦不只可以多執行緒,還可以多CPU,一次可以讓許多工作同時執行,我們可以同時排進大量的執行緒後在預期的時間結束工作。有時主管也會要求我們做類似的動作,就是同時要處理兩件以上的事情,而且要同時有進度且在預定的時間內完成。

在同時處理多件事時,意味著我們必須把第一件工作執行到某個程度時將之凍結,然後把腦中的資料清空後再將第二件工作之前凍結的狀態還原到腦袋裡;不可否認地有很多人可以快速的切換,但是切換時必定會耗費一些時間而且要承擔可能漏掉什麼的風險。

人腦到底是單執行緒的構造,專心一意才有可能把事情做得更好。曾有一篇文章說,上班時間內超過四分之一的時間是在接與手邊工作無關緊要的電話,以及重組被電話打斷的思緒;工作之間的切換也會造成類似的影響。要完成一項工作至少需要應有的時間,在中間插入其他工作只是在表面上都有進度而已,但是根本不會加快任何一項工作的時間。

另一個就是在看別人程式的時候。把自己的設計想法放開,去看懂別人的設計想法也是很花費腦中資源的,因為一切內容的放置都得重組才能瞭解;別人的Class、Method、Attribute,以至於元件的架構佈署、功能放置、命名規則都要摸索一番。之前公司開發底層架構時有另外找了一些新的人員加入,後來在查看問題時大家都說某人的程式的寫作風格不同而看不懂,因而造成難以修正以改寫的後遺症。這正是我推動一致寫作風格想避免的狀況。

2008年10月4日 星期六

R04 支援專案所見(2)──不是我寫的不知道

當系統發現問題或需要修改時,如果那時原來的開發人員不在難免會另外找人先瞭解問題,並希望他嘗試去解決。但是一開始總是會先聽到“那不是我寫的,不知道耶”這句話。在急迫時我也會被指定去解決比較困難的幾個問題,在剛進入專案幫忙時真的是什麼都不知道,那句話也不知在腦中盤旋幾次了,不過當然不能說出來,只能按部就班地抽絲剝繭下去找出問題的發生點。

一開始會先去問發生狀況的部分是要達成什麼功能,也就是先問出大致的需求內容;接著會問如何操作才會發生異常的狀況以便重現問題;然後確認正常狀況應該是什麼,以比較對與錯之間的差異是什麼。資訊收集後再來就是用除錯模式驗證程式碼,但是在此之前還要知道流程與程式的對應在哪裡,先弄懂程式上的架構才能追進程式的流程逐步檢查。

即使公司的專案已經先行提供通用的主要架構,但是在可以客製化撰寫程式的地方,每個人寫的程式碼都有自己的風格,物件的擺放也依自己的想法;公司目前沒有提供標準的設計方法論可遵循,以至於產出的過程與結果都依個人喜好而有所不同。由此可見,要追查一個陌生的問題難度著實不低。

更困難的是,客戶重視解決問題的同時要記錄系統對應修改了哪些程式,有哪些功能流程走過修改的地方而且可能造什麼影響;另外也會問是否有其他情況會在其他模組裡造成類似的錯誤。在時程急迫且修改者對修改部分不甚瞭解時,如果只改看起來是問題的地方就會時常漏東漏西的;一旦修改問題時發生其他side effect的機率偏高時,客戶對系統品質的信任自然會越來越低。

2008年10月3日 星期五

R03 支援專案所見(1)──不瞭解架構與函式

公司對於主力的客戶領域設計了重用的產品,好幾個專案共用同樣的設計架構(Framework),架構裡放置了許多以前專案經驗的元件。原本期望讓以前作過的東西在未來可以不用再花精神製作,但是因為開發的時程不足造成分層的佈置不夠完善與說明文件不夠詳盡,使得新人的學習曲線變得十分漫長。

依我所見,系統分析定義的流程動作不夠完整、系統設計沒有放置應有的對應函式、產品提供的函式說明不夠清楚,使得每一段負責的人都沒有辦法快速且正確地堆疊出達成需求的完整計畫。雖然說對於產品的瞭解是根據各人自己的學習,但是沒有層次條理又沒有足夠說明的產出,要只依靠追蹤程式碼以及口耳相傳來傳承想法的話實在是事倍功半的。

在支援專案的期間,有許多成員問過我很多關於產品的問題:大到一個元件的完整生命週期內容,小到一個屬性的意義,在在都能發現他們的所知都只侷限在他們曾經做過的功能上。架構與底層提供什麼?可以怎麼使用、怎麼操作?在專案上要怎麼應用、怎麼延伸功能?這些內容描述的不清楚,對成員來說都如同霧裡看花般難懂。

就像現代年輕人對於中文一樣,認識字的形、音、義不多,詞的意義與使用情境也瞭解很少,對於文字基本單位的認知過少,在組織文句的時候可以使用的基本字詞相對地就很少,在這種情況下怎麼能期待有流暢且意境深遠的作文產生呢?這也正是專案裡普遍所見的問題。

2008年10月2日 星期四

R02 實作型vs理論派

與同事聊天的時候,會將往不同方向堅持的兩個類型稱為實作型與理論派。

偏重實作者一切都以滿足需求規格書為主,客戶對功能與效能的要求都是開發的目的,但是除此之外其他的部分似乎都可有可無。對客戶而言,只要系統的輸入與輸出行為模式正確,這個系統就是堪用的;至於裡面如何設計與佈署,需求變更後的衝擊有多少就沒有人知道。

“設計應該保持彈性”、”系統應該進行完整的測試”,為了確保開發出來的系統品質與彈性,大師們提出了許多有用的論點。但是每一種堅持都需要額外花時間(而且是不少的時間)來達成,對於必須承擔時間壓力的專案來說,能夠勉強作出客戶能夠驗收的系統就已經普天同慶了,又怎有多餘的精力讓系統內部更美好?

看書不多的我只能算是實作型的一員,但是多年來的經驗令我明白設計的彈性與品質也是必須同時做好。然而在面對實作型的人提出要求品質的論點時,會被說是不看實際執行困難的理論派;面對理論派的人提出方便行事的快速作法時,又會被說成是講求速效走捷徑的實作型。處於夾在中間的尷尬境界,除非能夠提出一種令兩邊都能同時驗證通過的方式,否則到最後一定會被視為只會喊好聽口號的人──即使要說服心裡已經有固定想法的人很艱難,那也是必然會走的經過而已。

“做了、做完、做對、做好”是做事的四個評估階段,然而除了自己做好之外,讓其他人在未來“好做”,才是我放在心中的最後目標。

2008年10月1日 星期三

R01 知難行易vs知易行難

世界上有非常多的事物,只需要投入時間去摸索很快就能有小成就;像是釣魚、做菜、繪畫……與程式設計等等,知道如何開始後很快就可以有初步的能力。然而開始是容易的,若想要將自己的能力在該領域提升到較具水準的規模,卻必須要有足夠的專業知識才可以,否則做出來的成果很可能會七零八落。

由於入門容易,知難行易就是鼓勵大家放手去做,只要懂一點基本的道理就可以做出粗具規模的東西;實際領會困難,知易行難則是表達要邁入高手的境界,聽理論的內容感覺很容易但是卻難以達到盡善盡美的程度。大部分的學習曲線在前期都可以快速成長,到了後期卻得累積許多知識與經驗後才能稍微前進一小步。

公司的幾個專案陸續遇到了一些狀況造成氣氛不佳,主管幾位同在資訊業的朋友也抱怨做得很辛苦。在程式設計的領域裡有很多大師在開發軟體的各個方面都揭示過非常符合理想的概念,可是如果業界有理想標準的話,為何那些專案都無法因套用而受惠?使得大多專案成員都做到死去活來?而且同專案的成員都沒法看懂其他人所寫的程式呢?

實作是比較容易的,因為它可以經由測試加以驗證,任何人都可以很快地看到結果是否正確,而且修改與反應也大多是立即可以再度測試出結果的。但是設計並無法快速驗證,通常會在更改錯誤或是需求變更時才會發現整個改變過程裡發生窒礙難行之處才會恍然大誤,但是到那時通常都已經來不及再調整回來。設計的問題在內部結構開始定型後,就已經埋下了日後難以修改的地雷。

2008年6月10日 星期二

Q18 努力的方向(10)──留下現在的記錄供未來比對

終於,是現在階段的尾聲了。不知道在努力實現想法的同時可以影響多少人往相同的方向努力,也不知道是否存在著可以改變自己想法的現實。在此時留下程式設計的完整想法,總是可以在未來的某個階段再回頭檢視那時與現在的差異有哪些,而不會每次都懷疑著自己以前到底曾經做過些什麼。

如果你也認同這些,是不是也應該開始改變些什麼了?唯有開始動作,才會對未來產生影響……。

2008年6月9日 星期一

Q17 努力的方向(9)──循序漸進的想法

從別人指定的功能需求下找出適當的流程與動作,用計算機程式正確地達成目的是一種快樂,這種成就感正是這個領域吸引人的魅力。

然而在系統範圍較大、需求較多的時候,各個功能之間總有採用相同動作的時候,如何管理這些重疊的地方暫時都得靠個人的想法。強調絕對重用的人會讓重覆動作只存在於一個地方,只注重完成功能者就可能在各功能裡都有一份相同的動作程式碼。這樣的重疊也會出現在下一個相同領域的專案上,而且除了功能實現的想法外還要加上與前一系統的異同分析;甚至不同領域間的系統也要相互分析比較。不想作這些比對的人,手上就只會擁有一堆無法相容的程式碼。

歸本溯源,最初的想法都只是“如何完成一個功能”,想法是沿著Use Case的流程行走。不過這個功能的分解動作為了提供給同系統內其他功能使用會被收集到集合裡,整理出每個集合提供什麼動作也是必要的工作。對集合而言,每個提供外界操作的動作都是它的功能需求,如何在內部達成這個需求又回溯到“如何完成一個功能”的想法。

“想法”是所有人完成功能的思維,不管它被放在文件裡或是程式裡都是確實存在的。找出別人曾經有的想法與留存自己現在的想法,都是讓設計經驗得以留存的重要關鍵;只是現在大家普遍使用的記錄方式都無法保證足以讓他人明瞭原有的想法。因此,改善現有的作法以留下現有想法以減少後來負責人的負擔正是需要努力的方向。

要能從程式碼中快速回溯出原有的設計想法,需要的兩個前提是:正確的Component結構與Java構文解析。

2008年6月8日 星期日

Q16 努力的方向(8)──不同Domain使用的SA Tool

在相同Domain裡開發過幾個專案後,應大致可以掌握數個專案間必須要有的相同項目與其間的差異。SA Tool的功用就在於快速地從經驗裡選擇需要的功能,並能因應客戶的個別需要客製化功能細節(動作的變化與使用的資料等),快速地把設定與產出匯出給之前累積的系統程式來執行。

SA Tool的使用想法大致是:首先依類別顯示所有的功能清單,使用者勾選系統應包含的功能進入待開發清單;每個功能列出達成目標的分解動作(同時顯示對應的API)予以挑選與組合,必要動作需標註不得省略;每個動作使用的必要資料名稱也會在選取動作的同時帶出來。功能、動作與資料便是系統的精髓。

工具也必須至少分成數個層次,從最底層的單獨元件的選擇開始、組成架構的設定、管理工具的選擇(顯示對應的DB Schema)、Domain基本功能(顯示對應的DB Schema)、基於選擇的基本功能而出現的功能選項等等。依照重覆使用的層次出現各個層次可以選擇與設定的項目是工具進階的要求。

系統分析人員在一邊選擇與設定的同時,必須完整記錄有哪些欠缺的功能與無法做到的設定,這些是在這個專案開發的同時必須完成的工作項目。等到系統的經驗累積到用選擇與設定就足以完成系統功能時,就表示在這個Domain下已經擁有成熟產品。

2008年6月7日 星期六

Q15 努力的方向(7)──將經驗帶到往後的同型專案

如果使用能夠記錄下開發歷程的方法來設計系統,理應從第二個專案開始起可以使用到前面專案累積的經驗。其實只要開發過一個系統肯定會有經驗的累積,一個開發團隊的評量除了達成客戶要求的功能之外,就看他們把經驗累積到哪些地方。

最不好的經驗傳承是存放在自己的腦袋裡只留下程式碼,因為未來可以說是什麼都看不懂;再好些的經驗傳承是使用文件,很多東西都可以查得到,不過得看程式與文件有多少的同步化;較好的經驗傳承就是從程式碼裡產生完全正確的文件與想法,然而這也得視程式元件的定義是否理想。

在切割好所有Use Case、Module與Layer的層次並擺放好所有的對應元件後,將專案裡的Use Case逐一依設計的方法與使用的物件,逐層地放置到方法與資料應該在的元件並封裝對應的邏輯。下一個專案開始時將所有的產出先帶過去,再逐一檢視Use Case、方法與資料有哪些可重用、有哪些需要修改與哪些需要新增。

以原有的清單作為基準再記錄下異動的種類,每個差異都是新專案裡的工作項目。記錄下前後差異的項目是系統分析者的責任,研擬出融合新需求又能再拿回前一個專案的產出則是程式設計者的挑戰。如果經驗無法往後重用,那麼每個專案都幾近重新製作且各自平行,這又有什麼意義呢?

2008年6月6日 星期五

Q14 努力的方向(6)──制訂專案開發流程與產出

功能就像是待達成的目標,而設計就是找出完成目標的方法。對於單一目標來看,我們可以不用管層次或是共用問題只要達到目的就好;但是對於有許多不同功能存在的系統來說,進行Module層次的方法分析以抽取相同方法的動作卻是不應省略的。

在專案上的作法會從Use Case開始,先行分析一些重要的功能,檢查在完成功能時所需要的各個動作,並將之安置在對應的Module裡。接著逐步分析所有的Use Case,取得動作的最大集合並放置到對應的Module。在此之後應可以得到所有的系統動作與全部的Module,還有這兩者與Use Case的關聯。

動作的拆解比較趨近人類日常生活的想法,不過每個動作所操作的資料則是Domain特有的資訊;一個系統裡所有動作操作到的資料集合,可說是該系統Domain的範圍。每個動作用什麼樣的方式來操作資料,每種資料總共提供了哪些操作方式,在專案上主要是以使用者角度定義動作與資料的名稱(再往下是從設計的角度來定義動作與資料實際的程式分類)。

將系統的定義資料與操作方法分門別類地定義並明確地標示,在使用時快速地取得全部索引從中選用(同時藉此保證同一目的的程式碼不會存在多份),這是我所認定可以加快專案產能的方式。

2008年6月5日 星期四

Q13 努力的方向(5)──Source Code的佈署與混淆

Component的內部依其意義切分為數個部分是令人容易理解的結構,Interface-Abstract Class-Class的佈置則有助於重覆使用的實現。但是數量較多的程式碼與執行時大量參數的處理是否會造成執行效能的低落?答案當然是肯定的。

為了兼顧設計時的意義明顯與執行時的效能提升,我的想法是依據Properties裡的定義匯出執行時所需要的Java Source Code與設定檔。匯出是把Component完整結構裡的十八個檔案(扣除Component Interface)裡的變數與方法全部組成到一個Class裡,這就是現今一般很難令人看懂的程式編排,但是與完整結構比較起來卻有較佳的速度;設定檔則逐一判斷,儘量執行時用不到的屬性在匯出時變成程式並將之移除,使設定檔內只有必須的部分。

在匯出的同時還可以作反組譯的保護。定義隨機選擇一些英文字母作為方法的機制,在組合各個部分的方法時順便變換變數與方法的名稱,藉由把所有的方法集合成一堆同時讓名稱的可讀性大幅降低,就可以嚴重妨礙想藉反組譯來看懂程式的意圖;而且在此同時,我們手上擁有的還是結構清楚的原始程式。

2008年6月4日 星期三

Q12 努力的方向(4)──自動產生設計文件

設計文件一向都是程式設計者的最痛,除了要讓寫出的程式正常運作之外,還要把設計的理念訴諸文字。在寫完文件後倘使遇到還需要變動程式(或重構)的狀況,之前產出的文件要不是重新翻寫就是無人聞問。如果,設計文件可以跟著程式自動產生,是不是很棒的夢想?

第一份文件是Component Interface說明,每個Method的用法與說明使用都以Java Doc的方式寫成註解即可。第二份是每個方法實作的流程圖,流程寫在Flow的方法裡而動作寫在Action的方法裡,運用程式解析的工具很輕易地可以得知每個方法的流程與動作;目前至少可以抓出流程的註解與動作的Java Doc來組成步驟的順序,未來可以搭配流程圖API完成自動產生流程圖的理想。

第三份文件是每個流程與動作對於內部變數以及的Data Model、Properties與Exception使用關聯,在每個流程步驟或是動作裡,所使用到的變數、資料、參數與例外處理(不管是常數或是方法)都能夠在逐步演算程式的同時找到,只需要將各個關聯記錄下來,很容易再使用JXL產出所有的關聯追溯表。

第四份文件是每個流程與動作對於其他Package Interface Method的使用關聯,同樣可以在產生第二份文件時在每個流程步驟或是動作裡找到;只需要在處理時記錄就可以直接產出,甚至連只是單純使用或是內部有宣告變數放置都可以判別。

只要讓所有Package都具有相同的組成與意義,我相信依循這個想法所發展的設計文件產生工具並不是遙不可及的夢想;若有幸實現,這將是許多程式設計人員的幸福。

2008年6月3日 星期二

Q11 努力的方向(3)──規定元件內部軟體結構

每個Package的結構都會以自己經驗累積的一個Interface加六個Class為主。Interface是提供給外部操作的功能規格,也就是這個Package所應該實現的所有功能;六個Class則是以開發元件時切割的部分為基準。

在今年進行的專案裡,我已經準備好基本型態的Component,提供最根本的實作。另外還準備了一個小工具,只需要輸入Package Name、Component Name、Super Component Package Name、Super Component Name並選取要產生哪些部分,就可以自動產生所有已經命名好且建立好繼承與實作關係的所有檔案。

Package內部的適當切割有助於讓不同的人快速地看懂程式,這是因為各種相關的東西都集中在對應的部分,對外所使用的都是其他Package Interface。Component的首要是定義功能的Interface,對需要分層負責的各個部份都宣告Interface;Component內部則以Implementation作為入口,再分散到各個部分內部去處理。

雖然Component只需注重Interface的實現,內部怎麼去完成的可以不用在意,但是所有的Component都維持同樣的生成方式、同樣的分工模式與想法時,除了作法可以統一外,還可以根據一致的風格完成更進一步的目標與對應工具。(Component產生工具便是一例)

2008年6月2日 星期一

Q10 努力的方向(2)──建立軟體元件庫與使用文件

建立可以切割並重用的系統後,接下來是讓所有人都知道每一個空格裡有哪些元件、各有什麼用途、提供了哪些方法等等,這相當於建立全部元件的資料庫。建立的主要目的在於讓所有人可以快速知道全部有哪些元件、元件被放置在哪一個空格,當然也應該可以依特定目的來尋找。

下圖是一個專案裡的部分元件,所有的元件都依應用的層次被放置到對應的定義層次裡。如果有一個專案只是需要基本的架構在上面開發,就只需要拿基本與架構用的所有元件;如果要特定系統的元件就再多拿一層系統用的元件。除了基本架構的快速組成,在各個專案遇到的元件問題在解決後可以適用到其他使用同樣元件的專案裡。

分層負責的意義在於要做的動作在每個地方、每個層次都有負責的元件。如果開發的人為了求快速完成功能,而沒有去定義綠色圈圈所定義的元件時,我們可以思考一下原來該在那裡的變數與程式都會跑到哪裡去?找出那些消失的東西與學會使用它們是否需要額外的時間?是不是每個人都要去懂?需要使用部分的層次時,那些缺少的東西掉落在哪裡?

個人的快速是由於減少封裝的元件,進而造成其他人使用上的困難是我所不能接受的作法,更何況那些問題到頭來還是會掉在自己身上。維持元件架構的完整與提供齊全的文件都是在開發的同時應該做到的責任。

2008年6月1日 星期日

Q09 努力的方向(1)──定義各階層的應有元件

在作程式設計時,實在是有太多人只以最表層的執行結果作為目標,全然沒想過抽取相同子流程、避免相同程式碼與動作封裝到適當元件的方向。以這種方向作出的系統,在較公用的方法裡通常堆滿了不知從哪裡來、為什麼而做的程式,到最後只能使用一堆if-else來避免修改時影響到其他部分;同時千辛萬苦堆積好的系統,如果下個專案需要的只是基本框架的時候,根本沒法快速地把上層的部分移除乾淨,造成明明要用系統的一部分卻無法做到。

完成目標首先要切割出逐步進行的分解動作,接著把動作定位到系統三度空間的確定位置,最後把該空格內的類似目的的動作各自封裝到元件裡(分層負責);經由這樣的設計將系統的經驗收集在可以重覆使用的元件,使用時只應該找到唯一一個可以使用的動作方法(絕對重用)。元件提供給上一層次的是固定的動作,上一層次要如何使用這些動作則需另外再定義一組流程來達成目標(動靜分離);如果與實際需求有差距,就需要找另一個實作同一介面的元件或是另外定義一個作法來滿足需求(靈活替換)。

在下面的系統組成方塊圖裡,從專案Use Case的起點開始,找出達成需要的各個Module,每個Module的方法再往下串到不同層次上的元件動作,這一切的集合正是完成這個Use Case的所需的全部。為了避免多個動作被壓平為一個方法而形成無法抽離的困境,會努力將系統設計為不管從哪個方向切開都能夠再度使用(在切開的同時也要能看出切開部分所有的關聯)。

2008年5月31日 星期六

Q08 部落格的驗收(3)──堅持是沒有終點的漫長道路

最早打算只寫一百篇左右心得的部落格文章,就在想法不斷檢驗與修正下莫名其妙地寫了快一年。近半年接下的專案讓我有機會實際演練了自己寫出的方法與產出,也讓自己更願意在設計時承擔架構軟體時的多花的心力,以期望讓未來使用與維護的人用更簡單快速的方式完成他們想要做的系統功能。

搜尋過Google上關於OOAD的網頁,卻從未發現過有教人如何應用OOAD設計與實作的範例,說的幾乎都是應該怎樣、可以怎樣或是OOAD不實用等等的話語。因此,到了最後我幾乎是憑著要從頭到尾將內容完成的意志力,保持每天撰寫一篇文章的進度;我花了“非常多的”時間在這個部落格裡。

直到結束這天為止,每天造訪部落格的人數從未超過35人次,很感謝每天都進來看的人,因為會常來是心裡相信我的想法對自己有所幫助。或許現在能夠影響的人不多,但總期待有那麼一天可以影響更多的設計者跨出“只想趕快完成功能”的迷思。讓使用者感覺越好用與讓維護者容易修改,代表著設計者要在系統裡考慮更多的控制,所以我們得先多繞一點遠路奠定好底層的基礎後才會有跟著而來的收獲。

寫出的心得大約涵蓋心裡想法的八成以上,餘下的部分都是零散而難以表述的經驗。未來在工作經歷上的領悟會融合在心裡而不會回來修正內容,最後就讓這一年來留下的文字記錄成為永遠的紀念;這些是現在這個時期個人對於程式設計心得的總結,而且以後會努力用心裡的想法來影響身旁所有的人!

2008年5月30日 星期五

Q07 部落格的驗收(2)──公司內部可能遇到的難題

公司裡又有好幾個人提出離職了。在公司的幾年內也經歷過幾次離職人數較多的離職潮,除了因為搬遷因素而無可避免之外,大多集中在專案即將告一段落的時候,這是因為心裡感到疲累而想在責任告一段落時求去。

離去的原因大致分成兩類。一種是上層決策不合理或是勾心鬥角浪費資源。有同事說過專案明明做得很累,長官卻以其他專案忙為由遲遲不讓高手進去支援,因而打擊了士氣;也有同事說看到其他人加班到半夜卻不忍申報一百多元的誤餐費,卻看到上面的錯誤決策動輒損失數百萬元連眼都不眨。

另一種則是因為個人無法成長。跟著在專案裡做了許久,說明與訓練都不足,常常只指出程式碼在哪裡後就得自己去看,弄到最後都只是管中窺豹而已;即使努力地做完現在的專案,下一個專案也只是把現在的成果複製過去然後從頭改起,沒有reuse與開發方式最佳化使得每個專案的開發都耗費了相去不遠的資源。

公司高層決定的發展方向是既定的政策,員工只能被動地觀看公司的政策是否合乎個人的職場規劃,二者相輔相成時自然會選擇留下一起努力;反之只是把員工當棋子看待的話,遲早都會離去。對於管理因不是專長而無法置喙,但是讓設計容易被瞭解、開發變得較快速、專案人力更節省卻是我可以努力的方向;當工作變輕鬆、環境變快樂,員工就比較容易穩定地投入工作而不去看上面有多誇張(至少我就是這樣)。

2008年5月29日 星期四

Q06 部落格的驗收(1)──擁有能力就應該去做

有很多人認為系統設計就是用最快的速度正確地完成所有的功能,但是當致力於開發速度的快速時,通常會忽略其他方面對系統的要求。製作的系統如果屬於框架類型,沒對上層開發者制訂設計準則而要專案自行定義放任自己開發時,幾個專案做完後依然沒有標準的開發流程。

許多技術好的人都不屑OOAD的作法,因為他們覺得那樣會降低他們的產能;明明很快就可以做出來的功能,為什麼要多繞那麼遠的路做一些事再去完成?許多理論好的人知道要往某些方向作努力,但是礙於無法提出完整的實際作法以說服快速開發的人,於是只能提倡一些理論上的堅持。雙方的矛盾就這樣地產生,因而誰也不服誰。

我算是剛好居於夾縫中的人吧。實作很快且理論看得不多,但是總覺得原來的作法似乎有點問題而謀求改進。然而身邊的人不是不屑做到這麼精細, 要不然就是不知道要怎麼做,所以只有我一個人努力地往這個方向走。沒有人可以討論是寂寞的,因為我知道想法都會有差錯,沒法保證每個細節正確無誤;沒有人能夠檢驗是可怕的,因為正確的細節可能被忽略,或許再也沒被發掘出來。

直到現在我已經可以在開發系統時的任一個地方提出足以說服自己為什麼要這樣做的理由,也能夠遵循自己的想法去做事;雖然在實現的過程中也經過不少修正,但是總是找到屬於自己的道。期望在不久的將來,我的想法能夠通過同儕的檢驗並推廣到公司內,並依此想法製作出開發更快速、使用更容易的各種系統。

2008年5月28日 星期三

Q05 產出驗收文件(4)──應不應該給客戶完整的程式碼?

在思考這個問題之前,應該先檢查公司內部對於專案程式碼的控管是否妥善並確認客戶是否註明原始碼交由他們自己維護。

在公司規定統一的控管方式之前,各個專案裡會有一部CVS電腦放置程式碼,但是在驗收後就會撤除。那時每個在專案裡的人都會在離開之前備份“當時”所有的程式碼,可是我們知道程式還會不斷地修改,在驗收後又再度需要修改程式時問題就出現了:每個人都不知道手上的程式碼跟客戶用的是否相同?更慘的狀況是根本再也找不到完整的程式碼。公司內有套系統要不是離職員工自行備份了一份,恐怕就正式成為孤兒軟體了。

我去維護某個系統時就是這樣,在沒人有把握的情況下只好自己用反組譯程式拉出要修改的Class到類似的環境裡,修改後編譯檔案再放進Jar File裡。在C++語言的系統沒有程式碼時,由於沒法反組譯exe檔案,有問題只好拿類似系統的內容過來再按執行時的需要當場修改。這樣的維護真的是人見人怕。

為了避免這些淒慘的狀況再度發生,現今的我會不管客戶合約有沒有註明要附上程式碼,在驗收的同時會將最後的工作區用密碼的方式壓縮後放在客戶端的電腦裡(當然自己也會收藏一份)。但是即使確保了最後的備份,要確定未來修改時改動到的是最正確的程式碼,還是必須仰賴所有經手的人把修改的部分更新到統一的控管裡才能達到。

2008年5月27日 星期二

Q04 產出驗收文件(3)──執行檔案的反組譯與混淆

產出系統執行檔的動作也需要記錄,即使使用了Ant來自動產出結果,對於操作的步驟與設定檔的內容還是需要說明。不過還有一件重要的事是防止被競爭對手看到程式碼的保護。

Java語言跨平台的特性使得我們寫出的程式會先被編譯為一種中介檔案,各個平台的JVM再讀入這些中介檔案進行處理,並在各自的JVM平台上執行。現在有很多可以將這種中介檔案還原為source code的工具程式,雖然完全沒有註解卻還是可以從命名規則上看懂內容。

對很多設計者來說,其實只要瞭解需求後就有能力作出系統來,但是為了防止競爭對手快速地經由程式懂得這個領域的東西,多少還是會使用混淆的方式來保護程式。它的作法是將指定修飾字的Data、Method、Class與Package都使用無意義的代號加以替換,使得看到內容的人不知那是做什麼用的。但是在使用上限制不能對映射取得的一切加以混淆,因為名稱改變後就再也不能對應到正確的目標。

在我的元件結構裡,由於大量使用Interface(public method),這使用混淆的對象只能是protected或是private修飾字的那些,保護的功能是無法達到預期效果的。

2008年5月26日 星期一

Q03 產出驗收文件(2)──使用手冊、API手冊與教學文件

系統是要給人使用的,因此完成後首先需要一份使用文件好讓使用者知道如何操作系統。使用手冊首先要說明如何安裝系統與啟動,接下來可依照Use Case依序編寫。操作相關的內容大致上有啟始狀態、操作順序、反應訊息與設定方式等小節;這些可以說是Use Case需求文件進一步的實作說明。

API手冊(包括Component與API)是給會碰到系統內部的人看的,需要維護系統的客戶或是未來接手維護與新增功能的人都需要這樣的文件,這樣才能夠知道如何對系統除錯以及應付需求的變更。如果API裡需要固定的幾個串連,也必須在手冊裡說明才不致造成誤用。試想,即使維護者已經想好需要做到的系統動作,可是對應系統動作的元件或方法很難找出來或是根本沒有被封裝為方便執行的方法,這樣是令人無法再繼續下去的(以前面臨的就是這種情況)。

為了讓使用者與設計者都能快速瞭解常用功能或方法的用法,有時會需要撰寫教學文件。教學文件的編輯會先收集常用的功能後,再編出一個實際的故事來串連所有的操作,藉由模擬真實的輸入與產出讓使用者親自體會實際的使用以期快速地進入狀況。

不過,教學文件只是輔助性質的,實際需要的還是使用手冊與API手冊這兩種。

2008年5月25日 星期日

Q02 產出驗收文件(1)──建立系統執行環境的安裝手冊

系統要能驗收,首先要做的當然是得讓系統可以啟動,能夠啟動需要的是準備、安裝、與啟動。作法都是依步驟順序記錄成文字搭配抓圖,就可以讓使用者直接依照內容操作並驗證結果;必要的話甚至可將安裝的過程錄製為影像檔比對操作。

準備的意義在於告知使用者軟硬體的限制以及需要預先做好的事情,安裝是告知使用者怎麼將系統放置到他的電腦裡,啟動則是讓使用者知道如何執行整個系統的進入點。使用者依照手冊進行這些動作並正常地執行系統,之後才能夠依照使用手冊的內容進行各項功能。

在維護的場合,我們同樣需要建立一份開發環境的安裝手冊,讓接手的人知道怎麼從無到有地建立起原始程式的工作區。裡面除了需記載安裝的步驟外,每個使用的Library出處及版本,還有最新版本程式碼check out與check in的地方也需要詳細地記錄。當然也要包括如何產出系統執行檔案的說明。

以設計的角度來看,使用、開發與維護的這些動作每一個都是目的,針對完成這個目的都需要一連串的流程、動作與例外處理,也就是需要一份說明文件。用這樣的思維去看待那些動作,自然會明白一切的需要都是很合理的。

2008年5月24日 星期六

Q01 輔助的文件(8)──將使用的文件與工具建立為範本

大家都知道,最底層的Data Model固定後才能往上構築出對應的API、Controller,這個道理也同樣適用在開發系統時的產出。當我們的設計結構遵循著一定的樣式後,同樣可以把處理各個區塊的處理工具及產出收集起來,作為未來直接reuse 的標準。

想法的根本在於Component的結構明確地切割為MFAV四個部分,把所屬的方法放置在對應的地方;在此前提下,就可以使用結構產出工具快速得到。不管是在哪個專案,只要全部的SA或SD都應用同樣的結構,快速產出與容易相互了解都會是顯而易見的好處。

在SA與SD階段都有個重要的任務:決定Use Case並分析設計出裡面的Flow與Action。Use Case List、Action List以及建立Use Case與Action關連的Flow是該階段的重要工作,在收集資訊的同時建立關聯的對應,並依照產出的位置所撰寫的小工具同樣具有reuse的好處。

還有一個階段是從實作的程式裡再取回需要的資訊,像是列出Component裡所有用到的Data Name、Property Name與Message Name(可再去記錄每一個資料是從哪裡取得的)、倒回Rose Model建立靜態圖表、擷取Flow或是Action裡的處理流程產出動態圖表等等。

輔助工具的reuse擁有的最大好處是節省很多設計、實作與記錄的時間,但是這些好處都必須在擁有自己的方法論並固定好自己的產出結構才能擁有的!

2008年5月23日 星期五

P16 測試的過程(7)──任何修改後都進行迴歸測試

每一個找到的問題點都會有許多執行流程經過,完整找出經過這個問題點的所有執行流程並加以測試,是保證修改後所有功能依然正常的迴歸測試的任務。

修改某個Method之後,使用Eclipse的Preference功能可以找出上一層有直接呼叫的Method,根據找出的Method清單再向上找出更上一層的Method清單,再根據更上一層的Method清單往上找……。在沒有記錄的情況下往上找,可能就像從一大群同姓的宗親中找出自己的所有直系血親一樣困難,如果有系統裡有類似族譜的記載不是就容易得多了嗎?任何一個人都可以很快地找出自己的所有直系血親。

如果沒有做出自動測試的話,每次修改都要重作一次影響部分的Unit Test、功能測試、使用者測試……等,可以說改得越多就測得越痛苦。不管是時間因素或是人為因素,只要跳過沒測試的部分都可能在系統裡埋下了潛在的問題。以經驗來說,系統或元件都會是需要修改的,因此能夠節省時間的自動測試就相對變得重要。

測試是為了找出系統有問題的地方,但是進行測試是需要代價的。如果能夠讓測試自動地被進行,問題也自動地被找出來,那該是多麼理想的一件事?

2008年5月22日 星期四

P15 測試的過程(6)──多執行緒與重覆操作的壓力測試

在Client-Server架構下的效能測試還要包括多個Client同時存取一個Server時的壓力測試,用來驗證同一個交易在上百個Client同時對Server存取時是否同樣在執行時間的限制內正常地動作。

客戶會指定最少要在幾個Client下要能正常運作,我們的作法大致會讓一台電腦開啟10-20個thread同時發送交易電文到Server,然後多台電腦同時重覆對server發送交易電文,連續執行客戶指定的時數後沒有發生當機或是記憶體使用異常的現象才算過關。

重覆性的測試在UI的部分同樣也需要,因為使用者會在工作的時數內一直對UI作各式各樣的操作。理想的作法是用能錄下使用者鍵盤與滑鼠動作的Robot軟體,依序錄下指定交易的輸入流程(可包含故意輸入錯誤的操作),然後設定為重覆播放,這樣就可以對UI進行不斷的操作測試。

同樣地準備幾個不同的電腦用Robot執行不同的混合劇本,連續執行客戶指定的時數後若沒有發生當機或記憶體使用增加的狀況才過關。這個測試有時會被跳過,改由使用者測試時進行較多Test Case的操作來觀察是否有異常現象;不過這種替代方式的操作數比不上Robot而會有某些狀況無法測出的缺點。

2008年5月21日 星期三

P14 測試的過程(5)──進行系統設定測試與效能測試

由下而上一層層針對操作功能的測試告一段落後,接下來要做的是系統環境與設定的改變測試。對於一些不提供給使用者變更的執行參數,一般會放在設定檔案或是資料庫裡,測試時就逐一改變存放於外部的設定值並觀察是否依設計影響系統的行為。

這時候的設定vs Package追溯表就顯得很重要:改了什麼設定會影響哪些Package,Package又在哪些Use Case裡被使用,根據這個表格才能確認是否一切都正常。通常系統設定測試的內容會在Test Case時一併考慮,以節省再度重覆測試Test Case的時間。

與功能的流程、動作有關的測試結束後,還會進行非功能的測試,其中最重要的是效能測試。客戶對於一些重要的Use Case(例如Client送到Server再回來的反應時間,使用者按下某鍵後的反應時間,或是Client Server間傳送的流量等量化的測量),都會要求在特定條件下耗費的時間上限。

對於執行時間常見的測試法是在一進入指定的方法就先記下開始時間,執行返回前最後一步再取一次現在時間並減去之前記下的開始時間,所得即是執行總共所花的時間。我大多測試七次後取中間的五次作平均並記錄下來。測試流量則是在負責收送的架構元件上記下傳送與接收字串的長度。如果有不合規定規定的現象則要研判原因並加以修正。

2008年5月20日 星期二

P13 產出設計文件(5)──記錄Use Case與Module的關聯

等到使用者測試快到結束時,流程上的程式大致也趨近穩定,這個時候就可以開始記錄Use Case與Module之間的使用關聯。這裡的作法與Component Flow、Component Action幾乎雷同,只是層次換到Use Case Flow、Use Case Activity(對應到Module Interface Method)來記錄。

在直覺的設計下,如果目標放在如何完成功能的話,除了會造成處理流程只有達到功能的分解動作而失去層次感,這樣一來作任何修改都面臨無法縮小影響範圍的風險,因為執行順序的分工或是執行動作的深度都涵蓋了所有的層次,使用從上到下都有可能被影響。造船明明只造一個船體並依需要分割艙房即可使用,為什麼要多造出數層看似無用的防水匣門呢?這當然是為了預防“萬一”船體漏水時能夠限制影響的範圍。

系統開發已近尾聲,其實已經可以發現設計的方向可以導成遞迴處理,只要我們的產出是有規律且有層次的,就可以切割為處理方式相同的多個層次。另一方面,設計者在設計時只需要將注意力放在切割後的空間,可以在這限制的範圍內儘情陳設需要的結構而不用擔心影響其他地方;設計的產出遵照設計準則的話,還可以使用一些小工具來產生必要的資訊與文件。

設計的時候多花一些時間安排結構,可以回收的是快速找出問題點、修改時可限制影響範圍、容易讓別人瞭解並使用、減少文件的製作時間以及穩定地重用並擴充。設計時損失的時間可以在未來數倍甚至數十倍地回收回來,這還不足以令人轉變開發系統的觀念嗎?

2008年5月19日 星期一

P12 發現動作組合後的陷阱

在很多時候,只看單一動作的實現步驟時感覺不到任何異常而認為合理,但不知為什麼只要把幾個動作組合為功能實作後,就會發生意想不到的問題。整合測試與使用者測試除了要確認完成功能所需的動作是否依序執行之外,同時也要找出動作組合後才看得到的潛在問題。

UI是最容易產生陷阱的地方。例如focus的改變可以即時呼叫requestFocus(),但是在呼叫在內部會以另一個Thread來進行動作,這就會產生時間差的問題;有時誤以為呼叫後會立即生效,就接著在後面寫上預期focus已經移動的程式,問題有時就這麼出現。另一個可能是有兩個動作都有對不同UI元件requestFocus()的動作,這在各自檢視動作時並無不妥,可是兩個動作在同一個流程內一起經過時,就會發現focus有時會落在另一個UI元件上。

在流程內應該只執行一次的動作(像requestFocus())被呼叫過兩次就有可能產生問題,從流程上來看應該是經過所有動作的同時判斷該不該作那個動作。因此含有那個動作的方法可能會增加“要不要執行那個動作”的參數,或者是把那個動作另外拉出成另一個方法在流程裡自行呼叫,一切端看怎麼做最合邏輯。

在這個階段還有可能作方法的重構,是一定要具備的心理準備,如果動作不能迅速而安全地搬移或拆解,就會在此時付出嚴重的side effect代價。

註:有時在組合動作裡難以決定要怎麼做,那時會在被重覆使用的地方加上synchronized以保證一次只有一個thread進入。即使做兩次是浪費資源,但是至少每一次的結果都是正確的。

2008年5月18日 星期日

P11 測試的過程(4)──讓使用者確認系統沒有問題

在經過一連串的內部測試,確定在較正常的操作下可以無誤地執行功能後就準備進入使用者測試階段。在此之前要保證輸入後的執行與傳回都必須可以正常完成,因為使用者測試主要是測試各種輸入的狀況並確認執行時的檢查與傳回之顯示是否正確。

進行的方式與Test Case大致相同,不過使用者會根據他們的工作習慣來操作,同時還會故意用錯誤的操作與不正確的值來檢驗系統是否能夠防止並提示。功能的執行大多是線性的,頂多會有流程的分歧點指向另一段線性流程,但是UI介面的設計就具有很高的複雜度,這是因為使用者的每一個輸入動作都是獨立的功能,許多UI的動作又會相互牽動造成影響。我們可以想見在使用者測試時會冒出許多大大小小的錯誤回報。

使用者會根據交易的生命週期來切割出每一個交易應測試的範圍,然後就從開啟畫面、輸入資料、事件觸發、資料檢查、訊息提示、執行功能、畫面切換、結果回報……等等不同的層面來檢查系統各個階段的正確與錯誤的處理方式。對於使用者回報的問題,我們必須具有快速分辨出問題屬於哪個Module或Component問題的能力,方能快速、準確地定位問題並加以解決;如果一時之間無法找出發生點,可以經由討論與測試來縮小範圍以便定位。

解決使用者測試出來的問題,最忌諱的是產生side effect與defect reopen,這會令使用者感覺系統極端不穩定。問題應該越解越少,問題數能夠持續收斂的系統是可以預期在哪一天可以結束測試的。

2008年5月17日 星期六

P10 測試的過程(3)──驗證Use Case Scenario的整合測試

Test Case的目的是要驗證Use Case的正確性。Use Case在其所屬的Activity Diagram裡已經被切分為Flow與Activity,而Activity已經於功能測試裡保證其動作合乎需要,因此Test Case的重心將會是在Use Case Flow控制的正確性。

測試的想法與功能測試、Use Case是相同的,先分析出Use Case流程裡執行路徑的最大數,再決定在分歧點要準備的資料是什麼,而那些資料必須執行哪些動作才會出現,然後就依序將那些動作寫成測試步驟;記得每一種測試方法就是一個Test Case(換句話說,一個Use Case有一個或一個以上的Test Case)。

由於Use Case Senarios的操作大多經由UI的動作才能備齊,所以這部分的測試都會由客戶的測試人員進行。使用者在畫面上輸入劇本裡的資料後執行,同時檢視系統反應出來的結果是否合乎預期。如果整合測試時已經測試過純資料的執行,那麼此時只需要注重使用者輸入資料的正確與否、執行UI資料的收集與執行後反應在UI上的動作即可。

使用者的操作會出現許多瑣碎的輸出入反應,這些也是會被回報為錯誤;不過同時也會發現實際操作的使用者測試出來的問題,有很多都是在需求、設計與測試時沒有顧慮到的。為了應付問題修正時的改變,保持彈性的設計真的是必要的。

2008年5月16日 星期五

P09 產出設計文件(4)──記錄Package之間的關聯

在前面的Package Diagram與Class Diagram,我都會先作出“擁有關聯”的靜態圖表,先檢視結構編制。使用的關聯一般不會另外作出圖表,因為每一個Package或Class都會使用許多其他物件,如果要將所有的關聯物件拉到同一張圖裡,一定會發生過於擁擠且根本看不清楚的狀況。

另一個困擾是在程式裡面的寫法並無法在使用Rose倒回程式碼時顯示出來,因此會需要再花人力去更新為真實情況。對於這種需要再額外花資源的更新我一向比較排斥,所以在查詢使用關聯的時候,都會使用Reference功能尋找以及檢視import內容來達成。

要記得關聯是在流程或動作時使用其他物件來完成功能所產生的,只要流程或動作有所變化它就會跟著改變。由於使用的關聯錯踪複雜,並不希望把使用關聯同時顯示在一張圖裡;這時的希望仍然寄望在Java構文解析,指定某個Package、Class或是Method後就自動列出我們想要的資訊。

在CMMI裡一般都會要求水平追溯表與垂直追溯表,在非做不可的限制下就以Excel勾選Package vs Package、Package vs Component這兩個追溯表,至於Package vs Class可以參照Eclipse Package Explorer取得,而Class vs Class就直接看程式碼判別吧。

2008年5月15日 星期四

P08 產出設計文件(3)──用適當的方式補完動態圖表

UML的支持者可能會推薦製作Sequence Diagram或Collaboration Diagram,但是就如我所提過它們只能提供線性的執行順序而不易表達出分歧流程的現象,所以我幾乎不製作這兩種圖;當然,如果公司堅持要產出這種圖,也只好咬著牙畫出來交差。

利用Component結構的分工,流程與動作都分別在放在Flow與Action裡,我也建議在每一個分解的動作使用註解來表達該段程式的意義。因此只要順著Component Interface Method進入到Flow,幾乎就可以掌握到達成功能的必要經過,所以我沒有要求過一起開發的人畫過任何一張Sequence Diagram或Collaboration Diagram。這一方面因為Flow就足以表達所要傳達的意義,另一方面也是因為讓分析時的Activity Diagram等同於設計的流程與動作。

在所有Component都具有相同結構時,以Java構文解析技術去拆解Flow的程式碼搭配註解,再呼叫製作流程圖工具的API來產出流程圖,等到這個程度時甚至連Activity Diagram都可以省略,直接在Component裡設計。這必須要具有相當經驗後才能這麼做以免一做下去就亂成一團,另外可惜的是Rose並沒有提供這種API。

製作文件的時間點我認為在Module作完功能測試或Component作完Unit Test後就製作它的圖表,一方面是因為測試後比較穩定而較少修改,另一方面則是趁記憶猶新時加以記錄。(以Package為單位)

2008年5月14日 星期三

P07 產出設計文件(2)──倒回Rose補完靜態圖表

實作進行到一定程度,系統內的Class一多起來,Workspace拉開後的Package、Class清單排成一大串,根本不知道其間的從屬關係。一堆被壓平為階層式排法的檔案並無法詳細表達三度空間的關聯,因此繪製靜態的UML圖表交待關聯是勢在必行的。

在最上層需要Package Diagram來表示Module間的從層關係,即使同樣是Module也要依使用與被使用的層次由上而下排下來,同一層的儘量水平地方放在一排,接著還需要一組Package Diagram來表示Module與Component間的使用關係。如此一來我們就可以在這兩張圖裡立即看出Module的層次與使用Component的關係。當然要記得要同時放上Modele Interface與Component Interface以顯示提供Method的資訊。

接著以每一個Package為單位,繪製各自的Class Diagram。其實在所有Package內部結構都相同的情況下,各別拉出的Class Diagram幾乎都一樣,差別只在於Component Interface、Implementation、Flow、Action、Properties、Model與Exception各自提供了哪些Method,未來在修改或新增Controller那一層時就立即可以知道Package裡提供了哪些功能。

當然,這些在Rose下可以直接由整個Workspace倒回Rose Model,只需要找人依實際使用狀況與Component結構拉好就可以保存;日後即使在方法上有重構的現象,由於Method移動的範疇已在軟體架構上提供,所以圖都不用調整只要再從程式碼直接倒回即可更新。

2008年5月13日 星期二

P06 產出設計文件(1)──邊寫測試內容邊補程式說明

在撰寫Unit Test或是功能測試文件的時候,雖然理論上只要根據Interface Method的描述就可以寫出來,但是為了測試的完整性還是仔細參考Method內部的流程與資料比較好。

理論上測試程式應該由設計的人撰寫而且不要讓實作的人知道,以便在不知測試內容的狀況下真正測出程式的正確性,但是在實際的專案裡大多得自己進行Unit Test 甚至是功能測試,得到整合測試時才會有其他的人幫忙。在人手不足的專案裡,如果必須在自己實作後撰寫測試程式的內容時,倒是個review與補足註解的良好時機。

完整的Unit Test應參考所有的流程分歧點與動作的參數。在準備Unit Test資料的時候可以先檢查待測試的流程種類並決定如何來進入不同的分歧流程;進行動作的檢查時再參考執行時需傳入的資料作最大化的測試。在檢視的同時一邊調整、一邊補上註解與Data、Method的Java Doc,如此一來就能夠兼顧Unit Test、Review、Refactoring與Comment、Documentation。

2008年5月12日 星期一

P05 測試的過程(2)──Module Interface與Activity Interface的功能測試

底層的Component的出版理論上都已經通過自己的Unit Test,因此在專案裡選用這些Component都可以先不用擔心會有錯誤,能夠將注意力放在與Use Case實現有關的Activity Interface與完成Activity功能的Module Interface。

測試的範圍同樣以Interface Method為對象,測試的方式與原則與上一篇的內容相同,不同的是Module與Activity都屬於專案的範圍,應由專案內安排人力負責這個測試。功能測試的目的是測試各個分解動作是否都依照設計的要求運作,當我們確認所有的動作都正確地被實作出來後,接下來就能將眼光放到更上一層的整合測試。

其實功能測試的作法與Component Unit Test幾乎相同,不過測試的對象都屬於專案上負責功能的物件,而且測試時通常需要建立較複雜的環境。Unit Test測試的是底層元件的功能,功能測試則是測試專案元件的功能,在設計時記得儘量讓專案元件封裝底層元件;原因是專案元件提供的功能會符合專案Domain的意義,而底層元件只是達成特定計算機行為之瑣碎功能,並不適合專案上直接使用。

2008年5月11日 星期日

P04 輔助的文件(6)──從Excel文件產出Unit Test空殼

在開發元件的同時,可以從Excel文件裡根據Class Name與Method Name來產生Unit Test的程式空殼,這樣可以省掉一點從每個Class逐一產生Unit Test Class並勾選待測試方法的單調工作。

以Package為單位,每個要測試的Class應該要產生一個對應的UT Class放在相同名稱的Package;UT Class內應有的UT Method則以Package Interface為基準先行建立。在每一個Package的結構都相同、Constructor也相同的情形下,setUpBeforeClass()與setUp()兩個預備方法都可以預先建立預設型式加以修改,甚至tearDownAfterClass()就固定呼叫disposeObject()即可。

產生空殼並準備好測試前的程式碼後,就依照上一篇的原則撰寫測試程式碼進行Unit Test。每一個Class測試完後,記得依每個Project擁有一個Test Suite的原則,把該UT Class加入到Test Suite裡,未來可以用Project為單位,一次測試整個Project的所有Class的所有Method。

對我而言,未來還會在Interface Method後面定義各種不同的Method測試參數種類,在產生測試程式空殼時順便產生各種參數的呼叫程式碼,如此一來只要再補上Assert的內容就可以再加快Unit Test的準備速度。

2008年5月10日 星期六

P03 測試的過程(1)──對所有Component Interface作Unit Test

Component與API可以說是基本的積木,所有的系統主要都是先取用元件庫裡的元件根據客戶需要慢慢堆疊出來的;正因為會有很多系統都會採用基本的元件來使用,身負各個系統“絕對重用”責任的元件更提供有正確的功能,才能夠讓各個系統無後顧之憂地專注在開發上,以免因下層的改動而導致上層的混亂。

正因元件庫是系統的根本,所以元件與API的每一個動作都應該保證正確,才能夠再測試上一層的流程。在Unit Test的時候,主要是在確保Component Interface Method或API方法的功能是正確的,此時可以不用測試到裡面的每個Interface以免累死;不過當實作的Class有數個時,每一種實作Class都必須建立起來然後完整地測試一遍。

完整的測試,是指方法內的流程所有的支線都涵蓋在測試的範圍裡,而且每種狀況的所有輸入資料組合都有測試。首先計算流程所有的分支點,再列出改變執行流程的所有集合,接著再根據每一段流程片段所應處理與不應處理的資料加以記錄,列表為測試的內容建立Unit Test的內容;每個測試方法都依此方式建立,就能夠建立趨近完整的Unit Test。(撰寫完整的Unit Test所需的時間會比定義及開發功能還要多)

如果開發後的Component或API方法抽來換去的,原先為這個方法寫的Unit Test幾乎得全部作廢重寫,相信沒有人會這樣做的,因此定義好Component與API的功能方法並維持定義的不變,是建立方便重用元件庫的基本要素。

2008年5月9日 星期五

P02 所有修改都要為變動範圍註明原因

修改程式的原因如果不是為了重構就是為了除錯,無論如何都會有一個原因驅使我們去變動。在修改的同時,會希望把變動的原因以註解的方式記錄在程式碼裡,這樣就不會讓這次的改變在未來被淡忘。

註解除了記錄單次的改變之外,另一個目的是減少維護時的痛苦。對於後來接手程式的人來說,最好的狀況是只看現在版本的程式就能夠獲得所有版本的變動資訊,否則面對什麼註解都沒有的程式,勢必得拉出所有版本的內容才能明白哪裡有修改,基本上拿到一個版本號碼超過十的程式就不會有人再拉出來比對。

從這個角度來看可以明白程式裡適當的註解有多麼重要,可以讓所有人避免去比對多個檔案才能找出差異,而讓差異在同一份文件就看得出來,而且知道每一個差異發生的原因,這會是讓其他人迅速瞭解內部作法的捷徑。

2008年5月8日 星期四

P01 儘早來幾個Scenario Prototype

設計時幾乎只是純粹的思考,就像事先擬定工作計畫一樣,決定好的工作順序有時得開始實行後才知道執行順序是否正確以及動作是否需要增減。設計上雖然可以經由Review來修正看得到的問題,但是人為的動作都可能有盲點存在,一群人一致通過的設計並不代表一定可行。

因此在設計的初期,決定好架構與Module後,會針對系統的啟動與幾個重要的執行功能先行試作幾個Scenario Prototype,藉此找出在設計時被忽略掉的動作。在2007/11的專案裡,架構鋪設好後,就為了要不要先作一大堆的需求與設計文件頭痛,最後決定面對現實,先確定幾個重要功能再說。

在製作Scenario Prototype時,重點在於檢查流程的處理過程,至於動作的實作可以直接傳回要用的值來處理,而不要堅持連動作都要做好才測試以便加快速度。在測試的過程中一方面檢視自己的設計,一方面測試是否都執行到應該要做的動作,先確定系統重要的骨幹可以正常執行後再往下設計會是較安全的作法。

2008年5月7日 星期三

O17 Java構文解析的應用理想

回到現實面來看,設計都希望有設計文件的產生。如果依照一般的方法論在先作出設計文件,在實作或測試發現設計有錯誤的時候,除了改程式之外還要多出修改文件的動作;如果直接把想法寫成程式,在完成系統後還要再依程式內容重新寫出文件。

依照我的程式結構,流程是被集中在一個Method裡的,此時運用Java構文解析可以有三個方向可以努力:一個是分析出使用到的其它元件、一個是把流程轉換為流程設定檔,另一個則是將流程輸出為流程圖。當然,首先要做的是把程式碼讀入到處理的程式裡;目前可以運用的技術是JDT。

定義好規則之後,才能寫出對應規則的程式,再運用可以處理規則程式碼的元件產出我們要的東西。讀入程式碼後,我們可以按照需要輸出;以產出流程圖為方向的需求下,只要找到提供適當API的工具,就可以依照程式碼解析出來的內容產生對應的表格。

這樣做的好處是程式不管怎麼修改,只要符合規則就可以立即產出對應實際內容的文件(關聯與流程)。

2008年5月6日 星期二

O16 流程使用Flow Engine實作的可能

Component結構是把焦點放在最容易被修改的Flow上,努力將內部的使用對象依特性放置到不同的Interface裡。這樣的作法在Use Case上接下來的可能,就是使用Flow Engine來實現以定義檔來處理流程的可能。

想像有流程產生器時,程式寫作除了Action Method需要多加注意之外,其他部分的設計就變得很單純,加上Component, API已經建立在元件庫,就可以定義執行流程後透過Flow Engine使用映射來執行其他部分或其他Component的方法。想想這樣的系統開發是多麼棒的一件事。

將執行Method需要的Class Name、Method Name與輸入參數定義在動作,流程的控制可以根據執行動作可能傳回的結果設定接下來要執行哪一個動作,依此推疊出一個功能完整的流程。Flow Engine本身會是一個要額外開發的元件,如果想為流程編輯設計一個編輯工具又會是一個更大的元件,但是往這個方向開發卻可以有更多的回收。

Flow Engine需要設計的動作是有限的,經過完整測試後就可以不再變動;使用者需求的變動都被轉化為設定後,只要不出執行、判斷的範圍就只會改變設定檔而不用動到程式。重點在於系統任何的程式變動都需要經過一層層的測試才能確認是否沒有問題,使用流程設定則只需要測試過程是否正確即可。

2008年5月5日 星期一

O15 理想元件結構的調整

Component在我的想法裡會有19個檔案,這是為了解決之前提過的現象而鋪設。重點在於Flow與Action,在設計的流程裡經由Impl進入,參數參考Properties,處理的資料放在Model,處理的動作放在Action,唯一存取外部的是其他元件的Component Interface Method;Action裡也是類似的作法。在這個結構裡,接手的人可以把重心放在Flow與Action內部而不用到處找東西放在哪裡。

如果所有的元件結構都是這樣,在遇到有人新接手的時候,只要先講解Component的基本結構再說明現行系統的Package Diagram,最多只需要一天就可以讓新人開始追蹤與除錯。但是複雜的結構帶來的是設計與實作時必須在19個檔案中工作,手忙腳亂再加上偶而忘記把東西放在哪裡卻是免不了的,這也相對地更降低了產能。

在確定不需要某些需要的時候,可以縮減結構的複雜性。在各個部件裡,如果確定不會有改寫的需要,可以不要放置Abstract Class;Action動作不多時可以取消而以Method的型式放在Flow裡;Properties也可以直接用Java Properties或是基本Data Model的方式放在Impl裡取用。在堅持應有的Interface與Class下,元件的檔案數量可以減少到10個。

2008年5月4日 星期日

O14 設計與重構就像光與影的共存

設計的人雖然已經作出可行的系統結構,但是實作者是否有與設計者一致的觀念是一個問題,設計者對於這個系統的想法能不能傳承給實作者是一個問題,另外實作者撰寫程式時臨時發生的錯誤又是一個問題。

對於實作上較沒經驗的人,即使提供已經設計好的Method,實作時還是會發生責任錯置的狀況。在大陸的專案裡,有個集合物件本身擁有多個子物件,在需要記下所有子物件的現在狀態的功能實作時,把狀態記錄在集合物件而非要求各個子物件記錄自己的狀態。這是一個很明顯的責任錯置。

即使是設計相當有經驗的人只要一時閃神放錯地方,未來還是得重構。在設計的時候,我起初把數種不同元件的event處理都放到相同的Package裡,後來裡面Class實在太多太雜,只好依所屬元件再分開到幾個package存放,使其成為一對一的對應。

設計是把完成功能的動作放置到最適合的地方,但是沒人設計時能一直保持分工正確的,也沒有人寫程式不會出錯的,因此我們必須不斷地檢視自己所作的設計並予以調整。設計與重構就像是光與影的共存時常會發生,這也是我一直堅持要設計出只需移動方法就能完成重構的系統架構的原因。

2008年5月3日 星期六

O13 動態的應用(3)──Data

Data的存取是使用Class與Method混合搭配所產生的運用。我使用的地方在於Context的再生與Bean、Property內容的生成。

製作Context的輸出首先得要有易於列出現有資料內容的設計,然後要將列出來的Data名稱與值轉換為字串,這組字串可以存放在任何地方。再生的時候從字串裡逐一取得Data名稱與值再動態呼叫資料名的setter便可以復原儲存時間點的Context。

Bean的生成可以想成是Context再加上一個動態的Class,也因此定義的方式就可分析為Class與Data的組成。定義時需要特別注意Data的值為集合或是物件的狀況,集合主要有List與Map,要設計出集合類型與其中值的定義法;物件的文字定義或許可以依循bean的方式,只要維持固定的模式即可。

目前我選用的是用Hibernet的XML定義方式來存放執行時期所需的Data。

2008年5月2日 星期五

O12 動態的應用(2)──Method

另一個動態的應用是Method,是根據取得的字串對應指定的Class呼叫特定的Method。應用這個功能我們可以把想要執行的動作放到外部的字串裡。這樣的好處是可以把注意力放在提供執行方法的Class,而不用跟著變動執行呼叫的部份。

最常運用的是Data的setter與getter,根據屬性名稱組成存取屬性的方法並自動呼叫;雖然在提供存取的Class裡必須提供所有的getter與setter的方法,但是在呼叫的那一端就可以使用簡潔的程式碼來應付所有的Data存取。

除了資料的動態存取,利用Method的映射還可以作出執行的流程。讓執行的順序與內容定義在外部檔,用一個元件負責流程的處理,依照順序執行對應的動作(當然,動作要集中在一個Class或一個Class僅負責一個動作都可以),使用這種方法可以把變化的部分抽取到文字檔案的定義而不需要改動程式。

對系統而言,只要更動程式的任何一行都必須重新測試所有的影響部分,即使某些流程的改動很單純,但仍然需要完整的測試,一些要求更為嚴謹的客戶甚至會詢問改變的原因與影響。使用外部定義可以避免這個問題,這是因為定義的意義都已經定義為對應的方法,改變只限於傳入的屬性。

2008年5月1日 星期四

O11 動態的應用(1)──Class與Constructor

不管是Class的生成或是方法的呼叫都儘量使用映射的機制。這樣做的好處在於可以把字串動態地變成指定的Object或執行Obejct的特定Method,不必像初學者般運用大量的if-else判斷才能定位到應該要執行的程式。

當所有物件的規格都有定義Interface時,我們就可以在使用該物件的地方定義使用Interface。當這個Interface只擁有一個實作Class時可以直接new出來使用;但實作的Class種類變多時,就能夠依狀態的不同取得不同的Class Name再使用Class.forName(className).newInstance()拿到指定實作類型的物件。

生成物件時如果需要傳入參數,則需要取得Class的Constructor,取得時需要傳入Constructor所需的參數類型陣列;呼叫時也要將傳入的參數依序放在陣列裡,然後就可以取得指定的物件。一般來說會把Class Name加上id放置在設定檔裡,依不同狀況取用不同id的設定,如果需要傳入參數的話,則將相關設定放在同一組id下。

記得物件範圍的切割度會影響作法,物件範圍涵蓋得越大則越可能得準得許多應付不同需求的Class,但是如果切割得過小也會造成花費時間過多。這一直都是個值得深思的問題。

2008年4月30日 星期三

O10 我的設計準則(4)──靈活替換

一個以“滿足客戶既定需求”為目標的系統,只是勉強及格而已。因為無論是需求的改變或是因為要修正之前沒有被測出的錯誤,都會造成在固定結構下的變動而使得問題因為失衡而接二連三地發生。因此,彈性的設計雖然對實現功能沒有直接影響,卻能夠避免未來的巨大風險。

彈性設計的首要,在於切割出可以置換的單位。一個只能置換整個元件的設計,極可能在只有流程變動略大時就需要重新放上一個內容幾近相同的另一個元件;因此除了儘量應用參數化的設計外,我把元件的內部切割為六種特性放置屬於該部分的程式碼,每一個部分都可以根據實際的需要來改變實作的類別。

另一個要求是使用介面來定義切割出來的單位。要能靈活置換必須應用OO裡的多形特性,利用介面定義好切割單位的規格並在上層的程式使用,再用動態變換的設計(例如Design Patterns)更換實際工作的物件以應付不同的需要。

靈活替換還有一個重要的目標是使用較少的程式碼來達成較複雜的動作。像是Class、Constructor、Method等Java物件,以及Method所能涵蓋的getter、setter的資料存取方法,活用這些映射物件將有助於快速的開發與動態的改變。

2008年4月29日 星期二

O09 我的設計準則(3)──絕對重用

設計程式的人都知道,同樣目的的重覆程式碼應該抽取到同一方法裡作為重用。重用的好處主要是讓同樣目的的程式碼只有一份,在使用時有固定的用法且在修改時只需要修改一個地方就能改變全部。

在理想的設計裡應該抽取相同層級內相同目的的物件,不管是類別、方法或是常數,而不要包含不同層次的東西。在抽取方法的的時候,有時會有幾乎相同但有少許差異的地方,這時可把差異的部分用參數區別並在程式中判斷,如此就可以使用相同的程式碼。

對系統而言,特定方面的功能都集中在一個元件;對元件而言,六種類型的類別各自掌管屬於自己的行為;對方法而言,同樣的動作都抽取到同一個方法;對資料而言,使用的常數全部要定義在固定的介面裡。每一個可以抽取的地方都應該確實地實行,否則同樣目的的程式碼在系統裡到處都存在,會變得難以有固定的用法與切實的維護。

在相同使用層級的視界裡,努力把聚合力高的的共同部分抽取重用,是現在堅持的準則。檢視方法定義的所在位置是否正確與不應該存在兩份以上的相同目的程式碼或物件,是review設計的主要方向。

2008年4月28日 星期一

O08 我的設計準則(2)──動靜分離

撰寫程式時如果沒有適當切割與分類,會造成不同層次的動作都集中到一個類別,為了避免層次不清的混雜,所以希望可以遵循分層負責的準則切分出每一層該做的事。

在元件裡的動作雖然具有明確的目標,但是為了達成元件方法的目標,還是必須用正確的順序執行應該執行的小動作,所以這裡會依照動態的執行流程與靜態的固定動作加以分類,將不同的類型的東西封裝在不同的地方;也就是Flow與Action。

有許多人(包括我)奉行MVC的設計,但是在本質上來說MVC的組成會進一步劃分為MVFA。製作流程圖時是依照Use Case的流程串連各個負責實作的Activity,我是進一步把這個觀念應用在程式內部,以強制將流程與動作分離用來解決流程時常異動的問題。我的目標是在實作Flow的部分時,可以藉由週邊的Interface快速地知道可以使用的物件與動作全部有哪些,在幾乎不同多作學習慣情況下快速改變執行的邏輯。

另外不管是在系統上或是元件內,動作的實作是Activity所描述的,而且特定功能的動作會封裝在同一個Component。將流程與動作分離時,如果系統的精髓在流程,則把流程部分掌握在自己手中,將動作部分外包給其他公司開發;反之如果Component裡有設備或邏輯的機密,則將流程部分拿去外包而將動作部分留下來,如此將有助於商業機密的保護。

2008年4月27日 星期日

O07 我的設計準則(1)──分層對應

功能的完成是經由正確順序地執行所有必要動作所達到的,分析出所有的必要動作是製作功能的前提。收集大量的系統動作後,依據其執行的特性與目的加以群組化形成元件,這便是用於組成系統的軟體單位。

對開發系統而言,先準備好最底層的元件,像是Log、Message、Setting等基礎必用的元件,然後定義與Client to Server、DB存取元件,再來是定義基本的MVC元件;到這裡是所有系統都可以完全重用的基礎。再向上則是與Domain有關的MVC、Business Component、產生元件的Factory與系統Root Component。

往內來看,元件的處理邏輯有Impl、Flow、Action,處理的異常有Exception,處理時的變動參考Properties,處理的資料來源則是Model。這是元件內的六個組成單位,每個單位再封裝與自己負責功能的相關程式,依此向下完成每一個部分。

以Compnent為界線,向上組合成客戶想要的系統,向下再依MVC原則切分內部,每一個靜態物件或是動作類型都安排一個元件負責處理。二者的中間以Component Interface作為介面清楚地隔成兩個不同思維的層面,但是用相同的設計概念貫穿全部的Component,讓所有人快速瞭解整個系統是如何被架構起來的。

2008年4月26日 星期六

O06 做事的方法(11)──交付工作並非學禪

在民間故事裡,常會看到古代高僧做出一件事,讓大家根據自己的想法來推測有什麼含義,同時以檢視每個人的答案看誰的答案最接近高僧想的,然後把主持的位置交棒給他。用這個方式來確認誰的想法比較貼近出題者的想法是很不錯的,因為思維相近的人推想出來的邏輯都會差不多。

可是看一下其他的人提出的答案,同樣的動作在每個人的眼裡所解讀出來的結果都大不相同呢。如果主管交待事情時沒有描述出完整的想法,只單純地告知要做什麼,不僅接收命令的人會有“為什麼要做這個”的疑惑,消息讓其他的人聽到時還會造成每個人各自解讀主管想法再傳出去的結果,使得謠言到處流傳。

平時做事時,也有不少與主管想法不同的時候。管理階層想的是如何達成目標,實作階層想的是怎麼順利做事。有次幾個人為了怎麼順利執行進度而計畫了半天,最後跟主管確認時才發現公司政策上決定不能那樣做而要用另一種比較麻煩的方式。因此為了不浪費私下計畫的時間,所以養成先去詢問主管是否有政策考量的習慣;無法使用邏輯推理出來的結果,到底不是像我這樣的人可以理解的。

開發系統不應該也弄成這樣。設計者把想法直接做成程式,再讓一堆維護的人根據程式去猜想他原來為什麼這樣做,還有做了會有什麼好處或壞處,這是非常令人無力的事。如果自己不喜歡做維護的工作,又何苦把辛苦接手你開發系統的人搞得這麼累呢?

2008年4月25日 星期五

O05 說話為什麼會夾雜著英文單字?

以前在國內時,時常會疑惑為什麼有很多人在中文語句裡夾雜著英文單字,明明那些單字都有標準的中文名稱;那個時候都會要求自己說話時儘量使用中文單字。

在2007/11起與大陸的團隊一起進行開發,在討論與溝通的同時發現我說的話有時候他們無法明瞭,後來才發現是因為英文單字在大陸與台灣翻譯的用詞不同造成的。與其把話完全說完後才發現沒法溝通而重說一遍,倒不如直接把專有名詞全部置換在句子裡表達還比較快速;發現了這個道理之後,對他們表達的溝通內容就完全改變。

想像有本巨大的百科全書,裡面收集世界上所有的名詞、動詞、形容詞……等等,對一種語言來說,當你知道越多物件在該語言的名稱就有越多可使用的候選單字;在越多狀況下能夠用該語言表達自己的意義並且聽懂別人的表達就是越熟練的證明。

從根本來看,對每一種物品、動作、形容……如果可以輕易答出它在該語言下的念法,就更可以輕易地把自己的想法用該種語言的表達。而在溝通的過程中,由於每種語言對同一物件的形容不同,如果擔心產生誤會的話就使用大家都統一的英文來描述它。

註:倘使把字詞的本體想像為該物件的Interface,那麼各個語言下該物件的名稱就很像是該物件的實作。字典是收集指定字數內所有字詞的集合,各種字典內都具有該語言下所有字詞的名稱;把字詞名稱視為Object,字典就像是提供所有物件Interface實作的Factory。再往上看,我們在不同語言的需求下會取用指定語言的字典(Factory),這種選取的動作正是Abstract Factory的精神。

2008年4月24日 星期四

O04 讓參與者明白系統的一切

在進入實作之前,如果實作的人與設計的人不同,那麼就必須有移轉的動作;簡單地說,就是要把設計者腦子裡的東西“複製”到實作者的腦子裡。

移轉的動作常見的有兩種:一種是設計者把記得的部分跟大家說一遍,一種是循序漸進地將完成功能所需要的資訊製作文件。口述的方式的問題在於設計者想到什麼說什麼,除了細節難以即刻描述之外還有遺漏的問題;使用文件的話,一旦寫下就可以永遠保存,裡面記載的內容可以讓所有接觸的人看到全部的內容。

移轉最大的問題都在於設計者提供了些什麼內容?實作者所能知道的範圍一開始都被侷限在設計者提供的部分。雖然我們明白設計的人描述設計越詳細越好,不過由於把心裡的想法記錄出來需要時間,而且這對產能沒有任何幫助,因此許多設計者都把時間放在功能的實現而忽略掉文件內容的重要性。

適當的圖文描述有助於實作者直接吸收表達的內容,在描述內容有所缺漏的時候,實作的人必須再確認設計者的想法,或者自己去推想他原先想表達什麼。如果團隊裡想找出具有天份的人時,這的確是個不錯的方式,不過在正常的開發系統時,這樣地讓一群實作者繞一大圈才能懂設計的目標,真的是很浪費資源的作法。(節省設計者的時間,浪費實作者與維護者的時間)

因此,我強烈堅持應努力讓實作者快速明白系統的設計,以穩定而不易改變的元件與系統結構套用在所有地方,讓實作的人適當統一的寫作風格,也藉此機會減少設計者額外花時間製作文件。以這種方式,可以在一天的基本說明之後只依賴內部的註解進行系統的維護。

2008年4月23日 星期三

O03 測試先行──保證設計能有正確的結果

設計與撰寫的目的,最基本的目的是為了保證功能需求可以被正確的執行。無論系統內部設計得再爛,只要把執行功能需要的動作都依正確順序放入並通過測試,在使用者的角度來看都可以被稱為及格的系統。

由於系統有這個最基本的要求,因此“功能必須正常執行”成為首要的目標。在元件設計完後,我通常都會對它的initial、dispose與幾個重要的執行方法進行測試,不過我的目的在於檢查Method內執行的動作是否正確且沒有缺漏。設計時比較像紙上談兵,聽起來似乎都很有道理,但是實現起來就會發現有所缺漏或是邏輯相互抵觸,這些都必須依賴測試來發現。

外在測試單位是API與Component Interface Method,內部測試則是以Flow Method內部為主要,在執行時驗證Flow內使用的Action與結果是否符合完成功能的需要。從第一個實作的元件開始(應該是從底層開始實作),就應該在實作結束時實行Unit Test來保證功能正確而且不會被修改。順利的話,通過Unit Test後都可以預期使用是完全正確且沒有問題的;這會讓設計更上層元件的人專心在其內部而毌須擔心底層元件的問題。

如果有餘力的話,在設計與實作時應該要經常檢驗Component Interface的定義是否具有高內聚與低耦合的特性。雖然“功能必須正常執行”是最基本的要求,但是我們應該把眼光放遠,將目標放在“每一個元件都能被單獨重用”,這樣才能更加速自己系統與其他系統的開發速度。

2008年4月22日 星期二

O02 絕對不要直接寫程式來作設計

人腦是沒法放置太多資料的,除了極少數過目不忘再加上記憶持久的人,細節的部分到最後不是忘掉就是亂掉。在想要在程式裡執行一個動作的時候,如果遇上記憶沒法串連的情況發生, 放置的動作就有可能不在它應在的位置或是使用到不正確的方式。

程式的設計應該是嚴謹的,每種不同功能的相關動作必須要有一致的風格與寫法,而且必須在整個系統內予以貫徹。每一個動作的使用錯誤都會在切割於這個關聯上時造成必須改動的影響,這是因為在這個切割面上所露出方法的使用是不平整的。一個設計良好的系統,會在Use Case、Tier與Layer的三度空間中儘可能地放置整齊的切割面。

動作責任歸屬與使用方式的錯誤,因為功能還是達成所以可以通過所有的功能測試,但如果到系統開發後期才發現從某個切割面上必須調整,那可不是好玩的事;因為在那之上又堆疊了許多設計,任何下層的方法變動都會連帶造成使用它的方法必須修改。

在一次UI的設計上,欄位希望在輸滿指定長度時將游標移到下一欄位,但是開發的人寫成輸滿長度後要再多打一個字才會移動游標,後來為了改回正確的行為模式修改了很久,因為牽涉到得先檢查最後一字元是否合法以及移動游標時間點的衝突,而多花了不少時間。

維持各個可能切割面露出方法的正確性與一致性,這是直接寫程式作設計時無法滿足的需求;因此我強烈建議在每一個可能切割的地方先行檢驗其Interface與結構。

2008年4月21日 星期一

O01 設計常會出錯,而且實作時才發現有錯

不管哪個方法論都有這個問題,因為思考時所想的與實作時要做的有時會有差距,理論上設計經驗越多,差距應會越來越小,但是並沒有辦法保證所有的方法設計都做出完美的設計,出錯的部分通常是應該做的動作少做、動作順序錯誤或是執行結果的判斷錯誤。

主管時常問我:有沒有可能人在台灣帶領大陸那邊的人一起設計?我的回答都是:在設計階段是不可能的。人的想法都會有疏漏,定義固定的作業流程與不斷的檢查就是希望及早發現設計的疏失並解決,討論與檢查的行為在設計者之間有大量的資訊交流,拉開設計者的距離後在彼此之間的資訊交流相對地就變為比較困難。

主管還會問我:是否可以在台灣設計然後拿到大陸開發?我的回答也是:以現在的作法是不可能的。這是因為現在的設計者還無法寫出可以完整描述設計的文件,雖然現在公司已經導入CMMI,也規定出設計產出應有的章節,但是無法表現出設計精神的產出是難以順利開發的;更何況設計的人還沒有寫文件的習慣。

如何讓實作的人快速明白系統內部的設計概念是很重要的工作,因為讓實作人員依照設計用正確的順序呼叫正確的方法才可以保證系統的形成會符合設計;同時也因為設計的錯誤可能在任何地方發生,藉由設計概念的傳達也能夠讓實作人員協助發現設計時疏漏的問題。

2008年4月20日 星期日

N20 輔助的文件(5)──Component結構的所有檔案

與只擁有Interface、Controller、Model與Exception的簡潔型Component比較起來,自己所使用的Component結構實在算是個龐然大物,而且兩者間想達成的功能幾乎是一樣的。從上一篇的附圖來看,在開始設計方法前得先準備好一大堆Interface與Abstract Class,也難怪其他人根本不想改變原來的作法。

Component與Component之間在功能上是各自獨立的,但是以行為模式來說,所有Component的Implementation、Flow、Action、Model與Properties等等的特性其實是共通的。這個意思是針對Component來說,事實上也需要安置一個Abstract Component來放置所有Component裡各個零件共同的行為模式,同時還要加上管理物件生成與消滅的Interface與Method。這樣的需要就形成了這樣的Component結構:

把Component做成這麼大,如果得在佈置結構時花費過多時間,反而會降低產能,為了解決這個問題只好想辦法讓Component結構“自動產生”所有的物件。Component結構圖是既定的產出,輸入的變數可以侷限為Package Name、Component與output path,因此我在一開始就指定一個人專門製作這個工具。

如果三兩下就可以把這麼一堆的Component結構佈置進而開始設計,開發團隊的接受度與產能就可以相對提升不少。

2008年4月19日 星期六

N19 唯有先為他人著想,自己方能受惠

看別人寫的程式困難點其實在於找不到入口與看不懂處理邏輯,尤其在Component的實作只是直接從Interface後把所有的東西堆積在一個Class裡面時。

Component的設計雛型在D06時已經形成,MVC的設計方法將之修改為MF(Flow)A(Action)C並將之各自定義在專用的Interface裡。以類似SOA的觀念把Flow拉出來,對於設計者而言就變為只要挑選Action裡的方法組合為處理流程,再加上Properties改由外部傳入而不使用內部設定檔、有狀況時只拋出Exception而不在內部作任何處理,讓我的Component具有更高的reuse特性。

這樣的Component想法在2008/04時對開發團隊說明,參加的人也認同我的作法可以解決一些目前還難以解決的設計問題。我特別說明這個作法在設計時因為要把原來直接撰寫的程式拆解到不同Class的方法裡,需要直接撰寫大約五倍的設計時間,但是適當切割的作法可以讓修改除錯的時間減少一半以上,且能應付大多數的需求改變。

設計的人在定義系統時多做點,可以讓未來使用方便而且容易維護,自然可以節省未來親自處理的時間。但是習慣快速寫作的人是否能夠接受這樣的作法,尤其是多花自己時間的作法呢?如果內心的“設計”僵化到難以改變,也許人就已經定型為那個樣子了。

這張圖是我最後定型的Component內部結構。

2008年4月18日 星期五

N18 做人的方法(10)──收穫來自投入的部分

每個人一天都有24小時,扣除生活與工作必須花費的時間外,是自己可以運用的。雖然在生活中遇到的事件都會讓人學到些什麼,不過將空閒的時間投入什麼方面,在那個方面的就多少有更多的收穫,但是相對地沒有投入的部分成長會較為緩慢。

如果一個人把時間投入在讀書,與別人就沒有較多的互動,如果生活與工作上沒有辦法學習到與人相處的方式,那就有不懂做人道理的可能;把時間投入在遊戲或電影,要是內容沒有值得學習的地方,就只能得到休閒的效果而不會得到看書時能有的收獲;常與朋友一同聚會,倘使交談的內容沒有值得學習的地方,那同樣會讓學習止步。

學習的效率也是一個影響很大的因素,同樣的投入時間在不同的人身上會有不同的進度,有的人隨便看點書很快就能吸收,但是有些人投入很多的時間卻進度有限,不過只要有重覆的練習還是有很大機會學會。“一分耕耘,一分收獲”描述的是只要投入時間就會在該方面有所進步,“專家是訓練有素的狗”雖然很不好聽,卻也是描述在該領域的專家投入了更多時間反覆練習過。

如果希望自己學到什麼(目的),就要決定達成學習目的的過程(步驟),然後按部就班地投入資源,努力學習以期產生效果(影響)。我們應該依現有的資源來計畫好自己的人生可以有什麼樣的成果,依此決定更多有機會達成的學習目的,藉此慢慢構築出個人的生涯規劃並努力去實踐。

2008年4月17日 星期四

N17 API字典與一般字典的異同

所有開放的API都應該撰寫詳細的Java Doc給使用者參考,包括Package、Class、Method、Data,只要是使用者看得到、用得到的東西都應該要有說明。文件的製作就以Java Doc直接產生並加以收集保存,我會稱之為API字典。

不管是哪一種字典,目的都是收納該領域應該有的項目,使用者想了解某個項目的使用說明時,可以根據字典的分類原則從上到下找出需要的項目。不過若是想要表達某個意義,一般的字典卻無法讓使用者條列出符合該意義的所有項目供使用者挑選,使用者只能依印象或請教別人來找出達到某個特定目的的項目,再瀏覽解說看是否符合需要。

API字典的目的,一方面可以讓使用者明白某個Class的功能與有什麼樣的常數與方法,另一方面是明白指出系統內開放使用的API範圍。雖然安排的原則與一般字典相同,但是我們可以搜尋API資料夾的內容來找出擁有搜尋關鍵字的所有檔案,藉此縮小尋找的範圍以希望精確地找出需要的項目。

看到一個API後找出它的使用說明,因為某個目的找出所有可能的API作篩選,這是API字典應該提供給使用者的功能。

2008年4月16日 星期三

N16 設計的製程(10)──API的定義與分類

執行方法後立即傳回結果,而且在執行前後不需要額外的設定且不留下執行狀態者,我都會將之歸類為API。在這樣的定義下雖然可以把方法宣告為static但是我仍建議使用Singleton的設計,以確保將來出現變化時還擁有改變的彈性。

API可說是對於資料的常用動作,像對於String、Array、File、Collection甚至是XML Dom我都定義了Class來放置對應的API方法。雖然許多JDK的物件都已經提供基本的操作,但是還是有一些數個操作組合形成的動作,或是加強原先操作的功能與應用性等等的需要,這些製作的產出都要對應操作的物件類型放到各自的API Class。

除此之外,對於自己定義的Data Model也可以應用這樣的設計:Data Model本身只提供最基本的getter與setter,有組合性的需要時再於Data Model的上一層放置一個Data Model API來操作。把對資料的動作分成基本與組合兩層放置,這個觀念與Controller裡的Flow與Action是一致的。

2008年4月15日 星期二

N15 如何學好地理與歷史

地理與歷史雖然被分為兩門功課,但是兩者之間的關聯是非常密切的。地理描述了地殼的特質,包括了地形、礦產、種植、畜牧、城市等等;歷史描述的是人類在地球上生活時發生的重大事件,包括產生的原因、事件的內容與日後的影響。

從系統的角度來看,地形、氣候與礦產是位於最下方的靜態層次,城市、畜牧與種植是人類決定在此處生活後才有的中間層產出,最上層則是種族與戰爭、遷徙等歷史事件的發生。人類受地形影響決定在哪裡建造城市、在哪裡種植與畜牧,但人類的歷史同時也改變這些;人類可以改變小部分的地形,地形變化在人類歷史中雖很少發生,卻會改變該地大部分的人類(例如龐貝城)。

這兩門功課在求學階段裡都是硬背硬記度過的,主要是因為課目被切分為本國地理、世界地理、本國歷史與世界歷史,每位老師都很認真地教他們負責的科目而忽略了說明彼此間的影響,連帶地讓我沒法去融會貫通世界的平衡。

前陣子在觀看介紹圓明園的DVD時,裡面帶到建造圓明園時與乾隆死後的幾十年間,歐洲各國發生了什麼樣的事情(工業革命、法國大革命等),相較之下才更明白在某段時間裡各國的演變與之後發生事件的關聯。相信唯有明白在每塊地形上的人類在同一個時間點時各發生了些什麼事,才能對接下來的世界局勢走向有更清楚的掌控。

註:高中時選讀的是自然組,國中與高一的地理、歷史都是在課本上硬畫重點強記而及格的,因此以上僅純屬個人推測的結論。如有人看後嘗試不同的讀法而導致死當的話,本人概不負責!

2008年4月14日 星期一

N14 設計的製程(9)──Component的說明文件

對要使用的人來說,只需要說明詳盡的Component Interface註解就足以知道它是否適用;真正要使用時需要的是Component Interface Method的Java Doc加上簡單的使用範例。所以讓使用者明白Component的功能與用法是撰寫文件的方向。

功能與用途寫在Class的註解,常數與用法則寫在public Data與Method前,所有說明都用Java Doc的寫法以便產生給使用者觀看的文件。除了單一Component的內容之外,還要準備所有Component的分類清單,讓使用者快速依功能需要找出所有可以被應用的Component再從中挑選最適合的。

給維護人員看的是設計的想法,依之前提到的原則與順序,提供描繪Class與Interface的Package Diagram,同時在裡面標示所有Data Model、Action、Properties、Exception等靜態的方法,再以實作Component Interface Method的Controller說明來串連,就可以快速明白所有的方法合作的概念。

Component文件主要在於使用上的描述,讓使用者只要看文件就會運用的話,可以省掉教學的時間。由於理論上Component會在許多系統內使用,通常在初期時就會經過需求確認與較多測試,維護方面的要求相對的就會比較低一些。

2008年4月13日 星期日

N13 佈置容易看懂的軟體結構減少文件製作

似乎除了設計者之外的所有人都贊同文件的重要性,認為留下適當的文件是該做的事。但是真正在做事的人一邊寫程式還得一邊更新文件,這樣子相當於處理兩倍的工作量,每一個地方都這麼做的話肯定會累死的;但是要是只更新一邊就會造成內容不同步:正確的是程式,錯誤的文件就沒人要看,等於白做。

厲害的程式設計者通常不寫文件,就是因為他們覺得文件寫了很久卻沒有參考價值,反而拖累他們的開發進度。可是問題是他們寫的東西雖然功能正常,想要瞭解裡面做的事時,除了在沒有文字描述的輔佐下難以看懂之外,還得面臨詢問時得到“我忘記了”的回答。

大陸某大公司說他們採用敏捷開發,其大意是先定義好系統的模組,並且完整定義出每個模組的介面,結構經過討論確定後,每個模組的內容再由負責的人直接在裡面寫程式實作。發現這與我使用的開發方法很接近,只是我連模組裡的軟體結構都有佈置的準則。

每個獨立區域(包括System、Package、Module、Component)都應該要至少有一張靜態結構的Class Diagram,還要有完整的Interface與Interface Method清單來描述這個區域“應該”具有的功能;另外“如何”履行這些應該具有的功能,就努力將之收集在Controller的方法裡,以標明原因與目的的詳細註解加以描述,同時還是要記錄流程內使用同一Package內的動作Method。

動態的流程使用註解說明每一步驟的使用,靜態的動作記錄詳細的操作方式與使用效果,如此一來可以省卻變動性質最大的流程描述文件,同時保持記錄全部動作的必要資訊。

2008年4月12日 星期六

N12 備齊資料的記錄才能應付各類的分析

相信有許多人都喜歡看棒球,不懂門道的人大多只看球星上場或者自己支持的球隊表現如何;真正研究棒球的人會去收集許許多多的數據來統計每一場比賽裡的每一種事件,比較看看哪些人、哪些球隊、哪個球季有什麼樣的統計數據。

棒球比賽裡最基本的事件是投手的投球,投手投出一個球的時間很短,但是每一個球記錄下來的資訊卻是大量的。先看時間點,投手投某個球是這場比賽的第幾球、是哪一局的第幾球、是這名打者的第幾球?投出的球球種是什麼、時速多少、投到哪個位置、是好球還是壞球?對應投過來的球有打者的反應,打者的每一球有沒有出棒、被判好球或壞球、揮棒擊出後球飛往哪裡、是飛球還是滾地球、結果造成了幾個壘打數或是造成幾個人出局、怎麼出局?

再往上推還有今天投手與打者的統計數據、投手與打者的對決統計數據、防守者與跑壘者的攻守統計數據、如果這場比賽有誰有特殊的表現也會另外記錄……我雖然不是棒球專家,但是球賽看久了也知道有哪些數據常被提起,也可以發現專業的棒球可以說是想要知道什麼樣的資訊幾乎都有這樣的記錄。

在這裡我想要表達的是,在一場比賽裡每一次的投球所需要記錄的資訊是如此之多,再衍伸出來可以查詢的數據還有更多。倘使我們將系統(或元件)的開發視為整個球季,把每一個方法的設計視為每一次的投球,這樣是否可以感受到Method設計資訊在系統(或元件)裡的重要意義呢?

2008年4月11日 星期五

N11 令人感到麻煩的設計(15)──避免將參數放在設定檔

決定不用程式碼寫死使用關係的同時,表示我們必須在某個地方記錄對應的關係並在執行時取得。通常的最快作法是將參數放在properties檔案,再使用ResourceBundle方式讀入為Properties取用;不過使用時再多往幾個方向思考一下,或許會有不同的決定。

檔案位置的固定。很多時候為了方便設計與管理,Component的參數檔案會被指定放在一個固定的地方,而且通常是使用它的Package裡。這樣的放置會造成設定檔被“綁死”在那個地方,無法應付系統要求要集中所有的設定檔方便管理的需求。

檔案類型的固定。在設計上採用傳入檔案所在的URL後再於內部讀取內容,這樣的設計可以讓設定檔在任何存取得到的地方。不過取得的來源就只限於properties檔案,要是客戶另外有xml或是資料庫來存放設定的話,就沒法動態由不同的來源產生。

雙向讀寫的要求。即使設定檔由外部組成Properties物件再傳入Component的作法可以滿足以上的想法,但是Properties的特性是只能讀取的。設定一般來說是允許客戶修改的,直接修改檔案內容雖然快卻有修改內容發生錯誤的狀況,此時可能需要選用別的物件。

靈活替換的判斷資訊有很多存在於設定檔,針對以上各方面的考量,在設計上我會定義自己的設定檔物件,由客戶決定怎麼生成Component需要的設定來滿足以上的想法;當然,這個設定檔物件是存放在元件庫裡的。

2008年4月10日 星期四

N10 置換整個Class與用參數涵蓋可能變化的比較

在軟體工廠的概念裡,所有的元件都像是積木有自己的規格,系統的開發在大部分的地方都是只選擇適合的積木堆成我們想要的模樣。在需求變更的情況下若有元件變得不適用,可以將之拔除再另外放一個更適合的進去。

以取出積木再另外放一個進去的想法,有可能會使得功能幾乎相同的元件有許多分身存在,這是因為製作元件的人不想分析比較彼此之間的差異,而直接把變化寫死在一個特定元件裡。如此一來每次的小變化都形成一個元件,時日一久自然就可以擁有“看起來很多元件”的元件庫。

玩過積木的人應該注意到,同樣有那種具有可動部分的積木,像是螺旋漿、門窗、旋轉軸之類的。這種積木只要撥動一下可動部分,就可以讓積木以不同的的方式呈現。如果今天的積木在開門、關門與半開關之間都要求使用者“置換”該積木的話,玩的人會不會覺得頭很痛呢?

直接把變化對應到不同的元件是最直覺的,開發者幾乎連想都不用想就可以由copy-paste或是繼承快速地做出一個新元件;去分析改變與影響並在同一個元件內去涵蓋相對地就得花上比較多的時間處理,但是對使用者而言卻有能應付多樣變化的單一元件。如果你是開發者,會決定怎麼做呢?

2008年4月9日 星期三

N09 令人感到麻煩的設計(14)──Component間的避震器

Component更應該要堅守使用專用Data Model的原則,因為Component之上是各式各樣不同的其他元件以及系統。只要兩個以上的Component使用了同一種Data Model,即使二者之間並沒有直接呼叫對方的Method,兩者還是產生了間接的使用關聯。

如果在設計上它們不應該產生使用同一種Data Model的關係,那麼應該先讓兩個Component使用各自的Data Model,並另寫一個資料轉換的函式負責在兩種不同的Data Model間搬移內部的資料,或者是在系統裡準備一個共用的系統資料區,在全部需要準備Component Data Model的情況下都與系統資料區作資料的交換。在開發系統時我比較偏好後者,縱使需要為每個Component撰寫一個資料轉換程式,卻可以保證在同一系統下是同一種作法。

在動作方面,可能某一個系統的動作需要同時使用數個Component的方法,直接依序把Component Method放到系統動作方法裡是直覺的設計。但是在分析階段時,必須要檢視是否有一個系統動作需要同時執行數個Component Method的狀況,如果有的話還是另外抽取一個系統方法存放為佳。

無論是Method或是Data,在有差異存在時務必要安排一個物件(而且只有一個)來負責填補其間的差異,以期在使用時能夠直接呼叫而感覺不出有差異。

2008年4月8日 星期二

N08 開發系統與設計Component的差別

設計一個功能,是要解析出達成功能的所有動作,並將之以正確的順序串連起來。在時間急迫的專案裡必須以完成功能為優先,這時可以簡單地先寫出正確順序的所有動作而先不寫上完整的錯誤控制,等到測試階段再根據測試出來的錯誤補寫上錯誤控制。這是很好用的快速作法。

可以這樣做的原因在於專案的功能佈置最上層,除了少數幾個功能可能使用之外沒有任何程式使用,因此無論作什麼樣的改變都幾乎沒有影響。但是Component的設計卻絕對不能用這種走捷徑的方式,因為有非常多的程式使用Component,只要內部的設計有不正確的結果或是因為修正錯誤而改變,都會有多到難以估計的程式受到影響,根據理論所有被影響的地方都必須要重新測試過以保證正確。

有時使用其他元件時會遇到邏輯控制不理想的狀況,那時只能在自己程式的流程裡記錄現在動作的狀態,在後面的流程再判斷動作狀態的內容作對應的處理。這樣的繞路處理很像是在沒有設計好的主機板跳線來達成功能,是很不理想的設計結果。在之前使用swing與swt兩種UI元件時都曾經遇到許多這樣的現象。

設計Component的態度應該時時戒慎恐懼,一方面要努力思索提供足夠的Method應付全面的需求,另一方面要維持設計的品質與定義吻合且沒有錯誤,因為設計不良時微小的改變都會造成其他系統的大量變動。

2008年4月7日 星期一

N07 設計的製程(8)──提供不同範圍的流程Method

雖說Component只提供動作方法,讓系統定義各自使用Component的流程方法是最有彈性的作法,但是使用Component的流程也無可避免地會因為大多系統的使用方法都相同而必須抽取到元件庫的狀況。

系統對於列印的要求定義了一個方法printData(),內部的分解動作是進紙closeInserter()、列印print()、退紙closeInserter(),這三個動作是Component提供的基本方法。後來發現printData()是所有系統對印表機的共同功能需求,因此將集合數個列印動作的printData()放置到Component。請注意此時Component已經帶有了流程方法。

再更進一步在分析系統時又發現:即使列印是把三個動作集合在一起,但是如果接下來還要在同一張紙上接著印其他資料時是不需要退紙的,因此在原有的printData()加上列印後要不要退紙的參數。我們可以發現,Component帶有流程方法後就增加了設計的複雜度。

在流程方法裡的每個動作,使用者都有可能決定要不要做?該怎麼做?所以設計時也必須要同時考量是否要提出參數供使用者決定;但是如果呼叫時每次都設定一大堆參數又相對地比較難使用,所以同時要提供參數較少的預設的使用方法。有時的需求又會要把數個較小的流程方法集中在一個較大範圍的流程方法裡使用。

Component加上流程Method時將會增加許多設計的複雜度,切記要完全遵守OCP設計原則來設計。

2008年4月6日 星期日

N06 設計的製程(7)──備齊Component應有的Method

Component的初期只是系統裡的一個Package,Method的設計都是根據Activity的定義而來。當我們覺得這個Package適合抽取為Compoent時,或許在現在的系統而言只要直接將之劃歸元件庫就可以繼續開發,但是對於一個必須應付多方面需求的Component而言卻應該把所有該有的Method都定義清楚。

想像一下ATM對於晶片金融卡的動作,我們需要的是讀取卡片帳號、比對密碼、變更密碼、顯示約定轉帳帳號等動作,所以在ATM的設計裡定義了這些動作。後來覺得處理晶片金融卡的動作適合抽取為Component,但是原有的動作Method是足夠的嗎?當開發網路ATM系統時,我們會發現還需要加上寫入約定轉帳帳號的動作。

當然有人會質疑在第一個系統時並不知道未來需要什麼,但是我們可以根據簡單的原則來檢視定義的Method。首先是生成與消滅,用來確認如何取得Component物件來使用與不用時消滅它的方法;再來是資料的CRUD原則,Component裡的所有資料都應提供Create、Retrive、Update與Delete四種方法才算是完全的使用(有保護性質的資料就需要隱蔽其中幾種)。

這兩個原則用來檢視存取資料的靜態方法,如果Component裡提供了流程控制的方法,就應該有更進一步的考量。

2008年4月5日 星期六

N05 Component設計要恪守OCP設計原則

Component是基於抽取共用原則所抽取的Package層級物件,一旦標記為共用則表示每個Component Method都會在未知數量的系統被未知數量的程式所呼叫,我們可以想像如果Component Interface Method的定義改變時(例如原來沒有傳回值現在改為傳回boolean),跟著而來的會是大量修改與測試的動作。

處理所有共用部分的最高指導原則就遵守OCP設計原則:Open for Extension, Close for Change。對於原有的方法要努力讓它保持不變,如果內部的作法有所改變就試著在內部程式消化掉,永遠讓方法的使用維持著原來的定義;如果改變無法在內部程式消化而勢必影響到外部的使用,那就就另外定義一個新的Component Interface Method提供給新的需求使用。

單純目標的動作改變的機會不多,大部分的改變發生在流程裡,尤其是改變動作順序或增減動作之類的。也因為動作內部隱含的邏輯極少變化,所以它們是比較適合開放為reuse的部分,這也是一般Component Interface Method定義的方向。流程控制著動作方法使用的順序與方式,因為每個系統操作Component的需要都不一樣,所以會在Use Case實作方法的邏輯裡處理;不過還是建議針對一個Component的操作邏輯另行在系統裡拉出一個對應的地方放置。

使用Third Party Component時若擔心其他公司改版造成的影響,可以幫它加一個Wrapper Class把Component封裝在裡面,讓改變只影響Wrapper的內部作法而不致於擾亂系統的使用。如此將可以更放心地使用其他已經經過測試的好用Component。

2008年4月4日 星期五

N04 設計的製程(6)──設計Component Method

Component Interface Method所定義的,一般都以功能流程裡的一個動作來看待的;流程的分析裡有對某個動作的需求,因而要找出元件裡有支援該動作方法的來使用。

Component的抽取大多是以Package為單位,即使有單純以一組Class為單位的,但是整體來說都是基於Interface Method的定義適合獨立作業。M02裡拉出的特定用途Method時一方面需要檢視元件庫裡是否已經有同樣功能的元件存在,如果沒有的話還要檢視這個Method是否適合作為Component。決定是的話還要再決定它應存在的reuse層級(通用, 行業別, 系統別, 專案專用)

Component的本質是Package,我們可以將之視作更小型的System,裡頭應該只有使用到的其他Component或內部Method。首先我們要用Class Diagram描述Component的內部結構,接著每個Component Interface Method都要至少用一張Sequence Diagram描述執行的過程,作法就像處理System Interface Method一樣。

Component Interface Method進入點同樣會先進到流程Method,設計時要記錄流程裡使用到的每個動作Method;對於每個流程與動作Method也要記錄所使用到的內部變數與其他Component。以Method為主軸記錄使用到的Data與Component就可以追溯出Component的所有使用關聯。

2008年4月3日 星期四

N03 Compoent與API的判斷與佈置

在我而言,判斷的準則其實很單純:就看程式模組處理後是否在物件內保存著狀態。單純地處理傳入的資料並傳回者為函式,除了處理之外還將會影響本身內部狀態者為元件。例如:把檔案讀出為字串應是API、讀取檔案後放置資料的物件就屬於元件。判斷類型後再根據其應用範圍決定它的reuse層次,接著再依照功能放到所屬的區域裡。

在架構上來說,元件與函式都與專案無關。專案之下先是元件,再往下是API,最下面是Java API或是Third Party API,使用的方式是上層的可以使用下層且可以使用同一層內的物件(不可造成循環使用)。Third Party API有時也會被列入到自己的元件庫管理。

在元件的使用上,雖然可以直接呼叫元件提供的方法,但是在系統裡對於元件的使用結果通常會有自己的邏輯處理,這些元件專用的邏輯處理為了避免散落各處,最好是規定一個地方來放置。也因此,我建議對於元件的使用要用以下的佈置。

一個典型的範例在於要處理印表機的低階控制碼:元件動作管理控制碼的輸入、元件動作控制負責印表機對於每個控制碼的反應,這兩者都屬於元件層;專案對於列印的要求是組成固定的格式並控制進退紙與列印,這些複合元件動作就交由元件動作結果控制與元件資料轉換來處理。

2008年4月2日 星期三

N02 資料變化的設計與做事方法的設計

程式設計的目的是要存取資料,這是毋庸置疑的道理。基於這個想法所衍生出來的方法論,簡單地說是先分析出功能裡資料存取的順序與使用方式,再放置處理用的程式;因此我們可以得到資料流動的流程圖,與每個資料流動時的程式處理模組。

這樣的設計可以滿足系統的開發,因為系統最終的目的就是要改變資料;在這個情況下,資料結構與處理程式的定義與追溯關係就顯得十分重要。無論哪一種設計方式,其實都會要求資料結構與業務邏輯的對應關係,E-R Model與資料庫的正規化都可以讓我們設計出最佳化的資料結構。

OOAD的介紹裡常說它是貼近自然界的設計方式,一部分的原因是其設計的重點在於物件的封裝,將系統裡的資料依據特性分別包裝在對應的物件,同時也將處理資料的程式封裝在一起。功能依Use Case Driven的方式設計,強調功能裡應該具有的每一個動作這比起依資料變化所做的設計,更要求了做事方法的設計。

現在的設計方向走到了服務導向,希望連Use Case都可以做到reuse。設計從資料層次,往上推展到處理流程,再往上擴充到整個功能,開發時越來越便利的使用,表示設計時要容納更多的考量。隨著封裝更多的邏輯,除了要保證功能一切要正常之外還要顧慮到使用上的變化。每個功能越來越複雜,也越來越難實作……。

2008年4月1日 星期二

N01 元件庫與函式庫的重要性

在開發系統的過程中,有時候我們會發現某個Interface或某個Method,除了可以滿足現在的系統需求之外,還能夠應付未來的使用。這樣的特色,即使Interface或Method內需要作小部分的調整或是將一些邏輯變數拉成參數,但是還是適合投入一些努力使之成為可以reuse的元件或函式。

擁有元件庫與函式庫的好處在於各個不同的專案裡,為了相同目的所設計的動作,可以找到一個已經開發並且測試好的元件或函式直接使用。抽取元件與函式時,應該也要規畫元件庫與函式庫裡的結構,規定基本共用層、行業共用層、系統共用層等放置的地方(參考L07的層次,元件與函式都各要準備自己的結構),再依照其特性放置到該放的組織層次裡。

除了放置到應用上的結構外,還要依據其應用功能加以分類。我們可以想像元件庫與函式庫就像百科全書一般,想要特定方面的功能時可以先找出符合的元件與函式,再參考reuse層次從中選擇最適合自己需要的。對於一個特定的元件或函式,同樣也可以取得使用時的所有資訊。(也可以考慮將Third Party API加以整理歸類)

函式與元件應該是經過良好設計與完整測試的,在取用時儘可宜接應用而毌須浪費專案的資源再去了解設計、實作與測試。在系統需要與元件功能能夠吻合時,對系統的開發是有絕對好處的。

2008年3月31日 星期一

M25 Package、Class、Method與Data的使用想法

系統的Use Case流程與Activity間的作法與Interface Method內的流程及動作間的作法是完全一樣的,重點都在於將需求定義出分解動作,然後另外使用流程串連起來以完成功能。把動作與流程分離,同時每個相同的動作都抽取到一個動作方法來呼叫是設計時的重點;另外考量的重點是動作方法放置的地方,要在同一個Class、同一個Package或是另外的Package需要考慮好。

當實作功能所有的過程都被拆解成Method後,應該要為每一個Method作定位。定義每個Method所處的Package、所處的Interface,思考時除了系統層面的Module定位(水平)之外,還要再考慮重用部分的定位(垂直),有的時候Method必須另外放置在特性描述的Interface。唯有把Method放置在多維空間的正確位置,才能在面對不同方向的切割時還可保有應有的功能。

Method裡使用的Data擺放也是需要定義的。在內部用完即丟的Local Variable、記錄Class狀態的Class Attribute、聚集成Package專用的Model Class或是定義在系統資訊區的Package,根據實際使用的狀況決定好放置的區域,才可以在適當的時候作正確的切割。(常數也是需要定義的Data)

reuse的想法除了把相同用途的Method或Data提取出來(同時要放置在應在的Package與層次)之外,也要包含在呼叫Interface Method時傳入參數就能變更內部處理流程的設計(需要定義更多類型的動作Method來支援);但是唯有盡量發揮這兩種設計特點,才能讓系統有更理想的最佳化。

2008年3月30日 星期日

M24 系統軟體架構的層次(5)──瞭解本質後定義自己的設計準則

如果曾經接手過別人寫的程式,大多數的情況裡都會覺得很難看懂,這是因為風格的不同所造成。由於軟體結構切割觀念、類別封裝設計原則、執行方法層次認知與使用資料建立擁有的差異,使得我們不容易理解別人的想法。

這段日子公司正在推行CMMI制度,可是CMMI對於設計僅僅規範了 SA/SD應該有哪些描述用的文件,卻沒有規範設計產出的風格;在這個情況下無論文件作得再多,也難以讓別人知道系統內部的設計。所以有機會跟主管聊天時,我都會強調先規劃好使用的設計方法論以及設計模型風格的重要性;唯有全公司的人都有類似的設計想法,才能夠快速地接手其他人的設計產出!

我強調的是Package、Class與Method的設計都遵守一對一的對應,根據相同應用所使用的資源都要抽取到固定的地方。“分層對應,動靜分離;絕對重用,靈活替換”這十六個字所表示的四個原則,濃縮了我這兩年接觸OOAD後最終的心得。

看過的設計文章都是以Class為基本單位來闡述,但是在瞭解本質後發現Method才是系統功能的主角;至今為止,覺得自己想出來的設計概念與別人大不相同。未來如果有機會在別人的系統裡看到與自己想法相近的設計,應該可以合理懷疑是受到我部落格文章的影響吧?

2008年3月29日 星期六

M23 Method vs Method、Data的追溯關聯

至今為止我們應知道Method才是系統執行的靈魂,追溯表的根本其實是Use Case對Method的追溯,中間使用到的Activity、Package、Class等等都只是為了表彰分層負責與功能集中的中間產物。不過直接從Use Case拉出關聯到Method將會是非常繁雜且龐大的過程,因此還是將系統切割多個層次,分開表達每個層次都應有的資訊與追溯較為簡潔。

在這裡提的Method理論上應該要是所有的Method,不過最小的限度可以定義為Interface Method。對於一個Method而言,對外應該要知道它被宣告在哪個Interface,輸入輸出的資料以及拋出的例外類型;對內則應該知道它執行時呼叫了哪些方法,以及使用哪些Class Attribute與外部的資訊。

之前提過Method本身擁有大量的資訊,是記錄時的重點,如今的記錄必須再加上Method使用其他Method與Data之間的關聯。Method vs Method的水平追溯表,Method vs Data的垂直追溯表,系統裡大量的Method與Data要怎麼做才能夠記錄必要的資訊,又不致於造成過多的額外工作,真的是一個很重要的課題。

我認為製作時必須分為兩層:每個Interface所對應的Class要做一次與其他Class的關聯(Class層級),每個Package Interface也要製作一個Package與其他Package的關聯(Package層級)。

2008年3月28日 星期五

M22 設計的製程(5)──決定產生與擁有資源的方式

這裡所謂的資源,其實是另外抽取到其他Class放置的Method或是Data。在這裡需要決定每個Class是如何生成的、如何保存以及如何取得與置換;如果這些外部的Class都有定義Interface,就可以應用多形的特性來靈活地替換實際使用的Class。

建立的方式通常有從建構子傳入、從方法傳入、建立固定類別與動態建立類別等四種;擁有的方法通常有直接擁有物件、使用集合存放與從其他物件取得等三種;在動態生成物件的場合,物件的Class Name可以從外部傳入或是由設定檔取得。

Method本身就具有大量的資訊,與所擁有的資源(其他Class)之間也有需要記錄的資訊。每一種佈置一定都有其原因與用途,因此在Attribute上需要註記的說明也是不可或缺的。註解的記錄方式可以在Attribute上加上Java Doc,除了可以直接出現在產出的說明上之外,還可以經由Reverse回歸到Rose Model裡。

使用在不同Class或Package的Method時,宣告Interface再應用彈性的設計生成Object,彈性設計指使用的實體由外部傳入,或是傳入指定名稱再由內部生成實體。

2008年3月27日 星期四

M21 彈性地擁有Method與Data──再看Design Pattern

有專家歸納出目前常用的Design Pattern共有23個,各位仔細觀察的話會發現裡頭用UML描述的全是Interface與Class。實際上做事的是Method,擁有資訊的是Data,但是這兩者並不會被直接拿來定義Design Pattern,而且群組化Method與Data的Class;如果這個事實還無法令人想更加改善Class的設計,我也無話可說。

Design Pattern用另一個說法來看的話,可以說是設計者想根據某種用途彈性取得Method與Data的設計,內部的作法是經過許多次實現而得到的最佳經驗值。取得與置換資源的方式有很多種,Design Pattern由於有很多人改善過將會是比較理想的作法;將之視為非做不可的設計而大量使用倒也不必。

Design Pattern的應用有三個分類,下面是所有Design Pattern與其分類;詳細的作法請另外參考專門的介紹。

生成模式(Creational Patterns),Class生成時可依其生成模式選用。包含有Abstract Factory、Builder、Factory、Prototype與Singleton五種。

結構模式(Tructural Patterns),為特殊的目的在結構方式安排Class的關係。包含有Adapter、Bridge、Composite、Facade、Flyweight與Proxy六種。

行為模式(Behavioral Patterns),為了達到指定的功能而將Class再拆解為更小單位,並依其關係結合成形。包含有Chain of Responsibility、Command、Interpreter、Iterator、Mediator、Memento、Observer、State、Strategy、Template Method與Visitor。

2008年3月26日 星期三

M20 設計的製程(4)──設計Method使用的Data

Data是Method在進行時所需要的輸入或輸出資訊,就像是我們在做事時分解出來的工作項目有其需要的資源般,在程式裡時常需要將這些資訊的處理結果保存下來供之後的方法使用。

在Method裡即用即丟的Data稱之為Local Variable,它們的存在只是Method裡暫時宣告出來放置短暫的階段性資料;只要是每次進入時都能夠由其它Data演算出來的,都會以這個形式宣告。(Local Variable可以視為reture value傳回)

處理結果的資訊如果必須保存下來提供給下一次呼叫Interface Method參考的話,就應該把它宣告在Abstract Class或Class裡,根據不同的方法劇本來存取它的內容。在我的設計裡,Class Attribute通常都會宣告為protected以便在繼承它的Class裡直接取用。如果允許外部取用該Class Attribute的話,一定要另外再宣告getter與setter。同時Class必須在initialObject()與disposeObject()裡管理所有Class Attribute的生成與消滅。

Package層級也有放置階段性資訊的地方,此時佈置的是稱為Data Model的Class;再往上一層,系統會有在執行時放置相關資訊的地方,通常也會定義一個程作Context的Package作為系統資訊存放用。如何取得Data Model或是Context就要視系統如何設計,不過在系統限制只能有一份Data Model或Context的時候,可以使用Singleton Design Pattern來放置。

2008年3月25日 星期二

M19 讓Interface裡的一對一對應無所不在

在與大陸程式設計人員合作的時候,發現他們對於Interface有著相當好的概念。有幾次想要偷懶,認為同一個Package裡的Class直接使用就好,還有在Method上想直接用一個getProperty()來省略一堆的getter方法時,他們都會提出要一個一個全部應定義好的建議。

他們的觀念是正確的。每一個物件的存在,都應該至少有一個對應的Interface來描述該物件在某個方面所應該要具有的功能;而每一個獨立的功能,也同樣應該要有一個專屬該功能對應的方法。我已經有很多次將多對一的對應,切割為一對一的經驗了,每次把一堆粘黏在一起的想法重新釐清都很辛苦,所以現在都試著努力讓自己的設計與系統的功能形成一對一的對應。

一對一的對應相對的影響,會造成Java File的數量增加不少。在前面的那個專案裡,我們一共宣告了113個Interface、40個Abstract Class與81個Class,這數目與一般直接宣告Interface與Class的作法(通常會有是一個對一個,81個Interface與81個Class)多了幾十個Interface與Abstract Class。

或許有人認為檔案數的增加相對地更難理解系統,但是採用直覺式寫法的原有系統即使說明一週還是難以上手,而我現在有把握只有說明半天就能讓新進維護人員有超過前者的系統概念。能夠快速地讓新進人員上手,都是因為所有物件有依照規律放在該在的位置、同時處理它該處理的功能。

註:一對一的對應,指一個Interface只為了一個目的而存在,一個Method只為了一個動作而存在。雖然定義上較為繁瑣,但是易於切割與使用;有時候也會將幾個固定動作的Method以特定邏輯包裝為組合型的Method,不過這應該特別註明包裝了哪些動作Method。

2008年3月24日 星期一

M18 系統軟體架構的層次(4)──各類Method的佈置與可見程度

在一個Interface實作的Class裡所使用到的Mehod,在類型上主要有入口、流程與動作三類;不過在使用層面上會依據以下各個情況來定義Method的修飾字:(這是我自己使用的方式,適用在Abstact Class)

Interface的實作方法就依宣告使用public。倘使這個Interface不希望給其他Package使用,在宣Interface前不要使用public,但是Method還是使用public宣告。記得在其他物件使用時務必呼叫在Interface裡有宣告的方法以維持入口的統一。

在實作時額外定義的方法,基於讓所有繼承它的Class具有該特性,都應該宣告為protected。如果很肯定這個Method只是單純在Abstract Class自己的層級使用,可以直接宣告為private;如果該Method有可能會被Class覆寫但是現在沒有,應該宣告為protected final,已被任一個繼承Class覆寫過的Method則僅使用protected。(final的定義同樣適用在Interface Method的實作)另外,在Abstract Class還無法決定要如何實作的Method,應該都加上abstract的修飾讓它能夠立即被看出將會在繼承的下一層實作。

雖然在Eclipse裡有Ctrl+T的功能可檢查指定的方法被哪些Class所覆寫,但是那必須得一個一個方法去檢查。方法的修飾宣告與其亂定一通,倒不如應用一點技巧來讓所有人從Outline就能看出所有方法的大致結構,這不是相對地方便很多嗎?

2008年3月23日 星期日

M17 令人感到麻煩的設計(13)──Method的起始與結束保持唯一

Interface Method的進入點對於實作的Class是固定的,執行的流程都從這裡開始。在流程中如果遇到要提早離開方法的狀況,相信很多人都會直接return或是throw new Exception()。

執行效能是很常被要求測量的,使用者會希望知道每個Use Case Method或是Activity Method執行需要多少的時間?直覺的想法當然是在進入方法時記錄當時時間,結束時再取得時間減去開始的時間就是答案。可是,哪裡才是方法的結束?看起來到處都有結束點,而且出去後就是分散到各個使用方法了。

基於“設計時預留抽取相同作用的地方”的想法,在方法外包裝一個入口方法來控制執行進出就可以完美地解決問題,無論流程在任何時間點return都會先回到入口。再往上一層想,整個系統進出入口方法裡通常會插入固定的事情,可不要直接把要做的事放到入口方法裡,還是依抽取的原則準備一個Class來放置進入入口要做的Method與離開入口要做的Method,在入口方法的前後則固定呼叫這裡的Method。

2008年3月22日 星期六

M16 流程與動作Method分離的好處

在把功能寫成一大塊的時候,在講解與交接時其實都不是那麼容易,再加上Method切割的標準不一,更使得寫出來的程式難以交給其他人處理。根據功能需求把Method切割為分解動作與串連流程除了寫的人多做一點之外,剩下的幾乎全是好處。

在開發方面,厲害的人可以專心針對某個動作找出最佳化的作法,只需要確認功能上的要求都有做到而不需要顧及流程的邏輯控制。另一方面讓能力普通的人協助流程的實現,只需要判斷程式流程是否依照傳回值走向正確的地方,而不致於碰上技術上的困難。適當的分工會讓每個人的能力得到更有效率的發揮。

在外包方面,如果動作牽扯上設備技術的機密(像是特別連接的週邊,或是特別的作法),我們可以把動作Method留在公司內部,把流程外包出去;反過來如果在專業領域的流程經驗值才是公司的資產,那麼就留住流程Method,把動作外包給別的公司做。

在維護方面,列示出功能需求的入口後,很容易可以找到流程的實作,從流程上先了解實現功能的概念,然後再依順序進入一步步的分解動作研究。條理分明地逐步瞭解,肯定會比一次把全部的東西的一大包丟過來要好,更何況還會在每一步驟上記錄註解。

2008年3月21日 星期五

M15 入口、流程與動作的沾粘現象

至今為止我覺得程式設計最需要遵守的原則,是把相同目的的程式碼抽取到共同的地方,而且應該只有一份。短短的一句,但是如何切割才是理想的共用部分?如何佈置才讓所有人員都很快知道應該共用的東西在哪裡?

流程與動作是應該分離的部分,程式之所以難改大多是因為數個動作被寫成一個方法,後來卻有修改那個方法的需要(尤其是在那數個動作間要插入其他動作時)。這個時候若沒有正確的追溯,就無法清楚在其他地方如何使用這個方法,無法兼顧所有使用而只看著眼前的需要去修改,就很容易造成其他使用同樣方法的功能問題。許許多多side effect(改好這個卻冒出別的)與defect repoen(已經改好的問題又再度出現)等讓使用者無法忍受的嚴重現象就會跟著發生。

對於Method功能需求的實作完全由流程與動作囊括,但是在什麼地方使用是由入口來決定;以這個現象來看,入口與流程應該是不同角度的思考。想像在使用者按組合鍵驅動某功能的場合,如果這個功能的流程寫在組合鍵的listener裡頭,如果使用者想要在按某按鈕時也要驅動該功能的話,勢必得要切開程式碼作些調整或是copy-paste了。

有個系統把F1到F12的功能鍵寫為獨立的listener,每個方法內都直接實作了特定的系統功能。後來同樣的系統在另一個客戶那邊使用,可是有些按鍵的對應功能需要改變,結果在這種情況下只能另外複製一個Class並且搬移“要變更對應的所有程式碼“,再設定Class Name來整個替換使用。同樣的入口、同樣的系統功能,結果產出了兩個幾乎相同的Class。雖然結果符合所有客戶的需要,但是這樣的作法適當嗎?

註:方法的入口有兩種考量。一種是只提供一種作法,讓進入的變化在專案那裡處理;另一種則把變化放置在傳入的變數中,進入方法後由內部加上一層入口來控制要進行哪個流程。一般狀況下以第一種最多,但是我現在會選擇第二種。

2008年3月20日 星期四

M14 替代的作法(7)──Interface Method的入口、流程與動作

Interface Method的功能需求就像是一個待達成的目標;在思維上先有的應是動作、然後決定動作的所屬權責、最後再以流程串接起來,以這種方式完成一個指定的功能。

在理想的作法上,當然能夠決定出動作Method的名稱與所屬的Interface或Class的話,就可以像L14那樣在流程方法裡註記所有的處理順序,之後再找程式員依註解說明填入適當的Interface Method。這個部分曾經有一個想法:如果有UML工具支援使用API來製作Activity Diagram的話,我一定會寫個程式找出系統內所有有流程註解的方法,然後每一個Method的註解流程都自動產生出一張Activity Diagram;不過這短期內似乎不可能。

大多時候在流程裡我們會知道要做什麼,但是沒辦法確認由哪裡去達成指定動作。這個時候我們只能先收集這個Package所有想做的動作再加以分析歸類,再定義內部的Method或是另外的Package Interface Method;等決定好所有動作負責的方法後再用上一段的註解方式來說明。這時就很類似K12的作法。

至於為什麼要有入口,接下來會說明原因。

2008年3月19日 星期三

M13 設計的製程(3)──Interface Method的入口、流程與動作

分析與設計的界線大多以Activity Interface作為界限,如何去使用Activity Interface Method是依照客戶相關業務的流程所定義,屬於需求與分析的範圍,而Activity Interface Method所描述的動作要如何被實現則是屬於系統的設計。對設計人員來說,Activity Interface Method是設計的起點,Method內所做的事都要依照定義的規格來設計。

每個Interface Method都會歸分為入口、流程與動作,每一類都應被分割為獨立的方法以便用不同的方式來組合使用。入口是實作Interface Method的Class Method;流程的實作會保持在相同的Package裡(可能會另外獨立出Interface);動作方法要視它的範圍權責,看要與流程方法放在一起、獨立出Interface或者使用其他Package Interface(通常是Component Interface)提供的方法。

每一個動作方法其實是一個更小範圍的設計需求,尤其是使用Pakcage Interface Method時尤為明顯,此時同樣再切分為入口、流程與動作來設計。在靜態方面,所有的Method都應該被列出在Class裡;在動態方面,每一個流程方法都應該使用Sequence Diagram來描述動作的順序(能用流程圖當然更好),至少要用L14的註解方式註明處理流程。

Method的設計其實是一種遞迴的處理,每個動作方法都被切為三個類型,其中的動作類再細分為更小的三個類型,直到那個動作適合在那個Class裡完全做到為止。記得把Sequence Diagram的目的侷限在描述Package內部的處理流程與外部Interface Method的互動,就不會得到冗長而難用的描述文件。

2008年3月18日 星期二

M12 做人的方法(9)──不要只看自己需要什麼

人們在旅遊前都會考慮到出去後可能會臨時感到飢餓或口渴,就將需要零食與飲料放到背包裡帶去。解決可能的飢渴狀況是需求,攜帶食物與飲水是我們解決這個需求的作法,但是解決需求之後呢?有很多盛裝的容器就在解決的同時被隨意棄置,被期待由大自然去釋放,可是結果並不是這樣。

在很多時候人們會為了自己的需要而準備許多東西,我們可以列出為了什麼需求準備了些什麼,卻很少人注意使用之後那些資源是如何處理的,也更不用談使用後的資源會造成什麼影響。資源如何到自己手上、發揮了什麼用途、之後怎麼善後,這是它的完整生命週期。

人想要隨心所欲當然可以,想要什麼就弄到手,不要的時候就隨意丟棄,從自己的眼光來看當然沒什麼問題,因為想要的東西都拿到了。不過從旁人的眼光看時比較不會去看那人怎麼使用,而是他用什麼方式拿到東西,又是用什麼方式讓東西離開他的;也就是那人處理身旁資源的方式。

人養寵物的目的只是暫時的娛樂,希望寂寞的時候有牠陪;在太忙或不容許飼養時就會讓牠離去,很多人會轉送旁人,但是也有不少人就任意亂丟。寵物認定主人是牠一生最重要的人,一天的等待只為了主人回來後短暫的陪伴,即使僅是如此都能讓牠有很大的快樂;如果身為寵物,你能容許主人這樣輕易的拋棄嗎?

自私的人只看得到自己手上的東西,唯有以同理心去注意每個相關物件生與滅時,才會懂得它存在的意義。

2008年3月17日 星期一

M11 替代的作法(6)──Class遵循生成與消滅的Interface

對於一個Interface的實作,第一步要建立實作Class的結構;先在Class Diagram放置好Interface、Abstract Class與Class的結構再一次匯出到workspace裡,會是比較理想的作法。在製作Class Diagram的時候可以同時在圖表裡檢視繼承與實作的正確性,直接寫程式的話製作的只是單線動作,無法快速得到立體的結構。

接著放置好每個Abstract Class與Class的constructor()、initialObject(), disposeObject、並依規範呼叫super();這部分的基本型態可以另存文字檔再複製回來,修改constructor()的名稱就可以快速完成。最後在Class裡依建立時間點來呼叫initialObject()。這些Method檢查無誤後再reverse回rose model微調Class Diagram的位置。

finalize()是Java物件在回收前最後執行的動作,但是它是無法取代disposeObject()的,因為它必須確定無其他物件使用後才會執行,沒法主動作出釋放資源的動作。設計時養成在哪個Class生成資源就在哪個Class裡負責釋放才是良好的習慣;如果資源是由Factory生成並傳出使用時,得另外確認它的消滅時間點,並在那時呼叫資源的disposeObject()。

2008年3月16日 星期日

M10 BasicObjectInterface的內部作法

每個Interface的設計如果都要作成這麼複雜,負責的人應該沒多久後就會想離職;在某些確定不需要切開層次的用途時,還是可以作成一個Interface對應一個Class的最單純作法。會用複雜的作法,是期望每一層都可以像洋蔥一樣,剝去上一層後還是擁有屬於自己的完整功能與結構。

MyObject o = new MyObject();
當我們在Class放置這一行時,其實包含了宣告與初始化兩個動作;當宣告多了之後會弄不清楚有哪些有被初始化過,又有哪些物件內記憶了其他物件而沒發現。因此宣告時我會先宣告為null,new的動作則統一放置在initialObject()裡,同時在dispostObject()裡宣告使用物件的disposeOjbect()並將變數再設為null以保證資源的釋放。

Constructor()、initialObject()要先呼叫super()再處理自己該作的事,disposeObject()則要先做完自己該釋放的資源再呼叫super()。最後在Class裡視物件生成時資源宣告的時機在constuctor()或是另外的initial()裡呼叫initialObject()就完成了這部分的設計。這樣佈置另一個重點是:讓 Class不能被繼承就可以保證一個物件生成時initialObject只會被執行一次。

這是一個簡單的實例:
public Class PreferenceClass {
 protected String filepath = null;

 public PreferenceClass(URL filepath) {
  super(filepath);
  initialObject(); //視時間點呼叫; Abstract Class沒有這動作, 因會經由呼叫super而通過
 }

 public void initialObject() {
  super.initialObject();
  obj = new MyObejct();
 }

 public void disposeObject() {
  obj.disposeObject();
  obj = null;
  super.disposeObject;
 }
}

2008年3月15日 星期六

M09 設計的製程(2)──Class遵循生成與消滅的Interface

在我的設計裡定義了一個BasicObjectInterface的介面,系統裡的每一個Interface都必須繼承到,這也表示每一個Class都必須要實作到裡面的規管。裡面只規定了管理物件生成與管理物件消滅的兩個Method:
public void initialObject();
public void disposeObject();

我的目的是在佈置實作Interface的Class同時約定這兩個方法的執行內容,以保證每個物件在生成與消滅的時間點都必定會呼叫到繼承關係的Method,而且限制只呼叫一次,同時依使用時的關係與責任來消滅由自己建立的其他物件。下面是基本型態的Class佈置與initialObject()、dispostObject()的結構圖。

從最偷懶的一個Interface對應一個Class實作擴張為最基本要五個Interface、四個Abstract Class與一個Class,我相信任誰都不想多花精力去鋪陳這種設計,不過只宣告Class的空殼其實很快,尤其在放置Method的時候設計的想法與堆在一個Class是差不了多少。但是在這裡多花的一點時間,卻有機會換得彈性強、易說明、少side effect的系統,這是我在多花時間佈置後所樂意見到的。

2008年3月14日 星期五

M08 規範所有物件的生成與消滅

在執行Method的時候如果需要用到Class裡的物件,大家都知道要預先準備妥當,不管是事先將物件放置好或是由Method傳入;在消滅Class的時候大家都相信J VM的Garbage Collection,所以大多都只是把放置物件的變數設為null後便將之遺忘,任由執行環境將之回收。

然而執行環境也有回收失敗的時候,例如下面的例子:
// 在方法內生成物件的時候. target與list是Class內宣告的Object與List.
target = new Object();
list.add(target);
// 在target不用的時候
target = null;

在消滅target的時候只對它設置為null,卻忘記它在list裡還存放著reference,以致於target物件根本無法被回收;這種現象一多,執行的記憶體遲早會被佔用光而造成系統異常結束。此現象稱為Memory Leakage,是系統問題最難排除的一種,因為根本很難判定哪裡佔用了應該釋放掉的物件。

“需要的時候已經產生好隨時可用,不要的時候有沒有消滅掉都無所謂”,如果對於物件的管理只看重需要的時候,那麼其實是沒有秩序可言的。萬物的生滅應該有其道理,所以在設計裡應該也要將每個物件的生滅視為一種應該詳細定義的規格。

2008年3月13日 星期四

M07 每一組實作Class該做到的事

Package是概念上的範圍,完全沒有事情要做,只需定義Package Interface即可;不過Class是真正負責做事的集合,為了維持本身的正常必須多設計一些東西。因此佈置好Package內的Interface與Class的結構後,在開始設計Package Interface所定義的方法之前,要先定義Class存在時所應盡到的責任。

Class的存在就像一個人存在於世界上,會有出生與死亡的時間點與當時要做的事;而且其本身必須妥善管理自己擁有的資源,不管資源是在Class內部產生或是從外部傳入,只要Class內有屬性存放就必須加以管理。如果存放資源的屬性是集合類型(List、Map之類),管理動作還要涵括集合的管理。

Class建立時的行為、定義負責管理資源的屬性、定義資源管理相關的方法(提供外部操作的放到Interface)、Class消滅時的行為,以上四種類型動作的設計是Class可以正常運作的保證。

2008年3月12日 星期三

M06 設計的製程(1)──Package的內部結構

Package的結構圖是必要的,因為可以讓人很快地明白內部的結構,並表達Package內的佈置與垂直繼承的關係。因為每次直接看著project裡一堆Package以及展開後的一群Class總是有無從下手之感。

每個Package都要各自繪製一張Class Diagram,重點在傳達Package內部的物件結構,要包含內部所有的Interface、Abstract Class與Class。可以不用畫上其他Package的物件內容,因為使用的關係可以在Sequence Diagram裡找到。

Package內的每一個Interface、Abstract Class與Class都應該把他們實作與繼承關係的物件拉進圖裡。有個理論說正常人最多只能記得單一Class六層繼承關係,不過即使繼承層次不多,一次要弄清楚好幾個Class全部的繼承與實作關係卻也不是簡單的事。以L07的Interface結構為基礎,每個Interface都佈置出準備實作的Abstract Class結構,並且在專案專用的層次放置Class。下圖是根據L07附圖產生的一個基本Package Class Diagram。

2008年3月11日 星期二

M05 系統軟體架構的層次(3)──Package內的Class結構

Package內會有一個Class來實作Package Interface,如果要把入口、流程與動作放置在同一個Class裡的話,就只有一個Class安排在Package裡,再加上我認為必要的Abstract Class就是最基本的Package內部結構。

在D06裡有張Component的內部結構圖,可以拿來作為定義Package結構的內容。只看與Package Interface有關的部分,從左邊開始正好依序對應為入口、流程與動作;這三者分開不同層次置放是為了與做事的本質對應,各自定義了Interface則是可以使用映射或是Design Pattern予以動態置換。 即使Method在Package內被拆解為入口、流程與動作三個部分,但是每個部分都是獨立存在,一樣是需要設計的。下面的例圖是UI Bean內的controller依照UI實際特性佈置的結構,最左邊是Interface,最右邊一排是Class,其餘中間的全部都是Abstract Class。在我所做的系統裡每一個Interface(不管是入口、流程、動作或Data Model)之下的Class全都是作出這樣的設計。


2008年3月10日 星期一

M04 令人感到麻煩的設計(12)──不要使用Inner Class

在我的設計裡對於Inner Class也是禁用的,因為使用之後形成的是一種無可抽換的綁死關係;即使Inner Class實作了Interface也是一樣,因為就算使用時可以替換,但在實質上它是屬於這個Class裡的一部分。

另一個原因在於使用Inner Class後,無可避免地會在裡面直接使用Outer Class裡的屬性或方法,這時的Innter Class根本就是Outer Class的子集合而已。把Method從一個Class裡切割到兩個以上的Class是很痛苦的, 因為是把緊密相連的東西硬拉到兩個不同的個體裡……類似之前我誤把View與Controller寫成一個Class而必須拆開的痛苦,一有差錯就會失去原有的功能。

想像一下分割連體嬰時所花費的精力就能明白事先將該分開的物件切割是正確的,因此我完全不使用Inner Class的設計,而直接把Class獨立出來再於Method傳入需要操作的Class。

2008年3月9日 星期日

M03 令人感到麻煩的設計(11)──用Abstract Class作對應

接著上一篇提出的問題,從圖1的基本型態開始要加入另一個有7個方法不同的Class,不過這次採用並聯的佈置,讓它直接實作Interface,這會形成圖2的佈置。兩個Class有13個完全相同的方法,可是完全沒有地方可以放置相同的程式碼,這使得設計時“抽取相同部分”的動作無貫徹。

很明顯地在這個例子裡放置Abstract Class會是個好解法。(圖3)這種方式讓我們能夠把相同的13個方法放置到Abstract Class,再個別在Class裡分開實作那7個不同的方法;基於覆寫後可能的失控,能夠設計為沒有覆寫方式的話我都會採用該種作法。


常聽到Class是特性的定義、Object是Class實體的說法,不過在我自己的設計裡會調適為:Abstract Class是特性的定義、Object是Class的實體、Class只是為建立Object的需要而存在。儘量把共用的動作方法建立在Abstract Class裡,Class裡要放的只有該Class特殊化的方法,這時再看前一篇就能夠現在發現完全沒有繼承Class的必要,因為所有需要的動作全都在Abstract Class中!

這裡都是我自己所用的設計,會這麼做是因為能夠避免我所遇過的一些問題,但還是可以保有設計的功能性。

2008年3月8日 星期六

M02 令人感到麻煩的設計(10)──讓Class無法被繼承

Package Interface無論如何都會有一個Class實作,如果現在的需求明確地知道只有一種實作的可能時,相信大多數的人都會直接佈置一個Class,因為這是最快完成需要的方式。(圖1)此時我們先假設Interface裡有20個Mehod。

過了一陣子有不同的專案需求,其中7個Mehod需要改寫,為了快速的reuse,很多人會選擇直接繼承原有的Class後再覆寫那7個需求變更的方法。(圖2)再後來,又有一個新的專案需要改寫,但這次是前次變更的7個Method有兩個要與最原始那個一樣,這時一樣是繼承前一個Class再把2個Method從最早的那個複製過來。(圖3)


達到功能需求的變更這是最快的作法沒錯,但是有沒有覺得哪裡怪怪的?PackageImpl與PackageImpl_2裡有兩個方法是完全相同的;PackageImpl裡擁有的20個方法與PackageImpl_1擁有的7個方法沒法立即得知哪些被後面的Class覆寫。Class越多時同樣的狀況到處都有,恐怕連自己都會搞不清楚當初的佈置是怎樣。

雖然OO語言裡Class是允許被繼承的,但是為了避免這種混淆發生,我打算將所有Class都宣告為final,讓特性都繼承自描述用的Abstract Class。

2008年3月7日 星期五

M01 Class是類似動作Method加上使用Data的集合

Method是為了達成功能存在的,定義好系統的所有方法後如果沒有妥善分類,就會形成內部錯踪複雜或是到處都有類似的程式碼;Class是擁有Method實體並將之群組化的單位。

在定義Interface已經決定好有哪些類似目標的Method會被找出來並放在正確的位置,接著在每一個Interface之後都必然會有一個實作Class對應。Class上會有繼承與實作的問題,會依特性決定Class的“族譜”,再決定Method要在哪一層Class來實作,或者實作後要在哪一層因需要而改變。

Method在執行時通常會用到Data,執行時所用的Data在執行後不需傳回但需要保存留待後續呼叫的Method使用的話,就要定義為Class Attribute留存在Class的範圍內。在困擾的Method定位之後還有Data的設計,使用到其他層次的物件時的生成與管理……等等,Class這一層的設計是最煩雜的部分。

相對來說,Package並沒有任何實作而只是範圍上的定義,定義Package Interface會由哪些Class共同達成。