代碼優化 5 大原則,第一條就是別優化了!!!

“讓這代碼跑得快一點!!”——我碰到的第一件代碼優化任務就是這麼開始的。那個項目是一個巨大的 SAP 雲平臺應用程序,總共含有超過 3 萬行的代碼。

整個 App 加載數據的過程非常之慢,顯然用戶並不喜歡這種體驗。

然而,我必須承認,這個項目的代碼寫的挺不錯,數據庫調用很合適,只在有需要的地方進行循環,模組化也實現的很到位。我花了兩天時間,絞盡腦汁地進行各種測試,審查代碼邏輯,但完全沒發現到底是什麼地方讓這個程序變得如此之慢。

就在第三天,在我窮盡了所有的辦法,最後一點理智也快要消失的時候,我終於發現了問題所在。

在其中的一個讀取頁面上,被塞了一個等待語句,程序到這裏就停上 20 秒。

 

這大約是原來調試這段代碼的程序員在排查的過程中插入的等待命令,結果在將代碼合併進生產環境的時候忘記把這行東西去掉了。而在生產代碼中,每次調用讀取的時候,這段等待命令都會被執行,這就進一步放大了產生的問題。

於是,我把這行代碼刪掉了。好傢伙,一切都正常了!

有人說,代碼優化是一把雙刃劍

優化你的軟件是一件好事,但這並不能保證它永遠都會有好結果。

如你是在錯誤的原因驅動下,或是通過錯誤的方法進行代碼優化,這種所謂的優化往往可能增加成本,減緩生產速度,甚至可能會讓軟件的質量下降。

此外,大多數時候,優化並不是沒有代價的,你必須做出謹慎的權衡。例如,提高速度可能會使你在資源利用方面付出代價,更高效地利用存儲則很容易減慢運行速度。你需要仔細考慮你在其他方面做出的權衡,這樣你的軟件才能夠實現它的主要目標。

也許你會問,那我該怎麼辦?

下面是一些值得你考慮的要點,遵循這些原則,可以讓你的代碼更具響應性,也能減少你給用戶的設備以及它們連接到的數據庫帶來的額外壓力。

1. 不要進行優化

代碼優化的第一條原則就是,“不要”優化它。

這個程序是不是已經足夠好了?你要去理解這個程序將會被如何使用,知道它是在怎樣的環境下運行的,明白如果讓它運行的更快到底有沒有好處。在真正開始代碼優化之前,你必須要問自己這幾個問題。

沒錯,代碼優化所耗費的經歷和成本,只有在這樣的情況下是有意義的:

  1. 這個軟件很重要

  2. 它運行的確實很慢

  3. 在保證代碼健壯、正確、清楚的情況下,它確實還有改進的餘地

一個程序,就算它運行得再快,如果無法得到正確的結果,那就毫無用處。有效的優化,給軟件帶來的好處應該總要比壞處多。但如果你的優化走錯了路,那往往還不如別動它。

所以,你要做的第一件事,應該是設置一個合理的優化目標:

  1. 你需要清楚地瞭解你要達成的目標是什麼,以及各種優化手段與這個目標之間的關係。

  2. 你需要明確而簡單地說明這個目標,簡單到就算技術理解能力最差的部門經理也能夠理解和複述它。

  3. 你需要在整個過程中堅持這些目標。

要開始這項工作,最好的辦法是,根據對目標的影響確定每項任務的優先順序。你要做的每一件事情,都必須是可計量的。不要相信直覺,它基本上總是把你引向非常糟糕的方向。

2. 使用一個分析器

在沒有經過分析之前,不要貿然調整任何東西。最常見的錯誤做法就是,花了一整天去重構優化一段代碼,結果在運行的時候發現,這段代碼平時根本用不到。

分析器能精確地測量出你的程序把時間都花在什麼步驟上了。有些分析器能列出每一個函數,包括它們被調用的次數,以及每次執行的時候耗時的佔比等。

還有的分析器能列出每個命令的執行次數,被頻繁執行的那些命令,在總佔用時間上的權重肯定更高,而完全沒被運行的那些命令,往往就是一些無用的代碼,或者沒有經過合適測試的代碼。

一個好的分析工具,最有用的地方就是能讓你發現軟件中的“熱點”,也就是消耗了最多運行時間的那些函數或者命令語句。基本上如果你發現了一個熱點,你也就發現了問題所在。

性能分析的最佳使用方法就是識別出“熱點”,然後儘可能地優化它們,接着再次測量,以查看是不是有新的熱點冒了出來。性能分析的之前分享過很多,關注微信公衆號Java技術棧可以查找閱讀。

3. 啓用編譯器優化

通常情況下,有種比較靠譜的優化方式,那就是打開編譯器提供的那些內置的優化選項。

編譯器優化通常會給你的程序帶來幾個百分點到兩倍的運行速度提升。但某些情況下,這也可能反而降低速度,所以你需要在最終交付之前仔細測量性能優化的結果。不過總的來說,現代的編譯器在這方面已經做的足夠好了,程序員基本上再也不需要像以前那樣,不停地對編譯參數做各種頻繁的小調整。

