普通视图

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

2025 年初展望

2025年1月13日 18:57

2025 年已经开始一段时间了,我也不免俗写一下总结和展望。

工作

我 2023 年底离开了 TapTap,2024 年在 EMQ 管研发团队。这是一个朋友的创业公司。因为创始人是美籍台湾人,员工也大半在台湾,所以公司文化算是东西结合。每个公司从内部看都会有一堆问题,但是从我的偏好来说,EMQ 人比较少、关系比较简单、团队和同事之间的沟通方式比较体面,我相对更能适应一些。

职业生涯到现在,我对公司管理最深的体会是业务方向对,在做正确的事是最重要的,是解决一切问题的基础。业务上停滞,管理者又缺乏办法的时候就很容易开始卷成本和绩效。因为寻找好的业务方向很难也很不确定,而短期的降本增效很有确定性。辞退掉一些人一定能把成本降下去;绩效上的压力也很容易让大家看起来都很努力。但很少有公司失败是因为员工不够努力,也很少有公司成功是因为员工加班够多。确实有很多成功的公司员工也都很辛苦,但往往是因为他们看好公司的业务,相信自己的努力能换来足够高的回报,自然会更有动力。Google 的成功不是因为 OKR,而是在正确的时间做了正确的事情。

最近得知所有原 LeanCloud 的同事都已经离开心动/TapTap 了,而原来 LeanCloud 的开放资源网站也已下线。其实也是预料之中。在并购之后心动和 TapTap 经历了很大的变化,对从 LeanCloud 过去的同事来说文化和管理方式也就离预期越来越远了。LeanCloud 作为一个 VC-backed startup 没有很成功,主要原因是我的一些错误的判断以及能力上的短板,但 LeanCloud 的很多同事都是很优秀的。让人比较欣慰的是虽然前后在 LeanCloud 工作的同事只有几十人,但是他们后来创立了十几家公司,有的公司还是多位前同事创立的。

经过在新公司的这一年我基本熟悉了公司的业务,2025 年我希望自己通过在工作上多完成一些原来不熟悉的事来把收获最大化。我选择加入 EMQ 很大程度上是因为和创始人的朋友关系,希望能帮助他们渡过一个关键阶段。未来离开的时候应该大概率还是会做些自己的事,不太可能会加入别人的公司了。继承和管理一个即有的团队是件比较痛苦的事,因为我一直认为招聘是对管理最重要的事,只要招到有内在动力、对自己要求高的人,其他管理问题都容易解决。继承一个现有团队等于放弃了对最重要环节的把握,后面要在应对短期业务压力的同时去改变已经积累成型的文化和团队构成是个漫长和困难的过程。对于我这样不善画饼的人来说,尤其难以改变没有内在动力的人。

阅读

