自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

沒有被了解的API?一個(gè)老碼農(nóng)眼中的API世界

安全 應(yīng)用安全
即便做了20多年的軟件開發(fā),仍然發(fā)現(xiàn)自己經(jīng)常會(huì)低估完成一個(gè)特定的編程任務(wù)所需要的時(shí)間。有時(shí),錯(cuò)誤的時(shí)間表是由于自己的能力不足造成的: 當(dāng)深入研究一個(gè)問題時(shí),會(huì)發(fā)現(xiàn)它比最初想象的要難得多,因此解決這個(gè)問題需要更長的時(shí)間ーー這就是程序員的生活。

即便做了20多年的軟件開發(fā),仍然發(fā)現(xiàn)自己經(jīng)常會(huì)低估完成一個(gè)特定的編程任務(wù)所需要的時(shí)間。有時(shí),錯(cuò)誤的時(shí)間表是由于自己的能力不足造成的:當(dāng)深入研究一個(gè)問題時(shí),會(huì)發(fā)現(xiàn)它比最初想象的要難得多,因此解決這個(gè)問題需要更長的時(shí)間ーー這就是程序員的生活。

[[321951]]

即使自己清楚地知道想要實(shí)現(xiàn)什么以及如何實(shí)現(xiàn)它,還會(huì)經(jīng)常比預(yù)期的要花費(fèi)更多的時(shí)間,這種情況往往因?yàn)榕cAPI的糾纏。

目錄

  • 1 無所不在,API 的空間視角
  • 2 良好與糟糕,API 的真面目
  • 3 API 設(shè)計(jì)的經(jīng)驗(yàn)性原則
  • 3.1 功能的完整性
  • 3.2 調(diào)用的簡(jiǎn)單性
  • 3.3 設(shè)計(jì)的場(chǎng)景化
  • 3.4 有無策略性的設(shè)置
  • 3.5 面向用戶的設(shè)計(jì)
  • 3.6 推卸責(zé)任源于無知
  • 3.7 清晰的文檔化
  • 3.8 API的人體工程學(xué)
  • 4 性能約定,API的時(shí)間視角
  • 4.1 API的性能分類
  • 4.2 按性能規(guī)劃API
  • 4.3 API的性能變化
  • 4.4 API調(diào)用失敗時(shí)的性能
  • 5 確保API 性能的經(jīng)驗(yàn)性方法
  • 5.1 謹(jǐn)慎地選擇API 和程序結(jié)構(gòu)
  • 5.2 在新版本發(fā)布時(shí)提供一致的性能約定
  • 5.3 防御性編程可以提供幫助
  • 5.4 API 公開的參數(shù)調(diào)優(yōu)
  • 5.5 測(cè)量性能以驗(yàn)證假設(shè)
  • 5.6 使用日志檢測(cè)和記錄異常
  • 6 面對(duì)API,開發(fā)者的苦惱
  • 6.1 沒有 API
  • 6.2 繁瑣的注冊(cè)
  • 6.3 多收費(fèi)的API
  • 6.4 隱藏 API 文檔
  • 6.5 糟糕的私有協(xié)議
  • 6.6 單一的 API 密鑰
  • 6.7 手動(dòng)維護(hù)文檔
  • 6.8 忽略運(yùn)維環(huán)境
  • 6.9 非冪等性
  • 7 API 設(shè)計(jì)中的文化認(rèn)知
  • 7.1 API意識(shí)的訓(xùn)練
  • 7.2 API設(shè)計(jì)人才的流失
  • 7.3 開放與控制

個(gè)人認(rèn)為,現(xiàn)在所普遍使用的API 與二十年前C語言編寫的API 并沒有本質(zhì)的不同。關(guān)于API的設(shè)計(jì),似乎有些難以捉摸的東西。

API(Application Programming Interface,應(yīng)用編程接口)是一些預(yù)先定義的函數(shù),或軟件系統(tǒng)不同組成部分之間的銜接約定。API 提供了基于軟件或硬件得以訪問一組例程的能力,而無需使用源代碼,也無需理解其內(nèi)部的工作機(jī)制。

1 無所不在,API 的空間視角

一看到API,很多人首先想到的是Restful API,基于HTTP協(xié)議的網(wǎng)絡(luò)編程接口。實(shí)際上, API的概念外延很大,從微觀到宏觀,會(huì)發(fā)現(xiàn)API在計(jì)算機(jī)的世界里無處不在。

拿起顯微鏡,如果Rest API 面向的是網(wǎng)絡(luò)通信,可以想把空間限制在單機(jī)上。一臺(tái)主機(jī)上的IPC同樣由各種各樣的API組成,而一切代碼的執(zhí)行都會(huì)歸結(jié)到系統(tǒng)調(diào)用上來,操作系統(tǒng)提供的系統(tǒng)調(diào)用同樣是API。走進(jìn)操作系統(tǒng),走進(jìn)函數(shù)指針的API,調(diào)整顯微鏡的鏡頭,API 可能體現(xiàn)在Jump 指令上,在深入就會(huì)進(jìn)入電路與系統(tǒng)的領(lǐng)域了。

抬起望遠(yuǎn)鏡,感謝通信與網(wǎng)絡(luò)技術(shù)的發(fā)展,一切軟件都幾乎演變成了分布式系統(tǒng)。API 成為了分布式系統(tǒng)中的血管和關(guān)節(jié),Restful API 只是 RPC的一種。節(jié)點(diǎn)內(nèi)子系統(tǒng)之間的API通信往往形成了東西流量,節(jié)點(diǎn)間子系統(tǒng)之間的API通信形成了南北流量。調(diào)整顯微鏡的鏡頭,這個(gè)系統(tǒng)通過開放平臺(tái)提供了API,逐漸形成了生態(tài)系統(tǒng)。生態(tài)系統(tǒng)間的異構(gòu)API正在隨著網(wǎng)絡(luò)世界的延伸而形成新的世界。

[[321952]]

從空間的視角來看,計(jì)算機(jī)的世界幾乎就是通過API連接的世界。

2 良好與糟糕,API 的面目

好的 API給我們帶來樂趣,幾乎可以忽略他們的存在,它們能給對(duì)一個(gè)特定任務(wù)在合理的時(shí)間內(nèi)完成,可以很容易地被發(fā)現(xiàn)和記憶,有良好的文檔記錄,有一個(gè)直觀的界面使用,并能夠正確處理邊界條件。

然而,對(duì)于每一種正確設(shè)計(jì) API 的方法,通常都有幾十種不正確的設(shè)計(jì)方法。簡(jiǎn)單地說,創(chuàng)建一個(gè)糟糕的 API 非常容易,而創(chuàng)建一個(gè)好的 API 則比較困難。即使是很小很簡(jiǎn)單的設(shè)計(jì)缺陷也有被夸大的傾向,因?yàn)?API會(huì)被多次調(diào)用。設(shè)計(jì)缺陷會(huì)導(dǎo)致代碼笨拙或效率低下,那么在調(diào)用 API 的每個(gè)點(diǎn)都會(huì)出現(xiàn)由此產(chǎn)生的問題。此外,一個(gè)設(shè)計(jì)缺陷在孤立的情況下是微小的,但通過相互作用的方式具有驚人的破壞性,并迅速導(dǎo)致大量的間接傷害。

[[321953]]

對(duì)于糟糕的 API 設(shè)計(jì),后果眾多且嚴(yán)重,難于編程,通常需要編寫額外的代碼。如果沒有別的原因,這些額外的代碼會(huì)使程序變大,效率更低,因?yàn)槊恳恍胁槐匾拇a都會(huì)增加工作集合的大小,并可能減少 CPU 緩存的命中率。此外,糟糕的API還可能會(huì)強(qiáng)制進(jìn)行不必要的數(shù)據(jù)復(fù)制,或者對(duì)預(yù)期的結(jié)果拋出異常,從本質(zhì)上產(chǎn)生效率低下的代碼。糟糕的API更難理解,也更難使用。面對(duì)糟糕的API,程序員編寫代碼的時(shí)間會(huì)更長,直接導(dǎo)致了開發(fā)成本的增加。