一些現代的編譯器還具備全局優化能力,可以分析你的整個程序,以獲得潛在的提升。如果你的系統中有這樣的編譯器,請一定要試試。它可能會把運行時間減少個幾秒鐘。

注意:編譯器的優化設置越激進,最終編譯出來的程序中出現不明 Bug 的可能性也越高。所以,強烈建議你在開啓編譯器的優化選項後,務必重新進行迴歸測試,以避免出現一些奇怪的意外。

4. 調整代碼

只有到這時,你才真正開始修改調整代碼。在此之前,你必須已經通過第二步的性能分析發現了“熱點”,並且試過使用編譯器進行優化——畢竟絕大多數這些問題能讓編譯器幫你解決,也避免了你把這些代碼弄得過於複雜。

那麼,一般來說,有幾種比較成熟的方法來處理這些“熱點”。再次提醒,你必須非常謹慎,確保在提交每個更改之前,對它產生的影響進行測量。

那麼,讓我們看看這幾個方法吧。

將常用的表達式計算歸集在一起

如果同一個非常消耗性能的計算在多個地方重複出現,最好能只在一個地方進行計算,然後記住計算結果。除非必要,否則不要在循環中進行這樣的計算。

用簡單的計算代替消耗性能的算法

字符串處理對於任何一個程序來說,都算是非常常見的運算了。但如果你用錯誤的辦法去處理字符串,它們也有可能消耗大量的性能。類似的,在某些情況下,你可以用一系列移位操作來代替乘法運算。

但請務必注意,這種方式或許能帶來一些性能提升(其實並不一定),也有可能讓你寫出非常崎嶇複雜的代碼。所以在重構的時候,你必須非常注意代碼可讀性,以免寫出無法維護的代碼。

消滅循環

循環,往往是開銷最大的行爲,沒有之一。在允許的情況下(例如迭代數量不太多的時候),儘量避免使用循環。

緩存常用的值

緩存能有效地利用本地性——也就是程序(以及用戶)更傾向於重用最近的數據。你只需要緩存最常用的字符或數據,就能大大提高程序的性能。

使用一種更低層次的語言重寫

警告:不到萬不得已,不要這樣玩。

更低層次的語言在利用硬件設備性能方面往往更具效率(看看 Python 裏的內置函數是用 C 寫的就知道了),但要寫好這些東西,將會消耗更多的編程開發時間。

有時,通過用低層次的編程語言重寫關鍵代碼,能獲得較大的性能提升,但這是以降低可移植性爲代價的,也會讓以後的維護變得非常困難。因此,請謹慎做出決定。

請記住:在優化工作中,做出選擇這件事佔了90%的權重。值得花時間來決定你要做什麼,以及怎樣才能做的對。當然,這也正是編程的黑科技之處!

5. 在你的管理模型中加入代碼審查環節

這條是同時寫給開發者和管理者的。對於軟件工程的管理者,你必須確保代碼審查是項目開發過程的一部分;對於開發者,你應當將代碼審查作爲最佳編程做法中的必備環節。

推薦看這篇:基於 Gitlab 的代碼審查

低效的代碼不會對系統的日常運行造成太大影響。由於這個明顯的理由,我們往往會傾向於讓效率低下的代碼通過審查——因爲它並沒有產生任何真正的傷害,不是嗎?這可不對。隨着時間的推移,代碼效率將會越來越低下,並且導致執行速度變慢,最終使客戶端的處理時間大大超過可以接受的範圍。

是的,引入常規代碼檢查,刪除效率低下的代碼片段,或許會給你增加許多工作量。但從長遠來看,如果你把那些低效的代碼留在原地,未來你將不得不付出成倍的工作量,去檢查爲什麼代碼的運行要花上這麼長的時間——那時的你一定會感激現在的自己。所以說,不要讓現在的偷懶成爲你未來的痛苦。儘可能檢查並優化你的代碼效率。

一定要讓別人檢查你的代碼。理想的情況下,檢查者是你所欽佩的某個大佬,但基本上任何開發者都能互相檢查。不過,如果某人根本看不懂你的某些代碼,那可要非常警惕了——要麼是檢查者的水平問題,要麼就是你的代碼可讀性實在太爛了。

結語

最後,任何代碼的改進,都是從你自身開始的。在編程的世界裏,你不可能從第一遍就非常完美地寫出代碼。你總需要對代碼進行更改、修正錯誤,甚至有時代碼無論如何都無法按照你想要的方式工作。

這沒什麼問題,這完全就是成爲一名程序員的必經之路。讓寫出乾淨的代碼,成爲你的習慣吧。

正如極限編程的創始者,設計模式的先驅肯特·貝克(Kent Beck)指出的那樣:“我不是一個偉大的程序員,我只是一個不錯的程序員,加上偉大的習慣。”

本文來源「優達學城」

原作:Ravi Shankar Rajan ,譯者:歐剃

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章