2010年6月3日 星期四

X24 程式寫作員的未來(2)──框架的使用

經過同領域的數次專案洗禮,很快就會明白只管達成功能的作法會造成許多不一致的災難,包括想改一個問題得調上百個程式或是改問題就像倒骨牌一樣改好了這邊卻壞了另一邊,這時會開始使用框架的設計(當然,也有加快開發速度的考量)。無論是使用其他公司提供的或者是自己設計的框架,都會在系統架構上定義一些規範作為開發所遵循的標準。



對建立執行架構的人來說,使用框架的設計可以將一些通用的執行必須功能包裝在裡頭;讓開發人員從必須從頭到尾瞭解執行原理,簡化為只要知道如何設定且實作交易之所需。在沒有使用框架的設計,開發人員必須自己弄懂底層後包裝為可用元件並測試之;使用框架可以簡化開發的工作,技術比較好的架構人員必須去瞭解框架的原理與細節,開發人員就根據架構人員所定義好的系統架構進行交易開發。如此一來開發人員所需的技術門檻將會大幅降低。



從上圖應可概略看出開發人員減少需要技術的困難部分,但相對地需要知道要如何去運用底層框架──不管是框架本來就提供的功能或是架構人員因應專案所作的設定。因此,架構人員除了去瞭解其他技術並運用之外,還應該提供足以讓開發人員活用系統架構的教學與說明,才能發揮團隊的戰力。曾看過有人將流行的技術組裝出一個執行平台後,僅教開發人員說怎麼做可以正常執行而不說明其他可能的變化,到最後變化出現時不是沒人可處理就是自己還得再回去調整。

只留下一堆設定檔與程式的話,除了少數條件好的開發人員外幾乎無力改變任何東西。有些架構人員會說當初他們也是自己慢慢摸索出來的,但在我看來其話中的含意是說“條件差的就不要浪費時間再看,條件好的就跟著在他們之前摔過的地方同樣再摔一次吧”。以這樣的心態建立系統架構的話,沒有辦法快速拉抬開發人員的經驗與實力,自己也會感覺他們怎麼這麼不中用。

註:通用的Class應依照先訂Interface再設計開發的方式,這裡不再詳述。目前偏重架構上的描述。軟體架構要設計到什麼程度?是某本書的第八章,8.2提到高來高去式架構設計的症狀還真是很貼近我的體驗。

2010年6月1日 星期二

X23 程式寫作員的未來(1)──起步的時候

某天下午,A主管忽然感嘆說當SA、PM幾年下來已經開始不知道未來要怎麼走,真有點想要退休(註:他未滿35歲),同時也感嘆部門裡的PG不管是過得爽的或是被操累的都有人嚷著要離職,聽他這麼一說,還真有些很多職務都做不久的感觸。同樣是從programmer一路走過來的,如今試著思考從頭再來一遍的話自己會做些什麼事。

以一個常見的系統架構作為來推演:使用者希望在client的畫面上輸入資料後,經由server送到host執行,結果通過server回到slient後顯示在畫面並從印表機列印一張報表。下面是大概的硬體架構,暫且只將重心放在client-server-host間的執行,不去考慮存取database的機制。



瞭解的技術還不多的時候,面對這樣的需求心裡想的只會是如何達成。最直覺的設計就是在每一個單獨的硬體上各自執行一支程式來處理,硬體之間的資訊溝通再另外找一個好用的免費函式庫(像是Apache HttpConnection)來連接。這樣的做法肯定可以很快完成系統的需求,但是應該明白會造成類似C06提到的邏輯綑綁問題;而且沒有明確的規範時,有多少人開發就會有多少種風格的程式碼存在著。

還是初學者時如果想加快開發速度,就會避免學習很多現在不懂的技術,最有可能的寫法就是Client程式、Server程式都各用一個Class寫掉,再將一些常用的程式抽取成為API重覆使用。

2010年5月21日 星期五

X22 關於○○,你瞭解了多少?

前幾天在聯合新聞網上看到一篇報導:爭拔河起源 女研究生舌戰日韓學者

「拔河」起源於中國、日本,還是韓國?在今年四月的「拔河節」上,日、韓兩國學者為此爭得面紅耳赤,他們紛紛引經據典稱「拔河」是自己國家的非物質文化遺產。學歷史的熊夢霞知道「拔河」運動是最早源於楚國的「牽鉤」,當時是配合水戰的一種軍事技能,在從隋唐、五代、元、明、清時期都有很好的一直傳承下來。所以當時她大膽地站出來將所掌握「拔河源自中國」的證據在現場陳述。