舉個(gè)例子,如果一個(gè)設(shè)計(jì)不當(dāng)?shù)?API 會(huì)導(dǎo)致Microsoft PowerPoint常常崩潰的話,那可能會(huì)有海量用戶受到影響。類似地,筆者見過的大量安全漏洞都是由標(biāo)準(zhǔn) c 庫(如 strcpy)中不安全的IO操作或者字符串操作造成的。這些設(shè)計(jì)缺陷的API,造成的累積成本很容易達(dá)到數(shù)十億資金。

從開發(fā)的視角看API的話,大多數(shù)軟件開發(fā)都是關(guān)于不同層次的抽象,而API是這些抽象的可見接口。抽象減少了復(fù)雜性, 應(yīng)用程序調(diào)用較高級(jí)別的庫,通常再調(diào)用較低級(jí)別的庫提供的服務(wù)來實(shí)現(xiàn),而較低級(jí)別的庫又調(diào)用操作系統(tǒng)的系統(tǒng)調(diào)用接口提供的服務(wù)。這種抽象層次結(jié)構(gòu)非常強(qiáng)大,沒有它,咱們所知道的軟件就可能不存在,程序員會(huì)被復(fù)雜性完全壓垮。

有悖常理的是,抽象層常常被用來淡化糟糕 API 的影響: “這不重要,我們可以編寫一個(gè)API來隱藏問題。” 這種說法大錯(cuò)特錯(cuò),首先,就內(nèi)存和執(zhí)行速度而言,即使是效率最高的API封裝也會(huì)增加一些成本,其次,本來就是設(shè)計(jì)良好的API的份內(nèi)工作, 通常情況下,API 封裝會(huì)產(chǎn)生一系列的問題。因此,盡管API的封裝可以是糟糕的API可用,這并不意味著這個(gè)糟糕的API無關(guān)緊要,這里沒有“負(fù)負(fù)得正”,不必要的API封裝只會(huì)導(dǎo)致軟件更加臃腫。

3 API 設(shè)計(jì)的經(jīng)驗(yàn)性原則

有些時(shí)候,自己新的認(rèn)知可能只是別人的常識(shí)。在設(shè)計(jì) API 的時(shí)候,有一些經(jīng)驗(yàn)性原則可以使用。很遺憾。仍然無法提煉到方法論的高度。

3.1 功能的完整性

API要提供完整的功能,這似乎是顯而易見的,提供不足功能的 API 是不完整的。在設(shè)計(jì)過程中仔細(xì)檢查一個(gè)功能清單,不斷地問自己: “是否有遺漏呢?”

3.2 調(diào)用的簡(jiǎn)單性

API 使用的類型、函數(shù)和參數(shù)越少,學(xué)習(xí)、記憶和正確使用就越容易。許多 API最終成為了助手函數(shù)的組合器,C+ + 標(biāo)準(zhǔn)字符串類及其超過100個(gè)的成員函數(shù)就是一個(gè)例子。在使用 C + + 編程多年之后,自己仍然無法在不查閱使用手冊(cè)的情況下使用標(biāo)準(zhǔn)字符串來處理任何重要的事情。

為了很好地設(shè)計(jì) API,設(shè)計(jì)人員必須了解使用 API 的環(huán)境,并對(duì)該環(huán)境進(jìn)行設(shè)計(jì)。是否提供一個(gè)非基本的便利函數(shù)取決于設(shè)計(jì)者預(yù)期這個(gè)API被使用的頻率。如果頻繁使用,就值得添加。對(duì) API 向下兼容的擔(dān)憂隨著時(shí)間的推移而侵蝕 API , API 累積起來最終造成的損害比它保持向后兼容所帶來的好處還要大。

3.3 設(shè)計(jì)的場(chǎng)景化

考慮一個(gè)類,它提供了對(duì)一組字符串鍵值對(duì)的訪問,比如環(huán)境變量:

  1. class KVPairs { public string lookup(string name); // ... } 

lookup方法提供了對(duì)命名變量值的訪問。顯然,如果設(shè)置了具有給定名稱的變量,函數(shù)將返回其值。但是,如果沒有設(shè)置變量,函數(shù)應(yīng)該如何運(yùn)行?可能有幾種選擇:

  • 拋出 VariableNotSet 異常
  • 返回 null
  • 返回空字符串

如果預(yù)期查找一個(gè)不存在的變量不是一個(gè)常見的情況,并且可能視為一個(gè)錯(cuò)誤,那么拋出異常是適當(dāng)?shù)?。異常?huì)迫使調(diào)用方處理錯(cuò)誤。另一種情況,調(diào)用者可能查找一個(gè)變量,如果沒有則替換一個(gè)默認(rèn)值。如果是這樣的話,拋出異常完全是錯(cuò)誤的,因?yàn)樘幚懋惓?huì)破壞正常的控制流,而且比測(cè)試 null 或空返回值更困難。

假設(shè)如果沒有設(shè)置變量時(shí)不拋出異常,有兩個(gè)方式表明查找失敗: 返回 null 或空字符串。哪一個(gè)是更合適呢?同樣,答案取決于預(yù)期的場(chǎng)景用例。返回 null 允許調(diào)用者區(qū)分沒有設(shè)置的變量和設(shè)置為空字符串的變量,而返回未設(shè)置的變量的空字符串使得不可能區(qū)分從未設(shè)置的變量和顯式設(shè)置為空字符串的變量。如果認(rèn)為能夠進(jìn)行這種區(qū)分很重要,那么返回 null 是必要的; 但是,如果這種區(qū)分不重要,那么最好返回空字符串并且永遠(yuǎn)不返回 null。

3.4 有無策略性的設(shè)置

API 設(shè)置策略的程度對(duì)其可用性有著深遠(yuǎn)的影響 ,只有當(dāng)調(diào)用者對(duì) API 的使用與設(shè)計(jì)者預(yù)期的場(chǎng)景相一致時(shí),API 才能最優(yōu)地執(zhí)行。如果對(duì)將要使用的 API 場(chǎng)景知之甚少,那么只能保持所有選項(xiàng)的開放性,并允許 API 盡可能廣泛地應(yīng)用。

實(shí)際上,什么應(yīng)該是錯(cuò)誤和什么不應(yīng)該是錯(cuò)誤之間的界限非常細(xì)微,而且錯(cuò)誤地快速放置這個(gè)界限會(huì)導(dǎo)致更大的痛苦。對(duì) API 的背景了解得越多,它可以制定的策略就越多。這樣做對(duì)調(diào)用方有利,因?yàn)樗东@了錯(cuò)誤,否則就無法檢測(cè)到這些錯(cuò)誤。通過仔細(xì)設(shè)計(jì)類型和參數(shù),通??梢栽诰幾g時(shí)捕獲錯(cuò)誤,而不是延遲到運(yùn)行時(shí)。在編譯時(shí)捕獲的每個(gè)錯(cuò)誤都減少了一個(gè)錯(cuò)誤,這個(gè)錯(cuò)誤可能會(huì)在測(cè)試期間或生產(chǎn)環(huán)境中產(chǎn)生額外的成本。

通常情況下,人們對(duì)上下文知之甚少,因?yàn)?API 是低級(jí)的,或者就其本質(zhì)而言,必須在許多不同的上下文中工作。在這種情況下,策略模式往往可以用來取得良好的效果。它允許調(diào)用者提供策略,從而保持設(shè)計(jì)的開放性。根據(jù)編程語言的不同,調(diào)用方提供的策略可以使用回調(diào)、虛函數(shù)、代理或模板等來實(shí)現(xiàn)。如果 API 提供了合理的缺省值,那么這種外部化的策略可以在不損害可用性和清晰性的情況下帶來更大的靈活性。

3.5 面向用戶的設(shè)計(jì)

程序員很容易進(jìn)入解決問題的模式: 需要什么數(shù)據(jù)結(jié)構(gòu)和算法來完成這項(xiàng)工作,需要什么輸入和輸出來完成這項(xiàng)工作?實(shí)現(xiàn)者專注于解決問題,而調(diào)用者的關(guān)注點(diǎn)很快被忘記。

