作者:自由飛

網(wǎng)址:http://www.cnblogs.com/freeflying/p/4788494.html


我們?cè)?a target="_blank" data_ue_src="http://mp.weixin.qq.com/s?__biz=MjM5OTA1MDUyMA==&mid=211245272&idx=3&sn=5b62f633d9e7c153e08377f30075d70e&scene=21#wechat_redirect" style="margin: 0px; padding: 0px; color: #607fa6; text-decoration: none; max-width: 100%; box-sizing: border-box !important; word-wrap: break-word !important;">上一篇《架構(gòu)之路(1):目標(biāo)》中設(shè)定了架構(gòu)的目標(biāo),只有一個(gè),就是可維護(hù)性。完全沒有提性能,這是故意的。


似乎程序員都是急性子,或許是被windows冗長(zhǎng)的開機(jī)時(shí)間折磨夠了,有可能是因?yàn)樘嵘阅艿男Ч亲铒@而易見的……總之,我發(fā)現(xiàn),絕大部分程序員對(duì)性能的關(guān)注和熱情是無與倫比的!


  • C#剛剛推出的時(shí)候,就有人搖頭晃腦的說,“嗯,自動(dòng)垃圾回收,性能不行吧?”

  • DataSet橫空出世,馬上有很多人寫代碼,在DataSet里插入幾百萬條數(shù)據(jù),證明DataSet的性能問題

  • Linq當(dāng)然更要被罵了,尼瑪用反射?反射是什么,同學(xué)們知道么?性能大老虎呀!更不用說那些自動(dòng)生成的sql了,有我手寫的高效么?

  • ……


所以直到今天,我仍然看到很多程序員無怨無悔的用存儲(chǔ)過程來構(gòu)建他們的系統(tǒng),一個(gè)存儲(chǔ)過程可以有幾千行!然后,他們很無辜的問,“業(yè)務(wù)層有什么用?究竟能干些什么呢?”


在帶團(tuán)隊(duì)的時(shí)候,我最怕講的就是性能有關(guān)的問題。你要是不談性能呢,那代碼有時(shí)候真心看不下去;你要是強(qiáng)調(diào)性能呢,不知道他會(huì)給你整出什么幺蛾子出來。其實(shí)這就是一個(gè)“度”的掌握,所以非常難以用語言予以表示清楚。所以無數(shù)次挫敗之后,我只好咬牙切齒的說,“你的代碼,只有一個(gè)評(píng)判標(biāo)準(zhǔn),可維護(hù)性。性能的問題先不管!”這個(gè)答案似乎并不能服眾——尤其是對(duì)有上進(jìn)心的程序員而言。


所以,我先專篇講性能,希望能幫助大家更清楚的認(rèn)識(shí)這個(gè)問題。


一、性能不是不重要,而是他沒有可維護(hù)性重要。要理解這一點(diǎn),首先要理解可維護(hù)性的重要(請(qǐng)?jiān)僮x上一篇我花數(shù)周找bug的段子);然后要明白:解決性能問題,我們可以有很多代碼以外行之有效的方法,而可維護(hù)性基本上就只能靠代碼了;最后,還是要牢記:沒有犧牲,就沒有勝利!


二、所以,在絕大多數(shù)情況下,當(dāng)性能和可維護(hù)性相沖突的時(shí)候,性能讓位于可維護(hù)性。我們采用其他辦法來彌補(bǔ)代碼性能不夠高的問題。


空洞的說教沒有意義。我們還是舉例來說明吧!


破壞可讀性


前段時(shí)間我review代碼的時(shí)候發(fā)現(xiàn),這個(gè)程序員用Linq之后老是用First()而不是Single(),我就奇怪了,按業(yè)務(wù)邏輯,返回的值就應(yīng)該是一個(gè),難道可能會(huì)是多個(gè),多個(gè)應(yīng)報(bào)異常,不應(yīng)該取First()就完事了呀?想了一會(huì)兒,問這個(gè)程序員,他的回答讓我瞬間一種無力感,“First()性能更高呀!”以下為對(duì)話實(shí)錄:


