今晚原定的行程是改論文的第3.x節,但離開學校前,跟老師聽完最後2小時的OOAD Final Review後,有些想法趁還有印象趕快寫下來。整個OOAD Final Review從上禮拜開始,中間歷經過一次因豪大雨所以下午停課延期,到今天結束,一共25組將近9節課的時間,聽完他們的設計後有不少感想,其中一些已經當場回饋給學弟妹(學弟妹:明明就是電爆我們,哪是回饋),但整體來說,就是有一種感覺,他們都被框架(Framework)給綁架了,而且被綁的很深。

  其實,越是年輕世代的程式工程師越是幸福,不用考慮記憶體管理,而且平台多(除了傳統的Windows和Linux,最近學校都流行在iOS、Android、Windows Phone上開發程式),工具、框架、引擎甚至連開發環境(IDE)都比以前多而且方便。當然,這幸福是建築在龐大的學習時間上,一但有新的框架或是平台,就馬上要去學習跟使用,可能也是因為這樣,在時間有限的情況下,學習框架時很少去思考:框架該如何運用(Utiliize)?什麼!你在開玩笑吧!就照著網路上網友寫得或是官方的文件或例子,直接將程式碼複製貼上,然後修改,只要功能正確就是會用啦!ㄟ,我想運用(Utilize)和使用(Use)是兩碼子事,前者多帶了點創意與出乎原始用途的使用方式。

  事實上,不只學生是這樣,先前陪著老師去幫業界上課(主要是介紹軟體測試),也發現業界這現象很普遍,大概2年前,一個經濟部的案子,到B公司(ㄟ~別問我公司名稱)幫他們上課,討論到model的unit testing時,有點年紀的工程師一臉疑惑:哪來的model?詳細詢問後,原來他們早已被框架綁住到無法自拔的地步:就用『有點軟』的工具,把database的schema訂一下,然後就可以這樣那樣,原來他們寫程式已經不做model desing了,我並不是說這樣的工具不要用,但沒有任何的設計,如果某一天,有一個專案不需使用database做persistence,我在想這位工程師還會寫程式嗎?這裡透露一點小秘密,這家公司除了經濟部的案子和我們系上有合作,另外也曾簽了一個短期合約,幫他們維護一個從別家公司取得授權的軟體,據他們的說法,他們花了數月,都無法讓程式正常在手機上執行,當時實驗室派了二位剛進研究所的學弟妹,不到一個月就把問題解決了...

  這又讓我想起今年年初,跟老師去某單位擔任講課的助教,中午休息時間,某位已有1x年programming經驗的工程師問老師,他有一個已經放上Android market (當時還不叫Google Play)的程式,一開始沒想太多,所有的程式碼都寫在Activity裡,也沒想過要怎麼測試,都是丟到模擬器上用手操作來測試,最近要加新功能或是修改,都不太敢改,因為已經大到不知如何下手?也不知道怎麼做測試才有效率,我一聽苦笑,為什麼所有的程式都寫在Activity裡?但這不是我聽到的第一個例子,去年參加學妹的喜宴,同桌除了研究所的學弟妹外,還有學妹大學(大學和研究所不同校)的同學,據說在A公司(這公司真的是A開頭)擔任手機App開發工程師,他的同學問他:『你們有做分析和設計嗎?畫class diagram嗎?』沒想到他回答得更奇怪:『怎麼分析?需求都是一個個畫面,所以都是直接用IDE拉UI,然後直接把程式寫在Activity裡』,我心理就很納悶,照Google官方文件上的說法,Activity明明就是View啊!為什麼Business Logic都放在View裡了?這就像是當年懵懂無知學VB的時候,UI拉一拉,對著畫面上的按鈕點兩下,然後在VB產生的某副程式裡寫程式一樣,這...這像話嗎?難道他沒學過什麼是MVC嗎?

  上述都是被框架綁架的例子。今年OOAD有7組學生使用Android平台,1組iOS平台,2組Windows Phone平台,1組GAE平台及4組使用J2EE或ASP.Net MVC框架,不少學生說他們做完設計後,到真正實作時,全都變了樣,變了樣有兩種可能:第一種可能是一開始設計時沒經驗想太少了,於是設計出來的東西根本就無法實作;第二種可能是不知道怎麼把設計出來的東西和框架整合,所以放棄設計,全部照框架走。從紙本作業上學生所列的程式碼,上述二種都有,但後者的比例不低。例如某一組開發一個Android的App,當初設計時有一個Class負責取得社群網站的認證,後來這個Class不見了,原因是他發現有現成的API可以用,對!有API可以用,當然要用,不用全部自己打造輪子,但我問他,因為你使用現有的API,所以目前你的App只能跟該社群網站取得認證嗎?他說對,那如果要同時支援多個社群網站呢?他說可能要用if...else...判斷然後用不同的API吧!他可能忘記了書上提到Indirection pattern,也忘記了program to an interface, not an implementation。

  軟體設計真正在做的事是抽象化的動作,透過抽象化可以隔離外部系統和保護內部系統。先談保護內部系統,外部系統可能是一個Library、Framework或是Application runtime,當外部系統發生變動,內部系統就受影響,這是無法避免的,一旦少了抽象層,內部系統直接與外部系統掛在一起,如果內部系統只有在一個地方使用外部系統,那修改一個地方也就罷了,怕的是少了抽象層而且內部系統有好幾個地方都直接使用外部系統,要修改的地方可就不只一個地方了,光想像就覺得多費事,而且還不保證修改後所有地方都改對了,如果有抽象層,內部系統應該使用抽象層,因此只需對抽象層進行修改即可。接著談隔離外部系統,隔離外部系統有很多種目的,例如對內部系統進行測試時,可以用Dummy的外部系統(不知道為什麼突然想起EVA的Dummy駕駛艙了),由於有抽象層,對內部系統而言,它根本不知道他在跟假的外部系統溝通;又或者是測試時,不需要外部系統的加入,這可以帶來效率上或是安全性上的好處。

  就拿學生的專案為例,很多組都有使用database作為資料的儲存裝置,由於沒有做抽象層的設計,很多model裡的Class直接操作database,寫單元測試時,大部分的學生都是在測資料有沒新增/刪除/修改到database中,物件之間的關係、邏輯都沒有被測試,又或是使用Web services當成資料來源,但沒有抽象層的設計,於是測試時必須要有伺服器的存在,這實際上都是可以避免的,測試執行的時間也可以大幅縮短。這裡再舉一個例子,這是之前為了去外面教軟體測試所設計的例子,一個在Android上的ATM App,當初設計這個例子時,還沒有想到要移植到Android上,只想說先有一個Console mode的UI可以執行就好,所以先設計了圖1的下半部,然後替圖1的下半部寫了47個單元測試,執行這47個單元測試不需1秒,而且statement coverage和branch coverage都達到100%,是的,你沒看錯,真的二者都有到100%,如果要在Android模擬器上用操作UI方式完成47個單元測試,很抱歉,至少要1x分鐘,1x分鐘可以跑上百次單元測試了。