獲得可用 API 的一個(gè)好方法是讓調(diào)用者編寫函數(shù)名,并將該API簽名交給程序員來實(shí)現(xiàn)。僅這一步就至少消除了一半糟糕的API,如果API的實(shí)現(xiàn)者從不使用他們自己的API,這對(duì)可用性會(huì)造成災(zāi)難性的后果。此外,API 與編程、數(shù)據(jù)結(jié)構(gòu)或算法無關(guān),API 與 GUI 一樣,也是一個(gè)用戶界面。使用 API 的用戶是一個(gè)程序員,也就是一個(gè)人。盡管傾向于認(rèn)為API是機(jī)器接口,但它們不是: 它們是人機(jī)接口。

驅(qū)動(dòng) api 設(shè)計(jì)的不是實(shí)現(xiàn)者的需求,這意味著好的 api 是根據(jù)調(diào)用者的需求設(shè)計(jì)的,即使這會(huì)使實(shí)現(xiàn)者的工作變得更加復(fù)雜。

3.6 不推卸責(zé)任

“推卸責(zé)任”的一種方式是害怕設(shè)置策略: “好吧,調(diào)用者可能想要這樣或那樣做,但我不能確定是哪個(gè),所以我會(huì)設(shè)置它。” 這種方法的典型結(jié)果是采用五個(gè)或十個(gè)參數(shù)的函數(shù)。因?yàn)樵O(shè)計(jì)者沒有設(shè)置策略,也不清楚 API 應(yīng)該做什么和不應(yīng)該做什么,所以 API 最終的復(fù)雜性遠(yuǎn)遠(yuǎn)超過了必要的程度。一個(gè)好的 API 很清楚它想要實(shí)現(xiàn)什么和不想要實(shí)現(xiàn)什么,并且不害怕預(yù)先了解它。由此產(chǎn)生的簡(jiǎn)單性通??梢詮浹a(bǔ)功能的輕微損失,特別是如果 API 具有良好的基本操作,可以很容易地組合成更復(fù)雜的操作。

另一種推卸責(zé)任的方式是犧牲可用性來提高效率。性能增益是一種錯(cuò)覺,因?yàn)樗拐{(diào)用者“干臟活” ,而不是由 API 執(zhí)行。換句話說,可以以零運(yùn)行時(shí)開銷提供更安全的 API。通過僅對(duì) API 內(nèi)部完成的工作進(jìn)行基準(zhǔn)測(cè)試,而不是由調(diào)用方和 API 共同完成的任務(wù) ,設(shè)計(jì)人員可以聲稱已經(jīng)創(chuàng)建了性能更好的 API,是缺乏價(jià)值的。

即便是內(nèi)核也不是沒有瑕疵,并且偶爾會(huì)推卸責(zé)任。某些系統(tǒng)調(diào)用會(huì)被中斷,迫使程序員明確處理并手動(dòng)重啟被中斷的系統(tǒng)調(diào)用,而不是讓內(nèi)核透明地處理。

推卸責(zé)任可以采取許多不同的形式,各種 API 的細(xì)節(jié)差別很大。對(duì)于設(shè)計(jì)者來說,關(guān)鍵的問題是: 有沒有什么可以合理地為調(diào)用者做的事情是我沒有做的?如果有,是否有正當(dāng)理由不這樣做?明確地提出這些問題使得設(shè)計(jì)成為一個(gè)有意識(shí)的過程,而不是“偶然的設(shè)計(jì)”。

3.7 清晰的文檔化

API 文檔的一個(gè)大問題是,它通常是在 API 實(shí)現(xiàn)之后編寫的,而且通常是由實(shí)現(xiàn)者編寫的。然而,實(shí)現(xiàn)者被實(shí)現(xiàn)所污染,并且傾向于簡(jiǎn)單地寫下所做的事情。這通常會(huì)導(dǎo)致文檔不完整,因?yàn)閷?shí)現(xiàn)人員對(duì) API 太熟悉了,并且假設(shè)有些事情是顯而易見的,而實(shí)際上并非如此。更糟糕的是,它經(jīng)常導(dǎo)致API完全忽略重要的用例。另一方面,如果調(diào)用者編寫文檔,調(diào)用者可以從用戶的角度處理問題,不受當(dāng)前實(shí)現(xiàn)的影響。這使得 API 更有可能滿足調(diào)用者的需求,并防止許多設(shè)計(jì)缺陷的出現(xiàn)。

最不適合編寫文檔的人是API的實(shí)現(xiàn)者,最不適合編寫文檔的時(shí)間是在實(shí)現(xiàn)之后。這樣做會(huì)增加接口、實(shí)現(xiàn)和文檔都出現(xiàn)問題的可能性。

確保文檔是完整的,特別是關(guān)于錯(cuò)誤處理的文檔。當(dāng)事情出錯(cuò)時(shí),API 的行為是其中的一部分,也是事情進(jìn)展順利時(shí)的一部分。文檔是否說明 API 是否維護(hù)異常?是否詳細(xì)說明了在出錯(cuò)情況下輸出和輸入輸出參數(shù)的狀態(tài)?是否詳細(xì)說明了在錯(cuò)誤發(fā)生后可能存在的任何副作用?是否為調(diào)用者提供了足夠的信息來理解錯(cuò)誤?程序員確實(shí)需要知道當(dāng)出現(xiàn)錯(cuò)誤時(shí) API 的行為,并且確實(shí)需要獲得詳細(xì)的錯(cuò)誤信息,以便通過編程方式進(jìn)行處理。

單元測(cè)試和系統(tǒng)測(cè)試對(duì) API也有影響,因?yàn)樗鼈兛梢园l(fā)現(xiàn)以前沒有人想到的東西。測(cè)試結(jié)果可以幫助改進(jìn)文檔,從而改進(jìn) API,文檔是 API 的一部分。

3.8 API的人體工程學(xué)

人體工程學(xué)本身就是一個(gè)研究領(lǐng)域,也可能是 API 設(shè)計(jì)中最難確定的部分之一。關(guān)于這個(gè)主題,已經(jīng)有了很多內(nèi)容,例如定義命名約定、代碼布局、文檔樣式等。除了單純的時(shí)尚問題,符合人體工程學(xué)的實(shí)現(xiàn)良好是困難的,因?yàn)樗岢隽藦?fù)雜的認(rèn)知和心理問題。程序員是人,所以一個(gè)程序員認(rèn)為很好的 API 可能被另一個(gè)程序員認(rèn)為是一般的。

特別是對(duì)于大型和復(fù)雜的 api,人機(jī)工程學(xué)涉及到一致性的問題。例如,如果一個(gè) API 總是以相同的順序放置特定類型的參數(shù),那么它就更容易使用。類似地,如果API建立命名規(guī)則,將相關(guān)函數(shù)與特定的命名風(fēng)格組合在一起,那么就更容易使用。同時(shí), API 為相關(guān)任務(wù)建立簡(jiǎn)單統(tǒng)一的約定并使用統(tǒng)一的錯(cuò)誤處理。

一致性不僅使事物更容易使用和記憶,而且還可以轉(zhuǎn)移學(xué)習(xí)。轉(zhuǎn)移學(xué)習(xí)不僅在API內(nèi)部很重要,而且在API 之間也很重要。API之間可以采用的概念越多,就越容易掌握所有的概念。實(shí)際上,即便是Unix 中的標(biāo)準(zhǔn)IO庫也在許多地方違背了這一思想。例如,read ()和 write ()的系統(tǒng)調(diào)用將文件描述符放在第一個(gè)參數(shù)位置,但是標(biāo)準(zhǔn)庫中如 fgets ()和 fputs () ,將流指針放在最后,而 fscanf ()和 fprintf () 又將流指針放在第一個(gè)位置。這時(shí)候,往往要感謝IDE的代碼補(bǔ)全功能了。

[[321954]]

4 性能約定,API的時(shí)間視角

在 API 中調(diào)用函數(shù)時(shí),當(dāng)然期望它們能夠正確工作,這種期望可以被稱為調(diào)用方和實(shí)現(xiàn)之間的約定。同時(shí),調(diào)用者對(duì)這些功能也有性能期望,軟件系統(tǒng)的成功通常也取決于滿足這些期望的 API。因此,除了正確性約定之外,還有性能約定。履行合同通常是隱含的,常常是模糊的,有時(shí)是被違反的(由調(diào)用者或執(zhí)行者)。如何改進(jìn)這方面的 API 設(shè)計(jì)和文檔?

