过去十几年的一点流水帐
上一次写博客还是2013年,已经是十多年前的事了。上一次更新个人主页也是2016年,也已经接近十年了,那时就已经把当时的博客列为“Retired blog”了。但近来逐渐觉得有些东西还是有记录和分享的价值的,就有了再次开始写博客的想法。
不过我一直没有找到合适的平台。既不想依附于商业化的发布平台如Medium和Substack,又觉得自己搭博客很麻烦。再加上近年对英语和日语也更有自信了,因此也想试着搭建多语言博客,而没有什么博客系统对此有令人满意的支持。前一段时间了解到一款叫Astro的前端框架适合博客这类静态内容,便产生了用这个框架从头搭建一个博客的想法。
过去这十多年发生了许多事情,这第一篇博客就先简单地记一些流水帐,而这个博客的搭建过程就留给另外的机会来说吧。
2013年大学毕业以后来到澳洲,在悉尼大学念软件工程的硕士学位课程。因为觉得课业比较无聊,念了一学期之后就萌生找工作的想法。机缘巧合之下,由于之前给Firefox的贡献,特别是实现了CSS Counter Styles模块1,收到了加入Mozilla的邀请,于是2014年下半年就肄业加入了Mozilla公司。
说实话在Mozilla工作贡献开源软件根本是我理想中的职业,我在职业生涯的一开始就实现了理想,这是当时的我万万没有想到的。
Mozilla
Mozilla在澳洲并没有多少人,也没有办公室2。但早在那时候Mozilla就有远程办公的文化,据说多达半数的工程师都是在家办公的。而我除了第一周飞到三藩的办公室去做了简单的入职培训以外,之后也就都是在家了。公司给我配了两台Dell U2413显示器,还让公司报销了一张Herman Miller Embody的办公椅3,这些都是到今天还在用的。
当时出行的机会也很多,除了公司每年两次的All Hands,由于我直接参与Web标准的实现,每年还有机会参加W3C的TPAC,以及CSS工作组的F2F会议,有时团队也会组织额外的线下工作周把大家聚集到一起。那几年去了不少国家,美国、日本、台湾、加拿大、新西兰,最远一次去到了英国。当时年轻不懂得珍惜公费出行,大多数时候都只是去工作了一周然后便飞回来了,没有多在当地走一走、玩一玩,现在回想起来真是可惜。
加入Mozilla以后,第一个项目便是实现CSS Ruby标准。恰好大学时开始学习了一些日语,加上本身汉语母语的背景,对这种注解形式多少有一些了解,为我理解和实现这份标准带来了很大帮助。当然这份标准实现起来也有许多挑战,包括正确地处理换行,正确地处理不同位置上的空格4,保持多行注解之间的同步等等。在ruby支持默认启用之后,我还在Mozilla Hacks上写了一篇文章来介绍。在这份标准上还有许多我当时感到困难而没有实现,但至今依然觉得十分重要的部分,比如overhang和merge的支持。无论如何,Firefox现在对ruby的支持,至少从特性覆盖的角度依然应该是最好的。
在ruby之外,还实现了其他一些东亚语言特别是日语里常用的特性,如强调符(text-emphasis
)和纵中横(text-combine-upright
)。
之后的一个主要项目是改进全屏(Fullscreen API)的支持,包括重新设计的全屏流程取消了用户授权的请求,实现top layer机制,以及最重要的,优化进入和退出全屏的速度。
全屏API最主要的安全上的担忧是网站可能模拟用户的屏幕内容,从而绕过所有浏览器和操作系统的安全措施,欺骗用户输入敏感信息。在权衡安全和用户体验之后的设计是每次切换到全屏窗口都要显示全屏提示,进入和退出全屏时整个屏幕淡出淡入。屏幕淡出淡入成为了一个争议很大的设计,很多用户并不喜欢,而且在Linux上不能很好地兼容所有窗口管理器,所以这个部分最后在Linux上默认禁用了。
优化全屏的速度在开发时是以Youtube的全屏作为参考指标的,我发现全屏的过程中整个页面会重新排版数次,比如:fullscreen
开始生效时、全屏事件触发时、页面区域在窗口和全屏尺寸之间切换时。解决的方法便是在全屏变化开始以后冻结页面排版的更新,停住所有相关事件的分发,让所有变更在全屏全部完成后再一次性应用,如果需要的话也会预先提供预计的最终视界大小,以求把排版次数降到最低。
改善全屏支持的另一项挑战是当时正在进行的e10s计划,将Firefox改造成类似Chrome的多进程结构,分离界面所在的进程和页面渲染的进程以提升安全性和稳定性。然而进入和退出全屏需要在界面和页面之间进行许多的协调,当它们在不同的进程里时,许多流程便依赖消息传递,从同步变成了异步,大大增加了复杂性。除此之外,操作系统对全屏的支持也是一个麻烦,比如在Linux下,窗口的全屏事件与由之产生的窗口的大小调整事件,不但顺序没有保证,就连是否会出现都没有保证,给稳定性带来了很大的挑战。
后来加入了Stylo项目组,将Servo的样式引擎移植进Gecko来加速样式处理。这是我第一次在项目组里与许多人合作完成一个项目,也是我第一次开始在工作中写Rust代码。为了让Servo的样式引擎能够进入Gecko,需要让它跑通所有现有的测试集,为此我创造了一个拙劣的DSL来给测试分类,以确定哪些测试Stylo还跑不过,然后再据此逐步补上新引擎和旧引擎的差距。
在Mozilla的时光总体上是非常愉快的:拿着不错的工资,每年去世界各地旅行,贡献着知名的被广泛使用的开源软件,还参与进Web标准的制订。
虽然在Mozilla开发Firefox是我理想中的职业,但理想和现实终究是有差异的。因为之前一些项目上的合作,我跟台北办公室的许多同事有一些来往,在英国的那次All Hands前,我还和一些台湾的同事一起先在英国游览了一周。正因为如此,Mozilla撤销台北办公室、裁掉所有台湾同事这件事给我带来了很大冲击。不久之后,当时邀请我加入Mozilla的主管也离开了公司。除此之外,随着Firefox的市场占有率不断降低,而网络上对Firefox的批评也不绝于耳,这些在我听来都十分刺耳,也给我带来许多压力。在种种这些原因之下,我开始产生离开的念头。
恰逢这时,Canva的招聘者联系了我,向我介绍了这家当时估值已经十亿美元的澳洲本土独角兽公司,邀请我去参观。接着就顺势作为前端工程师加入了Canva,离开了工作四年的Mozilla。
Canva
很多人靠跳槽来涨工资,但我从Mozilla跳到Canva,工资上并没有什么变化,一样的基本工资,奖金被期权取代,到手的现金事实上还减少了。但我加入Canva的原因也是多方面的:一来开发了许久浏览器,看着浏览器处理的前端代码日益复杂晦涩,与曾经自己写的截然不同,对前端开发究竟在做什么产生了好奇;二来这四年以来一直都是在家办公,也想尝试一下办公室生活究竟是什么样的感觉;三来不可否认地,也对加入初创公司,股权能升值实现财务自由抱有幻想。
我进入Canva以后加入了Editing团队,参与其编辑器的开发,某种意义上说也是最核心的团队了。我加入的时候正值用React重写整个编辑器的E2项目刚刚完成,主要的任务多是实现小的特性。印象比较深的是使用Intersection Observer优化了编辑器,让它不去渲染编辑器里视界之外的页面。这一优化帮助了编辑器从每个文档只支持20个页面逐步增加到了100个。
Editing团队虽然说是核心团队,但曾经也一度解散过,所有的成员被分散到了不同的团队去,但依然有例会来协调讨论。那段时间我被分到Collaboration团队,参与开发了元素锁定,编辑器内评论,以及显示在线合作者头像等功能。不用多长时间,公司当然就意识到没有Editing团队是不行的,所以半年之后这个团队就重新被召集起来了。
我最开始参与的一个主要项目应该是EditorX,是更大的WebX项目的一部分,这个项目是要让Web页面能够跑在移动设备上,在移动客户端里取代原生代码实现的编辑器。既提供更一致的体验,也统一了实现的代码,不再需要大量的iOS和Android工程师各自额外维护一份编辑和渲染的代码。许多移动端的工程师被重新培训成为了前端或后端工程师。这个项目的一大挑战是与不同的平台作斗争,iOS和Android的WebView有许多不同,其中很重要的一部分就是软键盘,它的大小、显示时机、对页面排版的影响以及副作用(如将整个页面往上推)各不相同。许多的时间花费在理解它们的行为和尽可能寻找共通的技术方案。
另一个参与的主要项目是白板。白板项目最大的挑战有两个,一是性能,二是页面没有固定的大小。为了让现有的代码能够支持白板页面,内部代码进行了大量的重构,也引入了许多的技术债,之后的很多年我们都还在还这笔债。作为白板来说性能问题是绕不过的,因为白板有无限的空间,自然用户也会倾向于放更多的东西。Canva的一个主要技术方向是使用React+HTML进行设计的渲染。虽然得益于Web特性,这一方向带来很多实现上的便利,比如文本天然可以选择,无缝使用<iframe>
嵌入外部组件,较容易地支持复杂的文本渲染特性(比如纵排文字)以及视频播放,但同时因为受制于浏览器实现,也存在跨平台的渲染有差异等局限性。这种方法更重要的一个问题是性能,设计中的每个元素为了各种原因需要许多的React节点和DOM节点,因而随着元素数量的膨胀,性能也急剧下降。正因为如此,即使做了许多的优化,在发布初期我们依然将白板上的元素数量限制在1000个,远低于基于<canvas>
渲染的竞争对手们的产品。
再后来我又作为主要构架师参与了Doctopus项目,这个项目在今年早些时候作为“Visual Suite 2.0 in one design”对外发布5,允许在一个文档里嵌入不同大小和类型的设计页面。Canva内部很多功能都被绑定在一个称为“doctype”的概念上,它用以表明比如一个文档是幻灯片还是一份A4打印件。在很长时间里,Canva的编辑器都假定在其整个生命周期中只存在一个doctype,于是很多功能依据这个固定的doctype在页面载入的时候就确定了行为。然而Doctopus完全打破了这个假定,将doctype从文档级别迁移到了页面级别,因而让这些行为也必须要能动态决定。为此许多功能都需要一定程度的重构,有的部分甚至需要从产品的层面重新思考,整个公司不少团队都参与其中。但作为构架师,我除了写了最早的技术文档和初期的一些基础部件的代码以外,基本上就只是在与其他人交流,提供思路和方向,而真正繁重的实现工作都是团队的其他成员和更多团队的同事完成的。
在Canva工作了六七年,超过我在Mozilla的时间,参与了大大小小许多项目。随着公司组织的变化和自己的职位更加资深,写代码的时间越发变少,更多的时候是花在审核代码和与不同的人交流,理解他们的需求并提供技术指引。到这时候我才真正认识到软件工程师的工作其实不是写代码,而是交流:与过去和未来的自己交流,与其他工程师交流,与产品和设计人员交流,与电脑交流6,通过视频会议交流,私下面对面地交流,在即时通讯工具上交流,写文档和注释交流。
Canva是一家很好的公司,我觉得自己能加入这家公司是十分幸运的,所以也对当时招我进来的招聘者心怀感激。
公司有很注重产品的文化,持续不断地做着详尽的用户调查。不只是调查问卷或者数据收集分析,更有直接去采访用户,一对一地观察用户的行为和理解他们的思考。也有专门的人去追踪用户反馈,无论是直接的报告还是社交网络上的评论,都会被整理反馈给产品团队提供参考。我想这一点或许正是Mozilla所欠缺的一块。Mozilla有着很多非常厉害的工程师,也有很好的工程文化,但它对待产品的态度和方法,以及顶层对用户的理解和产品发展方向的把控,至少在我观察到的范围内是有很大欠缺的。
很好的福利也是它的优点之一。每天都有花样丰富的早餐和美味的热腾腾的每天不同的午餐,还有持续供应的各式的零食、饮料、冰淇淋。团队也不时会组织活动,或是庆祝完成目标,或是单纯的团队建设。而活动的形式也很多样,从远程团队一起玩游戏、一起做手工,到线下聚会去实体密室逃脱、开卡丁车。公司里有专门的团队去寻找各种不同的有趣的活动来为团队提供参考。除此之外,满足一定条件的俱乐部,每月公司也有根据人数提供活动经费7。公司还给每个人一年三天的“Force For Good”假期,可以用于志愿者活动,而公司也有与慈善机构合作,组织一些集体参加的志愿者活动来使用这些假期。
我加入公司的这几年里,公司迅速发展,人数大概有我加入时的十倍之多,而估值相比我加入时也翻了四十倍。随着人员的扩张,组织结构和管理方式自然也在不断变化。我最初加入的时候基本上大家既没有头衔也没有级别之分,软件工程师就是软件工程师,我的经理(或者按照Canva的说法,教练)也是软件工程师。那个时候也没有太多组织结构,好像就只是各个team。我们team那时候每周五下午有一个Show & Tell的例会,报告过去一周实现的新功能和修正,最初的时候创始人也都会来参加,赞扬我们的成果。后来在team之上有了group,接着又有了supergroup和subgroup,最近又加了一级org。从所做的工作来看,我们当时的team现在已经是两个group了。职位上也引入了职位级别,再后来又通过不同的头衔将所有工程师和工程经理的级别也公开化了。
在Canva的这几年,我是切实地感觉到自己的成长。不再只是埋头写代码,对于大型项目在构架上的考量有了更深入的理解,还接触到产品和运营的很多方面,与更多的人交流,有了越来越大的责任和影响力(当然也有压力和burnout)。
工作之外
这十几年来大多数时间都献给了工作,工作之外能做的事情就十分有限了。工作以后就感觉时间过得很快,一年一年转眼间就过去了。这让我开始怀念起学生时代,至少每年都有两个长长的假期可以安排自己的事情。虽然年假也是有的,但总是觉得不舍得请,就算请了又觉得应该好好利用起来去玩,于是到头来也还是没时间做其他的事情。
过去这十多年我最主要的娱乐是看日本动画,几乎是固定的每天三话地看。有想看的新番就看新番,没有就补旧番。我对这个娱乐方式非常满意,不需要太多额外的准备工作,不太会失败,而且也方便控制时间。虽然我时不时也有玩一些游戏或看一些漫画和轻小说,但这些不像动画那样有一话一话的明显节点,让我很难控制投入的时间,容易沉迷进去难以自拔。
稳定而持续地看动画带来的一个额外的效果是让我对日语也越发熟悉,以至于前几年当我想着或许可以试试去考JLPT(日本语能力测试)时,发现最高级别的N1的难度竟然是我裸考也能轻松通过的8,于是就报了当年的考试。虽然说是认为裸考也能通过,但我也想借考试的机会再精进一下日语,于是买了词汇和语法的书在考试前的半年时间里积极地复习。最后当然是毫无悬念地通过了。因为我的日语主要来源于动画,而英语主要来源于工作上的交流,我甚至觉得我的日语在理解日常会话方面可能超过了我的英语。
自从大约Rust 1.0发布以来我就是Rust语言的拥趸,到现在我的个人项目的默认首选语言也依然是Rust。在Mozilla时加入Stylo项目开始在工作中使用Rust开发,而在Canva期间虽然很长一段时间没有用Rust开发,但也曾经和同事一起举办过Rust培训,还说服了公司将一个用C++写了原型的项目改用Rust实现。过去很多年里我也在Telegram上管理运营一个Rust社群“Rust众”,但之前因为在工作上burnout以及与群友意见不合而选择结束了这个群。回想起来在这个群上也投入了许多时间、精力和感情,像是开发了evalbot,编撰了FAQ。群的用户名rust_zh
还一度被Telegram收回,为此不得不花钱兑换了Toncoin去竞拍把它赎回来。但结束这个群对我自己来说或许不是坏事,至少感觉少了一个巨大的心理负担。曾经相当长的一段时间每天早上醒来第一件事就是看看群里半夜有没有出什么问题,解散以后就再也不会为此担心了。我想我果然还是不适合做管理类的工作,即使只是一个群。
2020年左右接触到了Beancount项目于是开始记账。虽然过去也曾经用过一些手机应用,但并没有坚持下来。使用Beancount进行复式记账让我接触到一些会计的概念和思维方式,比如应收款项是资产而应付款项是负债,比如平摊支出。有人记账是为了知道自己的支出情况以更好地做预算,但我基本上更多的是要对自己的财务状况有更全面的了解。不过有一份账本也帮助了我更好地准备每年的报税,以及管理我的投资组合。这几年为了这份账本写了不少插件,像是自动计算信用卡返利的插件、将信用卡交易推迟到账单日之后的插件、显示现金报表的插件等等。当然用的多了,对Beancount的设计也会有一些不满意的地方,但这就留到另外的机会讨论吧。
前两年搬家之后,在新家装上了很大的太阳能板和电池,并且接入了Amber开始使用实时的批发电价。9这也让我更加关注电力市场以及可再生发电相关的资讯。因为澳洲实际上屋面太阳能发电已经太多了,很经常批发电价都是负的,也就是输出电力给电网,电网还要收钱,于是为了限制太阳能发电而开始部署Home Assistant。然而一旦部署了这套系统,就开始想着将越来越多的设备和数据接入进去。近期的不少个人项目便是围绕它构建的,包括自己设计实现的仪表面板、一些终端设备的信息报告服务等等。
过去这十多年发生了许多事情,这里写出来的这些也只是其中的一小部分而已。很多事情已经不记得了,有一些记得但或觉得不值得写或觉得不想写出来。但无论如何,写到这里以我来说也已经非常长了,就在此收笔吧。
脚注
-
Gecko当时是第一个实现这一模块的浏览器引擎,Firefox里早在2014年就支持了,而Blink到了七年之后的2021年才实现,WebKit则是更久之后的2023年。 ↩
-
好像全悉尼除我之外只另外有一个人,而墨尔本会稍微多一些。当时虽然新西兰也没有特别多人,但奥克兰却有一间办公室。 ↩
-
不得不说Herman Miller的售后真的是好,买了这张椅子十年以后才知道,竟然是上门维修的,而且修的人还告诉我其他可以修的地方让我再去报修。 ↩
-
CSS Ruby里对空格的处理非常复杂。这个标准添加了五种新的容器:
ruby
、ruby-base-container
、ruby-text-container
、ruby-base
和ruby-text
。一个空格出现在这些容器之间的不同位置有完全不同的处理。有的空格需要被忽略,有的空格要创建新的匿名容器包裹。这些都必须被精确地处理以保证后面的算法能够正确执行。 ↩ -
过去主要通过代码与电脑交流,现在越来越多地还与LLM交流。 ↩
-
当然我觉得有些人实在是有点滥用俱乐部这套体系了,比如原神和崩铁俱乐部集体抽卡这种,还有许多美食俱乐部到各种餐馆吃饭的。 ↩
-
做第一份官方例题时,语法词汇错1/3,听力阅读几乎全对。 ↩
-
太阳能板和电池的投入还是相当大的,如果看投资回报的话,这肯定是一笔失败的投资,我已经不期望它能回本了。 ↩