译 · Jake Wharton 访谈:Android 圈最熟悉的那个名字

Jake Wharton 是我的偶像,也是很多 Android 开发者绕不开的名字。Retrofit、OkHttp、Dagger、Ktx... 这些项目或多或少都影响过我们写 Android 的方式。

前几天在网上看到他的这篇访谈,读完觉得很受用。里面既有他早年做 Android、做开源的经历,也有他对职业选择、会议、倦怠、家庭和工作方式的很多真实想法。这里把它翻译成中文,分享给同样喜欢 Android、Kotlin 和开源的朋友们。

⚡️ 欢迎来到 Effective Interviews 第二期 ⚡️ 这是一个文字访谈系列,采访全球 Android 社区里备受关注的工程师。

这一次,我请到了 Jake Wharton。我觉得他大概不需要介绍,但还是简单说几句。Jake 是 Cash App 的软件工程师。他创建并维护了许多 Android 生态里被广泛使用的开源库,从一些经典老朋友,比如 ActionBarSherlock、Otto、Picasso、Retrofit、ButterKnife,到更现代一些的 Molecule,也包括一些和 Android 无关的库,比如 Mosaic。

🟣 欢迎来到 Effective Interviews,Jake!👋 很高兴请到你。期待更深入地了解你 🙌

谢谢,大家好!我上周读了 Gabriel 的回答,觉得很有意思。那我们开始吧,看看接下来能聊到哪儿。另外,当年造了 Otto,真是抱歉。

Otto 是 Square 早年推出的 Android/Java 事件总线库,作者之一正是 Jake。它在早期 Android 开发中很流行,也确实解决过组件解耦的问题;但事件总线模式在项目变大后,容易让事件流向变得隐式,排查和调试都比较困难。后来 Android 架构也逐渐转向更明确、生命周期感知的数据流方案。

🟣 最初是什么让你想成为一名 Android 工程师?你又是怎么开始的?

钱!但不是因为当时 Android 岗位市场刚兴起、薪水丰厚------那会儿压根还没有这样的市场。真正吸引我的是 Google 在第一届 Android Developer Challenge 里提供的奖金;那次挑战赛与 Android 这个刚发布的新东西放出首个公开 "M3 SDK" 同期,时间是 2007 年。当时我还在读大三,一心想赢下比赛,从此再也不用正经上班。

我决定做一个绘图应用,支持形状、画笔、图层、图片导入等等。以我当时对这类项目工作量的天真程度来说,居然还真的推进得挺远。几周之后,形状、图层、混合模式和图片都已经能跑了。不过,我花了太多时间清理代码、拆库、优化架构,一直没打磨到足够参赛的程度。(幸好这不会成为我至今仍耿耿于怀的糗事!)

又过了两年,我才再次开始做 Android。那时我已经从 Windows Phone 换到了 iPhone 3G,这意味着我的 C# 技能再也帮不上我给口袋里的那台电脑写程序了。我买不起 Mac,而 Windows Phone 显然也快不行了,于是我开始在模拟器里写 Android 应用。

我的第一个应用叫 SMS Morse,它会把收到的短信用摩斯电码的节奏震动出来。我并不懂摩斯电码,但你居然能在模拟器上做出这种在 Windows Phone 和 iPhone 上完全不可能实现的东西,这太牛逼了。我唯一能在模拟器上测试的方法,就是把震动写到 Log.d 里,然后祈祷真正的 API 调用能正常工作。我一开始收费 0.99 美元,直到卖出的份数足够赚回在 Android Market 发布应用的一次性 25 美元费用,之后它就变成免费应用了。这个应用等会儿还会再提到......

接着,我做了一个叫 SMS Barrage(短信轰炸)的应用,可以把同一条短信反复发送到同一个号码,最多 1000 次,并且可以配置发送间隔。这样我去看 Pittsburgh Penguins 冰球比赛时,就能确保我所在的看台区域赢下那些按短信数量决定的抽奖奖品(他们那时候确实这么玩)。这个应用很快就因为违反 Market 服务条款被下架了。后来我发现,只要通过 Cydia 在越狱的 iPhone 上装一个终端,我就可以从命令行发短信(可能是借助了另一个 Cydia 应用)。于是写个简单的 Bash 脚本,我在 iPhone 上也能实现类似功能,只是用户界面没那么理想。如果那些年你和我坐在 Pens 比赛的同一个区域:不用谢!

总之,说回 SMS Morse。这个应用有一批忠实用户,大约 4000 人,按今天的标准,用户很少,但在当时,Android Market 的规模还只是以"几千个应用"来衡量,而不是几百万。Google 做了一件相当离谱的事:给 Market 里所有拥有 2500+ 用户、评分 3.5+ 的应用开发者寄了一台全新的 Nexus One。所以 SMS Morse 最后帮我拿到了一台 Nexus One,在那个时间点,刚好让我没有升级到 iPhone 4。从那以后我就一直在用 Android 手机。收到那台 Nexus One 也重新点燃了我业余时间做应用的热情,后来更推动我把它变成了正式工作。

彼时的 Android Maket(Google Play 的前身)

🟣 你做软件工程师多久了?到目前为止做过哪些技术栈?

