為什麼使用API?什麼情況下避免使用API?

出版圈郭志敏發表於2013-07-24

什麼是API

API(Application Programming Interface)提供了對某個問題的抽象,以及客戶與解決該問題的軟體元件之間進行互動的方式。元件本身通常以軟體類庫形式分發,它們可以在多個應用程式中使用。概括地說,API定義了一些可複用的模組,使得各個模組化功能塊可以嵌入到終端使用者的應用程式中去。

你可以為自己、你所在機構中的其他工程師或大型開發社群編寫API。它可以小到只包含一個單獨的函式,也可以大到包含數以百計的類、方法、全域性函式、資料型別、列舉型別和常量等。它的實現可以是私有的,也可以是開源的。有關API的一個重要的基本定義是:API是一個明確定義的介面,可以為其他軟體提供特定服務。

現代應用程式通常都是基於很多API建立起來的,而這些API往往又依賴於其他API。如圖1-1中示例應用程式所示,該應用程式用到了3個類庫(1、2、3)的API,而這3個API中有2個又用到了另兩個類庫(4和5)。舉例來說,瀏覽圖片的應用程式可能會用到載入GIF圖片的API,而該API本身則可能又依賴更底層的壓縮或解壓縮資料的API。

enter image description here

圖字翻譯:
Application Code:應用程式程式碼
Library:類庫

圖1-1 從層次化API中呼叫例程的應用程式。每個方框代表一個軟體類庫,灰色部分表示其公共介面,對於類庫而言即是其API,白色部分表示隱藏在API後面的具體實現。

API開發在現代軟體開發中隨處可見,其目的是為某個元件的功能提供一個邏輯介面,同時隱藏該模組內部的實現細節。舉例來說,我們用來讀取GIF圖片的API可能僅僅提供一個LoadImage()方法,後者接收一個檔名作為引數,並返回一個2維的畫素陣列。所有檔案格式和資料壓縮的細節全部隱藏在這個看似簡單的介面之下。這個概念也在圖1-1中進行了說明,即客戶端程式碼只能夠通過該API的公有介面訪問。API公有介面如圖1-1中每個方框頂部的灰色區域所示。

為什麼選用C++來描述API設計