ATM Example
圖1 隔離Android外部系統與ATM內部系統

Unit Test Result
圖2 不需1秒即完成47個單元測試

  當初這個例子是一個Eclipse中單純的Java專案(圖3的ATMCore),開發完成並測試後才開始移植到Android上,所以另外開了一個ATMAndroid專案,透過Eclipse的Link source將ATMCore的程式連結到ATMAndroid專案中,所以當ATMCore有任何修改,ATMAndroid也會立即反應(incremental rebuild)。那該如何使用ATMCore這個外部系統?如果只是在Android的Activity中直接create一個ATMModel的instance並不是一個好方法,因為Android是透過不斷地換Activity來進行畫面變更,且每個Activity都是獨立的instance,若在Activity中create ATMModel的instance,那不同的activity instances都各自擁有自己的ATMModel instance,這容易產生不同步的現象,所以,後來是設計了一個ATMCoreService,橋接這二個系統,設計成Service,當Activity需要服務時,向Android runtime請求服務,才將activity instance和service instance做聯繫(binding),系統中只有一個service instance,因此也只有一個ATMModel instance。說真的,ATMCoreService只做一件事:把所有的請求delegate給ATMModel,那ATMCoreService需要測試嗎?根據too simple to break的原則(每個method都只有一行),這個class其實不需要測試,ATMModel已經是一個fully tested的class,ATMCoreService自然也是fully tested的service,剩下的是用Android testing framework幫這App寫的驗收測試(Acceptance tests),說實話,後續維護,我才懶得跑驗收測試,最少也要5分鐘,除非是GUI上的修改,否則都是跑不到1秒的單元測試。

Link Source Code
圖3 分別各自開發的兩個專案

ATMCoreServices
圖4 運用Android的Service介面橋接兩個系統

  這個例子是簡化過的例子,它沒考慮到persistence的問題,或是多重使用者登入的問題,但persistence是可以運用JPA、Hibernate或是Android上的其他Framework就可以完成的事情(ATMCore會再加入一些Interface用來取得物件,但ATMCore只知道這些Interface,並不知道真正使用的是哪個framework),多重使用者登入的問題可以修改ATMCoreService來處理,基本上ATMCore只負責ATM核心的商業邏輯(Business Logic),其他責任都不關他的事,這時ATMCore才是真正High cohesion的系統,透過ATMCoreService,ATMAndroid和ATMCore之間才會是Low coupling。OOAD所教的是如何將複雜的事物、邏輯給抽象化,降低事物之間的關係,即便最後使用的語言不是OO的語言,很多原則依然適用(ㄟ~但...請別在這門課交C寫的程式交差),至於使用什麼Framework並不重要,應該是反過來運用Framework來達成當初的抽象化設計,才不至於被使用的Framework所綁架。

  寫了好多...其中有不少是之前某篇未發表的文章中節錄出來的,看來那篇未發表的文章可以刪除了,雖然這部落格上討論設計的文章不多,但還是有一些,看來累積到一定的量,也來出書好了XD。

dbi1463 發表在 痞客邦 PIXNET 留言(0) 人氣()