普通视图

发现新文章,点击刷新页面。
昨天以前Ground Oddity

二十岁不再来

作者 Jiahao Cai
2025年12月20日 00:00

二十岁不再来

Dec 20, 2025

Reward function of life

两个月前过完了三十岁生日,算是正式告别了二十几岁的自己。生日那天好像没做什么特别的事,那天早上去上海大使馆签证了,满脑子只想着生日能否给签证带来好运,以及如果签证官发现了今天是我生日,怎么跟ta谈笑风生一下。回想二十岁的生日,感觉同样的模糊,只是更加久远,根本想不起来自己做了什么事。
但是内心还是会觉得有所差别,好像二十岁的时候一点也不觉得二十岁是个大事(也许当时觉得十八岁才是?),但是三十岁的时候就不免开始感叹时光易逝。

十年前是2015,我大二,当时应该正好从一个叫电子信息工程的专业转到软件工程。当时转专业的理由非常遥远 – 虽然这理由呼应了此时此刻的我 – 我觉得我以后想创业,但是玩了几下单片机之后觉得太贵了,贫穷如我如果要创业岂不是得从别人那里拿好多钱,我不喜欢从别人那拿钱的感觉。思考了一下感觉软件的分发基本没啥门槛,一台笔记本写写代码足够了,这就给我构建了一个足够自由,足够有想象空间的未来。当时有个老师对我特别好(就称她为M吧),她不想我转专业,理由就是她觉得原来专业的生源比较好,我没有听进去。等到我毕业的时候,她说,“你转专业真是转对了” :)

翻看2015年的邮件,虽然当年也不全是用gmail,但是里面的邮件已经足够有年代感了

  • 看到了朋友邀请我去知乎答题(虽然我一次也没去,邮件里的邀请链接都挂了)
  • 看到Amazon.com和Amazon.cn轮番给我发邮件,我买了kindle,又买了好多书,最后看完有印象的好像只有王小波,余华,三体以及毛姆
  • 看到一堆充值邮件,都是因为自己在玩炉石
  • 去了一趟成都,第一次住Airbnb
  • 看到自己在时光网上(Mtime)买电影票,还看到有个趣玩网,当时很喜欢去上面买礼物,刚打开看了却发现上面只买一款萝卜干
  • 尝试去做了一次英语家教,发现自己毫不擅长,于是那也就是最后一次
  • 看到自己转专业之后的迷茫,懵懵懂懂之间开始看安全相关的东西,反汇编和逆向之类的
  • 在某个网站开始写blog,我还记得有一篇是关于django的,记得是因为那好像是第一次得到了现实中的正反馈
  • 跨年去了南京,和小季一起看了李志,那时候觉得李志是最能与我心中的撕裂产生共鸣的

这十年里最大的感触还是学会了打破心中的枷锁。再往前十年,10-19岁之间,我学到最重要的东西就是“不做自己不喜欢的事,不勉强与不喜欢的人说话”,比如不喜欢的老师的课就不听,不想理会的人就不理,可是我不知道如何“找到自己想做的事”以及“找到方法去做自己想做的事”。因为自己想做的事总没有那么简单,于是当年的自己的第一反应总是退缩

  • 比如觉得高中上课的老师很垃圾,但是觉得好像只有我会对此表现出非常不满,所以我觉得根据学生的意见换掉一个老师根本是天方夜谭。后来去了北京上学,才知道这不是我的呓语,M就对我说她和其他家长联合起来帮自己的女儿换了老师
  • 比如其实很想出国去上本科,但是却又退却,因为总觉得需要的钱太多。后来才知道,出去其实有很多更经济的办法,可惜当年的自己并没有去了解过

至于为什么退缩,那又要再往前追溯十年,甚至追溯到父母的经历。可是我不想讨论这些,因为那时的自己太小,无法改变任何事情,况且我也接受那一切,接受人生的开局 :)

打破心中的枷锁,才能进而打破头顶的天花板,拥抱世界的无限可能。比如你可以直接给Jeff Dean,Geoffrey Hinton发邮件,没有人会阻止你 ;)
人们喜欢把世界分层一层一层,阶级/等级/平台,种种种种,其实很对,人们总是活在一个同温层里。每个人都被父母放在不同的同温层里,看起来有高有低,但其实只要在了,活在同温层里是最简单不过的事,少数的是往上或是往下走的人。我因为自己的迷茫和随性,往上往下两件事都干过,往上是因为不喜欢身边的人,觉得很无趣,聊不到一起去;往下是不知道自己该做什么,把寻找痛苦当作快乐,后来二十多岁才看到王小波,才发现痛苦不必是自己的痛苦,认识有趣的人也不必拥抱他们的全部。

在层级之间穿梭之后,回望来时路,我感到恰到好处的疲惫,我对自己做过的所有决定总体感到满意,我也依然被前方的未知所吸引。二十岁不再来,第一个我能真正做点什么的十年已经离开了,我感到自己好像孤身一人站在萧瑟的秋风里,但是闭上眼我又为这十年感到庆幸,也迫不及待想要开启下一个十年。

信仰之跃

作者 Jiahao Cai
2025年10月18日 00:00

信仰之跃

Oct 18, 2025

Stan in South Park, 5 years old

献身

上一篇文章中,我提到过自己在23年为什么可以放弃眼前升职的胡萝卜而转组,我真的就是被ChatGPT的能力震惊了,我觉得围绕着这样一个全能的模型可做的事情太多了。众所周知,搞科研里最高级的就是挖坑的人,其次是填坑的人,再次是排列组合100种相关工具,然后论证这些工具组合能不能把坑填上的人,工业界只能说是再再次了,low low hanging fruit,比排列组合还low。

ChatGPT这种模型的诞生,绝对是挖出了一个大坑。我的科研属于很烂的那种,但是我立马就能感受到了这新模型带来的冲击,新的范式肯定要来临。拿这新东西在程序分析这个领域怼着旧问题就能发100篇paper,何况这模型又是个全能选手,那不得在各个领域都翻江倒海。如果科研能搞出一堆排列组合来,那工业界绝对能创造出指数级的活来 – 不管这活是真的还是假的,有意义还是无意义的。

线性思维与路径依赖

转组两年,升了两级,看似很快,再回头看只觉得自己是中等情况。随便列几个潜在的更好的选择吧:

  1. 预判了大模型的前景,却无法预判它的前景能持续多久 – 如果早知道会持续这么好几年,就应该早点多放精力把自己多向ML倾斜。偶像Greg的雄文 How I became a machine learning practitioner 我可是读了好几遍,除了佩服还是佩服。我打开过deep learning教程,也打开过Andrej Karpathy的古早教程,最终都是开了个头就放弃了。
  2. 预判大模型前景用的是boolean(好/不好),而不是float(0-1) – 傻傻地只知道这东西好像好,但是不知道到底有多好,就好像隐隐约约看到地上有个坑,却看不清这坑有多大有多深。如果早知道这坑这么大,就应该跑步前进。
  3. 过度迷恋大公司的ladder,而ladder是个线性的东西 –当时觉得人就应该线性地成长,从3到4到5到6,正如他们规划好的一样。第一次升6失败的时候,伤心了几个晚上,其中一个晚上我投了Anthropic和OpenAI。过了一段时间之后,我都拿到了面试,但是我又没心情去准备,因为在忙着做升6的项目(我是发自内心地喜欢那个项目),然后不出意外地都挂了。其实OpenAI还挺想要我的,连续给了我不同位置的两次电面,可惜当时我的眼里只有升职。

写到这里我突然发现其实我也是陷入了路径依赖,说起来是特别感谢前辈能来美国留学,趟出这么一条安全的留美道路,如果不是可以借鉴前人,我根本就没可能来。这条道路走得人越来越多,也越来越清晰,美国留学,读CS,刷刷题,进大厂,爬梯子,过有wlb的生活。前面几点带给我的大概只有好处,但是我注定无法走完这条路,因为最后两点使我无法忍受。
爬梯子是一种线性的确定性的东西,是一眼就能望到头的生活;而wlb则是一眼就能望到头的生活的小小调剂。仔细一想,这不就是公务员生活的美国版么?当然在美国不需要维护复杂的人际关系,可以过更简单的生活,所以美国大厂生活 -> 公务员生活(纯净版)。

这种线性思维会在看到空降的高等级年轻同事之后坍塌,当然也有人会选择无视,你永远可以(应该)无视outlier,除非你想当那个outlier。

恐高症

工作的第一年我就开始炒股,然后立马就亏了一辆车,这辆车的钱在后来的五年里也没有赚回来。其实不是没有赚回来的机会,机会出现了很多次,但是每次都在股票涨到一半的时候卖了,俗称卖飞了。这么多次卖飞的经验终于使我意识到,我有恐高症。
股市里的恐高症其实特别容易理解,因为我就是股市白痴,没有金融知识。股票涨的时候不知道为什么涨,跌的时候也不知道为什么跌,更别说去预测股票什么时候涨停了。对我来说炒股可能就是图个乐子,卖飞了也都会原谅自己。

但是我发现我的恐高症不止于股市。

首先,我真的好像有恐高症,如果我在高楼上往下望,我会不自觉地手心出汗,然后很想要移动,往前往后都好,只要移动就行,虽然大部分时间更想往前移动。

其次,美股还是比较反映现实的,对美股恐高,就是某种程度上的对现实恐高。我在24年年初搞出的那个agent,在24年Google I/O 发布了,之后便是一大段跌宕起伏的心路历程:

  1. 刚发布的时候,毫无波澜,根本没用户,当时特别沮丧,显得所有的一切都是我们的一场巨大的自high。现在回头看,当时一是不知道marketing的威力有多大(我们根本就没用),二是这个世界真没准备好。
  2. 24年下半年,agent的声音多了起来,但是我一看,全都是我们之前试过又丢弃的想法。当时觉得有点兴奋想创业,但是很犹豫,一方面是自己不会没经验,一方面是虽然agent好像比之前火了,但是还是不能确定它的价值(或者说自己不懂怎么衡量它的价值)。
  3. 25年上半年,agent成了最火的词之一,可是我看来看去,却发现大部分都还是在做我们丢弃了的想法,少部分厉害的复刻了我们的想法,更少地实现了一些我们之前提出的猜想。我突然觉得整个业界好像都在重复我们做的东西,但是我们好像没有拿到太多名声,感觉有点悲伤与后悔。但是那时候的想法还是谦虚的,就觉得业界在重复我们一年多前提出的想法了,也说明业界跟上了,那业界聪明人这么多,新的范式应该也不远了吧。与此同时,我实在想不出来下一个新东西是什么,那如果我现在出去兜售我的旧想法,虽然还是最先进的,但是大概分分钟就会被新的想法给干翻了吧。再加上那时候升职了,感觉可以休息会儿,创业的心又收了回去。

25年下半年,也就是现在,agent依旧很火,但是火得大家有点疲惫了,因为没有什么新的突破。为什么呢?因为业界依然在重复那些旧idea,真的难以置信。此时的我,第一反应就是AI的泡沫要破了,这种没有突破的东西居然能喊这么久,那更不应该出去创业了,感觉应该在大公司好好待着。可是又总觉得有什么不对劲。
是恐高导致失去的机会,是拿到机会了没有all in失去的那部分,是成长过程中培养起来的对风险的厌恶。我大部分时候就是这种人,美股,尤其是AI股,涨起来了就觉得AI泡沫要破灭,美国要重蹈2000年互联网泡沫的覆辙。可是事实呢,我不知道以后,但是我确实是被美股/AI股一次又一次地打脸,很多时候我在想,这种恐高症到底影响我生活的多少方面?
我觉得我高估了经济的脆弱性,低估了AI的潜力,高估了业界的水平,低谷了自己的能力。我不知道怎么修复这件事,就只好祭出我从yinwang那里学到的最简单粗暴的方法:

在一而再再而三的上当受骗之后,我终于把所有的权威们从我的脑子里轰了下去。也许有时候轰得太猛烈了一些,但总的说来是有好处的。

其实就是“求其上者得其中”的另一面,不用盯着那个完美的平衡,甚至可以把矫枉过正当作目标,最后也许会落在最接近中点的地方。我曾经实践过这个方法了,完全同意yinwang说的,“总的来说是有好处的”。现在我想要再实践一次,我依然恐高,但是我想试试在高处活着,而不是因为恐高就活在了低处。况且,如果不在高处,怎么纵身一跃呢,小跳罢了 :)

困境

聊过很多公司,一个也没去,整体趋势就是公司越聊越小。比如最早考虑同等级大公司,然后变成独角兽,再变成A/B轮左右的,最后只想找五人以下的小作坊,跟founder聊聊天就能进的那种。人多的公司(包括OpenAI,Anthropic)其实面试也流程化了,聊着总是兴奋不起来,而且我也实在没心情准备面试这种傻叉东西,实在是浪费时间。

其实也聊过几个小作坊,包括一个同事的朋友的公司,非常早期,刚融完seed,说是那一届YC最强,两个founder也是都创业且成功退出过。因为是同事的朋友,所以聊起来进展特别快,当时我甚至在阿拉斯加旅游,但是忍不住继续聊,可是聊了一通之后最后还是没去。我给的理由就是钱太少了,但是其实有更深层的原因。

我想了三个挑选早期startup的原则,按优先级分:

  1. 人。你是否对公司里的其他人有信心,你是否享受跟他们一起工作?早期startup抛弃现有的产品迅速转向属于是家常便饭,如果公司转到了一个你不喜欢的方向,你是否还对公司有信心,是否还会享受在公司继续工作?最佳情况就是找到一帮人,大家一起无论做什么都是开心的,充满希望的。可是这本身就是一个悖论,如果你不加入公司和他们工作过,那又怎么知道会不会享受和他们工作呢 – 除非你们早就认识且在一起工作过。这也是我拒绝那家startup的原因之一,我其实和公司里所有的人都聊了一轮,包括两个VC。我发现那几个员工都是两个founder的朋友,他们都与founder在一起工作过,他们之间的信任是另一个层级的。如果我加入了,我会成为那个公司的第一个“局外人”,这感觉就像别人一群互为朋友的陌生人在开party的时候邀请了我,这种局我是没什么兴趣的。
  2. 产品。你是否坚信这个产品的前景?你是否对这个产品有自己的见解?你是否能对这个产品带来特殊的或者巨大的贡献?如果公司明天决定转向,你是否会失去热情,进而离开公司?
  3. 共同成长。这里主要说的其实是钱,founder吸引员工的时候往往会说公司有多少多少潜力,五倍十倍二十倍地吹。但是其实这就是一个反问的好时候,如果公司涨了十倍,但是你却没有拿到你心中的数字,那岂不是就像个陪跑的人?所以公司占股必须够多,饼必须够大,否则根本没必要加入。

简单来说,那个startup也许是有前景的,连续创业的founder加上不赖的赛道,但是基本与我没什么关系,我去的话只能拿三分之一的工资外加有限的股份,没什么意思。

退后

大厂的金手铐是真实的,我属于想得比较开的了,不在乎什么大厂的名声(待了五年也够了),其他小恩小惠的福利对我来说也像是鸡肋,从来也懒得薅,但是钱却是真实的。在我拒绝那个startup之后不久,就得到了来自公司的正反馈,VP神神秘秘地给我拉了一个15分钟的会。我当时就想,这到底是要把我开了还是要给我额外发钱,因为一月刚给我发过一笔,当时也是拉了这样一个会,如果再来一笔那是不是too good to be true了,感觉还是要把我开了的概率更大。

结果还真是发钱。可是跟一月份发钱的开心不同,当时我工作得很开心,额外的钱就像在精美的蛋糕上又插了一块写着表扬的话的巧克力,收到钱之后我很感激,也表示会继续留下来。但是事过境迁,半年下来可以发生的事真是太多了,我没有从前开心了,而且也聊了好几个公司了。于是我就经历了可能是我人生中最漫长的15分钟:

VP:给你钱,这次这笔不像上次那样需要等两个月,这个月底就能发到你手上
我:谢谢老板。可是...
VP:可是什么?
我:可是我已经有offer了,而且我也在认真考虑
VP:是公司外面的offer吗?
我:公司内公司外的我都有,我主要是觉得组里好像有趣的工作就做完了,我不太知道接下来该做什么
VP:我不同意,我觉得还是有很多有趣的工作可以做的(外加一通举例他手下别的组的项目)...如果我手下有别的你感兴趣的组,你也可以过去
我:好,我会好好考虑的,多谢

走出办公室的那一瞬间,其实还挺轻松的,因为这几个月确实做得不是很开心,感觉公司里的政治不可避免得影响到了我。但是其实自己的行为很傻叉,因为我根本没有offer,没有内部的offer(我以为我即将会有),也没有外部的offer(甚至都没开始面,只是朋友想叫我去),我就是觉得我不想待了,我无法给出虚假的承诺。我知道我想走,虽然不知道什么时候,也许下个月,也许再过半年,也许再过一年,总之我做不到先答应下来。

但是没过多久,等我冷静下来之后,我就开始担心,他会不会撤回这笔钱。虽然我觉得我做了对的事情,但是代价好像有点大,我回去打开电脑一看,好家伙,原来每个月有这么多钱,每个月发的比我所有还在拿的股票加起来还多,于是突然开始有点后悔自己怎么就说了实话。
接下来几天更狗血的事情发生了,过了两天,那笔钱从系统里神秘地消失了,我觉得那笔钱肯定被VP撤回去了。我束手无策,我又不能回去找VP说,“我现在决定留下来了,麻烦你把钱还给我吧”,因为我还是想走,我没法现在特意走回去找他撒一个谎,或者就此放弃离开公司的想法。与此同时,公司股票开始暴涨,那笔钱直接涨了四分之一,我直接陷入了深深的emo。
在那个场合下那么诚实,其实就一个好处,我自己心里能好受点,将来跑路的时候也好受点,其他的全是坏处。而且仔细想想,VP一月刚给我发过钱,那笔钱分两年拿完,当时我可是答应得好好的说会留下来,但是八个月之后,我还不是又开始计划着要走了。所以当时在办公室里说自己会留下来好像才是最优解,可是我就是喜欢直来直去地说。

我努力尝试处理这种沮丧的情绪,于是我找了一个我很信任的同事,我说我好像做了一件很傻的事。我把事情一五一十地告诉他,说着说着突然就哭了,因为我工作了五年攒的钱和这笔两年发完的钱差不多多,觉得好伤心。这是我第一次因为工作上的事情哭了,好像也是记忆里第一次因为钱哭了,我想更深层的原因是,我觉得我被撕裂了,被“做我觉得对的事情”和“赚更多钱”这两件事撕裂了,我当时无比地倾向于后者但是却又讨厌那样的自己。同事说,“你要想清楚,如果你真的要留下来,我可以去找VP说,但是你要想好,就只有这一次”,我说,“没事,不用了,我只是想找个人说说话”。我只是想找个人诉说那种被撕裂的痛苦。

接下来的几天,我更努力地处理这种情绪。
一方面是钱,我开始思考,我是否真的马上需要这笔钱。我找了很多现在不需要这笔钱的理由(借口),我就觉得其实我每年发的工资好像也用不完,多拿点钱好像也就是存着,有点复利;另一方面来说,就算是钱一分不花,全存下来,这种线性增长好像也没什么可令人兴奋的。所以想到某天有个VC对我说的,除非你在大厂每年赚2m以上,不然你出去创业的期望都是正的。我从来没算过她这句话对不对,但是用在这个场景下反正是令我心安了一些。
一方面是人,我觉得这次回绝,我都不知道该怎么再在组里待下去。每多待一天好像都是亏的,因为我本可以拿着更多的钱待着,而且我每天都会想到我们VP说不定已经当我走了,难受。但是好像还是VP先打破了沉寂,他发邮件给几个人说过两周有个summit,要我们过去搞一些poster展示一下以前做的东西,我也就回了个好的,好像什么都没有发生过。他真的是个很好的人,可以理解容忍我的“不尊重”。但是换个角度,我会想到乔布斯说过的话

在我的生涯里,我发现最好的人才是真正理解产品内涵的人,他们非常非常难以管理,但是你要忍受这些,因为他们是如此理解产品的内涵。这就是做成伟大产品的关键,不是管理过程,而是产品的内涵

我既然自我感觉良好地觉得自己还不错,那肯定也不是最好管理的那一种。

几天过去之后,那笔钱突然又出现在了系统里,像一个玩笑。不过经过那几天的思维实验,我好像全都放下了,我心安理得地接受各种结果,这笔钱在与不在,好像与我并没有什么关系。但是我还是开心的,开心的是我可以拿到激励我的钱,尽管我不会待太久;开心的是我向VP的坦诚好像并没有错,我可以心安理得地拿钱,然后随时走人。

吸饱的海绵

在写这篇文章的某一天,我在看离职手续都有什么,突然看到自己在公司已经差不多2000天了,五年半还要多,就想到了自己在这篇文章里写到的“没人值得你学习四/五/六年”。这五年半里,我在第一个组待了三年,其中包括第一次进入工业界的迷茫,第一次加入公司的兴奋;第二个组待了两年半,感觉自己从半成熟期直接蜕变了,接触了很多新鲜的想法,接触了很多产品,接触了很多比我年纪小的人。

我想也是时候到此为止了,我还是同样的观点,没有人值得我学习四/五/六年,谷歌作为一个公司,我已经在其中待得太久,如果不此时离开,我也许就要被他捕获了。

从另一方面来说,我也已经看到了很多谷歌的问题。就像上文提到的心路历程一样,我觉得那个VP很好,早期招募了很多很好的人,也可以容忍奇奇怪怪的人存在,整个队伍的气氛也很好。这里我又要引用一下乔布斯说过的话

当你费尽心思找到了足够多的人才(A player)时,你会发现他们真的能够很好的互相合作,因为他们之前从来没有得到过这样的机会(与这么多优秀的人一起共事)。他们也不会想要和水平不够的人共事(B and C player),于是就会形成一种自我监督,他们只会招募更多的人才。招募一些好的人才,然后这种情况就会在公司中传播,最后公司里就会充满了好的人才,这就是Mac团队的样子。

我不知道乔布斯招错过多少人,又因为招错而开掉过多少人,我觉得肯定不会少。谷歌的问题在于你没法在招募上犯错,特别是等级比较高的人,因为谷歌冗长的流程,你不得不依赖他们来帮助你招人。在一个需要迅速扩张队伍里,比如AI时代的队伍,招进一个B/C player就是毁灭性打击,因为他们会迅速地找到更多的B/C player。也许是因为能力不足导致的心虚,这种B/C player往往也喜欢招更多的人,干什么事都带着一帮人吵吵闹闹的。根源还是谷歌没办法开人,招进来就是招进来了,开个人可能要半年一年,说实话,这么多时间,LLM都能迭代好几轮了。此时唯一的缓解办法就是要更多的招人名额,然后期望能招到对的人,可是招人名额也不是这么容易拿到的,于是人可能就被淹没在茫茫多的流程里 – 这里我甚至一句都没提到关于开发产品的流程之繁琐。

我因为在队伍里的声望以及上面的信任,还是可以在队伍扩张中做一只独狼,做自己的事。可是当自己看到队伍里自己认识的人越来越少时,我觉得没有必要再待下去了。

烧掉标签

好吧我承认这一段也许只是我想表达我的疾世愤俗。

很多人都喜欢用各种标签来打扮自己,什么学校,什么公司,什么会议,什么名人,各种各种,这些标签使我上当受骗了好多次。第一次是我的高中,第二次是我的graduate school,第三次大概就是谷歌了。不能说这些地方一无是处,但是庸人们聚集起来使用这些标签,就彷佛是一种大型壮胆活动,过度美化这些标签,生怕别人知道了其实里面经常也是一滩烂泥。

我好像总是这样,看到一个标签,想要,得到了之后却发现其中问题重重,于是又想要撕掉。我知道世界并不完美,可是我还在找寻能让我满意的地方。

在当下这个时间点,我只觉得谷歌的标签很蠢,蠢的程度随工龄的增加而飞速增长。

再一次献身

xxxx

钱,想明白了吗?500k是一次很好的思想实验

看着业界重复着我做的东西,我感到非常遗憾,遗憾自己没有变成一个领头人

我无法回到过去,我手上也没有当时那样的牌了,可是我是否就应该就此沉睡?我能相信的只有我自己,和当时的那个团队,我相信自己可以再次做出领先于世界的东西,而这一次,我不想要再失手了。

可以看出,我好像已经不在最优势的那个位置了,甚至可以说是一无所有了

信仰之跃

写了这么多,其实觉得自己quit是很难合理化的。放弃了明年的百万年薪,想到自己在25年对小季说了很多很多次,“好想一年赚一百万啊”,但是近在咫尺的时候又放弃了,好像我看到系统里显示明年即将拿到这么多钱,跟真的拿到那笔钱,是差不多满足的,又或者,放弃这笔钱让我觉得更满足;绿卡还有半年排到了,换公司多出一堆麻烦事,也不知道能不能在排期到的时候及时拿到绿卡;现在自己也并不掌握什么核心技术 – 业界基本都在同一起跑线了,感觉只剩自信了呀。
上个月回国找了好几个人聊,我说自己很享受对这种(即将或已经)拿到的东西说不要就不要的感觉,我觉得很爽。聊之前我真心地因为大多数人都会认同说这很爽,但是大家好像都觉得我太作了。我现在觉得大家都是被生活鞭打过了,变成了王小波笔下被锤了的牛,而我他妈只想永远生猛下去。

最近一年多听了很多Leo王,他唱“希望自己死时自己手里握的不是老二是麦克风”,他唱“死前一秒我还在巡演的路上”,我想我们是一样的人。

Google 五年

作者 Jiahao Cai
2025年5月10日 00:00

Google 五年

May 10, 2025

Stan in South Park, 5 years old

前情提要

有必要吗?好像是有的,因为上一篇文章写在2022年,而且它是一篇不知所云凑字数的傻叉文章,上一篇正经文章其实是在2020年,一转眼就是五年。

简而言之,五年半前我愤怒地,意料之外情理之中地,决定不跟学校的导师继续做研究了,然后转到了学校的master项目开始找工作。当时找得比较困难,因为决定做得很突然,刷题不熟,校招也关了,后来还遇到由于新冠导致的各种停止招聘。但是经过一番折腾之后,还是拿到了Google的offer并且决定去了。

不知不觉下个月就是我来Google五年整了,在此期间我升了三次职,从L3升到L6,说实话是超出了自己的期待的,因为当时毕业了憋着一口气也就是觉得要在本该PhD毕业的那一年走到更高的一级(L5),结果一不小心升多了(笑),再想到自己当年的导师还没拿到tenure,不禁又偷笑一下。

但也就只是笑了一下而已,笑完之后就又陷入迷茫与虚无了,和以前并没有很大区别。

比学校好一百倍的“延伸”

回忆了一下自己从3升到4的过程,想了想跟自己刚进Google三个月时写的描述没啥区别。总结一下就是,节奏很慢,没啥期待,是个疗伤的好地方,如果觉得自己能力够了想早点升职就得不停地去催manager。

What if

当然今天回想起来还是会有一下遐想,比如说,当时因为COVID我一直在东海岸远程工作,天天睡到11点醒来在床上开会(听会),一两年内几乎只跟两三个人说话 - 如果当时我一入职就去湾区了会怎么样呢,我是否会每天八九点就到办公室,然后找几个人扯淡呢?

再回想自己在graduate school的时光,一大败笔就是由于太忙,几乎不跟人说话,然后自己又不懂,以至于花了很久才意识到自己导师是个PUA大师,早该远离的。想不到工作之后又(被迫?)犯了一样的错误,一方面是COVID被迫远程工作,一方面也是自己内向的属性,不愿意迈出舒适区。其实当年如果能和更多的人多聊聊,也许我就能早点弄明白下面要写到的“升职困境”了。

暴论一条

暴论:招人只招本科生,如果招不到的话,高中生也可以。

加入第一个组的时候很开心,因为80%的同事都是PhD,我觉得自己半路出家也能来这个组,很赚。现在回想起来就是俗人行为,太拿title当回事。当年在学校的时候就觉得身边读PhD的大部分都傻了吧唧的,凭什么觉得来了Google这里的人就会不一样呢?首先,这些人没那么强;其次,在Google,99%的工作不需要你的PhD学位。

当年我进组之前还问过一个已经走了的实习生,问他组里怎么样,他说人还行,就是年纪都比较大,聊不到一起去,有点无聊。前两年我一直远程工作还没觉得,直到我搬去了湾区,去那个办公室上班上了半年之后我就转组了,实在是难受。一方面那个楼很烂,没有阳光,且食堂可以竞争全Google最差食堂;另一方面办公室总是死气沉沉的,充满了一种说不上来的沉默。

后来我得出了一个残酷的结论,这里的工作用不到你的PhD知识,不管你是真强还是假强,我不在意,Google也不在意,因为你大概率是能把工作做完的。可是你读PhD时留下的trauma,却让我很在意,这个学位磨平了你的棱角,消磨了你对生活的热情,而它们对我来说很重要。我是如此介意以至于每次我经过那个办公楼时都觉得上面有乌云笼罩,一朵朵都是Sys PhD经历过的trauma(大部分Sys PhD怎么熬出头的应该不用我多写)。

后来去了新组,好多本科生,聪明又明亮,每天都带给我很多笑容。作为一个经历过短暂trauma的人,我很羡慕他们,跟他们工作的每一天都像是一场治愈。我片面地觉得,快乐教育是真好 :)

升职困境与ChatGPT

去Google工作了几个月我就感觉凭自己的实力,一口气升到5绝对不成问题吧,可惜并没有,然后我也搞不清为啥,摸不着头脑。后来片面地总结了一下,这组里大部分进来都是4,然后普遍要花三年才能升到5,好像是个传统?当然后来我知道这是一种资源与政治的综合原因。可是我不愿意等三年,我对排队升职这种事一点兴趣也没有。可是我当时已经在这个组当4当了一年半了,实在是一个尴尬的时间,好像再努力一把或者再多给一点耐心,也没什么大不了的。

当时我正在做的一个项目是基于语义的code diff,这件事不能说难,大方向上都已经有较好的理论支持,具体效果我来实现的话应该也差不到哪里去。但是有一个问题,就算算法分析出了两个文件是相似的,渲染的结果却非常难读,因为算法为了对抗代码混淆,一般是忽略变量名这种细节的,而渲染的结果是基于某种text diff算法的,所以渲染的结果里只会有大片大片的不同。当时对自己这方面的工作不是很满意,也经常在想怎么样自动化重命名反编译出来的变量名才算是合理的。

就在这个时候,ChatGPT横空出世,我们大组举办了一场hackathon,我的项目很简单,就是把混淆的代码丢进去,让模型根据上下文重命名变量,后续也很简单,我直接转组了。GPT3.5这种逆天的能力,是传统程序分析里从来没见过的,也许之前有过特制的模型可以较好地重命名变量,但是从来没有过一个模型可以整这么多花活,上能写诗,下能分析恶意软件。与此同时拥有这种逆天能力的门槛低得令人发指 - 只要给OpenAI交钱就行。 不得不说,quit这种事就是一回生二回熟,有了之前quit PhD的经验,那是quit起来已经得心应手了 - 在Google,转组就等于重置升职进度,但是我想,不就是一些沉没成本吗,无所谓。我当时的manager极力挽留,甚至承诺我当时那个季度就去升职,但是我的心已经飞向了LLM,不会回头了。

意外之喜

我离开那个组之后,接替我的人发现我做的有个项目很给力,于是他提出要帮我写一个proposal去评奖。于是我就把各种文档以及数据丢给他让他写,不得不说他写得挺好的,最后我们有了一个双赢的结局 - 这个项目拿到了Google的一个金奖而他的名字也在上面。 拿到这个奖之后,我突然膨胀了,问我的新manager我能不能这个季度就去尝试升职。尽管我们才认识两个月,他居然说,可以试试。我花了些心思写好了升职的文档,然后居然就过了,我五月份来到这个新组,八月份就得知了升职的消息。最讽刺的是,那个拿奖的项目,是我升职的重点,而它是我刚升到4的时候做的,从设计到实现,一共只花了一个半季度。

一种幻觉

转组之前的几个月正值Google史上第一次大裁员,我当时的想法就是失望,我觉得Google不应该裁员,太他妈丢人了。转去的新组是正在负责Google I/O上的某个LLM相关的项目,我当时就想,啊,就让我来搞这些最新的东西,提振股价,消除未来的裁员吧! 站在2025年的当下,我发现我在这两件事上都错得离谱。首先Google并非不应该裁员,而是应该更小心有策略地裁员,大公司病是真实存在的,它折磨着许多人的每一天;其次我的工作跟股价也没什么关系,也许它们曾在某个时间点上align了,可是correlation does not imply causation。当然这些都是后话了。

加速加速加速

加入新组的时候正好是23年Google I/O的前两周,我观摩了大家怎么准备I/O,然后感受到了前所未有的激情与混乱,当时就觉得太好了,搞事的机会来了。

当时做AI Coding就是三件套,code chat, code generation, and code completion。当时组里有自己的模型,大家也努力贡献自己的idea去train模型,我想要贡献一些程序分析的知识,但是却发觉low-hanging fruits是如此之多,纯ML人其实并没有看到跨学科的力量,或者说看到了但是刷low-hanging fruits在当时就是ROI更高的方案。 我进组的时候这三件套都有人做了,所以我onboard之后有人问我,要不要做agent,我说好啊,那时候还是2023年的7月,然后我一直做到了今天。

中间发生了很多事,随便写几个留念一下吧:

  1. 2024年3月,我写出了当时最新颖(AFAIK)的agent,它是真的work的,大家看到了之后也非常支持,想要在24年的Google I/O launch。其实I/O上宣布的很多东西都是teaser,给大家看个demo,就算结束了。但是我们当时非常ambitious地想要立刻让用户就能用上,特别是我,我想要给全世界看。于是我在I/O前的那两个月提交了350多个CL,其实一般工程师一年也就提交100多个CL,但是那时的我已经陷入疯狂,我非要把我写出来的东西productionize不可。组里的PM/TPM/Legal也是,一路披荆斩棘打通大公司里的各种流程。真的是我最爱的项目,所有人都只有这一个目标,land the cool thing。
  2. 我面对了很多不曾想过的问题,很多痛苦。比如当你搞出了一个产品,它是如此先进以至于这个世界根本就跟不上,大家不知道怎么用,看不到潜在的价值,站在大公司里,看到DAU/WAU/MAU 这些东西,我不禁会问自己我们到底在追求什么,从拓宽技术的边界到让用户真正喜欢且用上,到底是多么长得恐怖的一条路,也许只有乔布斯懂。站在2025的年末,一切显得更加讽刺,业界不停地吹agent,催生了无数startup,但是这一切其实都在I/O 2024之前实现了大半了。
  3. 如果说普通人无法理解,没有准备好迎接新的技术与产品,我觉得没关系,可是在公司内,说起来都是挺不错的工程师,也拒绝理解。拒绝理解的原因有很多,但是最让我难受的还是大家只愿意专注于自己的一亩三分地,不愿意理解新东西,不愿意站出来破局。其实直到今天我也不知道如何处理大公司里的这种情况,我不会push project forward,说起来我根本不是个合格的典型L6,我不懂八面玲珑也不懂如何利用公司里的level来敲打合适的人。当我不知道如何是好的时候,只有真诚是我的武器,我真诚地表达为什么这个产品这么重要,我真诚地表达请省下所有的bullshit,只要你们onboard,我会把技术上的一切都摆平。

金手铐

其实升职升快了很容易错失其他机会,或是更难改变自己,因为很容易overfit了。我明白人生不能什么都体验,但是还是经常会想没有走过的路。

比如说

  • 有好多个startup来找我做founding engineer,其中一个其实已经聊到比较深了,差点要去了,但是在钱的方面算起来就是70%以上的pay cut,就算乐观地估计公司的未来,比如调高公司估值10x的概率,收入的期望也就是差不多和现在持平。这样一来,我真的要离开公司就总是犹犹豫豫的,因为收入的锐减上难以接受,有时候也不免在想自己是否会错过下一个Google。不过话说回来,肉身总是只能加入一个startup的,收入给了自己额外的考量也并非全是坏事。比如说那个我差点脑子一热就要去的公司,拒绝后的一周我的情绪是fomo,两周后的情绪是烦心,但是两个月之后我却觉得当时做了对的选择,耐心等待时机比冲动加入更好(应该吧?)
  • 另一个是关于等级,等级高了转组就麻烦,因为没有那么多坑。Google内部很多组想要招个L6经常都指望你去当manager管人或者去搞定一些政治斗争,而我只想安安静静当个engineer。有时候跟朋友聊,其实觉得AI Infra真的还是挺有意思的,我比较想要研究inference optimization,但是组就很难找。如前所说,大部分组不指望你攻克技术难题而只指望你输出管理或者斗争,没有多少组会招一个L6然后指望你成长起来独当一面。但是少不代表没有,就比如说DeepMind里现在塞了五六千个人,大部分组就符合我前面的描述,但是还是有一些组里面很多人都在搞些务实的东西,就算他们的等级是L6/7/8/…。

未来之迷思

这篇文章从五月拖到了十月,其实就是这里一直落不下笔写,那么就暂时算了吧,我会新写一篇的 :)

新的旅程…?

作者 Jiahao Cai
2022年2月27日 00:00

新的旅程…?

Feb 27, 2022

Stan

Prove

21年10月的某一天,看起来应该很美好,因为那天是我的生日,而且那一天,我升职了,不过我好像并没有很开心,反而在这之后觉得很迷茫。11月的时候,想写点什么,但是看看网站上的文章,啊,已经一年没有写东西了,于是一种“很久没写了所以应该憋出一篇又长又好的文章才可以”的傻叉感觉占据了我。

其实当时想了很久应该写什么,想记录下自己停滞的心情,却进展很缓慢。某一天在明尼苏达的大雪里,我很开心地终于想到了这篇文章应该叫什么,我要叫它,”新的旅程“,一点也不fancy,却实实在在的是我的渴望。那天回家之后,我写下了下面的话:

好像呆在“我想要证明自己”这个状态里已经很久很久了,小时候不切实际地想要证明自己是世界的中心,天天看着历史书觉得自己以后会和他们一样;长大了点又想证明自己是个天才,不喜欢听课,每次都想要在考试的时候看到题目然后从零开始想,觉得那样才算是厉害;再之后不起眼的光环也褪去了,去了个无法忍受的学校,变成了只想证明自己跌倒后也可以再爬起来;然后机缘巧合开始读PhD,再次点燃了想证明自己不是一般人的热情;却又quit去了工业界,想要证明自己可以在工业界做更喜欢的东西,实现更大的抱负;最后终于觉得去他妈的证明个鸡巴,我不想要再证明什么了,却又觉得失去了动力,于是今天就坐在图书馆的凳子上发呆,想起四年前的自己,坐在瑞典某个图书馆里,对生活以及未来都充满了热情。前段时间突然想到自己在某篇文章里写到过的,拿到KTH的录取之后,一段开心的时光。我跑到已经不打算去的KTH去玩,一时兴起去学校旁听一门课,满怀期待地去问教授我能不能旁听,教授说可以,但是我也要做好回答问题的准备,因为他喜欢问问题,也不会因为我是来旁听的就不问我问题。我很开心他让我旁听,整节课都非常认真,也很好地回答了问题。

我不知道是从什么时候开始改变的,是我来美国之后吗?还是我开始读那个让我后悔又不后悔的PhD开始?

写完以后,我又陷入了停滞。再过了一段时间就是年末了,我想,就让我出去旅游一趟然后忘掉这一切,直接前进吧,希望回来就能踏上我所期待的,新的旅程。

回来之后的一小段时间里,我真的很努力地去相信我已经踏上了新的旅程,也似乎的确感受到了新的,活跃的气息,只是没有持续很久。很快我又开始觉得,我的生活是奄奄一息的,我是奄奄一息的。我挣扎着想要起来,我对自己的现状所感到愤怒,我向四面八方胡乱地出拳,却都好像打在了棉花上,最后用尽力气还是躺在原地。

Purpose

我觉得我失去了目标,刚毕业的时候我想,我要在三年内在Google升到L5,因为如果我不quit,也许再过三年我就会毕业了,而PhD毕业加入Google的话就是L4。如今我选择了另一条路,那我一定要在这条路上走得更远,我想要在本该毕业的那一年升到L5。现在再想到这个想法,我只觉得年轻人真好骗,这么离谱又情绪化的原因就能让我努力工作,努力朝着这个方向而奋斗。如今的我,就像上面的草稿中所说的,早就对证明自己这件事失去了兴趣,这也让我变得非常难搞。我试过了所有我知道的办法,我给我自己随便列了一些目标,又让我自己去跟成功的同龄人比较,但是每一次都像我在上面说的那样,像打在棉花上,用尽力气却没法移动分毫。此时脑袋里响起的是Agent Smith说的话:

Without purpose, we would not exist. It is purpose that created us. Purpose that connects us. Purpose that pulls us. That guides us. That drives us. It is purpose that defines us. Purpose that binds us…

Purpose,我失去了我的那一份Purpose,我应该像黑客帝国里那些失去了Purpose的程序一样,被送到源代码去删除。又或者我应该脑后插管,进到Matrix里,管他什么自由意志,只要给我一个Purpose就好,我想为了某些东西而活着。这种迷茫再衍生下去,就开始会觉得,国内那种什么年龄就干什么年龄的事也挺好的,环环相扣…想到这里,我知道我不应该再继续这样下去了,我真的需要一个,新的旅程。

Pain

我从小就相信痛苦才是创造的源泉,痛苦才是最能带来活着的感觉的东西,所以小时候总是想方设法地自己制造一些痛苦来尽量“有病呻吟”。直到后来上了大学,望向四周,所见之处皆是唾手可得的痛苦,却又在那时看了王小波1,才知道痛苦不必是自己的,悲惨的也不必是自己,原来观察他人的痛苦也能成为创作源泉(虽然今日细想之后觉得王小波当时也不过是在有目的地说给他的侄子听罢了,毕竟好的生活,永远不等于没有痛苦)。

我最近的痛苦就是我没有痛苦,在Google工作的每一天对我来说都过于轻松且无忧无虑,而且每天在家里工作,找不到出门的借口,默默地离人群越来越远,最后连他人的痛苦也观察不到了。当然我可以远远地看着一些新闻,却开始觉得自己越来越冷漠,越看越不再关心,越不关心越讨厌自己。

长大26岁

确实没有想到我的26岁只能被这么几个P开头的单词所主导,但是我又能想得到什么呢?仔细想来,我从来没有思考过毕业后的自己该是什么样的,更不必说以后的30岁及更远的未来。我自己也觉得奇怪,好像自己从来没想到过自己能活到这么大一样。现在开始努力思考,却也还是怎么也想不出来未来要干嘛,只觉得成年人的世界可真他妈的无聊。

一直很喜欢迷茫时期的人写的歌,也一直把他们当作养分。比如喜欢花儿青春的迷茫,李志愤怒的迷茫,热狗玩世不恭的迷茫,最近最喜欢的Leo王搞怪的迷茫。不过某天突然发现自己欣赏的人跟自己的年纪越来越近了,Leo王只比自己大一点,将来的某一天,自己就会需要从比自己小的人身上汲取力量了。这其实挺让人难过的,从之前仰望着羡慕着比自己大的人经历过的迷茫,也想要快点长大,希望有一天自己也能够那样抱怨迷茫。可是转眼间我已经快要开始欣赏比自己小的人所产生的迷茫,我不禁想问自己,我为自己的迷茫留下了什么证据?好像什么也没有,而我却已经开始进入表达欲没有那么旺盛的年纪了。

新的旅程

焦虑和迷茫就是间歇性的,距离我写完上面的文字又过了一个月,我感觉好些了。我不知道我的改善是否归功于这两件事,不过我在这段时间里确实做了这两件特别的事。

第一件事是我开始调酒了,之前只是随便喝喝,现在买了一套调酒装备,经常看各种bartender的调酒视频。下面这一杯叫做White Russia,我也不知道我为什么放了这一杯上来,想到最近的战争,禁不住有点政治性抑郁了。我经常每天醒来就开始刷新闻,刷完又是带着沉重的心情起床,这个世界会变得更好的证据也太难找了。

White russia cocktail

第二件事是我终于和小季一起领养了我们的第一只猫,名字叫ginkgo,ginkgo是我们来美国的第一个家的对面的中餐馆的名字,老板人很好,东西也很好吃,只是在我们离开夏村之后,老板也决定离开了,于是我们就少了一个回去的理由。Ginkgo是一只勇敢的小猫,带他回家的路上他一点也不焦虑,到家几个小时之后就想要把整个家都看个遍,我很喜欢他的勇敢。

Ginkgo the cat

希望大家都能好好生活 :)


  1. 我怎样做青年人的思想工作——王小波↩︎

I quitted PhD

作者 Jiahao Cai
2020年3月6日 00:00

I quitted PhD

Mar 06, 2020

The quit problem

做出这个决定大约是在2019年的10月中旬, 从加入到退出,我在PhD这个program里呆了一年时间,这一段经历对我而言,其实就是一段祛魅的过程。所有那些我在加入时有过的情绪,热情、激动,甚至是虔诚,慢慢变成了不解、不安、与痛苦,最后一切都烟消云散,我可以内心毫无波澜地审视这一切。PhD在我心里变成了一个再普通不过的东西,我不再觉得PhD这个title值得我用几年的时间去换取,也不再觉得相对于其他我能做的事,读PhD的过程可以带给我更多的收获。

所谓 world-class

不得不说,来了美国之后发现这里确实处处洋溢着世界第一的骄傲,比如说,在美国你可以领教到很多world-class的事,所谓的世界级。投简历的时候你会看到,类似于“Join us to do the world-class product”的描述;学生手册上写着“The PhD program prepares students for faculty careers at world-class universities and for research positions in leading government or industrial research labs”;就连夏村的消防车上都写着,“Charlottesville, a world-class city”。第一次面对如此多world-class的时候,我的心中怎么可能没有任何波澜呢?谁不曾想成为world-class的一份子?但是慢慢地我发现事情并没有那么简单。

回想我当初第一次看到学生手册上写的world-class,心潮澎湃,着了魔一般急切地想要投身于“world-class research”然后“expand the frontiers of knowledge”。那时候的我几乎是虔诚地对待这个词,我觉得world-class曾经是对我来说是触不可及的东西,我为自己朝着world-class迈进而感到无比高兴。这种感觉随着我读论文的数量增加,research的进度增长而慢慢消失,放眼望去,密密麻麻的都是重复性的,无聊的,炒冷饭式的工作。由于巨大的失望,最终彻底走到了这种感觉的对立面,我才知道所谓world-class是如此不堪一击。诚然,我们大部分人都耗费了巨大的精力才走到了这一步,走到了world-class的面前。可是如果此时你看见前方的生活并不是你想要的,你会回头吗?

至于夏村消防车上写的world-class,我只当这是当地人的一种幽默,否则的话,那就只能是一种无知式的自豪了。欧洲的那些默默无闻的宁静小镇们,看到这个world-class,估计只会哑然失笑。

world-class,大概是我来美国受的第一次骗。

PhD

这一年时间里,我从完全不知道PhD为何物到quit PhD,我的观念发生了很大的变化,比如说PhD这个title在我心里已经失去了价值。我曾经以为每个想读PhD的人都是充满好奇心的,真心实意地想要拓宽人类知识边界的人,但是我发现现实世界里读PhD的一般就两种人,一是不知道自己当下该干嘛的人,于是读个PhD,在学校里继续混着;二是一心想要找教职的人。前者一般天天划水,水平十分低劣;后者往往非常努力,但是目的性也非常之强,他们精确地知道想要找到一份教职需要多少顶会paper,我尊重他们的努力,不过也仅此而已。

为什么说PhD在我心里已经失去了价值,因为我自己在做research的过程中,见到了太多的所谓划水PhD,实在令我感到不适。所以现在的我觉得PhD这个title并不能说明什么,如果一定要说明什么,那就是我知道这个人八成在学校里呆了好几年 :) 读过PhD的人都知道,PhD们之间的差距可以大到令人咂舌,我们只能通过他在PhD期间完成的工作来真正说明这个头衔的含金量。可惜的是,由于这世界上的大部分人都不是PhD,他们不懂得如何分辨一个好的PhD和坏的PhD*;又由于PhD在这个世界上的稀少程度,人们往往愿意盲目地奉上较好的待遇。单凭PhD的title就能得到良好待遇的事实,吸引了大批只对该title感兴趣的人涌了进来,同时也让一大堆已经了解自己不适合读PhD的学生赖在这个项目里不肯离开,因为他们渴求这个title。很多人就像爱因斯坦曾提到的,他们是“为了纯粹功利的目的而把他们的脑力产物奉献到祭坛上的”1

我不知道是谁第一个把做research叫做搬砖的,但我不得不说这实在是天才般的比喻,PhD们日复一日,为了发paper而重复着微妙的实验。在这过程中,某些搬砖技巧不足,或者搬砖不够努力的人就会被请出这个项目;另一些人则非常努力的搬砖,不过与此同时有些人(比如我)会开始怀疑这一切的意义与动机。每个教授都会说自己是想要拓宽人类知识的边界的,PhD们也都是这么被告知的。一开始我也确实觉得research是关于一心为了拓宽人类知识边界的,后来我发现并不是;于是我退而求其次,只想做个老板眼中的“成功”PhD,但我又发现所谓“成功”PhD的终极意义居然就是找到一个好教职并继续做research,于是我开始问自己我是否真的想成为一个faculty。我想了想觉得没什么兴趣,比起一个做research的faculty,我宁愿去专心教本科生上课,我觉得那会带给我更多快乐。第二个学期的时候,我做了本科生Operating Systems这门课的TA,当时这门课分成了两个section,一个section是我老板上的,另一个section是C上的。一开始我不知道为什么C一个Berkeley来的,PhD期间research搞得风生水起的人,会来这里专心教书。但是渐渐我发现他对于教书的热情真的非常动人,这门OS是直接改的MIT的OS课,降低了一点难度又没有失去精髓;Piazza上有学生提出问题,他总是会及时地出现。有次半夜两点有学生问问题,我正在编辑回答的时候,突然发现怎么有个人在co-editing,仔细一看原来是C;每周开会C都会认真记下TA们的反馈,讨论课程的改进,其中有一个作业是实现ftp服务器,他一直期待大家提出一个更好的protocol让学生去实现,因为他始终觉得ftp设计得太垃圾了。刚才我看了一眼这个学期课程表,发现这学期OS两个section一共有两百多个学生,lecturer却只有他一个,也许是他觉得其他人教得都不如他吧。在我眼里,C的人生比他的大多数同事们都更有意义。

发现自己对faculty没兴趣之后,于是很自然地,我决定退出。说到底,一个“成功”PhD所需要的品质,比如头脑聪明,比如有想法,比如耐得住寂寞,仔细想想,这些品质放在任何行业和领域,都是非常宝贵的品质。对我来说,与其耗费好几年青春换得一个我没兴趣的职位(faculty)的入场券,不如带着这些品质去探索更大的世界。

有人说,PhD的过程是一种筛选,而这种筛选“总是筛掉最差的,但也筛掉最好的”2,其实有一定的道理。

*: 从某种意义上来说,这也是由如今research的方式决定的——大部分人都只在一个很小的细分领域内深耕,researcher们互相并不非常清楚彼此的research,而CS这个学科中又恰好充满了各种各样的细分领域。假设一个学校想要招生,很有可能学校根本没有那个细分领域的专家,评估新人的教授只能从侧面来考察这个候选人。不过从侧面考察这件事就很微妙了,说句实话,PhD这么多年读出来,不管自己的工作是不是真正重要,也能将自己的工作吹出花来了,说不定还早就给自己洗脑,相信自己的工作一定无比重要。毕竟如果自己都觉得自己的工作不重要,估计也就不愿意把自己的美好青春放在这些工作上了,早就半路quit了。↩︎

Getting PhD is NOT a war

Brain on a stick

老板与学生的关系向来都很微妙,尽管学生手册里写的是,“RAs and advisors are colleagues in research and the employer-employee relationship is rarely visible as they work together to expand the frontiers of knowledge”,但是事实并非如此。我的老板就经常把读PhD比做一场战争,而我则是其中的一个soldier :)

读PhD的几乎每一天我都过得很郁闷,因为在我眼里,我的身份更像是一个worker,一个employee,或者一个soldier,这让我感到迷惑且痛苦。而且我无法忍受某些话术,比如

“我给了你这个project,你就应该尽力去实现它,如果你觉得我错了,那你就指出来,否则就按我说的做”

这个话术的精髓在于,引入一个看似正确的逻辑(if x then y else z)让学生闭嘴并且去干活,但是却闭口不提这个结果(y)所需要的苛刻的前置条件(x)。有哪个刚入学的PhD可以随随便便地指出老板的错误呢?如果不能指出错误,那么就只剩下了服从,在脑子一片迷茫的情况下服从,是我所不能忍受的事。正确的做法应该是真诚地对待学生,与学生建立良好的平等关系,及时地引导学生并解决学生的疑惑。或许有人会说,“你的要求太高了,怎么可能会有这种老板呢”,我觉得这种人就是典型的自我阉割惯了,把自己的要求降低到了一个不正常的维度,于是看到别人提出的正常的要求就会露出一副吓坏了的神色。

“如果你能完成按时这个project,你每天睡12个小时我也不在乎,(对于你现在的困境)唯一的办法就是变得more efficient”

Again,这个话术的精髓也在于忽略达到结果所需要的条件,只提出了看似宽容的每天睡12小时。这样一来,就可以把workload过大的责任消于无形,问题现在出在了你的身上,你牺牲了睡觉的时间娱乐的时间来做这个project,完全是你自己的选择,因为你不够efficient。计划制定者一点错也没有,相反,他很仁慈,在某种实则不可能的条件下,你甚至可以每天睡12个小时*。正确的做法应该是为学生制定合理的计划,如果学生实在达不到要求,那么可以直接提出让学生另寻出路;或者招募更有经验的postdoc来充当老板与学生之间的缓冲。给着卖白菜的钱指望学生出卖白粉的力气是不可取的,指望学生长时间地用爱发电也是不现实的,事实上我真的在很长一段时间里尝试用爱发电,结果是我开始失去动力,开始怀疑我是否还喜欢security。

我可以理解Assistant Prof.往往会面临巨大的来自tenure的压力,但是我不愿意有人将这种压力直接(甚至是加倍)传导到我的身上。

其实大学里也有很多可以制衡老板的机构,但是这些机构一般只能在与学术无关的事情上发挥作用。另外,我也并不相信他们真的会站到我的这一边,而且我并不觉得他们能为我做什么,这是我与老板之间的事。

听到有人说读博和婚姻很类似,我觉得是的,老板与我的关系就能说明很多。如同很多婚姻一样,我们也有一个非常良好的开始。我还记得当初我刚来读Master的第一个学期,天天对着课表发愁,觉得没什么感兴趣的课可以上。就在这时候,他正好作为一名新AP加入,并且开了一门新课。那时候我一看他的履历就知道他的方向和我的兴趣应该是完美契合的,我自然马上选了他的课。相似地,我相信他也肯定感觉到了我的兴趣与实力,我记得那才是那个学期的第一节课还是第二节课,他就开始邀请我读PhD,并且在那个学期剩下的时间里时不时地找我吃饭,劝说我读PhD。我虽然一直犹犹豫豫,始终没有答应,但是我知道我的内心肯定还是希望尝试的。我嘴上说着自己要刷题找实习,但是我花在research上的时间,至少十倍于刷题找实习的时间。那个学期之后,我还是转到了PhD,我以为这是一段佳话的开始,我以为我们会像一个前途无量的startup一样,破土而出,在这个学术界里占据一个角落,留下我们的名字。可惜事与愿违,尽管老板与我之间并没有想象中的剑拔弩张,只是日子一天天过去,我们都慢慢明白彼此不合适。说不可惜是不可能的,我们在技术上互相赏识,当初是他亲手写的推荐信,在春季不招生的情况下让我破格转到PhD,我也一直把他当作榜样。只不过我是一个缺乏安全感的人,他对我的各种要求使我感到烦躁并且失望,而且我们之间也太缺少深入的,与学术无关的谈话了。在这种情况下,我陷入了深深的焦虑,我知道再这样下去我只会变成一台不属于我自己的机器,当我quit之后,我才终于又感觉到,我的命运又被我牢牢地抓在手里了。

说到我与老板之间的相处,让我给你们举一个例子吧。我刚转到PhD的时候,是春季学期,那时候他对我说,如果我们能在暑假结束之前发出两篇顶会文章,那你就可以做到“above average”了。我当时并不知道两篇顶会意味着什么——直到后来我才知道在system领域,有很多PhD花了三年甚至更久才能发出第一篇顶会;直到后来我才发现,隔壁某大牛组的PhD,花了六年拿着两篇顶会文章就毕业了。但是在那个时间点,我选择无条件地相信了他,我只知道在这个时间之内完成两篇顶会,一定是一个很棒的成就。如果你对CS System这个领域有所了解,那你应该已经能猜到我接下来的大部分故事了。在接下来的日子里,我非常努力地工作,也没有时间去看别的PhD在干嘛,当然也就不知道老板给我定下的“above average”的实际难度究竟是多少,我只知道我感觉越来越难。我开始对自己非常失望,我满脑子都是“暑假结束前两篇顶会 = above average”,可是直到第一学期结束前,我还没有完成我的第一篇文章,那我得有多糟糕?而且更让我感到恐惧的是,我对发论文一无所知,我并不知道发一篇顶会需要多少步骤,我也并没有被告知下一步是什么,我只被告知我应该尽快完成我手上的任务。所以我很焦虑,对未来的未知以及无法掌控加剧了我的焦虑,我每天都在挫败感中度过。但是老板不一样,他从一开始就知道我做不到,实际上,就连他自己也做不到。他只是想通过这种方式来push我,让我尽可能多地搬砖罢了。我当然也希望尽可能多地搬砖,可是以这种方式来push我,利用我的恐惧与焦虑,我实在是不能接受,也不觉得这是一种decent的方式。事实上,按照我的性格,从他开始这么做的第一天起,我们的分离就只是时间问题了。暑假结束之后,我投出了我的第一篇文章,也许我已经超过了95%或者更多的PhD,但是那又怎么样呢?长久以来,我都背负着巨大的压力,我的一口气都是被那篇paper吊着,我以为投出去之后一切就会变好,可惜我并没有那种感觉。我只觉得空虚,觉得没有意义,觉得自己应该重新规划自己的人生了。那个时候,我对老板的做事方式耿耿于怀,而如今,quit后的我也已经理解了,我又能改变什么呢?他是真心实意地想要帮助我,希望我成功,也是100%地相信,用那种方式push一个人是对的,是有用的。也许是因为他的成长环境吧,他也对我说过一些他以前的事情。我为他感到难过,也为能理解他的我感到难过。我想把那个令我难过的东西称之为”亚洲人的悲哀“,或者更广义一点,”内卷后遗症“。

*: 这样的话术在生活中其实十分常见,它们往往重点渲染结果,而对苛刻的条件一带而过。比如,前几天一个朋友对我说,“比特币现在没有那么有用,但是如果公链做的很牛逼,那这个代币就值钱了”,这句话本身也许是对的,但是这并不代表你应该投资比特币。↩︎

学术界的信息不对称问题

上面说到了我的那种失控的焦虑以及命运不掌握在自己手中的感觉,这里我可以详细解释一下。在此之前,我想起两个看似无关的事:

  1. 某天我跟同学在交流找工作的经验,我分享了很多有用的信息,同学感叹说,收集信息对于找工作来说真的好重要,
  2. 我在要转入PhD之前去找我的本科老师交流,她对此表达了一丝担忧,意思是如果我真的想读,让我还是去申请更好学校的PhD,找功成名就的教授。

收集信息的能力很重要,两个人想做同一件事,拥有更多信息的人有更大的成功机会,因为这些信息可以帮助这个人制定计划,评估风险,从而更好地把握整体走向。我的信息收集能力让我在大多数时候都掌握主动,并且信心十足,但是当我读PhD之后,事情产生了变化。首先,PhD应当探索人类知识的边界,由于当今学术的特性,每个人往往都只能专注于一小块领域,这块领域的资源是极其有限的,这个领域的论文就这么多,大家都读着同样的论文,似乎并没有什么信息优势可以获取。每个人都是平等的矿工,对着面前的各种难题一铲一铲地挖,期待着下面的金矿。可是事实果真如此吗?其实学术界也有信息优势,而它们存在于那些最顶尖的lab里,那些lab掌握了最前沿的信息,也引领了学术界的动向。而那些信息并不像我曾经获取过的信息一样,只要Google上查一会就能获得,相反,作为一个outsider,我接触不到那些信息,而那些信息几乎可以决定你在学术界的成就。这让我感到不安且烦躁,我觉得我的人生被莫名其妙地设置了一个天花板,我觉得我的命运不再掌握在我自己的手里。或许你听说过“老板的水平决定了你的上限”之类的说法,这就是了。

当然你会想,为什么要去蹭所谓的信息优势而不直面未知呢?那不是PhD的意义所在吗?说不定那些信息反而会限制你的思路,一个产生伟大研究的灵感就从你手上溜走了。头铁的我当然这么想过,但是老板是否会让你直面未知?特别是Assistant Prof.,要知道tenure的压力是很大的,AP决不会同意你冒着发不出论文的风险憋一个大招。就算你有了那种研究的自由*,你是否思考过直面未知的赢面又有多少?产生伟大研究的几率如此之低,而成本却是如此之高,与其沉浸在自己大多数时候都只能庸庸碌碌地做研究的痛苦之中,不如去寻找其他的方向。这让我想到Witten说过的一句话:“我们其实不需要100个弦论学者,只需要一两个就行了;但我们还是需要招100个研究弦论的学生,因为我们不知道他们之中哪一两个人能成为我们需要的弦论学者”。说实话,我不知道我是不是那百分之一(尽管我觉得我比我身边大部分人都强得多),可是我知道我百分之百可以成为一个很好的工程师,于是我的选择就渐渐清晰了。当然你可以鄙视地说,我看见了一笔不划算的投资,于是知难而退,which is very true. 说到底,人生太过短暂,我只想选择更能够实现自我价值的路。

Research in peace
“学术自由”

*: 其实作为一个PhD学生谈研究的自由是一件很搞笑的事,就像太监讨论性生活一样没有意义,因为本质上学生是为老板打工的(为什么大家把导师叫做老板?)。至于赢面问题,有兴趣的人可以看下The PhD grind,里面讲到了Klee-UC在Stanford诞生的故事,一个来自顶尖学校,功成名就的Full Prof.,想要学生实现他的一个想法,花了整整五年。代价是什么呢?在这五年里有四个学生尝试过这个project,只有一个学生成功了,剩下的两个直接像我一样quit PhD了,还有一个(作者自己)去寻找别的项目了。
> In the end, it took three attempts by four Ph.D. students over the course of five years before Dawson’s initial Klee-UC idea turned into a published paper. Of those four students, only one “survived” —— I quit the Klee project, and two others quit the Ph.D. program altogether. From an individual student’s perspective, the odds of success were low.↩︎

没人值得你学习四/五/六年

四/五/六年是一段漫长的时间,也是很多PhD毕业所需要的时间,在这么长的时间里,到底该学些什么?下面是我的看法:
我们都很了解“授人以鱼,不如授人以渔”的道理,换个角度思考,如果要学习,到底该学鱼,还是渔呢?我想大家都知道要选择渔,那么渔在这个context下代表什么?在我眼里就代表处理各种事的方法,包括生活,包括research。学习这些方法,我认为并不需要花费太久的时间,而且重要的是,随着时间的推移,学习方法的收益将会急剧降低,因为你已经见过老板处理大多数事情的方法。举个例子,你用了一年的时间观察了老板90%的方法,那么剩下的五年你就只能观察剩下的10%了。剩下的一些方法,就像程序中的rare branch一样隐藏得很深,需要特定的事件来trigger之后,你才能看到老板是如何处理的,但是这些方法真的值得你花上很多年去学习吗?如果你觉得方法的精要藏在那些你尚未发现的方法里,那你大概率是错了,因为真正重要的,需要学习的方法,就藏在每天的生活里,每天的research里。或许你会说,“那老板还是比我强啊,我还是可以从他身上学到很多东西”,可是你确定你学到的是渔而不是鱼吗?考虑以下情况,我们假设鱼 = 渔的efficiency * 时间,你有没有想过,当你到了老板现在的年龄的时候,你也许会比他拥有更多的鱼?其实结果并不重要,重要的是你有没有从这个角度思考过,也许很多人从来就没有想过,假以时日,你完全可以超过自己的老师们。我离开本科的时候,自信自己在写代码方面已经超过了我们学校的绝大多数老师,除了两个比较年轻的老师,不过我觉得,这没什么了不起的,两三年之后我就可以超过他们。

也许有人会说“没人值得你学习六年”太夸张,然后搬出爱因斯坦/费曼/普朗克云云,说自己愿意终身跟随这些大师学习。其实这又是把鱼和渔给混淆了,如果我与爱因斯坦共事,也许我六个月就会离开他了,一个月观察他的方法,剩下的五个月在纠结要不要离开他,毕竟我在离开一颗超级聪明的大脑 :) 渔,也就是做事的方法,是很因人而异的东西,有些方法需要你废寝忘食,比如每天两点睡五点起;有些方法则需要你智商180才能使用。有些人只看到了对方有一大堆鱼觉得对方身上一定有可学习的东西,其实是不对的。如果你看到这个方法对你而言毫无实践性,我看不到继续学习这个方法的意义。每个人都是独特的,没必要去想着做第二个爱因斯坦/费曼/普朗克,从各个人身上寻找可学习的长处,融合成为自己的方法,这才是最重要的。至于鱼的数量,当方法正确时,你需要的只是时间。当然,这种思考方式只适合像我一样希望修炼自己的钓鱼方式,然后钓到属于自己的鱼的人。向比自己厉害的人学习,从他们身上拿到鱼固然也可以给自己带来很好的结果,可是这却不是我所喜欢的方式。

所以呢,如果你想学习一个人做事的方法,根据对方水平不同,学习几个月到一年的时间即可。至于PhD为什么需要这么长时间,那是另一个复杂的问题。写到这里我突然想到一件事,在我转到PhD之前,我去找我们学院的一个快退休老教授,他问我为什么要读PhD,他说每个人读PhD都有他的原因,他当时读PhD因为他只想当个大学教授,而读PhD是通向大学教授的唯一路径。我时常想起这件事,并且感叹于他的坦诚。

不再年轻

或许有的人会说,从上面的字里行间可以看出,这些问题出现的主因在于我的水平不够,对于这种评价,我可以很肯定地告诉你,你错了,我可以很自信地告诉你,我就是我们学院最强的学生之一。我离开的原因之一单纯就是觉得我读得很不爽,不仅如此,我还呼吁所有PhD读的不爽的人都马上quit,去其他地方实现自己的价值。读PhD这事有时就好像996的怪圈,理论上只要大家都抵制996,拒绝为996的公司工作,那么996的现象就会消失,但是事实却是总有那么一帮没有骨气的人像舔狗一般地为公司工作着,永远以为自己和资本家穿同一条裤子。读PhD其实也一样,如果每个PhD都读得不爽就退学,那么老板还有可能会压榨学生吗?现实世界里有很多老板可以肆无忌惮地压榨学生,因为他们知道,无论如何,总会有不明真相或者执迷不悟的学生前赴后继地加入的。消除老板与学生间不平等的关系就像是一个美好愿望,它永远也不会实现。

想到这里,我突然觉得我不再年轻了,因为我开始希望别人能够听进去我的劝告,所谓过来人的经验。我在生活中,网络上,都观察到一些人,看到了有人从某老板门下quit,却仍前赴后继地想要加入。原因很简单,他们觉得别人离开都是因为能力不足,觉得自己都是特殊的那一个,觉得自己一定能抗下所有的事。我也曾经是这样的一个人,我就觉得自己就是那个最独特的人,不管所谓过来人如何描述某件事的难度,我也会觉得我自己一定可以的,他们将这件事描述得越难,我只觉得越兴奋,越想做成这件事。就好像我在第一学期的时候想要找实习,有个关系很好的朋友帮我联系了个面试。面试之前我在地里搜了一圈,确定了可能的面试题,但是我却一道都没有做。尽管我看过无数人说面试要刷题,但是我始终觉得我不需要,我觉得真正的面试就应该面对一道从未见过的题,然后在规定时间内解决它,那才刺激,那才是能力的体现。那是我的第一次面试,我面得很糟糕,毫无意外地挂了。那是一次很惨的面试,我第一次知道,尽管我自认为我的编程水平不知道高到哪里去了,可是我在毫无准备,毫无练习的情况下会表现得如此惊慌失措。面试官人很好,最后五分钟一直在安慰我,而我则因为意识到自己并不是那个特殊的人而在最后五分钟里一直努力忍住想哭的欲望。

是的,如果我一直坚持下去,我一定能完成这个PhD,我确实会完成了一个比较难的成就,可是那又怎么样呢?我得到的是我想要的吗?而我失去的又是我在乎的吗?我不再会纯粹为了挑战某件事而去完成它了,其实退一步看,摆在我面前的难题可以有很多,升职,创业,以及很多我还不知道的路。我没有时间再去想将他们全部完成了,我只想找到一条最能实现自我价值的路。

他人的看法与对自己的评价

我见过很多人对他人的评价耿耿于怀,非常在意别人如何看待自己,对不公正的批评感到难过,气愤,甚至痛苦,我觉得大可不必。我的观点是,这个世界上的每个人,只有自己才有资格公正地,合理地对自己作出评价,原因很简单:只有自己才能完整地看到全部的自己,而其他所有人都只能看到我生命中的一部分。换句话说,所有他人的评价都是片面的,带有偏见的,不完整的,我应该使用挑剔的眼光来看每一个来自他人的评价。我也曾经因他人的评价而痛苦,可是后来我才发现,随意在意他人的评价是很危险的,随意给予他人评价我的权力无异于递了一把可以伤害自己的尖刀给对方。后来我意识到,很多人其实根本没有评价我的权力。

然而,把评价自己的权力牢牢抓在自己手上是危险的,“With great power comes great responsibility”。想象一下,如果全世界只有你自己可以评价你自己,那么你的评价会左右你对自己在这个世界中的定位,如果定位错误,那就有可能陷入巨大的困境。我的应对方式很简单,我非常非常虔诚地对待这种权力,时刻思考反省,自己对自己的评价是否公正,是否有失偏颇。一味地看高或者看低自己,都是我所不希望发生的。

说回PhD的事,几乎每个PhD candidate都会收到来自老板的大量评价,很多人天真地将老板的每个评价认真对待,却殊不知这些评价往往都是带有某种目的性的。大部分所谓的老板-学生关系,其实就是employer和employee的关系罢了,想想现实世界里下雇主和员工的关系,你还会像以前那样天真吗?很多老板会给一大堆负面评价,损害你的自尊,伤害你的感情,贬低你的能力,很有可能这些都只是为了让你更听话地卖力工作罢了,“你已经如此糟糕了,再不听从我的建议,你还有未来吗?”。在我quit之后,我意识到,这种东西其实跟某种所谓精神控制的把戏有很多共通之处 :)

在这里必须要说明的是,我在上面写的都是关于“人”的评价,除此之外,还有对“事”的评价。比如你做了某件事,然后你的队友/上级对你做的这件事做出了一个评价,这就叫做对“事”的评价。这些评价往往是需要听取的,大多数时候它们都能帮助你进步。可惜的是,现实世界往往是复杂的,只有很少的人可以清晰地分开对“人”的评价与对“事”的评价,人们有意无意地将两种评价混在一起,以达到某种目的。所以,当你看到一个评价时,请擦亮眼睛,别让无谓的评价伤害了你。

把水放干

我在yinwang的文章里看到过一个很有趣的例子3,讲的是在Cornell求学的生活:

有人打了个比方,说Cornell说要教你游泳,就把你推到水池里,任你自己扑腾。当你就要扑腾上岸时,他在你头上用榔头一砸,然后继续等你上岸。当你再次快要扑腾上岸时,他又举起一块大石头扔到你头上,这样你就可以死了,可是Cornell仍然等着你游上岸… 这就是对我在Cornell的经历的非常确切的比喻。

现在我觉得自己就像那个到Cornell学“游泳精髓”人,本来就是会游泳的,可是每到岸边Cornell就搬起大石头来砸我,还说我不会游。于是我钻到水底下钻了一个洞,把水放干。

我也是一个渴望学到所谓“游泳精髓”的人,可我却发现在这一年里,我非但没有学到“游泳精髓”,反而天天被人说不会游泳,搞得很不开心。大多数时候,当我游到岸边时,我只会被一锤子砸晕然后被命令重新来过。可是,我不觉得我是一个不会游泳的人,相反,我很自信我可以游得很好,我不想屈服于这些奇奇怪怪的约束,我很清楚这不是我想要生活,于是我也在泳池底下开了个口,把水放干,然后离开。

正式quit & 加入Google

Quit PhD to industry

决定跟老板分开的时候,学期还剩下两个月结束,学院里的老教授建议我在剩下的时间里找别的老师谈谈,找一个合得来的老师重新开始。我很感谢他真诚的建议,但是我从来没有找过别的老师,因为我早就发现我对其他教授的工作都不感兴趣。部分原因大概就是,我发现department里的所有老师,除了我的老板以外,都在做Machine Learning相关的工作————无论他之前究竟是做什么方向的工作。至于原因,自然是兴趣,只是我分不清是对Machine Learning的兴趣,还是对funding的兴趣。

我花了两三天决定了接下来要干什么*:找工作。很遗憾,我quit的时候是十月中旬,那时候已经是秋招的尾声了。我打开Google的招聘页面,发现校招都关了,我意兴阑珊地准备直接放弃,可是我的一个好朋友强烈建议我投一下社招。没想到的是,第二天Google的recruiter就找到我说要谈一下,因为当时很晚了,他还很贴心地帮我跳过了之前的面试直接让我去onsite。由于跳过了之前的面试,所以我只有十几天的准备时间,本来也没抱太大的希望,只想去湾区玩一圈,没想到去了之后居然一下把所有面试题都解决了。后来就是通知过了hiring committee然后有了口头offer。为什么没有收到正式offer呢?因为我投的是社招,Google的社招必须要先进行一个team match的阶段才会发offer,这个match一般在入职前的一两个月才会进行,而我则一直要到夏天才能毕业。一开始我也很焦虑,不停地催recruiter,但是这件事也超出他的掌控范围了,他也只能对我说,“你100%会得到offer的”,或者是“你110%会得到offer的”。现在也算是佛系了,反正想去就得等,先干点别的吧。

无论最后的结果如何,我都非常感谢Google带给我的美好体验,第一次去湾区,第一次去onsite,第一次在白板上写代码,更重要的是我终于又清晰地知道,我依然是那个编程很厉害的我 :)
其实这里还有一个插曲,2019年的年底,Google只招很少的new grad,很多在我之前面试并且通过面试的人,也没能拿到offer,因为招满了。可能因为我投的是社招,所以并不受缩招的影响。我觉得自己很幸运,有时候我在想,也许这就是冥冥之中的宿命吧。
(Update: 我已经拿到offer并决定去Google了,我很幸运地match到了一个自己想去的组,工作内容大概就是通过Program analysis来分析Malware,这跟我PhD期间做得几乎是一模一样的东西,我可以换一个地方继续做我喜欢的东西了:D 还有一个有趣的事,在我签字后的第二天,Bloomberg就出了一条新闻,说Google要significantly slow hiring in 2020

再后来,到了该正式quit的时候,我拿着表格去找老板签字,快三个月没见了,去之前我还很好奇他会不会问我接下来要去哪。最后他还是问了,我说我应该要去Google了,我以为我说出这句话的时候会很爽,但是其实并没有,他很平静,我也很平静,就像某个学生来找一个素未谋面的教授签字一样。我从来不喜欢主动提出要走,就算签字已经签完了,我还是像以前一样坐在他面前,又像以前一样尴尬又沉默地过了三秒。最后还是像以前一样,“OK”,我先说,“OK”,他也说。我站起来准备走,他送我到门口,我想说点什么临别的话语,却脑子一片空白。我发现我还是像从前大多数的时候一样,来到这个办公室之前,总是没有准备好所有自己要说的话。只不过这一次,他应该不会再因为这个而感到不满了。慌乱之中,我留下一句“I may come back later to visit”,就这样吧。

*: 有朋友来信说很好奇为什么我之前写得雄心勃勃说想要做出伟大研究,而现在却只花了两三天就决定要退出。实际上,说是两三天,其实我只花了两三分钟,我给了我自己两三天只是因为我在强迫自己反复思考,我不希望自己后悔。至于只花了两三分钟的原因,自然是之前所有经历的总和累加起来的疲惫以及我发现读PhD这事根本不适合我。给大家分享一个我很喜欢的回答,我就是回答里那个“活在某种自我期许中的人” or “太成熟的人”。↩︎

写在最后

我依然很尊敬我的老板,但是我们也真的不合适,他自己承认不知道怎么教我,不知道怎么跟我沟通,还说自己喜欢debug,但是却找不到改变我的方法,找不到问题出在哪里,当时我坐在他面前,没有说出口的话是,也许问题并不只出现在我身上…确实,后来招进来的学生也遇到了和我一模一样的问题,或许更严重,我尽我所能地帮助他们,也告诉他们这一切都曾经也在我身上发生过。或许等时间慢慢过去,两个人都会改变,慢慢适应彼此。但是我和他都太知道我们自己想要什么了,我们都深信自己的路是对的,我们不愿意妥协,也不可能改变自己而成全对方。无论如何,对于我们俩而言,与其呆在一起相互消耗,分开确实是最优的结果。特别是当他知道我很好,而我也知道他很好时,我们不自觉地就将对彼此的期望拔高到一个很高很高的地方,没有达到那种期望所带来的失望,是会把人搞疯的。所以,没有必要再继续了,希望我们一切都好,未来的某一天,我们的生命也许依然可以有所交集。

一年前,我将自己的部分经历po在网上,有一些人通过那篇文章找到我,有希望得到我的帮助的,也有单纯钦佩我的勇气与洒脱的。我很开心那篇文章产生了很多正面的影响,并且希望这篇也可以帮助到一些人。

(The End)


  1. 探索的动机↩︎

  2. 常青藤联盟和“世界一流大学” - 我和权威的故事↩︎

  3. 图灵奖 - 我和权威的故事↩︎

LLVM Pass与程序分析

作者 Jiahao Cai
2020年2月23日 00:00

LLVM Pass与程序分析

Feb 23, 2020

LLVM logo

注:本文所用LLVM版本为3.8

0. Overview

根据官方介绍,LLVM 是一堆模块化的,可复用的编译器以及工具链。LLVM Pass是其中非常重要的一部分,它可以让你对程序进行编译器级别的修改,但是又不需要你真的实现一个编译器。所谓编译器级别的修改——如果你对编译器有所了解的话,就知道编译器首先会通过lexical/syntax analysis将源代码解析成AST(abstract syntax tree,即语法树),然后对AST进行semantic analysis以及各种各样的optimization,最后生成目标代码。在对AST进行分析的过程中,编译器会对AST进行一次或多次,局部或全局的分析,这些分析被模块化了之后,每个分析往往只负责一个相对独立的功能,这些分析也被称作Compiler Pass。LLVM会将源代码统一表示成LLVM自己定义的IR(intermediate representation,即中间表示),并基于IR生成AST,然后再将AST交给用户,让用户根据自己的需求去写Pass对程序进行分析。

这篇文章主要是关于用LLVM Pass来实现简化版taint analysis(污点分析)的核心部分。

1. Taint analysis

Taint analysis可以被看作是信息流分析(Information Flow Analysis)的一种,主要是追踪数据在程序中的走向。具体实现的话,用static analysis(静态分析)或者dynamic analysis(动态分析)都可以。这里主要是用static analysis。我们要实现的简化版taint analysis,目标是找到程序中所有有可能被攻击者控制的变量。下面我尝试简洁地说明什么是taint analysis。

1.1 几个问题

  • 为什么要追踪数据在程序中的走向?
    • 这里主要是为了最大程度地识别潜在的恶意输入,从而提高程序的安全性。在现实世界中,攻击者的payload是多种多样的,我们往往无法预知攻击者的payload会以何种形式呈现(无法从形式上防范),但是我们知道许多payload都有一些共同的特征,比如说很多payload经过复杂的转换,最终会作为参数传入system函数,并执行恶意指令。通过追踪这些数据的走向,我们可以识别用户的输入是否具有这些特征。换句话说,在程序分析中,用户的原始输入长什么样并不重要,我们在意的是这段输入对于程序运行状态的影响。
  • 哪些数据是需要被追踪的
    • 这里我们认为任何用户的输入都是不被信任的,所以任何来自用户的输入都应该被追踪。相似的,任何会被用户的输入影响的数据也都应该被追踪。
  • 数据是如何被追踪的
    • “追踪数据”是一个抽象的说法,具体到程序中,我们的做法就是找出程序中可能被攻击者控制的变量,然后监控那些变量是否会被用在某些危险的地方,如system函数中。

1.2 几个概念

  • source:指(不被信任的)数据的源头,这里我们可以简化为用户的输入。实际上source可以多种多样,任何可能被攻击者利用的输入都可以是source,比如一个配置文件,一个网络请求,或是一段音乐。
  • sink:指数据的出口,这里特指比较危险的函数,如C中的system(导致command injection), PHP中的echo(导致XSS),等等。
  • tainted variable:指在程序中存储(不被信任的)数据的变量,也就是可能被攻击者控制的变量。
  • sanitize:指程序中变量从被追踪状态(tainted)到不被追踪状态(untainted)的过程,一般是由特定函数或者重新赋值来完成。比如说下面代码中第一行的a处于tainted状态,因为程序对用户输入没有进行任何处理,直接将用户输入作为echo的参数会导致XSS;但第三行中的a就处于sanitized(untainted)状态,因为此时a中的特殊字符被htmlspecialchars转义过,XSS攻击在这里失效了。如果我们只关心XSS攻击,那我们就没必要再追踪a这个变量了(除非a的值在之后又被改变了)。这里的的htmlspecialchars函数就可以叫做是XSS的sanitizer。
1 $a = user_input(); // tainted
2 echo $a;
3 $a = htmlspecialchars($a); // not tainted because of sanitization
4 echo $a;

2. Strategy

2.1 high-level strategy

Taint analysis的难点在于已知source/sink的情况下,在茫茫多的变量中,如何精确地分辨source传过来的数据是否到达了sink。准确度很重要,如果不安全数据没有到达sink,但是你的分析却报了一条警告,那么这条警告就是false positive(FP);如果不安全数据到达了sink,但是你的分析却报了一条警告,那么这条警告就是true negative(TN)。没有人喜欢FP或是TN,试想一下一个杀毒软件整天报告说你的正常文件是病毒,却又遗漏了真正的病毒。

要追踪不安全数据的传播(taint propagation),其实很简单,变量总是一步一步传播的。比如说变量a被tainted了,然后b = a,那我们就说b也被tainted了,就是这么简单。

Tainted variable的传播方式主要可以分为赋值指令、布尔指令、算数指令、分支指令、位运算指令等。其中值得注意的是布尔指令和位运算指令,他们不是总能传播taintness的。考虑以下代码:

bool a = user_input(); // tainted
bool b = false; // not tainted
bool res1 = a && b; // not tainted
bool res2 = a || b; // still tainted

我们可以看到res1不再处于tainted状态,而res2依然被tainted,背后的原因我这里就不说了。

位运算指令也涉及到上述的问题,除此之外,它还涉及到追踪的精度问题,考虑以下代码:

uint8_t a = atoi(user_input()); // tainted
uint32_t b = 0xfffffff; // not tainted
b &= a; // tainted, but how?
uint32_t c = b & 0xffff000; // is c tainted?

我们可以清楚地意识到第三行的b是一个tainted variable,也就意味着它可能被攻击者所控制,根据之前提到的逻辑,c当然也应该是tainted的,因为它的值是从b里来的。但是事实如此吗?我们注意到a的长度是8个bit,而b的值是通过与a进行&操作得到的,也就是说攻击者至多只能控制b低位的8个bit,而c的值则完全不受b中低位的8个bit影响,所以实际上c并不应该被taint。

在下面的实现中,为简便起见,我们不考虑上面提到的布尔指令与位运算带来的问题,我们会简单粗暴地认为它们也总是会传播taintness的。

2.2 LLVM-specific strategy

如之前所说,在LLVM中,我们需要和LLVM IR生成出来的AST打交道,所以上述的high-level strategy必须落实到LLVM IR上才行。为了方便起见,这里的具体实现有所不同,上面说的是我们要先找到tainted data,然后追踪这些data如何在程序中传播。这里我使用的相反的策略,我打算找到程序中所有的常量(constant),那么除常量之外的,都是静态分析下无法确定具体值的变量,换句话说,它们的值是需要外部输入来确定的,也就是潜在的,可能被攻击者控制的值。 我们要关注的IR指令有以下几种:

  • StoreInst:语法是store src, dst,将src的值存到dst中,如果src的值是tainted的,那么dst的值也是tainted的
  • LoadInst:语法是dst = load src,将src的值存到dst中,如果src的值是tainted的,那么dst的值也是tainted的
  • CmpInst:用于比较数字的大小,包括整数与实数,假如该指令的一个或两个操作数被tainted,那么其结果也被tainted
  • CastInst:用于类型转换,如果被转换的变量是tainted,那么转换后的变量也是tainted的
  • BinaryOperator:所有二元操作符,包括算术指令,布尔指令,位运算指令等,虽然之前提到过布尔指令与位运算指令的问题,但是这里为了简化,直接定义成,如果两个操作数中有一个或两个是tainted的,那么它们的结果也是tainted的

LLVM中IR是以不同的的scope组织起来的,常用的scope从大到小排列如下:Module > Function > BasicBlock > Instruction。为了进行上述分析,我们可以通过遍历的方式来查看每一条instruction(在Module里遍历Function,在Function里遍历BasicBlock,在BasicBlock里遍历Instruction…),看这些指令是不是我们所关心的,但是也有更好的办法。回忆上面说到的AST,LLVM提供了不同scope的visitor(visitor pattern是compiler里最常见的设计模式之一),我们需要用到的是InstVisitor,顾名思义,InstVisitor会遍历AST中的每一条Instruction,我们只需重写我们关心的指令所对应的visit函数。

3. Implementation

首先我们需要做准备工作,即编译LLVM,这里我们使用LLVM3.8,编译大约需要20-40分钟不等。编译完成后,文章后面提到的所有LLVM相关程序如optllvm-dis等,都可以在llvm/build/bin目录下找到。

mkdir llvm; cd llvm
wget http://releases.llvm.org/3.8.0/llvm-3.8.0.src.tar.xz
wget http://releases.llvm.org/3.8.0/cfe-3.8.0.src.tar.xz
tar xf llvm-3.8.0.src.tar.xz
tar xf cfe-3.8.0.src.tar.xz
mv llvm-3.8.0.src src
mv cfe-3.8.0.src src/tools/clang
mkdir build; cd build
cmake ../src
make

然后开始写代码,首先我们需要新建一个struct并继承InstVisitor,并建立一个std::set来存储所有的常量。

struct TaintInstVisitor : public InstVisitor<TaintInstVisitor> {
  TaintInstVisitor() {}
  ~TaintInstVisitor() {}
  set<Value*> const_vars;
}

另外,我们需要一个判定常量的方法,这里我们使用LLVM自带的isa<Constant>()方法来检测目标变量是否是常量,如果一个变量是常量,那么我们就将这个常量加入上面定义的const_vars中;如果一个变量的值是从常量派生而来的,那我们也将它加入const_vars中,所以我们检测一个变量是否是常量的函数可以写成如下形式:

struct TaintInstVisitor : public InstVisitor<TaintInstVisitor> {
  // ...
  bool is_constant (Value* v) {
    return (const_vars.find(v) != const_vars.end()) || (isa<Constant>(v));
  }
}

接下来我们实现如何判断一个变量是否由常量派生而来。首先是StoreInst,判断方式很简单,如果src是常量,那么dst也是常量

struct TaintInstVisitor : public InstVisitor<TaintInstVisitor> {
  // ...
  void visitStoreInst(StoreInst &I) {
    // syntax: store src, dst
    // if src is a constant, then dst is a constant
    Value *op1 = I.getOperand(0); // src
    if (is_constant(op1)) {
      Value *operand2 = I.getOperand(1); // dst
      const_vars.insert(operand2);
    }
  }
}

然后是LoadInstCastInst,同样的如果src是常量,那么dst也是常量。在这里必须要提一下LLVM精巧的设计,在LLVM中,各种Instruction是继承自Value的,你可以通过Insturction的各种方法来得到关于这条指令的各种信息,而这个Instruction本身的值则代表了这条指令的返回值。比如说,load src,我们知道这条指令是将src的值赋给另一个变量,但是这个变量怎么表示呢?在LLVM里,load src这条指令本身就代表了被赋值的那个变量。在单操作数的指令中,如LoadInstCastInst,这条指令本身就代表了那个被赋值的变量。

struct TaintInstVisitor : public InstVisitor<TaintInstVisitor> {
  // ...
  void visitLoadInst(LoadInst &I) {
    // syntax: dst = load src
    // if src is a const, then dst is a const
    Value *op = I.getOperand(0); // src
    if (is_constant(op)) { // src is a const
      const_vars.insert(&I); // dst is also a const
    }
  }
  void visitCastInst(CastInst &I) {
    // syntax: dst = load src
    Value *op = I.getOperand(0);
    if (is_constant(op)) {
      const_vars.insert(&I);
    }
  }
}

剩下的则是各种二元操作符,包括比较,算术,布尔指令,等等,我们可以用一个统一的函数来处理它们。这里我们简单粗暴地认为只有当二元操作符的两个操作数都是常量的时候,我们才认为这个被赋值的变量也是一个常量。

struct TaintInstVisitor : public InstVisitor<TaintInstVisitor> {
  // ...
  void visitCmpInst(CmpInst &I) {
    handle_binaryOp(I);
  }

  void visitBinaryOperator(BinaryOperator &I) {
    handle_binaryOp(I);
  }

  void handle_binaryOp(Instruction& I) {
    Value *op1 = I.getOperand(0);
    Value *op2 = I.getOperand(1);
    // TODO: actually boolean operator don't follow: &&, ||, &, |
    if (is_constant(op1) && is_constant(op2)) {
      const_vars.insert(&I);
    }
  }
}

最后我们创建一个TaintInstVisitor的实例,并将其运行于这个Module,最后我们将检测到的所有常量打印出来:

struct Hello : public ModulePass {
  struct TaintInstVisitor : public InstVisitor<TaintInstVisitor> {
    // ...
  }
  virtual bool runOnModule(Module &M) {
    TaintInstVisitor tv;
    tv.visit(M);
    set<Value*> const_vars = tv.get_const_vars();
    for (auto it = const_vars.begin(); it != const_vars.end(); it++) {
      errs() << *static_cast<Instruction*>(*it) << '\n';
    }
    return true;
  }
}

4. Run

写完了Pass,下面就要开始运行了。首先我们要知道LLVM Pass运行于LLVM的bitcode上,一般来说,使用clang编译来得到bitcode:

clang -emit-llvm -o a.out.bc -c test.c

而我则偏好用wllvm来得到bitcode,wllvm是Python写的,你可以通过pip来安装它,然后用如下命令取得bitcode:

wllvm test.bc
extract-bc a.out # generate bitcode file called a.out.bc

wllvm的优势在于,当你在编译一个拥有许多源文件的project时,你也可以通过上面这两条简单的指令来获取整个项目的bitcode。

获取bitcode之后,我们使用LLVM的opt来运行它,这里我们写的Pass叫做Hello,我们可以使用如下命令来运行这个Pass

opt -load LLVMHello.so -Hello < a.out.bc > /dev/null

5. Verify

下面我们通过下面这个简单的程序来验证一下,其中只有b是常量,其他变量都是可以被攻击者控制的。

#include <stdio.h>

int main(int argc, char** argv) {
  int a = argc;
  int b = 1;
  int c = a + b;
  printf("%d\n", c);
}

使用上面提到的方法运行Pass,打印如下结果:

  %b = alloca i32, align 4
  %2 = load i32, i32* %b, align 4

这说明该程序中共有两个变量的值为常量,第一个是b,第二个是%2。其中%2是一个临时变量,它的值是通过load变量b的值得到的,也就是说它的值完全等于b的值。由此来看,输出结果与我们之前的分析相符。

我们可以通过llvm-dis将bitcode转换为IR,再来验证一下Pass输出的正确性。

llvm-dis a.out.bc

上述命令会生成IR文件,名为a.out.ll,主要内容如下:

@.str = private unnamed_addr constant [4 x i8] c"%d\0A\00", align 1

; Function Attrs: nounwind uwtable
define i32 @main(i32 %argc, i8** %argv) #0 {
entry:
  %argc.addr = alloca i32, align 4
  %argv.addr = alloca i8**, align 8
  %a = alloca i32, align 4
  %b = alloca i32, align 4
  %c = alloca i32, align 4
  store i32 %argc, i32* %argc.addr, align 4
  store i8** %argv, i8*** %argv.addr, align 8
  %0 = load i32, i32* %argc.addr, align 4
  store i32 %0, i32* %a, align 4
  store i32 1, i32* %b, align 4 ; %b is a constant
  %1 = load i32, i32* %a, align 4
  %2 = load i32, i32* %b, align 4 ; %2 is a constant
  %add = add nsw i32 %1, %2
  store i32 %add, i32* %c, align 4
  %3 = load i32, i32* %c, align 4
  %call = call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([4 x i8], [4 x i8]* @.str, i32 0, i32 0), i32 %3)
  ret i32 0
}

通过查看IR我们可以发现,Pass的输出结果是正确的,在这个程序里,只有%b%2的值是常量,而其他的值都是潜在的可以被攻击者控制的值。

6. More about LLVM

利用LLVM Pass还可以做很多很多事,简单的比如说生成call graph,control flow graph之类的,或者找出代码中所有的循环;稍微复杂点的,就不只是分析,可能会涉及到instrumentation,也就是利用LLVM更改程序行为,比如说在源代码中引入一个新的函数,并在所有特定类型的指令之后都插入该函数的调用。

当然LLVM也不是无所不能的,比如它不适用于没有源代码的项目,对于这种情况,或许使用来自Intel的Pin来做动态分析显得更为合适。

7. Taint analysis and others

7.1 Implicit flows

文章写到这里,看起来taint analysis已经很强大了,可事实上依旧存在它难以处理的情况,比如implicit flows。

考虑以下代码:

x = user_input(); // tainted
y = x; // tainted
if (y == 0) { // tainted
  z = 2; // not tainted
} else {
  z = 1; // not tainted
}
system(z);

沿用我们之前写的analysis,我们会得出xy是tainted的而z并不是tainted的结论,进而得出“system(z)是无法被攻击者控制的”这样一个结论。但是事实真的如此吗?我们注意到当y的值为0的时候,z的值必定是2;而当y的值不为0时,z的值必定为1。换句话说,z的值是由y的值来决定的,不同于上面提到的任何一种方式,这种方式是间接的,通过控制流(control flow)而非数据流(data flow)来决定的。

这时候,我们实际上陷入了一个进退两难的境地。激进的做法是,如果决定分支的变量与用户输入有关,则taint该分支中所有的变量,这会导致over-taint,很多不该被taint的变量最后也被taint了;保守的做法是,放弃在taint analysis中检测implicit flows,也就是说,在上面的代码中,z将不会被标记为tainted。不过,无论选择那一种做法,都是不完美的,都会导致分析的结果出现偏差。

所以就没有更好的办法了吗?说实话,我也并不是很清楚。不过如果你有兴趣,我会建议你google一下“causality inference taint analysis”,也许你会发现比taint analysis更好的方法。

(The End)

近况

作者 Jiahao Cai
2019年3月9日 00:00

近况

Mar 09, 2019

PhD vs Marriage

很久没写东西了,因为实在太空了,也因为实在太忙了。

从2018 fall的第一学期说起吧,那个时候算是太空了。上了master之后,我发现我们学校的cs master真的很简单,workload不是很大,然后老师给分也很仁慈。我就有点迷失自己,除了写下作业搞点研究之外就是天天打打炉石刷刷题,但是我其实对刷题没兴趣,所以打炉石更多一点 :)

读cs master的一个初衷当然就是在美帝找工作了,但是很搞笑的是,我的简历,我自认为还行,却被大公司拒了个遍,连面试的机会都没给。一整个学期下来,投了大概四五十个公司吧,马马虎虎地做烂了很多个oa,最后就电面了两个,有个属于技术面过了但是挂在HR面,因为我说不出来他们公司到底是干啥的… 当然我也很不喜欢找工作的体验,总感觉过简历在拼运气,而显然我的运气并不太好,总之找实习挺失败的就是了。

好吧就在这个时候,我跟着做research的教授,新来的,非常着急地在招学生,然后他又非常想拉我当他的phd,还老是出去请我吃饭,甚至跟我说you can transfer to phd anytime you want,说得我都不好意思了。当然我也挺想读他的phd,虽然他自己也刚phd毕业,但是我们的方向很match,而且我也觉得他挺厉害的。在第一个学期考完期末考之后,我依然没有找到实习,这时候我的感觉,说好听点,我觉得冥冥之中我注定要试一试读phd,说难听点,读phd是我那时候的最优解。然后我就毅然提交了转phd申请。大约一个月吧,申请就approve了,我读了一学期master之后,就转成了phd。

嗯,现在我承认我还没有准备好读phd。经济独立,phd头衔,本科直博,还可以顺便拿个master学位… 这些bonus在当时的我眼里变成了很重要的东西…

然后就是现在了,正式phd的第一个学期,跟ms完全不同的感受,对我来说如果说ms的难度是5的话,phd的难度大概是50吧… 这学期的难度就突然到了另一个level,导师好像把我想得太厉害了,分给我一个很复杂的project。硬着头皮做了2个多月,最后实在搞不下去就先搁浅了。然后上个星期换了个新project,不过现在已经3月了,而我们的目标依然是在学期结束前发出一篇顶会paper,只剩两个月不到了… 总之我现在就很忙很忙,今天春假开始了,但是我坐在家里一天没有出门,憋着写代码,跟导师交流,感觉时刻要爆炸。倒不是代码不会写,这点我还是很自信的,只不过我的domain background确实差了点,他跟我描述的project细节我都想不清楚,经常误解。唉,毕竟我只是个刚本科毕业,连公司实习都从没去过的愣头青罢了。

更惨的是,我是我导师的第一个学生,平时遇到问题了也没人问,只能直接问导师,终于有一天(其实就是今天),我把导师问烦了… 看看隔壁实验室八九个phd,层次分明,不禁还是有点羡慕。想到下学期有新人要来,感觉自己的水平实在有点堪忧,不过也无所谓,新人水平比我好可以多个人问问,水平比我差也可以一起自闭。不过还好我回到家里,家里还有个在一起很多很多年的女朋友,虽然我们俩的专业南辕北辙,很多技术上的事没法找她讨论,但是坐在一起的感觉可以稍微让我好受点。

最后,我也必须承认我之前膨胀了,感觉ms太简单,以为phd也不过如此,也许只需多花点时间罢了,但是现在看来真不是这样,phd真的是一场艰苦的修行,而我才刚刚开始罢了。之前在瑞典的时候,教授们都会主动说不要叫他们professor,我就把这个习惯带到了美国,我导师让我别老是对别人直呼其名,因为很多人不喜欢这样。我心里还想,为啥美国的教授这么矫情,我以后拿了phd就希望人家直接叫我名字。现在我渐渐理解了,phd is not just a title, it means a lot…


在读了一年PhD之后,我又决定quit PhD并加入工业界了,有兴趣的可以继续看这一篇

(The End)

SICP笔记 - 抽象的艺术

作者 Jiahao Cai
2018年8月24日 00:00

SICP笔记 - 抽象的艺术

Aug 24, 2018

sicp-cover

0. Overview

记得大二学C++的时候,老师对我们说面向对象最重要的就三点,抽象、继承与封装。但其实抽象却不是面向对象编程中特有的概念,抽象是编程世界最重要的概念之一,因为编程的过程其实就是使用编程语言描述现实世界的过程,而在这个过程中,最重要的过程之一就是对现实世界进行抽象,将其变成一个个抽象的模型。

1. 抽象

SICP的第一章使用了几个例子来描述了代码的抽象过程。

一开始我们的需求很简单,就是求[a, b]区间上所有整数的和,于是有了以下函数:

(define (sum-integers a b)
  (if (> a b)
    0
    (+ a 
      (sum-integers (+ a 1) b))))

过一会我们又有了新的需求,求[a, b]区间上所有整数的平方和,于是又有了以下函数:

(define (sum-squares a b)
  (if (> a b)
    0
    (+ (* a a)
      (sum-squares (+ a 1) b))))

接下来我们还会有更多类似的需求,比如求[a, b]区间上的立方和,比如我们要计算如下序列的值(该序列会缓慢地向\(\pi /8\)收敛):

\[ \frac{1}{1 * 3} + \frac{1}{5 * 7} + \frac{1}{9 * 11} + ... \]

(define (pi-sum a b)
  (if (> a b)
    0
    (+ (/ 1.0 (* a (+ a 2)))
      (pi-sum (+ a 4) b))))

仔细观察可以发现,上面的三个函数都满足同一种模式,我们可以从中抽象出一个模板函数:

(define (<name> a b)
  (if (> a b)
    0
    (+ (<term> a)
      (<name> (<next> a) b))))

实际上,这个抽象出来的模板函数,就是数学家们在很久之前发明出来的sigma notation,也就是:

\[ \sum_{n = a}^{b} f(n) = f(a) + ... + f(b) \]

注意在上面的代码中,我们缺少term以及next的定义,它们都是函数,分别代表要对当前数字要进行的操作,与获取下一个数字的方法。这里我们可以将这两个函数作为参数传入,这也是Lisp的优雅之处。下面是一个符合Lisp语法要求的模板:

(define (sum term a next b)
  (if (> a b)
    0
    (+ (term a)
      (sum term (next a) next b))))

这样一来,我们就可以将我们之前的三个函数分别使用该模板来改写:

(define (sum-integers a b)
  (sum 
    (lambda (x) x) 
    a 
    (lambda (x) (+ x 1)) 
    b))

(define (sum-squares a b)
  (sum 
    (lambda (x) (* x x)) 
    a 
    (lambda (x) (+ x 1)) 
    b))

(define (pi-sum a b)
  (sum 
    (lambda (x) (+ (/ 1.0 (* x (+ a 2)))))
    a
    (lambda (x) (+ x 4))
    b))

可以看到,函数简洁且易读了很多,这就是抽象的力量。

2. 再抽象

\[ \prod_{n = a}^{b} f(n) = f(a) * ... * f(b) \]

与求和类似,我们也可以轻易地定义一个乘积的模板,只需将求和模板中的+改为*即可:

(define (product term a next b)
  (if (> a b)
    0
    (* (term a)
      (product term (next a) next b))))

如果仔细观察的话,我们可以对模板sumproduct再抽象成一个更高级的模板,就叫它accumulate吧:

(define (accumulate combiner null-value term a next b)
  (if (> a b)
    null-value
    (accumulate combiner (combiner (term a) null-value) term (next a) next b)))

然后再重新定义一下sumproduct

(define (product term a next b)
  (accumulate
  *
  1
  term
  a
  next
  b))

(define (sum term a next b)
  (accumulate
  +
  0
  term
  a
  next
  b))

又是一次成功的抽象,但是我们却会发现,这个更高级的抽象并没有给代码带来多大的提升,既没有使代码更精简,也没有使代码更可读。

一味地追求更高级的抽象并不是什么好事,很多纯粹的东西看起来很美,但是却无法在现实世界中立足。作为一个工程师,一个engineer,终究还是要以解决问题为主要目标的,在各种复杂条件下达成完美的妥协,又会有一种平衡之美,那是另一种不亚于纯粹之美的美好。

大三上软件工程的时候,某个我认为非常没水平的老师说软件工程的精髓在于trade-off,我觉得这也许是他说过最有水平的一句话了吧 :)

(The End)

代码的taste

作者 Jiahao Cai
2018年8月10日 00:00

代码的taste

Aug 10, 2018

Linus on TED

今天在YouTube上看了Linus Torvalds的一场TED,Linus拿了一小段代码来说明代码的taste问题。

代码很简单,就是在一个单向链表里删除一个指定节点,一般学校里教的写法都是像下面这样的:

void remove_list_entry(linked_list* entry) {
    linked_list* prev = NULL;
    linked_list* walk = head;

    //Walk the list 
    while (walk != entry) {
        prev = walk;
        walk = walk->next;
    }

    // Remove the entry by updating the
    // head or the previous entry
    if (!prev) {
        head = entry->next;
    } else {
        prev->next = entry->next;
    }
}

值得注意的是,在代码的结尾有一个if statement,这是用来区分两种情况的:

  • 被删除的节点是这个单向链表的head,此时我们需要一个新的head
  • 被删除的节点不是这个单向链表的第一个节点,此时我们只需将被删除节点的前置节点的next指针指向被删除节点的后置节点即可。

但是Linus认为这种写法是缺少taste的,他更喜欢下面的这种写法:

void remove_list_entry(linked_list* entry) {
    // The "indirect" pointer points to the
    // *address* of the thing we'll update

    linked_list** indirect = &head;

    // Walk the list, looking for the thing that
    // points to the entry we want to remove

    while ((*indirect) != entry)
        indirect = &(*indirect)->next;
        
    // .. and just remove it
    *indirect = entry->next;
}

与上面的写法不同,这里我们直接将需要被删除节点的后置节点的next地址复制到被删除节点的前置节点的next指针上,无需考虑被删除节点是否是head这个问题,从而也省去了第一段代码中的if statement,这毫无疑问是更加方便的做法。

下面说一下第二段代码的原理。

首先下面的是我们这里用到的linked_list的定义,普通的链表节点:

typedef struct Node {
    int data;
    struct Node *next;
} linked_list;

假设此时我们的链表中有5个节点,其内容分别是1/2/3/4/5,我们希望删除节点3。

       head                       entry -+
        |                           |
    +-------+     +-------+     +-------+     +-------+     +--------+
    | 1 |   |---->| 2 |   |---->| 3 |   |---->| 4 |   |---->| 5 |NULL|
    +-------+     +-------+     +-------+     +-------+     +--------+

linked_list** indirect = &head;这一行代码将会构造一个指向head指针的二级指针:

       head                      entry -+
        |                           |
    +-------+     +-------+     +-------+     +-------+     +--------+
    | 1 |   |---->| 2 |   |---->| 3 |   |---->| 4 |   |---->| 5 |NULL|
    +-------+     +-------+     +-------+     +-------+     +--------+
        ^
        |
      +---+
      |   |
      +---+
     indirect

接下来是一个while loop

while ((*indirect) != entry)
  indirect = &(*indirect)->next;

首先我们解读(*indirect) != entry,一开始indirect指向head,而*indirect则等于head指针再被evaluate一次,那么就等价于链表的head,很显然此时的*indirect不等于entry,因为entry指向的是第三个节点。于是进入while loop执行indirect = &(*indirect)->next;,因为*indirect就等价于head,那么&(*indirect)->next就可以取得head的next指针所在的地址,这一行就相当于将head的next指针的地址赋给了indirect,如下图所示。

赋值结束后,我们又回到了判断条件(*indirect) != entry,此时indirect指向head的next指针,那么*indirect就相当于evaluate了head中next指针中所存储的地址,也就是第二个节点的地址。此时再判断,发现(*indirect) == entry依然不成立,继续执行while loop内部的代码。

       head                      entry -+
        |                           |
    +-------+     +-------+     +-------+     +-------+     +--------+
    | 1 |   |---->| 2 |   |---->| 3 |   |---->| 4 |   |---->| 5 |NULL|
    +-------+     +-------+     +-------+     +-------+     +--------+
          ^
          |
        +---+
        |   |
        +---+
       indirect

同样地,这次indirect = &(*indirect)->next会将indirect指向下一个节点的next指针,也就是第二个节点的next指针,如下图所示。此时再回到while loop的判断条件(*indirect) != entry,我们发现*indirect就等价于第二个节点的next指针,也就是第三个节点,也就是entry,于是退出while loop

       head                      entry -+
        |                           |
    +-------+     +-------+     +-------+     +-------+     +--------+
    | 1 |   |---->| 2 |   |---->| 3 |   |---->| 4 |   |---->| 5 |NULL|
    +-------+     +-------+     +-------+     +-------+     +--------+
                        ^
                        |
                      +---+
                      |   |
                      +---+
                     indirect

最后执行*indirect = entry->next;,由于indirect指向第二个节点的next指针,所以*indirect则等价于第二个节点的next指针,所以实际上这行代码就是将第二个节点的next指针指向entry的下一个元素,从而完美地将entry从链表中删去。

                          entry -+
      head                          |
    +-------+     +-------+     +-------+     +-------+     +--------+
    | 1 |   |---->| 2 |   |--   | 3 |   |---->| 4 |   |---->| 5 |NULL|
    +-------+     +-------+  \  +-------+     +-------+     +--------+
                     *indirect \              /
                                +------------+

第一次看到Linus的这种写法时,确实觉得非常惊艳,不过我还是对于这段代码保持一个中立的态度。首先,我们可以通过在头部增加一个dummy node,以这种更易读的方式来完成这项任务;其次,从现实世界的软件工程来看,这样的代码未必是最能提高生产力的代码。不过尽管如此,我还是从这个视频里看到了非常珍贵的东西,那就是对代码的追求以及对固有思维的打破。事实上,很多人会忘记对自己已经学到的代码进行思考,把它们当作理所当然的事。比如只会用学校里教的那种最古板的办法去删除链表中的节点,却不去思考这种方式有没有提升的空间。而这又可以拓展到更大的语境下,那些一两年前你认为是对的事情,现在的你是否有重新思考过它们呢?还是你会理所当然地相信曾经相对幼稚的你得到的结论呢?推特上那个Richard Feynman的账号很有意思,其中有一条我很喜欢的推

Learn. Unlearn. Relearn.

Unlearn?听起来会不会有点熟悉,是啊,Master Yoda训练Luke的时候也说提到过这个词。Unlearn很重要,在我看来,第一次的那个Learn,我更愿意称之为模仿,你模仿你的老师,模仿书本上的知识,最后慢慢地入门。而Relearn才是你真正理解某个概念的本质的时候,我觉得这也是最容易出现创新的时候。

you must unlearn what you have learned

最后,请注意Linus最开始写出这段代码的动机,学校里教的那种方法,多了一个if statement。每个人都应该想想,对于你自己来说,这是“仅仅只是多了一个if statement罢了”,还是“这里居然有一个多余的if statement”呢?


Thanks to the discussion on the Internet:

(The End)

我在欧洲的几次旅行

作者 Jiahao Cai
2018年7月2日 00:00

我在欧洲的几次旅行

Jul 2, 2018

travel in europe

1. 巴黎

巴黎给我的印象就是很美,宁静又喧嚣的,古朴的美。除此之外,还感觉巴黎的地铁太过复杂,就好像一段写满了temporary hacking的代码,再就是巴黎的很多地方都可以看到中文,以及法国人真的很不喜欢用英语,每次去饭店点个菜都发现菜单是全法语的,点菜困难。我去了几个著名的地方打了卡,巴黎圣母院,卢浮宫,埃菲尔铁塔,等等。

paris paris paris paris

走的那天在地铁上碰到一个法国女生,聊了一会发现她会好几句中文,真是厉害。她对我只在巴黎呆两天且没吃马卡龙的行为表示强烈抗议,我想了下,出门之前貌似是没怎么做功课,一般就随便走进一家看得顺眼的饭店就吃了,万幸的是貌似都挺好吃的。

当天晚上我离开巴黎前往布鲁塞尔,离开之前刚吃了一大块liver撑得要死,结果一上火车,乘务员又开始准备吃的了。这时候我才发现为什么这张巴黎到布鲁塞尔的车票这么贵,要50欧+,虽然很撑,我还是把火车上的食物都吃完了,因为真的很好吃。

2. 布鲁塞尔

布鲁塞尔是个很小很精致的城市,就像比利时的其他城市一样,我并不是非常喜欢,因为建筑物之间的距离太近了,显得有些压抑。我刚到比利时的时候,就在公车上碰到一个五六十岁的老奶奶和她的孙子,她说她去过中国,包括北京,台湾。我们聊了一会乱七八糟的事,她告诉我在布鲁塞尔一定要注意安全,特别是在这样的深夜里(我总是深夜里到达一个城市…),她说“I am not trying to scare you, but they can be very violent”,她还跟我说坐在车上不要把自己的包放下来,我看了下她,发现她真的上了车还背着包的。

布鲁塞尔吃的东西还不错,包括薯条,海鲜,巧克力以及华夫饼。

brussel waffle brussel seafood brussel seafood

晚上在布鲁塞尔去阿姆斯特丹的火车上,对面坐了一个学business的比利时人,David,他说他打算做一个可以追踪各种演唱会音乐会信息的App。我问他怎么做冷启动,他说他已经拉到了很多酒吧的投资,到时候可以通过酒吧来推销他的App。说着他就打开了他的ppt给我看,然后我突然发现他就是鲁汶大学的大四学生,作为一个刚被鲁汶拒了没几天的人,我感到有点尴尬,我们就顺便聊了一会关于鲁汶大学的事。他跟我说,他缺个程序员,问我写过App没,我说我就写过个Android App,他就想叫我跟他一起创业,说他现在有几万欧的启动资金,想让我去当主程。我很怂地说我需要先完成我的Master Degree,他的反应大概就是,come on…

我发现我遇到的好多外国人对中国的了解确实挺少的,我跟David聊了很多中国的东西,比如中国的现状,文革,计划生育,GFW,我们如何在Big Brother的Watching之下无所遁形之类的,他就一直说This is crazy。其实我觉得很多事何止crazy,简直就是creepy了。

他也给我介绍了一个有趣的活动,叫做urbex,全名应该叫Urban exploration,主要目的是探索各种人造建筑,比如下水道,地下墓穴,隧道等。David说比利时有很多废弃的城堡,他最喜欢和他女朋友一起去探索那些城堡了,他说他是带着respect去探索的,并不会去改变城堡原有的样子。当然有时候这种exploration也会带来危险,比如他说他会在废弃城堡里见到吸毒的人,他们毫无respect,经常会破坏城堡,也有可能在毒品的作用下发狂从而伤害别人;比如说他也会去探索废弃的化工厂,但是去之前需要做很多的功课,去的时候一定要戴上防毒面具,不然的话很容易中毒。

我们也分享了很多我们喜欢看的剧,比如South Park。他还喜欢看Rick and Morty,海贼王,King of the hill之类的,甚至还掏出一件写着中文的“一家之主”的套头衫给我看。听了他的推荐,现在不知不觉我也看King of the hill看到第九季了 :)

David貌似是去海牙看他的女朋友,而我则要一直坐到阿姆斯特丹。

3. 阿姆斯特丹

阿姆斯特丹真的很自由很美好。我到达阿姆斯特丹的时候是晚上11点,一推开Hostel的门就看到5个法国人在喝酒抽大麻,最大的92年生的,最小的97年。他们看到我很开心,请我一起喝伏特加和另一种酒,当然还有请我抽大麻。我们分享了很多事,主要是音乐上的,他们给我推荐了好多法国的本地歌手(尽管我都不认识),我们一起讨论共同喜欢的摇滚乐队,Pink Floyd, Halestorm, Guns N’ Roses, AC/DC之类。当然我们还都很喜欢Star Wars,那段时间正好是The Last Jedi上映的时候,其中有个法国人打扮得就很Jedi,他戴上帽子就很像一个真正的Jedi。后来我又被拉去了酒吧,准确地说是Coffee Shop,就是合法卖大麻的地方。那是晚上12点,阿姆斯特丹的Coffee Shop异常拥挤,我们在一家外面等了20分钟,放弃,然后在另一家门口等了10分钟,终于让我们进去了。我们在里面一起喝啤酒也喝龙舌兰,我第一次学到喝龙舌兰的正确姿势,原来需要配上盐和柠檬。Coffee Shop里挤满了人,当听到有人在唱Sweet Child O’ Mine的时候,我们都沸腾了,果然音乐是没有国界的,那真的是一个让我,即使现在回想起来依然万分感动的瞬间。喝多了之后,并不知道是大麻还是酒精的作用,我觉得我开始漂浮,朦朦胧胧中听到有个法国人跟我说他喜欢男人,我表示非常理解,我也确实觉得男人和男人呆在一起是很有趣的一件事。半夜三点多,我觉得我快醉了,就打算离开,我跟每个人拥抱说再见,然后我就走回了Hostel,一路上我看到阿姆斯特丹依然充满活力充满生机,大批的人依然在活跃在这个城市的夜里,这让我感觉非常美好。

第二天早上起来,大家都没醒,但是我得起来准备退房了,逛一逛阿姆斯特丹(阿姆斯特丹有很多很可爱的博物馆),然后晚上又要准备奔赴德国汉堡了。我想了想,写了张纸条向我的法国朋友们告别,我知道他们一定能看到这张纸条,因为我把这张纸条压在了那瓶没喝完的伏特加的下面 :D

farewell
请原谅我糟糕的handwriting

4. 汉堡

我在汉堡住了两三天,不过大部分时间都又回到荷兰的恩斯赫德去了,因为那时候我刚被University of Twente录取,想过去看一下,而这个学校正好在荷兰与德国的边境上。我对汉堡的印象并不是很深,就记得我去的时候基本都是阴天下雨,不过德国的啤酒和食物深得我心。记得比较清楚的一件事是,我在汉堡闯红灯,侧面有辆车开过来,完全没有减速的意思,我只好很快速而又狼狈地跑到对面。这与荷兰瑞典就大不相同,荷兰瑞典的司机一定不会这么干。有好多次,我一个人站着等红灯,司机直接就停下来让我先走了,我第一次碰到这个情况的时候简直一脸懵逼。感觉这是不是跟传说中的“德国人特别遵守规则”有关系,鉴于我遇到的例子太少,也许这只是我的脑子里的stereotype吧。

后来看了一部德国电影,The lives of others(中文名愚蠢地翻译为窃听风暴),又对德国有了新的认识。

值得一提的是,汉堡到哥本哈根的火车很有趣,是一截很短的火车,开着开着就开到船肚子里去了,然后我们就下车到船上,该吃饭的吃饭,休息的休息,等船开到对岸了,我们再回到火车上,直到开到终点。

hamburger to copenhagen hamburger to copenhagen

5. 卑尔根 & 奥斯陆

挪威从一开始就惊艳到我了,从飞机上往下看,可以看到白雪皑皑的峡湾,就像大地起伏的皱纹,那个时候,真的会感觉心灵很宁静,而且在心中升起一股对自然的敬畏。

fjord in Norway

卑尔根是个很美丽很美丽的城市,美中不足的是卑尔根每年下雨的日子往往接近300天,有“欧洲西雅图”之称,而人们无法在雨天领略卑尔根全部的美。

view in Bergen view in Bergen view in Bergen

刚到卑尔根的时候,我准备去一家当地比较著名的餐厅吃午饭,到了之后发现那里已经没有座位了,于是就跟一个印度朋友,Pankaj,坐在一起聊了起来。Pankaj说他是来哥本哈根参加一个Docker的conference,结束之后顺便来挪威玩的,他说他在印度某high frequency trading工作,我一听他是搞IT的,就开始跟他聊了一些业界的事。他来挪威是参加了一个叫做Norway in a nutshell的项目,这个项目很棒,会带他去看挪威最美的峡湾,去划独木舟,去探险去泡温泉等等。他说他其实还有个朋友跟他一起来开会的,可是他朋友是个素食主义者,所以就不愿意和他一起来吃饭,他朋友去海洋公园看动物去了 :D 那天下雨,整个卑尔根都雾蒙蒙的,我和Pankaj在城里漫无目的地闲逛。卑尔根真的是个小城,就这样随便走走,我们已经逛完了大部分的卑尔根,包括鱼市,卑尔根古城,布吕根码头以及各个教堂。虽然是小雨,但是在小雨下淋几个小时也不是什么轻松的事,后来我们选择去喝咖啡暖一下,喝着喝着,Pankaj的朋友也过来了,这好像是我第一次跟一个素食主义者相处。他并没有显得跟我们有什么不一样,喝完咖啡之后,我和Pankaj在他面前若无其事地啃着鹿肉香肠,他也并没有什么反应,甚至还走到M记里买了一个素食汉堡。傍晚我们在某个广场分别,Pankaj和他的朋友第二天要去斯塔万格(Stavanger),而我则要留在卑尔根,希望他们一切都好。

我很幸运,第二天卑尔根的天气是个大晴天,晴天的卑尔根真的很美,我一个人在漫无目的地走着,走累了就随便坐一会,有时候在观察这里的人是如何生活的,有时候在观察远方清澈的群山,有时候只是单纯地看鸟,喂鸟。

view in Bergen view in Bergen view in Bergen

去卑尔根不得不去的就是弗洛伊恩山,风景很美,并不是很高,大约40分钟可以到山顶,也可以选择坐索道上下山。在山顶可以俯瞰整个卑尔根,还可以看到北大西洋上的船缓缓开过。

Fløyen, Bergen, Norway

晚上11点我要坐夜车去奥斯陆,快要夏天了,北欧的白天已经变得很长很长了,一直到晚上11点,天也没有完全黑,我一直静静地坐在湖边,享受这挪威的阳光。后来我有点后悔坐了夜车去奥斯陆,因为我突然发现,在挪威,沿途的风光是如此不同,我应该白天坐火车的,这样我就可以欣赏更多挪威的风景了。

到了奥斯陆之后,才发现为什么说卑尔根的人都会自称来自卑尔根而不是挪威 XD 因为跟卑尔根比起来,奥斯陆真的逊了好多筹。个人主观感觉就是奥斯陆好土,而且几乎每走500m就会看到一个乞讨的难民,让人感觉有些不自在。我对奥斯陆仅有的回忆就是,我在奥斯陆拍了一张魔幻的照片,我总觉得这是人类败给AI之后的,荒芜的世界,让我感到很压抑。

Oslo, Norway

就好像是The Matrix里的世界,让我有种驾驶着Nebuchadnezzar冲破这些乌云直到看见太阳的冲动。

Nebuchadnezzar above the black sky

值得一提的是挪威的食物,大部分时间我都在吃M记的汉堡,一方面是因为挪威的食物太贵了,另一方面是因为挪威的食物有些真的欣赏不了,不过在卑尔根吃的鱼汉堡还是很让我怀念的。

6. 罗马

罗马是我去过最喜欢的城市,没有之一。雄伟壮观的万神殿与斗兽场自不必说,更让我沉醉的是罗马的艺术氛围,古老的教堂和随处可见的街头艺人。其厚重的历史无处不在,比如说罗马居然没有地铁,为什么呢,因为每次一开挖的时候就会挖到各种各样的遗址,导致施工无法继续进行。罗马的食物也很诱人,披萨,冰淇淋,提拉米苏…

Roma, Italy Roma, Italy Roma, Italy Roma, Italy

去罗马的时候我选择了Airbnb而不是像以前一样住在hostel里,因为像我这样不太主动开口说话的人,住在hostel里也很少遇到会一起说很多话的人,而房东则不一样,至少我可以跟本地人交流一些话题。我的房东,Alberto,是一个罗马人,还是本科生,在罗马大学读生物,他和他哥哥Dario住在一起,Dario是罗马大学的物理phd,看到他们两个之后,这是我这辈子第一次觉得,有兄弟姐妹真的是一件好事,sigh。我们一起谈论了很多有意思的事:

  • 第一天晚上我与Alberto站着聊天,他说他从来没有跟中国人聊过天,他对中国人的所有了解都来自于附近的中国商店,他只觉得中国人似乎很沉默,然后working crazy,然后我就试着解释了一下这些现象。不过这并不是重点,重点是我们聊天的时候有一只蚊子飞来飞去,我们都在骂damn mosquito,然后开始抓蚊子,Alberto成功把蚊子抓在了手里。当他发现这只蚊子还活着的时候,他说 “Let’s save this life”,然后就一路小跑打开窗户把蚊子给放了出去。说实话,这是对我冲击挺大的一件事,从前我一直以为这种事只会出现在艺术作品里,我真的从来从来没有想过要这么做,直到今天我还经常想起这件事,想起Alberto的举动。

  • 第二天晚上Alberto和Dario都在,我们聊了一会关于中国的事,依然是关于现状,关于计划生育,关于GFW,关于我们的隐私。特别搞笑的是Alberto去上厕所了,我跟Dario就开始瞎扯,他说自己是物理phd,我马上就说那你一定知道Richard Feynman吧,他很惊讶地问我怎么知道Feynman的,他说“not a lot of people know Feynman”,我说是某个教我的professor告诉我的。我们聊了很多关于Feynman的事,包括他如何不屑于诺贝尔奖,包括费曼讲义如何使Dario获益。后来Dario刚说完“You know Feynman says physics is like sex…”,Alberto刚好走过来说“Are you talking about sex? Ah, woman, it’s always about woman”,于是我跟Dario相视一笑。

  • Dario说他花了2000欧买了一辆1979年的房车,现在已经修好并且可以开了,他打算phd毕业之后开着这辆房车一直从罗马开到北极圈,然后再换一条路线开回来,他算了一下,一路上都住在他认识的人家里的话,单程大概就要开5000公里。他说他一定要去,就算到时候只有他一个人也要去,走之前我特意加了他的facebook,我对他说,你要出发的时候一定要告诉我 :)

  • 在罗马有一个Trevi Fountain,中文世界里有叫它许愿池的。我一开始还以为是投硬币就可以许一个愿望的,但是Dario告诉我并不是这样的,他说如果你在Trevi Fountain里投下一枚硬币,那就说明你这辈子会再次回到罗马。我投下了一枚硬币,并且坚信我会再回到罗马。

(The End)

瑞典生活总结

作者 Jiahao Cai
2018年6月21日 00:00

瑞典生活总结

Jun 21, 2018

flag of sweden

0. Overview

在瑞典呆了十个月,迄今为止人生中最美好的十个月之一,我觉得我有必要记录一些东西,碰到的美好的事情也好,日常琐碎的衣食住行也好,随便写点吧。

先说几个我知道的事实感受一下瑞典吧,449,964平方公里,1000万左右人口,其中100万外来移民。可以拿浙江比一下,浙江面积104,141平方公里,5657万的人口,瑞典面积是浙江的四倍多,人口却只有五分之一都不到。同时,瑞典有着超过60%的森林覆盖率,地广人稀,又有各种森林,如果在瑞典的话,充分跟大自然接触是一件必须的事。

1. 吃

我到瑞典之前一个菜也没做过,也没烧过饭,不过我还是非常适应地生活了下来,这可能是由于我对中餐没有什么执念吧。瑞典的食物都非常健康,或者说原始也行,对于能够欣赏食物本身味道的人来说,瑞典的食物还是很不错的。最近我回到国内,明显就感觉国内的食物油盐太重,吃不下去,非常怀念瑞典的食物。

1.1 早饭

刚去的时候早饭基本就吃吐司+煎蛋/煎培根/果酱/蔬菜等,后来发现买一大袋吐司经常吃不完坏掉,就开始尝试别的了,比如麦片/玉米片/坚果(?就是那种各种乱七八糟东西混在一起的,包括但不限于葡萄干/椰子干/核桃仁等等等等),然后拌一下酸奶/鲜奶就完事了。或者直接前一天晚上买做好的面包,第二天早上起来吃一个就行了。后期我的胃口变小了,一般就吃两个白煮蛋,再喝点酸奶,吃两三个肉丸(瑞典肉丸真的很好吃,我一天到晚当零食吃)。

1.2 午饭以及晚饭

第一个学期我很忙,作息表基本上就是早上8点出门,中午12点吃饭,下午3点吃一块巧克力(买那种体积大热量低的,增加饱腹感),晚上7点图书馆关门,我也就回去吃饭,睡一会,然后接着干活。那段时间中午都吃的学校的沙拉,不得不说学校的沙拉真的很健康,一开始还觉得不习惯,后来天天吃就觉得,真的还挺好吃的。

sweden lunch sweden lunch sweden lunch

后来空一点的话,一般就回去自己做了,一般就吃面,放点蔬菜放点肉什么的,平均下来一餐应该40SEK就能解决了,还是很便宜的。瑞典的三文鱼便宜又好吃,有段时间我连吃了一周三文鱼,烧法很简单,就是柠檬水和黑胡椒腌10分钟,然后放锅里煎1分钟,我一般只煎三面,朝上的那一面不煎,这样一餐大概也就60SEK吧,还是很好的。

sweden salmon sweden salmon

至于出去吃,一般都是80-150SEK,我觉得还行,我在村里的自助餐一般也是这个价,跟普通的食物没啥大区别。

2. 出门

出门这个事其实我没啥发言权,虽然我还跑了挺多地方的,但是我走的那天有人告诉我说瑞典貌似有学生优惠码,有时候从我那村里坐到斯京才花60SEK,当时我的内心 wtf??? 我买的票都是三到五百不等的,心好痛。瑞典的火车速度我觉得还行,100多公里每小时吧,国内跑跑挺方便的。瑞典进火车站倒是很方便,也不用安检,我就住在火车站走路5分钟距离的地方,一半就开车之前10分钟稍微整理一下就可以出门了,赶到车站直接上车就行了。

瑞典查票不是很严格,我有好多次火车坐完了都下车了也没人来查票。瑞典的官方火车App很不错,可以直接在上面买票,买完会有一个二维码,检票的时候把二维码给检票员看一下就行了,App还会实时推送这趟火车的行驶进度以及意外情况(如果有的话)。

至于飞机,26岁以下的人都可以去芬兰航空或者北欧航空购买青年票,有时候价格便宜到不敢相信,我记得12月有次我看北欧航空上的机票,回国往返加起来才2000+,非常实惠,去欧洲还是要趁早 XD

3. 杂项

  • 瑞典的大学之间合作很紧密,我之前有个Machine Learning的项目要做,但是我的Mac性能不够,我就跟我的导师说这件事。我的导师告诉我去借一台Super Computer,让我直接发邮件给service center就行了,就算这个学校没有Super Computer,你也可以从别的学校去借,比如CTH或者KTH。然后我就发了封邮件,过了两天就给了我一个超算的账号,144核,500G内存以及20T的硬盘空间。

(To be continued…)

如何在终端中优雅地听歌

作者 Jiahao Cai
2018年4月10日 00:00

如何在终端中优雅地听歌

Apr 10, 2018

play_music_in_terminal

0. 起因

起因就是对探索新歌失去了兴趣,并且觉得各种音乐播放软件都不怎么好用,而且懒癌发作,觉得写代码的时候想要换歌还要切换到音乐播放器再切回来实在太烦了。于是我把自己喜欢听的歌都下载下来了,放到一个文件夹里,准备用终端播放。整个过程非常简单,我所需要的只是一个想法和几行代码,迭代几次就搞定了。

1. 几次迭代

查了一下网上所谓的终端音乐播放器,似乎都不怎么样,有些还写了巨丑无比的UI,在命令行里画UI是个需要taste的事,画不好就会很丑,显然我不需要这些东西。找着找着,发现macOS上本来就有个命令行播放器,叫afplay,用法也很简单afplay后面加文件名就OK了,我想了想就用它了吧。

一开始想要的功能很简单,我需要方便地随机播放文件夹下的所有歌曲。我想了想,在.zshrc里写下了如下代码:

BYel='\e[0;33m';
function m() {
    m_path=~/Music/

    song_num="$(ls $m_path | wc -l)"                 # Total number of songs
    timestamp=$(date +%s)                       # Timestamp as millsecond, e.g. 1523305473
    song_index=$(($timestamp%$song_num+1))      # Generate a random song index

    song="$(ls $m_path | sed -n "$song_index"p)"     # Get the name of the certain song
    echo -e "${BYel}$song"
    afplay "$m_path$song"                            # Play it!
}

这样的话,每次我在终端里输入m,就可以随机播放一首歌曲。但是此时我面临了四个问题:

  1. 我不希望我为了这个“播放器”单独开一个终端,每次我要切歌,我还得先切到那个终端,
  2. 当我连续切歌时,随机性很差,因为文件是按文件名排序的,连续切歌时,时间戳增长不多,往往放来放去都是同一个人的歌,
  3. 目前的播放器实在太佛系了,只能随机放歌,其实有时候我也会想定向检索一首歌的,
  4. 每首歌放完之后都要重新输入m才可以开始下一首,是否可以自动播放下一首。

下面一个个解决这些问题:

  1. 我不希望我为了这个“播放器”单独开一个终端,每次我要切歌,我还得先切到那个终端

很简单,将afplay的进程改为后台即可,具体做法是加上一个&号,然后你就可以接着在这个终端里干活了。

function m() {
    ...
    afplay "$m_path$song" &                          # & will make the process running background
}

如果需要切歌,可以在终端里输入fg,将后台进程调到前台,然后ctrl+C终止之,但是其实问题并没有得到彻底解决。假如你在终端A中听歌,此时你在终端B中工作并产生了切歌的想法,你在终端B中输入fg,afplay的进程并不会被调到前台,你还是得回到终端A去操作这一切,这并不是我们想要的结果。此时我们可以定义一个新的函数n,这个函数主要就是找出afplay的pid,然后使用kill向它发信号并终止该进程,这里使用INT信号,其效用等同于ctrl+C。

function n() {
    pid="$(ps -ef | grep afplay | grep -v grep | head -1 | awk '{print $2}')"
    kill -INT $pid
}
  1. 当我连续切歌时,随机性很差,因为文件是按文件名排序的,连续切歌时,时间戳增长不多,往往放来放去都是同一个人的歌,

显然单纯地使用时间戳并不是一个好主意,我们需要别的随机数,Shell其实本身就给我们提供了随机数函数,我们可以通过$((RANDOM))来调用。这里我依然使用了时间戳,我把时间戳和随机数乘起来得到新的随机数再使用,这完全是个人喜好问题,你只用$((RANDOM))也没有关系。这样一来,你在不停切歌的时候,歌曲就不会从随机播放变成了顺序播放了。

function m() {
    ...
    dummy1=$((RANDOM))
    timestamp=$(date +%s)
    dummy=$(($dummy1*$timestamp))
    song_index=$(($dummy%$song_num+1))
    ...
}
  1. 目前的播放器实在太佛系了,只能随机放歌,其实有时候我也会想定向检索一首歌的,

这个也很简单,这个播放器运转的流程是1.生成随机数–>2.统计目录下歌曲数目–>3.计算歌曲索引值–>4.获取对应歌曲名–>5.播放歌曲,我们可以在第二步和第四步里加一个过滤器,其实就是一个所有Unix系用户都熟悉的东西,grep,具体代码见下:

BYel='\e[0;33m';
function m() {
    m_path=~/Music/
    keyword="."                                                     # default: play all the songs
    if [ -n "$1" ]; then                                            # play songs by keyword
        keyword="$1"
    fi

    song_num="$(ls $m_path | grep -i -e $keyword | wc -l)"               # Total num of qualified songs
    dummy1=$((RANDOM))
    timestamp=$(date +%s)
    dummy=$(($dummy1*$timestamp))
    song_index=$(($dummy%$song_num+1))                              # Generate a random song index

    song="$(ls $m_path | grep -i -e $keyword | sed -n "$song_index"p)"   # Get the name of the qualified song
    echo -e "${BYel}$song"
    afplay "$m_path$song" &
}

这样一来,我们就可以使用关键字检索我们要听的歌了,实际上,我们可以看作,每次听歌的时候,该播放器都临时生成了一张歌单,这个歌单是由所有满足你关键字的歌组成的。酷炫的是,由于我们使用了强大的grep,所以输入的关键字可以是充满想象力的正则表达式 ;)

  1. 每首歌放完之后都要重新输入m才可以开始下一首,是否可以自动播放下一首。

这里需要用到while循环和wait,while(1)死循环就是为了一直播放音乐,wait主要是为了等待前面的命令执行完毕,在这里afplay什么时候执行完毕呢,就是一首歌放完的时候,所以正好一首放完就可以开始放下一首了。当然要注意这里不能让afplay后台了,因为这会使wait失效,此处我们令整个函数后台运行。为了保持之前我们只要在命令行里输入一个m就开始放歌的简易方式,我们重命名原函数为music_by_keyword()然后用一个m()函数将这个函数包裹起来,这样一来,它的运行方式就跟之前一样了。

function m() {
    music_by_keyword $1 &
}

BYel='\e[0;33m';
function music_by_keyword() {
    m_path=~/Music/
    keyword="."                                                         # default: play all the songs
    if [ -n "$1" ]; then                                                # play songs by keyword
        keyword="$1"
    fi

    song_num="$(ls $m_path | grep -i -e $keyword | wc -l)"                   # Total num of qualified songs
    while [ 1 ]
    do
        dummy1=$((RANDOM))
        timestamp=$(date +%s)
        dummy=$(($dummy1*$timestamp))
        song_index=$(($dummy%$song_num+1))                              # Generate a random song index

        song="$(ls $m_path | grep -i -e $keyword | sed -n "$song_index"p)"   # Get the name of the qualified song
        echo -e "${BYel}$song"
        afplay "$m_path$song"
        wait
    done
}

最后,我们可以使用kill的信号量拓展一下这个播放器的功能,比如切歌/终止/暂停/继续:

function n() {  # next song
    pid="$(ps -ef | grep afplay | grep -v grep | head -1 | awk '{print $2}')"
    kill -INT $pid
}

function mm() { # terminate afplay
    pid="$(ps -ef | grep afplay | grep -v grep | head -1 | awk '{print $2}')"
    ppid="$(ps -ef | grep afplay | grep -v grep | head -1 | awk '{print $3}')"
    kill -INT $ppid && kill -INT $pid
}

function ms() { # music stop
    ppid="$(ps -ef | grep afplay | grep -v grep | head -1 | awk '{print $3}')"
    pid="$(ps -ef | grep afplay | grep -v grep | head -1 | awk '{print $2}')"
    kill -TSTP $pid && kill -TSTP $ppid
}

function mc() { # music continue
    ppid="$(ps -ef | grep afplay | grep -v grep | head -1 | awk '{print $3}')"
    pid="$(ps -ef | grep afplay | grep -v grep | head -1 | awk '{print $2}')"
    kill -CONT $pid && kill -CONT $ppid
}

2. 演示

play_music_in_terminal

(The End)

关于[乔布斯, 费曼]言论的一些摘录

作者 Jiahao Cai
2018年3月31日 00:00

关于[乔布斯, 费曼]言论的一些摘录

Mar 31, 2018

0. Overview

仅以此记录这两个我所欣赏的人的部分言论。

1. Steve Jobs

说起来我也用了很多年的Apple产品了(限于iPhone,MacBook Pro),但对乔布斯的了解甚少。当我还是大一的时候,我表哥送给我一本乔布斯传,我很庆幸直到今天我连那本书的外面的塑料薄膜都没撕开。因为在随后的几年中,我意识到一手资料的重要性,要学习知识,最好要直接寻找知识的源头,而不是去学习经过他人转述的二手知识。了解这个知识是为了解决什么问题而存在,是在何种状态下而被创造其实是很重要的,其重要性不亚于知识本身,这也是我为什么觉得中学就该好好教历史,当然我说的是数学史、物理学史、化学史等,而不是什么党史 :) 想要了解一个人也是一样的,看传记中的评价永远比不上看这个人本身的言论,每个人应当有自己的思考,给出自己的评价。
鉴于乔布斯生平的很多fact都能在互联网上能找到,我决定着重记录视频中出现的opinion,而非fact。

steve_jobs

1.1 The Lost Interview

这篇采访之所以叫The Lost Interview,是因为先前大家都认为这篇采访的母带在从伦敦运往美国的途中丢失了。不过后来,这个系列的Director,Paul,在自己家的车库里发现了一份VHS Copy,于是我们终于能看到这篇采访,感谢Paul。

背景: 这篇采访发生在1995,十年前(1985),乔布斯被Apple开除,此时的他正在管理NeXT,一家在他离开Apple之后建立的公司。有趣的是,在采访结束后的一年(1996),Apple收购了NeXT,乔布斯也最终回到了Apple。

主持人问乔布斯是怎么学会管理一家公司的:

在管理公司的这些年里,我发现了一些事,我总是会问别人为什么要做这些事,得到的回答却总是,“(想完成这个目标)就是需要做这些事的”,没人知道他们自己为什么要这样做,没人去深层次地对这些事进行思考。举个例子,在车库开发Apple I的时候,我们很清楚的知道这个产品的支出;当我们在工厂里开发Apple II的时候,会计部门想要计算标准支出,就是你设置一个标准支出,然后在季度结束的时候,你就可以根据实际支出去计算差值,我一直在问为什么要这么做,答案却是“事情就是这么做的”。大概六个月之后,我终于搞清楚了这是个什么东西,人们使用这个东西只是因为他们不能很好地预估自己的支出,所以他们给出一个支出的预估值,并在季度结束时修正自己的预估值,而这只是因为他们的信息系统不够好罢了,(人们应该摆脱这些东西)可是没人这么做过。当我们在开发Macintosh的时候,我们摒弃了这些陈旧的概念,因为我们很清楚地知道我们的支出。在商业中,我把很多东西叫做过时的传统(folklore),他们现在这么做只是因为他们从前是这么做的。所以只要你愿意去发现问题,并深入思考,你可以很快地学习商业管理,这并不是什么难事(rocket science)。

主持人问到编程相关的问题:

…我们那时写的程序跟实用这个词搭不上边,它们更像是一面反映了你的思考过程的镜子,我觉得编程最重要的价值就是学习如何思考。我认为这个国家的每个人都应该学习编程,学习一门编程语言,因为编程会教你如何思考,就像去一所法律学校就读一样,我并不认为每个人都应该成为一名律师,但是我认为学习法律是非常有用的,因为它教会你用一种特定的方式思考。同样的,编程也会教会你一种略微不同的思考方式,所以我把计算机科学看作是文科(liberal art),它应该是每个人都要学习的东西。

主持人问到为什么乔布斯说“The technology crashed and burned in Xeror”:

…John Sculley来自百事公司(PepsiCo),他也许每十年才推出一个新产品,而且“新产品”也许只是换了个大小不同的瓶子而已。如果你是个做产品的人(product person),你能做的非常有限,因为你不能经常改变公司的路线,所以谁决定了公司的成败呢?是市场销售人员。所以他们才是会升职加薪的人,他们才是会管理公司的人。对百事公司来说,这也许是ok的,但同样的事也会在科技公司发生,特别是那些获得了市场垄断的公司,如IBM和施乐(Xeror)。如果你是个在IBM或施乐做产品的人,你可以做出更好的产品,so what?当公司垄断了市场的时候,它就已经不再成功了。能让这个公司(看起来)更加成功的只能是市场销售人员,最后他们会掌管这个公司,做产品的人会被排除出决策层,这家公司也就遗忘了如何才能做出伟大的产品。那些一开始将公司带向成功的,对于产品的鉴赏力以及才能,最终被一群对产品好坏一无所知的人给毁坏了。他们不懂一个好的idea最终变成一个好的product需要什么样的过程,他们的心中也没有真正想要去帮助顾客解决问题的欲望。

当提到乔布斯想要将开发重点放在GUI上的时候,却遭到了一大堆人的反对:

…当公司渐渐成长的时候,大多数公司会想要复制他们一开始的成功方式,他们觉得成功的关键就藏在他们成功的过程中(也许他们并不知道究竟是过程中的哪个点促成了他们的成功),所以他们开始尝试在整个公司中将这一成功的过程给制度化(institutionalize)。用不了多久,大家就会开始将这个过程(process)误认为是产品的内涵(content),这就是IBM衰落的原因。IBM拥有世界上最好的管理过程的人才(process people),但是他们忘记了产品的内涵。这件事也在Apple内部有些许端倪,我们拥有很多很棒的管理型人才,可他们就是对产品的内涵一无所知。在我的生涯里,我发现最好的人才是真正理解产品内涵的人,他们非常非常难以管理,但是你要忍受这些,因为他们是如此理解产品的内涵。这就是做成伟大产品的关键,不是管理过程,而是产品的内涵。

主持人问到“在开发产品的过程中什么最重要”:

…这种病就是以为有一个好的想法就已经完成了90%的工作,如果你告诉其他人“我有一个好的想法”,他们就一定能完成这个想法。这种病的问题在于,在伟大的想法与伟大的产品之间,有着一条鸿沟。最初的想法会在实现的过程中不断改变并进步,最后被实现了的想法绝对不会和最初的想法是一模一样的,因为在这个过程中你学到了更多的东西,你明白了其中的微妙之处,你意识到了你必须做无数的妥协去实现你的想法。就比如你必须向材料的物理性质妥协,比如电子的性质,塑料的性质或者玻璃的性质。设计产品就好像放了5000个概念在你的大脑里,然后把它们用新式的不同的方式组合在一起,最终才能得到你想要的东西。每天你都能发现新的东西,新的问题或者新的机遇,从而能将这些概念更好地组合在一起,这个过程才是最重要的。

乔布斯谈到招募人才的重要性:

当你费尽心思找到了足够多的人才(A player)时,你会发现他们真的能够很好的互相合作,因为他们之前从来没有得到过这样的机会(与这么多优秀的人一起共事)。他们也不会想要和水平不够的人共事(B and C player),于是就会形成一种自我监督,他们只会招募更多的人才。招募一些好的人才,然后这种情况就会在公司中传播,最后公司里就会充满了好的人才,这就是Mac团队的样子。

主持人问到乔布斯你怎么知道哪个才是正确的方向:

You know, 这最终取决于品味(taste),取决于你要挖掘人类最好的那些东西,然后尝试着将这些好东西放到你正在做的产品里面。毕加索说过能工摹形,巧匠窃意(good artists copy, great artists steal),而我们总是在“无耻”地偷窃伟大的想法。我认为Macintosh之所以伟大的一部分原因在于,开发Macintosh的人是音乐家们、诗人们、艺术家们、动物学家们和历史学家们,他们又碰巧是世界上最好的计算机科学家,如果这些人不从事计算机科学,他们就会去做其他领域的美好的事情。我们努力想要将我们在其他领域看到的最好的东西放进这个领域里,如果你的眼光非常狭窄,我不认为你能做到这一步。

2. Richard Feynman

mr.feynman

2.1 The Fantastic Mr. Feynman

Feynman的父亲在一家生产制服的工厂工作,长期与军队的接触使他对于权威有着强烈的抗拒,而他也将这种价值观深深印在了Feynman的心里:

Disrespect是我的父亲教给我的众多东西之一。他翻到New York Times上的一张图片,图片上也许是个什么将军,他就会说“看看这些人类吧”,他说,“这个人就站在这里,而其他人都在向他鞠躬。他们有什么区别?为什么其他人都要向他鞠躬?只是因为他的名字与他的地位罢了,因为他身穿的制服,而并不是因为他做了什么了不得的事情。”

对方问Feynman,“你觉得你的工作值得一个诺奖吗?”,Feynman对此的回答:

“我不知道,我不知道关于诺贝尔奖的任何事,我不明白这一切都是怎么回事,什么值得不值得,如果瑞典学院(Swedish Academy)决定X,Y和Z获得了诺贝尔奖,那就获得了吧。瑞典学院的那些人认为这个成果足够优秀,优秀到可以拿一个奖,而我却根本看不到这有什么意义。我已经拿到了我的奖励了,那就是探索事物所获得的满足感。这个突破性的发现,以及它被广泛使用的事实,这些才是真实的东西。荣誉对我来说都是不真实的,我不相信所谓荣誉。它让我感到烦扰,荣誉对我来说是个麻烦。荣誉就是肩章,荣誉就是制服,我的父亲从小到大就是这么告诉我的。我根本无法忍受它,荣誉会伤害我。”

Feynman说到科学之美:

“我有个朋友是艺术家,他有一个我无法同意的观点。他拿着一朵花说,看这朵花多么美丽,我当然会同意这个观点。他说他作为一个艺术家,他可以看到这朵花是多么得美丽,可是你作为一个科学家,你只会把这朵花给拆解成不同的部分,于是它就成为了一朵无趣的花,我觉得他这个观点有点不正常。首先,他所看到的花朵的美丽对其他所有人都是可见的,我相信这当然也包括我,尽管我没有他那么训练有素,我也可以欣赏到这朵花的美丽之处。与此同时,我在这朵花上看到的东西比他看到的更多,我可以想象这朵花拥有灵魂,它会做很多复杂的动作,这些东西也有它们自己的美感。也就是说,(我能欣赏到的)不只是在这个厘米维度下的美,在更小的维度上,那里也有另一种美,它的内部结构,它的生长过程,它改变颜色来吸引昆虫来为它传粉的有趣事实,这说明昆虫也可以看到色彩。这就引出了另一个问题,那些比我们低等的生物,它们有没有美感呢?它们如何判断什么是美呢?这些所有由科学知识所引出的有趣问题,为花朵增添了一种神秘的令人兴奋的氛围,我不明白这些科学知识怎么会减少我所见到的美。”

mr.feynman

2.2 Feynman: Magnets FUN TO IMAGINE 4

这个视频从有人问了Feynman一个问题开始:

“如果你拿着两块磁铁并试着将它们推到一起,你可以感觉到它们之间的推力,如果将一块磁铁换到另一个方向的话,它们就会吸到一起。那么,这两块磁铁之间的感觉是什么样的呢?”

“什么叫这两块磁铁之间的感觉?”

“这两块磁铁之间存在一些东西不是吗?当我将两块磁铁推到一起时,我能感觉到它们之间有一些东西。”

“认真听我的问题,当你说‘这两块磁铁之间的感觉’时,你到底想表达什么呢?它们之间当然有一种感觉,可是你想知道关于它们的什么呢?”

“我想知道这两个金属块之间到底发生了什么。”

“它们互相排斥。”

“什么叫它们之间互相排斥?它们之间为什么要互相排斥?它们是怎么互相排斥的?我必须得说我觉得这是个非常非常合理的问题。”

“这当然是个合理的问题,而且还是个很棒的问题。我们来看你的问题,当你问为什么某些事会发生,一般人会如何回答为什么这件事会发生?比如说,Aunt Minnie进医院了,为什么?因为她在冰上滑倒了并且摔坏了她的髋骨,这个解释会让人们满意。但是这个解释无法让某些来自另一个星球的,对人类一无所知的人满意。
首先,你应该明白为什么当你摔坏髋骨的时候你要去医院,当你的髋骨摔坏了之后你怎么去医院,因为她的丈夫看到了她的髋骨摔坏了然后打电话给医院,医院派人过来,这些前提都是能够被人类所理解的。当你试着去解释一个为什么的问题时,你需要在某个体系之下去解释,在这个体系下我们必须允许一些条件为真,否则你就会无休止地询问为什么。为什么她的丈夫要打电话给医院,因为她的丈夫对她妻子很关心,但这也不是绝对的,有些丈夫就对他们的妻子不感兴趣,他们可能总是喝醉,总是很愤怒。
这样思考的话,你就会得到对于这个世界的非常有趣的理解,包括事物之间的复杂关联。如果你持续分析某件事,你就会从各个不同的角度越来越深入地去看待这件事。比方说你开始怀疑,为什么Aunt Minnie在冰上摔倒了,因为冰是很光滑的,大家都知道这一点,没问题。但是为什么冰是光滑的呢,这就有点使人好奇了,冰是非常非常光滑的,你就开始想为什么会这样呢。我告诉了你冰是非常光滑的,这就解释了Aunt Minnie为什么摔倒,你可以对你的这个回答感到满意,但是你也可以继续好奇,为什么冰是光滑的呢?这时候你就会牵扯到一些其他东西了,因为世界上并没有那么多东西可以跟冰一样光滑。你很难得到这种光滑的东西,就算得到了,也八成是一种湿乎乎的黏滑的东西,可是冰却是一种如此光滑的固体,这就很奇怪了。这是因为当你站在一块冰上的时候,在那个短暂的瞬间里,冰与人之间的接触面会被挤压并融化,所以这就相当于在那个瞬间,你在一个由水组成的表面上滑行。为什么这只发生在冰上却不发生在其他东西上?因为当水结成冰时,水是会膨胀的,而当人站在上面时,冰所受的挤压会逆转这个膨胀的过程,从而使之融化,但是其他东西在结冰时就只会产生裂缝,当你用力挤压的时候,它们依然是固体。那为什么只有水在结冰的时候会膨胀而其他东西不会?
我并没有在回答你的问题,但是我在试着告诉你一个为什么的问题是多么地难以回答,你必须要知道哪些东西是允许被你理解的,允许被你知道的,以及哪些不是的。你应该会注意到,在上面那个例子里,每当我问了一个为什么,我们就会发现更加深层次的,同时也更加有趣的东西。我们甚至可以再深入一点,为什么Aunt Minnie滑倒了就会摔倒呢,这就跟每个星球上的重力有关了,当然这还与其他的一些东西有关。不过我们先不管这些了,我们只要知道这个深入的过程是非常漫长的。
回到你的例子,当你问我,为什么两块磁铁会互相排斥,我的回答会有很多很多不同的层次,这取决于你是一个物理系的学生,或是一个对物理一无所知的人。如果是一个对物理一无所知的人问我,我就只能说,磁铁之间存在的磁力使它们互相排斥,你可以感受到它们的排斥。你可能会觉得很奇怪,因为你从来没有见过其他的力跟这所谓的磁力一样,当你将一块磁铁反向时,磁铁又变得互相吸引了。世界上存在着另一种相似的力叫做电力(electric force),它也面临这同样的问题,你会发现电力也是十分诡异的。可是你却能够接受以下事实:当你的手用力按压一把椅子的时候,你会感受到这把椅子正在用相同的力推你。但是我们根据观察发现,这是和磁力是类似的,电力的互斥(electrical repulsion)会阻止你的手穿过椅子,因为在微观层面存在着电力。事实证明,我一开始所希望解释的电力与磁力各自的互斥问题,最终是一个更深层次的东西,而这个更深层次的东西,又可以用来解释很多人们不知不觉就接受了的事实,比如大家都知道我们不能将我们的手穿过这把椅子,大家都觉得这是理所当然的。但是如果你问,为什么人不能把自己的手穿过椅子呢?这就会牵扯到上面提到过的互斥力了。这时候我们又会开始好奇,为什么磁力的作用距离比其他的力大这么多呢?这就跟一个事实有关,那就是在金属中,所有的电子都朝着同一个方向转动并形成直线,它们的力会被叠加直到大到你可以感受到这个力的存在…
我无法用一些你熟悉的东西去解释异性相吸的问题,如果我说两块磁铁之间的互相吸引就好像是它们之间被橡胶带给连在一起了,那我就是在欺骗你,因为它们根本不是由橡胶带相连的,我不该陷入这种麻烦,因为你马上就会接着问我橡胶带的本质是什么。其次,如果你足够好奇的话,你又会问我,为什么橡胶带会倾向于恢复原样,我最后又要用电力来解释它,而电力正是我想要利用橡胶带来解释的东西,那我就不只是在欺骗你了,我还欺骗得非常蹩脚。所以,我并不能告诉你为什么两块磁铁会互相吸引,我只能告诉你,它们就是互相吸引,这是各种不同的力的一个特性,包括电力,磁力,重力等,它们就这个世界的一部分。如果你是个物理系的学生,我就会告诉你电力和磁力存在着某种非常紧密的关联,而重力和电力之间的关系目前还不清楚,等等。我真的不能够做到用某种你更熟悉的事物来解释磁力,因为我不理解如何用你更熟悉的事物来解释它。

A thinking reed

作者 Jiahao Cai
2018年3月21日 00:00

A thinking reed

Mar 21, 2018

Man is but a reed, the weakest of nature, but he is a thinking reed.

人只不过是一根苇草,是自然界最脆弱的东西;但他是一根能思想的苇草。用不着整个宇宙都拿起武器来才能毁灭他;一口气、一滴水就足以致他死命了。然而,纵使宇宙毁灭了他,人却仍然要比致他于死命的东西高贵得多;因为他知道自己要死亡,以及宇宙对他所具有的优势,而宇宙对此却是一无所知。

小季有一次对我说,不知道为什么,感觉自己来美国之后越来越社恐,觉得有其他人在场的时候就不那么自在,然后说不知道怎么搞的,我反而变得越来越外向,我们两个渐渐地反了过来。

我说,其实我很久以前就发现了这个问题。有一次我想要用中文发邮件,但是我却感觉非常非常别扭。就算只是一个简单的问候,我觉得“你好”两个字非常地古怪,一点也不像我会说的话,但是让我写出"hello"却又很简单。我才发觉我似乎有两个人格,中文人格以及英语人格。前者代表了我在中国生活的前二十多年,而后者代表了我离开中国之后所学到的一切。后者的一个美妙之处在于,他跟前者没有太大的依赖关系。因为直到今天,我还能感受到我离开中国之前,对外界的那种渴望与向往,我尽量谦卑地去学习,去适应新的环境,重新学习我的日常生活,重新学习跟不同背景的人交流的方式,等等。我将旧的自我几乎抛在脑后,在外的孤独和独立又更加强化了新的自我,渐渐地,这个新的自我开始成熟了,并且他已经可以开始影响旧的自我了。

小季想了一会说,她小时候确实显得很外向,因为经常被父母赶鸭子上架去参加比赛,组织活动,尽管她从来都不愿意,而且很讨厌,但是她还是选择在父母面前做个听话的小孩。而我小时候则因为各种各样的原因觉得很孤独,明明经常有很多话想说,却又很少和别人开口说。

我觉得能拥有一个第二人格很幸运,人几乎不可能在小时候选择想要长大的国家,环境,以及家庭,就算拥有选择的权利,却也没有选择的智慧。如果在成年且有一定思考能力之后,还能够让自己在某种意义上重新开始,真的是一种bliss了。

-- 23:49, May 7, 2022
人的维度   

看电影的时候最讨厌的是国产的主旋律电影,因为里面的角色过于单薄,好人就是好人,坏人就应得一个悲惨的结局,可以预测到的剧情,这一切都距离真实的生活太远,这种幼稚的刻画让人觉得要么写剧本的人太蠢,要么就是有意为之。人是很复杂的,在每一个时间点上都是很复杂的,如果有人能写好这种复杂,即使只写某个时间点上的复杂,那也是佳作了;而人又是在每一个时间点上都是不一样的,从时间轴上俯瞰,那又是另一种复杂,我不知道你能否想象那画面。    

在不同的时间点里,我们其实可以是完全不同的人,了解到这一点其实很重要。我看别人的时候,经常把别人按时间切割开来看,这样一来,当他们变成完全不一样的人之后,我也不会难过,事实上我根本不care,他/她做了他/她的选择,而我则会永远欣赏并回忆那个时间点上的那个人。

可能你无法想象我读本科时的孤独,我曾经住在一个寝室两年,在里面说过的话不超过十句,在寝室之外也好不到哪里去。有次小季问我说,你大学最好的朋友是谁,我想来想去在乔布斯,李志和其他一些人之中纠结,她笑我有病。但是这确实是事实,在那段时间里,我经常反复想起他们,并且获得很多力量。

不过最近我有想修正一下我的说法,我的好朋友们是在斯坦福演讲的乔布斯,是穷困潦倒时的李志,是十几岁的大张伟,是十几二十岁的周杰伦,至于这些时间维度之外的他们,我并不了解,所以也说不好那些他们究竟是不是我的好朋友。你可能觉得这很冷漠,不过我觉得还好,因为我对我自己也一样。对别人而言,如果某个时间点的我能帮上什么忙,那我已经很开心了,没必要一直喜欢我,更没必要让所有人喜欢我;对我自己而言,把不同时间段的自己切割开来是好事,我很清楚地知道小学初中高中的我,都会鄙视现在天天炒股只想搞钱的我,但是这并不重要,因为我选择在不同的时间点,做不一样的人,这也许是在这短暂人生里活得精彩的唯一办法。

-- 20:12, Mar 2, 2021
我有一个很喜欢的乐队叫做腰乐队    
腰乐队有一张我很喜欢的专辑叫做“他们说忘了摇滚有问题”    
“他们说忘了摇滚有问题”里有一首我很喜欢的歌叫做“垃圾好比你的脸”    
“垃圾好比你的脸”里有一句我很喜欢的歌词    

那句歌词是“小市民的狂想/死的时候/身上贴满/彩票”

每当听到这句歌词,我总是想起很多年前,我还在上中学的时候,我经常在晚饭后跟爸妈一起散步。走着走着,很多次我妈会说,”买张彩票吧,又没事,就当是玩玩”。

我很庆幸那时候的我们还是乐观的,在能力足够之前知道得太多是不幸的,把太多事告诉一个能力尚且不够的人,应该也是一种罪吧。

-- 15:35, Sep 7, 2020
我是一个很喜欢回忆的人,回忆使我感恩我所拥有的一切,使我感恩我曾经遇到的人,回忆也使我将从前发生过的那些事连接起来,帮助我更好地理解当下。我最近很喜欢的一种表达方式是,“如果当初没有XXX,就没有今天的我”,这句话也许不是绝对的,但我相信它在我的人生中99%的时间里都是对的。但是我又很怕有人误解我的这种说法,我害怕有人觉得,“既然没有XXX就没有你的今天,那XXX一定对你来说很重要咯?那你一定很感谢XXX咯?”,所以我觉得我有必要解释一下这个说法。      
举个例子,在我小学的时候,有个退休的老师想让他的孙女去考一个新建起来才三年的初中,但是又怕她孤单,于是就想多叫几个成绩好的人一起去,我也是其中之一。在那个时候我根本没听说过这个学校,但是我还是抱着玩一玩看一看的心态去了,结果就是她没考上,我考上了,而且去了。去了那个学校被证明是我生命中最重要的决定之一,我在那里遇到了很好的人,受到了很好的教育,使我受益至今。现在想来,如果当初没有那个老师,我甚至都不知道有这个学校,在这个学校学习也就无从谈起了,这样看来,确实是“如果当初没有XXX,就没有今天的我”。    
但是是否当初有了XXX,就毫不费力地有了今天的我呢?并不是。当初那个老师给我提供的,是一个分支,就像程序里的一个if语句,而那个沿着那条分支努力走下去的人,是我自己。那个if语句虽然很重要,可是它终究也只是一个if语句罢了,最终还是得我自己来evaluate那个if语句,最终还是得我自己来书写if语句之后的东西。没有了那个if语句,我肯定不是现在的我,但是如果沿着另一条路,付出相同的努力,我的人生会更好还是更烂,其实我也说不清。所以说,“如果当初没有XXX,就没有今天的我”这句话,只是在陈述一个事实,我感谢当初给我提供那个人生选项的人,不过也仅此而已了。     

思考题:“没有共产党,就没有新中国”是什么意思?
-- 17:28, May 26, 2020
勇气很重要,这些年来我看到很多人——其实大家都有着差不多的智商,接受着差不多的教育,但是大家的轨迹却不尽相同,我可能是其中过得还行的那一个。我觉得很重要的一点就是,在很多时候我比较勇敢,在发现机会之后愿意主动抓住这些机会。勇气有很多种,此时在我脑海里盘旋的一种勇气叫做直面过去的勇气。比如说,像很多人一样,我也搭过很多次博客,而且每次都因为各种各样的原因而停止了。其中一个重要的原因就是没有勇气直视自己从前写下的文字,觉得幼稚,觉得愚蠢,觉得暴露了这些从前的文字让自己感到羞耻。不过现在的我更倾向于留下这些文字作为记录,过去的幼稚不应该成为一种羞耻,我想记住那个幼稚的自己。更何况,世界上还有许许多多的人,他们正处于我从前的那个幼稚的水平,或者正朝着我眼中所谓的幼稚水平前进,我希望我能用这种方式帮助他们。
-- 17:59, February 21, 2020
老师让我总结一下我的Master申请,我想了一两分钟,然后说:

从实力来说,我觉得自己很强;从申请材料来看,我觉得自己很弱;从申请结果来说,我觉得自己很幸运;从努力程度来说,我觉得我真的挺一般的。

当时想着自己回复得太着急了,深思熟虑一下应该能想出更好的回复的,但是当冷静下来之后,却感觉这四条似乎也算是完整地概括了我的申请季了吧 ;)

-- 20:48, April 19, 2018
> A number of these phenomena have been bundled under the name "Software Engineering". As economics is known as "The Miserable Science", software engineering should be known as "The Doomed Discipline", doomed because it cannot even approach its goal since its goal is self-contradictory. Software engineering, of course, presents itself as another worthy cause, but that is eyewash: if you carefully read its literature and analyse what its devotees actually do, you will discover that software engineering has accepted as its charter "How to program if you cannot.". -- Dijkstra

我的本科实行计算机大类的制度,简而言之,大一上学期到大二上学期大家都没有明确的专业,都叫做计算机大类,等到大二上学期结束,大家就会开始选择自己的专业:计算机科学,软件工程或者网络工程。当年的我觉得自己真是好喜欢写代码啊,软件工程这个专业一听就是写代码的吧(然而我连培养方案都没认真看懂),于是我毫不犹豫地选择了软件工程专业。现在临近毕业之际,看到Dijkstra的言论真是哑然失笑,他说软件工程是穷途末路的领域,其本质可以总结为How to program if you cannot。他的言论解答了我大三时的困惑,当时我在上“软件工程经济学”,“软件项目管理”等课程的时候就会觉得,wtf我为什么要上这些东西。这种东西根本就没什么好教的,好的程序员之间就是会互相合作的,你动用各种方法去强迫他们是很可悲的。这个观点被我后来在网上看到的抱怨所验证,像敏捷开发scrum这种东西,在中国的业界成为了压榨程序员的一种手段。

个人来说,我觉得软件工程在现实中的语义可以概括为:如何高效地驱使一群平庸的程序员在一起合作并产生最大的效益值<del>,手段往往是残酷的</del> :)

-- 00:36, April 8, 2018
有些人到了自己人生的某个阶段(e.g. 20岁)就会开始觉醒,认识到自己与不同阶级同龄人的差距,并感叹自己之前的人生过得多么“不懂事”,其实很多时候,这种事都无法改变。简而言之,当你还小还没有能力没有经验站在一个高度为自己写下一个十年二十年计划的时候,你的父母是唯一能为你做这些事的人,如果他们没有做,那么你的人生就会过得非常随机。随机的意思是,你20岁之前的人生会轻易地被一些事所改变,你所遇见的朋友、老师、恋人,会在很大程度上左右你的人生,而年幼的你并不知道如何去遇见更好的人,或者甚至无法分辨什么才算是好。从系统的角度来看,这一点也不Robust :)

> There is only one heroism in the world: to see the world as it is, and to love it. -- Romain Rolland

-- 10:50, March 24, 2018
曾经我以为编程能力就是一切,写得一手好代码就可以在这世界上赢得很多的尊重,曾经是指在我大二大三的时候;后来我失望地觉得编程能力只是一个微不足道的东西,因为人们更倾向于用可标化的东西来衡量你,GPA、TOEFL、GRE、Paper...我觉得我都还没有得到展示我自己所谓编程能力的机会,就被拒绝了,后来指的是我在准备申请Master的那段时间;如今我认为我终于正视了这一切,编程既谈不上多么高贵,也说不上多么卑微,它只是一个普通的技能,和木工、电工、摄影、厨艺都没有什么区别。我对它有过的热情以及失望,都来源于我对它的迷恋,而这本质上和爱上一个小姑娘并没有什么区别 :)

-- 21:30, March 7, 2018
❌
❌