2012年1月22日 星期日

MVC 架構心得

好一陣子沒有寫關於程式方面的文章,來說說昨天在公司內部 Demo 時講的主題︰「MVC 架構」,本來以為只是一個枯燥、沒幾個人會認真聽的內容,不過看到其它同事認真的聽我說對於這個架構的感想,發覺似乎 MVC 架構或是一些物件導向的概念,就好像武功裡的「腰馬合一」一樣,知道的人很多,但是真正懂得如何應用的人卻很少。

記得剛開始學習物件導向概念的時候,和很多人一樣認為因為程式中使用到類別和物件,因而稱之物件導向。所以從來也不認為非物件導向的語言,像是早期的 PHP 或是 C 語言(不是 C++)能夠寫出物件導向。也很單純得覺得,像是 Java 這種使用到物件,或是 Visual Basic 這類有一個一個物件的程式語言,怎麼寫都算得上的物件導向吧!

當然,事後證明這是錯的。要不然也不會有這麼一篇文章。

雖然不了解物件導向的概念,但是多少也在反複的閱讀資料下記起一些「寫作原則」,在一次使用 PHP 作出一個自以為物件導向的專案之後,深深讓我體會到對於物件導向理解的不足。許多資料都會說物件導向程式,會讓程式變得更有清楚明暸,也更容易維護。但是這麼一個「使用物件」的 PHP 專案,當幾個星期之後再度維護時,發覺得完全不能在短時間明白當初的想法,得完整看完程式才能找到要修改的地方,而幾天之後再次修改事,這樣的災難再一次發生…

原來物件導向並不是去記住那些設計原則,單純在專案中一個一個用出來就好,還得知道「為什麼程式要這麼寫」。

後來,在作一個專案的大改版前,我有點掌握了物件導向的一些精神。在重新設計資料的相關類別時,不斷思考那些設計原則的同時,也不斷參考一些其它人的程式寫法,來印證自己作法的優缺點,最後終於明白了 MVC 架構的真實用法,MVC 架構是一種思考問題解決方式的想法,而不是單純把要作的東西分成三種不同的類別,就很神奇的變得很有彈性,很容易維護。

沒有相關知識的人,拿了頂級的工具也難以超越拿著簡單工具的資深工匠。同樣的,使用某個有名的 MVC 框架,也不能讓程式就因此變得有彈性,反倒是容易被框架中的框框限制住。因此,學習程式的時候,還是首要掌握這個作法背後的原因或思考方式。

前言講得夠多,來進入正題。因為不想畫圖,就拿平常就會使用到的文書編輯軟體作為例子。在腦中出現一個程式,有著包含大部分功能的功能表,在程式的頂端,而放著常用功能按鈕的工具列排在它的下頭,而再往下則是用來編輯檔案的內容的文字編輯區域,就叫它「編輯區」好了。現在工作來了,假設今天要替工具列中,「開啟舊檔」按鈕加上功能,要寫哪些程式呢?

我之前所常見的程式寫法,就是在按鈕上增加按下事件的偵聽,然後進行開啟「檔案選擇視窗」的工作,在使用者選好要開啟的檔案之後,讀取檔案裡的內容(當然可能還有一些例外判斷,不過不是這裡的重點),將結果顯示在編輯區內。一切看起來沒有什麼問題,就像是遙控器一樣,按下按鈕之後會觸發機器一連串的工作,使用者根本不管有幾個步驟流程,只管最後的結果是預想中的那樣。

我們不久就發現,在功能表中也有一個「開啟舊檔」的選項,而一般軟體使用中,Ctrl + O 也是代表檔案開啟。那麼該怎麼辦呢?

一部分的人會選擇複製程式碼,再作小幅度的修改讓另外兩種方式也能順利開啟檔案。這是這種模式最快的處理方式,但也是日後維護災難的開始。因為程式碼分散在各處,假如今天用來顯示檔案內容的區域換成其它的類別,那就得到這三段程式碼中找尋並修改。而且很容易出現修改一個功能,但是卻跑出另一個奇怪的 bug。因為程式碼已經多到無法了解原先規劃的流程,可能一個簡單的 if 判斷卻帶有重要意含。