「你這麼年輕,研究過多少國家的拔河文化史?」一個日本專家向她反問。很自然地,研究過許多相關範圍的專家總會反問這麼一句話,情境一如我提出自己想法時,無論是鑽研理論的主管或是實務豐富的同事會講的話語。她雖然沒有對中國的拔河文化進行過系統考證,對韓國、日本拔河文化也不很清楚。「但是我至少要講出我自己所知道的真相情況,讓在座的人知道,拔河很有一種可能就是起源自於中國!」

在會場上她的這個「插曲」,讓日韓學者們達成共識要重新繪製拔河文化發展的歷史圖譜。不過在現實上,縱使看到一些“似乎”可以改進的地方,但若不讓自己的程度提升到與旁人相近的水平,無論說什麼都沒人會理睬的。這陣子被指派撰寫技術研發計畫書的工作,在公司的佈局之下不斷地涉獵所謂的“標準”同時參加外面的課程;以往落後的知識還是需要努力去追趕回來的。

未來的某一天,總是要在別人質疑自己對○○懂得多少時,能夠輕鬆地回答說:“我看過也實作過,很清楚○○的優點與潛在問題才這麼說的。”才有辦法讓別人認真聆聽自己的想法。

註:關於韓國努力地申請世界遺產之事,這裡有篇報導。擔心「被韓國」 就怕丟正宗地位 對於這個現象,以下的故事或許有人能看出終極的解決方法(強調:我什麼都看不出來。無論看出什麼,都是你自己的精闢見解,與我無關!)

有一個富翁得了無藥可救的重病,唯一的獨生子此刻卻遠在異鄉。當他知道死期將近時怕僕人侵佔財產,便立下了一份令人不解的遺囑︰”我的兒子僅可從財產中先選擇一項,其餘的皆送給我的僕人。”富翁死後,僕人便歡喜地拿著遺囑去找主人的兒子。富翁的兒子看完了遺囑想了一想,就對僕人說︰”我決定選擇那一項,就是你。”這聰明的兒子拿到父親全部的財產。

2010年5月10日 星期一

X21 相信組裝式開發的原因(3)──語音傳真系統

每一條線路都配置10個迴圈計數器與100個系統變數(VAR00-VAR99)保存必要的資訊,比較典型的語音系統指令有下面幾個:

●播放語句:播放指定的語音檔,可指定重播次數與每次重播間的等待秒數;同時設定選擇項與選擇保留變數。語音播放次數結束或是使用者有按鍵的話,都能夠跳往不同的流程區段。


●輸入資料:播放語句的同時接受用戶以DTMF輸入較長的資料並存放到指定變數,輸入後可用結束字元並進行長度的檢查,再根據結果進行不同的流程。


●電話撥出:撥出指定的電話號碼並依撥出結果執行不同的流程。搭配執行記錄可以做出撥出數量與結果的報表;撥通後搭配播放語句的選擇功能就可以做出隨機撥號的問卷統計。


●讀寫DBF檔、讀寫binary file、用RS-232C傳送資料也獨立為執行指令,取得使用者輸入的資料寫檔或是讀入資料後運用合成語句的方式將結果播報給使用者聽,也是很常出現的運用。

當年開發語音應用程式時,大部分的時間都是在畫流程圖與切割組裝用的語句,等到客戶確認流程圖後再選擇適合的指令組裝出流程。除了複雜的資料處理與極少數未規畫為指令集的行為還是要另外寫程式之外,九成以上的時間都在組裝指令與進行測試。這樣的開發模式除了比撰寫程度快速許多之外,對於不懂語音卡、資料庫、RS-232C底層API的人,只要經過少許訓練後就能上手開發。

自己曾經藉由這類的開發工具而步入職場,在理解設計原理後另外重新製作一套,同時享受只運用流程的組裝就建立系統的過程。在1990年代時就能如此,這正是我一直相信程式能夠設計為組裝式開發的原因。

註:當年運用這套語音工具開發系統的單位有
●XX政府便民語音傳真系統──除了提供民眾以語音或傳真取得資訊之外,同時提供維護者撥電話進入系統製作語音與傳真內容。
●XX證券分析師盤勢分析──民眾與分析師以用戶代號與密碼驗證身份後,根據不同身份進行聽取盤勢分析(另有扣點功能)或是錄製功能。
●XX公司庫存記錄系統──不同分公司的業務在銷售某項產品後,在外面撥電話進入分公司按入產品代號與數量,資料會藉由數據機傳回總公司即時計算存量後顯示在工作人員的電腦上。
●XX單位費用催繳系統──從文字檔讀入欠費資料在設定的時段依序打電話播放催繳費用的語句,並在最後讓使用者按鍵回饋聽取的結果。每天與每月都要產出結果報表作為記錄,並用人工催繳撥號失敗的用戶。