我在 2009 年拿到了第一份工作,在一家国际搬迁公司,帮助人们搬到世界各地,通常是为大公司服务。我大学最后几年花了不少时间把 C# 练熟,而他们当时正在做一个 C# 桌面应用来管理所有业务。现在回头看,我觉得很好笑:他们居然是从 Web 应用迁移到桌面应用,明明应该反过来才对。但在当时,能靠写 C# 拿钱就已经是我的梦想了。

不过很奇怪,第一年过去后,我反而更喜欢那套遗留的 Java 服务端代码。周围所有人都忙着让 C# 桌面应用跑起来,所以我几乎可以自由支配 Java 服务端。我把公司迁到了 git 和 GitHub,加上了 Maven 构建系统,改用 Maven Central 上的依赖,并且把部署到 staging 和 production 的流程自动化了。这也意味着我在代码库的各个角落都有很大的实验空间。后来,在最后一年里,我又花了相当多时间把专用服务器迁移到 VMWare vSphere 上的虚拟化基础设施。能参与一套业务系统里这么多截然不同的部分,真的很好玩。

因为在那份工作里重新找回了对 Java 的热情,又因为我们把基础设施迁到了 VM,而它没有移动端应用可以控制,我就开始在业余时间给它做一个应用。我的 vSphere Android 应用最终可能从未真正成型,但 ActionBarSherlock 正是从那个项目里诞生的。这个库让我在 2012 年拿到了 Square 的 offer,也让我坚定了接下来专注于 Android 开发的决心。除了偶尔用 Rust、Docker 或 Raspberry Pi 折腾一些个人小项目之外,我一直以来都是一名 Android 开发者。

在 Android 3.0 (API 11) 引入 ActionBar 之前,旧版 Android(如 Android 2.x)并没有原生的顶部导航栏。ActionBarSherlock 允许开发者在旧版本 Android 设备上实现与 Android 4.0 原生 ActionBar 相同的功能和外观。

🟣 你是怎么开始接触开源的?它哪里吸引你?

和 Gabriel 一样,我初中时也有一台 TI-83,并且自学了 TI Basic,写一些能在考试里帮到我的应用。像 Drug Wars(哈哈)这样的游戏在当时开始流行,它们是以源码形式在人和人之间传播的,是非常好的学习材料。到了高中,我发现了 ticalc.org(它居然现在还在?!),可以在那里下载别人的应用、参与协作,也可以发布自己的作品。那时我完全不知道"开源"是什么。它只是让我感觉像是"人传人分享"的超级加强版。

整个高中和大学期间,我都在参与自己使用的软件背后的开源社区,但并没有发布什么东西。我当时在一个小团队里,我们负责组织 LAN party(局域网派对),所以部署了很多 Linux 系统和相关支撑软件,用来处理报名和比赛。我也帮忙运营过大学内部的 torrent tracker(BT 种子追踪器) 和 DC++ hub,用来分发......呃......就说是 Linux ISO 吧。住在我们大学宿舍房子里的人,电视和电脑上都跑着 VLC 和 MythTV,用来访问一个公共的 samba 共享。所有这些软件都是开源的。我们懂得足够多,已经能把事情搞得有点危险,但你不可避免会遇到超出自己能力范围的问题。参与这些社区,单纯是因为你想把自己的问题解决掉,并不是出于什么对开源的哲学信仰。但开源意味着我可以接触到更广泛的人群,从他们那里获得帮助,偶尔也帮助别人。

我刚开始第一份工作的时候,GitHub 已经有了。我把 SMSMore、SMSBarrage 之类的项目从一个私有 SVN 服务器迁了过去。也发过一些小项目,倒不是有什么特别理由,只是觉得它们没必要私有化(大概也不想给 GitHub 的私有仓库付钱)。后来我开始做 Android vSphere app,引入了一大堆开源项目,从 GreenDroid 到 Apache commons,再到一个 XML SOAP 库,等等。差不多一周时间,app 的基础功能就跑起来了,界面也不算难看;如果没有这些开源的东西,可能要花几个月。所以当我需要在 XOOM 上 Honeycomb 新内置的 ActionBar(🤦)和 Nexus One 上 Gingerbread 的 GreenDroid action bar 之间写一层 shim 时,我想都没想就开源了,我觉得,那些项目帮了我这么多,也许我可以把这份帮助传递给几个有类似需求的人。结果后来证明,是很多、很多、很多人。

可能有些人会觉得,我做这么多开源工作,是我在单方面造福大家。但考虑到开源生态里那种奇妙的"规模经济效应",我其实不这么认为。开源确实需要成本,但这些成本仅仅是建立在那些你本来就必须完成的核心开发工作之上的附加开销。所以,你真正在权衡的其实是:承担这点额外开销,换来与外部开发者(而不仅仅是你自己或公司发工资的员工)并肩协作、汇聚众人智慧的巨大收益,到底值不值。

无论是我的个人项目,还是我在 Square、Google、Cash App 做过的项目,我们都收到过一些非常牛逼的贡献,我知道如果不拥抱开源,这些事情绝对不会发生。能让我们的项目吸纳这些优秀的贡献,去反哺他人的项目,以及能和更广泛的人一起协作,把一个开源库打磨得更好------这些体验珍贵无比,拿什么我都不换。