“你怎么知道First()性能更高呢?”我問。


“First()嘛,取了第一個(gè)合格的值就返回,就不會(huì)繼續(xù)查下去了;Single()的話,就會(huì)一直查,查出所有數(shù)據(jù),然后再取其中的一個(gè)。”


“你確定?你知道有一種東西叫做索引不?”


“啊?……”


然后我簡(jiǎn)單的告訴他,索引是一種樹狀結(jié)構(gòu),可以讓查詢更快等等。


“但我還是覺得應(yīng)該用First()”,他想了一會(huì)兒,還是很堅(jiān)定。


“為什么?”,我不明白了。


“就算有索引加快了查詢速度,但用First()在加快了速度上更快呀!更快總是沒錯(cuò)的吧?”


“……”,我真不知道該怎么說了,最后突然靈光一閃,“好吧,那你說說,微軟為什么要搞一個(gè)Single()方法出來呢?就為了搞出來誤導(dǎo)你們?讓用First()的產(chǎn)生優(yōu)越感,嘲笑用Single()的?”


他陷入了沉思。


評(píng)論里還在糾結(jié)Single()/First()的同學(xué),請(qǐng)大聲的吼三遍:可讀性!可讀性!!可讀性!!!


發(fā)現(xiàn)同學(xué)們還在糾結(jié)這個(gè)細(xì)節(jié)。好吧,再解釋一下:


1、你怎么知道數(shù)據(jù)庫(kù)用的就是MSSQL呢?你怎么知道就是用的關(guān)系數(shù)據(jù)庫(kù)呢?NoSQL不行么?所以,你怎么就知道Single()/First()具體是怎么執(zhí)行的呢?比如我就要寫個(gè)Linq實(shí)現(xiàn),把所有的數(shù)據(jù)全取出來,然后再在內(nèi)存里排序,最后取First呢?


2、這里我們考慮可讀性,意思是:讀代碼時(shí),看到Single()就能瞬間知道coder的意思是取唯一的一個(gè);看到First()就知道coder的意思是要取第一個(gè)。和性能沒關(guān)系,如果一定要糾纏性能,那好:你要確定唯一性,當(dāng)然要做檢查(包括不唯一時(shí)拋異常),這個(gè)性能損失是應(yīng)該的呀;你要取第一個(gè),當(dāng)然要進(jìn)行排序,排序也會(huì)有性能損失呀!


我剛?cè)胄械臅r(shí)候,還很是收藏了幾篇文章,比如《高性能編程的十大準(zhǔn)則》之類的,里面的內(nèi)容大致就是,“總是使用StringBuilder,不要使用‘+’;總是使用……,不要使用……”。這類文章下面總是有一堆人叫好,“不錯(cuò)!”,“謝謝分享!”但慢慢的,我就對(duì)這些文章產(chǎn)生了懷疑(也應(yīng)該感謝園子里的老趙,csdn里面的sp1234之類的大神);直到很后來,我才明白為什么這種說法是膚淺的;而只有通過上面的對(duì)話,我才能清晰的把我的理解說出來。


所有這些犧牲性能的簡(jiǎn)單封裝,都是有其目的的;而其中一個(gè)很重要的目的,就是為了提高可讀性。你為了性能,故意不使用這些現(xiàn)成的封裝,通常,喪失的就是可讀性。


想當(dāng)然


繼續(xù)上面這個(gè)例子。最開始的時(shí)候,這個(gè)程序員關(guān)于性能的考慮其實(shí)是想當(dāng)然的。這種想當(dāng)然的情形很多,大致有這幾種:


  1. 自己的理解完全就是錯(cuò)的

  2. 自己的理解不能算錯(cuò),但實(shí)際上底層已經(jīng)對(duì)該問題做了優(yōu)化

  3. 自己的理解沒錯(cuò),底層也沒優(yōu)化


第1、2種比較好理解,第3種為什么也說他“想當(dāng)然”呢?因?yàn)闆]有和硬件環(huán)境相契合。