當(dāng)今任何重要的軟件系統(tǒng)都依賴于其他人的工作,通過API調(diào)用操作系統(tǒng)和各種軟件包中的函數(shù),從而減少了必須編寫的代碼量。在某些情況下,要把工作外包給遠(yuǎn)程服務(wù)器,這些服務(wù)器通過網(wǎng)絡(luò)與你連接。我們既依賴于這些函數(shù)和服務(wù)來實(shí)現(xiàn)正確的操作,也依賴于它們的執(zhí)行性能以保證整個(gè)系統(tǒng)的性能。在涉及分頁、網(wǎng)絡(luò)延遲、共享資源(如磁盤)等的復(fù)雜系統(tǒng)中,性能必然會(huì)有變化。然而,即使是在簡(jiǎn)單的設(shè)置中,比如在內(nèi)存中包含所有程序和數(shù)據(jù)的獨(dú)立計(jì)算機(jī)中,當(dāng)一個(gè) API 或操作系統(tǒng)達(dá)不到性能預(yù)期時(shí),也是令人沮喪的。

人們習(xí)慣于談?wù)搼?yīng)用程序和 API 實(shí)現(xiàn)之間的功能約定。雖然如今的 API 規(guī)范并沒有以一種導(dǎo)致正確性證明的方式明確規(guī)定正確性的標(biāo)準(zhǔn),但是 API 函數(shù)的類型聲明和文本文檔力求對(duì)其邏輯行為毫不含糊。然而,API 函數(shù)的意義遠(yuǎn)不止正確性。它消耗什么資源,速度有多快?人們常常根據(jù)自己對(duì)某個(gè)函數(shù)的實(shí)現(xiàn)應(yīng)該是什么的判斷做出假設(shè)。遺憾的是,API 文檔沒有提示哪些函數(shù)有性能保證,哪些函數(shù)實(shí)際上代價(jià)高昂。

更復(fù)雜的是,當(dāng)應(yīng)用程序調(diào)整到 API 的性能特征之后,一個(gè)新版本的 API 實(shí)現(xiàn)或者一個(gè)新的遠(yuǎn)程存儲(chǔ)服務(wù)卻削減了軟件系統(tǒng)的整體性能。簡(jiǎn)而言之,從時(shí)間的視角來看,API的性能契約值得更多關(guān)注。

4.1 API的性能分類

為了實(shí)用有效,從計(jì)算復(fù)雜度來看,可以對(duì)API的性能做一個(gè)簡(jiǎn)單的分類。

恒定的性能

例如 toupper, isdigit, java.util.HashMap.get等。前兩個(gè)函數(shù)總是計(jì)算廉價(jià)的,通常是內(nèi)聯(lián)表查找。正確大小的哈希表查找應(yīng)該也很快,但是哈希沖突可能會(huì)偶爾減慢訪問的速度。

通常的性能

例如fgetc, java.util.HashMap.put等。許多函數(shù)被設(shè)計(jì)成大多數(shù)時(shí)候都很快,但是偶爾需要調(diào)用更復(fù)雜的代碼; fgetc 必須偶爾讀取一個(gè)新的字符緩沖區(qū)。在哈希表中存儲(chǔ)一條新數(shù)據(jù)可能會(huì)使該表變滿,以至于會(huì)重對(duì)表中所有條目進(jìn)行哈希計(jì)算。

java.util.HashMap 在性能約定方面有一個(gè)很好的描述: “這個(gè)實(shí)現(xiàn)為基本操作(get 和 put)提供了常量時(shí)間性能,假設(shè)散列函數(shù)將元素正確地分散在存儲(chǔ)桶中。對(duì)集合視圖的迭代,需要與 HashMap ‘容量‘成比例的時(shí)間...... “。fgetc 的性能取決于底層流的屬性。如果是磁盤文件,那么該函數(shù)通常將從用戶內(nèi)存緩沖區(qū)讀取,而不需要系統(tǒng)調(diào)用,但它必須偶爾調(diào)用操作系統(tǒng)來讀取新的緩沖區(qū)。如果是從鍵盤讀取輸入,那么實(shí)現(xiàn)可能會(huì)調(diào)用操作系統(tǒng)來讀取每個(gè)字符。

程序員建立性能模型是基于經(jīng)驗(yàn),而不是規(guī)范,并非所有函數(shù)都有明顯的性能屬性。

可預(yù)期的性能

例如 qsort, regexec等。這些函數(shù)的性能隨其參數(shù)的屬性(例如,要排序數(shù)組的大小或要搜索的字符串的長度)而變化。這些函數(shù)通常是數(shù)據(jù)結(jié)構(gòu)或公共算法實(shí)用程序,使用眾所周知的算法,不需要系統(tǒng)調(diào)用。

我們通常可以根據(jù)對(duì)底層算法的期望來判斷性能(例如,排序?qū)⒒ㄙM(fèi) nlogn 的時(shí)間)。當(dāng)使用復(fù)雜的數(shù)據(jù)結(jié)構(gòu)(例如 B 樹)或通用集合(在這些地方可能很難確定底層的具體實(shí)現(xiàn))時(shí),可能更難估計(jì)性能。重要的是,可預(yù)測(cè)性只是可能的; regexec 基于它的輸入通常是可預(yù)測(cè)的,但是有一些病態(tài)的表達(dá)會(huì)導(dǎo)致復(fù)雜計(jì)算的爆發(fā)。

未知的性能

例如fopen, fseek, pthread_create,很多初始化的函數(shù)以及所有網(wǎng)絡(luò)調(diào)用。這些函數(shù)的性能常常有很大的差異。它們從池(線程、內(nèi)存、磁盤、操作系統(tǒng)對(duì)象)分配資源,通常需要對(duì)共享操作系統(tǒng)或 IO資源的獨(dú)占訪問。通常需要大量的初始化工作, 通過網(wǎng)絡(luò)進(jìn)行調(diào)用相對(duì)于本地訪問總是慢的,這使得合理性能模型的形成變得更加困難。

線程庫是性能問題的簡(jiǎn)單標(biāo)志。Posix 標(biāo)準(zhǔn)花了很多年才穩(wěn)定下來,然而如今仍然被性能問題所困擾。線程應(yīng)用程序的可移植性仍然存在風(fēng)險(xiǎn),原因是線程需要與操作系統(tǒng)緊密集成,幾乎所有操作系統(tǒng)(包括 Unix 和 Linux)最初設(shè)計(jì)時(shí)都沒有考慮到線程; 線程與其他庫的交互,為了使線程安全而導(dǎo)致的性能問題等等。

4.2 按性能劃分API

有些庫提供了執(zhí)行一個(gè)函數(shù)的多種方法,通常是因?yàn)檫@些方法的性能差別很大。

大多數(shù)程序員被告知使用庫函數(shù)來獲取每個(gè)字符并不是最快的方法,更注重性能的人會(huì)讀取一個(gè)大型的字符數(shù)組,并使用編程語言中的數(shù)組或指針來操作提取每個(gè)字符。在極端情況下,應(yīng)用程序可以將文件頁映射到內(nèi)存頁,以避免將數(shù)據(jù)復(fù)制到數(shù)組中。作為提高性能的副作用是,這些函數(shù)給調(diào)用方帶來了更大的負(fù)擔(dān)。例如,獲得緩沖區(qū)算法的正確性,調(diào)用 fseek需要調(diào)整緩沖區(qū)指針和可能的內(nèi)容。

程序員總是被建議避免在程序中過早地進(jìn)行優(yōu)化,從而推遲修訂,直到更簡(jiǎn)單的做法被證明是不滿足要求的。確定性能的唯一方法是測(cè)量。程序員通常在編寫完整個(gè)程序之后,才會(huì)面對(duì)性能期望與所交付的實(shí)現(xiàn)之間的不匹配。

4.3 API的性能變化