🟣 开源项目可能非常消耗人。大家对开源维护者也不总是那么有同理心。你是怎么保持动力的?

很多时候,我搞开源项目的动机纯粹出于"私心"。说白了,我写这个项目单纯是因为我自己要用,而不是为了什么"造福社区"之类的宏大目标。单是这种心态就能省去极大的麻烦,因为我不需要去迎合任何人,只要满足我自己就行了。当然,如果有其他人参与进来,大家奔着同一个目标一起努力,那依然是一件乐事,但关键在于,(这种"利己"的初衷)能让你不至于被别人的期望所"绑架"。

至于那一小部分理直气壮的用户,提的 Bug 里连点有用的复现信息都没有,却催着你"赶紧修复 (ASAP)";提个需求(Feature Request)的语气,就像把你当成了他们的下属员工去使唤------遇到这种人,你别无他法,要么学着去包容,要么干脆学会无视。不然的话,他们早晚会把你逼疯。你能做的最优解,就是提前把规矩和期望定好(把丑话说在前面),然后只管专心去搞项目里让你觉得好玩的那部分。做开源本来就图个乐子,如果它让你觉得成了一种负担,那就说明有些事情需要改变了。

🟣 你是怎么想到要做哪些新开源库的?

这 100% 源于我自己,或者我们在开发 App 乃至编写其他库时,反复踩到的坑。我们从来不会"为了造轮子而造轮子",也压根没想着要去解决别人的问题。

至于到底什么样的代码才适合抽离出来做成库,其实并没有什么死规矩。当你在这个领域摸爬滚打,写过足够多的项目后,慢慢就会培养出一种直觉,能一眼分辨出:什么东西适合做成一个优秀的、可复用的通用工具,什么东西又和具体业务绑得太死、过于定制化。不过话说回来,有时候就算某个东西的应用场景很窄,我们也会把它开源,原因很简单------纯粹就是觉得它很有趣。

🟣 你觉得做开源工作有成就感吗?到目前为止,你最想分享的开源经验是什么?

看到自己的代码能惠及自身以外的群体,这种感觉当然很棒。不仅如此,开源还给了我们一个宝贵的机会,去和更广阔天地里的大牛们并肩作战------如果仅局限于日常的职业工作,我们可能永远也接触不到这些人。

我认为随着时间的推移,我们学到的最重要的一课就是:保持开源库的轻量和聚焦。Jesse Wilson 和我曾经一起做过一次关于开源的分享,当时他抛出了一条充满智慧的金句:我们要做的,是只包含所有人都必不可少的代码,并把其他一切(边缘需求)拒之门外。

因为,你个人想要的那 15% 的额外功能代码,跟下一个人、再下一个人想要的 15% 是截然不同的。当你试图去满足所有这些诉求,把一切都塞进库里时,你最终只会搞出一个无比臃肿、寸步难行的庞然大物,连正常的版本迭代都会变得极其困难。

🟣 在维护开源库时,你经常面临的最大挑战(挣扎)是什么?

除了刚才说的,要管住手,只留所有人都必需的核心代码来保持库的"小而美"之外,最大的挑战其实在于 API 的设计:你得让用户有能力"自己动手,丰衣足食",去解决他们特定的业务痛点。

我随口就能想到的好例子有:OkHttp 的拦截器 (Interceptors)、Moshi 的 JSON 适配器、Retrofit 的 Converter 和 CallAdapter 工厂,以及 SQL Delight 的列适配器。借助这些 API,开发者能以我们(库作者)根本预料不到的方式去扩展库的能力;同时,我们也完全不需要在库里暴露出成百上千个"配置开关"来支持那些五花八门的自定义需求。

只要打造出一套极其扎实的核心 API,再预留好恰当的"钩子 (hooks)",让用户能把自己 App 独有的业务逻辑无缝插进去,这个库就会爆发出强大得多的能量。

遗憾的是,并不是所有类型的开源库都适合提供这种强大的用户侧 Hook 机制。但这也没关系。为了坚守开源库的轻量和聚焦,你就必须直面第二大挑战:要有绝对的定力,不断地对"需求蔓延(Scope Creep)"果断说"不"。

🟣 你在 Square 待了很长时间,后来跳槽去了 Google,但之后又决定跳回老东家。从社区的角度来看,这波反复横跳非常有意思。能多聊聊这段经历吗?

在 Cash App 的时候,大约从 2014 年起我们就一直在不遗余力地在 Android 项目里硬推 Kotlin。当时我根本不在乎 Android 官方未来到底会不会支持它。Jack 编译器的"暴毙"意味着 Kotlin 无论如何都能在 Android 上跑得通(退一步讲,就算跑不通,当时还有 Jill 编译器能给我们兜底)。我当时唯一的诉求,就是呼吁 Google 用 Kotlin 项目去跑跑 Android Studio 和 AGP (Android Gradle 插件) 的测试,尽量别三天两头就把我们的集成给搞崩了。

