代碼審查關(guān)注什么:性能

在本系列代碼審查文章的第三篇,我們準(zhǔn)備討論在代碼審查中性能方面需要關(guān)注哪些事情。

和所有的架構(gòu)/設(shè)計(jì)一樣,一個(gè)系統(tǒng)非功能性的性能需求也應(yīng)該優(yōu)先考慮。不管你是在開發(fā)必須在幾個(gè)毫微秒內(nèi)響應(yīng)的低等待時(shí)間交易系統(tǒng)、還是在創(chuàng)建一個(gè)需要盡快響應(yīng)用戶的購(gòu)物網(wǎng)站,或者是在開發(fā)一款管理 “待辦事項(xiàng)”的移動(dòng)應(yīng)用,你都應(yīng)該有“太慢了”的概念。

我們一起來(lái)看看審查者在代碼審查在中應(yīng)該關(guān)注的影響性能的事情。

首先

這個(gè)功能有硬性的性能需求嗎?

被審查的代碼屬于有公開的 SLA領(lǐng)域嗎?或者說(shuō)需求描述中有單獨(dú)的性能方面的需求?

如果最初的需求是來(lái)自 bug 描述“登錄界面加載太慢”,開發(fā)者應(yīng)該已經(jīng)很清楚多長(zhǎng)的加載時(shí)間是合適的,否則審查者或作者怎么相信速度有明顯的提高?

如果是這樣,有測(cè)試證明達(dá)到這些標(biāo)準(zhǔn)了嗎?

任何性能關(guān)鍵系統(tǒng)都應(yīng)該有自動(dòng)化性能測(cè)試,保證達(dá)到公開的 SLAs (“所有的訂購(gòu)請(qǐng)求都應(yīng)該在10ms內(nèi)響應(yīng)”)標(biāo)準(zhǔn)。如果過(guò)沒有,你只有依靠用戶來(lái)投訴達(dá)不到你的 SLAs 標(biāo)準(zhǔn)。這不僅僅是糟糕的用戶體驗(yàn),還會(huì)導(dǎo)致不可避免的罰款和費(fèi)用。在上一篇文章中已經(jīng)詳細(xì)討論了審查測(cè)試。

bug 修復(fù)/新功能添加的代碼會(huì)對(duì)已有的性能測(cè)試帶來(lái)負(fù)面影響嗎?

如果你們定期運(yùn)行性測(cè)試(或者如果你們有一套性能測(cè)試程序可以按需運(yùn)行),確保新的代碼在性能標(biāo)準(zhǔn)方面沒有降低系統(tǒng)性能。這可能是一個(gè)自動(dòng)化的過(guò)程,但是在 CI 環(huán)境中運(yùn)行性能測(cè)試遠(yuǎn)不如運(yùn)行單元測(cè)試常見,所以在審查過(guò)程中將這一點(diǎn)提出來(lái)是很有必要的。

如果本次代碼評(píng)審沒有硬性的性能要求

那么花數(shù)小時(shí)時(shí)間極其痛苦的優(yōu)化代碼最終就節(jié)約了幾個(gè) CPU 周期就完全沒有必要。但是代碼審查者也需要確保代碼不要掉進(jìn)一些完全可以避免的性能問(wèn)題中。檢查列表的其他部分,看看是否可以輕易的阻止未來(lái)的性能問(wèn)題。

調(diào)用外部服務(wù)/應(yīng)用開銷很高

使用你自己應(yīng)用以外任何需要網(wǎng)絡(luò)的系統(tǒng)都會(huì)比一個(gè)糟糕的equals()方法消耗的時(shí)間要多的多。考慮:

調(diào)用數(shù)據(jù)庫(kù)

最糟糕的可能是像 ORM 這種隱藏在抽象后面的調(diào)用。但是在代碼審查中你應(yīng)該能夠發(fā)現(xiàn)普通的性能問(wèn)題根源,像在循環(huán)中單獨(dú)調(diào)用數(shù)據(jù)庫(kù)--例如,加載一個(gè) ID 列表,然后根據(jù) ID 為每一項(xiàng)單獨(dú)查詢數(shù)據(jù)庫(kù)。