可預(yù)測(cè)函數(shù)的性能可以根據(jù)其參數(shù)的屬性進(jìn)行估計(jì),未知函數(shù)的性能功能也可能因要求它們做什么而有很大的不同。在存儲(chǔ)設(shè)備上打開流所需的時(shí)間當(dāng)然取決于底層設(shè)備的訪問時(shí)間,或許還取決于數(shù)據(jù)傳輸?shù)乃俾?。通過網(wǎng)絡(luò)協(xié)議訪問的存儲(chǔ)可能特別昂貴,而且它是變化的。

由于各種原因,一般的函數(shù)隨著時(shí)間的推移變得越來越強(qiáng)大。I/O流就是一個(gè)很好的例子,根據(jù)打開的流類型(本地磁盤文件、網(wǎng)絡(luò)服務(wù)文件、管道、網(wǎng)絡(luò)流、內(nèi)存中的字符串等) ,調(diào)用打開流在庫和操作系統(tǒng)中調(diào)用不一樣的代碼。隨著IO設(shè)備和文件類型范圍的擴(kuò)展,性能的差異只會(huì)增加。大多數(shù)API的共同生命周期是隨著時(shí)間的推移逐步增加功能,從而不可避免地增加了性能變化。

一個(gè)很大的變化來源是不同平臺(tái)的庫接口之間的差異。當(dāng)然,平臺(tái)的底層速度(硬件和操作系統(tǒng))會(huì)有所不同,但是庫接口可能會(huì)導(dǎo)致 API 內(nèi)函數(shù)的性能或 API 間的性能變化。有些庫(例如那些用于處理線程的庫)的移植性能差異非常大。線程異??赡芤詷O端行為的形式出現(xiàn)ーー極其緩慢的應(yīng)用程序甚至是死鎖。

這些差異是難以建立精確的API性能約定的原因之一。我們往往不需要非常精確地了解性能,但是預(yù)期行為的極端變化可能會(huì)導(dǎo)致問題。例如,使用 malloc ()函數(shù)的動(dòng)態(tài)內(nèi)存分配可以被描述為“通常的性能” ,這將是錯(cuò)誤的,因?yàn)閮?nèi)存分配(尤其是 malloc)是程序員開始尋找性能問題時(shí)的首要嫌疑之一。作為性能直覺的一部分,如果調(diào)用 malloc 數(shù)以萬計(jì)次,特別是為了分配小的固定大小的塊,最好使用 malloc 分配一個(gè)更大的內(nèi)存塊,將其分割成固定大小的塊,并管理自己的空閑塊列表。Malloc 的實(shí)現(xiàn)多年來一直在努力讓它變得高效,提供虛擬內(nèi)存、線程和非常大的內(nèi)存的系統(tǒng)都對(duì)malloc 和free構(gòu)成了挑戰(zhàn),必須權(quán)衡某些使用模式(如內(nèi)存碎片)的效率和弊端。

一些軟件系統(tǒng),如Java,使用自動(dòng)內(nèi)存分配和垃圾收集來管理空閑存儲(chǔ)。雖然這是一個(gè)很大的便利,但是一個(gè)關(guān)心性能的程序員必須意識(shí)到成本。例如,一個(gè) Java 程序員應(yīng)該盡早被告知 String 對(duì)象和 StringBuffer 對(duì)象之間的區(qū)別,String 對(duì)象只能通過在新內(nèi)存中創(chuàng)建一個(gè)新的副本來修改,而 StringBuffer 對(duì)象包含容納字符串可以延長的空間。隨著垃圾收集系統(tǒng)的改進(jìn),它們使得不可預(yù)知的垃圾收集暫停變得不那么常見; 這可能會(huì)讓程序員自滿,相信自動(dòng)回收內(nèi)存永遠(yuǎn)不會(huì)成為性能問題,而實(shí)際上這就是一個(gè)性能問題。

4.4 API調(diào)用失敗時(shí)的性能

API的規(guī)范包括了調(diào)用失敗時(shí)的行為。返回錯(cuò)誤代碼和拋出異常是告訴調(diào)用方函數(shù)未成功的常用方法。但是,與正常行為的規(guī)范一樣,沒有指定故障的性能。主要有以下是三種形式:

快速失敗。一個(gè)API調(diào)用很快就失敗了,和它的正常行為一樣快或者更快。例如,調(diào)用 sqrt (- 1)會(huì)很快失敗。即使當(dāng)一個(gè) malloc 調(diào)用因?yàn)闆]有更多的內(nèi)存可用而失敗時(shí),這個(gè)調(diào)用也應(yīng)該像任何 malloc 調(diào)用一樣快速地返回,因?yàn)楹笳弑仨殢牟僮飨到y(tǒng)請(qǐng)求更多的內(nèi)存。為了讀取一個(gè)不存在的磁盤文件而打開一個(gè)流的調(diào)用很可能與成功調(diào)用返回的速度一樣快。

慢慢失敗。有時(shí),一個(gè)API調(diào)用失敗的速度非常慢,以至于應(yīng)用程序可能希望以其他方式進(jìn)行。例如,打開到另一臺(tái)計(jì)算機(jī)的網(wǎng)絡(luò)連接請(qǐng)求只有在幾次長時(shí)間超時(shí)后才會(huì)失敗。

  • 永遠(yuǎn)失敗。有時(shí)候一個(gè)API調(diào)用只是暫停,根本不允許應(yīng)用程序繼續(xù)運(yùn)行。例如,其實(shí)現(xiàn)等待從未釋放的同步鎖的調(diào)用可能永遠(yuǎn)不會(huì)返回。
  • 對(duì)于失敗性能的直覺很少像對(duì)于正常性能的直覺那樣好。原因很簡(jiǎn)單,編寫、調(diào)試和調(diào)優(yōu)程序時(shí)處理故障事件的經(jīng)驗(yàn)遠(yuǎn)遠(yuǎn)少于處理普通事件。另一個(gè)原因是,API調(diào)用可能在許多方面出現(xiàn)故障,其中一些是致命的,而且并非所有的故障都在 API 規(guī)范中有所描述。即使是旨在更精確地描述錯(cuò)誤處理的異常機(jī)制,也不能使所有可能的異常都可見。此外,隨著庫函數(shù)的增加,失敗的機(jī)會(huì)也在增加。例如,包裝網(wǎng)絡(luò)服務(wù)的API(ODBC、 JDBC、 UPnP等等)從本質(zhì)上訂閱了大量的網(wǎng)絡(luò)故障機(jī)制。
  • 一個(gè)勤奮的程序員會(huì)盡可能處理不可能的失敗。一種常見的技術(shù)是用 try... catch 塊包圍程序的大部分,這些塊可以重試失敗的整個(gè)部分。交互式程序可以嘗試保存用戶的工作,捕獲周圍的整個(gè)程序,其效果是減輕失敗的主程序造成的損失,例如保存在一個(gè)磁盤文件,關(guān)鍵日志或數(shù)據(jù)結(jié)構(gòu)等等。

處理暫?;蛩梨i的唯一方法可能是設(shè)置一個(gè)watchdog線程,該線程期望定期檢查一個(gè)正常運(yùn)行的應(yīng)用程序,如果健康檢查異常,watchdog就會(huì)采取行動(dòng),例如,保存狀態(tài)、中止主線程和重新啟動(dòng)整個(gè)應(yīng)用程序等。如果一個(gè)交互式程序通過調(diào)用可能緩慢失敗的函數(shù)來響應(yīng)用戶的命令,它可能會(huì)使用watchdog終止整個(gè)命令,并返回到允許用戶繼續(xù)執(zhí)行其他命令的已知狀態(tài),這就產(chǎn)生了一種防御性的編程風(fēng)格。

5 確保API 性能的經(jīng)驗(yàn)性方法

程序員根據(jù)對(duì) API 性能的期望選擇 API、數(shù)據(jù)結(jié)構(gòu)和整個(gè)程序結(jié)構(gòu)。如果預(yù)期或性能嚴(yán)重錯(cuò)誤,程序員不能僅僅通過調(diào)優(yōu) API 調(diào)用來恢復(fù),而必須重寫程序(可能是主要部分)。前面提到的交互式程序的防御結(jié)構(gòu)是另一個(gè)例子。