2017 年 Google I/O 大会(Steph 在那次大会上宣布了将 Kotlin 作为 Android 官方支持语言的史诗级消息)的前几个月,Romain Guy 联系了我。他透露这事极有可能会成,并表示他们那边可能会有一个职位,来帮着规划"Android 上的 Kotlin 生态"究竟该是什么样。当时我在 Cash 这边过得很好,但这(去 Google 操刀的机会)确实是千载难逢的。

在 I/O 大会前一周,我飞去 Google 面试。那天早上 Romain 去大堂接我进去,就在我们准备穿过安检闸机时,Steph 和 JetBrains 的 CEO Maxim Shafirov 正好走出来。由于 Google 偶尔会搞的那种"顾问委员会",Steph 和我互相认识;而 Max 和我则因为在 Cash App 做的那些 Kotlin 相关工作而互相久仰大名。我们心里都跟明镜似的,完全清楚对方为什么会出现在那儿,但因为各自都背着保密协议 (NDA),加上又身处公共场合,我们最后只能硬挤出几句尴尬的"嗨"和"拜拜",或许还交换了一个"心照不宣"的"祝你好运"。

除了面试,在 I/O 大会前一个月,我还被邀请和我的朋友 Christina Lee(她也是 Kotlin 的铁杆拥趸)一起做一场演讲,主要是为了帮官方在社区里背书、拉升信任度。尽管经历了面试,我们也在筹备这场演讲,但其实直到 I/O 主题演讲的前一天,我们才最终确信发布会真的要官宣了。

所以我顺理成章地接下了那个职位,一切都感觉都很自然。作为一名 Android 开发者,我已经花了三年推动 Kotlin,脑子里早就攒了一长串"如果我是 Google 我会怎么搞"的 to-do list。好吧,现在我就是 Google 的人了,所以我就直接把这些事给落地了:代码风格指南 (Style guide)、KTX 扩展库、Framework 层的可空性注解(Nullable/NonNull)、@Parcelize 插件的落地、R8 编译器的优化,以及推出 View Binding 来帮大家彻底干掉 Kotlin Synthetics(不好意思,但我一点也不后悔),等等。在那段时间里,以及在我离职之后,有无数人为了让这门语言在 Android 生态中立足而付出了努力,他们的贡献远超于我。我最大的成就,其实不过是做了最初倒下的那一小块多米诺骨牌,从而推动了后面更大、更壮观的骨牌接连倒下。

我原本的计划就是待两年,然后跳回去,亲自在业务里用用我自己造出来的这些工具。我没法脱离"真实的业务场景"去凭空造轮子。 有些人或许可以,还有些人"自以为"可以。但我发现,这样会让你严重脱离对实际痛点的感知,也无法判断你到底有没有真正解决问题。

此外,我在官僚主义和技术层面上也开始遇到太多的掣肘。就拿"要不要把 Kotlin 设为主要开发语言"这事儿来说,我们开了好几个月极其折磨人(想自戳双目)的会,最后只憋出了一个模棱两可的词:"Android-first(Android 优先)"。要不要支持 Kotlin 多平台 (KMP) 呢?又开了好几个月同样辣眼睛的会,最后只得出了一个"可能吧"。我曾搞了个原型,把 collections 库移植到 KMP,但 Android 的构建系统只跑在 Linux 上,所以 CI 只有 Linux 运行节点。那接下来咋办?而且,因为代码仓库是托管在 AOSP(Android 开源项目)里的,AndroidX 团队沿袭了那种"把所有的工具链和依赖都一并检入代码库"的老套做法,这在多平台环境下会变得极其复杂。三年过去了,这方面依然没有看到足够的实质性进展。

AndroidX 在很大程度上被严重拖了后腿,原因就在于它依然死死绑定在单体仓库 (monorepo) 里、依附于 AOSP、使用着 Android OS 的那套 CI 系统,而且被一堆冗余的基础设施给"卡着脖子"------而这堆基建的存在,纯粹只是为了试图绕过前面列出的那三个问题。AndroidX 这棵大树上有无数个叶子节点项目,其实更适合把它们单独拎出来,在 GitHub 上建独立的仓库。你只要去看看 KSP 项目,就能大致想象出每一个 AndroidX 库原本可以(且应该)怎样进行开发和发布。唉,一提起这个我就很难不开启无限吐槽模式,因为当时就让人无比抓狂,时至今日依然如此。这一段篇幅太长了,咱们就此打住吧。另外,#FreeCompose! (让 Compose 从 AOSP 的繁文缛节里解绑吧!)

当年我离开 Cash App 时的临别留言是:"我将处于 OOO (Out of Office,休假) 状态几年",所以在"休"了 2 又 3/4 年之后,我回去了,准备去好好享受一下我在 Google 亲手种下的果实。

🟣 你现在在 Cash App 的角色是什么?平时一天的工作大概是什么样?

我的主要职责是 Redwood 项目。它是一个跨平台的 Compose runtime(运行时),专门用来对接各个平台底层的原生 UI 工具包,借助它,我们可以只写一套通用的 Compose 代码,就能同时在 Android、iOS 和 Web 端跑起来,而它在底层会分别被渲染成传统的 Android Views、iOS UIViews 以及 HTML DOM。当然,这种架构也完全不排斥你混用 Compose UI 或 SwiftUI。

