[ program ] 02 十二月, 2010 18:34

如何判斷API的功能涵蓋範圍 
文/王建興 (清華資工所博士班研究生) 2010-12-02  

API設計 第2回API過多或過少都會造成一些問題,然而關鍵可能在於如何畫分API與客戶端程式碼的界線

在前一回中,我們提到,一組API所提供的功能應該要恰到好處,若是提供的功能太少,那麼客戶端程式設計者需要自己做的事就多,那麼API的價值就變低。但同樣的,功能包山包海的API不見得是好事,因為這麼一來,雖然有可能提供更多的作用、更好的彈性,但是相對而言,複雜度也會提高,客戶端程式設計者在學習時要花更多時間才能理解、也要花更多時間才能上手。同時,愈複雜的API使用上愈容易犯錯。

如何考量該由API負責的事情
在我們的經驗中,我們都使用過一些其實十分強大的API,但是,有時花在學習、花在錯誤排除的時間,還多過於API能為我們省下的時間。我們一方面期待API涵蓋的功能最小化、一方面又期待API所提供的功能夠完整,這兩個目標當然是相互衝突的。而設計者的工作,便是在兩種情況間取得一個良好的平衡。

API設計者和客戶端程式設計者是分工合作的,但二者之間應該依照什麼樣的原則來分工,那些東西適合畫歸API、又有那些適合留給客戶端程式碼完成呢?

當你在設計一組API時,通常會設定一個應用的領域,進而設想客戶端程式設計者在這個應用領域中可能的應用情境。你可以想像,倘若缺少了這組API時,程式設計者必須自行寫下那些程式碼,才能完成你所設想的諸般應用情境。接著,你便可以針對這些應用情境進行分析,藉以分出API與客戶端程式碼的界線。

有幾個基本的畫分原則可以參考,你可以視分析程式碼所完成之工作:(1)低階高階與否(2)通用與專用與否(3)常用與罕用與否,來進行畫分的依據。

就像套用成語一樣方便,用更簡易的方式駕馭複雜的底層功能
你可以很輕易區分出,那些工作是低階、那些是高階。低階畫給API,高階則留給客戶端程式碼。API之所以存在的首要目的,便是將程式實作時繁瑣的低階細節予以抽離並包裝,成為抽象化、可重複使用的程式碼,使得基於此API的客戶端程式設計者,毋需在一次又一次的開發中,反覆地重新實作相同的程式碼。

若你觀察C的檔案處理程式庫,便會發現,它成功地界定程式設計者在處理檔案時,API和客戶端程式碼間的界線。

如果我們沒有一套檔案處理API,我們自己必須實作那些事呢?舉例來說,得自己想辦法記錄每個檔案的名稱、每個檔案的內容究竟位在那個磁頭、磁軌、磁區,然後當我們想讀取某個檔案時,才能藉以找到這個檔案的資料,究竟是位於那些磁區之上,然後應該依照怎樣的讀取順序,才能依序、或是隨機地讀出檔案資料。同樣的,寫入資料的情況也差不多。

當然,我還可以舉出更多繁瑣的低階細節,像是檔案讀取寫入時的緩衝機制,以提升檔案處理時的效率等等,但這並不是這邊打算討論的重點。

重點是,檔案處理程式庫的設計者決定了究竟那些細節應該由API這邊畫出一道明確的界線,使得這些細節被阻隔在應用程式設計者之外。你看到的像是fread()、fwrite(),只要透過它們,便可以輕易進行檔案的讀取及寫入。你可能還需要像是fseek()來幫助你設定要讀取檔案的位置。有了這樣的API,你再也不需要明白究竟檔案的處理實際上會涉及那些細節,當然,你更不需要自己實作這些細節。

你可以想像,倘若這些檔案處理最低階的部份,都需要應用程式設計者自行實作的話,這樣子的軟體開發世界究竟有多可怕。但有了這些API函式後,事情簡化到很高的程度。

這些低階的細節,都是每個應用程式設計者在處理檔案時都會面臨的,所以由API以下的層次加以包裝起來之後,應用程式設計者,自然可以省去大量的時間。事實上,API的責任在於定義界線,就檔案處理的這個例子來說,有更多工作並不是應用程式庫完成的。

除了省去客戶端程式設計者的開發時間之外,你可以看到將低階細節隱藏起來的另一個威力所在,便是為客戶端程式設計者提供一個更高階、更簡潔、更直觀、更接近應用領域的程式設計模型。

因此,客戶端程式設計者在撰寫程式時,心裡想的像是:「我現在要從某個檔案讀取1024個位元組的動作」,而不是,「我要找出某個檔案究竟位在那些磁區上,然後我要讀取的檔案位置究竟位在那個磁區中,在讀取之前,我得先看看程式裡的緩衝區裡是否有想讀取的資料,有的話,就直接從緩衝區裡讀取,反之,則移動硬碟的讀寫頭到指定的磁區上,然後讀取1024個位元組」。