當(dāng)然,有許多程序的結(jié)構(gòu)和性能很少受到庫性能的影響(科學(xué)計(jì)算和大型模擬通常屬于這一類)。然而,今天的許多“常規(guī) IT” ,特別是遍及基于 web 的服務(wù)的軟件,廣泛使用了對(duì)整體性能至關(guān)重要的庫。

即使性能上的微小變化也會(huì)導(dǎo)致用戶對(duì)程序的感知發(fā)生重大變化,在處理各種媒體的節(jié)目中尤其如此。偶爾放棄視頻流的幀可能是可接受的 ,但是用戶可以感知到音頻中哪怕是輕微的中斷,因此音頻媒體性能的微小變化可能會(huì)對(duì)整個(gè)節(jié)目的可接受性產(chǎn)生重大影響。這種擔(dān)憂引起了人們對(duì)服務(wù)質(zhì)量的極大興趣,在許多方面,服務(wù)質(zhì)量是為了確保高水平的業(yè)績。

盡管違反性能契約的情況很少,而且很少是災(zāi)難性的,但在使用軟件庫時(shí)注意性能可以幫助構(gòu)建更健壯的軟件。以下是一些經(jīng)驗(yàn)性原則:

5.1 謹(jǐn)慎地選擇API 和程序結(jié)構(gòu)

如果有幸從頭開始編寫一個(gè)程序,那么在開始編寫程序時(shí),要考慮一下性能約定的含義。如果這個(gè)程序一開始只是一個(gè)原型,然后在服務(wù)中保持一段時(shí)間,那么毫無疑問它至少會(huì)被重寫一次; 重寫是一個(gè)重新思考 API 和結(jié)構(gòu)選擇的機(jī)會(huì)。

5.2 在新版本發(fā)布時(shí)提供一致的性能約定

一個(gè)新的實(shí)驗(yàn)性 API 會(huì)吸引那些開始衍生 API 性能模型的用戶。此后,更改性能約定肯定會(huì)激怒開發(fā)人員,并可能導(dǎo)致他們重寫自己的程序。一旦 API 成熟,性能約定不變就很重要了。事實(shí)上,大多數(shù)通用 API (例如 libc)之所以變得如此,部分原因在于它們的性能約定在 API 發(fā)展過程中是穩(wěn)定的。同樣的道理也適用于 api 端口

人們可能希望 API 提供者能夠定期測(cè)試新版本,以驗(yàn)證它們沒有引入性能怪癖。不幸的是,這樣的測(cè)試很少進(jìn)行。但是,這并不意味著不能對(duì)依賴的 API 部分進(jìn)行自己的測(cè)試。使用分析器,通??梢园l(fā)現(xiàn)程序依賴于少量的API。編寫一個(gè)性能測(cè)試工具,將一個(gè)API的新版本與早期版本的記錄性能進(jìn)行比較,這樣可以給程序員提供一個(gè)早期預(yù)警警,即隨著API新版本的發(fā)布,他們自己代碼的性能將發(fā)生變化。

許多程序員希望計(jì)算機(jī)及其軟件能夠一致地隨著時(shí)間的推移而變得更快。這實(shí)際上對(duì)于供應(yīng)商來說是很難保證的,但是它們會(huì)讓客戶相信是這樣的。許多程序員希望圖形庫、驅(qū)動(dòng)程序和硬件的新版本能夠提高所有圖形應(yīng)用程序的性能,并熱衷于多種功能的改進(jìn),這通常會(huì)降低舊功能的性能,哪怕只是輕微的降低。

我們還可以希望 API 規(guī)范將性能約定明確化,這樣使用、修改或移植代碼的人就可以遵守約定。注意,函數(shù)對(duì)動(dòng)態(tài)內(nèi)存分配的使用,無論是隱式的還是自動(dòng)的,都應(yīng)該是API文檔的一部分。

5.3 防御性編程可以提供幫助

在調(diào)用性能未知或高度可變的 API 時(shí),程序員可以使用特殊的方式,對(duì)于考慮故障性能的情況尤其如此。我們可以將初始化移到性能關(guān)鍵區(qū)域之外,并嘗試預(yù)加載API 可能使用的任何緩存數(shù)據(jù)(例如字體)。表現(xiàn)出大量性能差異或擁有大量內(nèi)部緩存數(shù)據(jù)的 API ,可以通過提供幫助函數(shù)將關(guān)于如何分配或初始化這些結(jié)構(gòu)的提示從應(yīng)用程序傳遞給 API。某個(gè)程序偶爾會(huì)向服務(wù)器發(fā)出 ping 信號(hào),這可以建立一個(gè)可能不可用的服務(wù)器列表,從而避免一些長時(shí)間的故障暫停。

5.4 API 公開的參數(shù)調(diào)優(yōu)

有些庫提供了影響性能的明確方法(例如,控制分配給文件的緩沖區(qū)的大小、表的初始大小或緩存的大小),操作系統(tǒng)還提供了調(diào)優(yōu)選項(xiàng)。調(diào)整這些參數(shù)可以在性能約定的范圍內(nèi)提高性能,調(diào)優(yōu)不能解決總體問題,但可以減少嵌入在庫中的固定選項(xiàng),這些選項(xiàng)會(huì)嚴(yán)重影響性能。

有些庫提供了具有相同語義函數(shù)的替代實(shí)現(xiàn),通常是通用API的具體實(shí)現(xiàn)形式。通過選擇最好的具體實(shí)現(xiàn)進(jìn)行調(diào)優(yōu)通常非常容易,Java 集合就是這種結(jié)構(gòu)的一個(gè)很好的例子。

越來越多的API被設(shè)計(jì)成動(dòng)態(tài)地適應(yīng)應(yīng)用,使程序員無需選擇最佳的參數(shù)設(shè)置。如果一個(gè)散列表太滿,它會(huì)自動(dòng)擴(kuò)展和重新哈希(這是一種優(yōu)點(diǎn),但偶爾會(huì)降低性能)。如果文件是按順序讀取的,那么可以分配更多的緩沖區(qū),以便在更大的塊中讀取。

5.5 測(cè)量性能以驗(yàn)證假設(shè)

常見方式是檢測(cè)關(guān)鍵數(shù)據(jù)結(jié)構(gòu),以確定每個(gè)結(jié)構(gòu)是否正確使用。例如,可以測(cè)量哈希表的完整程度或發(fā)生哈希沖突的頻率?;蛘?,可以驗(yàn)證一個(gè)以寫性能為代價(jià)的快速讀取結(jié)構(gòu)實(shí)際上被讀取的次數(shù)多于被寫入的次數(shù)。

添加足夠的工具來準(zhǔn)確地度量許多 API 調(diào)用的性能是困難的,這需要大量的工作,而且可能投入產(chǎn)出比較低。然而,在那些對(duì)應(yīng)用程序的性能至關(guān)重要的 API 調(diào)用上添加工具(假設(shè)能夠識(shí)別它們,并且正確的識(shí)別) ,就可以在出現(xiàn)問題時(shí)節(jié)省大量時(shí)間。注意,這些代碼中的大部分可以在新版本的性能監(jiān)視器中重用。

所有這些都不是為了阻止完美主義者開發(fā)自動(dòng)化儀表盤和測(cè)量的工具,或者開發(fā)詳細(xì)說明性能約定的方法,以便性能測(cè)量能夠建立對(duì)性能約定的遵守。這些目標(biāo)并不容易實(shí)現(xiàn),回報(bào)可能也不會(huì)很大。

通??梢栽诓皇孪葯z測(cè)軟件的情況下進(jìn)行性能度量,優(yōu)點(diǎn)是在出現(xiàn)需要跟蹤的問題之前不需要任何工作還可以幫助診斷當(dāng)修改代碼或庫影響性能時(shí)出現(xiàn)的問題。定期進(jìn)行概要分析,從可信賴的基礎(chǔ)上衡量性能偏差。

5.6 使用日志檢測(cè)和記錄異常