Redwood 本身是支持原生编译到各个平台的,但我们还有另一个项目叫 Zipline,我也参与维护。它直接把 QuickJS 引擎内嵌到了我们的 Android 和 iOS 宿主 App 里。简单来说,Zipline 的黑科技在于:它能让宿主环境里的 Kotlin/JVM 和 Kotlin/Native 代码,通过 kotlinx.serialization 跨越语言边界,与运行在 JS 虚拟机(VM)里的 Kotlin/JS 代码进行无缝通信。

重点来了:我们现在就是把 Redwood 的 Compose 代码放在这个 JS VM 里跑的。Compose 经过重组(Recomposition)后产出的结果------也就是一系列极其细粒度的 UI 差异(Diffs)------会被直接发送给宿主 App,并实时应用到原生的 UI 树上。

这套架构简直酷毙了!因为它不仅能让咱们在日常开发时,几秒钟就能热更新 Compose 代码看到效果;更绝的是,它能让我们彻底绕过应用商店(App Store / Google Play)的审核,直接对线上跑着的 App 推送动态更新。

除了这两个主心骨项目,我们还有一些像 Molecule、Turbine、Licensee、Retrofit 以及 SQL Delight 这样相对小一点的库,由我和社区里其他才华横溢的大佬们一起维护。虽说公司内部有海量的业务代码把这些轮子都串联在了一起,但我依然觉得自己极其幸运------因为我绝大部分的工作时间,依然是实打实地花在搞开源项目上。

我的日常作息并没有太严格的条条框框。通常每天早上,我会先花一两个小时来"分诊 (Triage)"------也就是集中处理内外抛过来的各种 Issue 和 PR。剩下的一整天就是个大杂烩:要么顺手秒掉几个能一口气结案的小任务;要么就在那些庞大的、跨度极长的史诗级(Epic)架构任务上,再一点点地往前啃点进度。

🟣 你和我一样,也是个父亲。有了孩子之后,你觉得自己的人生优先级变化很大吗?你看待生活的方式有变化吗?

我不太确定我人生的优先级是不是真的发生了什么改变。但是,当一个原本毫无自理能力的小家伙与你产生了那种深度的内在绑定时,这确实改变了我看世界的视角。至少在他们人生的起步阶段,他们能否在这个世界上顺利立足,完全取决于你的一言一行。

这个过程会让你深感自身的渺小与谦卑,充满挑战,乐趣无穷,同时也会让你累到完全虚脱。不过说到底,养孩子这件事其实很难真的彻底"搞砸",但我依然在不断地学习,试图把"父亲"这个角色做得更好一点。

🟣 替各位父母问一问:有孩子在很多方面都非常不可思议,但说实话,它需要大量时间。你是怎么保持高效产出的?

哈哈,如果从咱俩在这次采访进度上的反复拉扯来看,或许我压根就没保持住高效!

平衡这两者确实像是在抛接杂耍,而且难度还在不断飙升。时间变成了前所未有的稀缺资源。因此,在防止别人挤占我的时间这件事上,我变得极其冷酷无情。我几乎没有什么例会------只保留与我手头工作直接相关的、最核心的会。可惜我们公司办公用的是 Slack,所以我 90% 的时间干脆直接把它关掉。就算我打开了 Slack,我基本上也只看大约三个频道。

要完成我这种类型的工作,最简单的方法就是留出大段不受干扰的时间(制造"离线"状态),让自己能真正进入专注干活的心流。而在这些大块时间的间隙,我才会去扫一眼 Issues、PR,以及那个"噩梦般"的 Slack,确保没有人因为等我而阻塞(blocked)了进度。我一直在管理大家对我的预期:我的响应是"异步"的。但同等重要的是,我也在向外界传递一个信号:我依赖别人做的事情(比如回复我的消息或给我做 Code Review),同样也是异步的。

至于搞业余项目,我的策略很简单:把任务拆成更小的颗粒度。一周里朝某件事迈出五个小碎步,也好过试图大跳五次却每次都失败。这也会倒逼你认真审视:哪些项目配得上你的时间,它们是不是真的值得。如果我有无限的脑力和时间,那当然,脑子里蹦出的每一个随性的点子我都会去折腾。但是,当你的早晨和夜晚被彻底榨干,当你对优质睡眠的渴望直线上升时,你某天突然挤出来的那转瞬即逝的两小时,其价值将暴涨 10 倍。所以,要把时间花在刀刃上。

🟣 回首至今的职业生涯,如果能重来,你会改变什么吗?你会给年轻时的自己什么建议?

这个问题我琢磨了一会儿。以前我总会在心里念叨,我应该更早搬去旧金山,因为我很喜欢那段时间;或者,我应该早一点正式踏入 Android 开发的职业赛道;甚至会想,当初要是不折腾跳槽去 Google 也许更好。

但现在,我的答案很简单:"不会。"我其实什么都不想改变。我现在很幸运,能和好的人一起做有意思的事,,而这是前面所有经历,加上一大堆运气共同带来的结果。干嘛要去动它呢?

至于给年轻人的建议,我觉得年轻的时候,人很容易"上头",极其容易被所谓的"产品宏大愿景"给深度洗脑。这会导致你个人的成就感,会被这个产品在市场上的起起落落死死绑定在一起。

