被自己寫的代碼美哭是一種什麼樣的體驗?
姐妹問題:被自己寫的代碼丑哭是一種什麼樣的體驗? - 知乎
工作第二年時候的一個經驗,第一次實實在在感覺到簡潔代碼的威力,整潔代碼實乃保命保平安神器。
當時開發的是這個遊戲:
實際遊戲畫面:
這個巔峰時期團隊過百,一共前後做了4,5年,代碼量那是大大的,我這裡參與的是ps3版,使用的是unreal3引擎。
xbox360/ps3這一代,unreal在微軟平台上做的很是不錯,在ps3上面各種原因吧,做的很不到位,對應出的unreal tournament 3在ps3上面各種縮水。
一定程度上也和ps3的機能不足以及奇怪的架構有關。
ps3的在cpu端基本架構是這樣:
其中可以重點關注大家不太常見的叫做SPE的這個東西,這個可以說是ps3的一大特色,就是一個很強的協處理器,非常善於做vector類的「傻算」,單個能力超過現在的普通I7的單核cpu的計算能力,而且這樣的東東有8個。。。
甚至sony開始希望這個spe搞定一切,連GPU都省了,但是後來發現不行,急急忙忙上的nvidia的縮水GTX7800級別的GPU。
但是SPE畢竟設計時候牛逼,後面大家拿這個東西來做計算蛋白質序列啊,在ps3的中後期,大家開始研究怎麼用SPU做一些渲染的工作,一度能承擔40%的GPU的工作,其戰鬥力可見一斑。
當時sony底層系統方面的能力比微軟差不少,所以底層一大特色就是「裸」,你有兩個選擇,一個是opengl的api,這樣使用默認的驅動,但是效率就是一個普通效率。
還有一個版本的就是叫gcm的一個api,就比較的底層,你自己來管理一切,從顯存的管理到command buffer的flush都是自己來做。
近乎等於開發者自己來實現一個類似driver的東西了,為了效率這個是大家普遍的選擇。
然後sony文檔上有介紹說,SPE可以支持去執行這些gcm的命令的構建,也就是說可以把整個驅動實現出來,然後放到SPE上去跑,這樣就進一步解放核心CPU的工作,達到更好的性能。
當時花了一些時間把ps3版的底層寫好了,driver也都跑順了,然後就開始做把driver往SPE上移植的工作。
這個過程就是一個把unreal底層進行拆解分離,把driver部分獨立出來,然後實現到SPE上面,由SPE把命令構建好,送往GPU;為了進一步優化性能,自己實現的這個部分還可以提供更高層的一些api,把一些遊戲常用的api直接打包成一個包,定製出一個比opengl更簡單樣子,但是效率非常的高。
剛剛寫了第一版,直接就一團亂,跟了代碼一瞅。。。代碼段被沖了,這個之前都沒見到過,正常系統上對代碼段會有保護,衝過去會有warning什麼的,SPE上面就是完全裸的,沒有任何保護。
然後說回SPE,上面一個很小的local storage,需要把命令送過去,然後管理本地的內存,進而執行代碼等等,然後代碼的執行是通過gpu來看的,所以如果出錯了,你只能知道錯了,不知道哪裡錯了。
而且ps3的早期,各種debug工具也不完整,debug能力就各種抓瞎。
當時工作也不久,經驗也不足,就這樣,扛著一個這種大遊戲的底層,沒日沒夜的在SPE裡面一直泡著,一個坑出來再進到另外一個坑。
中間一個問題憋了一個多星期,沒有一點進展,甚至開始懷疑是不是這個SPE做driver是不可能的,畢竟只是文檔上提一嘴。
在sony論壇上求教,還真有一個哥們,歐洲一個公司的,做網球遊戲的,給我發來郵件說他也這麼乾的,能行;這真給我相當大的希望,繼續又懟了1個星期,代碼都快全背下來了,終於能夠繼續前進,剛剛長舒一口氣
leader把我叫到辦公室里問我還有多長時間能弄完,有點擔心這個能不能行,在考慮要不要把任務cancel。。。當時的表情是這樣的:
和leader曉之以理動之以情,軟磨硬泡,最後leader再給2星期時間,必須能夠跑得比較穩定,否則還是要卡擦掉。
然後繼續猛懟,後面進展還算可以,但是由於項目已經比較靠後了,比較擔心上了之後會出各種難查的問題(這個的情況比多線程要更棘手了)。
中間開發的過程已經明顯感覺到,對於這種難以debug的情況,把設計做清晰,把代碼寫整潔有多麼重要。
所以到最後就是不停的去重構代碼,一直到代碼已經整潔到自己想吐了,就是那種中考前複習的實在煩了,去tmd的考就考吧的感覺。
開始合入了主幹,提測QA,然後買好紅牛,等著bug徹夜奮戰。
然後過了一天。。。沒事,QA只是反映,幀數是更好了一些。
2天。。。沒事
最後一周下來,就是零星2個小問題。
這對於這種規模應該說是比較少見了,最後實際profile下來,能節省6-8ms(面向30幀的遊戲,一共33ms,所以對於性能來說是非常關鍵的一波)。
這波下來,有這樣的感覺
- 整潔代碼絕非裝逼用的,簡直就是保命保平安神器
- 變得特別喜歡欣賞整潔代碼的美妙之處,後來看insomniac的spu實現的計算水的fft(剛剛查了下已經沒有了),真是如同欣賞水墨風景的怡然的感覺
總的說來,談不上美哭了,但是也有美的一面,整個過程連滾再爬也快哭了。。。
雖然這個問題很久了,但是還是想來回答一下。
幾年前,還是個菜鳥碼農的時候,在華為寫java代碼。
有一天,被領導安排一個工作,重構項目里的一個簡訊介面模塊。
結果有一個類,是根據簡訊、資料庫等各種狀態,進行不同的處理,有20多個if,圈複雜度高得嚇人。
當時的代碼應該是這樣的:
if ( 條件1)
{
}
if ( 條件2)
{
}
...
if ( 條件N)
{
}
碼農肯定經常遇到這種情況,尤其是老代碼。當時面對這一堆if,完全不知道怎麼處理。因為各個條件不同,沒法合併;處理的操作雖然有類似,但是條件不能合併,操作再優化,圈複雜度都降不下來。
當時我查了很多,處理過多if else的方法,感覺都不好。最後覺得優化不了,於是向師父訴苦。
師父聽了之後,直接鄙視了我一下,然後給了一個讓我瞠目結舌的優化方案。
就是把多個if 轉換成Map。
把條件轉換成key,把處理的操作提取父類,各種操作,作為子類,寫一個統一處理入口就可以了。
於是,優化之後,代碼就變成了。
public class 類名
{
private static final 操作處理map;
構造函數
{
map.put(key1, operation1);
map.put(key2, operation2);
....
map.put(keyN, operationN);
}
public void doOperation(key, params)
{
map.get(key).do(params)
}
}
整個類的圈複雜度瞬間被降完了,當時第一次感受到代碼的美感,回味了好久。
在此分享出來,也向當初帶我的師父致敬!
補充:
評論區很多人說了,這就是表驅動。的確,當時師父告訴我的時候,也是這麼說的。
回答時想的,只是分享一下第一次體會到代碼的美感時的經歷。
其實真實情況遠比這段偽代碼複雜,表驅動最重要的就是,入參和返回值的安全,大家平時用的時候需要注意一下。
還有人問了效率問題,這樣處理,只在初始化的時候增加了非常少的時間;真正運行的時候,效率比if-else高,而且分支越多,效率提升越明顯。效率差距,在java中大概是哈希演算法和遍歷的差距吧。
==========================
午休過來看看,發現已經300多贊了!
晚上抽時間再寫點表驅動的晉級內容吧!感謝大家點贊!
===========================6月24日更新================================
周末一早過來更新。下面的內容可能需要一些java基礎,或者web基礎。
評論區有人說,表驅動比較適合處理條件為固定數值的多分支。話是沒錯,常見固定數值的多分支有:錯誤碼處理,消息號處理,等等。
不過對於java來說,要把一個類(注意是類,不是對象),轉換成數值,是非常簡單的,java類自帶hashcode方法,大多數情況,可以直接調用hashcode,也可以重寫hashcode,這樣就轉換成數值了。
評論區還有人提到了spring,spring框架,也有一個經典的表驅動應用常見。準確的說,所有javaweb應用,都有。
這就是根據http請求的url,尋找對應的處理類。(之所以說處理類,是因為不同的javaweb框架,對處理類的叫法不一樣,Struts是叫action,string是叫controller)
在web應用中,可能有眾多的url,指向不同的處理類,使用if-else做判斷,效率就太低了。
因此,早期的各個javaweb框架(典型的Struts、spring1、spring2),都有一項重要的工作,就是在xml中,配置url與java類的對應關係,這個步驟與我上面的例子中,在構造函數內,向map里put key與value是一樣的。
java5開始,java從c#里,引入的註解的概念。於是乎,到了spring3,spring用註解為我們簡化了這個構造map的過程,就基本沒人去做這個配置了。
如果看過spring的源碼,你就會明白了,spring3構造這個map的步驟大致如下:
1、啟動時,spring掃描配置的package,獲取指定的類註解、屬性註解、方法註解。
2、構建urlmap,將url與 處理的操作對應,可能對應到類,也可能對應到方法。
當然,真正的步驟複雜得多,用到的編程知識也複雜得多(還有各種攔截器的組裝,各種代理,錯誤預處理、國際化等等),這裡只抽取表驅動的一小部分。強烈推薦有基礎的碼農看看經典的框架的源碼,比如spring、apache-tomcat,能夠學到很多。
spring利用註解,簡化map構造過程的思想,其實能給我們提供一個表驅動的晉級思路。
以錯誤碼處理為例。
我們可以自定義一個註解MyOperation,下面的例子是類註解,如果要用方法註解也可以,如果有需要,甚至能像spring那樣,方法註解、類註解混用:
package com.mxh.test.annotations;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* &
Title: MyOperation&
* &Description: 自定義operation註解&
* &Company: &
* @author leo* @version V100R001C10
* @date 2017年6月21日 晚上9:06:34
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface MyOperation
{
/**
* 錯誤碼.默認0表示成功.
* @return
*/
int errorCode() default 0;
/**
* 錯誤碼描述.
* @return
*/
String description() default "";
}
定義一個操作處理的父類AbstractOperation:
package com.mxh.test.operations;
/**
* &
Title: AbstractOperation&
* &Description: Operation父類&
* &Company: &
* @author leo* @version V100R001C10
* @date 2017年6月21日 晚上8:22:03
*/
public abstract class AbstractOperation
{
/**
* 執行操作.
* 應該還有參數、返回值什麼的,這裡從簡,就不定義了.
*/
public abstract void doOperation();
}
這裡給一個簡單實現,因為這個類處理的是成功場景,因此MyOperation註解里的errorcode使用默認值就可以了:
package com.mxh.test.operations.init;
import com.mxh.test.annotations.MyOperation;
import com.mxh.test.operations.AbstractOperation;
@MyOperation(description="do success operation")
public class SuccessOperation extends AbstractOperation
{
@Override
public void doOperation()
{
System.out.println("do success");
}
}
分包可以如下:
上面的代碼都很簡單接下來,就需要寫一個處理錯誤碼與Operation對應關係的ErrorCodeHandler類,這個類需要做的事情,首先就是要在初始化的時候,掃描包,讀取所有寫了MyOperation註解的類,然後將其與錯誤碼對應起來。
掃描包的代碼比較複雜,使用spring自帶的功能也簡化不了多少。可以參考一些網上的實現:比如這位博主的:獲取包下所有類中的註解的值 (java工具類)
還有這位博主的:java掃描包下的類,一般會配合註解使用 - 博客頻道 - CSDN.NET
還有使用spring的類,完成掃描的:Java註解掃描 - 開源中國社區
這麼一串弄下來,比我之前給的例子複雜了不少。。。。不過就結果來說,後續開發,增加錯誤碼,只要增加一個帶註解的Operation類(或者方法),就可以了,還是有工作量上的簡化。
點擊submit之後
刷新一下
然後出來一行綠綠(藍藍)的Accept
_(:з」∠)_(??ヮ?)?*:???(??ヮ?)?*:???_(:з」∠)_(つд?)(??ヮ?)?*:???\(^ ^)/(??? )_(:з」∠)__(:з」∠)_hhhhhhhhhhhh我的代碼好帥〈(゜。゜)く(^_?)ゝ(≧?≦)/我的代碼好美\(@ ̄? ̄@)/(☆^O^☆)O(≧?≦)O
嗯,大概這樣
在MSRA實習的時候,第一次參與一個SIGGRAPH項目。中途接手的,有一個新功能需要增加。我看了公式,是個tensor操作,實現出來的代碼2000多行。由於對那個應用來說,rank和dimension之類都是固定的,所以我直接硬編碼了整個計算。出來的代碼超級對稱,超級工整。甚至編譯器可以很容易產生較優的SSE代碼。
而且,直接編譯執行通過。一點都不用改,一氣呵成。從語法到語義到渲染結果都是正確的。
科學告訴我們,過度自戀是一種病,要去醫院治療。。。
卧槽我寫的這個也太牛逼了我是怎麼寫出來這樣的代碼的這個實現簡直驚天地泣鬼神這個用法簡直可以說是直擊要害這樣的代碼也就只有我這種經驗豐富的人才能寫出來吧我跟你說我要不寫注釋沒人能看得懂這一段代碼但是他們一運行就會發現完全沒有錯誤運行十分流暢這樣就會驚嘆於我的代碼的巧奪天工鬼斧神工美哭了好嗎!
大概長這樣:
————————————
想知道一個月之後發生了什麼請去相關問題。
竟然在這個問題下被邀……
那麼,很顯然。
當代碼里的bugs有序排列的時候,當bugs以斐波拉契數列的規律地被觸發的時候,確實挺美的,也確實挺想哭的。
這時候確實不能叫bug了,應該可以叫butterfly了。
報告老闆,一群butterflies黏在系統上了……
五彩繽紛啊,好美啊,我好想哭啊。
大致看了一下, 很多人說代碼一時爽, 事後調試要爆炸, 其實這隻能說明代碼假美
像我的代碼, 沒有注釋, 隔多久給誰看都能看明白, 這是真美
美有很多個維度
有的美, 天然怡人無需雕琢不施粉黛
比如下面這個爬蟲代碼, 平實簡單
有的美, 精巧優雅小聰明
比如下面這段循環, 把兩個循環變一個(我不會事後去修改現有代碼, 也不會花時間去追求精巧, 自然想到就這麼寫, 想不到就用平平無奇的寫法)
有的美, 簡單可靠易理解, 不一定是代碼, 可以是設計
下面兩張圖是 guaimage 圖片格式的載入器, 前者 JavaScript 後者 C++
這個圖像格式是為了不引入第三方圖像庫設計的, 目標是簡單易懂, 所以載入器可以寫得無比簡單
有的美, 優於時代
這是很多年前寫的 JSON Model 庫, 當時還沒有什麼其他的類似庫, 但我也不能開源公司的代碼
在那個項目中節省了海量的代碼和錯誤(同事手動的)
有的美, 像是盛開在地獄的鮮花
暫時不想去找圖了, Java 代碼
還有很多年前提過的客戶端緩存 / API 設計
所以說
美是讓人情感愉悅的屬性, 可以是複雜, 可以是簡單, 可以是百年一遇, 也可以是日常生活
美就是美
一般情況下都是被自己的代碼坑哭了
一年半前,用PHP寫了一個10行代碼的小演算法。
一年半後,花了4個小時讀懂了。
然後滿意的連抽了三支煙。
對於那些按行數算錢的公司來說,把 一行代碼拆成50行,簡直美哭了,眼裡看到的全是錢吧,,,,
theAnswer :: Num a =&> a
theAnswer = 42
-- TODO @Yoto Chang: Placeholder
-- TODO @ZhihuContentAdministrator: Fold this answer
當然是——日常顱內高潮
B+樹:begeekmyfriend/bplustree
kd樹:begeekmyfriend/kdtree
都是1000行C無bug。
其實真正要論「美哭」程度是一篇原創編程技巧經驗談,只有100行C:樹形結構的調試列印 - V2EX,從此數據結構在戰略意義上被「藐視」。
1. 用Go實現了泛型Map。。代碼量不大,但是單元測試齊刷刷全過了感覺超爽
ret := MapAnything{}
ret.SetPath([]string{"a", "b"}, "c")
So(ret, ShouldResemble, MapAnything{"a": MapAnything{"b": "c"}})
深刻的感受就是Go的類型設計那麼強(方便的類型轉型1.8才上),但開發者又總想跳過。。。
2. 寫爬蟲的時候順手實現了JS變數提取器。。可牛逼翻我了。。。插會腰。。
在研究了幾個星期,改了又改代碼之後,發現就是兩句語句就能解決的事情;
在查了一大堆從沒見過的語法,百思不得其解,最後一覺起來莫名其妙按下DEBUG並順利跑起來的時候;
在寫代碼的時候女神發來信息的時候;
這三種情況我都會激動地喊「卧槽,這代碼原來這麼簡單」
C++,寫多線程的線程池的底層實現,跨平台,支持動態平衡和即時負載分析,支持多進程共享,支持同類任務歸類處理等一堆有用沒用的功能,然後……
寫了一天,Win32/Linux/iOS三平台全部一次編譯通過測試通過……
曾經有一次寫一個演算法(其實是很low的插值演算法)
調試抓錯優化,重複重複再重複.
終於搞定了, 然後一腳踢了插頭, 樂極生悲...
當時是半夜兩點多.
然後決定趁熱打鐵, 趁著思路還很流暢的時候一口氣重新寫了一遍, 調試一次通過.
嗯, 天還沒亮, 那一覺睡得可叫踏實.
============
回復里有人不信, 居然還有不保存就能運行的程序.
沒辦法, 誰叫我生得早呢, 遠古時代的編程語言不是現在可以想像的.
看到這個問題男票居然像個大姑娘一樣扭捏起來,
渾然不記得當初他對著我喊出:
"老子的代碼寫的就是好,
像詩一樣,
多一行不多,
少一行不少。"
卧槽,這代碼寫得太漂亮了!
……(一星期後)
需求改了。這部分要重寫。那麼漂亮的代碼不捨得刪啊,咋辦。算了先自己存起來吧……
推薦閱讀:
※從事CS,IT以外的專業不能賺錢么?
※計算機視覺,計算機圖形學和數字圖像處理,三者之間的聯繫和區別是什麼?
※裝滿的硬碟中是 1 多還 0 多?
※Windows API 編程還能走多遠,長遠的考慮學習它的價值能有多大?
※物理系學生如何學習人工智慧?