最簡(jiǎn)單的例子就是“緩存”。比如面試的時(shí)候,問你一個(gè)問題,“緩存能不能提高性能?”請(qǐng)注意,這是一個(gè)陷阱。答案應(yīng)該是:“不一定”。幾乎所有的人都認(rèn)為,緩存可以迅速改善性能,是因?yàn)榻裉煊?jì)算機(jī)的CPU和磁盤運(yùn)行速度,遠(yuǎn)跟不上內(nèi)存的發(fā)展。但即使如此,無節(jié)制的緩存,一樣可以拖垮整個(gè)系統(tǒng)。


類似的例子還有很多。你沾沾自喜,我節(jié)約了一次磁盤讀寫的時(shí)候,你同時(shí)增加了CPU的負(fù)荷;你優(yōu)化了算法,減少了CPU的運(yùn)算,但其實(shí)增加了內(nèi)存的壓力……天下沒有免費(fèi)的午餐。同樣的代碼,隨著數(shù)據(jù)的增加,硬件的改變,會(huì)呈現(xiàn)出截然不同的性能表現(xiàn)。


所以,開發(fā)過程中,很多的“優(yōu)化”,其實(shí)只是你的想當(dāng)然。與其這樣想當(dāng)然的優(yōu)化,不如在拿到性能測(cè)試結(jié)果之后再有的放矢的進(jìn)行優(yōu)化。這時(shí)候,又回到了我們之前說的,是不是代碼的可讀性更重要?這樣你才能迅速的找到該優(yōu)化的瓶頸啊!否則,一堆亂七八糟看都看不懂的代碼,你怎么去優(yōu)化,你連該優(yōu)化的點(diǎn)都找不到。


難以維護(hù)


另一個(gè)搞笑的例子是關(guān)于我自己的。創(chuàng)業(yè)家園項(xiàng)目里有一個(gè)功能:顯示博客正文的同時(shí)提供一個(gè)上一頁(yè)下一頁(yè)的鏈接。慣常的做法就是直接在數(shù)據(jù)庫(kù)里查就是了,但我總覺得不對(duì),這樣做兩次查詢有必要么?能不能優(yōu)化?于是我想到了一個(gè)“絕妙”的點(diǎn)子:為什么不直接在博客里存儲(chǔ)上一篇和下一篇的Id呢?這樣我一次性數(shù)據(jù)往返就能取到所有數(shù)據(jù)了嘛!各位同學(xué)是不是覺得我這個(gè)主意很棒?


噩夢(mèng)由此開始了。


首先,我們是想在發(fā)布博客的時(shí)候,設(shè)置他的上一篇和下一篇。但是,上一篇好設(shè)置,下一篇呢?還沒有啊!怎么弄,就只好在博客發(fā)布的時(shí)候,設(shè)置他的前一篇,同時(shí)設(shè)置他前一篇的后一篇。


然后,我們新添加了一個(gè)功能,除了上一篇下一篇以外,還需要在當(dāng)前博客所在分類中的上一篇和下一篇。怎么辦?再加字段唄。所以,博客里就有了Previous, PreviousInCategory, Next, NextInCategory。這時(shí)候,就感覺到有點(diǎn)不妥,但還可以接受。


接著,出現(xiàn)了一個(gè)問題,上一篇下一篇博客被刪除了,怎么辦?這個(gè)過程,就相當(dāng)于從一個(gè)雙向鏈表里移出一個(gè)節(jié)點(diǎn)一樣麻煩。頭開始有點(diǎn)大了。


再接著,博客除了發(fā)布刪除以外,還有各種其他狀態(tài),比如被屏蔽。而且被屏蔽之后,能否顯示和當(dāng)前用戶又有關(guān)系。當(dāng)前用戶是普通用戶,不能閱讀;當(dāng)前用戶是作者自己,就能夠閱讀。怎么辦?首先,屏蔽的時(shí)候,要設(shè)置上一篇下一篇;屏蔽取消的時(shí)候,還是要設(shè)置上一篇下一篇。然后,上一篇下一篇得根據(jù)當(dāng)前用戶不同變化的這個(gè)問題,基本上就傻眼了……