雖然有很多通用API設計方法學(可適用於任何程式語言或程式設計環境)可以講,但最終都需要使用一門特定的程式語言來表述。因此瞭解特定語言的特徵以促進規範的API設計是非常必要的。所以,本書專門使用一種語言(C++)描述API設計的問題,而非分散內容使其適用於所有語言。然而,想要使用其他語言(如Java或C#)開發API的讀者仍然可以從本書中獲得許多通用的深刻見解。本書的直接目標讀者是編寫並維護API的C++工程師,他們的API要供給其他工程師使用。

目前,C++仍是大型軟體專案中使用最廣泛的程式語言之一,並且日漸成為注重程式碼效能專案的首選語言,因此,你可以在自己應用中選用的C和C++的API種類非常多(前面我已經列出一些)。本書重點關注如何使用C++編寫優秀API,並引入了豐富的原始碼示例以更好地闡述這些概念。也就是說,本書會涉及一些C++特有的主題,例如模板、封裝、繼承、名稱空間、操作符、const正確性、記憶體管理、STL的使用、Pimpl慣用法,等等。

另外,在本書出版期間,C++也正經歷著巨大的變革。新版的C++規範處於ISO/IEC的標準化程式中。目前,多數C++編譯器遵循1998年首次釋出的標準,即C++98。隨後的標準於2003年出版,修正了前版的幾處缺陷。自那時以來,標準委員會一直致力於一個重大的新版本規範。在標準被正式批准生效並確定釋出日期之前,該版本一直被非正式地稱為C++0x。當你讀到本書時,新的標準可能已經發布了。但是,在我編寫本書的期間,它仍然被稱為C++0x。

儘管如此,C++0x已經達到標準化程式的高階階段,我們可以滿懷信心地預言一些新的特性。事實上,一些主流的C++編譯器已經開始實現許多建議的新特性。在API設計方面,某些新特性可以用來構建更加優雅和健壯的介面。因此,我一直努力在整本書中強調和解釋C++0x中的API設計。所以,本書在未來幾年中應該依然具有參考價值。

為什麼使用API

在軟體專案中為什麼要關注API,這一問題可以從兩個方面理解:(1)為什麼要設計並編寫API?(2)為什麼要在應用中使用其他人提供的API?我將在接下來的小節中回答這兩個方面的問題,並指出在專案中使用API的各種好處。

如果你正在編寫供其他開發人員使用的模組,不管他們是公司裡的其他工程師還是外部客戶,比較明智的辦法是構建API來讓他們訪問這些功能。這麼做會帶來以下好處。

更健壯的程式碼

  • 隱藏實現。通過隱藏模組內部實現的細節,開發人員就可以在未來的某個時間自由修改模組的實現而不給使用者造成重大影響。如若不然,會導致以下結果:(1)程式碼的更新將會受到限制;(2)使用者只有重寫程式碼才能使用新版本的程式庫。如果總是讓使用者不停地更新軟體版本,他們很可能不願再做更新,或者乾脆棄用,另外尋找不需要太多維護工作的API。因此,優秀的API設計對業務或專案成功至關重要。
  • 延長壽命。隨著使用時間增長,那些公開了實現細節的系統的內部程式碼會變得錯綜複雜,系統的各個部分要依賴其他部分的內部實現細節。因此,系統將會變得脆弱、死板、不可移植且粘滯性高(Martin,2000)。如此一來,公司為了改善這些程式碼,就不得不花費大量人力財力,甚至推倒重寫。事先花工夫做好API設計,而後維護好該設計以保證一致性,軟體壽命就能延長,從長遠看也能節省花費。在第4章的前面部分,我們會深入討論該問題。
  • 促進模組化。API通常用來完成一項具體的任務或用例。因此,API定義了一組具有一致目的的模組化的功能集。在大量API基礎之上開發的應用,其結構將降低耦合而更加模組化,每一個模組的行為都不依賴其他模組的內部細節。
  • 減少程式碼重複。程式碼重複是軟體工程中最惡劣的行為之一,任何時候都要避免犯此類錯誤。通過把所有的程式碼邏輯置於一個嚴格的介面之後,讓所有客戶使用這個介面,就能將程式的某種行為統一處理。這樣做意味著只需更新一處程式碼就可以改變向所有客戶提供的API的行為。這樣有助於消除程式碼中所有重複的實現。事實上很多的API就是這樣實現的,人們首先發現有重複的程式碼,然後製作統一的介面取代這些重複程式碼,於是就產生了API。這是一件好事。
  • 消除硬編碼負擔。很多程式可能包含硬編碼的值,並在整個程式碼中不斷複製。例如,在需要寫日誌檔案的地方就使用具體的檔名“myprogram.log”。我們可以使用API來提供這些資訊,而不用在整個程式碼層面複製這些常量,例如,使用GetLogFilename() API呼叫代替硬編碼的“myprogram.log”字串。
  • 易於改變實現。如果將所有的實現細節隱藏在公共介面背後,就可以在不影響任何依賴此API程式碼的情況下改變其內部的實現細節。例如,可以將原本使用std::string解析檔案的方式變為分配、釋放、再分配char*緩衝區的方式。
  • 易於優化。成功隱藏了實現細節後,在優化API的效能時就不用操心更改客戶端程式碼。例如,可以利用加設快取的方案優化某個計算密集型的方法。之所以這樣做是因為所有讀寫潛在資料的操作都是通過該API進行的,因此該API更確切地知道何時快取中的結果無效並且需要重新計算結果。

程式碼重用

程式碼重用就是使用已有的軟體去構建新的軟體。這是現代軟體開發所追求的一個神聖的目標。API提供了一種程式碼複用的機制。

在早期的軟體開發中,有種情況很常見,即公司不得不為其製作的任一應用編寫所有程式碼。如果某個程式需要讀取GIF圖片或者解析文字檔案,公司不得不自己編寫全部程式碼。如今,隨著優秀的商業庫和開源庫的增多,重用別人已經編寫過的程式碼變得非常簡單。舉例來說,如今已經有各種開源的讀取影像的API和解析XML的API供人們下載和使用。這些庫被世界上許多程式開發人員不斷地改進和除錯,同時也已經在很多其他程式中被實踐檢驗過。

通過使用不同的元件(它們用於構建應用程式各個模組)並藉助元件已釋出的API相互通訊,軟體開發從本質上已變得更加模組化。這種做法的好處是不需要了解每個軟體元件的所有細節,如同前面提到的建造房屋的比喻,可以將很多細節問題委託給專業承包商。這樣能夠縮減開發週期,這一方面是因為可以重用已有的程式碼,另一方面則因為可以將各種元件的開發計劃分離,還可以讓開發者把重點放在核心業務邏輯上,而不必浪費時間重新發明輪子。

然而,實現程式碼重用的一個障礙是,常常需要比原本計劃更加通用的介面,因為其他客戶可能有額外的期望和需求。因此有效的程式碼重用來自對軟體客戶的深入瞭解以及結合了客戶和自身利益的系統設計。

C++ API和WEB

依賴第三方API的應用程式在雲端計算領域裡越來越普遍。該領域中,Web應用越來越依賴Web服務(API)為其提供核心功能。對於Web混搭應用程式(mashup),應用程式本身有時僅僅是對多種已有服務進行再次封裝,從而提供新的服務。例如,將Google地圖API和本地犯罪統計資料庫相結合就可以為犯罪資料提供一個基於地圖的介面。

實際上,花一些時間強調C++ API設計在Web開發中的重要性是值得的。膚淺的分析可能得出這樣的結論:伺服器端的Web開發局限於指令碼語言,諸如PHP、Perl、Python(即流行的LAMP架構縮寫中的“P”),或者基於Microsoft ASP(動態伺服器頁面)技術的.Net語言。對於小規模的Web開發可能確實如此,然而值得注意的是,許多大規模的Web伺服器都使用C++實現的後臺服務,以求最佳效能。

事實上,Facebook開發過一個名為HipHop的產品,它將PHP程式碼轉換為C++,以此改善社交網站的效能。因此C++ API設計在可擴充套件的Web服務開發中確實佔有一席之地。此外,使用C++開發核心API不僅可以構建高效能的Web服務,而且這些程式碼還可以在諸如桌面或行動電話版本等其他形式交付的產品中重用。

說句題外話,對於這種軟體開發策略的轉變,一種可能的解釋是全球化推動的結果(Friedman,2008;Wolf, 2004)。實際上,網際網路、標準網路協議和Web技術的匯聚已經創造了一個軟體競技平臺。這使得來自世界各地的公司和個人都可以在大型複雜軟體專案中進行創造、貢獻和競爭。這種形式的全球化促成了一種環境,在該環境下,全世界的公司和開發者能夠以開發軟體子系統為生。世界各地的其他組織進而可以通過組合與擴充套件這些構建模組建立解決特定問題的終端使用者應用程式。就本書討論的焦點而言,API提供了促成現代軟體開發全球化和元件化的機制。

並行開發

即使你只是編寫內部軟體,與你共事的工程師也很可能要使用你的程式碼編輯程式。如果使用良好的API設計技巧,就可以簡化彼此的工作,不必回答諸多關於程式碼如何工作、如何使用的問題。這在多個開發者並行開發相互依賴的程式碼時顯得尤為重要。

舉例來說,假設你正在編寫一個字串加密演算法,其他開發者需要使用該演算法將資料寫入配置檔案。一種做法是讓其他開發者等你完成所有工作,然後在其檔案輸出模組中使用你的演算法。然而,更有效率的做法是,你們提前見面協商好恰當的API,然後你把API放在適當的位置,而API僅僅起佔位符的作用,這樣你的同事就可以立即呼叫它們了,例如:

#include <string> 

classStringEncryptor
{
public:
    ///設定Encrypt()和Decrypt()呼叫時使用的金鑰
    voidSetKey(conststd::string &key);

    ///基於當前金鑰加密輸入字串
    std::string Encrypt(conststd::string &str) const;

    ///基於當前金鑰解密輸入字串
    /// Decrypt()一個由Encrypt()返回的字串
    /// 返回同一個金鑰下原始的字串
    std::string Decrypt(conststd::string &str) const;
};

接著,你可以提供這些函式的簡單實現,使得該模組至少可以編譯和連結。例如,相關的.cpp檔案可以像下面這樣:

voidStringEncryptor::SetKey(conststd::string &key)
{
}
std::string StringEncryptor::Encrypt(conststd::string &str)
{
    return str;
}
std::string StringEncryptor::Decrypt(conststd::string &str)
{
    return str;
}

這樣一來,你的同事就能夠使用這個API繼續工作而不被你的進度所耽擱。雖然目前你的API實際上不會加密任何字串,但是這只是一個小的實現細節。重要的是已經有了一個雙方都認可的穩定介面,即一個契約,而且該介面的行為恰當,例如Decrypt(Encrypt("Hello"))將會返回“Hello”。當你完成了工作,並以正確的實現更新了.cpp檔案後,你同事的那部分程式碼不需要進行任何修改就能直接執行了。

實際上,有些介面問題很可能在編寫程式碼之前並沒有預料到,因此API設計可能需要多次迭代才能保證其恰到好處。在大多數情況下,API支援雙方能夠以最少的停頓並行工作。

這種方法還用利於測試驅動或者是測試先行的開發。事先確定了API,就可以編寫單元測試來驗證預期的功能,並且可以持續執行這些測試程式,以保證始終沒有打破你和同事之間的協議。

將該過程延伸到組織層面,你的專案可以有獨立的團隊,他們彼此也許相距很遠,甚至遵循不同的日程安排。通過預先確定各個團隊的依賴關係,並通過建立API來為這些關係建模,各個團隊就可以獨立工作,而幾乎不必關心其他團隊如何實現API背後的工作。資源的有效利用以及削減相應的冗餘通訊,能夠為組織節約大量整體成本。

何時應當避免使用API

設計並實現API相比編寫普通的應用程式程式碼通常要花費更多精力,因為API的宗旨是提供健壯、穩定的介面供其他開發人員使用。因此,與僅在單一應用程式內部使用的軟體相比,API在質量、設計、文件編寫、測試、支援及維護方面有更高的要求。

因此,如果編寫的是不需要和其他客戶端通訊的內部模組,那麼為模組建立並維護穩定公有介面的額外開銷就不值得了,然而這並不是編寫劣質程式碼的理由。為堅持API設計原則而多花費些時間,從長遠看來並不浪費時間和精力。

另一方面,假設你是一位想在應用程式中使用第三方API的軟體開發人員。前一節討論了在軟體中重用外部API的一些理由,但有時也可能需要避免使用特定的API,在如下這些情況下,你應該花精力自己實現程式碼或尋找替代的解決方案。

  • 許可證限制。雖然API可以提供所需的各項精巧功能,但是許可證限制可能讓你望而卻步。例如,如果你想使用GNU GPL(General Public License,通用公共許可證)釋出的開源包,那麼就必須使用GPL釋出所有衍生作品。如果在程式中使用這個包,就必須釋出整個應用程式的原始碼,這是商業應用可能不會接受的約束。其他的許可證(如GNU Lesser General License GPL,LGPL)就更加寬鬆些,在軟體庫中也更加常見。許可證問題的另一種體現是:商業API的費用對你的專案來說可能過於昂貴,或者許可條款可能過於嚴格,比如要求向每位開發者甚至每位使用者收取許可費用。
  • 功能不匹配。雖然API看似能夠解決所面臨的問題,但是它有可能以一種與應用程式約束或功能需求不匹配的方式執行。例如,可能你正在開發一個影像處理工具,想要提供傅立葉變換功能。雖然有許多現成的FFT(Fast Fourier Transform,快速傅立葉變換)的實現,但是其中大多數是1D演算法,而處理2D影像資料需要使用2D FFT。此外,許多2D FFT演算法只能在大小是2的整數冪的資料集上工作(如256×256512 × 512)。或許你找到的API不能在必須支援的平臺上執行,或者它不能滿足你對程式制定的效能標準。
  • 缺少原始碼。雖然有許多開源API,但是有時符合要求的最佳API可能是閉源產品。也就是說,只提供介面的標頭檔案,而背後的C++原始檔並不同庫一起釋出。這有幾項重要的影響,其中之一就是當庫中存在錯誤時,不能通過檢查原始碼的方式定位問題。對於跟蹤錯誤進而找到解決方案來說,閱讀原始碼是一個很重要的方法。
    進一步說,不能訪問API的原始碼就喪失了通過修改原始碼修復錯誤的能力。這意味著軟體專案的進度可能會受到所使用的第三方API中不可預期問題的不利影響,並且需要等待該API的所有者處理你的錯誤報告併發布修復補丁。
  • 缺乏文件。雖然API看似可以完全滿足應用程式的需求,但是如果API的文件編寫拙劣或根本沒有文件,那麼你最好再去尋找別的解決方案。在這種情況下,有可能是API的用法不清楚影響了它的使用,也有可能是你無法確定特定情況下API的行為,甚至可能是你根本無法信任那些連花點兒時間解釋程式碼都做不到的開發者。

本文摘自即將上市的《C++ API設計》

enter image description here

相關文章