當(dāng)分布式服務(wù)組成一個(gè)復(fù)雜的系統(tǒng)時(shí),會(huì)出現(xiàn)越來越多的違反性能約定的行為。注意,通過網(wǎng)絡(luò)接口提供的服務(wù)有時(shí)具有指定可接受性能的SLA。在許多配置中,度量過程偶爾會(huì)發(fā)出服務(wù)請(qǐng)求,以檢查 SLA 是否滿足 。由于這些服務(wù)使用類似于 API 調(diào)用的方法(例如,遠(yuǎn)程過程調(diào)用或其變體,如 XML-RPC、 SOAP 或 REST),因此可能是有性能約定的期望。應(yīng)用程序會(huì)檢測(cè)這些服務(wù)的失敗,并且通常會(huì)應(yīng)對(duì)得當(dāng)。

然而,響應(yīng)緩慢,特別是當(dāng)有許多這樣的服務(wù)互相依賴時(shí),可能會(huì)非??斓仄茐南到y(tǒng)性能。如果這些服務(wù)的客戶能夠記錄他們所期望的性能,并生成有助于診斷問題的日志(這就是 syslog 的用途之一) ,那將會(huì)很有幫助。當(dāng)文件備份看起來不合理的慢,那是不是比昨天慢呢?比最新的操作系統(tǒng)軟件更新之前還要慢?考慮到多臺(tái)計(jì)算機(jī)可能共享的備份設(shè)備,它是否比預(yù)期的要慢?或者是否有一些合理的解釋(例如,備份系統(tǒng)發(fā)現(xiàn)一個(gè)損壞的數(shù)據(jù)結(jié)構(gòu)并開始一個(gè)長的過程來重新構(gòu)建它) ?

[[321955]]

在沒有源代碼,也沒有構(gòu)成組合的模塊和API的細(xì)節(jié)的情況下,診斷不透明軟件組合中的性能問題可以在報(bào)告性能和發(fā)現(xiàn)問題方面發(fā)揮作用。雖然不能在軟件內(nèi)部解決性能問題 ,但是可以對(duì)操作系統(tǒng)和網(wǎng)絡(luò)進(jìn)行調(diào)整或修復(fù)。如果備份設(shè)備由于磁盤幾乎已滿而速度較慢,那么肯定可以添加更多的磁盤空間。好的日志和相關(guān)的工具會(huì)有所幫助; 遺憾的是,日志在計(jì)算機(jī)系統(tǒng)演進(jìn)中可能是一個(gè)被低估和忽視的領(lǐng)域。

誠然,性能約定沒有功能正確性約定那么重要,但是軟件系統(tǒng)的重要用戶體驗(yàn)幾乎都取決于它。

6 面對(duì)API,開發(fā)者的苦惱

對(duì)于向外部提供的API,有一些因素成為了開發(fā)者的苦惱。

6.1 沒有 API

API 允許客戶實(shí)現(xiàn)你沒有想到的功能,允許客戶更多的使用產(chǎn)品。如果存在一個(gè)API,開發(fā)者可以自動(dòng)使用API的產(chǎn)品,這將產(chǎn)生更多的應(yīng)用。他們可以自動(dòng)化整個(gè)公司的配置,可以基于你的 API 構(gòu)建全新的應(yīng)用程序。只要想想他們能夠通過 API 使用多少產(chǎn)品就可以了。

6.2 繁瑣的注冊(cè)

只有復(fù)雜的注冊(cè)過程才能保證API的安全么?實(shí)際上只是自尋煩惱。要么使整個(gè)過程完全自助服務(wù),要么根本不需要任何類型的注冊(cè)過程,這樣才是良好的API使用開端

6.3 多收費(fèi)的API

對(duì)服務(wù)收費(fèi)是正常的,或者只在“企業(yè)版”中包含 API 訪問 。讓 API 訪問變得如此昂貴,以至于銷售部門認(rèn)為 API 代表額外的利潤激勵(lì)。事實(shí)上,API 不應(yīng)該成為一種收入來源,而是一種鼓勵(lì)人們使用產(chǎn)品的方式。

6.4 隱藏 API 文檔

沒有什么比在搜索引擎中看不到API 文檔更糟糕的事了。那些將API文檔放在登錄之后的體驗(yàn)屏幕后面,可以認(rèn)為是設(shè)計(jì)人員的大腦短路。通過某種注冊(cè)或登錄來阻止競(jìng)爭(zhēng)對(duì)手查看API 并從中學(xué)習(xí),這是一種幼稚的想法。

6.5 糟糕的私有協(xié)議

一個(gè)私有協(xié)議可能很難理解,也不可能調(diào)試。SOAP可能會(huì)變得臃腫和過于冗長,從而導(dǎo)致帶寬消耗和速度減慢。它也是基于 XML 的,尤其是在移動(dòng)或嵌入式客戶端上,解析和操作起來非常昂貴。許多 API 使用 JSON API 或 JSON-rpc ,它們是輕量級(jí)的,易于使用,易于調(diào)試。

6.6 單一的 API 密鑰

如果只允許使用一個(gè) API 密鑰,相當(dāng)于創(chuàng)建了一個(gè)“第22條軍規(guī)”的情況。開發(fā)者無法更改服務(wù)器上的 API 密鑰,因?yàn)榭蛻舳艘矔?huì)在更新之前失去了訪問權(quán)限。他們也不能首先更改客戶端,因?yàn)榉?wù)器還不知道新的 API 密鑰。如果有多個(gè)客戶端,那基本上就是一場(chǎng)災(zāi)難。

API密鑰基本上就是用于識(shí)別和驗(yàn)證客戶的密碼。也許是密鑰泄露,也許某個(gè)員工離開了公司帶走了鑰匙,或許每年輪換密鑰是安全策略的一部分,最終,開發(fā)者都將需要更改他們的 API 密鑰。

6.7 手動(dòng)維護(hù)文檔

隨著 API 的發(fā)展,API 和文檔有可能脫離同步。一個(gè)錯(cuò)誤的API說明會(huì)導(dǎo)致一個(gè)無法工作的系統(tǒng),會(huì)令人極其的沮喪。一個(gè)與文檔不同步的 API,會(huì)讓人束手無策。

6.8 忽略運(yùn)維環(huán)境

將基礎(chǔ)設(shè)施視為代碼的能力正在成為運(yùn)維團(tuán)隊(duì)的要事。它不僅使操作更容易、更可測(cè)試和更可靠,而且還為諸如支付行業(yè)所要求的安全性最佳實(shí)踐鋪平了道路。如果忽略了Ansible, Chef, Puppet等類似的系統(tǒng),可能會(huì)導(dǎo)致一系列令人困惑的不兼容選項(xiàng),使得生產(chǎn)環(huán)境的API調(diào)用難以為繼。

6.9 非冪等性

假設(shè)有一個(gè)創(chuàng)建虛擬機(jī)的 API 調(diào)用。如果這個(gè) API 調(diào)用是冪等的,那么我們第一次調(diào)用它的時(shí)候就創(chuàng)建了 VM。第二次調(diào)用它時(shí),系統(tǒng)檢測(cè)到 VM 已經(jīng)存在,并簡(jiǎn)單地返回,沒有錯(cuò)誤。如果這個(gè) API 調(diào)用是非冪等的,那么調(diào)用10次就會(huì)創(chuàng)建10個(gè) vm。

為什么有人要多次調(diào)用同一個(gè) API?在處理 rpc時(shí),響應(yīng)可能是成功、失敗或根本沒有應(yīng)答。如果沒有收到服務(wù)器的回復(fù),則必須重試請(qǐng)求。使用冪等協(xié)議,可以簡(jiǎn)單地重發(fā)請(qǐng)求。對(duì)于非冪等協(xié)議,每個(gè)操作后都必須跟隨發(fā)現(xiàn)當(dāng)前狀態(tài)并進(jìn)行正確恢復(fù)的代碼,將所有恢復(fù)邏輯放在客戶機(jī)中是一種丑陋的設(shè)計(jì)。將此邏輯放在客戶機(jī)庫中可以確??蛻舳诵枰l繁的更新,要求用戶實(shí)現(xiàn)恢復(fù)邏輯是令人難受的。

當(dāng) API 是冪等的時(shí)候,這些問題就會(huì)減少或消除。