例如,第19行也許看起來(lái)沒什么問(wèn)題,但是它處在一個(gè) for 循環(huán)中--你不知道這段代碼會(huì)調(diào)用多少次數(shù)據(jù)庫(kù)。

不必要的網(wǎng)絡(luò)訪問(wèn)

像數(shù)據(jù)庫(kù)一樣,遠(yuǎn)程服務(wù)常常也是因?yàn)槎鄠€(gè)遠(yuǎn)程調(diào)用而過(guò)度使用,其實(shí)一次調(diào)用可能就足夠了,或者批量訪問(wèn)或者緩存處理也許就能阻止昂貴的網(wǎng)絡(luò)調(diào)用。再次說(shuō)明,和數(shù)據(jù)庫(kù)一樣,有時(shí)一些封裝后的方法隱藏了其背后實(shí)際是調(diào)用一個(gè)遠(yuǎn)程 API。

移動(dòng)應(yīng)用/穿戴式應(yīng)用過(guò)多的調(diào)用后端服務(wù)

這其實(shí)和“不必要的網(wǎng)絡(luò)服務(wù)”是一樣的,但是在移動(dòng)設(shè)備上,調(diào)用后端接口不僅僅是性能問(wèn)題,還會(huì)消耗設(shè)備的電量。

高效的使用資源

就和怎么使用網(wǎng)絡(luò)資源一樣,審查者要檢查對(duì)其他資源的使用,確認(rèn)是否可能存在性能問(wèn)題。

代碼在訪問(wèn)共享資源時(shí)使用鎖了嗎?這會(huì)導(dǎo)致性能低下或者死鎖嗎?

鎖就是性能殺手,并且在多線程環(huán)境中很難找到原因。考慮這樣的模式:只有一個(gè)線程寫/修改值,其他線程只能讀取,或者使用 lock free 算法。

代碼中存在內(nèi)存泄漏的可能嗎?

在 Java 中,導(dǎo)致內(nèi)存泄漏的常見原因有:可變的靜態(tài)屬性,使用 ThreadLocal 和使用 ClassLoader。

內(nèi)存的應(yīng)用消耗有可能無(wú)限增長(zhǎng)嗎?

這一點(diǎn)和內(nèi)存泄漏不同--內(nèi)存泄漏是未使用的對(duì)象無(wú)法被回收。但是任何語(yǔ)言,即使是沒有使用垃圾回收機(jī)制的語(yǔ)言,都會(huì)創(chuàng)建出無(wú)限增長(zhǎng)的數(shù)據(jù)結(jié)構(gòu)。作為審查者,如果發(fā)現(xiàn)新的值持續(xù)被添加到一個(gè) list 或者 map,問(wèn)一問(wèn)這個(gè) list 或者 map 有沒有清空或者調(diào)整大小,什么時(shí)候清空或者調(diào)整。

在上面的代碼中,我們看到所有來(lái)自 Twitter 的消息都被添加到一個(gè) map 中。如果我們仔細(xì)檢查這個(gè)類,就會(huì)發(fā)現(xiàn) allTwitterUsres 這個(gè) map 從來(lái)就沒有刪減過(guò),TwitterUsers 這個(gè) list 也是一樣。這個(gè) map 會(huì)迅速變得很龐大,這取決于我們關(guān)注了多少用戶以及添加 tweets 的頻度。

代碼關(guān)閉連接/流了嗎?

寫代碼時(shí)很容易忘記關(guān)閉連接或者文件流/網(wǎng)絡(luò)流。當(dāng)你在審查其他人的代碼時(shí),不管它以什么語(yǔ)言實(shí)現(xiàn),如果有使用文件、網(wǎng)絡(luò)連接或者數(shù)據(jù)庫(kù)連接,請(qǐng)確保它已經(jīng)正確的關(guān)閉。

