.NET的情形驅動設計(Scenario Driven Design)
在新概念泛濫的電腦業,甭管有事沒事,每天不吆喝幾句敏捷(Agile)啦、模型驅動(Model-Driven)啦、Ajax啦就顯得特別沒面子。但天天聽這些日子久了胃會感到不適。所以呢,今天特別表彰一下默默無聞埋頭苦乾的情形驅動(Scenario Driven)同學,也就是.NET Framework採用的設計方法。
情形驅動和模型驅動都是設計方法,不同的是情形驅動更側重API的易用性和功能的協調。我們將開發人員分成三類,張飛、關羽、諸葛亮。咋沒劉備呢?因為人家劉備是Manager。張飛是很生猛的那種,從來不看文檔,兵書有什麼好看的,先殺再說。張飛主要開發垂直應用,所用的API範圍較小,但務必簡單實用,不喜歡讀書,喜歡在實踐中學習。關羽比較實際,耍大刀之餘也要看看兵法,可是文武雙全的雙學位呢。關羽比較注重功能和開發效率的平衡,又想高效的開發程式,又想能使用各種進階功能。諸葛亮則是先熟讀兵書,而後一把羽扇論天下。要設計API給他用,那可必須將每處的實現原理詳實道來,要是人家不滿意,還得能允許人家用自己的實現替換。以前三位英雄各操各的傢伙,而現在,要設計一個類庫同時給這三位英雄用,這可不容易啊。
在模型驅動方法下,我們開啟一個UML工具,用面對對象的方法設計好物件模型,然後去領工資。在情形驅動的方法下,這是錯誤的,我們必須反過來,首先要寫一些使用者將要如何使用這些API的範例,也就是情形(Scenario),然後根據這些用法來設計API。就好比設計汽車時首要考慮的是坐在車裡開車的人的感受,而不是馬力如何、改裝的靈活度如何。這種方法設計的API有可能並不符合面對對象(OOP)原則,但其實,它正是強調不要拘泥於面對對象的方法來設計API。因為面對對象的方法是被發明用來設計易於修改、有彈性的內部實現的,而不是用於外部API設計的。
如何即能讓張飛容易上手,又能給諸葛亮足夠的功能和靈活度呢。很顯然,用同一個API是無法做到的。這裡所要用的原則就是“讓常用功能簡單,進階功能可用(Make the routine tasks easy, and the advanced tasks possible)”。在類方法上,為常用的方法調用提供參數很少的簡單重載,同時為進階功能提供有足夠參數的重載,如Graphics.DrawString();在類上,將常用類放在主名字空間內,進階功能類放在子名字空間內,如System.Drawing下是常用圖形類,而進階向量功能在System.Drawing.Drawing2D裡,進階影像處理在System.Drawing.Imaging裡。
張飛不看文檔的脾氣可不好對付,為了讓他能夠上手,API必須是自說明的(self-documenting)、可實驗的(support experimentation)。自說明意味著新API必須和張飛所熟悉的東西類似,能夠望文生義。比如組件的“建立、設定屬性、調用方法”使用模式,如果你的API遵從這個模式,張飛肯定知道怎麼用。可實驗意味著要讓老張能在用錯的情況下,通過幾次猜測和嘗試找出正確用法,那麼就必須在名字上具備引導性,同時要有指出該如何改正的異常資訊。比如有個類PrintQueue提供列印佇列的介面,但張飛根本不關心有沒有列印佇列,他只是要列印文檔,只想到要用Printer。
諸葛亮倒是捧起文檔,從頭看到尾,然後想出一堆我們從沒想過的用法,然後寫5分鐘的代碼。要是我們的API哪裡絆到人家了,那是咱的錯。這時,功能、效率、靈活性、擴充性等經典思想躍然紙上。即使如此,仍不要忘了編程的最古老的原則:小就是美。越少的代碼,bug越少。
張飛諸葛亮都搞定了,還怕關羽不成。
拋開嚴格定義不說,情形驅動和模型驅動裡的用例研究(Use Case Study)有相似之處,都是要從使用方法的角度最佳化,只不過用例只考慮最終的軟體使用者。