如果網(wǎng)絡(luò)是不可靠的,那么網(wǎng)絡(luò) API 本質(zhì)上也是不可靠的。請(qǐng)求可能在發(fā)送到服務(wù)器的途中丟失,而且永遠(yuǎn)不會(huì)執(zhí)行。執(zhí)行可能已經(jīng)完成,但是回復(fù)的信息已經(jīng)丟失了。服務(wù)器可能在操作期間重新啟動(dòng)??蛻舳丝赡茉诎l(fā)送請(qǐng)求時(shí)重新啟動(dòng),在等待請(qǐng)求時(shí)重新啟動(dòng),或者在接收請(qǐng)求時(shí)重新啟動(dòng),在本地狀態(tài)存儲(chǔ)到數(shù)據(jù)庫之前重新啟動(dòng)。在分布式計(jì)算中,一切都有可能失敗。

程序員們以做好工作和完善的系統(tǒng)給用戶留下深刻印象而自豪,令開發(fā)者苦惱的API往往出于無知、缺乏資源或者不可能的最后期限。

[[321956]]

7 API 設(shè)計(jì)中的文化認(rèn)知

如果讓API 的設(shè)計(jì)可以做得更好的話,除了一些細(xì)節(jié)性的技術(shù)問題外,還可能需要解決一些文化問題。我們不僅需要技術(shù)智慧,還需要改變我們認(rèn)識(shí)和實(shí)踐軟件工程的方式。

7.1 API的有意識(shí)訓(xùn)練

自己念書的時(shí)候,程序員的培訓(xùn)主要側(cè)重于數(shù)據(jù)結(jié)構(gòu)和算法。這意味著一個(gè)稱職的程序員必須知道如何編寫各種數(shù)據(jù)結(jié)構(gòu)并有效地操作它們。隨著開源運(yùn)動(dòng)的發(fā)展,這一切都發(fā)生了巨大的變化。如今,幾乎所有可以想象到的可重用功能都可以使用開放源碼。因此,創(chuàng)建軟件的過程發(fā)生了很大的變化,今天的軟件工程不是創(chuàng)建功能,而是集成現(xiàn)有的功能或者以某種方式重新封它。換句話說,現(xiàn)在的 API 設(shè)計(jì)比20年前更加重要,不僅擁有了更多的 API,而且這些 API 提供了比以前更加豐富且復(fù)雜的功能。

從來沒有人費(fèi)心去解釋如何決定某個(gè)值應(yīng)該是返回值還是輸出參數(shù),如何在引發(fā)異常和返回錯(cuò)誤代碼之間做出選擇,或者如何決定一個(gè)函數(shù)修改它的參數(shù)是否合適。所以,期望程序員擅長一些他們從未學(xué)過的東西是不合理的。

然而,好的 API 設(shè)計(jì),即使很復(fù)雜,也是可以訓(xùn)練的,關(guān)鍵是認(rèn)識(shí)到重要性并有意識(shí)的訓(xùn)練。

7.2 API設(shè)計(jì)人才的流失

一個(gè)老碼農(nóng)環(huán)顧四周,才發(fā)現(xiàn)周圍是多么的不尋常: 所有的編程同事都比我年輕,當(dāng)自己以前的同事或者同學(xué),大多數(shù)人不再寫代碼; 他們轉(zhuǎn)到了不同的崗位比如各種經(jīng)理、總監(jiān)、CXO ,或者完全離開了這個(gè)行業(yè)。這種趨勢(shì)在軟件行業(yè)隨處可見: 年長的程序員很少,通常是因?yàn)榭床坏铰殬I(yè)生涯。如果不進(jìn)入管理層,未來的加薪幾乎是不可能的。

一種觀點(diǎn)認(rèn)為,年長程序員的職業(yè)優(yōu)勢(shì)在不斷喪失。這種想法可能是錯(cuò)誤的: 年長的程序員可能不會(huì)像年輕人那樣熬夜,但這并不是因?yàn)樗麄兡昙o(jì)大,而是因?yàn)楹芏鄷r(shí)候他們不用熬夜就能完成工作。

老程序員的流失是不幸的,特別是在 API 設(shè)計(jì)方面。雖然好的 API 設(shè)計(jì)是可以學(xué)習(xí)的,但是經(jīng)驗(yàn)是無法替代的。需要時(shí)間和不斷的挖坑填坑才會(huì)做得更好。不幸的是,這個(gè)行業(yè)的趨勢(shì)恰恰是把最有經(jīng)驗(yàn)的人從編程中提拔出來。

另一個(gè)趨勢(shì)是公司將最好的程序員提升為設(shè)計(jì)師或系統(tǒng)架構(gòu)師。通常情況下,這些程序員作為顧問外包給各種各樣的項(xiàng)目,目的是確保項(xiàng)目在正確的軌道上起步,避免在沒有顧問智慧的情況下犯錯(cuò)誤。這種做法的意圖值得稱贊,但其結(jié)果通常是發(fā)人深省的: 顧問從來沒有經(jīng)歷過自己的設(shè)計(jì)決策的后果,這是對(duì)設(shè)計(jì)的一種嘲諷。讓設(shè)計(jì)師保持敏銳和務(wù)實(shí)的方法是讓他們吃自己的狗糧, 剝奪設(shè)計(jì)師反饋的過程最終可能注定失敗。

7.3 開放與控制

隨著計(jì)算的重要性不斷增長,有一些API的正確功能幾乎是無法描述的。例如, Unix 系統(tǒng)調(diào)用接口、 C標(biāo)準(zhǔn)庫、 Win32或 OpenSSL。這些 API的接口或語義的任何改變都會(huì)帶來巨大的經(jīng)濟(jì)成本,并可能引發(fā)漏洞。允許單個(gè)公司在沒有外部控制的情況下對(duì)如此關(guān)鍵的API進(jìn)行更改是不負(fù)責(zé)任的。嚴(yán)格的立法控制和更開放的同行審查相結(jié)合,在兩者之間找到恰當(dāng)?shù)钠胶鈱?duì)于計(jì)算機(jī)的未來和網(wǎng)絡(luò)經(jīng)濟(jì)至關(guān)重要。

[[321957]]

API設(shè)計(jì)確實(shí)很重要,因?yàn)檎麄€(gè)計(jì)算機(jī)世界都是由API連接的。然而,證明實(shí)現(xiàn)良好API所需的投入產(chǎn)出比可能是困難的,特別是當(dāng)一個(gè) API 沒有被客戶使用的時(shí)候。“當(dāng)幾乎沒有人使用我們的 API 時(shí),收益是多少? ” 產(chǎn)品經(jīng)理或者老板們經(jīng)??赡軙?huì)問這樣的問題。呵呵,也許你沒有做過這些事情,所以使用率很低。

【本文來自51CTO專欄作者“老曹”的原創(chuàng)文章,作者微信公眾號(hào):喔家ArchiSelf,id:wrieless-com】 

戳這里,看該作者更多好文

 

責(zé)任編輯:武曉燕 來源: 51CTO專欄
相關(guān)推薦

2024-01-15 15:11:03

物聯(lián)網(wǎng)5G數(shù)字孿生

2018-10-17 22:01:06

2017-09-18 08:21:42

碼農(nóng)AI人工智能

2018-01-16 15:02:20

存儲(chǔ)RAIDSAN

2016-12-02 08:54:18

Lambda代碼云計(jì)算

2013-02-20 09:46:39

軟件開發(fā)程序員

2015-05-12 10:15:15

程序員

2020-09-30 11:14:24

AI碼農(nóng)架構(gòu)

2023-07-16 22:34:55

2018-09-08 08:41:21

Python 3API框架API Star

2015-04-21 12:48:37

老碼農(nóng)技術(shù)理想

2023-08-27 21:07:02

2018-10-24 15:53:29

微服務(wù)后端JVM

2013-08-12 11:18:00

2020-12-17 10:20:27

碼農(nóng)高薪計(jì)算機(jī)

2019-08-15 08:58:55

銷售培訓(xùn)班碼農(nóng)

2013-07-01 11:01:22

API設(shè)計(jì)API

2020-08-05 12:27:18

Go語言碼農(nóng)

2016-12-01 14:16:18

GitSCM配置

2013-04-18 09:43:34

碼農(nóng)網(wǎng)站網(wǎng)站設(shè)計(jì)
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)