现在我更关注身边的人,以及寻找有挑战的技术问题来解决。产品往往是昙花一现的,而人脉的羁绊和攻克技术难题带来的成长,才是历久弥新的。 从后者中去获取成就感,要简单和纯粹得多。如果我能早点悟透这个道理就好了。

当然,我依然会在乎一点产品,但现在的态度完全是基于职场的"契约精神"。就像《广告狂人》里的 Don Draper 绝对会双手赞成的那句话经典台词一样:"毕竟公司付钱给我,不就是为了干这个的嘛!"(拿钱办事,天经地义)

🟣 在你的职业生涯中,你做过管理岗吗?你喜欢带团队吗?如果有过,你会推荐大家去尝试吗?至少从外界看来,你似乎就是一个非常出色的独立贡献者 (IC, Individual Contributor)。

我这辈子离"管理别人"最近的一次,大概就是带过一个季度的实习生。而且直到这名实习生来公司入职报到的第一天,都没人通知我这件事。简直是"大大的惊喜 (Surprise)"!更抓马的是,在他实习才刚满两周的时候,我们当时正在搞的一个原型项目直接被砍掉了。这就导致我们在接下来的好几个月里陷入了无所事事的空窗期,根本没有实打实的业务需求可做。可以说是"双重惊喜"了!

于是我就给这位同学提了个建议:不如去把当时 Android 生态里现有的所有数据库框架都调研一遍,写个对比报告。等我们开展下一个项目时,就能决定到底是用现成的,还是干脆自己手搓一个。那是在 2014 年。而那次调研最终结出的硕果,就是如今的 SQL Delight------这不光是 Android 平台上最顶级的数据库工具。考虑到我当时仅仅是动了动嘴皮子(几乎啥也没干),你很难想象对我来说还有比这更"躺赢"的巨大成功了。

Alec(也就是 SQL Delight 的作者)最终全职加入了我们。不仅如此,他现在已经成为了一名真正的 Manager,干着实打实的管理工作,把我和其他同事的工作体验安排得极其舒服。与此同时,在一众优秀贡献者的协助下,他依然把 SQL Delight 维护得极其出色。

我很难想象自己去带团队、管理别人,但长远来看,我也不会把这条路彻底堵死。不过就目前而言,能安安静静地做一个 IC(独立贡献者),我就已经超级开心了。

🟣 你更享受在平台/架构团队 (Platform Teams) 还是业务团队 (Feature Teams) 工作?

我真正享受的,是在贴近具体业务线团队的语境下,去做平台架构层面的工作。

我来这儿可不是为了像搞学术研究那样,把自己关在"象牙塔"里闭门造车,搞出个自认为完美的架构,然后直接"隔墙扔过去"丢给业务团队,让他们自己去头疼怎么接入。

即使我现在身处一个偏向于平台架构的团队,我们依然负责维护 App 里那些"公共宿主/共享区域"(Shared Spaces)------而这些公共区域本身,就被视为一项实打实的业务功能 (Feature)。

这就意味着,我们在开发这些公共区域时,必须直接"吃自己的狗粮"(亲自用自己造的轮子);同时,其他所有业务线如果想在 App 上跑起来并展示出来,就必须跟我们进行对接集成,从而也必须使用我们打造的这套底层基础设施。

🟣 Dagger 1 和 Dagger 2,如果从功能和灵活性来看,你更偏向哪个?

我觉得它们在各自的时代都是正确选择。

Dagger 1 让我们成功摆脱了 Guice。Guice 实在太慢了,而且随着 App 变大只会越来越慢。但更重要的是,它证明了一个真理:随着依赖图(Dependency Graph)和团队规模的不断壮大,编译时的依赖图校验是一个绝对必不可少的核心特性。不过,就像 Guice 和许多其他注入框架一样,Dagger 1 在底层是建立在一个"服务定位器(Service Locator)"模式之上的,并以此来执行自动依赖注入。这就导致它在"编译时和运行时究竟能做多少性能优化"这件事上,存在着严重的局限性。

Dagger 2 则带来了一个很新颖的理念:在编译期计算完整依赖图,并在依赖的提供方和使用方之间直接生成硬链接代码。 结果就是它快得飞起。而且正因为它生成的那些玩意儿看起来跟手写的常规代码毫无区别,所以这其中很大一部分代码能在打包阶段(比如被 R8/ProGuard)直接优化掉。

放在更大的社区语境下来看,我认为这俩框架都深受 Google 那套糟糕的官方架构指南和基础库的毒害。他们(官方)出的那些组件,没一个是为了支持"控制反转(IoC)"而设计的,而且他们的 API 设计简直是在公然鼓励开发者破坏分层架构。 无论是 Dagger 还是任何 DI 框架,只有在你坚持极其纯净的架构分层,并且使用相对纯粹的普通对象(Plain Objects)来封装业务行为时,才能发挥出最大的威力。然后,你再把这些封装好的对象注入并组装到那些"集成点"上------也就是咱们在这个世界上每天都要写的 Activity 和 Fragment。