以上就是「暗黑軟體工程師」的一種類型,剛開始撰寫的時候很快就有成品交付,增加功能似乎也很快就能夠完成,但是往後程式越來越多錯誤,需要越來越強的機器才能夠執行。修改一個錯誤要花費更多的時間,還不見得能夠完全解決。這一切都是因為程式碼已經複雜到超過維護人員腦袋能夠處理的的程度。人的聰明才智總是有個極限,機器能夠加快處理那些沒有效率的程式,在成本考量下也是有限制的。

而比較好一點的作法,就是去找出三種流程裡相同的程式碼,將同樣的動作變成新的類別,使之被呼叫、處理工作。所以當未來出現問題,也可以只修改一個地方,不至於出現有「忘記改到」的問題。實際上這種作法可以說是為了解決前面作法的問題,只是還是有一些地方沒有解決。

不管是「按按鈕」、「選擇功能表選項」或者是「使用鍵盤快速鍵」都需要撰寫引用、呼叫某個類別方法這樣的程式碼,今天如果要改變讀取檔案的方式,但是又要保留原來作法的時候,就得找到這三個地方去改變讀入的類別,所以並沒有完全解決「修改可能要異動許多地方的程式碼」的問題。舉個例子,如果要建立讀取電腦內和網路伺服器內檔案兩種版本的軟體,就會遇到得將這三段程式複製成兩份的問題。

如果能夠單純的「發出要求」,而不用理會是什麼類別或函式來處理的話,就不會有這樣的問題吧!至於組合要求和行為,如果可以由較為精簡的程式,或者設定檔來描述,就可以減少日後維護的複雜程度。因為共用的部分放在函數庫中,修改的時候只需要改變組合,或是針對有問題的部分修改就好,不用擔心會有哪一個地方忘了改到,那就能解決問題了吧!

上面所說的,就是物件導向的思考模式,也是 MVC 架構的精神之一。

而作法就是在「發出要求使用者介面」和「執行開啟檔案的行為之間」加上一個中介,讓使用者介面只需要發出「要開檔案」的要求,中介接收到之後去尋找負責執行的方法執行。因此,發出要求的使用者介面不需要知道執行的類別或方式是什麼,而執行的類別,也不需要知道是誰發出的要求。因此達到程式碼在未來擴充或組合上的彈性,達到物件導向中所謂的鬆散偶合。

在這個例子中,不論是「功能按鈕」、「功能表」甚至是「鍵盤的快速鍵」都是屬於 MVC 架構的 View。而前一段提到的中介,則是 Controller 。至於執行的方法與函數,雖然乍看之下好像是「控制」的一環,但是其實應該是 Model 角色,因為它和使用者將會操作的檔案有實際的接觸。

MVC 架構的優點,在於將一個功能區分成許多片段。負責 View 的部分只需要確定能夠發出正確的要求,以及當資料改變的時候,能夠呈現給使用者正確的結果。而 Model 只需要保證能夠確實完成要求,讓資料正確地被操作。而最後,Controller 將兩者結合,一個且有功能的軟體就完成大部分了。雖然可能要花費較多的時間進行前置規劃,但是一個好的架構,可以讓未來程式變得很容易維護。

MVC 的思想,實際上和物件導向一樣。都是為了能夠讓問題變得更小更細,讓撰寫程式的人可以專注在小範圍功能的邏輯上。而好的架構,則是提供使用者如何將問題分割,之後再如何組合的想法。如果不了解其思考方式,冒然硬是將功能分為 MVC 三個部分,反而會讓程式變得比原來的更加難以維護。甚至會用許多不必要的心力在開發。

程式的設計原則以及開發框架,其目的都是為了讓撰寫程式的人能更專注於要解決的問題,或是增加未來修改的彈性,程式的穩定性。而不是讓人得再去背幾個名詞,只能嚇嚇其它不懂的人,但是卻讓自己程式越寫越辛苦。

後記︰
說實在的,自己並不怎麼滿意這篇文章,總覺得想要表逹的想法沒有被確實呈現出來,不過讓我意外的事,一個以為不難的概念卻花了我三個小時以上的時間,修修改改卻得不到好的內容。也許是自己對這個概念了解不夠徹底,或是用文字表逹的能力還需要再加強吧!

1 則留言: