陳梓瀚Vczh,在網絡上,大家可能熟悉他在知乎的外號——輪子哥。而在現實中,他的名字就印在久負盛名的C++經典教程《C++ Primer 第五版》的封面上,因為他是這本書的審校之一。他常年利用閒暇時間開發C++圖形界面庫GacUI。這是一款在架構上跨平台、支持控件與模板分離、靈活的數據綁定以及全面支持MVVM模式的C++ GUI庫。
如今,他在西雅圖微軟總部為Office開發基礎組件庫,也就是專業「造輪子」,無論工作還是閒暇,他都樂衷於此,知乎「輪子哥」的外號也是因此而來。
在知乎上,除了為大家解答技術問題以外,他還關心國內外的時事以及網友們的生活情感問題,併為知乎情感話題不設優秀回答者表示遺憾。
今天圖靈訪談就來聊聊他與編程的故事。
文 | 李冰
我的大部分編程書都是在上廁所時看的
他的生活準則很簡單:為自己喜歡的事投入時間。
於他而言,編程並非一份職業或者事業。他曾説:「一日不編程,食肉無味。」喜歡便用心鑽研,然後十幾年不曾改變。而一切剛開始的那個時候,他沒有想這麼遠,就是覺得很有意思。
「上小學的時候,學校明明就沒有計算機,不知道為什麼有幾本 DOS 的教材,不過我還是拿來看了。然而家裏也沒有電腦,所以也只能當圖書看。那個時候對計算機唯一的概念就是去表哥家裏看他操作 Explorer,我對這些東西產生了好奇。
「後來上了初一,在我要求購買電腦之後,父親讓我用壓歲錢按人頭入股,買了一台聯想天禧 2000。儘管我壓歲錢的存摺其實就在父親手裏。
「聯想自帶了個幸福之家軟件,一開始我就擺弄那些東西,覺得很好玩。後來我試圖自學怎麼使用 Office,但當時打開了 Excel 之後一臉懵逼,根本不知道那個是什麼。再後來,我在書店裏看到了 PowerPoint 和 FrontPage 的教材,就開始學着瞎搞 HTML,剩下的時間就玩親戚和同學跑來我家裏裝的遊戲。
「恰巧,具有前瞻性眼光的廣東汕頭華僑中學,在初一開了計算機的課,學習如何用 DOS 和 Win 3.2,後來在初二又開了 QBasic + Visual Basic 5.0 的課。我一直都習慣開學的時候就把理科課本先給看了,於是我就開始自學。
「初中時我的成績不怎麼樣,雖然我未來考進了華南理工大學,但是那個時候父母還在擔心我能不能上一本。然而我對這種事情毫不關心,人為什麼要做自己不喜歡的事情呢?除非被父母打了。買完電腦不久,父親就限制我只有週末才能使用。
「所以我大多時候是在紙上學習 QBasic 的。我花了大概一個星期,就把 QBasic 半個學期的東西都給學了。書裏的內容很少,只教了怎麼聲明變量,聲明函數,還有條件和循環語句等。
「這時我接觸第一台電腦只有半年多,很多軟件都用不利索,系統操作也只會簡單的打字或畫圖,當年的 Windows 還自帶 QBasic,所以我學習編程跟學習計算機的使用是同步的。
「一個星期之後,我寫出來的第一個程序,就是輸入數字、回車、數字、回車,再加上操作符,然後算加減乘除,寫了從1加到100這種東西。然而這種簡單的練習只能讓我學會語法,但是我經常在需要什麼語法的時候都沒反應過來,還發生了像‘人肉展開循環’這種滑稽的事情。
「QBasic 的課本看完了,我就接着看 VB5 的課本。後來為了找資料,我經常往書店裏面走。我買到的第三本書就是《Visual Basic 高級圖形程序設計教程 》。這本書其實很難得,不僅涉及了很多我初二時還沒學過的數學知識,而且還教了很多圖形和圖像的算法,從渲染分形圖,到掃描曲線,到抗鋸齒,到如何把真彩色圖片降級到 256 色的算法,最後甚至連光線追蹤引擎也説了。
「我一開始覺得裏面的圖片很漂亮就買了,覺得 VB6 竟然不僅僅能畫窗口,還能編程弄出圖片來,很有意思。當然看懂是不可能馬上就看懂的,這本書我一直翻到了大學才看完,期間甚至還掉進過廁所裏。
大學時期的作品
「因為中學的那段時間,週一到週五就只有上廁所和睡覺前躺着可以看自己喜歡的書,剩下的時間要麼就上課,要麼就做作業,忙得很。我買的大部分編程書都是在上廁所的時候看的,現在想想覺得自己真是太勤奮了。
「當然光看書學習也是不行的,所以我還經常在一些論壇上跟人家互相交流,特別是當年的網易社區上有一個 VB 板塊。那個時候國內的計算機行業也不發達,大多數上論壇的人都是菜鳥,我學着學着發現,我也可以回答別人的問題了。
「那個時候上網很貴,撥號了還會導致家裏電話打不通,所以我也從不閒逛。我在 VB 板塊上經常做的事情就是問問題,或者回答別人的問題,還有跟網友互相交換自己的代碼,互相學習、評價什麼的。
「2000 年,谷歌和百度也就剛開始,當年最有名的就是搜狐的搜索引擎,但是其實也沒什麼用,什麼網站有什麼東西都是靠網友相傳的。大家互相聊天的時候就會知道一些信息,只能是這樣子來獲取資料。至於身邊的同學嘛,所有人都對編程不感興趣,整個班就只有我一個人在弄這個,所以跟真人交流什麼的,那是不存在的。」
一路獨自摸索,當然會遇到不少困難。然而,一旦把時間的刻度拉長,以十年為尺,這些算什麼彎路?以自己的方式堅持下去就是捷徑。
他説:「挫折當然是有的,但是我學習編程的出發點,就跟大家打籃球一樣。
「失敗了繼續找書找資料,實在不行就算了嘛,那麼多程序可以寫,為什麼非要死磕這個呢?所以當年雖然有很多嘗試都是失敗的,但是我覺得無所謂。儘管如此,有時候‘算了’之後的一段時間,偶然給我看到合適的資料,就又學了,還覺得挺驚喜的。
「大家打籃球,學 NBA 的球員花式傳球投籃扣籃,不也經常失敗嘛。因此就不打籃球了嗎?玩紅警的時候被 AI 屠殺了,玩 RPG 沒打過 boss,就不玩了嗎?當然是不會的。我對待編程就是這個態度。編程本身就可以給我帶來快樂,所以結果已經不重要了。
「我覺得自學對我的幫助,就是我變得特別有耐心。尋找知識困難,是從一開始就有的,我已經習慣了。程序越寫越大,調試起來花很多時間,這個也已經習慣了。
「寫代碼最重要的就是靜下心來,願意為學習編程花費大量的時間。
「我也經常通過 re-engineer 別人的東西來學習,看見一個什麼東西就想自己寫一遍。在這個過程中,不僅可以深刻地瞭解裏面的道理,而且你經過親自吃屎,你就能知道為什麼作者要這麼做而不要那麼做,甚至你還可以發現可以改進的地方。當然這也需要大量的時間。」
瞎編個故事,先把RPG遊戲搞起來!
編程界有句老話:不要重複「造輪子」。
而他正相反,寫代碼的這些歲月裏,他將絕大部分時間都花在了「輪子」上。
在大學,他開發了渲染器、腳本引擎、圖形界面、編譯器等。去微軟工作後,他申請了兩次調動,現於Office組專業造輪子。工作之外,他常年開發自己的 GUI 庫 GacUI,這些年,他造的輪子越來越精深,隨着知識和經驗不斷累積,他也越來越遊刃有餘。
有人很不理解,既然已經有了現成的工具,為什麼還要花大把時間再造一個?
一方面,他在不斷借鑑與改進,致力於使解決問題變得更容易;另一方面,動手實踐是學習的最好方式。最重要的是,對他而言,編程就是以自己的方式創造好玩的東西。
故事的起點,要回到他高二寫的那個一萬多行的 RPG 遊戲。
「其實我初三就嘗試過做遊戲,那個時候用 Picture 控件一個一個拼圖,理所當然地失敗了。
「後來我學會如何不在 Picture 控件裏畫圖,也慢慢找到 VB 寫 module 的感覺,就開始能做一些小遊戲了,代碼的組織雖然不能説好,起碼不能説沒有了。高中改用 Delphi,還學習了 Windows API 和 VCL 的一些操作,我終於搞明白如何直接畫進一個指針裏了,也終於可以快速刷新畫面了。這就為我的遊戲打下了基礎。
「那個時候我就想:來做一個 RPG 吧。
「事情的起因還是初中泡論壇的時候,網友分享了他們用 C++ 寫的 RPG 給我。我覺得既然他們可以,那我也可以啊。所以經歷了若干次失敗之後,甚至到了我的注意力從 VB 轉移到 Delphi 之後,我都一直記着這件事情。在終於學會了足夠多的技術之後,我就想把它做出來。
「所以我直接跟當時那個小組把素材給討了過來,瞎編個故事,然後就搞起來!
「記得立項的時候是高一的勞動節,我已經學習編程兩年多了,也已經熟悉了面向過程編程,也慢慢理解了為什麼大家要把一些功能相關的函數分組放在文件裏面,於是我就開始規劃這個代碼要怎麼寫。
「當時直接想到的就是,一個 RPG,要有 KV-store,要有 UI,要有腳本,要有 2D 的渲染引擎,還有一些其他部分。我可以挨個完成,做完了才來做地圖編輯器,然後再做遊戲本體,最後拿地圖編輯器來寫腳本編故事,遊戲就可以跑起來了。
「RPG 用到的腳本引擎長得就像帶有控制流語句的彙編語言。當時設計的時候,考慮到一個遊戲經常要畫對話框,讓玩家選選項,但是遊戲循環本身又不可能讓你的腳本引擎卡死,所以我想了一些辦法,最後專門給腳本引擎添加了一個斷點功能。
「遊戲跑到了那些需要暫停的命令裏,腳本引擎當場就退出了,然後遊戲會根據留下來的命令的‘屍體’來做相關的操作,操作完了再讓腳本引擎從當初停下來的地方接着跑。
「做這些東西的時候,數據結構、編譯原理什麼的根本聽都沒聽過,我只從一本 VB 的遊戲開發教材裏面,學習到了鏈表,加上 Delphi 自己帶的一些容器,剩下的東西都是在上面搭建的。因此當時做出來的 UI 引擎還挺搞笑的,控件只能套兩層,因為我沒有樹這個概念,更別提怎麼用代碼表達了。
大學時期的作品
「其中,我覺得最困難的部分,就是遊戲要如何快速刷新。那個時候我試圖學習DirectDraw失敗了,所以我就用GDI+DIB搞。當時我的電腦算是不錯的了,但也很慢,根本沒辦法讓我每一幀都完全從頭開始畫,貼到窗口上,最後還能保持60fps。
「如何在遊戲往前走一幀的時候,只畫上一幀的圖需要改的部分呢?最後我想到了一個辦法,每次遊戲修改地圖內容的時候,我都會留下一個flag,代表這裏動過了。玩家的角色還會移動,所以我直接把上一幀的整幅圖跟着移動的方向複製一遍。
「這樣複製還是挺快的,DIB都是可以拿到指針的,直接移動指針的數據就可以了。複製完,邊緣就會留下一些要修補的部分,再加上我打過的所有flag,全部按格子的分列先分組,再看每一組裏面有多少格需要更新,最後分批填上去。
「為什麼要分列呢?因為 2D 的 RPG 地圖的遮擋關係是上下的,而不是左右的,分列是為了按正確的遮擋關係畫圖。
「設計是美好的,但是遊戲人物是不跟地圖的格子對齊的,所以最後搞得很複雜,我寫了好多天才把 bug 調完,花了很大精力。
「這一萬多行代碼我大概寫了三個月,然後花了半年編故事畫地圖寫腳本,終於在第二年過年的時候 release 了。那一刻我覺得我完成了一項壯舉,就跟我現在練了兩年終於能深蹲150kgX10次/組的感覺很像。
「現在回想起來,日後的很多習慣,都是由我開發這個 RPG 所領悟到的東西慢慢發展出來的。
「譬如説,後來我回頭看我的代碼,發現很多函數都是圍繞着一個數據結構展開的,這不就是類嘛?我怎麼就都用函數來寫呢?於是我就開始大量使用類來編程。
「譬如我推崇的測試驅動開發。我做 RPG 的時候雖然不懂什麼叫測試,但也是從小模塊開始寫,寫幾個窗口讓我可以人工測試小模塊的代碼,然後慢慢搭起來的,並沒有選擇一上來就寫遊戲本體。因此後來我接受測試驅動開發的時候,覺得這簡直就是理所當然的事情啊,沒有任何理由不這麼做。」
到了大學,他才開始系統性地學習計算機的理論知識,同時與過去的經驗相互印證,又不斷產生新的領悟。
我轉C++的契機?因為Delphi跪了
C++是一門神秘的語言,業界流傳着許多它的傳説。
有人説自己14天就掌握了C++;有人講述自己3個月從入門到放棄;有人説自己有10年經驗,卻只算有一定的C++基礎,更不敢談什麼精通……
而他與 C++ 的故事也很長。
「我轉C++的契機完全就是因為我大一時Delphi跪了,所以並不是我喜歡C++才用C++的,開發遊戲不用Delphi就只能用C++,實在是沒得挑了。
「不過我初三的時候就在學習C++了。那時候我在看《Visual Basic高級圖形程序設計教程》,想跟網上的人交流,但是我發現這些搞圖形的怎麼全都用C++?很鬱悶。所以為了看懂他們的代碼,然後抄成VB,我就去學習C++了。」
可是,他在書店裏找到的資料是 MSDN 里語法手冊的影印版,而語法手冊不是按照學習順序來組織內容的,對初學者非常不友好。
當年的他對此一無所知,花了很長時間才看完了第一遍。看的時候他想:「怎麼C++這麼難,才學習怎麼寫函數,接着就講template class的東西?」這當然也不可能實踐了,只能幹看。但他從頭到尾看了兩三遍,竟然真的能看懂一些了。
不過C++的強大註定了故事不會這麼簡單。看懂不代表能寫,上大學前,他的C++水平是「只讀」的;能寫也不等於會寫,上大學後,他學會了用C++的語法寫Delphi。
然而,就像鋪墊會等來轉折,那些Delphi的日子,他研究它的內部原理和實現細節,這幫助了他理解和掌握C++;後來,他又遇到了函數編程語言Haskell,領略了語法邏輯之美,從另一種角度審視和深入C++。
大學時期的作品
如今,他越來越得心應手,C++的強大帶給他的是自由,而不再是束縛。
「我現在很喜歡C++,因為C++表達能力非常強,而且能實踐type-rich programming,意思就是説,你要把函數的邏輯表達在類型上。如果將來函數本身做出了breaking change,那麼你的函數簽名也要有breaking change,就可以儘量跟Haskell一樣,通過增加編譯通過的難度,來減少debug的需要,跟測試驅動開發(TDD)異曲同工。
「現在我也很喜歡TypeScript,理由是一樣的。我以前非常討厭JavaScript,因為JavaScript沒辦法type-rich programming,所以直到TypeScript 2.9誕生了之後,我才開始投入時間。
「好的語法對開發效率的幫助是很大的,這直接決定了你開發庫的時候,API 的設計是否能準確簡潔地表達業務邏輯,從而又決定了使用這些庫的代碼的質量。所以一段代碼的質量,排除程序員本身水平的因素以外,完全是由語法決定的。
「自從沒事擺弄了很多語言之後,現在學起新的來也很快了。我一般都會從‘作者為什麼要這樣設計’出發,但事實上很多語言是他們拍腦袋搞出語法的,這就導致我只想用我喜歡的那幾門語言(目前是C++、C#和TypeScript)來開發程序,受不了別的。」
編譯時刷知乎,一不小心成了大V
高考之後要幹嘛,瘋玩?沒錯,但他的「瘋玩」和別人不太一樣。
他解放了!再也沒人説編程會耽誤學習和考試,再也不用躲在廁所裏偷偷看編程書了。
高考後的暑假,他愉快地將高三時開發的簡易 Pascal 解釋器重新整理,寫了幾十頁的設計方案,打印出來帶到了華南理工大學。那個時候,數據結構他只會用鏈表,而且編譯原理也好,設計模式也好,都還沒聽過。
他選的專業是軟件工程,剛入學,他就把那沓紙給班主任陳健老師看,她看完什麼也沒説,給了他一本編譯原理的課本。
他用裏面的知識做了第一個真正意義上的腳本引擎,參考了Java語言的一些簡單部分,還添加了一個編譯時自動把模板參數都改成Object類型的語法。後來Java添加了泛型功能,竟然也是這麼幹的。
大學的課堂裏、圖書館裏,他流連在那些抽象的概念與理論之間,回顧自己摸索時趟過的泥坑,很多疑惑豁然開朗。他一邊把高二RPG遊戲中的那些工具改進或重寫,一邊開始更深入的技術探索。
大學時期的作品
大三,他照着 JavaScript 做了一個沒有糟粕部分的動態腳本語言。也是那年,陳健老師找老同學幫他投了微軟的實習簡歷。
終於,他的編程水準要經受外界的第一次考驗了。
在電話面試中,他與對方聊了自己做的那個動態腳本語言,還就一些數據結構和框架設計的問題進行了熱情洋溢的討論。沒過幾天,他就收到通知前往上海面試。
現場面試時,儘管由於緊張出現了失誤,但憑藉紮實的基礎與能力,他順利進入 WCF Tools 小組實習。
白天實習,晚上,他利用空餘時間完成了一門純函數式語言,後來成為了他的畢業設計。畢業前的幾個月,他又完成了一個簡化後的C 語言的編譯器,可以在內存中生成 X86 機器碼並馬上執行。
上中學,高考,上大學或者去實習,無論外界的環境如何變化,他執着於給自己的編程世界添磚加瓦。
2008 年10月,次貸危機波及了微軟上海。11月,他的實習結束了,而等待他的是收緊的轉正名額。儘管如此,他成功通過了五輪艱難的面試,正式成為微軟的一員,開始了他的職業生涯。他從微軟上海,到微軟亞洲研究院(MSRA),再到微軟西雅圖總部工作至今。
編程陪伴他的歲月,決定他的職業,塑造了他的生活,甚至性格的某些部分。讓他安靜下來,不在乎外界的雜音,投入於喜歡的事,並且選擇自己喜歡的生活方式。
工作後,從 2013 年開始,他的時間支出表上增加了一個大額新項目:刷知乎。迄今為止,他憑藉獨特的個人風格,在知乎共收穫 2773k 贊同,擁有 805k 關注。
除了程序員外,知乎大 V 成為了他另一個身份。一方面,這意味着大量來自外界的關注;另一方面,如果用時間來劃分地盤,知乎以碎片化的優勢積少成多,地位直追編程。
面對這些變化,他的心態很平穩。
他説:「上面有提到,我從一開始就是泡在論壇上的,後來一路換過來,最後到了知乎。我覺得跟我互動的人多了,完全就是因為現在上網便宜了,泡在網上的人多了,這是很自然的。
「至於説寫代碼的時間減少的這個問題,我更多的是覺得,現在精力不如當年了。以前我真的可以做到平均每天 8 小時寫代碼,但是現在寫不動了,連續寫那麼久代碼會覺得頭腦發暈想吃飯。
「不過一天 24 小時又不會因此縮短。我剛好還是一個睡眠時間不長的人,每天睡 6 個小時,第二天滿血復活,所以迫切需要很多事情來填補活着的這段時間。我覺得休息的時候有個知乎可以刷,挺好的。刷膩了我就玩遊戲,跟老婆和網友們到處找新的館子吃,生活非常充實。
「其實並沒有緊迫感,自己的項目又沒有 deadline,但現在胃口大了,開發什麼都很久,恐怕壽命是不夠用了。想想到時候快死了,腦子裏還有很多東西沒有親手試一試,覺得人生最慘的事情莫過於此了。」
他坦然接受生活帶給他的每種可能,踏出自己的代碼國度,在外界與自我之間尋找一種平衡,但他對編程的熱情從未改變。
大一,他就用OpenGL寫了個遊戲用的GUI庫當C++的大作業;大二,他開始設計一個基於OpenGL 的、面向軟件開發的GUI庫;大三重新學習如何封裝包含Vista新功能的Windows API;大四學習如何開發一個control template與control分離的新的GUI框架。到了開始工作之後,知識基本準備好了,他的GacUI終於立項了。
GacUI是一款在架構上跨平台、支持控件與模板分離、靈活的數據綁定以及全面支持MVVM模式的C++ GUI庫。GacUI在Windows上使用DirectX進行硬件加速,並且下一個版本計劃支持macOS以及wasm。
另外,開發GUI之餘,他在腳本引擎方面也在不斷地學習和進步。大一結束後他實踐了垃圾收集器;大二實踐了動態語言;大三實踐了帶惰性計算和類型推導的函數式語言;大四實踐了到機器碼的編譯。
這些項目跟隨他從大學畢業到工作,直到現在,不僅記錄了他技術的進步,熔鑄了他的理念,並且促使他不斷地向遠方探索。為了將GacUI移植到瀏覽器,他的學習範圍還延伸到了前端領域。
Github提交圖 來源:https://github.com/vczh-libra...
「關於GacUI,我的設計理念曾經有很多,但是凡是跟XAML不一樣的,最終都證明不如XAML,所以現在就是跟XAML無限靠近了。我做出的唯一改進是關於Data Binding的這部分。XAML只支持非常有限的語法,你自己要乾點什麼複雜的都要MVVM+DataBinding+resource+converter一套搞下來,煩得要命。所以GacUI支持你用任意表達式來做單向binding。逆一個表達式實在是太難了,所以我沒有支持雙向binding,雙向嘛,不就是單向寫兩遍。
「以後可能會用GacUI來寫一些小IDE吧,做做intellisense啥的,順便把代碼共享進VSCode當插件。我覺得很有意思。
「在開發GacUI的過程中我還接觸了很多東西,譬如説如何給C++做反射和讀C++代碼裏的註釋生成文檔,這個我就做了好幾遍,每一次都比前一次有了巨大的改進。就算是網站本身,我也通過開發它學習了一些內容。之前我還想過要把GacUI搬到瀏覽器裏,現在我想用wasm來實現,但是之前我竟然覺得可以用JavaScript重新實現一遍,所以學習了很多HTML 和CSS的排版知識。
「一個人親自開發一個複雜的東西,可以得到探索其他領域的動力,我覺得這已經成為了我的習慣。」
最後,他總結説,自己學習編程只有一個秘訣。
「學習編程,只要你能在過程中感到快樂,那所有的問題都是不存在的,你就會不斷地把時間投入到學習編程中去,時間積累夠了再怎樣也熟能生巧了。所以我一直都説,平均每天寫 8 小時代碼,從大一做到大四,不可能學不會的。有些人曾經提出反對意見,但歸根結底都是因為他們不認為編程是這個世界上最有趣的事情。」
編程時間線
- 2000年 初一
- 他第一次接觸計算機和編程
- 2004年 高二
- 他發佈用Delphi寫的RPG遊戲
- 2006年 大一
- 他用OpenGL寫了個GUI庫當C++的大作業
- 他實踐了垃圾收集器
- 2007年 大二
- 他開始設計一個基於OpenGL 的、面向軟件開發的GUI庫
- 他實踐了動態語言
- 2008年 大三實習
- 他照着 JavaScript 做了一個動態腳本語言
- 實習時,他還完成了一門純函數式語言
- 2009年 畢業轉正
- 畢業前,他完成了一個簡化後的 C 語言編譯器
- 2011年10月
- 他的C++圖形界面庫GacUI項目正式啓動
- 2012年3月
- GacUI項目開源了
- 2012年7月
- 他為GacUI做了一個網站
- 2014年
- GacUI主要功能完成
- 2017年
- GacUI現在的架構完成
- 2018年9月
- 他開始為了GacUI的文檔造含C++編譯器前端的網頁生成器
- 2019年8月
- 他將GacUI的網站gaclib.net重寫
往期訪談
寒冬 winter:代碼無捷徑,只怕有心人
C++之父Bjarne Stroustrup: 簡單的表述方式才是最優的方案
“龍書”作者Jeffery Ullman:相信你自己,自由地思考
《七週七併發模型》作者Paul Butcher:這是一個激動人心的編程時代,也是一個帶有很大不確定性的時代
想看看輪子哥的GacUI嗎?
戳一下這裏!