如果你非要把实打实的核心业务逻辑(而不仅仅是胶水代码)塞进 Activity 和 Fragment 里,那你绝对会被坑到怀疑人生。你知道什么代码最容易写单元测试吗?是一个通过构造函数注入(Constructor-injected)、生命周期完全由你掌控的纯对象(POJO)。你知道什么代码最难测吗?是一个不仅要负责渲染 UI,还非得伸手去包裹它的 Activity 里硬掏依赖,强行给自己做成员注入(Member injects)的 Fragment。简直恶心(Yuck)。

现在我希望大家都在用 Anvil(注:Square 出品的 Dagger 扩展库)。如果你非得用官方的,那用 Hilt 也行。但讲真,最好还是用 Anvil。

另外,如果你是自动化依赖注入的死忠粉,同时又对 KMP 感兴趣,那你绝对要去试一下 Eva 开发的 kotlin-inject。

🟣 最近有没有什么让你特别期待的项目?

Zipline 目前是基于在 QuickJS 运行时里执行 Kotlin/JS。为了让这套执行机制尽可能地快且高效,我们在底层玩了不少"黑科技(奇技淫巧)"------比如,我们实际喂给引擎的是 QuickJS 内部专属的字节码,而不是真正的 JavaScript 源码。但遗憾的是,我们最终还是被两点严重拖了后腿:一是 Kotlin/JS 目前还不支持导出 ES2015 标准的产物;二是 JavaScript 天生残疾,压根就没有 64 位整型(Long)这个数据类型。

随着 Kotlin/WASM(WebAssembly)技术线的逐步成熟和上线,我们已经迫不及待地想要开始测试,把底层的运行时从 JS 全面切换到 WASM。既然 WASM 在 JS 能跑的地方全都能跑,而且它作为一种原生字节码格式,其执行效率和深度优化的空间,绝对是纯文本格式(JS 源码)所无法比拟的。

更爽的是,因为我们早就在 Zipline 里把 JS 相关的恶心细节,全部完美隔离(隐藏)在了 Kotlin 接口之下,所以未来这波向 WASM 的底层大换血,对业务侧使用该库的方式没有任何影响(唯一改变的仅仅是你底层加载代码的方式,业务层的 API 调用连一行都不用改)。

🟣 你还有什么其他的爱好?我好像有时会看到你发一些和车相关的内容。

是的,其实我从大学起就一直是个车迷,也很迷赛车游戏。但直到最近我才突然"顿悟"------根本没什么能阻止我在现实生活里也去下场玩真实赛车。

几年前,我和几个同搞 Android 开发的老铁去参加了一个为期两天的宝马 M 驾控培训课程(M School),自那以后我就彻底"入坑"了。现在,我不仅有一辆平时能代步、周末能下赛道的走街车(street car),还专门搞了一辆纯粹的赛道化用车(track car)。目前阶段,我每年大概也就参加几次开放式的赛道日(Open Track Events)和高性能驾驶培训(HPDE)。等孩子们再大一点,我可能会去考个正式的专业赛车执照。不过就眼下来说,能不断刷新自己的圈速(Racing against your own times),也已经足够快乐了。

除此之外,我也花了不少时间看电影、追剧和看书。相比于编程时那种必须对全局保持"绝对掌控"的神经紧绷感,看书看剧时,能把这种控制权交出去,任由别人来主导剧情的走向和节奏,简直是一种极其清爽的"大脑按摩"。 我最近刚看完的一部电影是《单身动物园》(The Lobster),目前在追的剧是《双城之战》(Arcane),正在读的书则是《沙丘救世主》(Dune Messiah)。

🟣 你怎么看会议?

开会确实是通过"同步"讨论和拍板决策来推进项目进度的绝佳方式。在搞开源时,我们偶尔会开那种"史诗级"的碰头会------大家聚在一起,规划出未来需要协同落地的大量工作,然后在此后的 6 到 12 个月里埋头执行。这种会议的效果极好,因为它们极其稀缺,所以每个人都会打起十二分精神,力求让会议产出最大化。而在这些大决战式的会议之间,大家基本都是靠"异步"的方式来沟通和协同的。

但形成鲜明对比的是,现在有太多那种"雷打不动"的定期例会(Recurring Meetings),这反而毁掉了一场好会议本该具有的效率。也不是说这种例会就绝对毫无建树------偶尔也会有点用。但这玩意儿一旦加上了日历,就几乎永远不会消失,它们只会像洗不掉的污渍一样,在你的日程表上越积越多。很多时候,我们不过是在"走个过场",心里祈祷着这次会能有点价值,但现实往往是:哪怕这个会只有区区 30 分钟,它也像一个悬在头顶的干扰源,明明只有 30 分钟,却能打乱你一天里的好几个小时。

我个人更倾向于尽可能地采用异步协作。也就是通过长文讨论、Issue 追踪系统、提 PR(Pull Requests)之类的形式。在这种模式下,无论大家是横跨多个时区、刚休完长假回来,还是因为要兼顾生活琐事每天只能榨出一点碎片化的时间来干活,这些都不重要------每个人都应该感觉自己被包括在内,也能够参与进来。