最后流著淚把辛辛苦苦折騰了好久的代碼全改回來,就通過數(shù)據(jù)庫(kù)查唄,多么清晰簡(jiǎn)潔的邏輯啊!性能問題?首先,這樣做造成了性能問題么?然后,就算有問題,用一個(gè)緩存能解決不?


合理浪費(fèi)堆硬件


說了這么多,不知道有沒有引起同學(xué)們的反思。可能大家還是過不去心里那道坎:明明有一種性能更高的方法我們?yōu)槭裁床挥茫?/p>


因?yàn)槔速M(fèi)唄!


什么?你有沒有搞錯(cuò)?我的代碼,至少省了一塊內(nèi)存條!那是你還沒從“窮學(xué)生”的角色里轉(zhuǎn)換過來。你花一周的時(shí)間對(duì)代碼進(jìn)行了優(yōu)化(就先不考慮你的優(yōu)化帶來的維護(hù)成本增加了),為老板省下了一塊內(nèi)存條的錢。你以為老板會(huì)拍著你的肩膀表?yè)P(yáng)你么?老板打不死你!


兄弟,賬不是你那樣算的。當(dāng)你是學(xué)生的時(shí)候,你的時(shí)間成本是0;但你進(jìn)入工作崗位,每一天都是要發(fā)工資的。


通過代碼來調(diào)高性能,是一種無奈——對(duì)硬件性能不夠的妥協(xié)(參考:八十年代的游戲程序員。我找不到這篇文章了,大概是說那時(shí)候連畫張彩圖的內(nèi)存都不夠,游戲開發(fā)人員只能各種苦逼的用單色“做”彩色)。否則,絕大多數(shù)情況下,堆硬件比優(yōu)化代碼的效果好得多,而且便宜得多。硬件的成本按摩爾定律往下降,我們程序員的工資也能按摩爾定律減么?


明明window 10 比window 95更耗性能,為什么今天沒人用window 95?為什么VS 2013要10G的空間我們都還屁顛屁顛的趕緊裝上?為什么現(xiàn)在大家都用C#,沒人用匯編?我們站在人類文明積累的今天,就應(yīng)該理所當(dāng)然的享受這一切成果。有打火機(jī)你不用,你要鉆木取火。如果你是因?yàn)橐獙W(xué)貝爺荒野求生裝逼,可以理解;如果你說你是因?yàn)榕吕速M(fèi)天然氣,我……我……我怎么說你呢?“給做打火機(jī)的一條活路,行不?”同樣的,程序員大神同學(xué),你就當(dāng)做好事,給下面寫底層做硬件的一條活路吧!你的代碼都是010001000010000001010101……了,你讓其他人怎么活啊?


最后,我突然想到的一個(gè)程序員為什么對(duì)性能如此敏感瘋狂,對(duì)可維護(hù)性毫不在意的一個(gè)可能原因:


  • 性能很好理解,卡得要死和跑得飛快;可維護(hù)性很不好理解,至少得跑個(gè)兩三年才能體現(xiàn),那時(shí)候,誰知道爺在哪里偷著樂呢

  • 性能上不來,程序員只有羞愧的低著頭,都是我的錯(cuò);需求有變更,開口就罵,“哪個(gè)SB又要改……”;


大家覺得是不是這樣的?所以,愿意把代碼百煉成鋼繞指柔的人少。想來,是一種莫名的悲哀和凄涼。


最后最后,有一些我能想到的名言警句供大家參詳:


  • 過早的優(yōu)化是萬惡之源

  • 優(yōu)化首先需要找到性能“瓶頸”。否則,任何人都可以隨手一指,“這段代碼需要優(yōu)化”。

  • 可讀性更強(qiáng)的代碼總是更好優(yōu)化

  • 硬件永遠(yuǎn)比軟件便宜