立足於API之上的高階程式設計,有點像是運用成語一樣,可以讓寫作的人,表達出同樣的意思,但更精要、有時甚至還更為傳神。我們從高階低階的角度來畫分API和客戶端程式碼之間的分工責任時,除了思考能為客戶端程式設計者省去多少重複的工作之外,更值得思考:客戶端在運用API時,程式碼所呈現出來的面貌究竟會是如何。

如果透過你所設計的API,所編寫出來的程式碼,變得更加的直覺、更容易讀懂,那麼代表你的設計是成功的。反之,你就需要檢討為何自己所設計的API,反而讓客戶端程式設計者所寫下的程式碼變得不直覺、甚至可讀性降低。

用八十/二十法則來考量,通用的部份交給API來處理
除了高階、低階這個畫分的原則之外,通用與專用則是第二個原則。何謂「通用」?通用就是指在你所設定的應用領域中,客戶端程式設計者廣泛都會需要用到的。相反的,所謂的專用,便是在這個應用領域中,僅有少數特定需求的客戶端程式設計者才會需要用到。

「廣泛」、「少數」,都是不那麼精確的詞,這意謂著,當你依據這個標準在進行判斷時,必須針對各種條件來拿捏其中的分寸。

正如前面提到的,我們會希望在最小化API的前提下,盡可能地滿足最多客戶端程式設計者。特定的需求會被最小化的前提給過濾掉。這邊或許可以基於八十/二十法則來做決定,用20%的功能,來滿足80%的應用需求。

我在這裡建議的第三個原則是,將常用的部份畫歸到API,將罕用的留給客戶端程式碼。同樣的,這也是因為最小化API的前提之下必須要有的取捨。

有一些API的設計者,固然考量到在所設定的應用領域中,需要被滿足的常用需求,但他們的企圖心太大,他們還把那些冷門、根本不太會有客戶端程式設計者需求的功能,也都納入API的範圍之內。為了幾乎不太會被用到的功能,而讓API變大、變複雜,事實上,是十分不划算的一件事。

如果不將那些罕用的功能放到API裡,即使在少數的情況下,客戶端程式設計者會需要使用,因為情況終究是少數,所以,那時再讓客戶端程式設計者自行實作即可。

API的設計者還是必須從顧及全貌的立場出發來考量自己的設計。在「常用」及「罕用」的取捨上,一樣可以利用八十/二十法則來做為衡量的標準。

決定API的範圍和內容,是開發人員設計API時很關鍵的一件事,在本文中提供了幾種方式供你參考。在下一回中,我們會進一步討論和API設計有關的其他議題。 

[ program ] 02 十二月, 2010 18:26

為什麼要關注API的開發? 
文/王建興 (清華資工所博士班研究生) 2010-11-25

API設計 第1回應用程式介面(API)的設計議題是我們時常會接觸到的,而在這一次,讓我們來探討一下和API設計有關的一些事情

所有的程式人,對於API這個名詞肯定不陌生,這甚至是我們在討生活的過程中,總是時常掛在嘴上的一個名詞。

所謂的API,即為應用程式介面(Application Programming Interface),通常是作業系統或是程式庫,為了便利應用程式的撰寫,而提供的一組程式碼。其目的在於隱藏底層實作的細節,透過簡化、抽象、又一致的介面,讓應用程式的撰寫者,並不需要明白底層確切的實作方式,也不需要花費時間重新開發這些共通的輪子,而能更專注在自己所欲開發的應用程式之上。

在我們之中,或許有許多人的工作,便是使用現成的API來完成應用程式的開發,但是,或許還有些人的部份或全部工作,反而是開發特定的API,提供給其他的應用程式開發者來使用。

對後續的程式開發影響很廣泛,好壞很重要
和API設計者相對的角色,可以說是API使用時的客戶,雖然他們也是程式設計者,但對API設計者而言,他們仍然是站在客戶端,就好比應用程式的使用者一樣的地位。所以,通常會把他們稱為「客戶端程式設計者(client programmer)」,而他們立足於API之上所寫下的程式碼(也就是呼叫API的應用程式程式碼),則會被稱為「客戶端程式碼(client code)」。

身為API設計者,其目標當然是設計出好的API。好的API帶你上天堂,壞的API讓你住套房。因為一套API被設計出來,通常都會有許多客戶端程式設計者基於這套API來開發他們的應用程式,倘若在API設計中有一個缺陷,那麼這一個缺陷就會「傳染」到所有該API的客戶端程式設計者所開發的應用程式中,接著便會影響到所有應用程式的使用者。

因此,API的設計良善與否,影響層面往往十分廣泛。而且受到重視及眾多程式設計者採用的API,其生命期更是十分久長,即使API會演化,通常也會保持往下相容,所以,舊的API問題,倘若出在介面上,那麼演化時仍會從前一代繼續帶往下一代。