2024 年正好按照计划看完了 25 本书。其中我最喜欢的是:

  • The Blind Watchmaker(见我写的书评
  • The Song of the Cell
  • How to Hide an Empire
  • Number Go Up

2025 年我定的目标是 30 本书。虽然看书的速度在逐步加快,但还是赶不上 wish list 增长的速度,想看的书总是永远看不完的。

我儿子也很喜欢看书,但是以小说为主。回想起我小的时候,似乎有个趋势是小时候很喜欢看小说1,而现在看的却几乎都是非虚构类的作品。因为想看的其他书太多,就不太愿意在小说上花时间。虽然会有意识地尝试,但是很少有能持续看下去的。其实小说才是真正的文学作品,也许我今年会发现一些自己会喜欢的小说类型。

生活

疫情之后因为外部环境的变化和我自己的改变远行的旅游少了,今年除了回云南之外,去了香港、澳门、新加坡、泰国,都是周边距离近的地方。我计划今年夏天做一次两三周的远行。

学小提琴是我过去十年对生活有最多正面影响的选择之一。这是件纯自娱自乐,需要花大量时间而又没有任何财务收益的事,但它带我进入了一个新的世界,在阴郁的时刻带给我充实、快乐和平静。我在北京的时候有一位老师,不过后来疫情开始就不得不中断了,再后来我搬到了上海。虽然这几年也会抽时间练习,但是没有方向和诊断就进展很慢。因为成人不能像小孩一样有固定的时间上课,所以找合适的老师很看缘份。所幸最近找到一位可以灵活约时间的老师,希望今年在这方面能有明显的进步。

我过去用过各种 GTD 应用,从 Remember the Milk, 基于 Emacs org-mode 的任务管理系统到 Apple 的 Reminder,但是一直都效果不好,容易积累下很多未完成的 todo。2024 年我开始用纸质笔记本来管理待办事项,发现这才是最有效最可持续的方式。主要有两个原因:

  1. 用纸质笔记本让我确实会在合适的时间翻看查阅以前记下的东西,app 的定时提醒往往因为在做其他事而被关掉。
  2. 因为纸质笔记本的局限性,会需要把之前记下但没做的事抄录到后面新的页面。这样有些原本会拖延下去的琐事,在抄过几次之后要么就因为价值不大而被放弃,要么就被处理掉,总之会有个结果。纸质本的「低效」为拖延制造了门槛,反倒提高了大局的效率。

很多互联网应用,比如外卖等等,虽然直接的结果是提高了效率、提供了很多就业机会,但是从大局和长远看对社会的影响未必是正面的。这个话题也许后面值得单独写一篇探讨一下。

每到新的一年都会希望能比上一年多做一些事。即使按照人均寿命最长的国家的标准,我也已经走完了人生的一半。越感受到时间的珍贵,也就越想能做更多事。


  1. 比如我们这代人小的时候男生基本都看过金庸,女生基本都看过琼瑶的小说。 ↩︎

2024 年底曼谷之行

2025年1月3日 14:37

过去虽然去过很多地方,但是因为懒基本从没写过游记。每次出游别人写的内容给我带来了很多便利,也避免了很多麻烦,所以我准备此后尽量在每次旅行之后也写一写。除了能给人借鉴,也作为自己将来的回忆。关于行前准备我推荐一个网站 Wikitravel,上面会有很多国内内容平台上的游记攻略没有的内容,特别是中国游客不常去的小众之处,可以作为补充。

我小孩上的双语学校因为在圣诞和新年之间外教放假,学校的 project week 允许学生请假,所以我决定趁此机会带他旅行一下。因为时间有限不能去需要超过一周的地方,就选择了我也没去过的曼谷。不需要签证也比较方便。

因为是节日,看了一下喜欢的酒店价格都不划算,就选了 Somerset Rama 9 服务公寓。上次住 Somerset 还是在 Google 工作出差到北京时住他们的中关村公寓。后来看其实是个比住酒店更好的选择,因为在住宿超过两三天的情况下,公寓里有微波炉、洗衣机、大冰箱和餐具等就会比酒店要方便很多。空间大住好几天也会比较舒服。公寓旁边就是一个很大的夜市 Jodd Fairs Rama 9,可以吃海鲜夜宵、体验泰式按摩。

泰国规定入境必须每人带一万泰铢或等值外币(差不多 2000 人民币),如果抽查的时候拿不出可能会被遣返。过边检的时候那个姑娘把手机架在旁边边刷短视频边处理,按了指纹拍了照就过了。不知被抽查现金的比例有多高,不过最好还是按规定准备好。我没有提前兑换货币,到了之后在商场里的 ATM 取了 5000 泰铢,手续费 220 泰铢(按次,和金额无关)。后来才看到公寓对面就是中国大使馆和中国银行,在中国银行的 ATM 或许就不用交手续费了。后来尽可能用支付宝和信用卡,现金有点紧张但正好够用。大的商场一般可以用支付宝或者 Visa/Mastercard 信用卡,街头和夜市的商贩一般只收现金或本地的 PromptPay(一个扫二维码支付的应用).

我们前两天包车去曼谷周边。第一天去大城府(Ayutthaya),这里是泰国原来的首都,战乱中被缅甸军队占领,主要的庙宇和宫殿都被毁了,所以参观的主要是遗留下来的废墟和经过部分重建的遗址,包括邦芭茵夏宫(Bang Pa-In Royal Palace)、玛哈泰寺(Wat Mahathat)、崖差蒙空寺(Wat Yai Chai Mongkhon)、佛脚印寺(Prasat Nakhon Luang)。我对于泰国的历史和佛教都不太了解,所以就是作为外行走走看看。

缅甸军入侵的时候把玛哈泰寺的佛首都砍下,有一颗滚到了无花树的根部,多年后就形成了「树中佛」。

佛脚印寺的佛像

佛脚印寺的庭院

第二天去了美攻铁路市场(Mae Klong Railway Market)、丹能莎朵水上市场(Damnoen Saduak Floating Market)和暹罗古城(Ancient Siam)。铁路市场对我来说是一个 highlight,因为全球可能没有其他地方是这样的。游客去的水上市场很商业化了,据说现场买船票需要会砍价,否则容易被宰,所以我提前向包车的服务商买了票,102 人民币一条船,三个人。码头附近船很拥挤,其实就是让你坐船购物一圈。你的每笔交易掌舵的船家应该都有分成,半小时左右的船程结束的时候还会主动要小费。为了避免尴尬最好提前准备几十泰铢的零钱。

美攻铁路市场

丹能莎朵水上市场

暹罗古城是一个大公园,有很多泰国各地名胜古迹的复制建筑,因为都是新建的,没有 Ayotthaya 那样的历史感。而且网上看到的无人机拍摄后经过重度处理、饱和度拉满的照片和现实中看到的景象还是差别比较大的。不过因为带着孩子,租个电动车在里面开一圈也挺有意思。

网上的暹罗古城和现实中的暹罗古城

因为我到之前在携程预定了接机,服务商加了我的微信,所以这两天包车就直接向他购买了。后来看了一下,可能还是在携程下单比较划算,差不多的价格可以有中文司机服务。我们的司机是个年纪挺大的泰国人,只会很简单的英语,看起来应该至少 70 多了。开车很快,但是常走错路,Google Maps 导航也用的不是很熟,路上浪费了不少时间。到后来我得自己看着 Google Maps 以确保方向是对的。但是想到如果家境好谁都不会年纪那么大还出来工作,遇到脾气不好不理解的客人可能还要被责怪,我还是每天都额外给了他小费。

郑王庙

后面三天都在曼谷市区,行程就相对休闲。第三天见了一位我 Ph.D. 导师介绍的新朋友,她是一位前两年曾在 Yale 访问,目前在曼谷本地一所大学任教的 Fulbright Scholar。她带我们去了大皇宫、卧佛寺(Wat Pho,其实意译应该是菩提寺)和 ICONSIAM(暹罗天地)。在皇家的宫殿和寺庙都是外行看热闹,没太多可写。比较有意思的是在卧佛寺看到了很多中国的形似关公等人物的石像,是以前到中国的商船为了压舱带回来的。

来自中国的石像

大皇宫门票除了包含和皇宫在一起的另外两个景点,还包括观看泰国传统的面具舞(只有周末有)。因为剧院在另一个地方,所以在皇宫出口对面有一个集合点可以等大巴接送,按照牌子上的说法是 15 分钟一辆车。我们和一些其他游客一起等了很长时间也没见车,就决定直接走过去。在下午的炙热中走了 20 多分钟后,看到剧院门口贴着一张纸说这几天关门。沟通非常不靠谱,应该在大部分人等车的地方贴个通知。

皇家剧院门口的公告

曼谷中心有个独特的公园 Lumphini Park, 里面有很多野生的蜥蜴(water monitor),是个值得走走的地方。

Lumphini Park 的蜥蜴

这里的唐人街是我去过的最热闹的唐人街,主路两边挤满了游客和小吃摊。但可能因为口味不匹配,我个人觉得吃的一般。

喧闹的唐人街

这次也去了几个大商场。公寓旁边有 Central Rama 9,里面有很多餐厅,底层有超市,每天晚上回来后买点水果、牛奶之类很方便。ICONSIAM 可能是曼谷最高端的商场,以奢侈品牌为主,去那里最主要是吃饭,餐厅选择很多,也有个很大的小吃市场。可能因为靠近新年,广场上每天都在开演唱会,人特别拥挤,几乎是找门逃出来的。还有一个是 centralwOrld,除了大牌之外有一些泰国本土品牌,对面有个很大的 Big C 超市,可以买本地的零食和特产。

ICONSIAM 的小吃和餐厅

centralwOrld 商场外的佛像

我们也尝试了曼谷的所有公共交通系统,包括地铁(MRT)、轻轨(BTS)、公交和轮渡。和中国大部分城市不同的是,它们是完全独立的,有不同的购票系统,不能一卡通用。对中国人最友好的是轻轨,自动售票机支持微信和支付宝。其次是地铁,闸机可以刷支持 payWave 的 Visa 卡或者支持 PayPass 的 Mastercard 卡(卡上有像声波的标志,国内招行单标外币卡、香港 HSBC 的卡应该都行),Apple Pay 估计也行,但是没试。所以建议有几个人就带几张卡去当地铁卡,省去在售票机用现金购买的麻烦。轮渡有在湄南河两岸摆渡的(比如从大皇宫附近到郑王庙),也有沿着河在两岸码头停靠的(比如 Blue Flag 线可以从郑王庙、卧佛寺到唐人街,再到 ICONSIAM)。只能用现金买票,同一条线不管在哪里上下价格都一样。

湄南河夜景

公交最硬核,是人工售票,而且售票员一般不懂英语,乘客里也大部分是本地人,交流很困难,建议不要尝试。我们最后一天晚上在 centralwOrld 附近因为人太多太堵打不到车,就按照 Google Maps 公交导航坐上了一辆公交车。经过一番困难的沟通买了票后,发现走的不是 Google Maps 上的路线,就决定继续坐到大方向偏离的时候再下来打车。所幸虽然路线不一样,最终还是到了我们住处附近。

Google 地图上的公交车路线和实际走的路线

除了公交外,用 Grab 打车也很方便,绑定了信用卡或者支付宝后就可以像国内打车软件一样使用了。据说曼谷出租车宰游客的现象还是比较普遍,所以不善于砍价或对当地行情不熟悉的人还是用 Grab 比较好。打车价格比国内略低。

最后说下小费问题。泰国传统上是没有小费文化的,不过因为美国游客多了,可能一些地方的人还是会有所期待,所以我觉得作为游客还是可以适当给一些小费(有个例外是餐厅,很多正式餐厅的账单已经包含服务费,可以留意)。大部分泰国本地人的收入很低,所以同样的金额对你可能无所谓,却有可能让对方很开心。

荐书:The Blind Watchmaker

2024年11月19日 08:00

提起 Richard Dawkins, 大部分人想起的是他的第一本书《自私的基因》(the Selfish Gene)。他后来写的 the Blind Watchmaker 从另一个角度探讨了和进化论相关的一些问题,我最近看了后觉得同样有趣和重要。和自私的基因一样,这本书比较老,第一版出版于 1987 年,但是内容一点都没有过时。现在能买到的大部分是 2016 年出的 30 周年纪念版,作者在前言里说他几乎没找到需要修改的地方,看完之后我不得不同意。

这本书的书名来自于一个叫 the watchmaker argument 的支持造物主存在的论证。大致的内容是:如果你在路边捡到一块奇特的石头,尽管它的纹理或者形状很精妙,你也能相信它是通过一个自然的过程偶然形成的。毕竟天下有那么多石头,总有一些会具备看来奇特的性状。但是如果你在路边捡到一块表,你无论如何都不会相信这样精密的物件是一个自然过程的偶然结果,而不是来自于一个制表匠的刻意设计。自然界和生命的复杂度远远高于一块表,所以更不可能是偶然形成的,有意识有目的的造物主一定存在。这个论证最早出现在英国人 William Paley 的书里,但在他之前的牛顿、笛卡尔等人也都认为宇宙的运转和钟表类似,上帝就是钟表匠,科学家发现的只是上帝设计的规则。看这本书让我想起正好 20 年前的冬季我和导师到 Rutgers 开会,回 New Haven 的路上下着大雪,所以他只能慢慢开车。路上的几个小时里我们在聊为什么宗教吸引了那么多人,大概就是因为人类很难理解几百万年到几亿年这个区间里自然的演化过程能产生的结果,于是必须求助于造物主来解释生命的存在。

我初到美国的时候,最让我惊讶的事之一是在科技发展最前沿的国家竟然有一些州在争论是否应该在中小学教进化论,或者是否应该同时教神创论。直到现在其实也没有多少改变,现在美国大选最主要的议题之一是女性的堕胎权,而且反对者的依据来源是宗教而不是科学和伦理。我一直认为宗教对教育和社会的影响是美国的 bear case 里排前面的。在中国宗教离大部分人的生活很远,对教育更是没有影响力,总的来说是正向的事。但是因为少有争议和质疑,在大众文化中也就缺乏有意义的讨论。大部分人把进化论作为事实简单接受,从没考虑过其中的细节,比如像眼睛这样精密而脆弱的器官,是如何通过进化过程形成的。另外物种的边界在于同一物种个体间可以通过交配产生后代,不同物种的个体间无法产生健康的后代,那么如果不同物种是从共同祖先进化而来,那新的物种刚分化出来时岂不是无法繁衍?

这本书耐心并有说服力地回答了上述这些细节问题,一一拆解了进化论反对者的各种质疑。可以说在看过 the Selfish Genethe Blind Watchmaker 之后我才算真正理解了进化和自然选择,对自己在这方面的知识有一种踏实和完整的感觉。

(旧文)也说王垠退学

2005年10月6日 08:00

好几天前就在网上看到关于王垠从清华退学的新闻。本来我对这类事情是不会去关心的,认为又是媒体的炒作把事情闹大。昨天因为在一个朋友的blog上看到也在讨论这件事,所以忍不住去看了王垠的退学信1。我的第一反应是「原来是他!」。

在多年以前王垠还没有那么出名的时候,我就已经在他的主页上看过他写的关于 GNU/Linux, TeX/LaTeX, 和 Mutt 的文章,当时就觉得他这样一个对计算机科学充满激情的人将来必定有所作为。虽然我接触计算机以及对计算机科学产生浓厚的兴趣比他要早很多,常庆幸自己很早就明白喜欢从事什么行业,不过和他对这些东西的熟悉程度,对不明白的问题的钻研精神,以及那一股狂热劲相比,我真是觉得惭愧。

王垠发表在主页和 blog 上给清华的退学信中说到了很多中国的教育以及中国的大学存在的问题。可以说其中涉及到体制的部分大多数是客观和真实的,是很多学生的亲身体会,甚至可以说要是他到读博士才意识到这些问题,已经有些晚了。一些清华的学生和校友针对王垠的退学信设立了一个 blog -清华梦依然在,集中了一些反面的看法。双方各执一词,其实争论主要是集中在一些细节问题,还有王垠信中对清华和他们实验室两位导师的评价,对中国教育大环境存在的问题,其实很少有人真正持不同意见,只是大家有不同的解释。我不在清华,不了解具体情况,所以对涉及到清华和他们实验室的具体情况,自然没资格评论了。

王垠的信让我回想起自己本科的时候。我在武汉大学读的本科,武大在学术上不如清华,类似王垠说的问题表现得可能还更明显一些。那时候的我年轻气盛,嫉恶如仇,想法和态度大约和现在的王垠差不多:不喜欢很多学校里的教授,看不惯学校的很多事情。我从大二结束以后就很少去上课,因为总是觉得有些不屑,认为学不到什么东西。每个学期系里开什么课,我就去书店买对应的影印版英文教材看,到期末最后一节课去听老师划划重点,然后就去考试。往往到了一个学期结束还问同寝室的同学教某门课的老师是男是女。我虽然不像王垠,但也算是个敢作敢为的人,大四时在 BBS 上发过一些帖子,其中有说到武大的计算机科学系教的不是计算机科学,而就是一个电脑培训班。我们院的党委副书记同时也是 BBS 上院版的副版主,他常常把我封了,有时还打电话到寝室教训我。这种事情发生得多了,后来就成朋友了。将要毕业的时候,校学生会从应届毕业生里面找了包括我在内的三个人去给低年级同学做学习经验交流。当时因为院校合并等一系列事情,学生对学校的怨气挺大的,自由问答的时候有个学生问我学校的种种不好对我有没有什么影响。当时在教五楼的礼堂,我对着下面的几百学生说:“武大要强起来就得靠学生自己。每个学生为着自己的理想去奋斗,每个人都做好自己的事,武大自然就强了。靠现在行政大楼里面那帮人去决策,武大不可能强起来的”,不知道有没有把邀我去的那个学生会学习部的 mm 吓到。那时候我还没有拿到毕业证。

大三时一个在日本工作了多年回到武大的教授对本科生开了一门面向对象软件工程的课,主要讲UML的基础和应用一类的。因为我一向对软件开发很有兴趣,他讲的那些东西当时在国内算是比较新的。那时他的实验室刚重新启动,正在用人之际,他也比较鼓励本科生的参与,而我的专业和英语都还不错,所以从大三的某个时候开始一直到大四结束,我都一直在武汉大学的软件工程国家重点实验室做些事。那里研究生太多,导师也顾不过来,更不用说我这个本科生了。实验室老板对我很好,很照顾,只是他实在太忙,连他自己的研究生都指导不过来。那时我在那里做了很多翻译之类基本没有技术含量的活。科研方面就和王垠说的一样,就是读很多国外的 paper,然后考虑是不是能对一些小的方面进行改进,不可能会有什么突破性的结果。就这样一边做这些事情,一边应付每个学期的考试,一边考 TOEFL,GRE,GRE CS Sub,申请,办出国手续,一直到毕业。

凡事都有两面。王垠做出退学的决定,我想他只看到了一面。我本科时所处的环境,促使我养成了独立学习,独立思考的习惯和能力。我在软件工程实验室虽然常常在做些无谓的事情,不过大量的阅读使我对软件工程发展的概况有了全面的认识。王垠对他实验室的教授有颇多抱怨。本科时有一个数学系的教授在瞒着我的情况下把我交的作为一门课的 term project 的程序带到高交会做演示,后来还是一个去了的人说起我才知道。到后来买方提出一些要求,他因为搞的是数学,不熟悉编程,无法解决,才又来找到我。当时曾因此很不愉快,并非我想从那个程序得到什么商业利益,而是因为我最不喜欢被人欺瞒。可是要是不是因为他开了那门课,我也不太会对那个领域进行深入的学习,也就不会认识我现在的导师。所以要是没有他,我现在会是另外一个情况,或许差一些,或许好一些。人生际遇,从独立的一件事是难以判断祸福的。最重要的是以平和的心态看待问题,不管处于什么样的环境,都要 make the best out of it。

环境的不尽如人意并不是一件坏事,以其说“天将降大任于是人”之类的空话,不如看看真实的例子。很多人说中国的教育体制不能培养出世界一流的科学家,真是这样吗?现在在清华大学高等研究所的王小云在对 MD5 和 SHA-1 等一系列 hash function 的分析方面得到了突破性的进展,现在美国搞 computer science 的人基本都听说过她的名字,密码学界就更不用说了。可以说她和她学生的研究代表着理论密码学的 state of the art。 Wikipedia上说:

At the rump session of CRYPTO 2004, she and co-authors demonstrated collisions in MD5, SHA-0 and other related hash functions. (A collision occurs when two distinct messages result in the same hash function output). They received a standing ovation for their work.

在一个学术会议上,所有人起立为一个 talk 鼓掌,这是很少见的。她的简历上说:

Education:

  • B.S., Mathematics Department , Shandong University, 1987.
  • M.S., Mathematics Department , Shandong University, 1990.
  • Ph. D, Mathematics Department , Shandong University, 1993.

Employment Record:

  • Lecturer, Mathematics Department, Shandong University, 7/1993-6/1995.
  • Assistant Professor, Mathematics Department, Shandong University, 7/1995-6/2001.9.
  • Professor, School of Mathematics & System Sciences, Shandong University,7/2001.9-Present.

她更本没有在国外接收过教育或者做过研究。据我所知,她今年准备去美国参加 CRYPTO’05 的时候还被据签了,她的新成果是 Adi Shamir 代为宣读的。她出生就在山东,从大学一直到成为正教授都一直在山东大学,一所在中国都算不上一流的大学。她的主要成果也是在山东大学信息安全研究中心做出的。清华学生所拥有的环境是让中国很多别的学校的学生羡慕不已的,我想不会不如王小云成长起来的环境吧。客观条件可以起一定作用,可是要成为什么样的人还是取决于自己。王垠说:

我觉得自己一个学生力量太小,曾经试图找大师帮忙。我找到 Andy Yao,述说我的苦衷。结果他对我说:「别试图去改造环境!你没有这个能力。改造好你自己就不错了。」改造好我自己,可是怎么改?所以我决定先换一个环境,到一个真正搞研究的地方去体会,去学习。

其实姚期智说得没有什么不对。在大环境下多数人的力量都是薄弱的,难以去把环境变得让自己喜欢,何况每个人的看法和喜好是不一样的。人不能控制环境,但是可以控制自己,不管环境怎样,始终可以努力做自己想做的事。在觉得出了问题的时候,怪罪于环境之前,恐怕首先要找找主观原因。毕竟在同样或者更不好的环境下,还是有别的人可以做得很好。每个人都把自己份内的事情做好了,环境自然也就变好了。

王垠说他打算退学后出国找一个喜欢的学校做他心目中真正的科学研究。我想他会失望的,因为理想的学术殿堂不存在,失望的结果可能有两种,他或许会明白一些事情,从此有一个比较平和的心态;或许会做出更极端的选择。2他信中说的很多问题都是 universal 的,有人的地方都存在这些问题,只是因为中国发展变化得太快,所以各方面的矛盾也表现得更为明显和极端。美国也是一个功利的国家。在美国教授同样要靠发 paper 申请 funding 和拿 tenure,很多研究生同样要做很多无聊的表面工作。很多美国实验室的老板,特别是还没有拿到 tenure 的教授,不管学生愿不愿意都要逼着学生干他觉得需要完成的活,比起国内有过之而无不及。我认识的一些别的学校的中国学生,放暑假想要回国探亲老板都不让,就算让也只给很短的假期。况且在美国你的学费生活费都是靠导师从科研经费出的,和导师关系搞僵了就得卷铺盖走了。3王垠在信中说国外大学都有 common room,而国内没有。另外他觉得学生之间的讨论很重要,而实验室组织的讨论每个学生讲讲对自己看的论文的看法,他又觉得那样的讨论不好。有没有 common room 只是一个形式,形式决定不了内容。我就基本不去 common room ,因为我不喜欢人多的地方,我喜欢一个人静静地思考。有时在走道遇到一个人想说什么事情,就靠着墙一说就是一两个小时,也没有觉得需要一个 common room。在国外大学里的学术讨论其实和他所描述的他们实验室的讨论差不多。通常也就是一个人说一说对某篇论文的看法,经常也是很长时间没有什么结果,很多想法被提出来被否定掉。要找一个他心目中理想的科学研究的殿堂,恐怕穷其一生也找不到。科学研究中的大部分工作都是要静下心来独立完成的。讨论的真正目的是在有一定结果的时候告诉别人,让别人挑刺,所谓的 peer review。他说导师不鼓励学生之间的讨论就不知道别人在做什么,什么已经做了,什么没做,有些什么有趣的问题。以什么样的方式做研究是自己的事,导师鼓励不鼓励只是一个参考意见,毕竟已经不是小学生了。我的导师每个星期和我见一次面,听我说说自己的点子,给我一些指点,除此之外都是我自己安排。我觉得对计算机科学来说,到了博士阶段,正确的研究方式是独立查找资料、独立思考、独立完成工作,这个只是个人的看法,但有一点我可以肯定:要知道他所说的那些东西,最好的方法不是讨论,是 Google。

到现在为止我在美国生活了三年多,最大的收获有两方面,都和学术无关。首先是看到的事情多了,有了比较的基础,心态成熟和平和了很多,看问题不像以前那么片面。另外明白了什么东西才是对自己最重要的。学习,工作,科研并不是对我最重要的,财富也好,学术声誉也好也不是最值得追求的;亲情,友情,爱情,以及一般人之间的关爱才是最值得一个人珍视的。事业只是生活的一部分,生命中有很多珍贵的东西值得去追求,一个人只有首先做好一个普通人,一个普通的好人,他在专业上的expertise才能对社会发挥正面的作用。

我并不是要写那么长一篇文章批评王垠。这三天来正正反反的文章已经很多了,只是联系到自己,颇有感触。对王垠我首先是欣赏的,当年看他写的关于 Linux 和 TeX 的文章,受益很多,现在看到他的退学信,觉得他是个有激情、有勇气的理想主义者。中国需要很多能坚持自己理想,不被环境所同化的人。可是一个人光有理想和激情是不够的,在坚持原则的同时,还要有一颗宽厚、平和、和乐观的心,这样才不会因为对现实的失望而最终放弃自己的理想。我本来不喜欢李敖,不过他在清华的演讲其中一段让我很感动:

富兰克讲了一句话,非常动人,他说,哪里有自由,哪里就是我的祖国。告诉大家,富兰克林是错误的,这句话要被我李敖改写,怎么说,这里是我的国家,我要使它自由。

他说的虽然是自由,不过这段话应用到别的方面也是实用的。中国是问题不少,不过再怎么样总比几十年前好多了吧。如果当年那批知识分子都说:「哇,中国怎么这样?我闪」,那么也就不会有现在这个可以算是(或者说正在变得)繁荣富强的中国。(不要骂我在美国还说得那么虚伪,我是毕业后就要回国的。)古人有句话是「势利纷华,不近者为洁,近之而不染者为尤洁;智械机巧,不知者为高,知之而不用者为尤高。」所以逃避环境并不是最好的选择。最后祝愿王垠在以后的路上可以为自己的心找到一个位置。也希望在这件事平息下来以后,中国的学校,教育者和决策者们能对王垠提出来的问题有认真地思考,不要简单地归咎于一方。


  1. 水木清华的原帖已经找不到了,这是后来别人在知乎的转载。 ↩︎

  2. 近 20 年后再回头看,很遗憾发生的是后者。 ↩︎

  3. 我留学的年代大部分中国学生都是靠全额奖学金来支付学费和生活费。当时我父母没有支付任何我在美国的费用(也无法支付),后来我还汇了从奖学金里存下的一万美元回家。现在富裕的家庭很多,自费留学的人也很多,所以这一条就不是很适用了。 ↩︎

王垠传播的「自然视力恢复法」真的有用吗?

2024年6月26日 08:00

最近我偶然看到了王垠 Substack 上的《不要去医院验光》,当然因为没付费订阅我只能看到开头几段1。然后点上面的链接到了他博客上的「自然视力恢复法」。考虑到这两篇文章的危害,我觉得值得写一点东西给看我博客的人做一些参考。

王垠这个名字很多人都不陌生了,他从清华退学的时候我还在读博,当时还写了一篇文章来评论这件事,~不过现在已经找不到了~2。我那时看他写的宣布退学的文章留下的印象是他是个很聪明但视野有些片面、想法有些偏激的年轻人,没想到他后来变得越来越极端和愤世嫉俗,成了阴谋论的传播者。他的聪明和在计算机方面的一些天分让他成为了一个 influencer,颇有一些追随他意见的人。而当他推广的看法超出计算机领域,有可能实际影响别人的生活时,就危险了。

首先验光之所以要散瞳,就是为了测量在肌肉彻底放松的状态下眼睛的度数,这样才能让眼睛在戴眼镜后也能尽量放松,不存在他说的「度数会验得比实际需要的度数大」。而且医生也往往会透过放大的瞳孔检查其他眼科疾病。散瞳并不像他说的一样是「故意的欺骗行为」。他在文章里说:

而且散瞳用的阿托品会麻痹肌肉,这种药物应该也是有毒物质。偶尔一次可能还好,经常往眼睛里滴这种东西,不知道是什么后果。

如果不知道的话,恐怕不应该凭想象说某种药物「应该」也是有毒物质。

他在自然视力恢复法一文中提倡的基本观点是,近视是由于眼球外部肌肉长期紧张导致眼球变形造成的,近视的人应该戴度数浅一些的眼镜或者不戴眼镜,这样可以逐步让眼睛恢复正常。他说:

近视产生的根本原因,是长时间紧张地看近距离的物体,而跟光线,遗传什么的都没有直接的关系。

现代医学广泛接受的结论是:遗传是近视最主要的风险因素。

王垠在文章里引用的主要依据是 William Bates 写的 Prefect Sight Without Glasses。这本书是 1920 年由作者自出版的,里面说的近视治疗方法被称为 Bates method。而 1929 年 FTC 就发出对他的投诉,指控他进行虚假或误导性的宣传。在这一百年里相关的临床试验都表明他在书里说的那些练习对治疗近视是无效的。确实有一些近视的人在不戴眼镜一段时间后报告说视力比原来有所提高,但这个现象有很合理的与主流医学相洽的解释。不戴眼镜一段时间后,因为看到的东西是模糊的,大脑会在处理视觉信号时进行补偿来适应光学上的失焦。这就好像你可以用 Photoshop 来提高照片的锐度,或者用计算机视觉模型来让原本有些模糊的图像变得看起来更清晰。但这种提升是有限的,也不是对近视的治疗,更不是提倡不戴眼镜或者降低度数的合理原因。

Bates 的书篇幅很长,我以其中一章为例来说明它有多边缘、荒谬和危险。第 17 章是 Vision Under Adverse Conditions a Benefit to the Eye,可以翻译为「不利条件下的视力:对眼睛的益处」。在这一章里,他说包括阳光在内的强光对眼睛都是无害的,所有的不适感都是暂时的,直视太阳不但不会对眼睛造成永久伤害,还能帮助恢复视力。

Bates 书里的图 47:一个 37 岁的妇女和一个 4 岁的儿童直视太阳而没有不适。(本文注:请勿尝试!)

Bates 书里的图 47:一个 37 岁的妇女和一个 4 岁的儿童直视太阳而没有不适。(本文注:请勿尝试!

他甚至还说有一部分患者如果是把阳光直接聚焦在眼睛上效果会更好。

Bates 书里的图 48:用放大镜把阳光聚焦到一个病人的眼睛上。(本文注:请勿尝试!)

Bates 书里的图 48:用放大镜把阳光聚焦到一个病人的眼睛上。(本文注:请勿尝试!

Bates 所在的年代抗生素还没发明,可以说现代医学还没开始,他的书和观点在后来广受主流医学界否定。引用他写的内容来给别人提供医学建议实在是非常不负责任。

总结一下,术业有专攻,健康相关的问题还是要听医生的,如果对一个医生有疑问就多问几个医生,不要受互联网上非专业人士(包括我在内)写的东西影响。也不要相信某个行业在全世界范围内联合起来秘密牟利这样的阴谋论。


  1. 伪科学竟然还要钱 🤷 ↩︎

  2. 后来意外从 Wayback Machine 找到了。 ↩︎

从高考志愿到职业选择

2024年6月25日 08:00

读博士的时候我们系每年春季都会有一两天让在读的博士生留出特定时段待在自己的办公室,因为一些接到耶鲁的 offer 但还没有做决定的学生会到学校参观,他们可以到系里走访在读的学生,通过提问交谈更全面地了解学校和院系来帮助他们做决定。有一次走进来一个头发已经有点白的人,我还以为是本校其他系的教授,而他告诉我他拿到了我们系的 offer,所以来看一看。他多年前毕业于一所知名法学院,在纽约已经是一位成功的律师,但是他后来对计算机更感兴趣,所以决定不再做律师而从头开始学计算机。和我面谈时他已经完成计算机科学的本科学业,并被几个学校的博士项目录取。我问他是不是希望做计算机犯罪之类与法学相关的领域,他说不是,就是想完全转行。我对这件事印象深刻,一直记得。在我学生时代的认知里,高考时的选择基本上决定了一生的方向,而这个人在人生到了一半的时候不是只改变了方向,而是回到一个新的起点重新开始。

今年我一位侄女高考,所以问了我一些关于学校和专业选择的问题。我的基本建议是优先考虑报一所好的学校,专业是其次,尽量和兴趣接近就行。因为学校决定了未来四年的学习环境,这是很难再改变的因素。而大学里通常所有课都是可以选的,想在另一个专业再拿个学位也不是不行。这是我在走过这条路后回过头来按理性的思维给的建议,但是我自己高考的时候却是反其道而行,所有志愿填的都是计算机相关专业,并且不服从调配。当时我们还是先提交志愿后参加高考,不确定性更高。那时自己的概念里大学专业就决定了未来的从业领域,所以即使被录取到差一些的学校也不想在专业方面做妥协。所幸没有考得太离谱,还是被按第一志愿录取了。

我曾经觉得自己在这方面很幸运,在中国的同龄人里较早开始接触计算机,从小就知道未来想做什么,所以在别人纠结专业的时候我完全不需要选择,而自己的兴趣也正好是发展很快、机会很多的领域。但是随着年龄和经历的增长,我越来越觉得因为过早地在某方面产生强烈兴趣而排除了其他可能性未必是一件好事。Nike 创始人 Phil Knight 在他的自传 Shoe Dog 里说:

I feel sorry for the people that know exactly what they’re going to do from the time they are sophomores in high school. I think the process really needs to go through a time period before you really find what it is.

他所同情的大概就是我这类人。我在中学之后偏科是比较严重的,注意力都在计算机和英语上,到了最近这些年反倒因为对其他领域的好奇心而看了很多书来填补知识的欠缺。我比较后悔的一件事是在有时间有条件的学生时代没有更多地向广度扩展知识体系。

如果你关注 Charlie Munger 的文章或演讲,会知道他在很多场合强调过多学科思维的重要性。他认为具备多学科的基础知识和思维模式对于做正确的决策是至关重要的。只精于一门的人,一方面容易管中窥豹,拿着锤子就觉得什么都是钉子1,另一方面在某方面的资历越高,就越容易自信、越看不到自己的盲区、越可能在自己的称职范围之外做错误的决定。Liberal arts 教育对本科的定位是有所侧重的通识教育,所以很多美国大学都是先进入学校学习一段时间后再选专业。耶鲁的很多本科生到最后一年才选定专业,并且本专业的学分只需要占到毕业所需总学分的三分之一,所以拿双学位是很常见的事。在我们系即使是博士,前两年也要上各方面的课程,必须通过所有四个领域的综合考试2才能选择导师和具体的研究方向。我的导师在计算机科学的多个方向都有很高的成就,所以无论我选择在哪个方向深入下去,他都可以指导我。有一次我告诉他我对很多东西都挺感兴趣的,苦恼于如何决定博士论文的主题。他对我说「不用担心,大部分人的问题是专业化得太早,那并不是一件好事」。

很多父母都会努力让孩子「不要输在起跑线」上,就好像人生是一场赛跑。如果这么看的话,人生的轨迹就应该是线性的,每个阶段做的事情都应该是以上一个阶段为基础,不断积累,这样才能把到达终点时的某个指标最大化(可能是金钱、名望、或其他用来衡量人生的标尺)。以这样的人生观,本文开头的那个律师就不应该放弃已经有丰厚回报的事业到另一个领域从零开始。但是人生并不是一个比赛,往回看的时候充实感来自于在有限的人生里对多种可能性的探索,对大千世界的体验,以及对自己好奇心的满足。我很尊敬那些追随内心,不介意成为另类,在任何年龄都有勇气再次成为新手的人。


  1. 币圈和链圈大概有不少人属于这种类型。 ↩︎

  2. 分别为:计算理论、程序设计语言、人工智能和科学计算。 ↩︎

浅谈 Apple Intelligence

2024年6月13日 08:00

过去一两年有多位投资人朋友和我讨论过 AI 相关的创业机会。我的观点一直都是:大的机会基本上是巨头的,小公司没有特别好的机会。

当技术上的突破让小公司有机会颠覆大公司时,新技术最初的应用都在巨头看不上的新兴细分市场。随着这些市场快速扩大,小公司在成长起来后迅速地进入主流市场抢占原主导者的份额。随着原本不存在的个人电脑市场兴起的 Intel、Microsoft 等是最好的例子。近年来 AI 的发展在技术上有很大突破,但商业上的局面却没有给创业公司颠覆性的机会。一方面这是巨头们从一开始就重视的领域,投入很大;另一方面机器学习本身就需要大量的资源和数据,所以大公司或者他们投资的企业往往更有能力持续产出最好的成果。

另一个原因是新 AI 技术的应用往往是对现有场景和过程的改进或补充,没有创造出以前不存在的全新场景,所以对于已经掌握了用户关系的产品来说,后来者无法形成威胁。我喜欢举的例子是 Adobe 能在 Photoshop 中增加 AI 功能,Stable Diffusion 和 Midjourney 却不可能做出替代 Photoshop 的产品。在这种情况下,谁掌握了与消费者的直接关系,谁就掌握了市场。没有什么是比随身携带的手机和消费者关系更密切的,在可见的未来仍然如此。手机不仅仅是设备,还是包括应用和开发者在内的整个生态。Humane 和 Rabbit 之类的公司错误就在于试图做一个完全独立于手机但是又无法让用户不用手机的产品。Apple 的产品与消费者之间的密切关系决定了短期在技术上有没有走在最前面不是特别重要。OpenAI 做不出 iPhone 的替代品,更别说 iOS 的整个生态。但 Apple 可以把 OpenAI 的能力整合进自己的产品。如果 OpenAI 不愿意合作,还有 Claude 等众多选项,虽然不一定是最好,但差别也不大。

Apple 的设备会把一部分非个性化的请求发送给 ChatGPT。有一些媒体说与 Apple 的合作对 OpenAI 是利好,但是我认为 OpenAI 的收益是非常短期的。第一,Apple 与 OpenAI 的协议规定他们不能存储用户数据1;第二,Apple 在技术上也有措施避免 OpenAI 把同一用户的多次请求关联到一起。所以 OpenAI 得到的基本上仅限于财务收益,和用户之间建立不起有意义的关系。Apple 之所以在使用 ChatGPT 的时候明确告诉用户,一方面是为了透明,让用户知道信息发送到了哪里;另一方面恐怕也是要避免为 ChatGPT 引起的问题背锅。考虑到 Apple 走的从芯片到整机、到软件、到服务的垂直集成路线,以及 OpenAI 与 Microsoft 的密切关系,Apple 一定会在尽可能短的时间内用自己的方案替代 ChatGPT。可以说在战略上 OpenAI 是比较被动的,这是个他们无法拒绝的 offer。无论接不接受,他们自己的独立 C 端产品在 Apple 的平台上都不再有存在的意义,但自己赚这笔钱总比让竞争对手赚好。而且无论如何 Tim Cook 都会说「我们找了市面上最好的 partner 合作」,OpenAI 可不愿把这个背书给别人。

现在的 OpenAI 让我想起以前的 Nuance. Nuance 曾经是语音识别做得最好的公司。据说 Google Voice Search 最早是用 Nuance,但同时 Google 也用 Nuance 返回的结果训练了自己的语音识别系统,最后向第三方推出了语音识别服务,成了 Nuance 的竞争对手。过程是否真是这样我没能找到实证。很多知名汽车品牌的车机系统和 Siri 的早期版本都使用过 Nuance 做语音识别,但这家公司后来一直没发展到很大,最后被 Microsoft 收购了。即使是现在股价已经上天的 Nvidia,从长期来看在整个生态中的地位也并不是坚不可摧。芯片设计生产和相关的底层技术门槛当然很高,但是往后绝大部分面向消费者的 AI 应用的集成点会是 Apple、Google、Microsoft 在操作系统层面提供的 API,而不是 CUDA.

Apple Intelligence 是目前为止最让我兴奋的 AI 产品。之前的各种 chatbot 都仅限于给用户提供答案,实际根据这些答案执行动作还要靠用户,而 Apple 则有条件让 AI 代替人完整执行一些任务。当然这样让 AI 的决策直接造成现实中的结果是有风险的,如何尽量把人工操作在流程中减少,同时又让风险可控,这是个需要仔细平衡的问题。Apple 的另一个独特优势是可以访问用户的大量私有数据,从而能帮助用户完成高度个性化的任务。说到这个,Google 曾经有个叫 Desktop Search 的产品,是帮助用户检索 Windows 和 Mac 上的本地文件的,后来被关闭了,如果留到现在会很有价值。

Apple Intelligence 带来的可能是类似 2007 年 iPhone 一代发布所引起的从功能机到智能机那样的重要变化。Apple 的商业模式不依赖于所谓 user engagement2,在隐私保护方面也有比较好的 track record,在几大巨头里或许是最适合推动这个历史进程的公司,结果应该会更符合大众利益。


  1. 虽然以 OpenAI 的 ethical standard,我很怀疑这一条能执行得有多严格。 ↩︎

  2. 比如 Apple 还有 Screen Time 这样的功能来帮助用户避免过度使用他们的产品。这方面的讨论详见我写的关于搜索和广告的文章。 ↩︎

2024 年,我为什么开始为搜索付费

2024年6月3日 08:00

我 2007 年至 2010 年在 Google 工作的时候,中国的同事们都说百度无良,欺骗用户,把广告显示得和原生搜索結果几乎一样、难以区分。Google 的搜索广告在 2013 年之前一直是通过背景色与原生结果明确区分的。

带背景色的 Google 广告

带背景色的 Google 广告

后来背景变得和原生结果一样,改为用彩色的标签来标识广告。无论在用背景色的时代,还是用标签的时代,Google 都做过很多试验来通过调整颜色提高转化率。从 2020 年起,Google 的广告变成这样了:

现在的 Google 广告

现在的 Google 广告

不但视觉上和原生广告完全融合,而且首屏已经被广告占满。这个结果可以说是必然的,不做任何试验就能知道广告样式越接近于原生结果,点击率必然越高。之前做的种种实验无非是 Google 创立之初的价值观和与之矛盾的商业模式相纠结的漫长而耗资巨大的过程。

很多人都读过 Google 的两位创始人在创立公司之前写的 PageRank 论文,但很少人会看附录。这篇论文的附录 A 是 Advertising and Mixed Motives

… The goals of the advertising business model do not always correspond to providing quality search to users. … It is clear that a search engine which was taking money for showing cellular phone ads would have difficulty justifying the page that our system returned to its paying advertisers. … we expect that advertising funded search engines will be inherently biased towards the advertisers and away from the needs of the consumers.

Since it is very difficult even for experts to evaluate search engines, search engine bias is particularly insidious. … Furthermore, advertising income often provides an incentive to provide poor quality search results. … This of course erodes the advertising supported business model of the existing search engines. … But we believe the issue of advertising causes enough mixed incentives that it is crucial to have a competitive search engine that is transparent and in the academic realm.

不难看出在刚有 Google 的时候两位创始人认为基于广告的商业模式与用户的利益和高质量的搜索结果是有根本冲突的。只是当时 Google 还不是一家公司,所以他们可以义正辞严地批评其他搜索引擎并说明 Google 做为一个无广告的、学术界的搜索引擎的重要性。他们似乎认为靠广告盈利和作为学术性的、非盈利的服务是一个搜索引擎的唯二选择。

广告成为互联网的主要或者说默认的商业模式对用户和行业生态是个悲剧。这个话题值得单独写一篇文章来探讨,但简单地说:靠广告得到收入让用户因为不用支付钱这种容易衡量价值的资源而有一种「免费」的错觉。但同时用户往往在付出更加宝贵的资源,比如时间和隐私。这些资源虽然价值更高,但因为难以衡量,所以大部分人在付出时并不像对金钱一样敏感。向用户直接收费的产品往往会通过使用频次、时长等信息来了解用户习惯,从而在产品设计上进行取舍,但不会为提高这些指标而做对用户没价值的改动。对于以广告盈利的产品来说,因为提高广告收益是终极目标,优化的是与此相关的用户行为指标,而不是对用户的价值。这种利益的错位在基于推荐的产品中比搜索引擎更加严重。为了引导利于广告收益的行为模式,无论是设计产品的人还是推荐内容的算法往往会让用户更多接触到符合自己已有观点的信息,形成信息茧房,限制用户的视野,加剧社会的极化。所有公司都会宣称把用户价值放在第一位,对有的公司来说是产品为用户提供的价值,对更多的公司来说实际是用户给广告主提供的价值,取决于哪一种含义是和商业模式对齐的。

因为以上的原因,我很愿意支持那些有简单和健康的商业模式又为用户提供高价值的优秀产品。所以去年试用 Kagi 之后我开始付费,到现在已经用了半年多,累计省了很多时间,感觉再也回不到 Google。Kagi 是一个订阅制的无广告的搜索引擎。它和过去出现的 DuckDuckGo、StartPage 等 Google 的替代产品有两个显著差异:

  1. 搜索结果质量大部分时候优于而不是劣于 Google;
  2. 收入来自于用户付费。其他产品要么缺乏商业模式,要么和 Google 一样收入靠广告。

我就偷懒借 Vlad 博客里的两个例子来说明。

当你搜索一个技术问题时,你想看到的是官方文档或者同行的技术博客,而不是一些工具和云服务的广告。

当你在购物前做研究时,你希望看到的是论坛上真实用户的评价和评测网站上的对比,而不是一堆电商平台的下单链接。

除了总体上更好的搜索体验以外,Kagi 对我来说有三个比较特别的优势。

第一是强大的个性化功能。每个用户可以按自己的需要把个别网站在搜索结果中置顶、提权、降权或屏蔽。比如如果你经常查找编程相关的内容,可能会想把 Stack Overflow 或者 MDN 提权或置顶。

第二是完善的家长监护功能。我小孩会花过多的时间看 YouTube 上的 Minecraft 视频,虽然我可以在 Screen Time 把 YouTube 禁用,他还是会用 Google 结果里的内嵌视频看。我买了 Kagi 的 family plan,设置好他的儿童账号,在 Kagi 把 YouTube 禁掉,再在 Screen Time 把 Google 禁掉就完美解决了这个问题。

第三是如果订阅 Kagi Ultimate,除了搜索外还可以使用 OpenAI、Anthropic、Google、Mistral AI 的所有语言模型,价格只相当于其中一个的订阅费用,是很划算的。

自从有搜索引擎以来,主流的搜索引擎都是免费的,所以大部分人也都已经习惯,为搜索付费听起来是件不可思议的事。就连英文媒体介绍 Kagi 也用 premium search engine 来描述,暗示并不针对大众用户。但是有足够多的人愿意订阅在线流媒体服务,Netflix 能成为市值近三千亿美元的公司。相较而言,与学习、工作、生活息息相关的搜索价值要大得多。Kagi 在五月份宣布已经实现盈利,商业上的可持续性得到了初步验证。如果你也觉得 Google 的体验越来越差,也希望给你提供信息的服务动机和你的利益而不是广告主的利益一致,可以考虑用钱包投票支持一下 Kagi 这样的产品。如果这样的商业模式成为主流,用户在真正意义上成为各种网络服务的客户,互联网生态可能会变得很不一样。

运气与努力

2024年5月31日 08:00

我一直很喜欢 Out of the Gobi1 的作者单伟建。他的三本书我都读过。他在文革时被流放到戈壁六年,失去了上中学的机会,但从没放弃学习。没有放弃对更好未来的追求。在文革结束后他抓住了机会到美国留学,在金融界取得了很高的成就,并创立了亚洲最大的私募基金集团 PAG。他在母校旧金山大学的一次演讲中说2,在任何领域取得成功,最重要的是三方面:

  1. 坚持终生学习。他本人的经历最能说明这一点。如果他在离开学校后没坚持学习就沒有后来的一切。
  2. 好的判断力(good judgement),他特别提到这比 Angela Duckworth 强调的毅力(grit)3更加重要。方向对了努力才有意义。没有人是生来就有好的判断力的,正确的决策要建立在知识的学习和积累上,所以这可以算上一条的补充。
  3. 运气。很多人把成功归功于自己的才能和努力,却没有意识到好运在其中的重要性。忽视了这一点就难以保持谦虚,难以不断学习。

不少很成功的人都公开谈过运气在自己经历中扮演的角色。Charlie Munger 在 2017 年的一次演讲4中说,大部人只要每天起来后都努力做事,有一些自律并且持续学习,最终都会发展得很不错。但是目标不能定得太高,像成为总统或者成为亿万富豪之类的事,运气的影响太大,成功的可能性太低。Warren Buffet 也在回答一个关于资本增值税的问题时说,「卵巢彩票」恐怕是影响一生最大的因素,而他和 Charlie Munger 都赢了卵巢彩票。5李光耀曾经在采访中说胚胎形成的时候已经决定了一个人特质的 70%,因为领袖是天生的而不是教出来的。6我不完全同意他关于个人特质的观点,但那一刻除了决定基因外,更重要的还决定了后天的成长环境,确实对人的命运起到了近乎决定性的作用。

我最早接触计算机从 DOS 用起,直到后来的 Windows 3.0/3.1、Windows 95。我中学时看了 Bill Gates 的传记和他本人写的《the Road Ahead》,他的故事也是最早让我产生创业梦想的原因。Bill Gates 的成功除了自己的天分和努力外,也离不开他出生的时代和家庭背影。他 13 岁的时候就读的湖滨中学由家长集资购买了一台可以访问通用电气西雅图分部的 PDP-10 小型机的终端。当时微处理器、Atari、Intel 都还不存在,他学编程的时候全美国没有几个同龄人能接触到计算机。他父亲是成功的律师,当时已经在做天使投资,她母亲虽然从没全职在商业公司任职,但是因为家族是很成功的银行家,在很多公司的董事会里。他童年时每天家里晚餐的客人不是 CEO 就是议员。后来他母亲与 IBM 董事长的社交关系也帮助他得到了 IBM 的合同7,这是微软起飞最重要的一步。在那之后微软的快速成功也很仰赖于合作伙伴的局限性和竞争对手的失误。微软一开始的重心在程序设计语言上,唯一的产品是 BASIC 的解释器,对操作系统并不重视。在与 IBM 合作的初期,他们推荐 IBM 去找开发 CP/M 操作系统的 Digital Research 合作。而 Digital Research 的 CEO 因为未知原因没有见 IBM 派去的代表,所以双方没有达成合作,微软只能另想办法。当时有一家当地硬件公司 Seattle Computer Products 克隆了 CP/M 用来测试 Intel 8086 处理器,这个操作系统叫 QDOS8。微软向他们购买了 QDOS,并雇它的作者来基于 QDOS 开发了 MS-DOS,而从它开始的操作系统成了微软帝国的根基。可以说微软的成功是非凡的天赋、正确的策略、和令人难以置信的好运合力作用的结果。

就我自己的经历而言,哪怕是一些和别人比起来很小的成功,运气也起了重要作用。比如我从本科毕业后能去耶鲁读博,就是一件很巧合的事。我读本科的时候美国的研究生院对中国各大学的了解还不多,往往只会招收比较熟悉的几所学校的毕业生。耶鲁的计科系在那之前只招收过清华,中科大,南大这几所大学的学生。因为不了解其它学校的情况,所以 GPA 等也就无法比较。我能被录取,我后来的导师的推荐信起了关键作用。起因是我在本科时和他有一些关于他一篇论文的 email 讨论,后来又参加了同一个学术会议,他听了我的演讲后主动和我说如果我以后申请研究生院,他可以给我写推荐信。申请研究生院的人中,很少有人能得到这样被深入了解的机会。如果没有这样的机会,我自己的努力恐怕是无论如何都不够的。我上大学时只是因为英语和计算机方面比别人先行一步,所以能有一些时间在本科就参与一些研究,开始写论文而已。以当时的水平能做的事很粗浅,真要有多出色的成果是不太可能的。当时没想到的一点是,我得到录取也算是给后人开了一条路,后面几年就能在系里遇到低年级的武大校友了。

明白了运气的重要性,就知道不是人人生而能得到平等的机会,在遇到处境不如自己的人,不能假没这种差别是聪明或努力程度的不同造成的,应该知道要善待弱者。我们这一代 80 年前后出生的中国人,父母都经历了文革,很多人受的教育有限,对下一代的教育也就不太重视。我遇到过有些比我聪明也比我努力的人在年少的时候因为父母的观念,或者迫于家庭的财务压力没有得到好的教育。有的能凭借自己的努力加上后来的机会,克服早年的逆境,有的就没有那么幸运。所以我一直觉得自己是极其幸运的,也很感念父母始终把我的教育放在第一位,在自己的生活上做了很多牺性。

当然,说运气很重要,并不是指它总是唯一决定因素,只能听天由命。运气和很多其他东西一样都是呈正态分布的,真正一直很倒霉和一直很走运的人都很少,而好运的人也不乏浪费很多机会的。单伟建说他是极度幸运的,这当然是很谦虚的说法。在宝贵的少年时期经历文革本身就是极大的不幸,而且他还因为和领导关系不好而失去了离开戈壁去求学的第一次机会,所以只能说有一些不幸中的大幸给他提供了关键机会,而他因为自己坚持学习抓住了机会。拿我当年申请研究生院来说,如果之前没有足够努力,既使得到了展现自己的机会,恐怕也没有优点可被看到。大部分人都是既有不幸,又有机遇。机会到来的时候,有准备的人更容易抓住。

大家向成功的人或公司学习的时候,往往会带着一种偶像情节,希望通过复制他们的所有特点和行为方式来复制成功。所以学 Steve Jobs 的 CEO 们往往先学到了他性格上的弱点,学阿里等企业的公司,往往先学会了一种畸形的企业文化,因为最流于表面、最没有深度的东西最容易学。成功当然是值得学习的,但是需要分辨不同因素起到的作用:有哪些是因为出现在正确的时间正确的地点,又具备了正确的条件,被时代的趋势所推动;有哪些是偶然事件;有哪些是主观作用。对主现因素,应该分清楚成功者是因此而成功,还是虽然如此但还是成功了。这样才知道该学的到底是什么。

不少略有小成的人,大概因为成功来得容易,同時又看不到偶然性的作用而认为自己有一套可复制的系统或者方法论可以高价教给别人。又或许他们其实很清醒,只是不想错过利用别人渴望成功的心态让自己更加富有或增强个人品牌的机会。

不久前一个朋友说有个曾在 IBM 做过中层管理者的人建入群费 200 的微信群教人职场和生活方面的经验。很快就有几千粉丝入群,后来大概觉得价格定低了,又建了一个入群费一万的 VIP 群,马上就有大几十人报名。现在可以说是学习最容易、成本最低的时代。无论哪方面的信息在互联网上都唾手可得,而且大部分是免费的。当然,好的老师、课程、书籍是有价值的,因为虽然大部分信息都能免费找到,如果被组织成更体系化、更容易理解的形式,吸收起来就更快,也更容易让人得到启发。但是没有任何微信群是值一万进群费的。9

商业模式往往是以某种信息不对称为基础。互联网消除了很多信息上的不对称后,就有人人为制造出一种信息不对称的错觉和神秘感。卖成功学的人往往要用各种理论包装一番,因为如果看起来很简单就无法赚钱。成功的要点单伟建和 Charlie Munger 的几句话已经总结得很好,简单但不容易,如果能长期坚持就会比大多数人成功。


  1. 我以前写的书评。如果有条件的话,这本书建议看英文版。国内中文版你懂的。 ↩︎

  2. 见 YouTube: Dr. Weijian Shan at the University of San Francisco - May 18, 2023 ↩︎

  3. 有兴趣可以看 Grit: The Power of Passion and Perseverance 或者她的 TED Talk。 ↩︎

  4. A Conversation with Charlie Munger and Michigan Ross ↩︎

  5. Here’s why Warren Buffett says that he and Charlie Munger are successful ↩︎

  6. Lee Kuan Yew: The Grand Master’s Insights on China, the United States, and the World ↩︎

  7. How Bill Gates’ mom helped Microsoft get a deal with IBM in 1980 – and it propelled the company’s huge successWhy you shouldn’t imitate Bill Gates if you want to be rich ↩︎

  8. QDOS 的全称是 Quick-and-Dirty Operating System。 ↩︎

  9. 更不用说是传授 IBM middle management 经验的微信群。 ↩︎

刷新了一下对内容审查粒度的认知

2024年4月9日 08:00

我看书大部分时候是用 Kindle,偶尔也会从京东、多抓鱼,或者嘉里中心的现代书店买纸质原版书。新书的扉页背面基本都会盖这样一个章。

扉页背面的审查印章

扉页背面的审查印章

我原本以为这基本是一个形式,获准进口的成年人类别的纸质书无论是什么内容都会在这一页上盖这个章。直到前几天在一本很厚的书中间的一页看到这个章以及被涂改液覆盖的脚注。

被审查涂改的页面

被审查涂改的页面

没想到我国内容审查的粒度竟然那么细。很多人都会问为什么中国的税率那么高,社会福利安全网却还比较差。从这件事的人工成本就能想见政府运行的成本有多高,应该说大家的税还是没有白交的…

从某个时候起,中国的任何互联网产品只要有任何形式的 UGC(包括头像昵称等),就会有很高的审核成本。这类产品早就不是小创业公司能做的了。

如果你好奇,这本书是 Behave,是一本讲人类行为的科普读物。涂改液覆盖的内容是:

(11) Mao-induced Chinese famines, 1.4 million

排在人为灾难年化死亡人数的第 11 位。

离开心动和 TapTap

2023年12月19日 08:00

在 LeanCloud 被心动收购快三年的时候,我离开了心动和 TapTap。

Removed from LeanCloud GitHub organization

Removed from LeanCloud GitHub organization

一段时间前我看到 Will Larson 写的 Deciding to leave your job,其中讲到决定是否离开时应该问自己的四个问题:

  • Has your rate of learning significantly decreased?
  • Are you consistently de-energized by your work?
  • Can you authentically close candidates to join your team?
  • Would it be more damaging to leave in six months than today?

这几个问题可以说贴切地描述了我当时的状态。我对于公司的管理风格和文化有比较强的看法,过去创业最重要的原因也是要实现自己的这些想法。所以如果在这些方面对所在的公司失去认同,就很难专注地把工作做好,很容易把注意力转移到个人自身的提高上。这或许可以算是我的缺点。所以应该说离开的决定在内心早就做了,只是一直没考虑确定具体时间,也一直没好意思提出。在国庆前正好我创业时的合伙人俊文也说准备离开,所以国庆后我就一起提出了。毕竟他离开就意味着很多工作要交接,组织结构也要调整,以其过几个月再调整第二次,不如一次到位。过去三年内外部环境都变了很多,我所负责的业务对现在的 TapTap 来说确实价值很有限,我们离开也好给公司需要做的一些改变让路。

之后我很快加入了 EMQ。这是一家做跨境支付的公司,国内还有一家做 IoT 的同名公司,所以很容易混淆。我在 10 多年前还在负责 AVOS 中国的时候就通过 Steve Chen 介绍认识了 EMQ 的创始人 Max,当时他还没创业。EMQ 和 LeanCloud 是同一年开始的,后来 Max 多次邀请过我加入,虽然时机都不太合适,但我一直关注着这家公司的发展。这次时机对双方来说都很合适,所以我就决定加入了。过去很多人都说我离钱比较远,那么这对我是个离钱近一些学习 financial services 的机会。

EMQ 是一家比较西式的创业公司,有 80 多人,开发团队有十多人,在台湾、香港、新加坡、印度、欧洲和东南亚都有同事。让我比较意外的是产品和运营团队的不少同事也都有很不错的技术背景。我们的技术栈包括 Erlang、Python、TypeScript、Clojure、Postgres 和 AWS,目前前后端都有职位在招聘,如果你有兴趣可以把简历发到 engineering-hiring@emq.com 和我们联系。春节之后在上海应该会有办公室,此外我们也会考虑很优秀的远程候选人。

如何反转一个链表?

2023年5月17日 08:00

「如何反转一个链表?」是一个在面试中被问滥的问题。我参与的面试中偶尔也有我们自己的面试官问。 如果你去别的公司面试被问到这个问题,要是给出的答案是(以 Python 为例):

def reverse(l):
 l.reverse()
 return l

或者是:

def reverse(l):
 return l[::-1]

肯定会被拒掉。面试官所预期的是你自己定义节点,再定义链表:

class Node:
 def __init__(self, data):
 self.data = data
 self.next = None

class LinkedList:
 def __init__(self):
 self.head = None
 # 以下略,问 ChatGPT 就可以了。

不过既然是个被问滥的问题,如果遇到不妨尽量给出一个面试官没见过的答案。 比如,构造链表:

def cons(h, t):
 return lambda f: f(h, t)

取头:

def car(l):
 return l(lambda h, _: h)

取尾:

def cdr(l):
 return l(lambda _, t: t)

反转:

def reverse(l):
 rev = lambda l, r: rev(cdr(l), cons(car(l), r)) if l else r
 return rev(l, None)

以上就是完整答案。为了展示方便写个打印链表的函数:

def printl(l):
 toStr = lambda l: str(car(l)) + ' ' + toStr(cdr(l)) if l else ''
 print('(', toStr(l),')')

写个例子试一下:

l = cons(1, cons(2, cons(3, cons(4, None))))
printl(l)
printl(reverse(l))

输出是:

( 1 2 3 4 )
( 4 3 2 1 )

这应该是自己构造链表的最短答案了,但是有一定风险被面试官以奇怪的理由据掉。如果你是面试官,又想问这道题,就得了解各种实现方式,避免把不该拒的人拒了。🙃

如何高效地协作开发:一些 Google 的实践

2023年3月23日 08:00

这是我一年前发在 TapTap 内部 Confluence 的一篇文章,经过一些修改后公开出来,希望对更多人有价值。

Google 的很多软件工程实践都在对外发布的各种 Tech Talk、CppCon 的演讲以及多本已出版的书里提到过(比如 Software Engineering at Google、Site Reliability Engineering 等),所以这篇文章的内容并不算新鲜事,只是贡献一些个人视角。另外我在 Google 工作已经是 10 多年前的事,现在可能已经变化很大,但我认为 2000~2010 年的时候是 Google 最有创造力、最高效、对人才的吸引力也最强的时候。

一点背景

有时言必谈前公司的人会有点让人讨厌,不过无论是之前创业,还是现在,讨论起工程方面的事情都很难避免提起 Google 是怎么做。Google 和所有公司一样,并不是所有方面都做得很好,比如产品能力以及饱受诟病的客户服务。但就是因为在 engineering 方面领先大部分公司很多,所以削弱了其他方面的弱点带来的影响。Google 取得成功的大部分产品基本都是在技术实现上大幅领先同时代的产品,从而实现用户体验上的领先,早期产品中最有代表性的是 Search、Maps 和 GMail。Google 的 engineering culture 也对包括 Facebook 等在内的大量硅谷公司以及国内包括字节这样的公司产生了深远影响。

我在 Google 的三年是在一个叫 Google Web Server(简称 GWS)的团队。这个项目可以说是 Google 历史最悠久的项目,从 Google 存在开始就有 GWS,到现在 20 多年,Google 的 HTTP header 里 server 还是 GWS,应该还是同一个项目、同一个 code base 和 binary。

GWS in Google’s HTTP header

GWS in Google’s HTTP header

一开始的时候 Google Search 就是 GWS。后来从它里面拆出了一部分放到前面承担类似 SLB 的角色,叫 GFE(Google FrontendEnd);又把实现单纯搜索的部分拆了出来叫做 Superroot。GWS 更多地成为了一个实现搜索相关的整体业务逻辑的服务,它后面有 15~20 个后端服务,除了 Superroot 外,还有广告、拼写检查、搜索词修正、query rewrite、用户偏好等等。经过多年的演变,GWS 的开发语言也从 Python 变成了 C,再变成 C++,后来又为了方便快速做试验内嵌了 Python 解释器。在整个过程中,GWS 从来没被真正意义上重写过,因为 Google 一直都有大量的业务需求等着实现,不可能有停下来重写或者重构的机会。所有大的改变都必须以渐进的方式来实现,包括换语言。尽管只有 Google 一家公司在用,但是 2010 年的时候 GWS 就支撑了全球 13% 的活跃网站,是排在 Apache、Nginx、IIS 之后的第四大 web server,因为 Google host 的网站很多都是 GWS 来 serve 的,包括自定义域名的 custom search 以及 enterprise search。

几乎所有在搜索结果页用户可见的改动都会需要改 GWS 的代码。这就涉及到了很多其他项目和团队,除了和搜索有紧密关系的广告外,还有地图、新闻、财经,甚至小到汇率转换、计算器。Google 的搜索框不仅是狭义的搜索,其实功能是很多的。这就导致项目非常复杂,改动也非常频繁。GWS 的二进制文件编译出来有 1G 多,在当年已经超过了 gcc 和 gdb 的上限,需要使用内部改过的版本。项目每周发布一个新版,平均有大几百个 changelist。Changelist 是 Perforce 的名词,相当于是 GitHub 的 pull request 或者 GitLab 的 merge request,是一次 code review 的单位,大小通常是多个 commit。

这样一个项目听起来似乎需要一个庞大的团队来维护,而且很容易成为瓶颈。我 2007 年加入的时候,团队有不到 20 人。2010 年我离开的时候有 40 人左右(负责的范围也变大了),分布在 Mountain View、芝加哥、匹兹堡、纽约。除了 GWS 外,这个团队还维护着一些服务端的基础组件,比如我们开发维护的一个项目是 Google 大部分 C++ 服务的基础,提供了标准的健康检查、feature flag、监控变量管理、实验框架等功能。团队成员还有时间发起一些 20% project,比如我和另外两位同事合作开发了一个 serve Google 所有静态文件的新服务,把这部分职责从 GWS 分离出来。听起来这么多的事情由那么小的团队来做是不可能的。秘诀在于 GWS 每周大几百个 changelist 中大部分并不是这个团队里的人写的代码。

GWS team 不负责实现其他产品在搜索里的需求,但是会花大量时间 review 其他产品的工程师提交到 GWS 的改动。由于每周的改动量巨大,很多人会花一半以上的工作时间在 code review 上。GWS team 自己也做开发,但是责任是不同的,日常开发大概有几类

  • 为了提高 GWS 的性能和稳定性,或者为了团队本身的开发效率做的重构和改进
  • 为了让其他团队能更容易地修改 GWS 而开发的模块化功能,比如 OneBox、ManyBox、Universal Search
  • 为搜索和广告的整体业务需求增加的功能,比如试验框架
  • 上一段所说的输出给其他项目的基础组件以及从 GWS 衍生出的新项目

因为这是我的第一份全职工作,其实在 Google 期间并没有觉得有多好,以为大公司都是这样的。但是离开之后才觉得,能有这段各团队之间能顺滑地高效协作的工作经历是很幸运的。这样的工作模式在大部分其他公司很难完全复制,因为它需要一些很强的基础设施做支持。以我们目前的工作模式,很难想象能达到同样的吞吐量,可能也很难复制同样的模式。但是分析一下别人做得好的地方还是能给我们提供一些方向上的参考,做一些努力能提高的空间还是很大的。下面我就介绍一下支撑 Google 团队间高效协作最重要的几个方面。

代码管理和安全

Google 的每个工程师都可以访问全公司 99.9% 的代码,这是决定 Google 的工作方式最根本的条件。剩下只有少数人能访问的那部分叫 HIP (High-value Intellectual Property),主要是防 SPAM 的逻辑。这部分代码如果泄露了,很快就会被恶意网站利用使得搜索质量显著下降,并且没有可以迅速补救的办法。其他所有项目的所有代码都是对所有工程师开放的。

Google 全公司共用一个代码库,叫做 google3,用 Perforce 做版本管理,但是自己在 Perforce 的命令行客户端 p4 之外包了一个工具叫 g4(为了描述方便,以下 Git 和 Perforce 里 submit、merge 等名词区别就不纠结了,从上下文意思应该清楚)。每次一个工程师在自己本地创建一个 workspace 的时候,g4 会用 OverlayFS 从 NFS 上把最新版本的整个 google3 映射到本地。从工程师的视角,整个 Google 的代码树就是自己电脑上一个只读的大目录。当他 checkout 某个路径进行编辑的时候,g4 会把对应的子目录实际复制到本地,这样在他的视角那个目录就变成可读写的了。所以任何一个工程师都可以编译、测试、修改公司的任何一个项目。

很多人都会问,这样代码不会泄露吗?在那么大的公司可以肯定一定是会泄露的。2010 年前后有很多 Google 中国的人去百度,人民搜索(云壤)、盘古搜索也都是 Google 的人出来做的,要说没有知识产权外泄谁都不信。但是对公司而言,让自己跑得更快远比让竞争对手跑得慢一点更重要。所以大部分情况下保密措施应该是以不伤害效率为前提的。对用户数据的保密除外,但是保护用户数据的措施通常不会影响到大部分人的工作效率。

当时内部邮件列表每个人都可以建,项目管理工具的项目每个人都可以建,工程师可以看任何一个数据中心的任何机器上任何服务的日志,甚至可以动态修改它的 feature flag。理论上这些都可以被滥用或者误用,但是滥用往往缺乏动机,误用可以从设计上避免。一个大原则是风险可控或可逆的事情默认是没有流程的,只有实际发生了问题,证明必要时才会靠引入流程来解决。有了流程就需要有人审批有人执行,如果它解决的问题不常发生、有其他方案或者产生的危害不如流程带来的成本,那么设立流程就是不理性的。

共用一个开放的 repo 目的是让每个工程师都能访问、修改、运行公司的任何项目。要做到这一点,还需要每个项目都使用同样的基本工具。

构建工具

Google 使用的构建工具叫 Blaze,支持 Google 当时允许使用的四种语言:C++、Java、Python、JavaScript(Closure)。后来 Blaze 开源了,改名叫 Bazel,由社区增加了对更多语言的支持。

无论哪个项目,编译的方式都是在 google3 目录运行 blaze build [path],而 blaze test [path] 是运行项目里的所有测试。一个工程师如果需要对某个他不熟悉的项目做一些小的修改,需要的知识是很少的,只要做完改动后确保新的代码有测试覆盖,所有老的测试能通过,就可以提交到 code review 的系统了。

每个项目会有一个 Wiki 页面介绍怎么在本地运行和调试,通常来说是描述需要传递什么命令行参数来连接本服务依赖的线上或测试环境的其他服务。对于需要实际运行、进行人工验证的复杂改动,看了 Wiki 以后就能自己在本地跑一个实例,这类改动在 code review 时也通常会要求提供一个本地 demo 的地址。所以任何一个工程师都可以把 Google 的任何一个服务从源代码编译并运行起来。

自动化测试

GWS 作为一个 C++ 的项目,测试覆盖率保持在 90% 以上,这是非常不容易的。用静态语言的项目测试难度比动态语言大很多,因为对象的属性和方法无法动态替换,想要能在测试中 mock 掉 side effects 需要在设计上做更多的努力。自动化测试的好处相关的书上有很多,就不赘述了。我只说两点:第一,对自动化测试的要求确实可以产生更好的设计,比如鼓励面向接口的设计;第二,对于 Google 那样的协作模式来说,自动化测试不是一件锦上添花的事,而是必须。因为无论是去修改其他项目的信心,还是让其他人来修改自己的项目的信心,都来自于很高的测试覆盖率。

确保自己项目的稳定性和质量的方式不是不让别人改,而是把自己关心的东西加到测试里去。比如当时我们有一个用来表示用户请求的类叫 GWSRequest,一个 GWSRequest 对象的生命周期就是整个请求处理的过程,所以这个类自然就成了存这个过程中产生和消费的各种信息的地方。时间一久它的属性就越来越多,还出现了多个属性重复存了同样的信息的情况。后来我们加了一个测试 STATIC_ASSERT_LE(sizeof(GWSRequest), MAX_GWSREQUEST),试图往 GWSRequest 加新属性的改动在编译时就会失败。如果有人想改 MAX_GWSREQUEST 的值,就需要说明为什么是必须的。有的比较卷的团队为了控制代码的复杂度,还把自己 code base 的行数上限放到了测试里。如果有人增加了 10 行代码,就需要重构其他地方的代码来省出 10 行,或者提供一个好的理由来提高上限。

Code Review

Code review 是每个 Google 的工程师日常工作中很核心的部分。每一个改动都需要经过除作者外至少另一个人的 review 才会 merge。无论是对发明 Python 的 Guido van Rossum 还是发明 C 的 Ken Thompson, 都是如此。google3 代码树中每个项目的目录有一个 OWNERS 文件,里面是一些工程师的 ID。当有人提交一个 changelist 后,系统会自动把这个 changelist 分配给涉及到的每个目录的一个 owner 来 review,每个 owner review 通过,并且涉及到的各目录的测试都成功之后才能 merge。Guido van Rossum 加入 Google 后前几年的成果就是开发了内部的 code review 工具 Mondrian,可见这件事对 Google 的重要程度。

做 Review 的人主要关心几件事:

  • 改动的业务逻辑是否正确,具体实现在性能和可读性方面是否合理;
  • 是否符合 Google 的代码规范以及本项目附加的一些条件;
  • 新的代码是否有足够的自动化测试覆盖;
  • 用户可见的改动是否被产品经理批准(会要求提供 Buganizer 的链接,Buganizer 是 Google 自己开发的类似 Jira 的系统)。

除了日常的 code review 外,每个新员工会需要学习公司的代码规范,并通过工作中会用到的每个语言的可读性 review。方式是准备一个百行以上的 changelist,提交给一个有资格做 readability review 的工程师,通过之后才有权限提交用在生产环境的代码。有权做这类 review 的是在一个叫 readability committee 里的资深工程师,他们也负责制定公司的代码规范。比如当时给我做 C++ readability review 的是Generic Programming and the STL 的作者 Matt Austern。我刚入职时同组的同事告诉我 Guido van Rossum 到 Google 后第一次 Python readability review 没有通过,不知真假。

Code review 除了让项目的 owner 保证代码质量,让其他项目可以快速推进需要的改动外,还起到传播知识的作用。很多时候一些在公司多年的工程师的 reviewer 是入职不久的新人,做 review 的人往往也能从别人的代码里学到很多东西。

版本管理和发布流程

Google 在内部做版本管理的方式就是不做版本管理,所有项目都 live at head。当然,每个项目都有对公司外的版本发布流程,live at head 是对内依赖的策略。一般不会出现一个项目依赖一个组件的 1.1,而另一个项目依赖这个组件的 1.2 这样的情况。每个项目在每次编译的时候,都会把所有依赖从最新的代码编译,使用到的依赖的版本是由编译的时间点决定的。这自然就意味着每一次提交后的代码状态都应该作为稳定版本对待。

一般外部的开源项目,会分不同的版本号,由用户决定什么时候升级,并且由用户做升级所需要的改动。按照 2015 年的数据,Google 有 20 亿行代码,其中的依赖关系错综复杂。而 C++ 对于一个项目用到的两个组件依赖同一个组件的不同版本的场景(菱形依赖)支持是很脆弱的。所以不太可能使用 SemVer 之类的版本方案,除了纯技术上的问题外,每个组件要为内部用户维护多个版本也是成本巨大的。所以 Google 把版本管理完全倒了过来,每个项目/组件都只要维护一个最新版,所有的改动最重要的原则是不能破坏任何测试。所以如果有人在一个共享组件里做了向前不兼容的改动,就会需要在同一个 changelist 里把整个代码库里所有调用到这个接口的地方改过来。有的改动会动到十多个项目,需要十几个 owner 来 review,这并不少见。当然 Google 有强大的代码搜索工具来辅助这样的事。据几年后 Google 工程师在 CppCon 上的分享,他们还用 MapReduce 做了大规模重构工具,不过是在我离开之后了。

没有内部版本管理就意味着要保证每个版本的稳定。Code review 的工具会保证每次 merge 前都运行了所有涉及的测试,但是有时还是会发生代码 merge 之后导致测试失败的情况。比如可能有 race condition,也就是两个人几乎同时 merge 了会相互影响的代码;也可能失败的测试和改动的文件不在一个目录,导致 merge 前没有运行。GWS team 有个工程师轮值的角色叫警长(sheriff),一旦 GWS 的任何测试变红,本周的警长就会找出来是谁干的,并把对应的改动撤销。后来有人做了工具把这个流程半自动化了,一旦有测试失败,就会自动生成一个 changelist 把对应改动撤销并发 code review 给警长,他只要确认、跑测试、点 merge 就行。再后来有的团队把这个过程完全自动化了,不用人工干预。

GWS 每周会做一次 binary push,也就是二进制文件的发布。流程是每周一早上负责发布的工程师从当前的代码做一个发布分支编译出一个二进制文件,交给 QA 开始测试,发现 bug 就把修复 cherry pick 到发布分支。由于大部分逻辑错误都会在开发过程中的自动化测试发现,QA 阶段发现的大部分是通过截图比对工具找到的某条线移了一个像素之类的问题。QA 流程通过后,就从单数据中心单台机器开始灰度,逐步发布到全球。如果到周四下班还没完成发布,本周的发布就会被放弃,下周再重复同样的流程。发生发布失败的情况很少,如果发生会作为事故来开会复盘。

除了 binary push 外,还有 data push,也就是数据发布。数据包括 GWS 的配置文件、各种黑名单白名单、模版文件等。Data push 比较轻量,每天有多次,都是采用灰度发布到全球的方式。

以上这样 live at head 的方式意味着不太可能有长期存在的功能分支。如果一个工程师在独立功能分支上开发了几周,那基本上是不太可能合并回主线的。无论是多大的新功能,都会需要拆成很多小的 changelist,高频地提交到主线。只是未完成的功能会用一个 flag 屏蔽掉,在生产环境不会运行。所有用户可感知的改动都是用与试验一致的方式发布,从单个数据中心千分之一的流量开始灰度到全量。如果发现问题,只要做 data push 把 flag 的值改回来就行,因为老的 code path 已经在线上运行了很久,所以改回来一定没问题。这让发布很安全,大家也有信心做大胆的尝试。

关于项目管理和排期

排期这个词在 Google 其实很少出现,离开 Google 之后都在小创业公司就更不会出现。如果大部分事情都需要项管排期,一件很小的事情也可能被排到一两个月后,而这件小事可能 block 了很多其他工作。把一个工程师的时间按开发任务线性地排列,完全做完一件事再开始做另一件事,也并不是高效的方式。工程师确实是需要专注的、避免多任务切换的时间,但这样的时间应该是以小时计,而不是以天或周计的。一个工程师应该把任务分割成尽可能小的单元,写完代码和测试后及时提交给别人 review,并且也需要 review 其他人的 merge request。从天和周的维度看,本来就是需要在多件事情之间切换的。

在 Google 的三年多里,我们团队的 project manager 对于工程师来说存在感一直比较低,项管不会过多干预个人的工作计划。每个工程师都相当于自己的项管,工程师之间会互相就优先级进行沟通达成共识和妥协。这样的好处是工作量小但 block 了其他人的事情会被快速完成;实际做事情的人用专业的语言沟通,不需第三者传话,也不容易造成误解;一些重要但不紧急的事情,比如重构、还技术债,也可以由工程师在日常工作中穿插地推进。我无法想象在 Google 当时的团队能按集中排期来安排工作。过去十多年里,硅谷比较成功的互联网公司都是用与 Google 相似的方式来工作的,这不是偶然现象。网状的沟通协作与传统的树状比,表面可能感觉混乱,但是因为不容易形成瓶颈、沟通中信息损耗少,效率是很高的。

我们能先从哪些事做起

Google 的人喜欢说 Google 的工程实践是为连续运行十年以上的软件设计的。10 年不是很长时间,已经基本站稳脚跟的公司需要探索在开发协作上更 scalable 的方式。我们不太可能去复制 Google 的所有东西,Google 的方式也未必放在所有公司都是适用的,但是有一些方向性的结论在整个业界是得到了共识的,我们也应该朝那个方向去努力。以下是我们可以在公司推动改进的一些方向。

在工程师团队里培养测试文化

在这个年代写完代码就交给 QA 去测试,靠人工来保证质量,是很落后的方式,因为它不能 scale。人工的测试做十次就是十倍的成本,哪怕每次内容都一样。自动化测试是所有人都知道好,很少人实际做,更少人能做好的事。Google 也不是从一开始就把测试做得很好,而是由一小群人努力地在全公司推动起来。最可见的一件事是 Testing on the Toilet,内部简称 TotT。他们在全球办公室的几百个马桶前都装上了这样的海报,这样大家在上厕所的同时还能学习如何写测试。久而久之,重视的人越来越多,开始在 code review 中执行测试覆盖率的要求,大部分的项目都逐步建立起高质量的测试。

让自己的项目更容易被别人改动

要改代码首先要看得到。我一直认为代码应该默认是对内公开的,虽然可能会增加泄露风险,但和效率的提高比是微不足道的。如果市值几千亿美元的公司都能对每个工程师开放代码,对大部分公司而言部门之间还互相捂得很严就太小气了。Git 本身不是为 Monorepo 设计的(虽然最近有一些支持,以及微软有全球最大的 Git repo),可能短期我们也难以大范围转为 monorepo,但是 GitLab 把 repo 组织成树状的设计一定程度上是为了在权限管理上模拟 monorepo 按目录管理的方式,是很容易做到默认 owner 可读写,其他人可读,让任何人都可以通过 merge request 改任何代码的。

让别人更容易改动还包括提供内部文档,让别人知道如何修改、运行、调试(别人包括团队新人、其他团队、未来的自己);提高可读性和模块化,让别人容易理解;提高测试覆盖率,让别人不容易改坏。

尽量提交代码而不是需求

现在经常会遇到有一件事需要涉及多个项目的改动,把需求提到其他项目,发现要排到很久以后了。解决优先级上的冲突,让对自己重要的任务早点完成的最好办法就是把一件事的开发收缩到同一个团队,不管要改的代码在哪里,都尽可能由同一个团队完成开发,由 owner review。从经济上来说,由需求方投入资源也是更合理的方式。这样的工作方式偏爱能力全面的团队和工程师,在 Google 一个人经常性地提交 2~3 种语言的代码是很常见的。

当然,也不是所有的事情都能收敛到同一个团队进行。有的任务是需要对多个项目进行根本性的改动的,就会需要来自多个团队的工程师形成一个临时的小团队了。

建立代码规范

很多公司通常是从很小的时候就会建立公司范围的代码规范,因为有了一致的规范,才好使用同样的工具,阅读和修改其他团队的代码时才能比较容易。如果在规模较大时再从零开始做这件事会非常困难。可行的路径可能是自下而上的,各个团队先建立自己的规范,协作得多的团队可以取长补短,从能建立共识的部分形成共同的规范,逐步扩大适用的范围。

关于 LeanCloud 被心动/TapTap 收购

2021年9月12日 08:00

其实这是知乎上的回答。公司并购之后由于团队变大了很多,特别忙,所以博客和播客都断更了好久。

其实我们官网的公告已经把主要的前因后果介绍得比较清楚了,没有太多其它的新内容可说。

其它答案里说的大公司同类竞争之类倒不是主要原因。从我们创业开始,阿里、腾讯就陆续有过多次形态类似的产品尝试,后来字节也有相似的产品,但是都并没有很成功。选择不用 LeanCloud 的用户大部分是会用云主机等形态更底层的云服务的。所以本质上我们主要是和更传统的云服务竞争,而不是和形态相似的产品竞争。

LeanCloud 创业的几年中,各方面都有不少改进的空间,但是在资源有限的条件下,我觉得在国内环境能做的范围内,产品、团队和文化都还是有一些做得不错的地方,各方面被同行借鉴得也不少。但是我们在财务上并不成功,至少没有达到 venture-backed 的创业公司的增长期望。一方面团队创始人都是技术出身,公司一直都没有过在市场、BD、销售方面有资深经验的人才,虽然有一两次碰到比较合适的人,但都因为各种原因没有实现;另一方面也有行业发展的客观原因,做一些横向对比就能发现。Starbucks 前 CEO Howard Schultz 说过,公司文化等等很重要,但是一家公司在财务上能 deliver 是一切的基础,不然其他东西都是不可持续的。

LeanCloud 并不是第一次收到并购邀约。过去曾有 A 股上市公司,加密货币和区块链最火爆的时候也有这个领域的公司,不过都没推进到谈价格的阶段。因为两个前提要有保证:第一是产品的未来发展和对现有用户服务的连续性;第二是文化和管理上我们创始人自己和团队得愿意加入对方。

知道和使用 LeanCloud 的游戏公司数量很少,但是如我们的公告上说,这部分公司贡献了将近一半的收入。其实我们一直以来都很想拓展这个领域的客户,但是一直都没找到好办法。与 TapTap 的结合对 LeanCloud 来说是一个让我们的产品真正进入游戏行业的很好的机会。对所有现有用户来说,也意味着我们未来有充足的资源继续改进产品。

心动从做「天天打波利」开始就用 LeanCloud 的服务,一开始我和前 CTO 沈晟沟通得比较多,从去年开始聊并购后和两位创始人及其他高管都有交流。他们所期望的公司管理方式和文化和我们自己还是很一致的。创业的人大多有两方面的目标:一方面当然是财务上的;另一方面是创造一家符合自己自己理想的公司。只是很多人做着做着就忘了第二点了。我觉得心动/TapTap 是一家很难得的公司:两位创始人在坚持朝着自己的理想迈进,而同时财务上又能 deliver。其实因为财务上的空间比较大,心动和 TapTap 在管理和文化上在做一些比 LeanCloud 更加大胆的尝试,只是因为公司一直比较低调,所以在互联网圈子里大部分求职者对这家公司还缺乏了解。

LeanCloud 团队目前已经和原 TDS(TapTap Developer Services)团队合并,LeanCloud 的各项服务也已经引入 TDS,详情可以看我们两天前办的 TapTap 开发者沙龙的报道现在 TDS 正在积极扩充团队,欢迎优秀人才加入,详情请见我们的招聘网站

small talk #3:从 IPFS 聊到 Web 的开放性

2021年2月13日 08:00

这一期我们从 IPFS 项目及 Brave 浏览器的一些新尝试展开聊了 Web 的开放性相关的话题。

特别感谢 Kai Luo 为我们制作了片头片尾音乐。

如何收听

本期主播

相关链接:

small talk #2:聊聊用 M1 芯片的新 Mac

2020年12月28日 08:00

在 small talk 的第二期我们聊了一下搭载苹果 M1 芯片的 Mac。我们回顾了各自使用 M1 Mac 的感受和遇到的问题,后半部分讨论未来可能的行业影响以及购买方面的建议。

欢迎留言给我们提出反馈以及以后希望听到的话题:

  • 语音留言(你的留言可能会出现在以后的节目里)
  • 文字留言:请在本帖下面发评论

如何收听

本期主播

讨论中提到的相关链接

怀念两位老师:Stan Eisenstat 和 Paul Hudak

2020年12月24日 08:00

最近难过地得知 Stan Eisenstat 教授在 12 月 17 日去世,我在 2017 年回学校时还去找他聊了会儿天。这也让我想起几年前去世的 Paul Hudak。这两位教授虽然不是我正式的导师,但都对我影响很大,所以就想写一写我对他们的记忆。

Stan Eisenstat

Stan Eisenstat

我在去耶鲁上学之前就在 Joel Spolsky 的 Joel on Software 里读到过 Stan Eisenstat。他教的 CS323: Systems Programming and Computer Organization 在学生中是一门传奇性的课程。选课的人往往在那个学期会需要花大量时间熬夜甚至通宵来完成他布置的几个大作业,而在课程完成后都会觉得收获很多。进入耶鲁后我抓住机会申请做了这门课的助教。当年(1998 - 2002)我在国内接受的计算机科学本科教育说实话和美国好的大学比还有很大差距,直到后来国内大学有越来越多留学回来的人加入,这个差距才缩小,所以其实读博期间还要弥补一些知识面上的裂缝。给本科生课程做助教对于提升自己也是非常有益的。他在一个学期里会让学生完成几个大作业:实现一个 UNIX shell,一个 LZW 文件压缩/解压程序,重新实现 make 等等。每个项目 Stan 都会准备好一套测试,学生提交完后他跑一遍测试就能马上得出正确性的得分。他也准备了详尽的代码风格文档,所有的作业和考试都有正确性和风格两部分分数。到期末他会让助教自己设计一个期末项目给学生作为期末考,助教也要负责给这个项目写一套自动化测试来给学生提交的代码打分。所以每一届学生遇到的期末项目都不同,而每个助教也有自己的发挥空间,参与这个过程的每个人都很有收获。我现在还记得给学生出的题目是做一个支持用 telnet 登录的多线程 BBS 服务器,我写了一个 telnet 机器人来测试他们实现的各项功能。Stan 在耶鲁计算机科学系将近 50 年,这门课和他教的 CS223: Data Structures and Programming Techniques 一直都是计科系的核心编程课,无数学生在 Zoo(系里的机房)耗费了无数夜晚来完成这两门课的作业。

Paul Hudak

Paul Hudak

我对 Paul Hudak 也是在去耶鲁前就有很多了解。我本科的毕业设计是函数式语言的课题,所以找了一些 Haskell 的资料看。Paul 是 Haskell 的主要设计者,当时他正好出了一本新书。那时市面上还几乎没有正式出版的 Haskell 的书,国内更是完全没有。我给他发邮件问问题时提了一句很遗憾在中国买不到他的书。过了两周多就收到了他给我寄过来的书。后来进了耶鲁我选了另一位教授开的 functional programming 课,因为这方面基础薄弱学得比较差。后来 Paul 教这门课时我又去报名做助教,相当于自己也又学了一遍。虽然后来我没有选择在程序语言方面深入下去,但因为他的原因一直保持着对这个领域的兴趣和关注,所以当 Clojure 这样更具实用性的函数式语言出现时我很早接触并应用到实践中,后来创业时也选择它作为主要开发语言,使团队能在早期很高效地开发出产品。

Paul Hudak’s book

Paul Hudak’s book

耶鲁的计科系格外地重视教育。这句话听起来似乎有点奇怪,所有的大学都应该是重视教育的,但其实各个学校在研究和教学上的投入还是很不一样的。很多地方的教授更愿意把时间投入到研究上,多发几篇论文,而教学更多地是必须完成的义务和负担。耶鲁的很多教授,包括 Paul、 Stan、Stan 的妻子 Dana Angluin(也是同系教授)、我导师 Mike Fischer 都对教学有一种很纯粹的热爱,他们年复一年地打磨同一门课程,不吝惜花大量的时间做到尽善尽美,把培养出的学生看作最重要的遗产。我在 Yale Daily News 上看到,Stan 对学生的爱持续到了生命最后一刻,他要求等期末考结束再宣布他去世的消息,所以他离世后系里还不知道,第二天在 Zoom 上的虚拟节日聚会里大家还录制了一些祝他早日康复的消息。

small talk #1: 聊聊你的私有云

2020年12月10日 08:00

我们一直都想做一个中文播客节目,但是因为最初提出想法的人都比较腼腆,一直没有启动。最近我来推动开始了这件事。

播客名称叫 small talk,除了闲聊的意思外,也有个编程语言叫 Smalltalk,算是有些双关。这个播客的目的不在于宣传产品或公司,只是聊一些有趣的技术话题。会长期参与的除了我自己外还有 V2EXLivid 以及我在 LeanCloud 的同事王子亭。我们也会邀请其它嘉宾来临时参与,欢迎在评论里留言建议以后的话题。

第一期的话题是聊聊每个人家里的私有云。为了快速发布,片头片尾音乐等细节就省略了,以后再慢慢迭代。目前发布在以下平台:

或者可以直接播放:

其中提到的软件和网站有:

如何在 Emacs 里做所有事

2020年11月23日 08:00

很长时间之前我在公司内挖了个坑说要做个 Emacs 和 org-mode 的内部分享1。后来一直因为拖延症没有做。上周我和其他同事讨论准备做播客的事,就又想起这件事,周末花了点时间录成一个视频放在 YouTube

这也是我做的第一个视频教程,看完别忘了点击订阅 😁。很快我们也会发布第一期播客,请关注。


  1. 忘了具体原因,大概是有一次受不了新同事在编辑器方面的品味。😏 ↩︎

Remark Ninja: 一个简单的评论系统

2020年9月29日 08:00

几年前一位 LeanCloud 的用户做了一个基于 LeanCloud 的评论系统叫 Valine。后来在中文独立博客,特别是用 Hexo、Jekyll 这样的静态博客框架的用户中很流行。但是最近一两年因为监管变严,无法自己备案域名的国内用户不得不迁移到国际节点。另外免费的开发版服务本意是用于开发测试,而不是需要持续运行的正式项目,所以用户们想了一些比较 hack 的办法来绕过开发版限制。大多数人都会直接 copy 网上搜到的教程,不做任何修改,这样也给 LeanCloud 带来了一些奇怪的问题。比如我们花了不少时间来分析为什么每天总是在同一时刻有大量用户的应用会开始运行定时任务。

我用业余时间断断续续做了一个叫 Remark Ninja 的评论系统,目前到了可以开放给其他人用的状态。我自己的博客、LeanCloud 的博客,以及一些自己发现 Remark Ninja 的第三方网站已经用了一段时间。由于我自己在前端基本只用 React,所以目前只提供了 React 组件Gatsby 是一个基于 React 的功能强大灵活的静态网站框架,我个人很推荐。非 React 的网站可以在局部用 React,或者直接用 RESTful API

除了博客以外,以 thread 方式组织评论的网站都可以使用这个服务。目前提供了以下功能:

  • 基本的评论和管理功能
  • 给站长的新评论提醒
  • 给原评论者的新回复提醒
  • Markdown 支持
  • 没有用 Google Analytics 之类收集用户数据的服务

计划中的功能:

  • 垃圾评论过滤

由于前端水平有限,React 组件做得比较简陋,欢迎提 pull request

Woman、man、camera、TV:如何做一个完整的深度学习应用

2020年8月21日 08:00

前段时间 Trump 的这个采访成为社交媒体焦点的时候,我正好在复习一些 neural network 的材料,于是想到可以用一些新的开源工具做一个识别 woman、man、cemara、TV 的完整应用试试。这个例子足够小,可以在很短时间完成,很适合用来说明如何做一个完整的深度学习应用。完成的应用部署在 https://trump-sim.jishuq.comLeanCloud的一个云引擎实例上)。

做这个应用分为三步:先用一些图片完成模型的训练,然后把模型导出,做一个后端的 API 用来识别图片,再做一个前端用来上传图片和显示结果。

准备训练数据

Jupyter notebook 是个很流行的用来做数据分析和机器学习的交互式环境,它可以把 Markdown 文档和 Python 代码放在一个笔记本里,也可以以图表、图片等友好的方式显示代码的运行结果。这里也会用到 FastAI,它是一个基于 PyTorch,提供了很多网络和文件批量操作便捷接口的开源库。这篇文章就是在 Jupyter notebook 里写的,所以你可以直接 clone 这个 repo、安装依赖、启动 Jupyter notebook。

git clone https://github.com/hjiang/trump-sim-notebook
pip install -r requirements.txt
jupyter notebook

我们还会用到 Bing image search API 来获取做训练的图片,你需要自己注册并申请一个免费的 API KEY。当然,因为搜索到的图片是在很多第三方网站上的,所以你需要能无障碍地访问中国之外的网站。🤷‍♂️

把你的 Bing image search API key 放在项目目录下的 .env 里,以免在代码里泄露出去:

BING_SEARCH_API_KEY=XXXXXXXX....

然后在 Python 里读进来

import os
from dotenv import load_dotenv
load_dotenv()
key = os.getenv('BING_SEARCH_API_KEY')

写一个函数用来搜索图片:

from azure.cognitiveservices.search.imagesearch import ImageSearchClient
from msrest.authentication import CognitiveServicesCredentials
from fastcore.foundation import L

def search_images_bing(key, term, min_sz=128):
 client = ImageSearchClient('https://api.cognitive.microsoft.com', CognitiveServicesCredentials(key))
 return L(client.images.search(query=term, count=150, min_height=min_sz, min_width=min_sz).value)

实际验证一下, 搜一张 Artemis 的图片:

from torchvision.datasets.utils import download_url
from PIL import Image
import fastai2.vision.widgets

results = search_images_bing(key, 'Artemis')
urls = results.attrgot('content_url')
download_url(urls[0], '', 'artemis.jpg')
image = Image.open('artemis.jpg')
image.to_thumb(128, 128)

确认图片下载没问题后,我们把关心的四类图片下载到 /objects 下面的四个目录里。

from fastai2.vision.utils import download_images
from pathlib import Path

object_types = 'woman','man','camera', 'TV'
path = Path('objects')

if not path.exists():
 path.mkdir()
 for o in object_types:
 dest = (path/o)
 dest.mkdir(exist_ok=True)
 results = search_images_bing(key, o)
 download_images(dest, urls=results.attrgot('content_url'))

你可能会看到一些图片下载失败的信息,只要不是太多都可以忽略。网络上有的图片是损坏的,或者是 Python image library 不支持的格式,需要把它们删除。

from fastai2.vision.utils import get_image_files
from fastai2.vision.utils import verify_images

fns = get_image_files(path)
failed = verify_images(fns)
failed.map(Path.unlink);

预处理

在开始训练前,需要告诉 FastAI 如何标注图片,并加载到它的数据结构中。下面的代码完成以下几件事:

  • 使用父目录名(parent_label)来标注每个图片。
  • 保留 20% 的图片作为验证集(validation set),其它的作为训练集(training set)。训练集就是用来训练神经网络的数据,验证集用于衡量训练好的模型在遇到新数据时的准确度。这两个集合不能有重叠。
  • 把图片缩小以提高效率

最后一行代码会显示验证集的前三个图片。

from fastai2.data.block import DataBlock, CategoryBlock
from fastai2.vision.data import ImageBlock
from fastai2.data.transforms import RandomSplitter, parent_label
from fastai2.vision.augment import Resize

objects = DataBlock(
 blocks=(ImageBlock, CategoryBlock),
 get_items=get_image_files,
 splitter=RandomSplitter(valid_pct=0.2, seed=42),
 get_y=parent_label,
 item_tfms=Resize(128))

dls = objects.dataloaders(path)
dls.valid.show_batch(max_n=3, nrows=1)

在做图像识别的时候往往还会对图片做一些随机的缩放、裁剪等变换,以便产生足够多的数据来提高训练效果。可以从下面代码的结果看到对同一个图片做不同变换的结果。

from fastai2.vision.augment import aug_transforms, RandomResizedCrop

objects = objects.new(
 item_tfms=RandomResizedCrop(224, min_scale=0.5),
 batch_tfms=aug_transforms())
dls = objects.dataloaders(path)
dls.train.show_batch(max_n=6, nrows=2, unique=True)

训练数据

接下来终于可以开始训练了。对于图像识别这样的应用场景来说,往往不会从零开始训练一个新的模型,因为有大量的特征是几乎所有应用都需要识别的,比如物体的边缘、阴影、不同颜色形成的模式等。通常的做法是以一个预先训练好的模型为基础(比如这里的 resnet18),用自己的新数据对最后几层进行训练(术语为 fine tune)。在一个多层的神经网络里,越靠前(靠近输入)的层负责识别的特征越具体,而越靠后的层识别的特征越抽象、越接近目的。下面的最后一行代码指训练 4 轮(epoch)。

如果你有 Nvidia 的显卡,在 Linux 下,并且安装了合适的驱动程序的话,下面的代码只需要几秒到十几秒,否则的话就要等待几分钟了。

from fastai2.vision.learner import cnn_learner
from torchvision.models.resnet import resnet18
from fastai2.metrics import error_rate
import fastai2.vision.all as fa_vision

learner = cnn_learner(dls, resnet18, metrics=error_rate)
learner.fine_tune(4)
epoch train_loss valid_loss error_rate time
0 1.928001 0.602853 0.163793 01:16
epoch train_loss valid_loss error_rate time
0 0.550757 0.411835 0.120690 01:42
1 0.463925 0.363945 0.103448 01:46
2 0.372551 0.336122 0.094828 01:44
3 0.314597 0.321349 0.094828 01:44

最后输出的表格里是每一轮里训练集的 loss,验证集的 loss,以及错误率(error rate)。错误率是我们关心的指标,而 loss 是控制训练过程的指标(训练的目标就是让 loss 越来越接近于 0)。需要这两个不同的指标是因为 loss 要满足一些错误率不一定满足的条件,比如对所有参数可导,而错误率不是一个连续函数。loss 越低错误率也越低,但他们之间没有线性关系。这里错误率有差不多 10%,也就是准确率是 90% 左右。

接下来我们要看看验证集里到底有哪些图片识别错了,下面的代码会打印出 confusion matrix。在这个矩阵里,对角线的数字是正确识别的图片数,其它地方的是识别错误的图片数。

from fastai2.interpret import ClassificationInterpretation

interp = ClassificationInterpretation.from_learner(learner)
interp.plot_confusion_matrix()

从输出的矩阵可以看到一共有 11 个错误,其中男女性别错误有 4 个,此外电视和其它几类的混淆也很多。🤔

下面我们把 loss 最高的图片显示出来看看具体有什么问题。

interp.plot_top_losses(12, nrows=4)

输出的结果反映出了从互联网上抓来的数据存在的典型问题:噪声太多。比如电视的搜索结果里有电视遥控器、电视盒子、电视剧海报,还有一些是完全无关的结果。

FastAI 提供了一个 cleaner 可以帮助我们对比较小的数据集做手动清洗。它可以把整个数据集中 loss 最高的图片列出来让用户可以手动修改标签或者删除。

from fastai2.vision.widgets import ImageClassifierCleaner

cleaner = ImageClassifierCleaner(learner)
cleaner

注意 cleaner 只是做标记,你需要用 Python 代码来做实际处理。我通常就直接把有问题的图片标记为 delete 然后删除。

for idx in cleaner.delete(): cleaner.fns[idx].unlink()

清理完之后重复训练的过程。

objects = DataBlock(
 blocks=(ImageBlock, CategoryBlock),
 get_items=get_image_files,
 splitter=RandomSplitter(valid_pct=0.2, seed=42),
 get_y=parent_label,
 item_tfms=Resize(128))

objects = objects.new(
 item_tfms=RandomResizedCrop(224, min_scale=0.5),
 batch_tfms=aug_transforms())
dls = objects.dataloaders(path)
learner = cnn_learner(dls, resnet18, metrics=error_rate)
learner.fine_tune(3)
epoch train_loss valid_loss error_rate time
0 1.663555 0.510397 0.201835 01:11
epoch train_loss valid_loss error_rate time
0 0.458212 0.226866 0.091743 01:32
1 0.358364 0.145286 0.036697 01:31
2 0.281517 0.146477 0.036697 01:32

如果你注意到 error_rate 在后面的 epoch 有上升的话,可以降低 fine_tune 的参数以达到最好的效果。因为如果训练轮数过多,模型会对训练集 over fit,在遇到新数据时错误率会变高。从上面的输出可以看到准确率提高到了 96% 以上。

达到满意的准确率后就可以把模型导出用到线上了。下面这行代码会把模型保存到 export.pkl

learner.export()

后端 API

后端 API 是这个项目最简单的一部分,只有一个 endpoint。加载前面导出的模型,收到新图片时用模型来预测分类就可以。

trump = load_learner('model.pkl')

@app.route('/api/1.0/classify-image', methods=['POST'])
def classify():
 image = request.files['image']
 res = trump.predict(image.read())
 response = jsonify({'result': res[0]})
 response.status_code = 200
 return response

完整的代码在 GitHub 上。按照文档部署到 LeanCloud 云引擎就行。

前端网站

前端也比较简单,只需要一个页面让用户上传照片,在浏览器里把照片缩小然后发送给后端 API 就可以。完整的 React 项目在 GitHub,主要的代码在 App.js。限于篇幅就不详细说明了,只附上一张运行的截图:

给读者的作业

你可能已经注意到上面的后端 API 服务是无状态的,没有存储任何数据,所以其实识别的过程可以在前端完成。如果你有兴趣的话,可以调研一下如何把 PyTorch 模型转化为 JavaScript 可用的模型,尝试在浏览器里直接识别照片。在真实的应用中,这样的方式由于不需要向服务端传输任何数据,可以完美地保护用户隐私,这也是 Apple 在推动的 on-device machine learning 的方向。

图片识别是机器学习可以解决的最简单的一类问题,因为有很多现成的结果可以重用,新的应用即使只有少量训练数据也能达到比较好的效果。还有很多其它类型的问题没有那么容易得到让人满意的结果。LeanCloud 目前正在开发机器学习方面的新产品,以帮助开发者更容易地发掘数据的价值。你如果对此感兴趣,可以关注我们的微博、微信公众号、Twitter,或者注册成为 LeanCloud 用户。不久后我们会公布更多信息,并邀请一些用户试用新产品。

荐书:走出戈壁:我的中美故事

2020年5月24日 08:00

最近看完了 Out of the Gobi: My Story of China and America,要是翻译成中文应该叫「走出戈壁:我的中国和美国故事」。作者单伟建上完小学后就因为文革下放到内蒙古生产建设兵团。文革后,没拿到小学毕业证的他进入了北京外经贸大学,之后在旧金山大学获得了 MBA,在 UC Berkeley 取得博士学位,后来在 University of Pennsylvania 任教。现在他是亚洲最大的私募基金之一 PAG Group 的主席和 CEO。这本书提到了很多在中国大陆比较敏感的话题,所以很可惜在目前回归个人崇拜和个人权威的环境下,应该短期内在中国见不到不经大量删减的中文版了,所以只能看 Kindle 版或者听 Audible 的有声书

这本书讲述了作者前半生的历程:出生在北京一个大院,经历大跃进、大饥荒和文革初期,亲眼见到一些人被残酷地批斗致死,后来被下放到了内蒙古戈壁滩做了六年多徒劳无功的苦活。文革结束后他进入了外经贸大学并争取到了做访问学者的机会,后来在美国一流大学取得多个学位。

这本书里有很多动人的小故事,其中两处大概因为和我自己经历有些共鸣,最打动我。

第一是作者在戈壁滩的六年多里从没放弃学习,没有放弃对更好未来的追求,除了阅读能找到的一切书籍外,还让同被流放的前民航飞行员教他英语。他在去年 UC Berkeley 的一个演讲中说,他看到很多比他更有才华的人因为文革毁掉了一生,周围充满了悲观,但是他不断告诉自己:如果大环境一直很糟糕,自己要在戈壁待一辈子而没有出头之日,他没有谁可怨;但是如果将来发生变化,因为自己没有准备好而失去了改变命运的机会,他只能怪自己。所以他在逆境中也一直在为将来准备。

第二件事是他得到了亚洲基金会赞助到旧金山大学访问,他希望能参与该校的 MBA 课程并争取拿到学位但苦于没有学费。在想尽各种办法无果将要放弃时,他的导师给他带来了好消息:一个匿名捐助人愿意赞助他的学费。于是他成功地注册并开始了 MBA 课程。过了一段时间待他学业有小成之时,他导师告诉他那位匿名赞助人希望约他在旧金山的某个高档餐厅共进晚餐见一面。当导师夫妇身着正装出现在餐厅时,他才猛然意识到原来他们就是捐赠人。多年以后他以导师的名字命名捐赠的奖学金,以帮助更多人追求梦想。

我与作者很有共鸣,因为虽然时代不同,但是我 2002 年以当时所在的学校和背景到美国留学原本也是一件可能性很低的事(更不用说进入一所好学校),这个过程也得到了很多人的帮助,而在美国的 8 年也改变了我很多。我看完这本书后给远在 New Haven 正因为 COVID-19 疫情在家隔离的导师寄了一本作为生日礼物。

这本书在当下的环境有特别的意义。仇恨与冲突都是政客制造出来用来操纵民意、博取支持和所谓合法性的工具。普通人的生活本来离这些东西很远,却往往被煽动起来的狂热毁掉。只有国家走向开放,不同国家的人之间能自由交流,美好的事情才会发生。只愿我们的下一代还能有一个开放、和平的世界。

LeanCloud 开始周期性远程工作了

2020年5月22日 08:00

最近我们决定正式开始每季度一周的全员远程工作,这是我发给公司的邮件:

大家好,

我们在疫情期间已经经历过比较长时间在家办公,以后一段时间,特别是冬季来临时,疫情可能出现反复,还可能出现类似的情况。因为这次疫情,全球的工作方式都可能出现永久性的改变。我们希望每个人都能在远程的条件下有高效工作、协作的习惯和能力,所以决定从 6 月开始试行每个季度全员在家办公一周的政策。除了让大家习惯于远程工作,让我们未来在招聘上有更大的灵活性外,也能满足一些同事兼顾家庭的需求。

具体的试行方案如下:

  • 在 3 月、6 月、9 月、12 月的第二个工作周,全员远程办公。比如这次是从 6 月 8 日开始的那一周。
  • 远程办公的时候和日常的要求没有差别,无法在正常时间工作的同事还是需要照常请假。
  • 请大家保证工作的环境,不要跑到偏僻或网络不好的地方。每个人要为自己的安全负责,公司不为远程办公期间出现的包括交通事故在内的安全问题承担责任。

等试行一段时间后我们再根据结果和大家的反馈进行调整。

谢谢大家。

去年的时候我们就不定时地试验过远程工作周,今年因为疫情年后又进行了比较长时间的在家工作。根据过去的经验和内部的反馈,我们正式定下了周期性远程工作的方案,希望这是个成功的试验,未来能有更多大胆的改变。

树莓派:用 Pi-hole 来保护隐私和过滤广告

2020年4月22日 08:00

我家里的局域网除了电脑、iPad、电视、PS4 等常见设备外,最主要的额外配置是一个 Synology DS416slim NAS 和两个树莓派(Raspberry Pi)。

我的树莓派 3 和树莓派 4

我的树莓派 3 和树莓派 4

大概两年多以前我为了把公司老笔记本上换下的一些 2.5 寸硬盘利用起来,买了一个二手的 DS416slim NAS 用来做局域网的共享存储。不过它只有 512M 内存,CPU 也很弱,除了做文件服务器外跑不了太多东西。后来我陆续增加了 Raspberry Pi 3B 和 Raspberry Pi 4B 用来支持其他服务。

Raspberry Pi 4 有 4G 内存和千兆网口,可以跑比较多东西。我用 Clash 配置分流规则并配合 iptables 把它作为局域网内其他设备的网关,除此外还运行着 Radarr、Sonarr 等服务,mount 了 NAS 用于数据存储。

Raspberry Pi 3 上运行的 pi-hole 是我想重点介绍的,它在国外的 Raspberry Pi 用户中很流行,但国内用户相对少。Pi-hole 是一个用于屏蔽广告和保护隐私的 DNS server,它会把用于提供广告、追踪效果、收集用户信息或存在恶意软件的域名屏蔽。很多人都已经用浏览器插件达到同样目的,但是因为 pi-hole 是在 DNS 层面屏蔽,所以对电视等各种联网的设备同样有效,更适用于家庭局域网。

Pi-hole 的界面

Pi-hole 的界面

除了屏蔽域名外,pi-hole 也会把内网域名解析的统计数据可视化,让人更清楚地理解家里的网络活动,比如访问次数最多的域名以及被屏蔽次数最多的域名和设备。你知道你的智能电视即使在关机状态下也在不断发送请求吗?根据 pi-hole 统计的信息,我卸载了 Android TV 上某国内大厂旗下的视频应用,家里的请求数下降了 90% 以上。

访问最多的被屏蔽域名

访问最多的被屏蔽域名

Pi-hole 还附带了一些其他相关功能,比如它也有一个 DHCP 服务。如果你的路由器自带的 DHCP 配置比较简单,无法实现一些特殊需求(比如和我一样你想用路由器之外的另一个设备做网关),可以用 pi-hole 来提供 DHCP,这样就能控制那些不支持手动配置网络的设备了。

爱国指南

2020年2月8日 08:00

在我加入 Google 几个月后,北京奥运在全球的火炬传递活动开始了。很多人可能都对那段时间的事有印象:在几周前拉萨刚发生了暴乱,很多无辜的生命消失了,并且 CNN 等媒体使用了不实的图片进行了有偏见的报道;在巴黎,支持藏独的示威者多次粗野地干扰奥运火炬传递,还有人从坐在轮椅上的运动员手中把火炬夺走。巴黎的下一站就是旧金山,在湾区工作的中国人大多都请假去了旧金山以支持火炬传递,避免巴黎的事件重演。当天 Google 一个餐厅的菜单上出现了一个甜点叫 Free Tibet Goji Berry。由于中国员工大多都在旧金山,没什么人注意到。在第二天这个名称又再次出现在菜单上,我看到中国员工的邮件列表里有人发照片开始讨论这件事。Google 的人力资源副总裁 Laszlo Bock 后来写了一本书叫 Work Rules(中文名「重新定义团队」),其中提到这件事,中文版里是这么翻译的:

菜单贴出来后,一名谷歌人给埃里克1写了一封邮件,主要内容如下:“这是从今天的菜单中摘录的。如果公司不给出合理的解释或行动,我就辞职抗议。”

这名谷歌人将他的留言抄送了几个小范围的邮件列表,而后一位工程师又将邮件转发给全公司范围的邮件列表,作为杂事讨论。

后来这个话题创造了最快达到一百条回应的记录,成为第一条突破 1000 条回应的主题。有一名谷歌人数过,总共有 1300 多封关于这个主题的电子邮件。

他说的那个给 CEO 发邮件的谷歌人就是我。除了一些细节以及没有考虑事件背景外,他在书中对于这件事的描述基本是准确的。其实我在邮件中没有用「辞职抗议」这样的语言,而是说「我不愿意在这样一家公司工作」,不过意思差别不大。

当时 Bock 和参与讨论的很多人都对这件事的本质有一些误解。这件事本质并不是公司里有一些人对西藏持某种观点,而大部分中国员工对此持相反的观点,所以双方发生了矛盾。我在美国生活的八年里,有各种背景的朋友和熟人,包括美国人、中国人、欧洲人、也包括台湾人、香港人,政治上的差异从没有成为障碍,大家通常也并不热衷于政治。我之所以反对当时发生在 Google 的事,是因为它并不是针对某个政府的抗议行为,而是在当时特定背景下针对公司内部一部分员工的有些挑衅的行为。Google 之外的人不会知道这件事,会看到并注意到这个菜名的,主要是 Google 的中国员工。这位工作人员利用工作上得到的权力来向 Google 的员工单向地推广他的政治观点,这是不应该发生在工作环境中的,不是适用于言论自由的问题。

现在回过头看,我还是认为应该提出异议,但是会采用一种更成熟更职业的方式。

之所以说起这件往事,是希望避免有人看了我写的东西质疑我不爱国。我想我的爱国程度应该还是高于平均水平的吧,毕竟网上大部分声音很大很爱国的人士,实际上愿意为之付出的个人代价是很低的。

虽然爱国这个概念我们从小就被灌输,但是大部分人没有想过爱国爱的到底是什么。拿五四运动来说,习主席对它的定性是:

一场以先进青年知识分子为先锋、广大人民群众参加的彻底反帝反封建的伟大爱国革命运动

当时不但没有中华人民共和国,中国共产党都还没有成立,而五四运动反对的是当时执政的北洋军阀政府的外交政策。可见爱国不是爱政府,也不是爱特定政党,并且还有可能是反对政府。

那爱国爱的是什么,国家是什么呢?

国家和公司、家庭这样的集体名词一样,并不是物理世界客观存在的东西。它只是人意识中的一个概念、一个想法,只有很多人的意识中认同这样一个概念,并且对它的范围理解基本一致,这个国家才存在。很显然,爱一个概念是说不通的,所以爱国归根到底应该是爱认同属于一个国家范围的这群人,希望包括自己在内的这群人过得更好。一个人是否爱国,在于他的言行是否有利于他所属的这个国家人民的福祉,是超越任何政府或政党的。所以如果仅仅因为一个人批评政府而指责他不爱国是很荒谬的事。

政府和国家是两个完全不同的概念。政府是一个国家的人通过税收来维持的机构,它为这个国家提供立法、执法、行政等公共服务。政府本身是不产生收入的,上至主席总理下至窗口办事人员都要靠税收供养,政府的其它支出也来自于税收或者基于税收的收入(比如投资和借款)。一个国家在不同的历史阶段会因为内部或外部的各种原因建立不同的政府。很多人会有种错觉,似乎补贴之类的东西是政府给的恩惠,其实你从政府得到的所有东西本质上都来自于其他公民。

互联网上的争论都容易变成互相贴标签的辩论,导致观点越来越极端。有很多人盲目或者说本能地批评他认为不爱国的人,也有人认为所谓爱国主义都是骗人的宣传。客观地说,爱国主义是有必要的。上千万上亿人要能进行大规模的协作就必须有一种一致的集体认同感,这种认同感就逐渐形成了前面所说的国家概念。爱国指的是你的国家对你来说是独特的,你和这个国家之间有特别的关系和义务。同样地,你和工作的公司之间、和家庭之间、和朋友之间、和你自己都有特别的关系和义务。这些不同的义务之间有时会产生冲突,需要你去平衡和作出选择。人生本来就是复杂的,没有简单的总是正确的答案。如果有人告诉你「你的国家是至高无上的,国家利益高于一切,在任何冲突里都必须选择国家」,这不是爱国主义而是法西斯主义。法西斯主义把生命的意义简单化,把选择从个人手中拿走了。这也是它容易在经济发展不好的时候,容易在对现实失望和迷茫的群体中生根发芽的原因。

这篇文章说的都是再浅显不过的道理,没什么新意,不过因为过去一年里发生了一些影响很大的事,我还是认为值得说一说。如果本文的读者看后能留下一些印象,我觉得以下两点最重要:

第一,政府的权力是需要监督和制衡的。权力导致腐败,绝对的权力导致绝对的腐败,这个简单的道理是人性的一部分。监督和制衡来自于内部和外部的压力。对于一个健康的社会来说,内部的压力应该起主要作用。但是当一个国家缺乏有效的内部制衡机制来约束权力的时候,多一些外部压力往往对于普通人来说是好事。所以下次当你听到对政府的批评时,不管来自于哪里,在开始习惯性的辩护前应该先仔细想想这些批评造成的结果会是什么,对你我这样的普通人来说有利还是有弊。别忘了爱国归根到底是要维护和提高人的权利。

第二,当其他人在为一个群体争取更多权利时,即使你不属于那个群体,往往也会成为受益者。近处的例子没法说,我就举个远一点的例子。我们学历史都学过美国国会在 1882 年通过了排华法案(Chinese Exclusion Act),禁止中国人移民到美国,这也是美国第一个禁止特定族群任何成员移民的法案。这种基于种族的歧视性移民政策直到 1965 年因为民权运动才被基本完全废除(Immigration and Nationality Act)。虽然美国民权运动主要是由黑人发起的,但是惠及了所有当时在美国的华人,以及后来移民到美国的华人。


  1. 当时 Google 的 CEO,Eric Schmidt ↩︎

我在 2019 年觉得不错的几个习惯

2020年1月2日 08:00

2019 年已经过去了,我想分享几个我个人认为比较好的习惯。

远离微信朋友圈(和类似的产品)

其实这不算是今年开始的。我很早就把微信朋友圈功能关闭了,在更早的时候就已经不上微博了。在近几年我除了偶尔转发本公司的公众号文章外,几乎没有看过朋友圈。微信对我来说是个因为工作不得不用的 IM 工具,而朋友圈就和 /dev/null 一样是个 write-only 的地方。这样并没有使我错过任何有价值的信息。我订阅了 WSJ 和 Bloomberg 这样的『传统媒体』电子版,值得关注的事一般都能看得到比较深入和专业的报道。我的朋友圈里以投资人和创业者居多,所以被转来转去的内容最多的就是各种 PR 稿和软文。其实大部分人的朋友圈都是如此。除了公司以外,个人也是有做 PR 的需求的1,每个人发在社交媒体上的东西都是希望别人看到的自己,这一点机构和个体并没有太大区别,所以一个人最喜欢发到朋友圈的东西所反映的往往不是他拥有的,而是他缺乏的。虽然我本来就不是容易因为别人而产生焦虑感的人,不过自从关掉朋友圈之后仍然能感受到内心更加平静,少受他人影响。

在信息过剩的时代,过滤低价值信息和寻找想要的信息同样重要。很多人沉迷社交媒体,一有空就忍不住打开微信刷来刷去,因为人的天性是懒的,而被动地接受信息是件毫不费力的事。自动推荐的内容以及你的社交圈出现的内容让你的眼界局限于所处的圈子里,往往会加强既有的偏见。主动地思考自己不知道什么、需要知道什么、寻找高质量的内容是很重要的。

听有声书

这是今年开始的新习惯,我为此写了一篇单独的文章:怎样利用好路上的时间。我看书比较慢,所以对于传记、回忆录等不太需要停下来思考的书,用 Audible 的手机应用听比在 Kindle 上看要快一些。另外也可以把一些不能用眼睛看书的时间利用起来。

坚持培养与工作和 IT 技术无关的兴趣

我一直觉得培养和保持一些与日常工作差别很大的兴趣是很重要的,因为这会使身体和大脑的不同部分得到锻炼。

近年来新增的爱好里坚持时间最久花的时间最多的是小提琴,2019 年是第三年了。一开始从 YouTube 上的一个系列视频开始,后来订阅了 Red Desert Violinthe Violin Lab,再后来找了一位线下的老师。很多朋友听说我在 36 岁高龄开始学小提琴都比较惊讶,因为在大部分人眼中这些似乎是小孩子的事情。然而这可能是过去几年里带给我最多快乐的一件事。除了出差以外,我每天晚上都会花一些时间在上面。

我学小提琴有两个原因。第一,其实我刚上初中的时候就想过学小提琴。我父亲认为什么都是能自学的,所以听我说后就买了把琴(可能还是成人的)和一本书给我,结果可想而知。其实小提琴可以自学也不算错,我这一次开始也是根据视频教程自学,半年多后第一次和老师上课时他说我自学得还不错,没明显错误。只是小的时候没有互联网,没有自学的资源罢了。第二,音乐是个我本来所知很少的领域,所以一直有学一样乐器来补上这个缺口的想法。而既然要学就学一门比较难的,乐趣和成就感也会更多。

这一年也新学了一些东西,比如国际象棋,开始捡起了过去的一些爱好,比如绘画。不过这些都属于可以有空时做一下的事,不需要每天练习来保持,也就算不上习惯了。


  1. 比如这篇文章就是如此。我就只说了好习惯没有说坏习惯。 ↩︎

WeWork 的兴衰和创投的游戏

2019年10月30日 08:00

前段时间的一个大新闻是估值最高的创业公司之一 WeWork 上市失败被投资人软银接盘控股,CEO Adam Neumann 离开。软银接盘 WeWork 并购买 Neumann 手中的股份时以 50 亿美元计算公司的估值1,而 1 月软银最近一轮投资时给出的估值是 470 亿美元,也就是说这家公司在几个月时间里估值降到了上一轮融资的 17%,也相当于四年前估值的一半。就在几个月前纽交所的主席和纳斯达克的 CEO 还上门拜访了 Neumann,他们都想争取让 WeWork 在自己的交易所上市。Neumann 向他们提出希望在交易所的餐厅禁用塑料杯和禁食肉类。纳斯达克甚至许诺为致力可持续发展的公司建立一个以 WeWork 命名的「We 50」指数。

这件事无论是看公司/创始人还是看投资人都有很多戏剧性的成分。

WeWork 的外部危机始于它提交的上市文件。这份文件公开的信息让很多投资人对 WeWork 的高额亏损开始担忧,并且很多关键信息没有清楚披露,使得媒体开始挖掘一些问题。首先是 WeWork 和创始人 Neumann 之间有大量的自我交易和利益冲突。比如 Neumann 先注册了 We 的商标,然后以 600 万美元卖给公司;他从公司直接借款或者用公司股份担保贷款数亿美元用于购买房产,再出租给 WeWork 作为办公场所;他提前行权用的 3 亿多美元也是从公司借的,也就是说用公司的钱给自己买了更多股份。Neumann 的很多亲戚都在公司担任高管,包括他妻子以及他们的表兄弟。WeWork 一向把自己的品牌和可持续发展、对环境友好联系在一起,所以她妻子作为首席品牌官坚持他们在上市过程中提交的文件都必须用再生纸打印,然而她对打印效果吹毛求疵,不断要求重新打印,浪费了大量纸张,以至于他们的打印服务商都拒绝合作,让他们另请高明。WeWork 在准备上市文件的时候,Neumann 在马尔代夫度假,因为不想中断度假,他让遇到问题的下属到马尔代夫见他。

New York 杂志的一篇长文里有一段对 Neumann 第一次见孙正义的描述。孙正义在 WeWork 总部见到 Neumann,说他只有 12 分钟参观公司。之后他邀请 Neumann 一起坐到他的车里,在 iPad 上给出了 44 亿美元的投资框架。孙正义让 Neumann 把 WeWork 做到他原计划的 10 倍大,并且告诉他要学习一点人生经验「疯狂比聪明更重要,WeWork 还不够疯狂」。他认为 WeWork 能值数千亿美元。Neumann 因为疯狂而得到了丰厚回报,后来确实越来越疯狂了,但很显然孙正义没有得到他想要的结果。

Neumann 离开 WeWork 时,员工的期权一文不值,软银却为购买股份支付了他十亿美元,并另外提供了他个人 5 亿美元贷款。一位接受媒体采访的 WeWork 员工说「一家以 We 命名的公司却是一个人获取了巨大利益,真是讽刺」。

Matt Levin 在 Bloomberg 的专栏发表的文章里说:

他一把火烧掉了软银的 100 亿美元,然后还去找他们要了 10% 的佣金。绝对是一个商界传奇。

软银在这件事中也很有意思。

很多人看了新闻都有一个疑问。在 WeWork 上市失败后软银拿出了 95 亿美元救急(包括购买股份和贷款),加上之前已经投的钱一共有将近 200 亿美元。而如果这家公司现在只值 50 亿美元,为什么不及时止损,而把更多的钱投进去呢?WeWork IPO 出现问题的时候,软银的第二期 vision fund 正在募资,孙正义正在中东和沙特王子们开会让他们投钱,这大概是主要原因之一吧。毕竟 WeWork (曾)是第一期 vision fund 的明星项目,要是这个时候清算了,实在是太难看,留有希望总是好的。有趣的一个细节是,软银特别声明虽然再次注资后拥有 WeWork 80% 的部分,但是并不在董事会占多数席位,并不算控制公司,所以 WeWork 并不是软银的子公司。这是通过放弃一部分投票权而避免把 WeWork 有高额亏损的财报合并到软银的财报里,因为 SEC 规定在衡量是否是子公司时看的是是否持有一半以上有投票权的股份。这或许是软银的接盘预算里多了 20 亿美元给其它股东的原因 — 以便让他们配合这样的安排。

在让 Neumann 离开后,软银说要让 WeWork 重新专注于租赁办公场所的核心业务,潜台词是 WeWork 的现状主要是前 CEO 选择了错误的发展路线,不够专注导致的。可是如果软银是把 WeWork 看作一个中端办公场所房东,整个美国二级市场的办公房地产基金市值加起来也只有一千亿美元,即使 WeWork 把它们都兼并了,价值也只是上轮估值的两倍左右,没有投资人会投最高回报率是两倍的项目。但是如果它是「一种意识形态」,「世界上第一个实体社交网络」,「容纳人们工作生活的方方面面」,那么价值简直无可限量,以后得相当于好几个 Facebook,只有这样才支撑得起 470 亿美元的估值。很显然,软银在上一轮投资时是认可 Neumann 所描绘的景愿的。然而,是这样吗?私募基金难道不是应该比二级市场更有耐心和长期眼光的吗,为什么一被二级市场质疑就改弦易辙了呢?事实是软银关心的并不是 Neumann 描绘的美景是否能实现,而是二级市场是否为它买单。

WeWork 的事因为金额巨大,引起很大震动,但它不是偶然的个例。创始人和投资人在其中表现出的特点对这个行业来说是很有代表性的。

我认识的创始人里有的在拿到 A 轮过桥贷款时就开始想办法把钱放到自己口袋里,谁知道他们在拿到投资后会做什么呢,更别说到上市的阶段了。所以你会看到有很多创业者虽然公司没有做得很成功,但是自己已经开始做天使投资了。并不是所有人都靠第一次成功来得到第一桶金,至少不是传统意义上的成功。

和一定数量的 VC 聊过的人大多能感觉到大部分 VC 看企业只看规模和销售额,几乎不在乎利润,尤其是美元基金的投资人,毕竟在美国上市只要有规模,利润不重要。只要能说某某公司在某某行业市场占有率第一,不管亏损多少,总会有足够的人卖单。所以能不断看到快速扩大规模的以价格为核心竞争力的公司,大致的发展道路是融资 → 以补贴和低价吸引用户 → 融更多的钱 → 更多的补贴和更低的价格吸引更多用户。只要能快速扩张,投资人就会愿意投钱进来,一些幸运的公司能把这种模式一直运转到上市后,直到规模扩大到了难以再大,不能完全再靠讲故事,而业务本身又没发生能突然盈利的奇迹时,问题才暴露出来,但入场早的人已经赚得盆满钵满了。如果 Neumann 稍微「正常」一些,上市前没有那么多问题暴露出来,WeWork 成功上市了,那么一年后被套牢的就是公开市场的股民,不是软银了。这种类型的公司在遇到质疑时最喜欢自比亚马逊,喜欢说你们看亚马逊也是到多少年后才盈利。亚马逊这类公司是在盈利和更快地发展之间做选择,另一类公司是没有选择。大部分公司属于后者。

华尔街日报的文章里有个图比较了几家近几年高调上市的独角兽上市后的股价,趋势一目了然。中国几家这两三年在美国上市的公司的情况我也好奇看了一下,不过就不好意思指名道姓了,反正股价和财报都是公开的,有兴趣的人可以自己去了解。

Unicorn post-IPO performance

Unicorn post-IPO performance

我自己在互联网行业多年的体会是,这个行业进入成熟的阶段后也快速地世俗化,早期的创新精神和理想主义渐渐被机械和无情的商业机器所替代。过去几年出现了一大批职业创业者,有的人几乎每年做一个新创业项目,从移动应用到区块链,到电子烟,什么概念火就做什么。而资方也形成了成熟的产业链,FA 关心的是把项目推介给资方拿佣金;天使投资人关心的是在 A 轮、B 轮卖给基金;C 轮后的基金和投行关心的是如何让二级市场的股民去接盘。创业者和投资人之间对各自的小算盘其实都很清楚,但毕竟总的来说是在合谋一件事,所以只要没大的冲突也都心照不宣。这个产业链就像一个大机器,不断把社会财富吸收到少数人手里。


  1. 大部分媒体报道 80 亿美元,但由于其中 30 亿美元是给其它股东的,所以其实公司的估值是 50 亿美元。 ↩︎

计算机专业学生该如何提高自己

2019年9月10日 08:00

这是为一个知乎问题的答案。

大学是系统性地学习基本原理的时候,没有必要追逐最新最流行的编程语言和开发框架,毕竟到了毕业工作的时候往往又有很多新的变化。把基本的知识体系掌握好会终身受用无穷。大学只是提供环境和条件,学习的方向和重点需要自己把握。下面这几方面对于本科生是尤其重要的。

计算机体系结构:这是理解计算机工作原理的基础,即使以后只做软件方面的工作,这方面的知识也是至关重要的。一方面软件运行在硬件上,理解计算机各部分的工作原理以及他们之间的通信机制对于代码优化和系统级的软件开发都是必须的;另一方面硬件只不过是石化的软件,软硬件不过是逻辑组合的不同实现方式而已。很多美国大学的这门课程都会让学生用代码把一台虚拟计算机实现出来,这确实是个好办法。

操作系统:所有做软件开发的人都需要对进程、线程、内存的管理有清楚的理解。操作系统为在计算机上运行多个应用程序提供基础,Docker 等这些现在流行的技术也无非是把操作系统提供的机制和工具进行了封装而已。

编译原理:编译原理让你理解程序在运行的时候具体在做什么。只有能把高级语言的每条语句在大脑中映射到 CPU、寄存器、内存里栈和堆的具体操作,才能写出高效、错误少的代码。每个计算机专业的学生都应该尝试自己实现一个语言。

一门系统编程语言(C/C++):C 语言仍然是最好的用来学习编程的语言。它在语言层面的概念比较少,容易比较快地把注意力从语法转到程序设计上。同时由于 C 在标准库里没有提供现成的容器,所以不可避免地需要学习如何实现基本的数据结构:不同类型的链表、队列、字典等,在实现这些结构的时候又不可避免地要熟悉指针操作和递归。虽然在日常软件开发中很少会需要自己实现这些容器,但是理解他们的实现和性能特点可以避免你用 Java 之类的语言时什么都用 Vector 来做。在熟悉 C 之后可以学一下 C++。C++ 之所以复杂,在于它将控制权交到了程序员手里。其他的主流面向对象语言里之所以没有 virtual function 等 C++ 特有的概念,是因为在语言层面已经帮程序员做了选择,虽然降低了语言复杂度,但是也导致不能总得到最好的性能。深入学习 C++ 可以让程序员了解面向对象特性的底层实现以及它们在编译时和运行时的开销,这对于使用其他语言也是很有帮助的。

算法、数据结构:如果你将来做系统软件开发,将会需要自己做一些算法的实现。即使是做应用软件开发,也需要在这方面有足够的知识来权衡不同的选择。虽然现代的高级语言大多提供了丰富的容器、算法函数、中间件等等,只有理解他们的性能特征和资源代价才能作出好的选择。

一门学科的知识体系就好像大脑中的一个架子,一开始搭建它的时候会感觉是个很慢的过程。但是如果你耐心地把它建立起来,再学习新的东西就会知道该把新吸收的知识放在什么位置,它和架子上现有的内容是如何联系的,在理解、记忆、应用上都会事半功倍。

怎样利用好路上的时间

2019年9月7日 08:00

大部分人在上下班路上做得最多的事就是看手机。盯着手机屏幕的时间久了很影响视力,而且往往会养成走着路也在看手机的习惯,很危险。我最近几个月在上下班和出差的路上都在听 Audible 上的有声书,这是个能把路上的时间利用起来吸收有益的信息,又不用老看着屏幕的方式。

Audible 是 Amazon 旗下的公司,所以它的移动应用里可以听 Kindle 电子书的有声版,此外有一个专门为听者设计的 the Great Courses 系列是非常值得推荐的。这是请一些全球知名的学者录制的各个领域的系列讲座,每个讲座一般有几十集,大约半小时一集。以下是我过去这段时间听的几个:

The Fall and Rise of China: 主讲人是 UCLA 的 Richard Baum 教授。他因为年轻时看了 Edgar Snow 写的 Red Star Over China 而对中国近现代史发生强烈的兴趣,此后他的博士论文和整个学术生涯都在研究中国。比较有意思的是他的博士论文及之后的很多学术成果都建立在他从台湾偷出去的国民党间谍从大陆获取的绝密档案上。除了开头简要介绍中国古代史外,主要内容覆盖了从鸦片战争前到 2009 年这两百年。他在文革结束后到了中国约五十次,待了相当长的时间,中文很好,和毛泽东之后的历任中国领导人都会见过,近距离见证了很多历史时刻。希拉里在竞选总统时请他做中国问题顾问,不过他因为不认同希拉里处理中国问题的方式很快辞职了。他作为一个有很高声誉的学者对历史的叙述可以说是很客观的,对于只学过中国教科书版本历史的人来说听一听他的讲座是很有价值的。

How to Listen to and Understand Great Music: 这个讲座介绍了从古希腊和罗马到现代的整个西方音乐史,包括每个时代一些有代表性的作曲家和作品,重点是巴洛克及之后我们现在所理解的西方器乐发展起来的这段时间。我听这个讲座主要和个人兴趣有关,所以不一定每个人都会喜欢。因为前一段时间正好看了讲绘画和建筑史的 The Story of Art,很容易和每个时代音乐上的变化对应上,对于欧洲社会的变迁会有更全面的了解。

Redefining Reality: The Intellectual Implications of Modern Science:这个讲座也可以看作是讲历史的,讲的是近现代的科学史,数学、相对论、量子力学、天文学、生物、医学、心理学、社会学、经济学等等各方面的重要成果都覆盖到了。是个全面了解近代科学各个领域发展的好方式。

荐书:Educated - 一部震撼人心的回忆录

2019年7月11日 08:00

Educated 在去年出版后很快就出名了,Obama、Bill Gates 都推荐过,不过我最近才看完。这本书似乎还没有中文版,不过除了 Kindle 版外在国内也能买到纸质原版

作者 Tara Westover 讲述了她从童年到博士毕业的故事,主题涵盖家庭、忠诚和教育。她出生在一个美国中部山区的摩门教家庭,父亲经营一个金属废品处理场,母亲给人接生和出售草药,有七个子女。他们相信世界末日即将降临,所以一直都在囤积各种东西以确保生活的自给自足。她父亲有精神分裂症,她从小也经常受一个哥哥欺凌虐待并且得不到父母的支持。由于父母相信阴谋论,不相信联邦政府,所以他们家人生病、受伤、生小孩从来不去医院,完全在家里靠草药解决问题。因为父亲认为学校教育是政府对人的洗脑,她在 17 岁前都没有接受过正式教育,一直在父亲的废品场做帮手。

因为过去经历的点点滴滴(比如小时候在一个后来也出去上学的哥哥的房间听他收集的唱片)使她对外面的世界产生了强烈的好奇和向往,在 16 岁时她决定靠自学尝试考大学。之后她被犹他州的 BYU(Brigham Young University)录取,并在期间得到了 Bill Gates 设立的奖学金而到剑桥学习。她最终在剑桥大学取得了历史学博士学位。由于观念与家庭格格不入,她在漫长的内心挣扎后与大部分家庭成员决裂不再往来。

这本书的震撼之处一方面来自于 Tara 的独特经历,另一方面来自于她写作的诚实。书中有大量她饱受兄长欺凌的描写,也讲述了很多尴尬的经历,比如她刚开始在 BYU 上课时由于说不知道大屠杀而被老师和同学误解为纳粹种族主义者。Tera 毫不介意向读者揭示她曾经有多幼稚和无知,这是大部分人难以做到的。

从作者的经历看,一方面可能学校教育不像很多人想的那么重要。毕竟一个在大学前都没有上过学的人也在剑桥得到了博士,并且他们家 7 个孩子里 3 个到外面求学的全都得到了博士学位,完全超出了「正常」家庭的教育水平。另一方面,学校教育又或许不像有的人想的那么没用,毕竟是大学给作者开启了全新的可能性,让她知道了原来除了从小她父亲作为事实教给她的东西外,原来世界上可以有那么多不同观点,可以通过研究和思考来形成自己独立的信念。Tara 曾在一个访谈中说,虽然她后来的价值观和父母越走越远,但是父亲从小教她的一个观念一直伴随着她:「你可以教会自己任何东西」。

教育包括「教」和「学」两面,前者只有通过后者才能起作用。一个对写作没有兴趣的人即使有一个得过诺贝尔文学奖的老师也成不了作家;对一个有强烈兴趣的人,给他一部优秀作品,他就能从中得到很大提高。很多家长对待子女教育时往往把注意力都放在「教」的因素上,孩子一定要上好的学校,要有好的老师。小孩从幼儿园开始就每个阶段都在为下一次考试和择校做准备。然而如果学习是受考试而不是兴趣和好奇心驱动,从小就在一种对「学」很焦虑的环境长大,在来自长辈和外部的压力失效之后是很难保持动力的。教育是自我发现的过程,我想无论是在考虑自己的持续教育还是考虑下一代的教育时都需要记住这个本质。

大部分人都不会有 Tara 那样的经历,但是每个人都有从小仰视父母到长大逐步形成自己独立观念的历程,Tara 的成长不过是这个过程的极端版本。相信每个人都能从这部回忆录得到启发。

入门 React hooks + 后端集成

2019年4月12日 08:00

2019 年 2 月发布的 React 16.8 正式引入了 hook 的功能。它使得 function 组件也像 class 组件一样能维护状态,所有的组件都可以写成函数的形式,比起原有的以 class 的多个方法来维护组件生命周期的方式,简化了代码,也基本消除了因为 this 绑定的问题造成的难以发现的 bug。这篇文章就介绍一下最常用的 state hook,以及在这种新的方式下怎么与后端 API 通讯。

本文以一个管理任务的 Todo list 应用为例,可以增加新的任务,点击可以把任务标记为完成。部署好的效果可以在这里看到,代码在这个 GitHub repo。这个 demo 使用 LeanCloud 作为存储数据的后端,用的是一个 LeanCloud 开发版应用,所以可能遇到请求数超限的情况,建议在本地运行并替换进自己的 AppId 和 AppKey。

这个应用只有一个叫 App 的组件:

function App() {
 const [inputValue, setInputValue] = useState('');
 const [todos, setTodos] = useState(undefined);
 const [error, setError] = useState('');

开头先定义了它使用的状态。useState 的参数是状态的初始值,它会返回一对结果:用来读取这个状态的一个只读引用,以及一个设置状态新值的函数。这里创建了三个状态:

  • inputValue: 输入新任务的 <input> 元素的当前值
  • todos: 当前显示的任务。这里初始值设为 undefined 表示尚未加载,而 [] 则意味着已经加载过,但是为空。
  • error: 当前显示的状态信息

每次这个组件被重新渲染时,App() 这个函数都会被调用。每个 useState 只有第一次被调用时返回的状态是初始值,之后每次都会返回已经记住的当前值。这里有三个状态,React 是用调用 useState 的顺序来区分他们。可以理解为 App() 的所有状态存储在一个数组里,第一个 useState() 返回的是第一个状态,第二个 useState() 返回的是第二个状态,以此类推。所以使用 hook 必须保证这个组件函数每次运行中:

  1. useState() 的调用次数必须是一样的。
  2. 与各状态对应的 useState()的调用顺序是一样的。

这就意味着 useState() 的调用不能放在条件分支或循环中。为了避免出错,最好把所有 useState() 调用放在函数开头。

接下来是添加一个任务的函数 addTodo

 const addTodo = () => {
 saveTodo(inputValue).then(todo => {
 setInputValue('');
 setTodos(prev => [todo].concat(prev));
 }).catch(setError);
 };

这里 saveTodo() 是一个 helper 函数,会在文末介绍。在后端保存了新任务后,会把输入清空,并把新的任务加到用于显示的任务列表的前面。这里使用了设置新状态的两种方式:setInputValue('') 直接设置新值,setTodos(prev => [todo].concat(prev)) 是传递一个更新状态的函数。后者通常在新状态依赖于旧状态的时候使用。

再下一步检查任务列表有没有初始化过,如果没有的话,就查询后端数据把它初始化:

 if (todos === undefined) {
 loadTodos().then(setTodos).catch(setError);
 }

然后是定义如何切换任务的完成状态:

 const toggle = item => {
 item.set('finished', !item.get('finished'));
 item.save()
 .then(() => setTodos(prev => prev.slice(0)))
 .catch(setError);
 };

这里值得注意的是在设置 todos 的新值的时候用 prev.slice(0) 把这个数组复制了一份。这是因为切换一个任务的状态只是这个数组中一个元素的一个属性发生了改变。在使用 hook 更新状态时,作为一个优化,React 会用 Object.is() 比较新老状态,如果在这个语义下它们相等,React 会认为状态没有改变而不重新渲染这个组件。Object.is() 认为满足以下条件之一的两个值相等:

  • 两个都是 undefined
  • 两个都是 null
  • 两个都是 true 或者都是 false
  • 两个都是字符串并且有相同的长度,相同的字符以相同的顺序出现
  • 两个是同一个对象
  • 两个都是数字并且:
    • 都是 +0
    • 都是 -0
    • 都是 NaN
    • 都不是零或 NaN 并有相同的值

这对于数字、布尔、字符串这样 immutable 的简单类型来说不是问题,但是对于数组和对象来说,就意味着只有传递一个新的对象才会触发渲染。好在这里 slice(0) 只是做一个浅拷贝,没有复制数组引用的对象,所以代价是比较低的。

最后是把上面👆的一切放到渲染结果里:

 return (
 <div className={AppStyles.app}>
 <div className={AppStyles.error}>{error.toString()}</div>
 <div className={AppStyles.add}>
 <input placeholder="What to do next?" value={inputValue}
 onChange={e => setInputValue(e.target.value)}
 onKeyUp={e => { if (e.keyCode === 13) addTodo(); } } />
 <input type="button" value="↩" />
 </div>
 <ul>
 {todos && todos.map(item =>
 <li key={item.getObjectId()}
 onClick={() => toggle(item)}
 data-finished={item.get('finished')}>
 {item.get('content')}
 </li> )}
 </ul>
 </div>
 );
}

下面两个函数是 App() 里用到的从 LeanCloud 更新和加载数据的 saveTodo()loadTodos()

function saveTodo(content) {
 const Todo = LC.Object.extend('Todo');
 const todo = new Todo();
 todo.set('content', content);
 todo.set('finished', false);
 return todo.save();
}

function loadTodos() {
 const query = new LC.Query('Todo');
 query.equalTo('finished', false);
 query.limit(20);
 query.descending('createdAt');
 return query.find();
}

有的人认为 React 的 hook 让 React 变得更加「函数式」了。我的看法恰恰相反。把什么都变成了 JavaScript 的 function 并不意味着程序更 functional 了。在有 hook 之前,React 的组件分为 class 组件和 function 组件,本来 function 组件可以看作是纯函数,传递进去的 props 能决定渲染结果,是 functional 的。有了 hook 之后 function 也可以有状态了,所以变成了披着 function 外衣的 object。如果不仔细了解实现机制的话,很容易产生一些微妙的 bug。不过也不可否认,使用 hook 开发简化了组件生命周期的概念,减少了代码量,在开发者熟悉了这个新模式之后,还是一个很有价值的改变。

荐书:Bad Blood,创业公司的谎言与欺诈

2019年1月15日 08:00

去年 12 月看完了 Bad Blood,本来想写一篇推荐作为 2018 的最后一篇博客的。因为拖延症变成了 2019 的第一篇。

这本书的作者是两次获得普利策奖的华尔街日报记者 John Carreyrou,讲的是曾经红极一时硅谷创业公司 Theranos 和它的创始人 Elizabeth Holmes 的故事。

Elizabeth Holmes 创建 Theranos 的时候只有 19 岁,她号称发明了突破性的验血技术,只要在指尖取一滴血就能做上百种测试,给病人提供详尽的健康数据,并且宣称 Theranos 像苹果的产品一样精致小巧,可以放在病人家里随时使用,完全淘汰市面上那些庞然大物。在细心策划下,她得到了各大媒体的竞相报道和各种活动、展会的邀请。有些讽刺意味的是最早把聚光灯照到 Holmes 身上的主流媒体也是后来让她身败名裂的华尔街日报。和很多名人一样,她也在 TED 做了打动人心的演讲,并且受总统 Obama 邀请参加过白宫的晚宴。媒体太需要这样一个充满标签的故事了:斯坦福辍学生、硅谷首位成功的女创始人、女乔布斯,并且有个人故事 - 她从小特别怕抽血,所以要做这样一个产品,所以他们不会思考一个辍学本科生是不是真的能做整个医疗行业一直做不到的事。就好像前几年中国的媒体特别需要 90 后创业者的故事,福布斯们每年都热衷于 20 under 20,30 under 30,他们也不在乎一个本科学生是不是真能造火箭或者颠覆人工智能技术。

Elizabeth Holmes

Elizabeth Holmes

这家公司顶峰时估值接近百亿美元,投资人里包括各大顶级 VC 和 Oracle 的 CEO Larry Ellison 等知名企业家,董事会包含了两位美国前国务卿(包括中国人民的老朋友基辛格)、两位前四星将军(包括美国前国防部长 Perry 和现在的国防部长 Mattis)、一位前参议员、一位前海军上将。代表 Holmes 和 Theranos 的律师 David Boies 是美国最知名的律师之一,曾在微软反垄断案中代表美国司法部,在 2000 年总统选举诉讼中代表戈尔,在去年好莱坞最大性侵案中为 Harvey Weinstein 辩护。她请的广告公司是创作了苹果 1984 和 Think Different 广告的 TBWA\Chiat\Day。可以说在各方面都占尽了最好的资源。

可惜的是 Theranos 并没有更先进的技术,他们的产品也从没达到过可用的程度。后来泡泡越吹越大,终于被和她打专利官司的人注意到破绽并把信息提供给调查记者 John Carreyrou。Carreyrou 在长时间的调查取证后在华尔街日报发表文章揭露了这个骗局,导致 Theranos 快速陨落,最终在去年 9 月关闭。Holmes 和她的合伙人因诈骗受到起诉。

Theranos 是一家硅谷公司,但是它的故事里不难看到很多中国公司的影子。每次有投资人或名人参观 Theranos 的时候,Holmes 都会邀请他们实际试试验一次血。Theranos 看起来精巧的设备在采完血后会在屏幕上显示一个缓慢前进的进度条,然后 Holmes 会解释说数据正在上传到服务器做分析,今天网络有问题比较慢,让对方先回去,验血结果会事后发送过去。在客人离开后,会有员工马上把血样取出来,稀释以后拿到从西门子采购的设备上去化验 - 也就是 Holmes 口中说的那些已经被 Theranos 淘汰的过时的、丑陋的庞然大物。其实类似这样的把现有的成熟产品以某种方式包上一层外衣拿出来当作是颠覆式创新的事在我们身边的创业公司里实在太多了。

「精益创业」和欺骗之间的界限有时很模糊。像 Dropbox 一样做个视频测试市场的反馈是一回事,但把自己完全不知道怎么做的事说成是创新和核心竞争力是另一回事。英语里把后者叫 fake it until you make it,而这种行为通常是被容忍的。在 2017、2018 年每个人都在说人工智能的时候,不知有多少公司的创始人在发布会上、投资人的会议室、视频里显示着「人工」实现的「智能」。既然做人工智能的实现不出来可以装一下,那么做操作系统的就可以先把 BSD 拿来把启动画面换掉装做中国自己的操作系统;做芯片的就可以先把别人的芯片拿来磨掉 logo 把自己的打上;做浏览器的就可以把 Chrome 拿来包在自己的皮里面当作自主研发、世界领先的内核。他们在开始时想必都觉得无伤大雅,毕竟先拿这些假装的东西做做 PR,等融个几千万美元,雇一堆牛人,不怕做不出来。可惜现实总是跟不上吹出去的牛。在很轻易地得到名声、关注、金钱后,人就难以有耐心回过头来做那些困难和枯燥的事了。

Elizabeth Holmes 野心很大,但同时也算是个很「理想化」的人 - 她醉心于成为像 Steve Jobs 一样的传奇人物。在中国,与她类似的人往往会更加「现实」一些。创业投资这个圈子的特点是能有效执行的规则很少,而涉及的利益又巨大,这样的吸引力自然让它成了一个骗子密度很高的环境。那么多年接触了非常多创业者,我见过从 A 轮过桥贷款开始就想办法把钱往自己兜里揣的,也见过公司廉价卖掉后,利用管理职位把大公司的钱往自己腰包里洗的,当然投资人里用基金的钱给自己个人的投资接盘的,甚至直接向被投公司要回扣的也不少。毕竟大家花的都是别人的钱(other people’s money,简称 OPM,发音同鸦片),创业者花的钱是投资人投的,投资人投的钱是 LP(limited partners)的,而 LP 里除了富豪和企业外,如果是国资的基金,其实最终的 LP 就是各位纳税人了。这些潜规则被容忍着,大部分人都心知肚明,见好就收,倒是很少听说像 Elizabeth Holmes 这样的人。不过话说回来,要是在中国有 Theranos 这样的公司,董事会里不是政治局常委就是将军,想必不是一个小小的记者能扳倒的了。

Bad Blood 值得每个对创投行业感兴趣的人一读,算是一种警示吧。

LeanCloud 的故事 — AVOS 时期

2018年10月23日 08:00

LeanCloud 的团队起源于 LeanCloud 这个产品诞生前两三年,当时我刚离开 Google 回国不久。在 Google 内部用来做缺陷和任务管理的工具叫 Buganizer,有一些独特的功能,当时我觉得 Buganizer 比外界的 Jira 等同类工具好用很多,于是组建了一个四个人的小团队,准备做一个可以和 Jira 竞争的产品。

因为我很喜欢 Clojure 语言,所以团队的早期成员都是从当时刚出现的 Clojure 中国社区招募的。这个社区最初几年的聚会也大多是我们主办的。它是个很小众的语言,所以社区里比较容易找到技术不错又喜欢学习新东西的人,这正是一个小型技术团队所需要的。我们用大约三个月时间开发并发布了第一版,因为还没有收入,资金很有限,所以我开始寻找天使投资。在这个过程中我通过创新工场认识了 YouTube 的两位创始人 Chad Hurley 和 Steve Chen(陈士骏),他们当时离开 Google 创立了 AVOS Systems。Steve 很希望在中国成立一个子公司,他很快到了北京一趟,因为都是工程师出身,又都在 Google 好几年,我们有共同语言、聊得很投机。一方面由于受到名人光环的吸引,另一方面也希望向他们学习成功经验,我决定带着当时的团队加入 AVOS,后来我们就成了 AVOS 中国子公司。

AVOS 的 A 和 V 来自于 HTML tag 的两个尖括号,把 < 和 > 各旋转 90 度就成了 A 和 V 的形状。而 OS 指 operating system。

当时 Yahoo! 正计划关闭他们几年前收购的知名社交书签网站 Delicious, Chad 和 Steve 知道后让 AVOS 以很低的价格收购了 Delicious1,暂时挽救了这个在 Web 2.0 时代有代表意义的产品。然后凭借着两位创始人的名声,AVOS 在商业模式还没有被验证时就从几家知名 VC 那里融到了两千万美元,估值大约两亿美元。AVOS 可以说是起点最高的创业公司之一。

很多在 Google 多年的人都会深受它的影响,Chad 和 Steve 也不例外,他们的景愿概括地说就是让 AVOS 成为一个迷你版的 Google、一个创新的平台,所以从一开始就有多个产品齐头并进地进行。然而在大约一年后,公司开始感受到来自投资人的强大压力。投资人是冲着 YouTube 的两个创始人来的,他们的期望是重现 YouTube 18 个月达到 16.5 亿美元估值的奇迹。

成功是很多因素合力的结果,而大部分人都往往高估主观因素的作用,大部分成功学的书也通常忽略运气对于结果的影响。事实上在几乎所有的成功里,运气都是很重要的因素,有的时候甚至是最重要的因素。回顾我自己的经历,即使是个人层面的小成功,偶然因素也扮演着重要角色。这也是成功往往很难被复制的原因,即使曾经成功过的人也很难重复自己的成功。犹太圣经里的一首诗很好地表达了偶然因素的重要性:

我回来见到阳光之下:快跑的未必能赢;力战的未必得胜;智慧的未必有粮食;明智的未必得到财富;有技能的的未必得到喜爱。降临到各人的在乎于时间和机会。

哈佛商学院和法学院的教授 Mihir A. Desai 在他的 The Wisdom of Finance 中说运气对人生来说是被低估的决定性因素之一:

Luck is a dominant and underappreciated part of life and performance. […] People will most naturally view their successes as related to their abilities as opposed to luck. So-called attribution errors occur everywhere in life.

Steve 是很优秀的工程师和创业者,也是很坦诚的人,并不讳言过去的幸运之处。有一次线下活动上有个听众问他 YouTube 是怎样吸引用户保持高速增长的。如果换一个人,恐怕会在台上大谈 viral marketing 和 growth hacking。Steve 停顿了一下回答说 YouTube 从来没有需要解决用户增长慢的问题。YouTube 从上线到被收购之间遇到的最大问题是用户增长太快,IT 架构跟不上需求,所以研发团队一直都处于 burn out 的状态,以至于在内部讨论是否接受被收购时,只有一个刚加入不久的人认为该独立发展,其他人都赞同卖给 Google,因为实在太累了。

后来在聊天时 Steve 回顾过 YouTube 成功的原因,他认为有两点很重要,第一是宽带的普及,使得在互联网上观看大量视频内容成为可能,第二是相关技术逐渐成熟,特别是 Flash 对视频的支持,使得他们可以实现这样一个平台。但是这些都是事后回顾总结出来的,在当时并不是显而易见。至于为什么包括更强大的 Google Video 在内的同时期竞争者都不如 YouTube 那么成功,他们也没有很好的答案。

由于投资人的期望值很高,AVOS 的每个产品在正式发布之后一个季度如果没有出现爆发式的增长,投资人就会施加压力希望公司尝试其他东西,所以产品上的变化非常频繁,并且一直是在并行开发多个产品。我作为中国子公司的负责人不需要直接面对股东,但是在每个季度董事会前配合 Chad 和 Steve 准备材料的时候都能感受到他们承受的巨大压力。我们团队在 AVOS 的时间里除了负责各个产品的后端架构外,也会自主开发一些面向本土的产品。在 2013 年,我们在中国发布了两个决定我们后来走向的产品:手机短视频应用「玩拍」和后端云服务「AVOS Cloud」。

在确定玩拍的产品形态前我们有一两个月的原型迭代过程。一开始团队想做一个有些类似 SoundCloud 的声音分享 app,但是后来觉得视频会更有意思,而且毕竟 AVOS 的创始人上一家公司是全球最大的视频网站,在这方面会有更多资源和经验。当时美国的短视频应用 Vine 在发布前就已经被 Twitter 收购,很被看好,这也是我们决定尝试这个方向的重要原因。虽然我们很少花钱做推广,玩拍在发布几个月后在市场上还是有了一定的知名度,有了一批忠实用户,还有一些演员在片场拍视频片段分享到微博上,产生了比较好的传播效果。比较有意思的是虽然 Vine 是这类产品的先行者,不过后来可能也从玩拍借鉴过去了一些功能。玩拍发布以后我们很快看到了在 Twitter 和 Vine 的办公室拍的视频。后来我们增加了一个功能,在分多段拍摄的时候,在屏幕上叠加上一段的最后一帧,这样用户可以无缝把片段拼接在一起,拍出平滑的动画或者魔法效果,过了两个月后 Vine 也推出了这个功能,叫 ghost mode

13 年下半年时百度当时社区产品(贴吧等)的负责人和百度的投资并购部找到我们,希望收购玩拍。当时贴吧这样在 Web 时代很成功的产品正在急于抓住移动互联网的新机会,他们希望把玩拍收购后进行产品上的整合。在和我们见面之前,他们以为玩拍就是一家国内的创业公司,所以收购的事只要谈好价格很快就能交割。但是很遗憾由于我们资本结构的原因,我只能把百度介绍给总部,让 AVOS 的董事会来做决策。对于 AVOS 而言,这是个比较尴尬的决策。当时中国团队在整个公司的股份比例是相对较低的,而百度想收购的产品又完全是中国团队自发做的,所以很难决定如何在中国团队和 AVOS 之间分配收益。此外百度希望的是把中国团队和产品一起收购,而由于中国团队在 AVOS 的其他项目中也承担着重要的角色,所以总部当时是不愿意把整个中国公司卖掉的。最终 AVOS 的提议是百度可以投资 AVOS,然后和我们在中国合作。这和百度的期望相去甚远,所以他们只好放弃。

和百度合作的失败成为了我们从 AVOS 独立的直接原因之一,因为我们开始认识到中国团队难以有一条清晰的发展路径,即使做出成功的产品我们也无法控制后面会发生什么。到 2013 年底时 AVOS 又面临着来自投资人的新一轮压力,美国和新西兰办公室都要裁员以降低成本、缩短产品线。于是我向 Steve 和 Chad 提出让中国团队独立发展,这样 AVOS 不需要再向中国投入资金,中国团队更独立会发展得更好,而 AVOS 作为股东也能得到收益。AVOS 的股东会最初提出的方案是 AVOS 仍然持有多数股份,这样无疑会导致我们很难独立融资。最终 Steve 和 Chad 帮助说服了其他股东,让 AVOS 只保留天使投资人通常的比例,这使得我们独立以后在相对短的时间内顺利完成了 A 轮融资。AVOS 后来收缩了产品焦点,改了名叫 MixBit,并在 2018 年卖给了一家公司。在出售时,LeanCloud 的股份是 MixBit 除了团队之外的主要资产。


  1. 很多年轻的读者可能没听说过 Delicious。它是由 Joshua Schachter 创建的一个让用户可以保存和分享网址的网站,在发布时使用的域名是 del.icio.us,是知名互联网产品里最早的 domain hack 案例。Delicious 是最早以现在的意义使用「标签」(tag)这个概念的互联网产品,是 Web 2.0 时代的代表之一。在后来被很多产品借鉴后,标签的重要性才被广泛认识。Delicious 也是我们在国内的公司名「美味书签」的由来。 ↩︎

一点人生经验 👓 — 如何学英语

2018年8月25日 08:00

最近写了中国人学好英语的重要性。我想续一篇说说如何学好英语。写上一篇的起因是我想找几本工作相关的书送给一些同事的时候,发现国内出版的目标领域的中文书里实在挑不出值得推荐的,最近看过觉得好的英文书在国内也没有翻译出版。所以这一篇也算是写给各位同事看的。

本文说的方法比较适合于成年人,也就是没有英语考试的压力,只是为了自己的提高而学的人。另外这里主要说的是英语阅读,因为这是我认为最有价值的技能,而且因为可以日常持续使用,达到比较好的水平后容易保持。口语对于一般人来说在日常生活中没有经常使用的环境,难以达到并保持在高水平。

首先最重要的一点是尽可能学以致用,不要为学英语而学英语。你的目的是获取内容和信息,英语能力的提高是服务于这个目的的。学语言最难的是长期坚持,对成人尤其如此,因为没有学生时代的考试压力,如果是在一段时间内感受不到价值、没有成就感,许多人就容易放弃。所以我建议把你本来就有的阅读时间分配给英文的素材。当你工作上需要看某方面的资料的时候,就尝试找英文的来看;如果你喜欢看小说,那就从短篇的英文原版小说看起;如果你生活中有看新闻的习惯,那么就转向看英语新闻。这个方式对于在大学考过四六级的人应该是可以马上开始的,无非就是比正常的阅读慢一些,但只要能坚持下去,速度是会提升很快的,重要的是你在相对短的时间内就能感受到回报。如果你很长时间没有接触英语,过于生疏,那么先花两周到一个月的时间专门看看英语学习的书,重新熟悉语法和恢复基本词汇量也是可以的。如果原本基础比较弱,那么就得靠坚持和自律先纯粹地学习语言一段时间了,这是绕不过去的。

到哪里找看的东西呢?很多人都不知道,但这是最容易解决的问题。去美国亚马逊网站注册一个帐号。国内银行发行的支持 VISA/MasterCard/AMEX 的双币或外币信用卡应该都可以在 amazon.com 支付。因为你不会购买需要实际邮寄的货物,所以随便填一个搜到的美国地址就可以。然后按照喜好下载 Kindle 的移动/桌面应用,或者在中国亚马逊购买一个 Kindle 阅读器(注意不要买和国内一些电子书网站合作出的订制版,否则可能无法用美国亚马逊帐号登录),就可以在美国亚马逊买电子书看了。大部分的书都会有 Kindle 版,价格大多是 10 美元至小几十美元之间,比国内的纸质书贵不了多少,应该是大部分人可以承受的。如果你有看新闻的习惯的话,New York Times, Wall Street Journal, The Economist 都是质量很高,并且可以以不高的价格在线订阅电子版的刊物。我正在整理看过的 Kindle 电子书,会逐步把值得推荐的放到 blog 的荐书页面,如果你有兴趣可以关注那个页面的更新。

在内容选择上按照每个人的需求和兴趣选择最重要,因为这样最容易坚持下去。但普遍来说我觉得工作或兴趣相关领域的专业图书要优于小说和文学作品,小说和文学作品优于时事新闻。因为领域性的图书往往会更加注重使用逻辑性强、易懂、直白的语言来表达,阅读难度最低,并且学习到的东西也容易在短期得到应用、体现出价值。文学作品中抒情、修辞的成分比较多,不同作者的风格也千差万别,易懂通常不是高优先级的属性。时事新闻类的素材往往依赖的背景知识比较多,内容相对小说而言更枯燥,也会使用英语里的很多习惯用法,如果没有在美国生活过可能会没见过,所以阅读难度不低。

最后说一下阅读时的要点 - 不要以学生时代那种遇到生词就去查字典的方式阅读,因为目的不一样了。高频率的中断会让你的效率变得很低,影响对文章的理解,也容易产生沮丧的情绪。遇到生词的时候只要根据上下文能猜出大概意思,不影响对段落的理解,就应该继续往下看。当一个词在不同的上下文里遇到了多次后,你多半就已经猜出它的准确含义了。如果晚一点你发现它是个常用词,想弄清楚它的详细意义和发音,那到时再查字典也不迟。Kindle 支持长按一个单词查字典,还能把词自动记到生词本里,是很好的提高效率的工具。在阅读中自然地提高词汇量比专门学习大量单词是更容易长期坚持的。

我相信如果你能坚持这里所说的方法半年至一年,一定会觉得这是你做过的事里回报率最高的之一。

一点人生经验 👓 — 学好英语的重要性

2018年8月23日 08:00

如果现在我必须把过去学过的所有知识都忘记,只能保留一样技能,我会毫不犹豫地选择英语。因为我在初中以后的学习过程非常依赖英语,特别是英语阅读。

我上大学的时候,专业课老师选择教材时大多不是从质量出发,通常要么是自己写的教材,要么是本校出版的教材。在大一之后我基本就不再去上专业课,而是大部分时间在图书馆看影印版的英文教材自学1,每个学期结束前两周再去课堂对照一下老师勾的复习重点和自己学的内容,一方面防止自学的范围有缺漏,另一方面防止考试时中英文的说法对不上。这种学习方法的一个副作用是后来考 GRE 和 CS 专业 GRE 的时候相对比较容易。

对中国人来说,学好英语对生活不是必须,但是会让你在学任何其它东西的时候选择多很多,让你的知识体系更加完整,为你打开新的窗户。这有几点原因。

第一是中国的出版和媒体处于一个非常不自由和畸形的环境。好的英语阅读能力可以让你全面地了解历史和时事,避免因为信息的残缺而被蒙蔽。

第二是在很长的时间内,对大部分学科而言,一流的文献和出版物都是英文的。优秀的出版物很多不会被翻译成中文,或者是翻译的质量很差。这一点在信息技术这样高速发展的领域尤其明显。由于翻译是一件回报率很低的工作,很少有真正的行业专家愿意把时间花在翻译上2,这样就导致了做翻译的人往往对专业知识没有足够的理解而输出低质量的结果。比如最近我想买两本关于市场计划的书送给公司负责市场的同事,可是发现这方面的中文书实在没什么可买的。

第三是目前中国互联网和欧美互联网的文化差异很大。大天朝的局域网对于求知的人来说是很贫瘠的。无论你想了解什么领域或者学习什么技能,在 Google 上一搜都能找到很多高质量的、不同层面和类型的内容,并且大部分是开放和免费的;而在百度上用中文搜一搜,可以保证结果差得很远。如果你获取信息的范围是整个互联网的话,你会发现国内这两年火热的这些所谓知识付费产品绝大部分都是浪费钱和时间。所以如果你愿意为了知识付出时间、精力和钱的话,最高效的办法是尽量在学好英语上多投入一些。

续篇:如何学英语


  1. 清华大学出版社当时出了一套完整的国外知名计算机科学教科书影印版。 ↩︎

  2. 如果你看到了这里,而且正好翻译过一本书,那你一定是少数专业知识丰富又愿意奉献时间在翻译上的专家之一。 ↩︎

Docker 和 Kubernetes 从听过到略懂:给程序员的旋风教程

2018年7月16日 08:00

早在 Docker 正式发布几个月的时候,LeanCloud 就开始在生产环境大规模使用 Docker,在过去几年里 Docker 的技术栈支撑了我们主要的后端架构。这是一篇写给程序员的 Docker 和 Kubernetes 教程,目的是让熟悉技术的读者在尽可能短的时间内对 Docker 和 Kubernetes 有基本的了解,并通过实际部署、升级、回滚一个服务体验容器化生产环境的原理和好处。本文假设读者都是开发者,并熟悉 Mac/Linux 环境,所以就不介绍基础的技术概念了。命令行环境以 Mac 示例,在 Linux 下只要根据自己使用的发行版和包管理工具做调整即可。

Docker 速成

首先快速地介绍一下 Docker:作为示例,我们在本地启动 Docker 的守护进程,并在一个容器里运行简单的 HTTP 服务。先完成安装:

$ brew cask install docker

上面的命令会从 Homebrew 安装 Docker for Mac,它包含 Docker 的后台进程和命令行工具。Docker 的后台进程以一个 Mac App 的形式安装在 /Applications 里,需要手动启动。启动 Docker 应用后,可以在 Terminal 里确认一下命令行工具的版本:

$ docker --version
Docker version 18.03.1-ce, build 9ee9f40

上面显示的 Docker 版本可能和我的不一样,但只要不是太老就好。我们建一个单独的目录来存放示例所需的文件。为了尽量简化例子,我们要部署的服务是用 Nginx 来 serve 一个简单的 HTML 文件 html/index.html

$ mkdir docker-demo
$ cd docker-demo
$ mkdir html
$ echo '<h1>Hello Docker!</h1>' > html/index.html

接下来在当前目录创建一个叫 Dockerfile 的新文件,包含下面的内容:

FROM nginx
COPY html/* /usr/share/nginx/html

每个 Dockerfile 都以 FROM ... 开头。 FROM nginx 的意思是以 Nginx 官方提供的镜像为基础来构建我们的镜像。在构建时,Docker 会从 Docker Hub 查找和下载需要的镜像。Docker Hub 对于 Docker 镜像的作用就像 GitHub 对于代码的作用一样,它是一个托管和共享镜像的服务。使用过和构建的镜像都会被缓存在本地。第二行把我们的静态文件复制到镜像的 /usr/share/nginx/html 目录下。也就是 Nginx 寻找静态文件的目录。Dockerfile 包含构建镜像的指令,更详细的信息可以参考这里

然后就可以构建镜像了:

$ docker build -t docker-demo:0.1 .

请确保你按照上面的步骤为这个实验新建了目录,并且在这个目录中运行 docker build。如果你在其它有很多文件的目录(比如你的用户目录或者 /tmp)运行,docker 会把当前目录的所有文件作为上下文发送给负责构建的后台进程。

这行命令中的名称 docker-demo 可以理解为这个镜像对应的应用名或服务名,0.1 是标签。Docker 通过名称和标签的组合来标识镜像。可以用下面的命令来看到刚刚创建的镜像:

$ docker image ls
REPOSITORY TAG IMAGE ID CREATED SIZE
docker-demo 0.1 efb8ca048d5a 5 minutes ago 109MB

下面我们把这个镜像运行起来。Nginx 默认监听在 80 端口,所以我们把宿主机的 8080 端口映射到容器的 80 端口:

$ docker run --name docker-demo -d -p 8080:80 docker-demo:0.1

用下面的命令可以看到正在运行中的容器:

$ docker container ps
CONTAINER ID IMAGE ... PORTS NAMES
c495a7ccf1c7 docker-demo:0.1 ... 0.0.0.0:8080->80/tcp docker-demo

这时如果你用浏览器访问 http://localhost:8080,就能看到我们刚才创建的「Hello Docker!」页面。

在现实的生产环境中 Docker 本身是一个相对底层的容器引擎,在有很多服务器的集群中,不太可能以上面的方式来管理任务和资源。所以我们需要 Kubernetes 这样的系统来进行任务的编排和调度。在进入下一步前,别忘了把实验用的容器清理掉:

$ docker container stop docker-demo
$ docker container rm docker-demo

安装 Kubernetes

介绍完 Docker,终于可以开始试试 Kubernetes 了。我们需要安装三样东西:Kubernetes 的命令行客户端 kubctl、一个可以在本地跑起来的 Kubernetes 环境 Minikube、以及给 Minikube 使用的虚拟化引擎 xhyve。

$ brew install kubectl
$ brew cask install minikube
$ brew install docker-machine-driver-xhyve

Minikube 默认的虚拟化引擎是 VirtualBox,而 xhyve 是一个更轻量、性能更好的替代。它需要以 root 权限运行,所以安装完要把所有者改为 root:wheel,并把 setuid 权限打开:

$ sudo chown root:wheel /usr/local/opt/docker-machine-driver-xhyve/bin/docker-machine-driver-xhyve
$ sudo chmod u+s /usr/local/opt/docker-machine-driver-xhyve/bin/docker-machine-driver-xhyve

然后就可以启动 Minikube 了:

$ minikube start --vm-driver xhyve

你多半会看到一个警告说 xhyve 会在未来的版本被 hyperkit 替代,推荐使用 hyperkit。不过在我写这个教程的时候 docker-machine-driver-hyperkit 还没有进入 Homebrew, 需要手动编译和安装,我就偷个懒,仍然用 xhyve。以后只要在安装和运行的命令中把 xhyve 改为 hyperkit 就可以。

如果你在第一次启动 Minikube 时遇到错误或被中断,后面重试仍然失败时,可以尝试运行 minikube delete 把集群删除,重新来过。

Minikube 启动时会自动配置 kubectl,把它指向 Minikube 提供的 Kubernetes API 服务。可以用下面的命令确认:

$ kubectl config current-context
minikube

Kubernetes 架构简介

典型的 Kubernetes 集群包含一个 master 和很多 node。Master 是控制集群的中心,node 是提供 CPU、内存和存储资源的节点。Master 上运行着多个进程,包括面向用户的 API 服务、负责维护集群状态的 Controller Manager、负责调度任务的 Scheduler 等。每个 node 上运行着维护 node 状态并和 master 通信的 kubelet,以及实现集群网络服务的 kube-proxy。

作为一个开发和测试的环境,Minikube 会建立一个有一个 node 的集群,用下面的命令可以看到:

$ kubectl get nodes
NAME STATUS AGE VERSION
minikube Ready 1h v1.10.0

部署一个单实例服务

我们先尝试像文章开始介绍 Docker 时一样,部署一个简单的服务。Kubernetes 中部署的最小单位是 pod,而不是 Docker 容器。实时上 Kubernetes 是不依赖于 Docker 的,完全可以使用其他的容器引擎在 Kubernetes 管理的集群中替代 Docker。在与 Docker 结合使用时,一个 pod 中可以包含一个或多个 Docker 容器。但除了有紧密耦合的情况下,通常一个 pod 中只有一个容器,这样方便不同的服务各自独立地扩展。

Minikube 自带了 Docker 引擎,所以我们需要重新配置客户端,让 docker 命令行与 Minikube 中的 Docker 进程通讯:

$ eval $(minikube docker-env)

在运行上面的命令后,再运行 docker image ls 时只能看到一些 Minikube 自带的镜像,就看不到我们刚才构建的 docker-demo:0.1 镜像了。所以在继续之前,要重新构建一遍我们的镜像,这里顺便改一下名字,叫它 k8s-demo:0.1。

$ docker build -t k8s-demo:0.1 .

然后创建一个叫 pod.yml 的定义文件:

apiVersion: v1
kind: Pod
metadata:
 name: k8s-demo
spec:
 containers:
 - name: k8s-demo
 image: k8s-demo:0.1
 ports:
 - containerPort: 80

这里定义了一个叫 k8s-demo 的 Pod,使用我们刚才构建的 k8s-demo:0.1 镜像。这个文件也告诉 Kubernetes 容器内的进程会监听 80 端口。然后把它跑起来:

$ kubectl create -f pod.yml
pod "k8s-demo" created

kubectl 把这个文件提交给 Kubernetes API 服务,然后 Kubernetes Master 会按照要求把 Pod 分配到 node 上。用下面的命令可以看到这个新建的 Pod:

$ kubectl get pods
NAME READY STATUS RESTARTS AGE
k8s-demo 1/1 Running 0 5s

因为我们的镜像在本地,并且这个服务也很简单,所以运行 kubectl get pods 的时候 STATUS 已经是 running。要是使用远程镜像(比如 Docker Hub 上的镜像),你看到的状态可能不是 Running,就需要再等待一下。

虽然这个 pod 在运行,但是我们是无法像之前测试 Docker 时一样用浏览器访问它运行的服务的。可以理解为 pod 都运行在一个内网,我们无法从外部直接访问。要把服务暴露出来,我们需要创建一个 Service。Service 的作用有点像建立了一个反向代理和负载均衡器,负责把请求分发给后面的 pod。

创建一个 Service 的定义文件 svc.yml:

apiVersion: v1
kind: Service
metadata:
 name: k8s-demo-svc
 labels:
 app: k8s-demo
spec:
 type: NodePort
 ports:
 - port: 80
 nodePort: 30050
 selector:
 app: k8s-demo

这个 service 会把容器的 80 端口从 node 的 30050 端口暴露出来。注意文件最后两行的 selector 部分,这里决定了请求会被发送给集群里的哪些 pod。这里的定义是所有包含「app: k8s-demo」这个标签的 pod。然而我们之前部署的 pod 并没有设置标签:

$ kubectl describe pods | grep Labels
Labels:		<none>

所以要先更新一下 pod.yml,把标签加上(注意在 metadata: 下增加了 labels 部分):

apiVersion: v1
kind: Pod
metadata:
 name: k8s-demo
 labels:
 app: k8s-demo
spec:
 containers:
 - name: k8s-demo
 image: k8s-demo:0.1
 ports:
 - containerPort: 80

然后更新 pod 并确认成功新增了标签:

$ kubectl apply -f pod.yml
pod "k8s-demo" configured
$ kubectl describe pods | grep Labels
Labels:		app=k8s-demo

然后就可以创建这个 service 了:

$ kubectl create -f svc.yml
service "k8s-demo-svc" created

用下面的命令可以得到暴露出来的 URL,在浏览器里访问,就能看到我们之前创建的网页了。

$ minikube service k8s-demo-svc --url
http://192.168.64.4:30050

横向扩展、滚动更新、版本回滚

在这一节,我们来实验一下在一个高可用服务的生产环境会常用到的一些操作。在继续之前,先把刚才部署的 pod 删除(但是保留 service,下面还会用到):

$ kubectl delete pod k8s-demo
pod "k8s-demo" deleted

在正式环境中我们需要让一个服务不受单个节点故障的影响,并且还要根据负载变化动态调整节点数量,所以不可能像上面一样逐个管理 pod。Kubernetes 的用户通常是用 Deployment 来管理服务的。一个 deployment 可以创建指定数量的 pod 部署到各个 node 上,并可完成更新、回滚等操作。

首先我们创建一个定义文件 deployment.yml:

apiVersion: extensions/v1beta1
kind: Deployment
metadata:
 name: k8s-demo-deployment
spec:
 replicas: 10
 template:
 metadata:
 labels:
 app: k8s-demo
 spec:
 containers:
 - name: k8s-demo-pod
 image: k8s-demo:0.1
 ports:
 - containerPort: 80

注意开始的 apiVersion 和之前不一样,因为 Deployment API 没有包含在 v1 里,replicas: 10 指定了这个 deployment 要有 10 个 pod,后面的部分和之前的 pod 定义类似。提交这个文件,创建一个 deployment:

$ kubectl create -f deployment.yml
deployment "k8s-demo-deployment" created

用下面的命令可以看到这个 deployment 的副本集(replica set),有 10 个 pod 在运行。

$ kubectl get rs
NAME DESIRED CURRENT READY AGE
k8s-demo-deployment-774878f86f 10 10 10 19s

假设我们对项目做了一些改动,要发布一个新版本。这里作为示例,我们只把 HTML 文件的内容改一下, 然后构建一个新版镜像 k8s-demo:0.2:

$ echo '<h1>Hello Kubernetes!</h1>' > html/index.html
$ docker build -t k8s-demo:0.2 .

然后更新 deployment.yml:

apiVersion: extensions/v1beta1
kind: Deployment
metadata:
 name: k8s-demo-deployment
spec:
 replicas: 10
 minReadySeconds: 10
 strategy:
 type: RollingUpdate
 rollingUpdate:
 maxUnavailable: 1
 maxSurge: 1
 template:
 metadata:
 labels:
 app: k8s-demo
 spec:
 containers:
 - name: k8s-demo-pod
 image: k8s-demo:0.2
 ports:
 - containerPort: 80

这里有两个改动,第一个是更新了镜像版本号 image: k8s-demo:0.2,第二是增加了 minReadySeconds: 10strategy 部分。新增的部分定义了更新策略:minReadySeconds: 10 指在更新了一个 pod 后,需要在它进入正常状态后 10 秒再更新下一个 pod;maxUnavailable: 1 指同时处于不可用状态的 pod 不能超过一个;maxSurge: 1 指多余的 pod 不能超过一个。这样 Kubernetes 就会逐个替换 service 后面的 pod。运行下面的命令开始更新:

$ kubectl apply -f deployment.yml --record=true
deployment "k8s-demo-deployment" configured

这里的 --record=true 让 Kubernetes 把这行命令记到发布历史中备查。这时可以马上运行下面的命令查看各个 pod 的状态:

$ kubectl get pods
NAME READY STATUS ... AGE
k8s-demo-deployment-774878f86f-5wnf4 1/1 Running ... 7m
k8s-demo-deployment-774878f86f-6kgjp 0/1 Terminating ... 7m
k8s-demo-deployment-774878f86f-8wpd8 1/1 Running ... 7m
k8s-demo-deployment-774878f86f-hpmc5 1/1 Running ... 7m
k8s-demo-deployment-774878f86f-rd5xw 1/1 Running ... 7m
k8s-demo-deployment-774878f86f-wsztw 1/1 Running ... 7m
k8s-demo-deployment-86dbd79ff6-7xcxg 1/1 Running ... 14s
k8s-demo-deployment-86dbd79ff6-bmvd7 1/1 Running ... 1s
k8s-demo-deployment-86dbd79ff6-hsjx5 1/1 Running ... 26s
k8s-demo-deployment-86dbd79ff6-mkn27 1/1 Running ... 14s
k8s-demo-deployment-86dbd79ff6-pkmlt 1/1 Running ... 1s
k8s-demo-deployment-86dbd79ff6-thh66 1/1 Running ... 26s

从 AGE 列就能看到有一部分 pod 是刚刚新建的,有的 pod 则还是老的。下面的命令可以显示发布的实时状态:

$ kubectl rollout status deployment k8s-demo-deployment
Waiting for rollout to finish: 1 old replicas are pending termination...
Waiting for rollout to finish: 1 old replicas are pending termination...
deployment "k8s-demo-deployment" successfully rolled out

由于我输入得比较晚,发布已经快要结束,所以只有三行输出。下面的命令可以查看发布历史,因为第二次发布使用了 --record=true 所以可以看到用于发布的命令。

$ kubectl rollout history deployment k8s-demo-deployment
deployments "k8s-demo-deployment"
REVISION	CHANGE-CAUSE
1		<none>
2		kubectl apply --filename=deploy.yml --record=true

这时如果刷新浏览器,就可以看到更新的内容「Hello Kubernetes!」。假设新版发布后,我们发现有严重的 bug,需要马上回滚到上个版本,可以用这个很简单的操作:

$ kubectl rollout undo deployment k8s-demo-deployment --to-revision=1
deployment "k8s-demo-deployment" rolled back

Kubernetes 会按照既定的策略替换各个 pod,与发布新版本类似,只是这次是用老版本替换新版本:

$ kubectl rollout status deployment k8s-demo-deployment
Waiting for rollout to finish: 4 out of 10 new replicas have been updated...
Waiting for rollout to finish: 6 out of 10 new replicas have been updated...
Waiting for rollout to finish: 8 out of 10 new replicas have been updated...
Waiting for rollout to finish: 1 old replicas are pending termination...
deployment "k8s-demo-deployment" successfully rolled out

在回滚结束之后,刷新浏览器就可以确认网页内容又改回了「Hello Docker!」。

结语

我们从不同层面实践了一遍镜像的构建和容器的部署,并且部署了一个有 10 个容器的 deployment, 实验了滚动更新和回滚的流程。Kubernetes 提供了非常多的功能,本文只是以走马观花的方式做了一个快节奏的 walkthrough,略过了很多细节。虽然你还不能在简历上加上「精通 Kubernetes」,但是应该可以在本地的 Kubernetes 环境测试自己的前后端项目,遇到具体的问题时求助于 Google 和官方文档即可。在此基础上进一步熟悉应该就可以在别人提供的 Kubernetes 生产环境发布自己的服务。

LeanCloud 的大部分服务都运行在基于 Docker 的基础设施上,包括各个 API 服务、中间件、后端任务等。大部分使用 LeanCloud 的开发者主要工作在前端,不过云引擎是我们的产品中让容器技术离用户最近的。云引擎提供了容器带来的隔离良好、扩容简便等优点,同时又直接支持各个语言的原生依赖管理,为用户免去了镜像构建、监控、恢复等负担,很适合希望把精力完全投入在开发上的用户。

加密货币与区块链(三):什么是信任

2018年5月13日 08:00

Bruce Schneier 的《应用密码学》(Applied Cryptography)是我的密码学启蒙书。我上本科的时候读过一遍影印版。读博士的时候导师说他有本出版社请他写书评时送的,听说我喜欢就送我了,所以又读了一遍。过了几年 Bruce Schneier 又写了一本书叫 Secrets & Lies。即使你没有兴趣看这本书,它的前言也值得一读。这里我只翻译一部分:

七年前我写了另一本书:《应用密码学》。我在其中描绘了一个数学的乌托邦:可以永远保守秘密的算法,以及能安全可靠地执行无监管的赌博、抗检测的认证、匿名货币等美妙的电子化交互的协议。在我眼中,密码学是带来平等的伟大技术;任何有廉价计算机的人都能拥有与最强大的政府同样的安全。在两年后写此书第二版时,我甚至说「仅靠法律保护自己是不够的,我们还需要靠数学保护自己。」

然而我错了。密码学做不到以上的任何一点。

这并不是因为 1994 年以后密码学变弱了;也不是因为书中的技术内容不再正确。而是因为密码学不存在于真空中。

密码学是数学的一个分支。和数学的其它部分一样,它是关于数字、等式、逻辑的。而对于你我来说在生活中能感受到的安全是关于人的:人知道的事、人之间的关系、人和机器的关系。信息安全是关于计算机的:复杂、不稳定、有缺陷的计算机。

数学是完美的;现实是主观的。数学是确定的;计算机是易怒的。数学是有逻辑的;人是不确定、易变和难以理解的。

《应用密码学》的错误在于我没有对上下文做任何讨论。我把密码学当作答案 ™ 来讲述。这是很幼稚的。

[…] 一个同事曾和我说,世界上充满了《应用密码学》的读者设计的糟糕的安全系统。[…]

几年前我听过一句话,在这里稍微改动一下:如果你认为技术能解决安全问题,那么你既不懂安全也不懂技术。

有很多关于区块链的文章都说「区块链解决的核心问题是信任问题」,但是我没有看到有人回答了关键的问题:到底什么是信任?什么是所谓「信任问题」,它存不存在?什么算是「解决了信任问题」?事实上如果在 Google 上搜一下这句话,会找到大量的复制粘贴和人云亦云。Brice Schneier 书里那句话改一改也是适用的,如果有人认为技术能解决信任问题,那么他恐怕既不懂信任也不懂技术。

Ken Thompson 1984 年的图灵奖演讲 Reflections on Trusting Trust 很好地从技术的角度解释了信任的本质。读者不妨去看看原文,或者也可以参考我在 XcodeGhost 出现时写的一篇简介。细节在本文就不再复述了,简单地说,他演示了一个不仅通过源码分析无法发现,通过反编译二进制码分析也无法发现的攻击。他在演讲中说「关于一个程序里没有木马的保证到底有多可信呢?或许更重要的是信任开发软件的人。」他展示了除非你使用的整个软硬件栈都是自己制造和开发的,就得信任其他人。

信任是社会存在和运转的基础。我们把钱存在银行,相信需要的时候一定能从取款机取出来;我们每天上班,信任路上的司机不会开车撞我们;乘飞机旅行,信任飞行员技术够好不会坠机,信任其他旅客不是恐怖分子,也信任飞机制造商没有埋下安全隐患。我们经常会在美国电影上看到一个人对最亲密的人说「I trust you with my life.」,然而看起来我们其实每天都在以生命来信任陌生人。事实上我们信任的不是某个个体,而是整个社会系统。这个系统通过道德、价值观、经济体系、法律定义了规则,并让违反规则的人受到惩罚。这个系统保证了大部分司机会尽可能安全驾驶,机场会检查登机旅客,航空公司会严格地审查和培训飞行员。

技术在信任和安全方面扮演的角色就好像门锁。大部分人不偷不抢是因为他们想做被他人接受的社会成员,尽量不做不道德的事;剩下的人中 99% 以上的人是因为担心受到法律的制裁;只有极少数的人是因为打不开锁。所以你从不会听说一个城市通过投资制造更好的锁来降低犯罪率。假设给我两个选择,要么是生活在北京,但是没有门锁,要么生活在一个没有法律、道德约束的地方,但是可以装最好的锁。我一定会选择前者,我多半会丢失一些东西(取决于住什么小区),但如果选择后者肯定会没命。

好的技术是对社会的安全、信任机制的补充,它无法替代这些机制,更不能破坏这些机制。到目前为止,区块链已经实现的可以放在阳光下说的实际应用价值约等于零。当很多区块链公司在苦苦寻找应用场景来讲故事的时候,加密货币倒是成了 Silk Road、Dream Market 等黑产市场的主要交易媒介,最近几年知名的多次 ransomware 攻击也都是让受害者以比特币支付赎金。对于需要逃脱现有社会机制可能带来的惩罚和后果的人来说,加密货币发挥着很大作用,或者说是它真正完成货币功能的最主要场景,所以有很多人直接用黑产交易量来估算加密货币的合理市值。加密货币对现有社会机制的影响,以及它为什么群体带来最多的便利和价值,是值得更多思考的问题。

安全和信任是一个链条,它的强度是由其中最弱的一环决定的。历史上的安全事件几乎没有任何针对密码学算法和协议的攻击,即使是技术上的攻击也大多是针对具体软件实现,更多的是通过欺骗、引诱、胁迫等手段针对人的攻击。当密码被主动交给攻击者的时候,加密算法是用 128 位秘钥还是 2048 位密钥并没有什么差别。2007 年在比利时,有一个人靠个人魅力敲开了银行保险库偷走了接近三千万美元的钻石:他靠经常请大家吃巧克力得到了银行职员的好感和信任。要知道比利时人可不缺巧克力。

电影「猫鼠游戏」(Catch Me If You Can)的真实原型 Frank Abagnale 在 16 岁离家出走后使用过 8 个身份,在很长时间里成功地伪装成各种不同职业人士,包括民航飞行员、医生、政府官员、律师。在最终被抓住后他加入了 FBI 成为反欺诈专家。有一次他在 Google 做讲座,台下有人问他现在警察和 FBI 有很先进的监控和侦破技术,罪犯还有没有可能像他当年那样成功。他的回答是现在唾手可得的信息只会让那些事情简单百倍。有无数的案例说明在安全和信任的链条中,人的因素是最薄弱的一环,而人的问题往往不是靠技术能解决的。

可能上面的欺诈案例离区块链有点远,我说件很多人都熟悉的事。以太坊(Ethereum)上最早有很大影响的智能合约叫 The DAO (The Decentialized Automomous Organizaiton)。「The」是官方名称的一部分,这让我想起 Facebook 早期的名称「TheFacebook」。The DAO 的使命是:

To blaze a new path in business organization for the betterment of its members, existing simultaneously nowhere and everywhere and operating solely with the steadfast iron will of unstoppable code.

Unstoppable code 的加粗是原文里的。我觉得认为 unstoppable code 是好事的人多半比较缺乏人生经验,有点 too simple。The DAO 可以理解为一个分布式的投资机构,大家都能把钱(用以太币的形式)投进去,以投票的方式决定投资到哪些项目。它不靠人为管理,一切都靠程序来执行。The DAO 上线以后成为了有史以来最大的众筹项目,一万一千个投资者投入了相当于一亿五千万美元的以太币,占当时以太币总量的 14%。和任何一个有些复杂的程序一样,The DAO 的智能合约代码是有漏洞的(换个说法,是有一些大部分人没注意到的功能)。于是有攻击者利用这个漏洞/功能从 The DAO 里累计转走了价值 5000 万美元的以太币。因为智能合约的代码在两周内无法修改,所以没有人能阻止他,毕竟要是能 stop 就不能叫 unstoppable code 了嘛。当时有人在 Reddit 上发了一幅漫画:

一幅 Reddit 上的漫画

一幅 Reddit 上的漫画

攻击者第二天在 Pastebin 上发布了一个声明:

我仔细阅读了 The Dao 的代码,在发现这个有利可图的功能后,决定参与进来。我使用了这个功能并合理地得到了 3,641,694 个以太币。

智能合约的支持者认为它的优点在于代码就是法律(code is law),不需要也不应该由人来诠释,那么有人通过代码定义的规则获得了回报确实是合理的。本来 The DAO 只是以太坊上的一个应用,这件事和以太坊基金会(Ethereum Foundation)没有太大关系。然而因为以太坊社区的很多大佬和核心开发者都投了很多以太币在 The DAO 里,以太坊基金会决定要挽回在 The DAO 损失的以太币。唉?不是说区块链上的数据和交易都是不可能改的吗?不改数据,可以改对数据的解释啊,重新定义一下以太坊就好了嘛。以太坊基金会因为 The DAO 改变了整个以太坊的实现,对以太坊区块链进行了硬分叉。以太坊区块链出现了两个分支:一个叫 ETH,在这个分支上对 The DAO 的攻击相当于没发生;另一条叫 ETH Classic,是原来没经过修改的分支。虽然对外说这个决定得到了社区共识,但当时以太坊基金会并没有讨论和解决分歧的官方渠道和投票机制。不要说算力可以自由选择投入到哪个分支,是用算力投票。第一,影响力决定算力。在分叉已经发生后,参与者考虑的是哪个分支最有前途、会让自己在未来得到最高回报。在以太坊社区里又有什么是比得到以太坊基金会的背书更有说服力的呢?要是美联储今天出来说我们要在一个新的区块链上发行一个新的货币,其它的加密货币我们都不承认,我想大部分比特币和以太坊的矿工也会马上改弦易辙的吧。第二,多数算力集中在少数人手里,这是规模经济不可避免的结果,在上一篇文章里已经说过就不赘述了。社会结构里权力和影响力的集中并不会因为有区块链而有什么改变。本来我写了一小段说明民主不是简单的多数人说了算或者多数算力说了算,但是这个问题太深,不适合夹在本文中间,所以删掉了。

绝对不能改的东西在世界上很少有,在数字世界更是不存在,主要还是看谁想改。代码就是法律,只是对一部分人来说在代码面前只有其他人是平等的,如果真想改,不开人大也能改。此外又有谁能证明对 The DAO 的攻击不是写智能合约的人故意留下漏洞,监守自盗呢?毕竟在代码里埋下别人难以发现的漏洞(功能)是很容易的,每年都有这样的编程比赛。区块链到底让谁更能信任谁了呢?

技术工具是有价值的,就和锁是有价值的一样,但是人和社会的问题大多是无法用技术解决的。相反,技术的发展往往会放大这些问题。区块链是一个分布式时间戳服务,它解决的问题是在一定条件下让参与者对数据项的顺序以大概率达成一致,但对这些数据的内容和性质并不提供内在保证。区块链不解决信任问题,更不是下一代互联网。如果你的应用场景需要一个分布式时间戳服务,并且没有我上一篇文章指出的那些问题,区块链就可能对你有价值。如果不需要,它对你就没有价值。我听说过的区块链应用都有其它更经济、更高效,并且通常更可靠的实现方式。大多数时候和区块链拉上关系只是为了靠近热点而已,毕竟在当下的环境只靠流行词和吹牛也能让一个人或一家公司走得很远。当然,我也不能因为自己没有看到就说不存在,但是对于一个已经火了那么久、吸引了那么多资本和创业者的有价值的技术来说,应该不至于还没出现有很多人经常在用的产品。

我在 Twitter 上看到过一个决策图,应该对在考虑是否需要区块链的人很有帮助:

Blockchain Decision Diagram

Blockchain Decision Diagram

离开微信公众平台

2018年4月26日 08:00

以下是我在自己的微信公众号 StartupJourney 发的最后一篇文章。

这个公众号从 2016 年 9 月后就没有更新过,其实这期间我有写文章,但是都只发在了知乎专栏和 blog 上。首先是因为微信后台的编辑器很难用,从 blog 把 Markdown 文本同步过来很麻烦。我一不是网红,二不开微店,也就不在乎微信的流量,没有动力来和这个编辑器做斗争。其次我一直挺讨厌微信这样一个从产品理念到运营方式都很封闭的系统,也就没有动力为这个 walled garden 添砖加瓦,在一个连外部链接都不允许的地方写东西。

微信对我是个因为工作原因不得不用的 IM 工具,我很少打开朋友圈(发工作广告时除外,惭愧)。最近收到邮件通知说长期不登录的公众号会被删除,所以就登录进来写最后一篇文章让这个公众号和各位订阅者告个别。

如果你有兴趣继续关注,欢迎关注我的知乎专栏,以及 LeanCloud 的技术专栏。所有我自己写的东西都会先发在我的 blog,上面有我维护的邮件列表,在每一篇文章的末尾,欢迎订阅。有一些之前散落各处的文章后续我也会找时间汇总到 blog。

❌
❌