陈梓瀚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移植到浏览器,他的学习范围还延伸到了前端领域。
「关于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重写
往期访谈
C++之父Bjarne Stroustrup: 简单的表述方式才是最优的方案
“龙书”作者Jeffery Ullman:相信你自己,自由地思考
《七周七并发模型》作者Paul Butcher:这是一个激动人心的编程时代,也是一个带有很大不确定性的时代
想看看轮子哥的GacUI吗?戳一下这里!