2010年5月7日 星期五

X20 相信組裝式開發的原因(2)──第二份工作

新的公司標到某個縣市政府便民資訊系統建置的合約,其中包含一套公司內沒人會做的語音傳真系統,因此雇用我在五個月內無中生有地完成那一套語音傳真系統。在當年寫作風格還只是包裝常用API來呼叫的情況下,我當然可以只製作一個僅符合需求規格的系統;不過受到第一個工作使用系統的影響,我選擇了更高一層的挑戰──在DOS下實作相同規模的語音系統。

整個語音系統分為editor與runtime兩大部分,開發人員在editor選好指令製作出流程後,就能在runtime依狀況或使用者選擇進行對應的行為或播放語音內容。延伸來自之前工作的經驗,我應用在這次的系統設計裡:

●語音系統指令的定義
根據之前開發語音系統的經驗,除了補強原有指令的設定參數之外,同時增訂幾個存取資料(讀寫資料庫與文字檔)的指令,另外也加強變數處理功能,儘可能地將常用的行為涵括到指令集內。當然,還是保留呼叫外部客製化程式的[自定函數]功能以應付更複雜的變化。

這是完整的指令表:


●快速的畫面開發與變化
利用控制顯示文字與顏色的兩個bytes[80][25]陣列,將要顯示的內容定義在外部檔,於需要時快速計算貼入陣列裡顯示;建立多層視窗重疊顯示與逐層隱藏,也加快了編輯畫面的開發。像等待來話的設定畫面就先定義在外部檔,在顯示時將兩個中引號之間轉變為輸入欄位,並依設定字元作輸入的檢查。