偶尔来点"短平快"的沟通(IM 聊天)也是可以的,但这需要优秀的工具来辅助:它得能把高频活跃的讨论浮现出来,并且在话题发散时,允许你对信息流进行重新分组和整理。很不幸,我们在工作中用的偏偏是像 Slack 这种设计糟糕的短消息工具,它本质上就是毫无逻辑的"线性聊天",外加极度敷衍的 Thread(回复串)功能。相比之下,你看看 Zulip 这种工具在 Rust 和 WebAssembly (WASM) 社区里简直是大放异彩,我真希望大家能多用用它。

🟣 这是一个我很喜欢问的问题,因为我现在正在写一本关于软技能的书。很多人会掩饰自己正面临"职业倦怠(Burnout)"的事实。你有没有经历过职业倦怠,或者出现过相关的症状?你是如何克服它的?对此你有什么建议吗?

肯定有过一些初期的症状,但我从来没有在任何项目上彻底"崩盘(硬性倦怠)"过。

我有三个常用的应对法宝:

首先,我总是尽量保证自己在任何时候都有不止一件可以做的事。而且不能只是简单的事,比如我正在做的功能里的另一个任务。它得是那种方向完全一百八十度转弯的东西。我们公司的技术文化以及我负责的项目性质,赋予了我这种特权。公司内部有极其多样的开源库、编程语言和平台,这就让我可以花上几个小时、一整天甚至一个月的时间,在不同的技术栈之间反复横跳换换脑子,从而持续保持产出。遗憾的是,并不是所有工作环境都有这种自由度。要想把这招变成你随时能打出的一张牌,是需要很长时间去积累底气的。

其次,至少在有孩子之前,我会尽量根据自己的状态灵活安排工作时间。很多编程工作都是创作型劳动,你很难、甚至根本不可能去强迫自己进入状态。事实上,你越是强迫,造成的伤害可能越大于好处。去遛遛狗,去看场电影,或者干脆打一整天游戏。就算在工作时间去干这些事也没关系,而且千万别告诉任何人(悄悄摸鱼)。 但是,一旦你进入了"心流(in the zone)",那就顺着状态一口气狂撸 12 个小时代码。只是记得要平衡回来,比如第二天别工作之类的。真正能有效管理你的,只有你自己,但这一切有个大前提:你得确保老板对你的工作成果挑不出毛病,而且同事们对你的"在线响应状态"感到满意。

最后,把那些毫不费力的简单任务"囤"起来,就像存一笔"未雨绸缪的摸鱼基金(rainy-day fund)"一样。 它们能让你保持产出,但不需要你投入太多高强度劳动。你可以用它们让别人觉得你仍然有产出,同时自己在精神上放松一下。它也能在你深陷倦怠期后,帮你重新找回"进度条在动"的成就感。随你怎么用。这里最难拿捏的其实是选任务的火候:你得找到那些你能轻松秒掉,但又不能太过于"低垂的果实(low-hanging fruit)",免得等你真需要拿它们来"凑数"时,它们早就被别人顺手给干掉了。

我敢肯定,有人看了上面这些招数会大骂:"卧槽,这些条件我特么一个都达不到!" 这种现状确实很悲哀。这意味着你周围的职场文化和环境,本身就是在逼着你走向职业倦怠,此时不管是我的招数还是别人的偏方,可能都救不了你。又或者,单纯只是你的工作方式刚好和我不同而已。毕竟,软件工程里如果真的存在"银弹(Silver Bullet,一劳永逸的解决方案)",那职业倦怠也就不会成为咱们这行反复发作的顽疾了。

🟣 虽然我觉得(在咱们这个圈子里)应该没人不知道你了,但按惯例还是问一句:读者们可以在哪里关注到你?

我在 Mastodon 上的账号是 @jw@jakewharton.com。或者再过几周,大家可以直接来 KotlinConf(Kotlin 开发者大会)现场找我面基!

🟣 非常感谢 Jake 和我们分享,太棒了 🙏 回头见!🙋‍♂️

相关推荐
三少爷的鞋1 小时前
Android Data 层 Flow 最佳实践:以冷流为基础,按需转热,避免过早共享状态
android
私人珍藏库9 小时前
【Android】Soul v5.86.0 内置模块版
android·app·工具·软件·多功能
千里马学框架10 小时前
aosp新增窗口层级 Type 完整实现方案(有源码)-wms需求和面试题
android·智能手机·架构·wms·aaos·车机
程序员cxuan14 小时前
我花了两天时间,终于把 Codex 额度掉太快的问题整明白了!!
人工智能·后端·程序员
“码”力全开14 小时前
解耦异构算力与多协议接入:基于Docker与源码交付的开源企业级GB28181/RTSP边缘计算AI视频管理平台架构深度解析
人工智能·docker·开源
小北的AI科技分享15 小时前
指尖上的工业革命:Open Claw如何重塑现代抓取技术
开源·抓取·
峥嵘life15 小时前
Android 蓝牙设备连接广播详解-2026
android·python·学习
冬奇Lab16 小时前
每日一个开源项目(第117篇):Recordly - 零剪辑基础也能制作电影级产品演示视频
开源·资讯
FIT2CLOUD飞致云17 小时前
支持AI网关和Skills Hub,1Panel企业版正式发布
ai·开源·1panel