原作者很容易忽略這個(gè)問(wèn)題,就如上面的這段代碼,編譯不會(huì)有任何問(wèn)題。作為審查者,你應(yīng)該發(fā)現(xiàn)在函數(shù)退出之前,connection,statement和 Result set 都需要關(guān)閉。在 Java 7中,由于可以使用 try-with-resources 使得這一點(diǎn)更容易管理。下面的截圖展示了作者使用 try-with-resource 修改代碼后的結(jié)果。

資源池正確的配置了嗎?

一個(gè)環(huán)境可選的配置項(xiàng)依賴于很多因素,所以并不是審查者立刻能夠知道。例如數(shù)據(jù)庫(kù)連接池的大小是否配置正確。但是有些要點(diǎn)是你一眼就能發(fā)現(xiàn)的,例如池子是否太?。ㄈ绱笮?)或者太大(數(shù)百萬(wàn)線程)。很好的調(diào)節(jié)這些值需要一個(gè)盡可能真實(shí)的測(cè)試環(huán)境。但是在代碼審查中可以辨別的常見問(wèn)題是池子(例如線程池、連接池)太大。邏輯上說(shuō)當(dāng)然是越大越好,但是每一個(gè)對(duì)象都要占用資源,并且管理數(shù)千個(gè)項(xiàng)目的開銷要遠(yuǎn)遠(yuǎn)高于從它們當(dāng)中獲取的好處。如果有疑問(wèn),默認(rèn)值通常是不錯(cuò)的選擇。代碼配置如果和默認(rèn)值不一致,應(yīng)該有測(cè)試或者結(jié)論說(shuō)明設(shè)定的值更合理。

審查者很容易發(fā)現(xiàn)的警告信號(hào)

有一些代碼會(huì)引入潛在的性能問(wèn)題。這取決于所使用的代碼和庫(kù)。

反射

在 Java 中使用反射要比不使用反射慢。如果你正審查的代碼中使用了反射,問(wèn)一下是否真的需要。

上面的截屏展示了審查者在 Upsource 中點(diǎn)擊一個(gè)方法查看它來(lái)自哪里,你會(huì)看到這個(gè)方法返回了java.lang.reflect包里的某個(gè)類型,這應(yīng)該是一個(gè)警告信號(hào)。

超時(shí)

當(dāng)你在審查代碼時(shí),你也許不知道一個(gè)操作合理的超時(shí)是多少,但是你應(yīng)該考慮“當(dāng)超時(shí)在倒計(jì)時(shí)時(shí),對(duì)系統(tǒng)的其他方面會(huì)議什么影響?”。作為審查者你應(yīng)該考慮最壞情況-在5分鐘的超時(shí)時(shí)間內(nèi)系統(tǒng)會(huì)阻塞掉嗎?如果這個(gè)時(shí)間設(shè)置的是1秒最壞會(huì)發(fā)生什么情況?如果代碼的作者不能為說(shuō)明為什么選定超時(shí)的長(zhǎng)度,作為審查者你也不知道選定的超時(shí)長(zhǎng)度的優(yōu)缺點(diǎn),這個(gè)時(shí)候需要邀請(qǐng)知道這個(gè)超時(shí)影響的人來(lái)討論。不要等待用戶來(lái)投訴性能方面的問(wèn)題。

并行

代碼使用了多個(gè)線程來(lái)完成一個(gè)簡(jiǎn)單任務(wù)?使用多線程是添加了時(shí)間和復(fù)雜性而不是提高性能?在最新的 Java 中,這可能比直接創(chuàng)建一個(gè)線程更隱蔽:代碼中使用了 Java 8最新的并發(fā) stream 但是并沒有享受到并發(fā)的好處?例如:使用并發(fā) stream 處理少量的數(shù)據(jù),或者使用 stream 處理一個(gè)很簡(jiǎn)單的任務(wù),有可能比使用串行 stream 處理更慢。