V0000=╔══════════════════════╗;
V0001=║ □行號:[####] 指令函數:[############] □ ║;
V0002=╠══════════════════════╣;
V0003=║用戶撥入電話執行行號 [DDDD] ║;
V0004=║在等待來話時執行行號 [FFFF] ║;
V0005=║用戶撥入後偵測到對方掛電話執行行號 [DDDD] ║;
V0006=║要清除本線的系統變數範圍 [DD][DD] ║;
V0007=║ <確定><取消> ║;
V0008=╚══════════════════════╝;


●用迴圈控制16條線路的執行
在runtime的實作上就需要模擬多執行緒的技巧。當時並不像現在只要new Thread()就可以獨立撰寫一條線路,必須要依序對每一條線路切割工作狀態,每次迴圈經過時都執行一點點工作就要跳到下一條線路;複雜或時間較長的指令還得再細分為更小的多個步驟來執行,以免因為耽擱過久而造成語音停播的窘況。(聲音取樣為8K/Sec,語音卡緩衝區是4K,也就是半秒內必須保證迴圈繞完一圈──CPU是486DX)

為了保證執行狀態夠快且不會錯,當時做了很多切割方法的測試才終於找到了最佳的結論。自己認為這樣的經驗值磨鍊了許多開發的思維。

●試作報表產生器
利用寫資料庫指令,在流程經過的同時寫下該線路的相關資訊,累積下來就有語音系統的使用資訊。根據記錄的規則填寫對應的參數,就能夠獲得許多類型的常用報表。像是作為服務系統時的來電數統計、選擇項統計、線路用量統計……等,作為外撥系統時的撥號數統計、接通數統計、問題回答統計……等,都能用定義的方式客製化出要顯示的內容。

五個月後,終於如期完成了這個系統。

2010年5月3日 星期一

X19 相信組裝式開發的原因(1)──第一份工作

每次與同事間討論軟體工廠的可性行時,其他人到最後都認為程式設計的複雜度太高而無法用組裝方式來開發。由於最近幾年同事們都在專案上打滾,而我一直從事工具開發與維護其他人的程式,有時難免被懷疑是否理論的書看太多而開始相信書上說的那一套理想?

照理說,書看得比別人少且專案實務也比別人少的我,不應該執著於要花很多功夫的軟體工廠才是,但是事實上卻偏偏相反。在最近的一次討論裡,我才驀然發現第一份工作對自己的影響是這麼地深遠;就是它讓我到現在仍然一直相信軟體是可以組裝的。

時間回溯到1990年代的初期,作業系統還只是DOS的年代,市場上主流的開發語言是C與Pascal。大學混四年畢業的我在退伍後,等於什麼都不會的狀況開始求職,由於不熟主流開發語言而沒法找到較好的工作,最後進入了一間開發語音系統的公司(參閱S24)。很多同期進去的人大多因為碰不到主流技術而沒待下來,卻沒想到公司裡另有兩位資深的人一直使用C語言開發許多特別的功能。

從事第一份工作兩年多一些,現在回憶起來帶給我一些特別的經驗:

第一年的語音系統
●特點在於區分為編輯器與執行器。在編輯器上有十多個指令,運用basic語言行號的觀念能夠很快地將流程圖的內容輸入為許多指令的集合。
●語音的斷句、組合與聲調處理。
●主機電文的處理經驗,包含上傳電文的組合與下傳電文的拆解。

第二年使用C語言
●以迴圈方式模擬同時處理多個執行緒運作的技巧。
●操作與主機連線的moden而熟悉com port API。
●以一個bytes[80][25]陣列控制顯示文字,另一個bytes[80][25]陣列控制顯示顏色的技巧。
●利用關鍵字的設定,快速地製作或調整多個輸入欄位的設計方法。

帶著累積下來的一些經驗,當時的我預備踏向另一段旅程。

2010年4月29日 星期四

X18 隨時檢查自己做的動作

某天開會,幾位同事各在白板上寫了一些字。有位同事忽然用羨慕的口吻對我說:“你的字寫的好整齊,而且每個字的大小都差不多;不像我寫的字,不僅大小不一而且還會越寫越往下。”我微笑著說,寫成這樣並沒有什麼,只要在幾個下筆的時機做一些檢查就可以辦到。

●寫第一個字時,決定所有字型的大小
●寫第二個(含)以後的字下第一筆時,要決定與前字的間隔與水平位置
●寫字的每個筆劃時,都要寫出與字型大小相符的筆劃
●寫第二行(含)以後的第一個字時,要決定與上一行的垂直間隔
●總之,寫每一筆前的瞬間都先做一次檢查並調整

同事心存疑惑地在白板上依照這些原則試寫十多個字後停下來看了看,滿意地說原來自己的字也可以寫得這麼整齊。我回答道:”是啊,每個人下筆前隨時檢核自己要做的動作,就可以擁有整齊的字。 ”

前幾天有人要我依影印稿幫忙在電腦上畫出一張建築物的平面簡圖,當時所有的電腦裡都沒有任何的繪圖軟體,我回答道:“就讓我這個小畫家之王(因為我不會別的)用小畫家來畫吧!”。下方的平面圖是半個小時之後的產出:


用小畫家畫略為嚴謹的圖時,我遵循著兩個原則:
●先計算比例尺,計算出一公分的長度等於圖上的多少點
●該直的絕對是直的,該圓的也絕對是圓的;橢圓則先量出長與寬的數值

畫每一個線條時,先決定起點、再決定方向、最後決定長度。隨時對每一條線做以上的檢查,自然能保證該線條的正確性;每一條線都在正確的位置上作正確的呈現時,整張圖表現出來的自然就會是正確的結果。

註:十多年前工作上急需印出一張軸承的規格(必須用autocad畫),當時只有一張手繪的底稿,而且會用autocad的人全都不在,我只好使用小畫家彷照autocad的表示法慢慢地畫出該軸承的三視圖。最後,對方接受了那張圖。

2010年4月20日 星期二

Y20 錦囊之計 vs 設計程度

話說古代有位軍師總會在出征前拿個錦囊給統率的將軍,要他在不知如何是好的狀況下照內容做,就能逢凶化吉或是出奇制勝。

在此處暫且將時空切割為二:甲時空的軍師通常在出征前一刻根據最新軍情花一點點時間寫錦囊,將領大多時候依錦囊行事倒也正確,但是總有一兩成的機率無法應付現況,需要派人再回去稟報軍情後重新跟軍師拿一個錦囊;乙時空的軍師都會在出征前一天思考整個晚上後,在錦囊中寫下各種可能發生的情況並逐一列舉不同的應對方法,據說不符機宜的機率降低到半成以下。

在這裡說的將軍是在客戶戰場中衝鋒的專案人員,軍師則是指公司裡製作共用元件(錦囊)的支援人員。如果你是軍師,會想用多少精力寫出什麼樣的錦囊?如果你是將軍,又會希望拿到手的是什麼樣的錦囊呢?

身邊絕大多數的人都屬於將軍型,能夠快速使用資源做出客戶想要的結果,遇到不符需求的變化時也能快速地應變;只有很少數的人在面對需求時會如同軍師般思考各種可能情況再作整理設計。將軍型看軍師型會感覺在設計同樣的功能時花較多的時間且產出額外用不到的方法,軍師型看將軍型的產出卻又感覺思慮不夠周延又難以捕捉改變後的影響。更頭痛的是,有些將軍在拿到後方送來的木牛流馬時會嫌錦囊寫的文件看不懂,而且擔心萬一故障時前線不能修理將影響軍情,乾脆重新打造一套很類似卻只有自己懂的東西才會有安全感。

將軍型與軍師型的角色,在雙方的定位清楚並且相互信任的前提下,是可以發揮很大功效的。公司製作共用元件時依照標準開發流程控制品質,提供面對不同狀況下的對應參數;專案人員信任共用元件與正確性與方便性,快速地依客戶需要組裝出該有的功能。這樣不就是很有效率的開發團隊嗎?

註:某個功能在遇到例外狀況,而且從模組內修正那個例外非常麻煩時,將軍型通常會在模組外部添加某些東西,並在模組內部最前端加上判斷添加物是否存在的條件另外處理。軍師型則會堅持分析模組內的處理流程,直到找出可以明確判斷出例外狀況且不影響其他狀況的正解為止;如果找不到這樣的解答,會認為那個模組是個失敗之作。

2010年4月16日 星期五

Y19 First Practice(4)──設計階段

最近很努力地直接開發工具程式,眼看著功能慢慢堆砌出來,也逐步給專案人員協助作測試與試用。

兩週後的某一天,主管問這次開發的工具程式能否在未來重用其中的部分?我愣了一下,由於撰寫中逐漸感覺系統有種“被綁死”的感覺,便回答說這個工具已經因為趕時間而寫壞,不可能切割出可重用的模組。以下就是我給主管的原因:

●沒有全面定義與使用介面
直接寫程式時,對物件常缺乏時間定義應有的行為。在需要對物件新增方法時,會感覺介面得多花時間去設定與調整,再加上認定系統就只會使用這種實作的物件,就順手地將方式寫在Class上。我們都很明白沒有使用Interface會讓物件間的耦合度很高……。



取得物件的途徑不一致
上圖是工具程式的顯示畫面,最外面的MainFrame簡單地分為左右兩個Panel再各自盛裝UI元件。最外層的MainFrame設計為singleton,在設計時希望一邊Panel的UI物件可以存取到另一個Panel上的任一個UI物件來操作。例如左邊的Clear Data按鈕要能操作到右邊的Table以便做到清除的動作。但是程式寫到一半會發現並不是每個Button“都一致定義”擁有Panel物件,再加上趕時間而“懶得”調整,因此取法變為兩種(甚至更多):

1) getLeftPanel().getMainFrame().getRightPanel().getTable();
2) MainFrame.getInstance().getRightPanel().getTable();