有一個故事是這樣子的,B語言的發明者,也是C語言的共同發明者、同時還是Unix的建立者Ken Thompson,他在設計Unix的檔案系統API(Unix稱為系統呼叫,system call)時,把開檔函式傳入參數值的一個常數定義名稱拼錯了,這個常數定義名稱也就是O_CREAT,所有C程式設計者對這個定義名稱都不陌生,但不知道你有沒有懷疑過,它是原作者拼錯字下的產物呢?Ken Thompson一直想把O_CREAT糾正成為正確的O_CREATE,但因為已經被列在POSIX中的規範,所以,根本就沒有機會,全世界所有的人只能繼續將錯就錯。

在這個例子中,設計者所犯下的錯誤雖然只是拼錯一個字,無礙於整體的運作,也不會造成什麼傷害,頂多只是看起來礙眼(對Ken Thompson來說,可能是刺眼),但是,這說明了,倘若在API介面中犯下其他型式的錯誤,想要修正這個錯誤的代價可能就十分的沉重。

那麼,你接著自然會問,一組好的API應該具有什麼特質呢?

如何辨識優良的API
一組好的API最重要的特質,便是要能滿足客戶端程式設計者的需求。就這一點而言,就和好的應用程式相同,並無二致。API的基本條件是包裝實作的細節,降低客戶端程式碼編寫時的複雜度。當你在設計你的API時,你必須分析對客戶端程式設計者而言,究竟那些實作的細節、以及複雜性,是他們所不願意碰觸的,是他們所不願意花費時間去重新撰寫的,進而歸納出,那些操作是應該被包裝成更高階、更抽象的介面,藉以隱藏這些實作細節及複雜性,對客戶端程式設計者提供撰寫程式時的便利。

有些API設計者所設計出來的API,提供的功能太少,這使得客戶端程式設計者即使有了API,但仍然許多動作得憑藉著自己的力量去完成,API的存在意義便減少許多。也有些API的設計缺乏彈性,只考慮到一些特定的應用情境(通常這是因為API的設計者只考慮到自己或少數人,而沒有考慮到API對象的共通需求),這麼一來,客戶端程式設計者仍然無法得到API應該帶來的好處。

但相反的,也有一些API設計的太有彈性,不僅客戶端程式設計者需要用到的,都能透過它來達成,連根本不需要用到的,它也都能提供。這乍看之下是件好事,但此類的API因為太過於通用,通常複雜度也就跟著提高,對客戶端程式設計者而言,一來不容易上手,二來運用難度也高、同時容易出錯。

通用性超過實際需求的API,一樣會造成客戶端程式設計者的困擾。API提供的功能太少、或太多,缺乏彈性或太有彈性,都不適宜,API的設計者需要拿捏出其中平衡點,讓你設計的API恰如其份,而這也是學問所在。

再來,好的API設計應該要夠直覺,也就是說,對客戶端程式設計者而言,不需要花費額外的心思才能理解,也不需要花費額外的力氣才能參透運用API的深層奧妙,它的設計對程式設計者來說,應該像是天生就如此理所當然的那樣。好的API並不會在介面上做高深的賣弄,和客戶端程式設計者之間相接觸的介面,應該是愈平實、愈容易理解、愈符合客戶端程式設計者自然的預期愈好。

當然,我並不是在說API設計不需要文件輔助說明,但是,好的API設計,的確可以在不倚賴太多文件說明的情況下,就能讓客戶端程式設計者自在的運用,而且不會發生誤解的情況。這便是因為這樣的API設計結果足夠直覺。

為了讓API更直覺,採用一致且容易正確套用與延伸的命名方式
所有的程式碼設計其實都應該要夠直覺,只是API設計更需要!要怎麼達到這個「夠直覺」的境界?最重要的第一步當然是命名。無論函式名稱(若是物件導向程式語言,那麼就是類別名稱、資料成員名稱、成員函式名稱)或是傳入函式的參數名稱,都能為客戶端程式者提供足夠的隱喻及暗示。

只要是適當的命名方式都可以為客戶端程式者帶來直覺,但重點是——要有一致性。具備一致性的東西,能夠讓客戶端程式者很容易舉一反三。

例如,在Java的群集器(collection)API中,將元素加到群集器中,一律都用「add」這個名稱,移除則一律採用「remove」這個名稱。一旦涉及傳入另一個群集器的操作,則都會使用「all」來表示,例如將另一個群集器中的元素加入某個群集器中的動作是「addAll」,而將另一個群集器中的元素自某個群集器中移除的動作則是「removeAll」。判斷某個元素是否在群集器中,是用「contains」,很自然而然的,你會推論,判斷某個群集器中的所有元素是否皆在另一個群集器中,它是使用「containsAll」這個名稱。

這便是命名的重要性,同時也是一致性帶來的好處,即使我們不知道,也可以根據這個一致的規則,「推論」出究竟應該是什麼。
當然,一致性不僅僅只反映在命名上,所有設計原則都應該要有一致性。從Java群集器API的設計,你就能大概體會到,一個直覺的API設計,對客戶端程式設計者來說,在理解上能夠起多大的作用。