在上面的例子中,新增加的 parallel 調(diào)用好像沒有給我嗎帶來(lái)任何好處--這里 stream 作用于一條 Tweet, 也就是最多140個(gè)字符的字符串。將并發(fā)應(yīng)用到這么短的字符上可能不會(huì)帶來(lái)任何性能的提高,相反將字符串分割到各個(gè)并發(fā)線程所付出的代價(jià)要比并發(fā)帶來(lái)的好處要高。

正確性

這些事情不一定會(huì)影響系統(tǒng)的性能,但是由于好多線程環(huán)境強(qiáng)相關(guān),所以它們和性能這個(gè)主題相關(guān)。

代碼中為多線程選擇了正確的數(shù)據(jù)結(jié)構(gòu)?

在上面的代碼中,在第12行使用了一個(gè) ArrayList來(lái)存儲(chǔ)所有的 sessions。但是這段代碼會(huì)被多個(gè)線程調(diào)用,尤其是 onOpen 方法,所以 sessions 應(yīng)該使用一個(gè)線程安全的數(shù)據(jù)結(jié)構(gòu)。在這個(gè)例子中,我們有很多選擇:可以使用 Vector,也可以使用 Collections.synchronizedList() 來(lái)創(chuàng)建一個(gè)線程安全的 List,但是最好的選擇可能是 CopyOnWriteArrayList, 因?yàn)檫@個(gè) list 被修改的頻率要遠(yuǎn)遠(yuǎn)低于被讀取的頻率。

代碼是否容易產(chǎn)生競(jìng)爭(zhēng)條件?

在多線程環(huán)境中,非常容易寫出導(dǎo)致競(jìng)爭(zhēng)條件的代碼。例如:

盡管遞增的代碼只有一行(第16行),很有可能會(huì)出現(xiàn)另外一個(gè)線程在當(dāng)前線程讀取并存儲(chǔ)新的 value 之間加1。作為審查者,需要注意是 getset 組合不是原子性的的情況。

正確的使用鎖了嗎?

這一條和競(jìng)爭(zhēng)條件相關(guān),作為審查者,你應(yīng)該確認(rèn)不允許多線程以可能沖突的方式修改值。這樣的代碼可能需要使用 synchronization、locks 或者 atomic variables 來(lái)控制對(duì)代碼塊的更改。

為代碼添加的性能測(cè)試是否有用?

因?yàn)楹苋菀讓懗龊懿畹臏y(cè)試代碼?;蛘邷y(cè)試用例中使用的數(shù)據(jù)和真實(shí)數(shù)據(jù)完全不一樣,這可能會(huì)給出誤導(dǎo)我們的結(jié)果。

緩存

使用緩存是阻止過(guò)多的外部請(qǐng)求的一種方式,同時(shí)它自身也會(huì)帶來(lái)問(wèn)題。如果你審查的代碼中使用了緩存,你應(yīng)該關(guān)注一些常見問(wèn)題,比如:緩存項(xiàng)目的有效處理不正確。

代碼級(jí)別的優(yōu)化

如果你是代碼審查者,同時(shí)又是開發(fā)者,接下來(lái)的這一節(jié)也許有你喜歡建議的優(yōu)化方法。作為一個(gè)團(tuán)隊(duì),你需要知道性能對(duì)你有多么重要,以及這些類別的優(yōu)化對(duì)你的代碼是否有用。