●物件操作的方式未統一
原本定義了ModelService來負責變更Model的工作,但有時“難免忘記”應該經由ModelService來做事而直接呼叫Model的修改方法,形成跳層的使用。如此一來,層次間的關聯變得複雜,而且沒法保證要做的事都會經過ModelService。

●忽略行為的封裝
需求裡提到使用者按下Clear Data時要清除右邊的Table,所以開發時直接在它的listener內直接寫下:

getLeftPanel().getMainFrame().getRightPanel().getTable().clearData();

後來發現工具列上也有個按鈕具有Clear Data的相同功能,所以也直接在這邊的listener裡寫上這一行程式。後來使用者在測試時回饋說清空Table的同時,Delete Data應該setEnable(false),這時候就直接在兩個listener(最好是還記得有兩個地方)內多加一行而形成:

getLeftPanel().getMainFrame().getRightPanel().getTable().clearData();
getLeftPanel().getMainFrame().getRightPanel().getDeleteDataButton().setEnable(false);

在趕時間的前提下,當然沒空在Right Panel上定義clearTableData()同時封裝這兩個動作,因而繼續讓重覆的程式碼散落在各地。

基於以上的快速撰寫方式,雖然讓我的工具程式在被壓縮到不合理的時程後還能如期完成,卻同時造就出一個內部脈絡極為複雜、等於已經僵化的系統。在快速開發的同時無法“完全遵循”某些設計準則,就會付出類似的代價。