從 Java 大神 Joshua Bloch 提煉 API 設(shè)計(jì)的三個(gè)核心原則
一個(gè) API 應(yīng)該容易學(xué)習(xí)和使用,且不易被誤用。它還應(yīng)該隨著時(shí)間而發(fā)展,優(yōu)秀的設(shè)計(jì)需要預(yù)見(jiàn)并適應(yīng)這種變化。
Joshua Bloch 曾在 Sun 擔(dān)任杰出工程師,之后加入谷歌成為首席 Java 架構(gòu)師。他主導(dǎo)了 Java 平臺(tái)上的很多功能,包括 Java Collections 框架,java.math 包,assert 機(jī)制等。他也是 Effective Java 的作者。
在谷歌 2007 年的一場(chǎng)重要演講中,軟件工程師兼技術(shù)作家 Joshua Bloch 強(qiáng)調(diào)了 API 是一種極其重要的商業(yè)資產(chǎn)。他指出,這主要是因?yàn)槿绻?API 對(duì)外開(kāi)放,客戶可能會(huì)選擇在上面進(jìn)行大量投資,從而很難改變使用習(xí)慣。
Bloch 還警告說(shuō),設(shè)計(jì)糟糕的 API 可能會(huì)導(dǎo)致無(wú)休止的客戶支持電話,極大地阻礙公司的發(fā)展。
Bloch 進(jìn)一步指出,以 API 設(shè)計(jì)為思考核心,能顯著提高編寫(xiě)程序的質(zhì)量。
即使你作為一個(gè)程序員并不直接參與面向公眾的 API 開(kāi)發(fā),實(shí)際上你也在持續(xù)地創(chuàng)建 API。優(yōu)秀的編程應(yīng)該是模塊化的,模塊間的邊界自就是 API。同樣,如果你參與的是一個(gè)現(xiàn)代化的、分布式的、基于微服務(wù)架構(gòu)的系統(tǒng),那么服務(wù)間的邊界也構(gòu)成了 API,只是架構(gòu)有所不同。
盡管如此,API 設(shè)計(jì)仍然是許多程序員面臨的一個(gè)挑戰(zhàn)。那么,一個(gè)好的 API 有哪些特點(diǎn)呢?
1、名字至關(guān)重要
從宏觀角度來(lái)看,API 應(yīng)該易于學(xué)習(xí)和使用,同時(shí)難以被誤用。它還需要隨著時(shí)間的發(fā)展而進(jìn)化,而一個(gè)優(yōu)秀的設(shè)計(jì)會(huì)將此考慮在內(nèi)。
命名的方式極其重要,因?yàn)?API 在實(shí)質(zhì)上是一種需要用戶學(xué)習(xí)的簡(jiǎn)約語(yǔ)言。
「真正合適的命名可以解決問(wèn)題并避免誤解,因?yàn)榍‘?dāng)?shù)拿軌蚍浅C鞔_地表明事物的本質(zhì)?!筍oftIron 首席科學(xué)家 Harry Richardson 在接受 The New Stack 采訪時(shí)表示。
Richardson 特別指出,對(duì)于開(kāi)發(fā)者來(lái)說(shuō),命名塑造了我們的思維模型。
「改變一個(gè)已經(jīng)形成的思維模型是相當(dāng)困難的工作,這不一定是在代碼方面,而是關(guān)于我們思考問(wèn)題方式的方面?!?/span>
因此,投入時(shí)間去精心挑選一個(gè)能夠精確描述 API 功能的名稱是非常值得的。
作為一個(gè)作家的基本工具 —— 字典和詞典 —— 在 API 命名過(guò)程中也能提供幫助。如果你發(fā)現(xiàn)某個(gè)名字特別難以確定,這可能意味著它嘗試同時(shí)承擔(dān)太多的職責(zé)。就像需要將過(guò)于復(fù)雜的句子分割成更簡(jiǎn)單的句子一樣,當(dāng)一個(gè)模塊過(guò)于復(fù)雜時(shí),也應(yīng)該考慮將其拆分。
要避免使用讓人費(fèi)解的縮寫(xiě),并且注意保持命名的一致性。比如,不應(yīng)該同時(shí)使用 getBasicSalary() 和 getBaseSalary() 這樣意義相同但命名不一的方法 —— 如果你的 API 中既有 remove() 又有 delete() 方法,使用者能夠清楚地知道它們之間的區(qū)別嗎?
使用的語(yǔ)言需要與組織或供應(yīng)商公開(kāi)的其他 API 保持一致性。這種對(duì)一致性的追求意味著,實(shí)施一定程度的集中化管理會(huì)很有幫助。
比如,一些大型企業(yè)會(huì)把高級(jí)技術(shù)寫(xiě)作人員的職責(zé)擴(kuò)展到幫助工程團(tuán)隊(duì)統(tǒng)一命名方法、屬性和字段。
如果你正在開(kāi)發(fā) REST 風(fēng)格的系統(tǒng),獨(dú)立咨詢師兼《掌握 API 架構(gòu)》一書(shū)的合作者 Daniel Bryant 建議參考已有的 API 指南集,這有助于在 API 的行為上實(shí)現(xiàn)一致性。對(duì)于基于 HTTP 的 API,他推薦考慮使用 OpenAPI,還有其他包括 Atlassian、Google 和 Microsoft 在內(nèi)的指南。
同時(shí),雖然所有 API 都需要恰當(dāng)?shù)拿?,但這些命名本身是特定于領(lǐng)域的;比如,為量化分析師編寫(xiě)的 API 與為零售商編寫(xiě)的 API 使用的語(yǔ)言會(huì)有很大不同。理想情況下,選用的術(shù)語(yǔ)應(yīng)與企業(yè)已經(jīng)使用并至少理解的術(shù)語(yǔ)匹配。
為此,Bryant 在對(duì) The New Stack 的講述中提到,最佳做法是進(jìn)行用戶研究,確保覆蓋所有潛在的 API 使用群體。
「QA 團(tuán)隊(duì)成員與開(kāi)發(fā)者對(duì)于你的 API 應(yīng)如何運(yùn)作會(huì)有不同的看法,」他說(shuō)?!肝医?jīng)常見(jiàn)到開(kāi)發(fā)者在沒(méi)有詢問(wèn)誰(shuí)會(huì)使用它的情況下設(shè)計(jì) API,結(jié)果暴露了內(nèi)部的領(lǐng)域模型?!?/span>
他推薦從「待完成的工作」(Jobs-to-be-Done)的角度來(lái)考慮,比如:你的關(guān)鍵任務(wù)是什么?你的工作流是怎樣的?你是如何處理它的?你希望如何處理它?最后一個(gè)問(wèn)題至關(guān)重要,因?yàn)閲@已建立的流程可能會(huì)形成慣性。
「如果你能簡(jiǎn)化復(fù)雜事物,你就有可能顛覆人們的世界觀,隨著系統(tǒng)的演進(jìn),通常會(huì)出現(xiàn)很好的機(jī)會(huì)」Bryant說(shuō)。
2、最小意外原則
你的 API 也應(yīng)該符合其所用編程語(yǔ)言的慣常用法,并尊重該語(yǔ)言的工作機(jī)制。例如,如果 API 要和 Java 配合使用,就應(yīng)該通過(guò)拋出異常來(lái)處理錯(cuò)誤,而不是像在 C 語(yǔ)言中那樣返回錯(cuò)誤代碼。
API 應(yīng)遵循最小意外原則。這一原則部分通過(guò)對(duì)稱性實(shí)現(xiàn);比如說(shuō),如果你需要添加和刪除方法,這些操作應(yīng)該在適當(dāng)?shù)牡胤奖灰恢碌貙?shí)施。
一個(gè)優(yōu)秀的 API 應(yīng)該僅包含少數(shù)幾個(gè)概念;在學(xué)習(xí)它時(shí),不應(yīng)被迫學(xué)習(xí)太多內(nèi)容。這并不特指方法、類或參數(shù)的數(shù)量,而是指 API 所涵蓋的概念范圍。理想情況下,一個(gè) API 應(yīng)該只專注于完成一個(gè)任務(wù)。
也最好避免無(wú)謂地添加任何元素。「不確定時(shí)就不要添加,」Bloch 這樣建議。你通??梢栽谛枰獣r(shí)向 API 中添加某些內(nèi)容,但一旦 API 被公開(kāi),就無(wú)法再移除其中的任何部分。
如之前所述,你的 API 需要隨時(shí)間發(fā)展,因此設(shè)計(jì)的一個(gè)關(guān)鍵方面是,在后續(xù)過(guò)程中能夠進(jìn)行更改而不破壞整體結(jié)構(gòu)。
「歸根到底,關(guān)鍵在于 API 應(yīng)該反映現(xiàn)實(shí),」Richardson表示?!咐?,如果一個(gè)人可以有多個(gè)地址或電話號(hào)碼,即便你目前只關(guān)注一個(gè),也不應(yīng)該僅允許存在一個(gè)地址。忽略現(xiàn)實(shí)最終總會(huì)帶來(lái)問(wèn)題。」
3、API 的粘性
Richardson 指出,僅實(shí)施你當(dāng)前需要的 API 的一部分是一個(gè)常見(jiàn)的錯(cuò)誤模式。這種做法的風(fēng)險(xiǎn)在于,你可能沒(méi)有徹底思考 API 的設(shè)計(jì),最終導(dǎo)致在其他場(chǎng)景下不可用的結(jié)果。
「API 設(shè)計(jì)需要比任何其他事情投入更多的思考,」Richardson 說(shuō),「因?yàn)橐坏┙ǔ?,你就無(wú)法再對(duì)其進(jìn)行更改。」
第二個(gè)問(wèn)題涉及到封裝和實(shí)現(xiàn)細(xì)節(jié)的泄露。
「一旦實(shí)現(xiàn)細(xì)節(jié)泄露,你就無(wú)法更改它,」Richardson表示?!敢虼?,你需要考慮,這里進(jìn)行的操作是什么?這個(gè)數(shù)據(jù)結(jié)構(gòu)的真實(shí)含義是什么?」
錯(cuò)誤處理通常是被忽略的一個(gè)領(lǐng)域。比如,如果你使用數(shù)據(jù)庫(kù)作為后端存儲(chǔ),就不應(yīng)該讓 SQL 錯(cuò)誤直接暴露出來(lái),因?yàn)槿绻阋院笙敫拇鎯?chǔ)機(jī)制,這樣做就會(huì)遇到障礙。
就像軟件開(kāi)發(fā)的任何其他方面一樣,認(rèn)為你可以孤立地把自己鎖在一個(gè)房間里獨(dú)立完成 API 的開(kāi)發(fā)是一個(gè)錯(cuò)誤。這樣做,你可能會(huì)過(guò)于堅(jiān)持自己的設(shè)計(jì),即便設(shè)計(jì)存在問(wèn)題。最好是像對(duì)待任何其他系統(tǒng)一樣,頻繁地與合作方一起測(cè)試你的想法。
在開(kāi)始編碼 API 之前,編寫(xiě)一個(gè)簡(jiǎn)短的規(guī)格說(shuō)明書(shū),向合作方展示它將做什么以及如何工作是個(gè)不錯(cuò)的主意。規(guī)格說(shuō)明書(shū)保持簡(jiǎn)短,這樣可以增加被閱讀的可能性,并防止你一開(kāi)始就過(guò)于投入你的方案。如果你花費(fèi)幾個(gè)月時(shí)間編寫(xiě)了一個(gè) 100 頁(yè)的規(guī)格說(shuō)明書(shū),你就很難承認(rèn)它可能并不那么優(yōu)秀。
文檔是被極度低估的一方面,這不僅適用于 API 設(shè)計(jì),在整個(gè)計(jì)算機(jī)科學(xué)領(lǐng)域都是如此。技術(shù)文檔編寫(xiě)者經(jīng)常被低估和低薪,而文檔最多被當(dāng)作事后的補(bǔ)充,這種情況常被「代碼即文檔」這一危險(xiǎn)的觀點(diǎn)所體現(xiàn)。
雖然你希望你的 API 易于理解和學(xué)習(xí),但它的文檔仍極為重要。它應(yīng)當(dāng)是完整而全面的,至少包含每個(gè)方法的用途、每個(gè)字段的作用以及可能的錯(cuò)誤條件。
「你希望它能列出所有可能返回的錯(cuò)誤代碼及其對(duì)應(yīng)的情況」
Richardson 強(qiáng)調(diào)。
投入時(shí)間來(lái)打磨和修正文檔,避免諸如使用不容易理解的縮寫(xiě)這樣的常見(jiàn)錯(cuò)誤。
在開(kāi)發(fā)過(guò)程中,繼續(xù)根據(jù) API 編寫(xiě)示例代碼。Bloch 提到,許多開(kāi)發(fā)者在開(kāi)發(fā)過(guò)程往往半途而廢,但是如果在整個(gè)實(shí)施過(guò)程中持續(xù)對(duì) API 進(jìn)行編碼,你將能夠真實(shí)地感受到它的工作時(shí)機(jī)和方式。
「這些代碼不是無(wú)用功,」Bloch強(qiáng)調(diào),「因?yàn)樗粌H幫助你打造出更優(yōu)秀的產(chǎn)品,還提供了一套可供其他程序員學(xué)習(xí)的范例。」
這些示例極為關(guān)鍵,因?yàn)樗鼈儗⒈黄渌_(kāi)發(fā)者不斷地復(fù)制使用,從而根本性地影響 API 的使用方式。