對(duì)大多數(shù)不是構(gòu)建低延時(shí)應(yīng)用的團(tuán)隊(duì),這些優(yōu)化可能劃入提前優(yōu)化(Premature Optimization)類別。

  • 代碼中是否使用了不必要的同步/鎖?如果代碼總是在單線程上運(yùn)行,鎖就是不必要的開銷。
  • 代碼中是否使用了不必要的線程安全數(shù)據(jù)結(jié)構(gòu)?例如:Vector 是否可以用 ArrayList 來(lái)替換?
  • 代碼中是否使用常用操作性能很差的數(shù)據(jù)結(jié)構(gòu)?例如:使用了單向鏈表,但是需要經(jīng)常在其中搜索一個(gè)單項(xiàng)?
  • 代碼使用懶加載是否會(huì)更好?
  • 是否將 if 語(yǔ)句或者其他可以短路的邏輯判斷放在最前面?
  • 是否使用了很多字符串格式化?可以更高效的完成嗎?
  • log語(yǔ)句是否使用了字符串格式化?有提供給 if 語(yǔ)句根據(jù) log 級(jí)別判斷是否輸出?

上面的代碼只輸出級(jí)別為 FINEST 的信息。但是開銷昂貴的字符串格式化每次都會(huì)執(zhí)行,不管消息是否輸出。

像上面的代碼這樣修改可以提高性能,只有需要輸出的消息才格式化。

在 Java 8 中,不使用 if 這樣的模板代碼也可以獲得這些性能。由于使用了 lambda 表達(dá)式,字符串格式化操作只有在 log 輸出時(shí)才會(huì)執(zhí)行。這應(yīng)該是 Java 8 中最好的方法。

在考慮性能問(wèn)題時(shí)要擔(dān)心的問(wèn)題非常多。。。

正如在我的第一篇文章中列出的關(guān)注列表一樣,本文重點(diǎn)介紹了你的團(tuán)隊(duì)可能在代碼審查中一直檢查的一些領(lǐng)域。這取決于你的項(xiàng)目對(duì)性能的要求。

盡管本文是針對(duì)所有的開發(fā)者,很多例子是特別針對(duì) Java/JVM。我還是想用 Java 代碼審查者幾條簡(jiǎn)單的建議來(lái)結(jié)束本文,這樣可以讓 JVM 來(lái)優(yōu)化你的代碼而不是你自己親自來(lái)優(yōu)化:

  • 方法和類盡量小
  • 邏輯盡量簡(jiǎn)單--沒有很深的 if 判斷或者循環(huán)

你的代碼對(duì)人類的可讀性越高,JIT 編譯器就越有機(jī)會(huì)足夠理解你的代碼并優(yōu)化。這在代碼審查中是很容易發(fā)現(xiàn)的--如果代碼看起來(lái)整潔可讀,也就有很大可能良好運(yùn)行。

結(jié)論

在考慮性能問(wèn)題時(shí),需要記?。河械膯?wèn)題在代碼審查階段界需要解決(例如:不必要的數(shù)據(jù)庫(kù)訪問(wèn)),有的問(wèn)題暫時(shí)提交評(píng)論就可以(如代碼級(jí)別的優(yōu)化),因?yàn)檫@些事情可能不會(huì)給你的系統(tǒng)帶來(lái)足夠的價(jià)值。

本文譯自:What to look for in a Code Review: Performance

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 178,940評(píng)論 25 709
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,540評(píng)論 19 139
  • 女人的容顏易逝,年華易老,隨著年齡的推移,一個(gè)女人或許會(huì)失去他人的寵愛。一個(gè)女人,他們要想一輩子能夠得到他們的寵愛...
    溫暖星座閱讀 2,004評(píng)論 1 2
  • 清華大學(xué)副校長(zhǎng)、清華大學(xué)生命科學(xué)學(xué)院院長(zhǎng),中國(guó)科學(xué)院院士施一公教授在“未來(lái)論壇”年會(huì)上發(fā)表了題為《生命科學(xué)認(rèn)知的極...
    雷光祥閱讀 405評(píng)論 0 0
  • 由于我們這邊數(shù)據(jù)的特殊性,數(shù)據(jù)量大、刷新頻繁等原因,于是使用到了Google得protobuf協(xié)議。相比較于Jso...
    Joyous閱讀 1,397評(píng)論 2 1

友情鏈接更多精彩內(nèi)容