如何設計一個優秀的API
到目前為止,已經負責 API 接近兩年了,這兩年中發現現有的 API 存在的問題越來越多,但很多 API 一旦發布后就不再能修改了,即時升級和維護是必須的。一旦 API 發生變化,就可能對相關的調用者帶來巨大的代價,用戶需要排查所有調用的代碼,需要調整所有與之相關的部分,這些工作對他們來說都是額外的。如果辛辛苦苦完成這些以后,還發現了相關的 bug,那對用戶的打擊就更大。如果 API 經常發生變化,用戶就會失去對提供方失去信心,從而也會影響目前的業務。
但是我們為什么還要修改 API 呢?為了 API 看起來更加漂亮?為了提供更多功能?為了提供更好的性能?還是僅僅覺得到了改變了時候了?對于用戶來說,他們更愿意使用一個穩定但是看起來不那么時髦的 API,這并不意味著我們不再改進 API 了。當糟糕的 API 帶來的維護成本越來越大時,我想就是我們去重構它的時候。
如果可以回頭重新再做一遍,那么我心目中的優秀的 API 應該是怎么樣的?
判斷一個 API 是否優秀,并不是簡單地根據第一個版本給出判斷的,而是要看隨著時間的推移,該 API 是否還能存在,是否仍舊保持得不錯。槽糕的 API 接口各種各樣,但是好的 API 接口對于用戶來說必須滿足以下幾個點:
- 易學習:有完善的文檔及提供盡可能多的示例和可 copy-paste 的代碼,像其他設計工作一樣,你應該應用最小驚訝原則。
- 易使用:沒有復雜的程序、復雜的細節,易于學習;靈活的 API 允許按字段排序、可自定義分頁、 排序和篩選等。一個完整的 API 意味著被期望的功能都包含在內。
- 難誤用:對詳細的錯誤提示,有些經驗的用戶可以直接使用 API 而不需要閱讀文檔。
而對于開發人員來說,要求又是不一樣的:
- 易閱讀:代碼的編寫只需要一次一次,但是當調試或者修改的時候都需要對代碼進行閱讀。
- 易開發:個最小化的接口是使用盡可能少的類以及盡可能少的類成員。這樣使得理解、記憶、調試以及改變 API 更容易。
如何做到以上幾點,以下是一些總結:
1、 面向用例設計
如果一個 API 被廣泛使用了,那么就不可能了解所有使用該 API 的用戶。如果設計者希望能夠設計出被廣泛使用的 API,那么必須站在用戶的角度來理解如何設計 API 庫,以及如何才能設計出這樣的 API 庫。
2、 采用良好的設計思路
在設計過程中,如果能按照下面的方式來進行設計,會讓這個 API 生命更長久
- 面向用例的設計,收集用戶建議,把自己模擬成用戶,保證 API 設計的易用和合理
- 保證后續的需求可以通過擴展的形式完成
- 第一版做盡量少的內容,由于新需求可以通過擴展的形式完成,因此盡量少做事情是抑制 API 設計錯誤的一個有效方案
- 對外提供清晰的 API 和文檔規范,避免用戶錯誤的使用 API,尤其是避免 API(見第一節)靠后級別的 API 被用戶知曉與誤用
除此之外,下面還列出了一些具體的設計方法:
- 方法優于屬性
- 工廠方法優于構造函數
- 避免過多繼承
- 避免由于優化或者復用代碼影響 API
- 面向接口編程
- 擴展參數應當是便利的
- 對組件進行合理定位,確定暴露多少接口
- 提供擴展點
3、 避免極端的意見
在設計 API 的時候,一定要避免任何極端的意見,尤其是以下幾點:
- 必須漂亮(API 不一定需要漂亮)
- API 必須被正確地使用(用戶很難理解如何正確的使用 API,API 的設計者要充分考慮 API 被誤用的情況:如果一個 API 可能會被誤用,那么它一定會被誤用)
- 必須簡單(我們總會面臨復雜的需求,能兩者兼顧的 API 是更好的 API)
- 必須高性能(性能可以通過其他手段優化,不應該影響 API 的設計)
- 必須絕對兼容(盡管本文一直提到如何保證兼容,但是我們仍然要意識到,一些極少情況下會遇到的不兼容是可以容忍的)
4、 有效的 API 評審
API 設計完成以后,需要經過周密的設計評審,評審的重點如下:
- 用例驅動,評審前必須提供完善的使用用例,確保用例的合理性和完備性。
- 一致性,是否與系統中其他模塊的接口風格一致,是否與對稱接口的設計一致。
- 簡單明了,API 應該簡單好理解,容易學習和使用的 API 才不容易被誤用,給我們帶來更多的麻煩。
- API 盡可能少,如果一個 API 可以暴露也可以不暴露,那么就不要暴露他,等到用戶真正有需求的時候再將它成為一個公開接口也不遲。
- 支持持續改進,API 是否能夠方便地通過擴展的方式增加功能和優化。
5、 提高 API 的可測試性
API 需要是可測試的,測試不應依賴實現,測試充分的 API,尤其是經過了嚴格的“兼容性整合測試”的 API,更能保證在升級的過程中不出現兼容性問題。兼容性整合測試,是指一組測試用例集合,這組測試用例會站在使用者的立場上使用 API。在 API 升級以后,再檢測這組測試用例是否能完全符合預期的通過測試,盡可能的發現兼容性問題。
6、 保證 API 的向后兼容
對于每一個 API 的設計者來說,都渴望做到“向后兼容”,因為不管是現在的 API 用戶,還是潛在的 API 用戶,都只信任那些可兼容的 API。但向后兼容有多個層次上的意義,而且不同層次的向后兼容,也意味著不同的重要性和復雜度。
7、 保持逐步改善
過去我們總希望能將現有的“不合理”的設計完全推翻,然后按照現在“美好”的思路,重新設計這個 API,但是在一段時間以后,又會碰到一樣的狀況,需要再推翻一次。 如果我們沒有有效的逐步改善的辦法,依靠推翻現有設計,重新設計 API 只能讓我們回到起點,然后重現之前的過程。 要有一套行之有效的持續改善的辦法來在 API 兼容的同時,改善 API 使之更好。
8、 把握 API 的生命周期
每一個 API 都是有生命周期的,我們需要讓 API 的生命周期更長,并且在 API 的生命周期結束時能讓其平滑的消亡。
- 告訴用戶我們是如何設計的,避免誤用,提供指導,錯誤的使用往往是縮短 API 壽命的一大殺手
- 提供試用期,API 不可能一開始就是穩定,經過試用的 API 才能有更強的生命力
- 為 API 分級:內部使用;二次開發使用;開發或試用中;穩定;棄用 API。避免 API 被濫用的同時,我們可以通過調整 API 的級別,來擴大其影響力,也能更優雅的結束一個 API 的生命周期。
開發 API 的過程其實就是一個溝通交流的過程。溝通的雙方就是 API 用戶和 API 設計者。
9、 一些具體的實施方案
在一個 API 不可避免要消亡或者改變的時候,我們應該接受并且面對這個事實,下面列舉了幾種保證兼容性的前提下,對 API 進行調整的辦法:
- 將 API 標記為棄用,重新建立一個新的 API。如果一個 API 不可避免要被消亡,這是唯一的辦法。
- 為其添加額外的參數或者參數選項來實現功能添加
- 將現有 API 拆成兩部分,提供一個精簡的核心 API,過去的 API 通過封裝核心 API 上實現。這通常用于解決用戶需要一個代碼精簡的版本時。
- 在現有的 API 基礎上進行封裝,提供一個功能更豐富的包或者類