淺談程序的核心——復(fù)雜度
文/安柏霖
在《The art of unix programming》中,復(fù)雜度的控制被看的非常的重,里面一句話提到編程項(xiàng)目的核心就是對于復(fù)雜度的控制,以及simple原則其實(shí)也在講這個(gè)事情。
我自己在08年也寫了關(guān)于這個(gè)的話題:復(fù)雜度與習(xí)慣。7年過去了,也經(jīng)歷了《天涯明月刀》這樣的重型項(xiàng)目的磨練,也有了更多的認(rèn)識(shí)。
復(fù)雜度的要點(diǎn)
復(fù)雜度的要點(diǎn)所在就是程序給大腦帶來的負(fù)擔(dān),它等同于程序員提升和開發(fā)程序的難易程度,這個(gè)負(fù)擔(dān)隨著模塊的復(fù)雜度大約是平方級數(shù)增長。
如果負(fù)擔(dān)很低,那么一段程序的就容易控制,程序員就容易提升程序的質(zhì)量(包括開發(fā)效率,運(yùn)行穩(wěn)定性和運(yùn)行效率)。
所以我們也不需要在任何時(shí)候任何情況去做復(fù)雜度的最小化,如果一個(gè)模塊本身規(guī)模很小,那么就不需要花很多精力去做進(jìn)一步簡化(當(dāng)然處于自我提升和精益求精的本能,在時(shí)間允許的情況下,做這個(gè)當(dāng)然好的了)
同時(shí)低復(fù)雜度度也不等同于最少行數(shù)的代碼,而是給大腦帶來最少負(fù)擔(dān)的代碼,比如后文舉得代碼例子,雖然另外一種寫法代碼行數(shù)更多,但是由于它符合一個(gè)更穩(wěn)定的模式,所以在大腦負(fù)擔(dān)和心理負(fù)擔(dān)都更輕,它可以認(rèn)為是更低復(fù)雜度的代碼。
復(fù)雜度控制的實(shí)際意義
實(shí)際價(jià)值
先從實(shí)用的角度來看:關(guān)乎運(yùn)行效率和開發(fā)效率(當(dāng)然其他的擴(kuò)展性等等也會(huì)包括,但是實(shí)際在項(xiàng)目里的感受是這兩個(gè)尤其的明顯)。
其實(shí)7年前我也是毫無疑問的這么認(rèn)為的,但是實(shí)踐起來并不是一碼事情,大約幾年前,才真正的形成開發(fā)的原則。
開發(fā)效率
這個(gè)最深刻的認(rèn)識(shí)原則當(dāng)初開發(fā)地形系統(tǒng),包括從編輯器的底層部分(UI部分是另外一個(gè)同事做的)以及runtime部分,從材質(zhì)到高度圖,系統(tǒng)龐大而且復(fù)雜。
開發(fā)過程中,也不可避免的遭遇到需求變動(dòng)(包括材質(zhì)系統(tǒng)的能力,地圖大小這種非常顛覆性的)。
時(shí)間緊任務(wù)重,一直想盡量快點(diǎn)把東西做好,開發(fā)過程中,代碼整理和系統(tǒng)整體控制沒有做太多,然后其他組可以同步進(jìn)行,然后再進(jìn)行代碼整理。
但是對于一個(gè)龐大的系統(tǒng),這種策略就不好。
寫程序的時(shí)候,質(zhì)量和效率***的情況就是始終對于整個(gè)系統(tǒng),在代碼級別保持一個(gè)非常清晰的狀態(tài),你心里知道要寫成什么樣,寫的過程,整體的代碼也清晰合理,與你心里的樣子相印證,然后可以心如止水的一直非??斓膶懀麄€(gè)過程非常的享受。
而如果實(shí)現(xiàn)過程中,缺乏對于系統(tǒng)良好的認(rèn)識(shí)和整理,希望“隨便搞搞,搞出來再整理“,這種在小型情況下是ok的,但是大型系統(tǒng)下,即便思維保持清晰,但是龐大的系統(tǒng)缺乏整理,而造成非常的復(fù)雜,很多東西由于前后設(shè)計(jì)的不一致,導(dǎo)致是處于一個(gè)不合理的復(fù)雜情況–需要你去死記。
這樣造成的結(jié)果就是,即便你對于整體系統(tǒng)的設(shè)計(jì)非常的清晰,但是在編程過程中,由于系統(tǒng)的一定的混亂,讓你沒法整個(gè)過程非常清晰的,心如止水的進(jìn)行,整個(gè)的過程,磕磕絆絆,讓人疲憊不堪。
所以在后半段,就停下來改變了策略,先做充分的整理,把不需要的部分去除,然后把代碼整理到完全準(zhǔn)備好來做新代碼的實(shí)現(xiàn),才去做新的實(shí)現(xiàn),這樣反而是最快的,寫起來也愉快迅捷。
運(yùn)行效率
處理效率,常規(guī)的基本做法是profile熱點(diǎn),以及根據(jù)游戲的情況進(jìn)行feature的關(guān)閉。
但是這個(gè)能做的事情是非常有限的,如果想做進(jìn)一步提升性能,接近性能的極限,必須要做的就是:
- 對于每一個(gè)模塊有充分的理解
- 可以做到快速的反復(fù)嘗試迭代
處理性能熱點(diǎn),在優(yōu)化早期是一個(gè)非常高效的做法,準(zhǔn)確來講,熱點(diǎn)處理是”在有水分的情況下,高效提升性能“的方法。
但是在追求極限性能方面,熱點(diǎn)優(yōu)化還是不夠,某一個(gè)模塊的性能消耗是不是超過了它應(yīng)該有的,以及一個(gè)排名10名開外的模塊其實(shí)是不需要高頻運(yùn)行的等等,這些都是熱點(diǎn)處理不能解決的。
在對于程序有充分了解,就可以進(jìn)行更徹底的調(diào)整,把大量的運(yùn)行做并行,低頻執(zhí)行或者直接優(yōu)化掉。
實(shí)踐中看下來,這樣的處理會(huì)把程序的性能帶到一個(gè)新的臺(tái)階。
這個(gè)道理可以說是知易行難,難就難在,對一個(gè)超大系統(tǒng)(比如對于《天涯明月刀》來說,就是整個(gè)客戶端,覆蓋幾十萬行的代碼),如何做到充分理解,如何做到容易的徹底的修改優(yōu)化。
所以關(guān)鍵點(diǎn)又回到復(fù)雜度,只有程序的復(fù)雜度得到***的控制,才能較好的做這個(gè)工作。
這個(gè)后來在實(shí)踐中,優(yōu)化過程中,大約一半時(shí)間是在做代碼的調(diào)整和重構(gòu),代碼合理就會(huì)讓優(yōu)化更加的可行和高效。
復(fù)雜度控制的方法與實(shí)踐
實(shí)踐下來,復(fù)雜度控制的能力在我看來可以從三個(gè)方面來拆解:渴望,目標(biāo)與時(shí)間積累。
渴望:
首先最有效的方式就是去承擔(dān)實(shí)際的,要覆蓋非常大范疇的開發(fā)任務(wù),這種情況下,你就會(huì)對于復(fù)雜度有切膚之痛,你就會(huì)非常真切的了解到復(fù)雜度是什么,什么是重要的,讓你抓狂的,什么只是虛張聲勢,無足輕重的,有了非常充分的渴望,那么后面的積累和實(shí)踐就容易多了。
目標(biāo):
方法和實(shí)踐會(huì)是非常的多,但是目標(biāo)卻簡單很多,就是能夠始終保持對于整個(gè)系統(tǒng),在代碼級別非常的清晰。在開發(fā)設(shè)計(jì)和做決定的時(shí)候,能有心如止水般的順暢即可。所以一定程度上,可以說復(fù)雜度控制還是比較主觀的,也很看火候的。比如有時(shí)候項(xiàng)目本來就比較小,即便復(fù)雜度控制不是很好,但是也非常的清晰,hold住,那就可以把更多的精力放在其他方面。
方法:
個(gè)人實(shí)踐中,這幾個(gè)方面可以注意下:
- 任務(wù)切分+代碼整理:在較小型的任務(wù)結(jié)束的時(shí)候,就開始做小規(guī)模的代碼整理,始終保持代碼是干凈的
- 模式+自然:積累更多的模式,比如一大片的代碼,其實(shí)就是做了pool的事情,那么這一大片的復(fù)雜度就是一個(gè)詞:pool。讓所有的東西都更加自然,符合編程的優(yōu)秀實(shí)踐,這樣需要你記和注意的東西就很少,那么它就是一個(gè)很低的復(fù)雜度。
比如下面這個(gè)代碼:
- int a[5];
- for(int i=0; i<5; i++)
- {
- printf("%d",a[5]);
- }
這個(gè)在實(shí)際程序中就不是一個(gè)好的實(shí)踐,在看到這片代碼的時(shí)候,應(yīng)該本能的注意到a[5]如果它的大小變化了怎么辦,就會(huì)出現(xiàn)for的訪問越界的可能。
- #define ARRAY_NUM(a) (sizeof(a)/sizeof(a[0]))
- int a[5];
- for(int i=0; i<array_num(a);i++) {="" printf("%d",a[i]);="" }<="" pre=""><p>
那么再次看到這樣的代碼的時(shí)候,就會(huì)比較放心,一路就過去了,那么這個(gè)就可以認(rèn)為是復(fù)雜度比較低的(需要注意的或者刻意要記的東西少)。
所以保持一個(gè)總結(jié)積累就變得非常重要,對于編程模式或者算法越來越多的積累,那么在開發(fā)和思考的時(shí)候,就可以以更高的維度去做,那么對于壓縮復(fù)雜度,提升思維速度和質(zhì)量就非常的重要了。
并且,在這個(gè)層面上看,盡量返璞歸真的編程風(fēng)格是一個(gè)更加有力的編程風(fēng)格。
復(fù)雜度控制的“敵人
沒有意識(shí)到“復(fù)雜度”的重要性
遇到不少程序員(甚至是大部分)對于復(fù)雜度無感,把一些算法和效率因素重要性遠(yuǎn)遠(yuǎn)放在復(fù)雜度之上,甚至是以寫出很復(fù)雜的程序?yàn)闃s。這一塊不是很容易溝通,只有實(shí)際去承擔(dān)大量的程序?qū)崿F(xiàn),對復(fù)雜度有切膚之痛的情況,才能有一個(gè)真實(shí)的認(rèn)識(shí)。
還有就是沒有及時(shí)和項(xiàng)目組溝通,爭取足夠的時(shí)間來處理復(fù)雜度問題以及清理代碼,相當(dāng)多的程序員都不會(huì)對復(fù)雜度有充分的認(rèn)識(shí),那么要求項(xiàng)目經(jīng)理有足夠的認(rèn)識(shí)在我看來不太合理?;旧陷^有可行性的方法是程序員給予足夠的溝通,以及在實(shí)現(xiàn)估時(shí)上留有充分的余量,而如果出現(xiàn)沒有意識(shí)到,沒有溝通充分,甚至是為了取悅manager而無視復(fù)雜度,瘋狂追求實(shí)現(xiàn)時(shí)間的情況,這都太糟糕了。
進(jìn)度問題
時(shí)間緊任務(wù)重的情況,這個(gè)前面已經(jīng)提過了,但是實(shí)際項(xiàng)目中還是會(huì)反復(fù)出現(xiàn),這塊其實(shí)是可以是一個(gè)大的話題。
首先每個(gè)程序員需要建立一個(gè)代碼實(shí)現(xiàn)的profile機(jī)制–我個(gè)人一直使用worklog,然后對于自己的開發(fā)效率有一個(gè)跟蹤,這樣才能知道哪種方法是正確的更快的。磨刀什么情況下才不誤砍材工,profile了才知道。
根據(jù)具體情況采取具體的策略,個(gè)人經(jīng)驗(yàn)下,相當(dāng)?shù)那闆r都是一邊實(shí)現(xiàn)一邊整理是更快的。
編程基本功,就是快速穩(wěn)定的實(shí)現(xiàn)了,這個(gè)需要長期的有意識(shí)的積累。
good for the programmer’s soul
Low-level programming is good for the programmer’s soul.” - John Carmack
對于卡神的這句話,無比的贊同,做底層代碼實(shí)現(xiàn),對硬件和系統(tǒng)有透徹的理解,對于程序員去清晰的理解整個(gè)程序如何運(yùn)行的至關(guān)重要,你就會(huì)更好的以底層的思維去思考。
同樣的道理,也可以用于高層的復(fù)雜度控制上面,更多的優(yōu)秀的編程實(shí)踐,更好的理解要做的事情,理解系統(tǒng)本身,***達(dá)到一個(gè)最簡潔的實(shí)現(xiàn),整個(gè)設(shè)計(jì)和實(shí)現(xiàn)的過程,可以讓人進(jìn)入心如止水的狀態(tài),同樣的”good for the programmer’s soul“