阅读视图

发现新文章,点击刷新页面。

现代浏览器插件开发指南

最近又写了一本小书。

这本小书定价 29 元,你可以在 这里 购买 PDF 版本。如果你有特殊需求,也可以购买 Markdown + PDF 版本,价格是 49 元(为了增加盗版成本)。

这本书篇幅不长,但因为比较小众,所以定价稍高,请读者自行斟酌购买。

这是一本关于浏览器插件开发的书。这本书面向的是那些希望开发自己的浏览器插件满足自己需求的,也许你是专业的前端程序员,或是略懂前端代码的后端人员,在 AI 的加持下,大概都能从中受益。

之所以想写这本书,是因为我在过去一两年开发过不少浏览器插件(如微信读书笔记同步助手 Notepal, 浏览器 AI 助手 SumBuddy, 专注插件 等等标签 等等),积累了不少经验。这几年越来越多人开始做浏览器插件,尤其是在 AI 时代,浏览器插件作为重要的输入入口,能做的事情越来越多。我想可以把这些经验分享出来,让更多人可以开发满足自己需求的插件。

插件开发其实不是一件很难的事,特别是现在出现了一些成熟的框架,比如 Plasmo, WXT, CRXJS 等。这些插件把代码构建、打包过程都封装得很好,开发者只需要关心插件的逻辑即可。

我自己用过 Plasmo 和 WXT, 这本书我会使用 WXT 进行教学。因为经过自己的长期实践,我认为 WXT 比 Plasmo 的设计更加合理,配套的工具更好用。当然每个框架有每个框架的利弊,即使你想使用其它框架,你还是能从这本书收获很多框架以外的知识,因为框架只是辅助。

另外,本书以 Manifest V3 为背景编写,不会讨论 Manifest V2 的内容。如果你不知道两者具体的区别,你也不需要知道(因为我也不知道)。简单来说 V3 比 V2 在安全性上做了更严格的控制,这一点虽然备受争议,但对于大部分的情况不会有太大的影响。

本书在涉及 UI 开发的部分我会使用 React, 这是我自己日常使用的库,对于使用 Vue, Svelte 等其它库的读者,也不需要担心,用什么框架并不是那么重要。并且 WXT 也支持不同的 UI 库。如果你想用其它库,翻看 WXT 的文档即可。

本书的实战中,我不引入任何样式库,因为会增加额外的和插件开发无关的复杂度。请读者不要计较实例的美观性。

读这本书前,我默认读者了解以下技术:

  • 了解 JSON 这种数据结构
  • 了解基本的 JavaScript/TypeScript, HTML, CSS 知识
  • 有能力搭建 Node.js 开发环境

读完这本书后,读者能学习到开发一个浏览器最重要的几个知识点:

  • 数据存储
  • content script UI 的构建
  • 消息传递

这几个知识点贯穿了所有实际场景必需的元素,我在开发自己的插件时,基本是围绕这三个基本的功能展开。至于其它更多的插件 API, 读者应该自己按照需求查看文档使用。

在讲解消息传递的实例中,我用 AI 消息流作为例子讲解,对于需要开发 AI 相关的浏览器插件的读者应该很有启发。

目录

  • 概述
  • 插件的基本结构
    • manifest
    • popup
    • content script
    • options
    • background script
  • 插件 API
  • 创建插件开发项目
  • 基本数据存储:Todo List 实战
  • 进阶数据存储
    • 监听数据变动
    • 数据迁移 (Migration)
  • Content Script 基本知识
    • 动态注入 content script
    • 在 content script 中渲染 UI
    • 真实环境中的一些经验
  • 消息传递
    • 单次传递
    • 长连接传递
      • 实战: 渲染 AI 消息流
  • 多语言支持
    • 插件原生多语言机制
    • 使用 i18next
  • 附录
    • 最小权限原则
    • 处理用户登录
    • host permissions
    • 定时任务
    • 自动生成不同尺寸的 icon

一些预览

alt text

alt text

我们高估了智力的重要性

在 AI 越来越强大的这段时间里,我思考得比较多的一个问题是,我们是否高估了智力的重要性。

引起这个思考的原因有很多,一是大语言模型的进化程度让我感受到,语言模型在智能(推理、学习速度、知识广度等)方面已经远远超越人类,而这样的智能是每个人都能运用的。也就是说,人类完全可以把需要智能的行动交给 AI 处理,人类更多地是负责决策层面的工作。

二是因为我自己就是一个智力平平的人,小学当我学到除法的时候,我花了很长时间才理解除法是什么。我在开始学 JavaScript 的时候,也花了很长时间理解什么是 callback, 为什么函数能作为参数被传递和调用。

但我这个智力平平的人还是得到了算是不错的成果。我总是对别人说,我不是一个很聪明的人,我只是 13 岁就开始学编程,笨鸟先飞罢了,很多人大学学一年就能超过我的水平。所以我才更深刻地体会到,正常水平的技术,往往通过时间可以弥补,在这条水平线,不需要很高的智力。

不过,即使通过时间可以弥补智力的不足,但不是很多人能在这段时间里坚持下去。这也引出了我认为智力被高估的同时显现出来了另一个问题 —— 自我效能 (self-efficacy) 的重要性被远远低估了。

所谓的自我效能,是指相信自己有能力成功完成特定任务或应对特定情况的信心。很多复杂的因素决定了一个人自我效能的高低,这并非天生的。成长过程中长辈的态度、通过对他人的观察、个体与环境互动等等都影响一个人的自我效能水平。

在同样面对一个问题,自我效能低的人,看到的往往全是问题,最终放弃。而自我效能高的人,有强大的信念认为可以解决问题。前者也许在智力上比后者更高,但后者可以通过这种信念一直前进,超越前者。

图由 gpt-4o 生成

尤其是在 AI 时代,智力变成了一种更容易弥补的差距。我认为智力是边际效益递减的,除非超过了某个阈值。但这永远是很小一部分的人。像我这样智力平平的人是多数,我们只是站在了巨人的肩膀上。就像 Dijkstra 只有一个,这个世界也需要他这样的天才,但我们还是可以享受他给我们带来的成果。

这不是反智,而是我认为,智力水平有一个临界点,对于临界点以下的人,智力的重要性被高估了,因为智能越来越不稀缺,稀缺的是自我效能,是主动利用智能的人。

自我效能是完全可以通过后天训练的。自我效能是心理学家 Albert Bandura 提出的概念。他总结了影响自我效能的四种因素:

掌控经验 (Mastery Experiences)

个体通过亲身成功完成任务的经验来建立自信。反复的成功会增强自我效能,而失败(尤其是早期或没有应对策略时的失败)则会削弱它。

我觉得这里指的「成功」并非大成功,而是细微的成功。例如在我学习编程的早期,我通过写出各种各样的小程序获得这种成功感,对我建立技术自信有很大的帮助。

替代经验 (Vicarious Experiences / Modeling)

观察与自己相似的人成功完成任务,会提升观察者对自己也能做到的信念。看到别人能行,会觉得“我也许也可以”。

对我来说,小时候读的名人(科技精英)传记就是一种 Modeling, 尤其是李开复的《世界因你不同》,这些「洗脑」式的输入,会让我越来越希望自己能成为这样的人。

乔布斯有一句广为流传的话,他说

Life can be much broader once you discover one simple fact: Everything around you that you call life was made up by people that were no smarter than you.

一旦你认识到一个简单的事实——你周围那些你称之为“生活”的东西,都是由并不比你聪明的人创造出来的——你的人生就会变得更为广阔。

乔布斯从更极端的思路获得高度的自我效能感 —— 通过对比别人不行,觉得自己可以。

社会说服 (Social Persuasion):

受到他人的鼓励和积极评价可以增强自我效能感。而负面评价则会削弱它。

这个条件比较被动,这里不谈。

生理与情绪状态 (Physiological and Emotional States)

个体在面对任务时的生理反应和情绪状态会被解读为自身能力的信号。如果将紧张解读为“我不行”的证据,自我效能会降低;如果解读为“兴奋”或“迎接挑战”,则可能不会降低甚至会提升。

在这个方面,我面对舞台的经验可以充分论证。记得第一次面对众人做技术分享和第一次上台唱歌,我紧张得不行,表现都很糟糕。随着不断地强迫自己上台,我发现自信完全取决于自己的念头,我学会在上台前欺骗自己是一个有很多粉丝的人,台下的人对我非常崇拜,或很喜欢听我唱歌,这样的念头让我在台上的表现有很大很大的改进。

人是事件的反应器,通过刻意训练,是可以掌控自己的反应的。

以上是我对 AI 时代的到来的其中一点思考,希望能鼓励到和我一样智力平平的人。

我如何使用 Apple Notes 做笔记

我从两年前开始放弃了所有笔记软件,换用 Apple Notes 记录我的所有笔记。这个举动缘于我读了 Tiago Forte 的一本书 The PARA method.

the para method

可能大家比较熟悉他的另一本书 Build The Second Brain, 是关于如何使用笔记软件构建自己的「第二大脑」。这本书也有提到他的 PARA 理论(我在我之前的文章也有提到过我如何应用这个理论管理我的笔记)。两本书都读过以后,我认为 The PARA Method 这本书更偏向实操,我从中收获了更多的方法论。如果你只想读一本,那么我推荐读这一本。

读完这本书后,我立即在 Apple Notes 中基于 PARA 方法建立起了笔记组织的形式,这一套组织形式一直用到了两年后的今天都没有改变过。也就是说,这两年来,在做笔记时我再也没有花过时间去想应该如何组织我的笔记,光从这一点,就让我受益颇深,我可以没有任何心智负担地迅速记下我需要的东西,不用担心我应该怎么记,一切都是那么的简单。

这篇文章就是想和你分享我如何在 Apple Notes 记笔记,我在 PARA 的基础上根据我自己的实际场景做了一点小小的改良。希望对你也有所启发。

为什么是 Apple Notes

在分享方法论之前,我想先解释为什么我选择了 Apple Notes. 我认为 Apple Notes 有很多优点。

  1. 它是系统自带的,我在手机、电脑上可以直接使用,不需要安装额外的 App
  2. 它天然是支持同步的,不需要注册账号,不需要把资料上传到别人的服务器上。It just work.
  3. 你可以直接在 Apple Notes 中粘贴文件、图片、音频,没有限额(严格地说这也占用你的 iCloud 存储,但我本身已经买了存储,这变成了边际成本)。
  4. Apple Notes 打开的速度很快。

当然 Apple Notes 也有缺点,但两年使用下来,我没有因为这些缺点感到被限制,比如:

Apple Notes 支持的格式有限,只有最基本的列表、标题、加粗等等的文字样式格式。但我用下来发现,我根本不需要使用什么高级的格式。有人问我 Apple Notes 不支持代码块,你是怎么记代码相关的笔记的?我说我基本不会把代码片段作为笔记来记。比较多的场景是一些 JSON 格式的片段,我只会直接粘贴到笔记里,不加任何的格式。

我找到了两条比较经典的笔记:

可以看到,里面不仅有 JSON, 还有 toml, 还有命令行。甚至还会有 spell check 的红线让它显示得很丑。但是我一点也不在意。因为它们是拿来用的,不是拿来看的,我不在乎它好不好看,只要我需要用到的时候能找到拿来用就好了。

也就是说,「美观」在我记笔记的需求里排不上什么优先级,我的最高优先级是能快速打开,快速记录,让我记东西的摩擦力降到最低。能帮我当下的想法、要处理的事情相关的信息记下来才是最重要的。所以我看过很多人把 Notion 模板做得很漂亮,也不为所动,因为对我来说这不是我需要的东西。不可否认,美观的笔记系统能给人情绪价值,它没有对错之分,只要合适自己就好。所以最重要的是知道自己是否需要,如果你认为这对你来说不是很重要,就回归简单吧。

Apple Notes 也没有「双链」功能,这是现代流行的笔记软件中很重要的功能,我在用 Logseq 的时候也被这个功能打动,因为它能把相关的笔记自然地关联到一起,带来额外的启发。最初用 Apple Notes 时我也担心会不会因此失去了这个优势,但用下来才发现,我并不是那么的需要它,即使没有,也没有让我感觉因为没有了他而失去了灵感。

Apple Notes 的标签功能也很弱,但我不使用标签。

PARA 是什么

虽然在之前的博客也提到过 PARA, 但我还是想在这里简短地解释一下我所理解的 PARA 是什么。

PARA 的全称是 Project, Area, Resource, Archive. 翻译成中文就是「项目」「领域」「资源」「归档」。它代表了一个笔记应该放在什么地方以及一个笔记的流动过程。

PARA

没错,在 PARA 的方法论中,笔记是可以「流动」的,它有时候在 Project, 当项目结束后,它不再被需要,就会流动到 Archive. 如果它在以后有利用价值,它可能会流动到 Area 或者 Resource.

  • Project 指的是你现在正在进行的项目,比如你正在构思的一篇文章,你正在做的一个项目等等
  • Area 指的是和你有关的领域,比如如果你是设计师,那么设计就是一个你的领域。
  • Resource 指的是你会用到的资源,个人理解它和 Area 是一样的,只是优先级的不同。假如你是设计师,你偶尔会了解数字货币,但它在你生活中的优先级比较低,那么数字货币相关的笔记应该归类为 Resource
  • Archive 指的是不再需要的笔记。通过我会把 Project 中完成的或者难产的项目拖动到 Archive.

理解 PARA 的关键是理解 PARA 的哲学:你记下的笔记应该要服务于你的行动

也就是说,在 PARA 的系统里,当你想要把东西记下来时,你的 mindset 应该是,这条笔记和我接下来的行动有什么关系?如果没有关系,那么它应该放在 Area 还是 Resource?

在 The PARA Method 一书中,开篇的一句话能解释这个哲学:


Every word in this book is designed to do one thing: propel you forward into taking action

这本书中的每一个词都是为了一个目的:推动你付诸行动。

Apple Notes 中的 PARA

简单了解了 PARA 后,看看我如何在 Apple Notes 中使用 PARA.

我在 Apple Notes 中会单独建立一个 PARA 目录:

PARA 目录

里面有基本的 Projects, Areas, Resources, Archived 文件夹。在这个基础上,我有两个改良,第一个是添加了一个 Drafts 文件夹,用于记录我日常的零碎想法:

这些想法一般是在手机上记录下来,在有空的时候进行深度思考和研究,有些会最终成为一篇推文,或者基于想法变成一篇博客文章。

第二个改良是增加了一个 Inbox 文件夹,用来存放打算做的项目,这些项目还没进行深入的思考,不确定是否立项来做。如果想清楚要做,我会把它拖动到 Projects 中。否则直接拖到 Archive 归档。

Projects 目录是我最常用的,因为里面都是当前正在进行的项目:

比如,当我决定要写一篇长文,就会在 Projects 中新建一个文件夹,比如现在这篇文章,就是在 Apple Notes 里写的。有了这个文件夹后,当我看到任何和这个项目相关的信息,就会把它记在这个文件夹当中:

也就是说,当我看到任何关于 Note taking 主题的值得记下来的东西时,我很清楚要把它放在哪里,因为它和我正在进行的项目有关系。

这也解决了一个很普遍的问题,很多人想记笔记,想有「第二大脑」,却不知道应该记什么,或者记下来很多,最后都只是放在自己的笔记软件里,发挥不出任何作用。而在 PARA 的系统中,你清楚地知道你应该记什么笔记,因为你知道哪些信息和你正在做的事情有关系。这些笔记才是真正能发挥作用的

比如我即将写一篇在我 30 岁时发布的博客文章,虽然还没开始写,但它是我的一个 Project, 那么当我读到和这个主题相关的信息,就会把它记在这个 Project 里,当我真正写这篇文章的时候,能直接参考它们,对于我来说,这才是有用的笔记:

Projects 不仅仅是项目,它还可以是即将到来的一个事件(Event),也可以是你正在研究的一个课题。比如你约了一个重要的人,你需要记下来这次约会需要准备点什么。约会结束后,就把它拖动到 Archive.

再举一个例子,比如你正在开发一个软件,可能需要记一些技术相关的笔记。在以往,可能你会把这些笔记按主题记下来,比如 React, Vue, Rust, 前端, 后端之类的分类。但在 PARA 中,你应该为你正在开发的软件创建一个 Project,无论你想记下来的技术笔记是什么分类,都放到这个 Project 当中。这样记下来的笔记才是真正有价值的,因为它真实地服务于你正在开发的项目,你在开发时可以反复在一个地方参考。

项目结束后,你可以将整个 Project 拖到 Archive, 不需要再管它。如果你认为 Project 中的一些笔记有复用的价值,就把它拖到 Areas 或 Resources 当中。Apple Notes 的拖动十分方便。但按照我的经验,一般来说直接 Archive 整个 Project 也没什么问题,因为它不是被删了,你还是能通过搜索重新找到它。这时 Archive 的笔记又会「流动」到 Project 当中。

我自己的 Areas 和 Resources 其实很少碰,基本只在 Projects 和 Archive 中活跃:

使用 PARA 的另一个好处在于,只要展开我的 Projects 目录,我能一目了然我正在做的事情。我不再需要单独的 Todo 工具,因为 Projects 就是我的 Todo, 而且 Apple Notes 也有 checklist 功能。这也是 The PARA Method 中所说的:

You will gain greater focus on what matters most: You will have greater clarity about what’s important so you can intentionally move your life into alignment with your interests and goals.

你将更加专注于最重要的事情:你会更清晰地了解什么是重要的,从而有意识地将你的生活与自身兴趣和目标协调一致。

常见问题

没有双链,如何找回笔记

善用搜索。虽然只是关键字查询,但按照我的经验来看,这就足够了。比如我在 Apple Notes 记过我常用的 API key, 我只要搜索 azure 这个词,我就能立刻找到,我甚至都不知道它被我放在哪里了:

怎么把旧的笔记迁移过来

不需要迁移。在新的工具重新开始,需要用到的时候才在旧的工具重新找回,有必要时再复制过来。反正没人要求你用新的工具就把旧的数据都删掉。而且,我相信你最后会发现,你并不怎么需要找回你的旧数据 😂 这两年我就没打开过我以前一直用的 Logseq, 也没有做任何迁移。

结论

以上的分享不一定适合所有人,但它非常适合我,我也从这套系统中得到了很大的解放。你甚至可以什么系统都不用,在同一个文件夹记下来所有东西,最后你会意识到,重要的不是把笔记放在哪里,而是感谢自己当初记了下来。这就是为什么降低记录的摩擦力是如此地重要。

PARA 是一个方法,你不一定要用 Apple Notes, 你可以在所有的工具用它。不要让工具阻碍了自己最重要的事 —— take action. 真正有用的笔记是帮助你 take action 的笔记。

P.S. 这篇文章在 Apple Notes 写成:

新 MacBook 的设置和软件

趁着国补,把手头用了 5 年的 MacBook Air M1 换成了 MacBook Pro 14 寸 M4. 顺便手动重新配置新电脑,在此记录一下每次设置新电脑时我会做的一些设置和必装的软件。

设置

把点按切换成轻点:

点按切换成轻点

我不喜欢用触摸板点按拖动来移动窗口,觉得有点费力,所以我会调整为三指拖移。旧版本的 OS X 可以直接在系统设置中调整,新版本的 macOS 竟然把它归类到辅助功能里了:

三指拖移

把 Control 键和 Caps Lock 键互换。因为我需要频繁使用 Control 键,Caps Lock 的位置是最合适的,这也是 HHKB 的默认布局:

Control 和 Caps Lock 互换

取消 Spotlight 的所有索引,因为我用 Raycast, Spotlight 的索引会浪费计算资源:

取消 Spotlight 的所有索引

软件

  • Microsoft Edge: 用了很多年的主力浏览器
  • 清歌五笔输入法: 最好用的五笔输入法
  • Raycast: 不必多说
  • Cursor: 目前主力 IDE, 也是 Cursor 长期的订阅用户
  • CleanShotX: 最好用的截图软件,除了截图还有很多好用的小功能(比如 OCR, 录屏)
  • 1Password: 密码管理
  • Warp: 主力 Terminal Emulator, 已经离不开通过 AI 写复杂的命令
  • iStat Menus: 在 Menu bar 显示系统信息,我用来实时看到网络传输速度和内存使用情况

2024: 在夜海中观望浪潮

高中时我读吴军的《浪潮之巅》,觉得很可惜,我出生太晚,错过了一个又一个的浪潮。我总是想,如果我身处这些浪潮,是否也会造就一点什么?我是不是也能创造一家了不起的企业而为人常道?

那时我才 17 岁,我在沙滩上观望浪潮,抱怨生不逢时。而现在,我 29 岁,已经在海中游泳多时。就在这几年,OpenAI 发布 ChatGPT 后,这片海似乎又卷起了一波巨大的浪潮,又有人站在了浪潮之巅,但我还没有。我已经无法再抱怨生不逢时,因为我已经在海里了。在很多个深夜,我差点就想承认,我这辈子只能是一个普通人,我太高估自己。但到了第二天,我又开始不服气,还是不能接受作为一个普通人活着,想做出伟大的产品,想做一家伟大的公司,想在世界上留下一点美妙的痕迹。

知道答案的人只有未来的我自己,所以每当这些迷茫时刻,我总会想起昨夜派对的《夜海的秘密》:

生命中所有来去 都是月亮的潮汐

只有闭上眼才能看到 夜海的秘密

你会看到有船从身旁驶去

船上站着来自未来的自己

他逆着霞光 看不清模样

如一尊塑像 带着先知般的哀伤

只是沉默望着你 一句未讲

你蓦然落泪 想一路追上

跟随他去往 那迢遥未知的远方 可是未来还未来 唯有此刻 是真实

昨夜派对的歌的歌词,就像诗一样,能给我一点疗愈。我习惯通过文学作品自我疗愈。别人问我,为什么还能保持阅读的习惯?我都会回答:如果你发现我某一段时间读特别多的书,那就是因为我这段时间过得非常不好,我需要从书中寻找答案。

2024,我读了 30 多本书。

因为想做一家伟大的公司,我读了柯林斯的《基业长青》和《卓越基因》,研究那些成功的企业是怎么走过来的。

因为想做出伟大的产品,我读了梁宁的《真需求》,lululemon 创始人的《lululemon 方法》,张小龙的《微信背后的产品观》。

其中一本叫做《做难而正确的事》(英文名是 Fall in Love with the Problem, Not he Solution) 让我印象很深刻,它不仅是一本给我打兴奋剂的创业鸡汤,它还教会了我,什么是真正的 Make something people want.

这些书让我对做产品有了另一个体会:如果我想做一个影响范围非常大的产品,我就需要做让人上瘾的「毒品」,要做满足用户「贪嗔痴」的产品。因为 PMF (product-market fit) 的最重要的指标是用户留存率。

恰好我读到了一本《玩家思维:游戏设计师的自我修养》,了解到了游戏让人沉迷的原理,大概也能运用到产品中去。

今年上半年我的朋友启师傅来佛山,我们在先行书店见了一面,期间我们聊到,做互联网产品有点厌倦,想做一些真正看得见摸得着的东西,能让用户在手上把玩,然后赞叹一句 wonderful 的东西,而不是拿着手机给别人看,说「你看我写的这个 App…」。

于是我想到 18 年我曾经卖过一段时间的程序员贴纸,那时我发现国内没有人在做,在国外买又非常贵,动辄一张几美元。那时我联系了一家质量非常好的厂家,做了一批质量很好的开源项目 logo 贴纸,卖得非常不错。更重要的是,中国的程序员终于可以在国内买到质量又好又便宜的贴纸。不过因为我还在上班,有点忙不过来,最后关掉了店铺。

后来我在杭州参加 AdventurX 的活动,趁着机会又见了多年的网友 Linmi, 他是 Notion 中文社区的负责人。我们也聊到了这件事。他鼓励我重新把这件事做下去。

回到家后,我重新认真思考了这件事。我确实应该重新捡起来卖贴纸这门生意,但不能像几年前一样只卖贴纸。我相信程序员(或者泛互联网从业者)需要的不止是贴纸,我们有我们独特的文化,我们有我们真正发自内心热爱的东西,我们乐意把它展示出来。

所以我决定把这件事用经营一个品牌的方式重新去做,贴纸只是其中一个产品。无论在后来是卖贴纸还是卖其它东西,我希望能聚集一群有相同文化认同的朋友,做出这个群体喜欢的产品。我想了很久品牌的名字,最终还是冒了一个风险,取了一个只有程序员会懂的名字 PaddingLeft, 这是一个 CSS 属性的名字。幸运的是,我居然能买到 padding-left.com 这个域名。

我花了些时间选择工厂,甚至跑到了工厂观察他们的机器,和工厂聊价格排期。终于重新上线了店铺。

贴纸卖了一段时间后,其实没有赚到多少钱。期间我一直在想下一个单品应该做什么。恰好那段时间我一直想给自己买一条项链,何不就做项链?

起初我跟一些人聊这个想法,他们都不认可做项链,认为程序员没有戴项链的习惯。这个观察其实不无道理,可是在做这个品牌的时候,我其中的一个想法是可以通过 PaddingLeft 的产品,让用户在使用我们的产品的同时能增加自己的时尚属性,摆脱大众对互联网工作者的刻板印象。而且项链可以做得非常精致,也适合送礼。

这个想法有点自大,但我认为值得尝试。

第一批项链做出来后,卖得也不错。

项链

做 PaddingLeft 的这段经历,虽然也和互联网有点关系,但后来我才慢慢意识到,我在做和做软件截然不同的东西。我对供应链、库存有了新的认识,这些是在做软件的时候不需要处理的问题。和传统行业的人打交道,沟通的方式也和在互联网公司里沟通的方式截然不同。关于这些,我想在以后可以专门再写一篇和大家分享。

在开单的喜悦过后,我又开始想,这到底是一门多大规模的生意?18 年我卖贴纸,我只是把它当作赚外快来看待,如果一个月能带来 2000 块的收入,我会觉得是不错的零花钱钱。但是现在,我是在为自己的生计赚钱,我要思考的问题变成了,从商业上来讲,这是一个值 2 万人民币的 market, 还是值 5 万,还是值 10 万?如果它只是一门值 5 万的生意,我有必要做下去吗?或者说,它有可能发展成价值 20 万规模的生意吗?它是一个可以「流动」的生意吗?

就在刚刚,又有人在店里下单买了些程序员贴纸,于是我走到工作台一张一张地放进包装袋。我边发货,边盘算这单赚了多少钱,得到的数字是 8 块。我忍不住笑了。我曾经的日薪是这一单的 100 多倍。我做的这件事情,它真的有意义吗?

没有人知道答案,我只能安慰自己,这件事总得有人做,而我适合做。我了解程序员、设计师、产品经理这些人喜欢什么,因为我就是程序员。每卖出一张贴纸,我都会给对应的开源项目捐一块钱,以现在的定价,我基本是在做慈善。现在也有很多店做程序员贴纸,但没有人这么做过,因为我做过开源,我知道开源项目的窘境,这就是文化的一部份,也是做这个品牌的意义。如果你曾经买过 PaddingLeft 的贴纸,你会发现我用的自封袋和普通的透明袋有点不同,因为我用的是环保可降解的自封袋。这对用户来说没有丝毫影响,但我挺享受这种自我感动,这也是其中的意义。


直到现在,我还不知道,我还要在海里游多久,才能游到浪潮之巅。我想起曾经的一场梦,梦到在一个饭局,张一鸣就坐我旁边,我说我会有一天像你这样的。张一鸣笑了笑,我也笑了笑,然后梦就醒了。

我的这些执着在多年后是否会成为笑话?Think different 广告中的那句「只有那些疯狂到认为自己能改变世界的人,才真正改变了世界」这句话,到底是不是真的?妈妈,你 10 年前和我说的,算命的说我 30 岁会创业成功,到底是不是真的?

AI 和写作

Sam Altman 最近有一个关于 AI 和写作的访谈,让我开始思考 AI 辅助写作这个课题。

在辅助生产代码方面,Cursor 和 GitHub Copilot 已经证明了 AI 和人类在编程方面的协作非常有效。通过代码的上下文和注释,加上人类显示声明需求,AI 能很好地完成写代码任务。

我在思考在写作的时候,是否也能有一个 Copilot 辅助人类更好地写作。

大部分人不会进行深度写作,根据二八法则,80% 的人在消费 20% 的人生产的内容。这 80% 的人,偶尔进行浅度写作,例如写社交网络的动态、写用于工作中用于信息同步的文档。这类写作,我认为 AI 可以辅助的地方非常有限,用户基本不会主动为这样的场景寻找专门的写作工具或到 ChatGPT 这样的聊天窗口专门改写润色。这样的场景将来会被系统级的 AI (如 Apple Intelligent) 解决。

有的人希望写作,但不知道应该写什么。我曾经在书店看到过一本书,叫《642件可写的小事》,如标题所言,里面有 642 个开头,可以随便选一个续写。现在也有一些这样类型的 App. 这些书和 App 乍看起来很有意思,能解决不知道写什么的问题,但用起来我发现我根本不想写,因为我对里面的话题毫无兴趣。

我认为 AI 能很好地解决这个问题,因为我们可以在 prompt 中告诉 AI 自己的爱好和关注的领域,来定制化地生成自己会感兴趣的创意写作话题。

Notion 流行后,很多笔记工具开始标榜自己是「思维的工具」(Tools for thought). 其实写作这个行为本身就是「思维的工具」。Sam Altman 在访谈中说「写作是思考的外化」(writing is externalize thinking),而且「写作是模糊想法的放大器」。应该有不少人有同样的感觉,我们每天有很多想法和灵感,我们以为我们对这些想法非常清晰,但其实如果试着把想法写下来(或说出来),才发现很多思考在头脑内部其实处于非常混沌的状态。只有通过思考的外化(交流、写作),才会发现思维的漏洞、连结零散的思维。在修补思维漏洞的过程中,混沌的想法会逐渐变得健壮 (robust). 思考和灵感就像一颗种子,它非常迷人,但在播种前,它什么也不是。写作就是播种,认知是你的土壤,它会长出你意想不到的枝叶,然后又生长出新的枝叶,这就是为什么「写作是模糊想法的放大器」。

造成在头脑中的想法非常混沌的原因,是人脑无法承受过高的「认知负荷」 (John Sweller 提出的理论) 。借助书写可以把信息扩展到外部记忆,使得人在处理复杂信息时能更好地理解和思考。

很多人害怕写作,认为写作有门槛,但我不这么认为。我认为只要会思考,就会写作;只要能和人正常沟通,就会写作。写作只有把文字变成作品被品味被评价才是一种有门槛的艺术。我们受学校教育的影响,把写作完全视作一种文学艺术,才让人对写作感到害怕。却没有人教育我们,写作是一种思维的工具,写作可以帮助我们思考。我们其实可以只为自己写作,它是没有门槛的。把写作视作和自己对话,你就会发现写作也就是那么一回事儿。和自己对话需要遣詞造句吗?不需要。需要遵循什么文章结构吗?不需要。

把写作看作是思维的工具后,我对 AI 辅助写作有了全新的思考角度。从表面看,我们似乎需要的是辅助我写作的 Copilot, 但在本质上我们需要的是辅助思考的 Copilot.

现在很多文本编辑器都有 AI 功能:自动补全、自动总结、选中文本 Ask AI, 重写、润色等等。但我对这些功能在写作中的作用非常失望,真正在写作时,这些功能都是非常 annoying 的,而这不止我一个人有这样的感受:

这些功能,完全不能称作写作辅助,它们只不过是文本处理辅助罢了。它们对写作毫无用处,甚至有反作用。它们对思考也毫无用处,无法「放大」我的思考。

当写作作为一种思维工具时,我不需要 AI 辅助我自动补全,甚至有些可以生成一个段落的文字。这不是写作,这是在制造垃圾。

到底什么样的形式是一个好的写作 Copilot, 我也没有答案,但我认为它一定不能是侵入式的。它应该在一旁默默地观察我写出来的想法,然后在合适的时候告诉我它对此的观点。它有比我多得多的知识量,可以做到:

  • 在我提出一个观点时,找出其中可能的逻辑漏洞,帮助我更完整地思考。
  • 在我提出一个观点时,找出其在学术研究中对应的名词可以解释这个观点,甚至找出对应的科学实验、论文、现实中的案例。这在非虚构类写作中十分有帮助。我最近在读 Ali Abdaal 的 Feel good productivity 一书,里面十个观点有八个都能举出一个科学实验来论证,我基本可以单方面认为 Ali 一定是在用 GPT 来辅助他写这本书的。
  • 找出我在写作风格中的缺点,比如过于冗长,信息密度过低,句子难以理解等等。

也就是说,写作除了和自己对话以外,增加了和 AI 对话的一层。它可以提供灵感、帮助做 research, 写作风格指导等等的帮助。

虽然我希望有这么一个写作 Copilot 出现,但我觉得很难做出一个这样的 Copilot 出来。它的难点在于,不同类型的写作有着截然不同的需求,如果需要匹配不同的需求,最终还是会变成一个 Chat IM 的形态。上面提到的需求,其实直接使用对话也能解决,能进一步做的无非是在文本编辑器里加上选中文字展开对话,预设一些跟写作相关的 prompt 而已。

至于 AI 是否会扼杀写作,我不这么认为。Sam Altman 在访谈中也提到了这点:当你读到很好的作品,你会想去了解作者,这个作者经历的事情,他的思考方式,慢慢地和作者形成一个连结。这是 AI 做不到的。

「代码艺术家」不会被 AI 取代

最近大量地使用 Cursor 替代了 VS Code, 开始习惯直接在编辑器里告诉 AI 我的需求,让它来代替我写出代码段。

请注意,我用了「代码段」这个词,而不是「代码」,因为我想做一个区分 —— 按照我目前的经验来看,生成式 AI 非常擅长生成一段内聚的代码,而不是一整个应用程序。

在没有 AI 生成代码前,我写代码也是这样的一个流程:

  1. 思考整个应用的架构、模块
  2. 选择适合的技术栈
  3. 开始写代码,设计目录结构、抽象
  4. 真正开始实现实现

AI 也许能为第 1 和第 2 点提出建议,但我目前不需要。第 3 点我认为对于稍微复杂一点的生产级应用, AI 还做不到把这一块也做到。可能很多人看到现在 Claude 直接能写出一个全功能的 Todo List 就惊叹 AI 要取代程序员了,我觉得真正写过一个完整的给用户使用的「应用」的朋友对此都会很淡定。

对于我来说,只有在第 4 步(实现) 的阶段才真正能杠杆 AI 的能力。我会尽可能地描述清楚我的需求,让 AI 能理解我要做的任务,让它来生成满足我的抽象的代码,或修改现有的代码。

这里我提到的描述清楚我的需求是用 AI 生成代码中最重要的一点。所谓的「需求」不仅仅是描述这个函数需要做什么事情,还需要包含这个函数应该接收什么参数,返回的是什么数据结构。

例如,我在做的一个应用,其中我需要一个上传文件到 S3 的函数。在这个需求中,如果我单纯告诉 AI它要做什么,那我很有可能得到一个可以实现功能但不适合我调用的函数,因为 AI 没有上下文去确定我可以传哪些参数给它。

![cursor 截图]../../../images/telegram-cloud-photo-size-5-6168067452672523878-y.jpg

在深度和 AI 「结对编程」后,我对于「AI 是否能取代程序员」这个问题有了更深刻的思考。

有了 AI, 我现在写代码花的精力主要是在「设计」上,例如思考这个应用的交互设计,例如整个应用的架构设计。所谓的架构设计,一部分的工作是决定这个系统里要有什么模块,一部分的工作是决定这些模块如何串联在一起。而这些设计工作恰恰是我写代码的时候最喜欢做的,对我来说,写代码就应该是一个设计的过程,设计出优雅、易用、易扩展的接口是一件很有成就感的事。这也是我当初看 Head First Design Patter 这本书时的感受。
 如果写软件变成了一个只需要花精力在设计而不是实现上的过程,那么写软件的人就是「代码艺术家」了。我觉得「代码艺术家」是不会被 AI 取代的,因为设计的起点和终点都是人类,AI 可以给你 100 个设计上的答案,但只有人类最终能感知到现实和当下的环境和信息,创造出能触动另一群人类的产品。

如果你从现在开始,开始把 AI 当作是你的员工,就像某一天你突然只需要 $20 一个月就能招无数多愿意帮你打工的人,你很快就会发现,你最终会面临两种局面:

局面1:你将手足无措,你突然发现如果你不是实现函数的那个人,你就不知道你应该做什么了。从前你沾沾自喜的手写快排,手写红黑树突然变得一文不值,无处施展。

局面2:你将如虎添翼,你突然发现你曾经有很多想法没有精力和时间去实现,现在突然有这么多廉价劳动力将不厌其烦地帮你写代码,而你要做的只是设计好整个系统的结构,把具体实现外包给 AI. 然后把产品推出市场,去碰壁,去失败,去成功。显然,AI 不能替代你去碰壁,去失败,去成功,但真正让你变得强大的不是你手写快排有多烂熟于心,而是去碰壁之后学习到的东西

AI 不会替代「代码艺术家」,因为 AI 是「代码艺术家」的喷射机

读到这里,可能有人要说,Randy, 你飘了,你开始技术虚无主义了。在这里我要申明,这篇文章我是写给有一定经验的程序员看的。对于没有什么经验的程序员,多写点代码总是好的(至少目前来看)。AI 能力的上限是由用的人的上限决定的。无论是任何行业,充分掌握领域知识后配合 AI 才是最好的做法。

就像下面这个例子,我只要说一句 add tanstack query provider 就能让 AI 帮我把 @tanstack/query 加到我的程序里。我自己会写,但我自己写可能要花一两分钟,但 AI 一下子就好了。

![cursor 截图]../../../images/telegram-cloud-photo-size-5-6165685253356765301-y.jpg

但如果你没有任何代码经验,你连 tanstack query 是什么都不知道,也不知道要放在程序的哪个地方,那用 AI 还是有点困难。

写下这篇文章是因为最近用 Cursor 有感,加上刚好看到 Daniel Nguyen 发了一篇 Software is Art, 有感而发,不吐不快。在此粗浅翻译(非 GPT),作为结尾:

I realize the reason I like building is not just because I’m a builder.

我意识到我一直喜欢创造点东西的原因不只是因为我就是个创造者.

It’s because software products are how I express my creativity.

而是因为写软件产品是我表达我的创意的一种方式

It’s like a poem to a poet, a song to a songwriter, a painting to a painter…

就像诗人的诗,歌手的歌,画家的画

Software is my art form, my medium of expression.

软件是属于我的一种艺术形式,是我表达(创造力)的媒介。

不上班的第一年

2023 年的 5 月,我离开了微软,开始了自己做产品的旅程。到现在刚好满了一年,这一年发生了不少事情,有些在 2023 年终总结里提到过,这一篇我想更详细地列出在这一年我具体做了什么、对自由职业的思考、对做产品的一些思考等等。

首先是我的产出,这一年我的产出主要是两个产品、一个播客。

两个产品

Notepal

离职后第一个认真做的产品是 Notepal, 这是一个用于把微信读书笔记同步到各个笔记应用的插件。我在这篇文章详细写过这个产品的起源和在做这个产品的时候的一些思考。我在做这个产品的时候完全没有想到,这个只卖 50 块的插件,在这一年帮我付了我一整年的房租。

EpubKit

epubkit

EpubKit 是另外一个我投入比较多的产品。这是一个把网页转成 ePub 电子书的工具。正式上线不到一个月,收获了几十个付费用户。

一个播客

刚好今年的 5 月份也是我和 GeekPlux 一起做的播客节目《代码之外》 的一周年。起初我们只是玩票性地做闲聊节目,后来因为有了听众来信的栏目,我们慢慢会讲更多职业发展方向的主题,这些主题意外地无形中对很多听众有不少的帮助。后来还邀请我刚工作就很欣赏的前辈勾股作为来信栏目的常驻嘉宾,为节目带来了很多很好的观点。今年在参加一些线下活动时,有些听众会跟我说从节目中得到了启发。从统计数字上看,我们好像的确是做了一件影响力不算小的事:

  • 小宇宙订阅数 6000+, 总播放量 11万+
  • Bilibili 粉丝 6000+,总播放量 13万+
  • YouTube 订阅数 2500+, 总播放量 3万+

在这一年,我对于做产品和生活都有了很多思考。对于不上班这件事,有人羡慕,有人好奇,刚好在这篇总结里,我想跟大家分享不上班的好处和缺点。

不上班的好处

时间自由

不上班的好处对我来说最明显的是时间自由。我是一个喜欢晚睡晚起的人,所以对我来说,准时上班是一件很难的事。不上班可以让我睡到自然醒,有充足的睡眠时间。

认识不同的人

上班的时候基本只能认识职场环境里面的同事朋友,或者在网络上交流的朋友。但是在自己做产品后,我开始认识一些同样是在做产品的朋友,和这些朋友可以聊得更深入。这些人往往对很多事情都有自己独特的思考。我从他们身上学习到非常多。我经常觉得如果我只是和以前一样在公司上班,业余写写博客和开源,我可能很难和这些朋友有共同话题,最多也只是互相点赞之交。

赤裸地直面市场

不上班后只能完全依靠自己赚钱,这代表我需要很赤裸地直面市场。

第一层赤裸,指的是脱离公司,作为一个个体,我可以给消费者提供什么他们认为值得为我付费的价值?

第二层赤裸,指的是一个产品如果脱离大公司本来就有的入口红利,我靠什么给我的产品带来自然流量?

这些都是很难的问题,但一旦开始学习,收获都是巨大的,而且都是属于自己的。在公司里最可怕的地方在于,我们很容易把公司给的平台误以为是自己的能力,很容易把流量看成是理所当然会有的。

也正因为如此,在这个过程中,我可以学习到很多以前不会主动学习的技能。

学习到更多技能

自己做产品的时候,没有公司平台给的入口,首先需要学习的就是如何做 marketing. 也是只有在自己做产品的时候,才会发现要别人发现你的产品是一件多么难的事。回想以前在阿里做业务,我们写一个营销活动页面,只要把入口放到某个栏位,基本不需要担心没有流量。

为了获客,我学会了研究 SEO, 学会了怎么做小红书,读了很多关于 marketing 的书。这让我学习了很多技术之外的知识,我觉得这些知识是终生受用的,而且它们不仅可以在互联网行业受用。

不上班的缺点

没有固定收入

不上班最大的缺点就是没有固定收入,这是很现实的问题。坦率地说,我以前买东西基本不看价格,只要觉得有价值,我就会买。没有固定收入之后,我买东西变得更加谨慎了。比如最近我很想买一台 Studio Display, 换作以前,无需多想,直接下单。但是现在,我会想我要卖多少份软件才能 cover 这台显示器,想想还是算了。

有时候我的朋友说羡慕我不用上班,我就跟他们说我这一年的收入还没有你们一个月赚得多,他们心理也就平衡了🤣。

孤独

可能有些人就是喜欢不用和别人交流,但对我来说,我是喜欢社交的,我在适当的社交中可以获取能量。不上班的时候,大部份的时间都是在家里写代码,看书,在网上和朋友聊天。有时候一整天都不需要开口说一句话。到后来我有点受不了,开始到外面的咖啡厅办公,只要能见到旁边有活人,就能有所缓解这种孤独感。

不确定性

上班有上班的不确定性要面对,不上班也有不上班的不确定性要面对。你无法确定这种生活可以维持多久,你的产品是否能卖得出去,就算卖得出去,它是否能养活自己,这些都是要面对的问题。

这些就是我体会到的不上班的一些优缺点,接下来,我想讲讲这一年在做产品的过程中我的一些思考和感受。

思考和感受

独立开发?

「独立开发」这个词在这一年非常火,用来标签像我这样自己做产品的开发者。其实我一直不称自己为「独立开发者」,因为我根本不想「独立」开发。我想做 scope 更大的事情,只是现在还没有条件。如果可以,我更愿意和两三个志同道合的人一起做产品。就像 61 的谜底科技,像少楠的 flomo.

而且自己做产品这一年,让我深刻地意识到,真正要做「独立」开发是很难的,因为没有人能擅长所有技能

没有人擅长所有技能

曾经我觉得只要我愿意去学,我就能做好。比如设计。我很注重产品的 UI/UX 设计,但我一直没有经过认真的设计学习和训练。我读了些关于 UI/UX 的书,以为就可以成为一个设计还不错的程序员。后来发现我错了,理论和实践之间原来有一条很大的鸿沟我虽然知道很多设计和用户体验的理论,但是一旦真正动手做页面,这些理论完全无法转换成实际的设计。

这是因为设计和写代码一样,是需要长期积累的。优秀的设计师可能有审美天赋,但他们一定也是每天都观察大量的设计,在自己的脑中内化了很多设计的模式(我不知道有没有专业的术语来形容),这些积累使他们可以面对一个新的需求的时候,根据自己内化的东西产生新的设计,这是我这种只读过一些理论的人无法做到的。正如会写代码的设计师,可能可以写一点能跑的代码,但缺乏多年的代码实践经验,是不可能像真正的专业程序员一样根据经验做好技术选型和想应该用什么设计模式(design pattern)的。

不过,我还是学会了在看到一些设计的时候,比以往更深入地去思考这个设计,这些元素为什么会这么摆放,颜色是怎么运用的,等等。

人的时间和精力是有限的

Notepal 和 EpubKit 同时做,让我更能体会到人的时间和精力是有限的。把时间放在一个产品 4 小时,那么另外一个产品投入的时间就永远少了 4 小时。而且上下文切换(context switch)是非常耗精力的。比如你正在修产品 A 的 bug, 这时用户报了产品 B 的 bug, 要从产品 A 跳转到产品 B 的开发,是很难一下子切换过来的。

所以不是自己一个人做产品就不需要项目管理,还是得学会充当自己的 manager 管理自己。给项目列好 roadmap, 排好需求优先级。

关于这个问题我还专门请教了图拉鼎,他告诉我要把自己当成不同的角色来用,比如规定上午作为客服,专门处理用户反馈,下午是程序员,专注写代码。这个方法也让我很受用。

自己做产品,同时要充当开发、marketing、客服、产品设计,我觉得这是「独立」开发的最大挑战。

学会专注

开发完 Notepal 后,我曾经陷入了很长时间的 burnout. 眼下赚的钱也不多,但又不知道应该做什么。期间有一些零星的 idea, 开发完后也不了了之。后来开发了第一版 EpubKit, 有了一些用户,之后又 burnout 了,因为开始觉得它做得不好,也很小众,渐渐又想放弃,想做点别的试试。

后来一些朋友「批评」我说我不够专注,我也开始反思,其实自己做的东西不算很糟糕,只是自己太着急,没有把它们都做得极致就失去耐性。经过反思,我决定好好打磨自己现有的产品,才有了现在这一版自己比较满意的 EpubKit.

乔布斯说专注不是指只做最好的,而是对其它也很好的东西 say no.

另外,缺乏专注很有可能会消耗用户对你的信任。

警惕「快速试错」

「快速试错」、「快速验证想法」是很多人做产品的信条,我相信快速验证想法是重要的,但是更重要的是交给用户的这个产品不应该是一个半成品,它最好是一个 Finished software. 也就是说,即使你不再维护这个产品,它还依旧是个可用的软件。

我曾经也「快速试错」,发布了一些产品,最终不再维护,甚至还有几个用户付费了。我对这种「试错」是愧疚的,因为这很有可能伤害支持你的用户。因此我现在反而发布新产品会更加谨慎,要把用户的感受放在第一位。

这篇文章很好地表达了这个观点。

总结

以上就是不上班的一年我对自己的一些思考的总结,希望对读到的朋友有帮助。这一年我其实算是比较幸运的,虽然收入微薄,但做产品也算是能卖出去,能解决很多人的需求。但同时我还是处于迷茫的阶段,认为自己做的产品还是小打小闹,scope 太小。我还是希望将来能做出满足更大众需求的产品,覆盖面能更广。

我的这些观点不一定对,但也能让读者感受到一个程序员脱离公司的其中一种可能性。

在 Electron 中使用 SQLite 的最好方式

上周刚刚发布了一个用 Electron 的应用 EpubKit. EpubKit 是一个把网页制作成电子书的工具。在 EpubKit 里,我需要一个数据库来存储内容,最好的选择是 SQLite.

但是,由于 Electron 有 renderer 和 main 区分开的机制,所以在 Electron 中使用 SQLite 会非常麻烦 —— SQLite 的执行要在 Main process, 但调用要在 Renderer process. 在 Electron 里,Renderer 和 Main process 之间的通信是通过 IPC (Inter-Process Communication) 实现的。也就是说,我可能需要把每一个有关数据库操作的业务逻辑单独写成一个 IPC 通信的事件,然后在 Renderer 里调用这些事件。

我想要做到的是,我在 Renderer process 中直接调用 ORM, 但实际的执行是在 Main process 中。这样一来我就不需要单独地写很多个 IPC 事件了。

例如:

万幸的是 drizzle 居然有一个 HTTP Proxy 的机制。这个机制能让你所有的 ORM 操作都流到一个地方,在这个地方你能拿到最终生成的 sql 语句,然后你可以自己决定怎么执行这个 sql 语句。

也就是说,我可以在这个 proxy 里,把 sql 语句通过 IPC 发送到 Main process, 然后在 Main process 里执行这个 sql 语句。

接下来我会简单描述一下我在 EpubKit 里是怎么做的。

编写 Schema

在你的项目里找一个地方,把 drizzle schema 写下来:

import { int, sqliteTable, text } from "drizzle-orm/sqlite-core";

export const posts = sqliteTable('posts', {
  id: int("id").primaryKey().default(0),
  title: text("title").notNull().default(""),
})

在 Renderer 里创建一个 drizzle database 实例

根据 文档, 在创建 drizzle db 实例的时候,可以传入一个函数,这就是 proxy 的本体。我们要做的是在这个 proxy 里,拿到 ORM 最终生成的 sql 语句、执行方法、变量,然后通过 IPC 发送到 Main process.

export const database = drizzle(async (...args) => {
  try {
    // 通过 IPC 把 SQL 发送到 Main process
    const result = await window.api.execute(...args)
    return {rows: result}
  } catch (e: any) {
    console.error('Error from sqlite proxy server: ', e.response.data)
    return { rows: [] }
  }
}, {
  schema: schema
})

这里有一个 window.api.execute(), 是怎么来的呢?其实是在 preload 进程里面定义然后暴露的, 它的作用就是通过 IPC 发送 sql 语句到 Main process:

// preload.ts

const api = {
  execute: (...args) => ipcRenderer.invoke('db:execute', ...args),
}

也就是说,实际上我们以上做的事情就是,通过 proxy, 把 SQL 语句通过 Main process 里的 db:execute handler 最终执行。

Main process

在 Main process, 我们创建一个 IPC handler:

// main.ts
ipcMain.handle('db:execute', execute)

这里的 execute 就是 Main process 里最终执行 SQL 语句的函数。

import { drizzle } from 'drizzle-orm/better-sqlite3'
import Database from 'better-sqlite3'
import { migrate } from 'drizzle-orm/better-sqlite3/migrator'
import * as schema from '../renderer/src/db/schema'
import fs from 'fs'
import { app } from 'electron'
import path from 'path'

// 初始化 sqlite
const dbPath = '../databse.db'

fs.mkdirSync(path.dirname(dbPath), { recursive: true })

const sqlite = new Database(
  dbPath
)

// 创建 drizzle 实例
export const db = drizzle(sqlite, { schema })

// 这里是 execute 方法
export const execute = async (e, sqlstr, params, method) => {
  // 得到执行需要的参数后,用 better-sqlite3 执行
  const result = sqlite.prepare(sqlstr)
  const ret = result[method](...params)
  return toDrizzleResult(ret)
}

function toDrizzleResult(row: Record<string, any>)
function toDrizzleResult(rows: Record<string, any> | Array<Record<string, any>>) {
   if (!rows) {
    return []
  }
  if (Array.isArray(rows)) {
    return rows.map((row) => {
      return Object.keys(row).map((key) => row[key])
    })
  } else {
    return Object.keys(rows).map((key) => rows[key])
  }
}

在上面的代码中,我额外实现了一个 toDrizzleResult 的方法,是为了把 better-sqlite3 的返回值按照 drizzle 需要的结构返回。

到这里,你就已经可以在 Renderer process 里直接用 drizzle 了:

function App(): JSX.Element {

  const [postList, setPosts] = useState([] as any[])

  useEffect(() => {
    database.query.posts.findMany().then(result => {
      setPosts(result)
    })
  }, [])

  return (
    <div>
      <div>
        <form onSubmit={async e => {
          e.preventDefault()

          const formData = new FormData(e.target as HTMLFormElement)
          const title = formData.get('title') as string
          if (title) {
            await database.insert(posts).values({
              id: Math.floor(Math.random() * 1000),
              title
            })

            // refetch
            const result = await database.query.posts.findMany()
            setPosts(result)
          }
        }}>
          <input name="title" type="text" placeholder="title" />
          <button>add</button>
        </form>
      </div>
      {postList.map(post => {
        return (
          <div key={post.id}>
            {post.title}
          </div>
        )
      })}
    </div>
  )
}

export default App

但这时候执行,会报错。原因是我们还没有初始化数据库。我们需要在 Main process 里初始化数据库。

首先需要用 drizzle-kit 生成 migration 文件。在 drizzle.config.ts 中指定了 migration 文件的地址:

// drizzle.config.ts
import type { Config } from 'drizzle-kit'

export default {
  schema: './src/db/schema.ts',
  out: './drizzle',
  driver: 'better-sqlite'
} satisfies Config

然后写一个 runMigrations 方法,用来初始化数据库:

export const runMigrate = async () => {
  migrate(db, {
    // 在 drizzle.config.ts 里指定的路径
    migrationsFolder: path.join(__dirname, '../../drizzle')
  })
}

这个方法需要在 Main process 启动时执行的:

async function createWindow() {
  // ...

  await runMigrate()
  createWindow()

  //...
}

实例源码

你可以在 这里 找到这个示例的完整源码。

特别感谢 EGOIST 提供灵感。

读《岩田先生:任天堂传奇社长如是说》

《岩田先生:任天堂传奇社长如是说》封面

虽然我有 Switch 游戏机, 但我不是游戏的发烧友,对任天堂及其社长岩田聪更不甚了解。前些天听《半拿铁》的《任天堂往事》系列,对任天堂的发展有了一些了解,也听闻岩田聪同时是一个非常出色的程序员,所以开始对岩田聪产生了一些兴趣,于是找到了这本收集了岩田聪说过的话的书,希望能一睹这位天才程序员对管理企业和写程序有哪些独特的见解。

这本书不长,读完后我对岩田聪确实有了更多了解。首先是从他身上学到了一些管理企业的方法。

岩田聪的管理风格非常务实和「接地气」,他讲到他喜欢和员工一对一面谈:

我在与全体员工谈话的过程中,发现了许多“经过面谈才头一回意识到的事情”。就算对方是一向保持着沟通的人,也有在一对一的场合才讲得出口的话。这样说或许不太恰当,但我重新认识到:“假如不制造推心置腹的机会,人是不会敞开心扉的。”

在书的最后有宫本茂和糸井重里谈岩田先生的追忆文章,其中糸井重里回忆说:

虽然是第一次见面,但他的话语让人觉得值得信赖。岩田先生曾回忆说:“第一次见面时我也相当紧张。”但我一点也没看出来。“在已有基础上修改,还是从头做起?”这句话单从字面上看会让人觉得不够谦逊,但岩田先生完全没有居高临下的姿态,而是传递出十分看重对方自由选择的权利的感觉。怎么说呢,原本是我们请他来帮忙,但在技术问题之外,岩田先生工作方式的魅力也令我们深受感触,见面次数越多就越发信赖他。

结合我从前被管理的感受来看,这让我意识到,管理者能给被管理者足够的信任感是非常重要的,而信任感可以来自于谦逊和务实。

岩田聪还对公司的会议规定必须有一位「会议统筹」:

所谓“会议统筹”,就是让会议良性运转的人。如果会议中缺少创意,就负责添加创意;如果创意太多,导致过于发散,就负责归纳总结。简言之,就是会议的指挥。无论什么会议,都必须有一个决心“要在会议上得出答案”的统筹者。

我们在厂里工作过的都知道,很多时候会参加一些没有结论的无用会议,如果每次会议都能指派一个这样的「会议统筹」,相信大家的效率会提高很多。

从岩田聪的谈话中,我感觉岩田聪是一个非常能够洞悉到他人内心感受的人,所以在推进项目的时候,也会非常照顾同事的感受:

世界上许多改革都是运用否定现状的逻辑推行,但这样的做法会让很多人陷入不愉快。因为当下的现状正是许多人的心血、诚意、热情构成的。如果现状由不诚实的东西构成,去否定也无妨,但由诚实的产出构建的现状,不应该被否定。

也许正是这样的性格,才能让岩田聪清楚洞悉玩家的感受,从而做出改变世界的游戏。同时,也是因为他是真正有「给别人带来快乐」的强大 passion:

岩田先生发自内心地喜欢看到大家的笑容。他也将这个目标列入任天堂的经营理念。的确,他是一个希望给世界创造更多快乐的人。并且,他是一个为了实现这个目标不惜舍身奉献的人。他喜欢帮助他人,喜欢钻研各种事物,也喜欢为此而沟通交流的过程。所以说,每周一与宫本先生一起吃午饭的时间,凝聚着岩田先生所喜爱的一切。因为他们一起讨论着游戏创意,说着“我懂了”,试图给自己与玩家都带来笑容。

我一直觉得,能成大事的人,都是那些内心有一种很强大的 passion 的人。我虽然不是资深玩家,很难从游戏中感觉到快乐,但从岩田先生的谈话中,我还是为他的这种 passion 所感动。

如果你本身是一个玩家,可能会对这本书有更深刻的理解。

最后,用岩田先生的话结束这篇读后感:

在我的名片上,我是一名社长。在我的头脑中,我是一名游戏开发者。但在我心里,我是一个玩家。

On my business card,I am a corporate president.In my mind,I am a game developer.But in my heart,I am a gamer.

名刺のうえでは、わたしは社長です。頭のなかでは、わたしはゲーム開発者。しかし、こころのなかでは、わたしはゲーマーです。

读 React 18 文档有感

昨天 Sixian 提到了 React 的新版官方文档,之前一直没有去读,今天抽空读了其中 Escape Hatches 的部分,有一些收获,也有一点感想,在这里简单分享一下。

声明:我经常使用 React, 但我不是 React 的专家,我也不再是一个对技术会进行非常深入探究的人了。所以对于 React 最新的一些 API, 可能对某些人来说是老生常谈,但对我来说是新鲜的。所以本文只是简单分享一下我看到的之前不知道的一些 API.

sixian 的 Tweet

跑两次的 useEffect

开发环境 useEffect 跑两次是故意的,是为了帮助开发者在开发环境中发现你是否正确地给你的 effect 做了 teardown.

The right question isn’t “how to run an Effect once”, but “how to fix my Effect so that it works after remounting”.

如果你的代码因为 useEffect 跑两次所以出问题,很可能你用错了 useEffect.

Alt text

讨论这样的特性是否是一种傲慢对我来说意义不大,重要的是我理解了其动机。

flushSync API

React 有一个 flushSync API, 可以强制 update DOM. 在以前我们一般是把希望在 DOM 更新后才执行的代码放在一个 setTimeout 里.

Alt text

如果想重置一个 Component, 直接给它一个新的 key

如果希望某个 component 的 props 被改变的时候做一些重置,应该直接给这个 component 赋一个新的 key, 使 React 直接重建整个 DOM, 而不是用 useEffect 手动做重置工作。

其实以前我也经常这么干,比如 Modal 在关闭和打开后重置里面 Form 的值,我会直接给 Modal 一个新的 key. 一直不知道这是否是一个 best practice, 现在得到了官方认证。

Alt text

useSyncExternalStore

这是一个我不知道的 Hook, 看起来很适合用于把别的库移植到 React 时使用,而且它对 SSR 非常友好。

一些感想

很多人觉得 React 繁琐,心智负担特别大,我也这么认为。但我一直觉得,这不是 React 本身的问题,而是 JavaScript 的问题。

React 是一个特别函数式编程思维的框架,但很可惜,JavaScript 只是一个半吊子的函数式编程语言,它只是在被设计的时候学了一点 Lisp 的皮毛,也提供了一点点函数式的 API, 但它没有很多真正的函数式编程语言应该有的基本特性,才导致了我们要写额外的代码来解决一些问题。

例如,Memoization 在一些函数式编程语言里是标配,但 JavaScript 里没有,所以你需要给函数手动套一层 React.useCallback.

函数式编程语言里, Immutable 也是标配,而在 JavaScript 里,需要用 Immer 之类的第三方库。

所以这就是为什么在多年前我很看好 ReasonML (现在叫 ReScript) 这个语言,因为它本身就是真正的函数式编程语言(它是基于 OCaml 的 JavaScript 方言),你会发现,用它来写 React 是多么舒服的一件事,因为语言本身就提供了你在 JavaScript 里需要调 API 才能实现的功能。有趣的是,React 在刚开始设计的时候用的就是 OCaml.

成也 JavaScript, 败也 JavaScript.

2023 年终总结: 和自己对话

可在小宇宙收听本文音频版

变化

2023 年初,持续了 3 年的疫情封控结束了,整个社会迎来了一个转变的开端,没人知道会变好还是变更糟糕。随后 3 月,我决定离开微软,我自己也迎来了一个转变的开端,我也不知道会变好还是更糟糕。

我财务自由了吗?如果财务自由有一个绝对值,那么无论是多少,我肯定都没有达到。离职后有很多朋友问过我这个问题,现在刚好可以在博客也跟我的博客读者分享:当我在考虑不工作时,我在思考什么?

1. 如果继续现在的状态,那么 3-5 年内我会有什么不同?

这是我在考虑是否入职微软之前用的同一个方法,是我朋友教我的。当年我觉得如果不入职微软见识见识,我一定会后悔,留在原来的公司也不会有太大的变化。今天即使我已经离职了,但我很感谢我有选择微软,我的收获非常多。所以我运用同一种决策方式来思考,即使我在微软继续工作 10 年,很大可能我得到的只是 title 和薪水的不同,这和我的目标背道而驰。这也引出了我的第二层思考:

2. 我正在做的事和我的长远目标是否对齐?

过去我会担忧和焦虑我达不到目标,后来我醒悟:没有人可以控制结果,只要我正在做的事是朝向我的长远目标就可以了。继续工作显然不是。

3. 没有固定收入怎么生活?

我有一点积蓄,虽然不多。很多人存了钱,买了房子。而我用我的积蓄买一段可以不工作的时间,我觉得还是很值得的。而且我不工作也不是天天躺着什么都不做,只要朝着我的目标在努力,这也算是一笔投资。

幸福之道

离开公司后,我的心情很复杂。我觉得我一直想寻找一个答案,但实际上我连问题是什么都不知道。我想起了年轻时读的《刀锋》,应该像书中的拉里一样去寻找,于是 7 月份我去了泰国,得到了一段出乎意料的佛教哲学之旅

创造

今年的输出比我预期的要更丰富一些。

视频创作

继 2022 年的 Logseq 分享视频,今年又做出了一个超 50,000 播放量的视频《我如何做笔记》

《代码之外 Beyond Code》播客

今年终于和 GeekPlux 一起把盘算了多年的项目真正做了出来,意外地得到了很不错的反响。甚至能在线下聚会的时候听到一些我们的听众表达的感谢。

其实节目的制作形式受到了很多我喜欢的不同的播客节目,我由此也更深刻地感觉到,一个人能做出什么样的东西,很大程度上取决于其接触、输入的东西。只有吸收优秀的东西,才有机会做出优秀的东西。

这跟做产品也很像,如果一个人从来没有体验过真正优秀的产品,那就很难做出优秀的产品。所以我总是鼓励一些朋友,不要因为一个产品要花钱所以不去用它,就算只买一个月,也要去体验一下。

Randynamic Studio

离职后我就计划把我之后做的所有小工具统一到 Randynamic Studio 的名下. 很幸运,它的真正意义上的第一个产品 Notepal 算是非常成功。它没有为我赚巨额的钱,但它解决了很多人的需求,并且这些人愿意为它付费,这是我写在博客首页很多年的一个目标,在今年终于实现了。

多年来我把 Y Combinator 的 slogan 一直放在心中,有很长一段时间我每天睡醒后心里就会自动播放这句话:

Make something people want.

很幸运我朝着这一个目标又迈进了一步。

线下聚会

解除封控后,终于可以参加一些技术圈子的线下聚会了,也有机会见到一些相识多年但从未谋面的网友。

先是参加了佐玩开发者交流会,见到了很多很年轻的后辈,更惊喜的是很多是《代码之外》的听众,他们都说在节目中收获到了很多。在那一瞬间我觉得我做的很多事情都是有意义的。

然后我到杭州找了图拉鼎,见识了数字游民的聚合地「玉鸟集」。在那里还认识了非常多的独立开发者和做产品的人,大家都很有自己的想法,对自己做的事情有自己的理解,有自己的观点,自己的品味。这种类型的朋友是很难在工作的时候遇到的。

总结

2023 是我和自已对话的一年,我开始了解自己想要的是什么,了解自己的情绪,了解自己擅长的是什么,不擅长的是什么,了解自己追求的是什么,等等。在清迈,我学会了倾听内心的声音,我也意识到,在真正了解自己的情况下,做决定似乎没有那么难了。

展望

我其实很希望在 2024 年能做一些 scope 更大的事情,同时也需要找到和我有相同 passion 的人。我一直不称自己为「独立开发者」,一是我觉得我还没达到,二是这也不是我的目标。我的目标一直是两三个有同样追求的人组成一个小 team 做一个美且在商业上可行的产品。

希望自己能往目标再迈进一大步。

开发一个浏览器插件在第三天卖出 1000 元

写下这篇文章是我的插件 Notepal 发布的第三天,目前收获了几十名付费用户,在这短短几天已经有 5000 多条微信读书笔记被同步到 Readwise, flomo 或 Obsidian 中。

(28 号没有数据是因为那天出了事故,日志没有进来。)

很感谢点进这篇文章的你没有因为 1000 元是个小数目所以选择无视,这个数目确实很小,它甚至没有很多程序员坐在办公室待一天赚得多,但「有人愿意购买」的意义远比数目的大小对目前的我来说意义更大,而且我在开发和上线整个过程中学习到了不少,也实践了不少我读的一些关于 start a business 的书,我发现在亲身经历了这个过程后,再重读这些书,体会更加深刻,所以我很想在这篇文章和大家分享我学到的,以及我所采用的一些方法论。

最初的需求

我在差不多半年前因为想把微信读书的笔记同步到笔记软件 Readwise, 所以写了一个很简单的网站(Notepal 的前身),可以把微信读书 App 里复制出来的笔记,解析成自定义的格式,也可以解析成 Readwise 能实别的 csv 格式。

虽然这种方式还是有点繁琐,而且 csv 格式其实很不稳定,很容易被笔记里的一些字符导致导入失败,但在此之前我想基本没有人可以这么「自动化」地做到这件事了。

我把它发布到了 V2EX, 写了点介绍,并且代码是开源的。

但是,在当时,我隐约觉得这个需求会有人愿意买单,因为我相信绝对不止我一个微信读书和 Readwise 的共同用户有这个需求。但我当时刚好在读 Sahil Lavingia 的 The minimalist entrepreneur 这本书(在接下来的内容我会多次提起这本书),里面有一个让我感触很大的观点—— start with community, 创业从社区开始。

什么意思呢,如果你觉得你能解决一个很多人都有的需求,首先要做的不是急着做软件做产品,而是先找到这群人,在这个群体里帮助他们。

所以当我隐约认为我能解决一部分人的需求且他们有可能为此付费时,我做了一件和开发完全没有关系的事——建用户群,然后放到网站上,我希望把有这个需求的人先聚集起来,等有一天我把这个工具做得比较稳定的时候,有一个卖出去的机会。

后来加进群的一共 50 多个人,这些人其实也没怎么说过话,有些人提了一些使用问题,然后群就基本死了。

大部份的人反馈的问题都是 csv 出问题,这个问题很难解决,我觉得如果要稳定,必须通过调用微信读书的接口,拿到 json 格式的笔记,然后通过 API 同步到 readwise. 所以我一直想把这个需求用浏览器插件来解决,我在群里也提了一句「我准备写一个浏览器插件」,但是也没人理。

于是我就把开发插件的事情搁置了。

转折,冷启动

从这个工具发布一直过了半年,突然有位推友发了一条推,对我这个工具表达了感谢:

这不足以让我惊讶,最让我惊讶的是这条推的数据:113 转推6 引用373 喜欢次数237 书签。

这让我有了动力去把我曾经想实现的插件写出来,我决定先快速做一个 MVP, 只要实现在微信读书页面展示有笔记的书,并且用 Readwsie 的 API 一键同步笔记,最后展示同步到 Readwise 后的笔记。

为什么要这么做?因为我需要做一个「半冷启动」,我知道把插件打磨到可以上线并收费的工作量不算小,但我需要做一个流畅的视频,来展现这个 idea, 并且是一个真实的实现。把这颗小石头抛出来,看能引起多大的水花。

把视频发出来后,引起了不少的回复:

这时我决定要做出来一个真正值得被这些人购买的产品,解决他们的需求,让他们心甘情愿地因为产品解决的问题和体验而买单。

从冷启动到正式上线

在写正式的产品前,我问自己一个问题,做到什么程度可以发布第一个版本。我心中的答案是这样的:

  1. 为了尽快上线,只做同步到 Readwise 的功能
  2. 上线 day one 就要开始收费,可以提供免费试用,但一定要有收费入口
  3. 体验一定要最直接——填 API token -> 选书 -> 同步 -> 查看同步到 Readwise 的笔记,一气呵成
  4. 要记录一些非隐私日志,例如同步笔记的量,错误日志

尽快发布

第一点,很多人肯定会觉得,Readwise 这么小众,同时用微信读书和 Readwise 的更小众,会不会太少人用了。这是正确的判断,但是我认为我的当务之急是把产品 ship 出来,解决我能真实看到的那不到 10 个人的需求,看能不能从如此利基(niche)的圈子赚到钱,再谈扩张用户量(而且我很快在后面的版本支持了 flomo 和 obsidian)。

Day One 就要收费

第二点,有人会说,这么小众的需求,真的能赚到钱吗?我认为,可以,但看你是想赚大钱还是小钱,小众需求难赚大钱,但越小众的人越愿意付费。我已经对这个产品有了预期——我不认为这是能赚大钱的产品,我对这个产品的目标是:解决小部分人的需求,让他们为产品提供的价值付费。

为什么要赚钱?其实我大可以免费发布,我也完全不能靠这个产品吃饭。但和多年前的我不一样,我现在觉得「用户掏钱买你的产品」是验证你的产品价值最有用的方法,无论是便宜还是贵,即使是 1 块钱,就和免费完全有不同的意义。就像打德州扑克,用你真实的钱打和用游戏币打完全是两回事

既然要收费,就有两个问题:一是收多少,二是怎么收。

收多少的问题,首先有两个选择,一次性买断和订阅制。不过像这样的产品,我觉得理智的人都不会觉得适合订阅制。谁会为了一个低频需求订阅一个插件?

如果是买断,多少钱合适呢?我没有做什么调研,就拍脑袋觉得 30 块是个不高也不低的价格,而且是一次性收费。如果用户觉得好用,还是比较大概率选择购买的。

最难的问题其实是怎么收,尤其是这个产品面向的是国内的用户。众所周知,国内的付费渠道接入门槛是相当高的。可能做这个插件也就几天的事,但接入收费渠道就要几周。这是不能接受的。就像 Paul Garahm 说的:Do things that don't scale.

为此我想了很久,于是想出了一个办法。这也是在 The minimalist entrepreneur 这本书受到 Sahil Lavingia 的启发,他说 Gumroad 第一个版本是他自己每个月手动给 creator 发钱的。

我大受震撼,我想到,我这个产品,能不能卖得出还是个问题,即使能卖,也不可能卖很多,很频繁地交易,我何不像路边滩一样挂个收款码解决呢,只是我把收款码放到一个「购买页面」而已。

于是有了这个购买页面

但是,即使我能通过收款码收钱,那如何和我的插件联动,在用户付钱后解锁功能呢。我想到最传统的软件售卖方式——卖激活码,用户通过付款码值钱备注邮箱,当我收到款后,往他邮箱发送激活码,就完成了。

流程有了,但激活码怎么来?没有办法,这个还是要自己写点代码。我花了半个小时,用 Next.js + PostgreSQL 写了一个最简单的激活码生成和验证的 API service.

激活码机制也有了,但手动发激活码还是麻烦。所以我用写了一个「快捷指令」,只要填邮箱发到我的激活码 API service, 就会自动完成从创建激活码到发送邮件一整个步骤。虽然还是手动,但我的工作就变成了收到钱,运行「快捷指令」填邮箱。

读到这里,读者应该可以深刻地体会到,Paul Garahm 所说的 Do things that don't scale 是什么意思了。我觉得在做产品的时候,一定要瞄准最重要的事情——你给客户提供的价值是什么,而不要在其它地方花太多的精力,在这些地方甚至可以先手动做一些事情,在成功了之后再把他自动化,而不是一开始就自动化。

读者应该看过一个 meme 图,一个 ATM 机,其实背后是一个人手工把钱塞出来。这个图有点搞笑,但这是一个完美的 Do things don't scale 的例子,客户需要的是拿钱,用户并不关心你背后是多简单或多复杂的流程。新产品上线,最重要的是解决客户的问题,而不是怎么解决得比较优雅。很多工程师很容易陷入一种「自动化」的诱惑,想把一切都做得最自动化,然后忽略了真正在解决的是什么问题。

自动化可以做也必须做,但不是从 day one 就开始做。

用户体验

对于体验,我在上文从冷启动的视频到上线版本都强调了「查看同步到 Readwise」的笔记,很多人可能觉得这是一个可做可不做的功能,而且这是一个从技术上来说非常简单的功能。但我觉得这是整个产品最重要的 aha moment, 我要让用户能看到笔记一键就被放到了笔记软件里面的样子,而不是只是提示同步成功,让他自己去笔记软件查。这个体验,做和不做在技术上没多大区别,但对于体验来说是有天壤之别的。

记录日志

记录日志也相当重要,尤其是错误日志,在上线以后,有一些我自己的场景没有 cover 到的 case, 都通过用户反馈然后我看错误日志成功解决了。另外,行为日志也蛮重要,可以看到自己的产品有没有被使用,使用的量有多大,从而帮助自己做一些产品决策,也是一种对自己的激励。

上线,卖出去

如果是以前的我,我一定会有一种迷思,认为产品发布上线后,就等着她被传播,如果运气好,被大 v 传播,就有流量,就能变现,如果运气不好,可能就是没人有这个需求,这个产品就失败了。

但读完 The minimalist entrepreneur (我又提到这本书了),我对销售有了很不一样的理解。书里提到,大部分的产品,头 100 个付费用户是一个一个谈来的,只有用户足够多以后,才会出现口口相传的情况。而且广告是没用的(如果不是一个以流量为核心的产品)。

Eventually strangers will buy your product, but mostly because your customers are spreading the gospel of your business and product, not because they saw an ad. But it will take time to get there. It’s not something you hit on day one.

所以我这次上线没有被动地等待,而是主动出击,在互联网上搜索关键词,找到和我有一样需求的人,评论和私信他们,告诉他们我也有一样的需求,并且做了一个十分方便的插件,请他们试一试。

刚开始这样做的时候,我还是有点羞耻,觉得自己是在互联网上发布垃圾广告,但客观来说,我是作为有同样需求的人,分享我自己做出来的可以解决他们需求的工具,好像也不算垃圾广告。而且让我惊喜的是,很多人回复都很友善,并且感谢或最终购买了我做的产品。

这也让我想到,做产品,首先要想的不是怎么做,而是卖给谁。这是最重要的问题。如果连自己都不知道目标用户,又谈何 marketing 呢?

总结

一口气写了很多,从发布上线到现在其实也才三天,其中有一天还发生了一些事故,但还是给我自己上了很重要的一课,相信我分享出来的这些也能给大家一些启发。

虽然赚的不是什么大钱,但这对我来说是非常有意义的一次体验。就像我在博客的首页写的,小时候我的理想是改变世界,现在长大了,发现能做出用户愿意付费的有为用户提供价值的产品已经很不容易了。这是我第一个发布的付费产品,而且还收获了愿意付费的一些用户。我读了很多关于创业、产品、用户体验、营销的书,等这一刻已经等了很久了。

第一次听到到账 30 元的通知的那种激动,我想会是一辈子都不会忘记的。在厂里拿 30k, 代表我面试表现还不错,也能写出公司愿意付钱的代码,但卖产品拿 30 块,代表我洞悉了需求、直面了市场(这是说出来简单,但做的时候很难的事)、为付费用户解决了问题。

非常感谢这些用户,这对我来说是很大的激励。谨以此文作为我对大家的回报。

在清迈冥想学习 7 天后,我不再「追求」幸福

在清迈结束了一周的禅修学习,想把自己的体验和感悟写下来,才发现这些我感觉不可思议的体验,用文字和图片表达出来是多么的苍白。

事缘起年初意识到自己的心理健康状态不再适合继续工作,遂辞职休息。刚好看到早见小姐分享了她在巴厘岛冥想的点滴,才发现泰国清迈有寺庙提供冥想学习。于是当晚就找好了寺庙,订下去泰国的机票。

我在出发前一直在想这次远行学习到底能不能符合我的期望学习到「真正的」冥想技术。一周下来,我的感觉是这一周学习到的和体验到的远远超出我的期望。我不仅在老师的带领下练习冥想,还对佛教有了新的认识。

通往幸福的道路在哪里

我到的第一家寺庙是 Wat Umong, 中文应该译作乌蒙寺。我在这里待了五天四夜。Umong 基本是靠自己练习,没有系统的教学。这家寺庙在丛林里,所以特别贴近自然。

寺庙虽然没有强制不使用手机,但我在第一天开始就尽量不用手机了,只限制自己一些时间查看是否有重要信息,其余时间都是飞行模式,把手机当作相机用。

当身边没有任何可以让你转移注意力的介质(手机、电脑、互联网)的时候,内心就像被放了一个扩音器,一点微小的想法都会被放大和延伸。这让我真正地了解了自己内心的声音。

在离开 Umong 的前一晚,我和另一个中国人和一个俄罗斯人一起在黑夜中喝茶。这位俄罗斯朋友带了普洱和茶壶,跟我们两个中国人科普茶道。我们从 9 点聊聊到凌晨一点,聊了关于冥想的体验,我们各自的烦恼。我说当我在冥想的时候,我总是会想到未来的事,在这几天的冥想练习,我才发现原来我的内心一直纠结在我想像的未来,我一直找不到我的快乐和幸福在哪里,于是我很痛苦。

在黑夜中喝茶聊天

俄罗斯朋友(大意是)说:Don't think too much. Focus on where you are, what you have, and what you can do with what you have.

我从他的回答得到了很大的启发。我一直在寻找快乐和幸福,以为它们在前方,我需要走一些路去找到它们。但我忽略了「当下」,在我已经拥有的东西,和当下我做出来的事情中感受它们。我也终于理解了寺庙里其中一棵树上挂的一句话的含义:There is no path to happiness, happiness is the path.

there is no path to happiness, hapiness is the path

那晚我们还聊了很多,那一刻我希望时间可以过得更慢一点,可以在这样宁静的夜晚多待一下。萤火虫在我们身边飞来飞去,远处还有虫鸣,还有壁虎的叫声(我第一次听到壁虎也有叫声)。

第二天早上我们一起到寺庙的洞穴里面,那时还没有游客进来,我在洞穴里冥想了一会儿。然后俄罗斯朋友带我们去了附近的一个动物园,他说那里有一只会讲泰文的鸟。我不知道这种鸟叫什么(应该不是鹦鹉),我们听到它竟然会讲几句不同的话,还会唱一段相同的旋律。

三人一起在洞穴

临走前,俄罗斯朋友竟然送了我一个茶杯。

下午我就离开了 Wat Umong, 出发到 Wat Suan Dok.

介于哲学和信仰之间的佛教

到达 Wat Suan Dok 的 Monk chat office, 首先是我们接下来三天课程的老师 Phra K.K. 跟我们教授关于冥想和佛教的基本知识。K.K. 讲了差不多一个半小时,我学到非常多。后来在去冥想中心路上我和其它学员聊天,我们都一致认为我们学到了超出了我们预期的知识。这也让我们对接下来三天的学习很有信心。我们这一期一共有 15 个学员,来自世界各个地方,英国、美国、中国、西班牙、印度、新加坡、韩国等等。

教学内容

在冥想中心,我们被要求一切都要慢下来。走路要慢,即使敲钟集合,一定不能急着过来,必须慢慢走。吃饭的时候,需要 eating mindfully (中文应该就是「正念饮食」),慢慢地把食物送到嘴里,慢慢地咀嚼,感受食物。

这是个小小的改变,但是三天下来,我的内心状态因此有了翻天覆地的变化。远离社交网络、做事慢下来,让我得到了内心的平静。我们从小就被鞭策一切都要快,要「抓紧时间」—— 上课铃响,要赶紧跑回教室;在学校吃饭,每次都要被老师催要吃快点回宿舍休息;走在路上,明明没有很赶时间,步伐也要很快。

当我开始把步伐慢下来的时候,就发现肢体上的缓慢,会使得内心变得平静。反过来,当内心开始胡思乱想,肢体动作就会开始不自觉地变快。有好几次,我在缓慢行走时开始分神,回过神来发现自己走得越来越快。

在第二天的讨论环节,K.K. 解答了我们很多问题,正是这些讨论,让我对佛教有了全新的理解。

我以前认为佛教是一种迷信,因为身边接触到的跟佛相关的东西,无非是「求神拜佛」。现在我才知道,佛教反对迷信。佛教不解释人是如何被创造的,没有像上帝这样创世的神。佛教是教人如何摆脱痛苦的。佛教让你相信你自己,而不是相信别人,也不是相信佛。只有你自己可以帮你自己摆脱痛苦,而不是相信一个神可以帮你。只有你自己去体验过,你才能去相信。

老师说,每个人都可以顿悟,只要持续练习(冥想)。佛陀叫作 Buddha, 意思是觉醒的人(the awakened one),每个人都能是 Buddha.

佛教是介于哲学和信仰之间的东西。我被其哲学的部分深深吸引。

知名投资人 Naval 把自己的生活哲学称为「理性佛教」,这也是我今后打算不断探索的一块。我想学习佛教中对人生、痛苦的理解,形成自己的生活方向盘。

对我来说,理性佛教意味着理解佛教所倡导的内在修行,以此让自己变得更快乐、更富有、更能活在当下、更能控制自己的情绪——成为一个更好的人。

所以,我的人生哲学就包含这两个方面:一方面是进化论,进化论是约束性原则,因为它解释了关于人类的诸多问题;另一方面是佛教,佛教是关于我们每个人内心状态的精神哲学,是最古老、最经得起时间考验的哲学。我认为这两点并不矛盾,可以相辅相成,互为补充。

—— Naval

出家的佛教徒需要遵循很多戒律,因为佛教认为痛苦来源于欲望。你的欲望越多,你将拥有的痛苦就越多(The more you desire, the more you suffer)。今天你买卡西欧,明天你就想要劳力士,于是你痛苦。有一天你得到了劳力士,就会发现不过如此,你的欲望让你还想要别的,于是你痛苦。

当然我只是一个普通人,一个带点野心活着的普通人,我不会遵守严苛的戒律,但通过冥想练习,我似乎更有能力从客观的角度观察(aware of)到我此刻的冲动的来源,从而冷静地思考是否要做出下一步的行为。

智慧是一种知道个人行为的长期后果的思维能力。 —— Naval

使人直面内心的冥想

在冥想的一周里,我基本没有怎么说话。但当我在进行冥想练习时,我实则和自己的内心对话了数千次。冥想能锻炼我们对情绪的觉察能力。我在头几天的冥想练习里,头一回通过观察我走神时不自觉思考的内容,清晰地观察到我的内心世界。在第三天,我就在我的日记本中写下了一句:「我太纠结于我想象中的未来」。

我和一起学习的一些学员交流我们的冥想体会,有的人会想起很多过去的事,而我会想到很多还没发生的,我想象的事:我接下来应该做点什么?别人会如何评价我的行为?我接下来要读什么书?要学点什么?等等等等。

佛教也认为痛苦来自对过去和未来的纠结,过去的已经过去,未来的还没发生,我们拥有的只有当下,我们应该在享受当下的幸福。而我意识到,我一直在未来寻找幸福 —— 读这些书会幸福、买这些东西会幸福、写这些东西会幸福。但是,一旦真正实现,我还是觉得不幸福,因为未来已来的时候,我又从未来的未来寻找幸福在哪里。我一直忽略了感受当下。

人们总是用一生来等待开始新的生活,这是很常见的现象。等待是一种思维状态,意味着你需要未来,而不要现在;你不要你所拥有的,而要你所没有的。任何一种形式的等待,都让你无意识地在你的此时此刻创造了一种内心的冲突:你不要此时此刻,你把希望寄托于未来。丧失对当下时刻的意识,会大大降低你的生命质量。

设定目标并努力去实现目标本身并没有错,错误的是你将它看成是你对生命和对本体的感受的替代品。

—— 《当下的力量》

在这一周的冥想,我只记得就只有短暂的 2 秒钟,我仿佛真正地感受到了当下。我不再想过去,也不再想未来,我只感受到当下,听到鸟叫,虫鸣,风吹过脸上。有一瞬间,我感觉被一个不知名的气泡笼罩,在这个气泡里有很大的安全感,我感觉我在此刻是无敌的,因为过去已经过去,未来的我不再去想,当下我可以做任何事。

你还可以通过将你的注意力集中在当下这一刻,从而在思维中创造那种空白。就是全神贯注于当下的时刻。这是一件非常值得去做的事情。通过这种方式,你可以将意识从思维活动中引开,并创造一种无思维的空白。在这种空白中,你高度警惕,注意力高度集中,但是你没有在思考。这就是冥想的本质。

—— 《当下的力量》

最后

一周的时间很短暂,但我想我这辈子都会记得这一周。离开寺庙,我还是比以往更慢地做一切事情。慢慢走、慢慢吃,没有事情是那么的紧急,手机并没有那么着急去看。不必用尽一切去填满我的时间,吃饭不看手机并不会损失任何东西。保持冥想练习,保持对自身情绪的觉察力。

最后三天我只用手机在离开前和我的老师拍了张合照。K.K. 是个非常棒的老师,跟我们讲了很多佛学知识,带我们做冥想练习,在傍晚带我们在外面欣赏晚霞和自然的宁静,在大雨的清晨 5 点多带我们听着雨声和鸟叫冥想,最后一天还送给我们每人一串带上他的祝福的珠链。

他说,monks get no salary in Thailand, I’m teaching you for free. 离开前我把我身上所有现金都 donate 给了他。

我和 KK 的合照

这是他在 YouTube 上曾经接受过的采访:https://www.youtube.com/watch?v=3g7TUT1kjC8

FAQ

教学用的语言是什么?

英语。我认为高中英语成绩不太差的水平就能听懂。

在哪里可以报名?

Wat Umong 不需要提前报名,每天早上 8 点半前报到既可。

Wat Suan Dok 的 monkchat 在官网上可以预约。3 days 的每月都有一次。虽然有 2 days, 但我建议 3 days.

复读和命运

冯大辉 Fenng 发了一条 Tweet

对于各位高考过来人,如果成绩只能上二本,要不要复读?

以下是我的想法。

我当时考到的是二本,除了学费贵一点之外,我觉得都挺好的。我个人的浅见是,命运是没有人能预测的,复读和不复读是两个不同的分支。当然这说了等于没说。所以我来试试纸上谈兵一下:

我们假设复读一定能考上一本,那么我们可以把问题简单化为一个博弈:是否用人生的一年时间去换这个一本学历?

我有一个高中同学选择了复读,最后考到了比不复读更好的学校,但是在我对他的理解和我的个人认知里,他复读和不复读,并不会有很大的区别。

在我有限的经验观察里,人和人的距离,一本和二本的差距只占了非常非常小的部分,最重要的是人本身,他是否能客观地认知自己,自己的目标在哪里,想要的生活是什么,追求的东西是哪些。再以此为前提,是否能为了目标去努力地寻找资源,运用自己的主观能动性,去趋向自己的目标。然后在运气来临的时候抓住它。

就像有些人对我的评价,我「只不过是每次赶上了末班车」。我觉得这个评价很正确。我原本是 2018 年的大学毕业生,但我 2015 年就退学了。没有人知道我退学之后是好还是坏,我也不知道,但我们马后炮地回看,当时的时间点退学,让我赶上了互联网发展的「末班车」,如果我等到毕业,在这鼓浪潮开始退去的时候,我这个二本学生,不一定能混到我现在的状态,这辆「末班车」早就跑了。

我的例子不值得效仿,能不能坐上「末班车」,除了时机,还有我早就付出过的那些别人不一定看得到的努力。但我想表达的是,在做选择的时候,不能轻视了「时间」这个非常重要的因素。早一年和晚一年,会认识不同的人(这决定了人脉),会影响你身处在浪潮哪个位置,等等等等。

这个世界就是一连串随机事件的结果,它没有好坏之分,好坏都是靠自己的努力去定义的。如果这位朋友对自己的目标并不明确,那么早一年晚一年,好像也不会有什么本质上的区别,只要自己复读的一年里心理状态可以保持得很好,那复读也不是一个坏选择。

只有在「有目标 + 肯努力」的状态下,才能把人生这张彩票玩成一场能拿一手烂牌打赢的德州扑克。

我的笔记管理法

本文视频版:

作为一个程序员,我每天都会消化大量的信息。我以前总是会担心我花了这么多时间读各种各样感兴趣的内容但最后都派不上用场,或者不知道怎么找到想要的东西。所以我喜欢学习一些关于知识管理的方法论,然后结合自己的情况慢慢摸索出一套比较适合自己的笔记方法。在这个影片我就要给大家分享一下我从浏览信息到记录笔记再到输出的整个过程是怎么样的,我是怎么在我的笔记工具里整理我的笔记的。

其实与其说是在「记笔记」,我觉得更贴切的说法是我们在构建我们自己的「第二大脑」。我们本身的大脑的长处不是记东西,而是思考和创造。所以我们需要借助计算机创造一个我们的「第二大脑」,把我们见过的有价值的东西一直记住,等我们需要的时候再从里面找出来用。

当新的一天开始,信息就会不断地进入我们的大脑。如果我们想把我们接收的信息和知识好好地进行管理,我们必须先想清楚我们的信息来源到底有哪些。

信息输入

以我为例,信息来源主要来自:

  • 脑子里突然出现的想法、灵感。
  • 我关注的社交媒体的信息流。
  • 读书、听播客。

想法、灵感

对于脑子里突然出现的想法和灵感,如果我在用电脑,我会写在 Logseq 的 Journal 里;如果我在外面,我会立刻用手机写下来。我自己用的是 Drafts 这个 App, 因为我觉得它的 tag 比 Apple Notes 的要好用。但是你可以用任何自己喜欢的 App, 可以是自带的 Apple Notes, 可以是 flomo 等等。无论是什么 App, 最重要的是找一个你能点开就开始写的 App.

至于记下来之后要怎么处理它,在后面我会在讲组织笔记的方法时详细说明。现阶段你要知道的就是,你必须找一个点开就能写的工具帮助你抓住那一刹那的灵感。

关注的信息流

我每天起床洗漱完开始打开电脑后,都会开始进入我的「快速浏览」模式,这个过程的主要目的是从我固定的一些信息获取渠道快速地了解有什么事情正在发生。

比如我每天会固定浏览这几个信息来源:

  • Twitter
  • HackerNews
  • YouTube

我在 Twitter 上主要是关注了很多国内外的程序员、设计师、产品经理、独立开发者,还有像 Paul Graham, Naval 这样的神级人物。我每天可以从里面看到我关注的这些我很欣赏的人他们在关注什么事情,有什么最新的技术,有什么最新的观点,他们在读什么书等等。

另外一个是 HackerNews, 应该算得上是世界上最大的程序员社区了,在上面可以让我发现技术圈目前最被关注的事件是什么,当然还会有很多技术之外的好文章。

还有就是 YouTube, 我在 YouTube 除了关注一些娱乐的内容之外,还会关注一些关于生产效率、技术等等的一些偏严肃的内容。我只会在晚上睡前刷 YouTube, 早上一般来说不会主动打开 YouTube.

「快速浏览」的关键在于要把重点放在「发现」而不是「吸收」上面。因为前者花的时间很短,而后者会很长,最有效率的做法是,看到你感兴趣的,就把他扔在一个统一的地方,然后忘掉,去看发现下一个。等刷完你的时间线后,再开始「吸收」刚刚扔进来的一堆信息。这有点像你在 shopping, 把你想要的都放在购物车上,然后回家再把这一车的东西吸收整理。

快速浏览的流程

应该统一放在什么地方呢?我个人在不同的情况下会用不同的工具。我感兴趣的内容一般会有以下几种:

  • 新的语言、框架、库
  • 我感兴趣的文章、视频
  • 新的产品

对于我感兴趣的文章和视频,我会首先打开它们快速地略读,如果确定我有兴趣深入地读,我就会把他保存到 Readwise Reader 这个稍后读工具里。无论是文章还是视频,我在浏览器只要点一下就可以收藏。

这里要注意的是,很多人用稍后读工具,就是点一下保存之后就把页面关掉。而我会做得更多一些,我会根据这篇文章的内容打一些 tag, 这样等到将来我需要写关于某个主题的文章时,可以在 Readwise Reader 里通过标签直接找到关于这个主题的文章。

我还会在这篇文章加上注释 —— 为什么我想读这篇文章?我想从这篇文章里得到什么?我会强迫自己添加一篇稍后读的文章的时候思考这个问题,并且用十几个字简单地描述。这样当我在之后读这篇文章的时候,我可以带着我的问题去阅读,这样会更有效率。

如果看到的是新的产品,新的技术,新的开源库之类的,我会点进去快速地了解一下这个东西,如果我觉得可能在日后我会用到它,我就会把它保存到 Raindrop 这个书签管理服务。首先是给这个东西打上合适的 tag. 然后如果我觉得我需要更深入地学习这个技术,我会把它存到一个叫作 Inbox 的文件夹,我会在「消化」内容的阶段检查我的 Inbox 文件夹。

总的来说,第一个阶段就是快速地把我感兴趣的内容捕捉下来,根据不同的内容类型分别存到 Readwise Reader 和 Raindrop 里面。

Raindrop

读书笔记

我近一年最主要的读书渠道就是微信读书,我会在书里面直接划线和写想法。可惜微信读书没办法同步到 Readwise, 所以我写了个叫 NotePal 的工具帮我把微信读书的笔记转换到 Readwise 的格式,然后我会在读完一本书之后手动地同步到 Readwise.

NotePal 界面

内容消化

我一般会在晚上下班后的闲暇时间开始我的「内容消化」阶段。这个阶段主要的任务是把我在「快速浏览」时捕捉到的东西认真地消化。所谓的「消化」,其实指的是我试图从这些内容里面:

  • 学到新的东西
  • 得到新的灵感、想法
  • 得知新的工具,将来解决某些问题的时候可以搜到。

在 Readwise Reader 里,我会在读到让我有所收获的句子下面划线,然后打 tag, 有时候还会写下我的想法。我比较喜欢用 Readwise Reader 是因为他还包含了 RSS 订阅,我可以直接把它当作 RSS 阅读器读到我订阅的博客,然后直接在上面做笔记。而且如果保存了一个 YouTube 视频,它会把字幕显示出来,可以直接在字幕上划线做笔记。这些笔记会自动同步到我的 Logseq 里面。

Readwise Reader 界面

Readwise Reader 阅读视频

Readwise 自动同步到 Logseq

稍后读的内容过完了之后,我就会检查 Raindrop 的 Inbox, 如果我被某个产品或者技术启发出来了新的想法,我会直接记在 Logseq 里。

笔记组织

把东西记在笔记软件里只是第一步,更重要的一步是怎么去组织这些笔记。组织笔记的目的不是为了满足强迫症,而是尽可能地让我记下来的笔记更容易在我需要的时候被我发现。我一般是在把笔记写进 Logseq 的同时做好组织的,因为在 Logseq 组织笔记的形式无非在关键词里加上双向链接,或者打上合适的 tag.

但我后来发现如何打好 tag 是一个学问。在 Building a Second Brain 这本书里,Tiago Frante 对如何组织笔记提出了一个框架,叫 P.A.R.A. 也就是 Projects, Areas, Resources, Archives. 这个框架主张把笔记分这四种不同的类型存放。

PARA

  • Projects 指的是正在做的项目,比如正在学习的某一门课,正在开发的某个产品。
  • Areas 指的是你感兴趣的领域,比如理财、编程、职业规划、商业等等。
  • Resources 指的是在未来可能会有用途的东西,我的理解是那些你可能会用到的,将来可以立刻作为参考的东西。比如最近我会看到很多关于 Prompt Engineering 的东西,虽然这不是我的领域,但我觉得将来会有用,所以我的 Logseq 里会有 Resources/Prompt Engineering 这个页面,跟 prompt 相关的笔记都会带上这个 tag.
  • Archives 指的是你已经完成的项目。

我觉得 P.A.R.A 这个框架给了我很大的启发,我在 Logseq 里实践了这个框架,并且根据我自己的情况,我对这个框架进行了一些适用于我的改造。

  1. 我基本不需要 Projects 这个分类,因为我有用其它任务追踪工具。
  2. 同样地,我不需要 Archives.

因此我只保留 P.A.R.A 里面的 Areas 和 Resources 两个分类。

另外,在 Building a Second Brain 这本书里有提到关于物理学家费曼的一个故事。很多人问费曼是如何做到在这么多领域都能获得成就的,费曼分享了他的一个技巧:他说他会在心中记住十几个他很感兴趣的问题,即使这些问题他不会主动拿出来钻研,但当他每次看到一些新的发现,新的研究成果的时候,都会把这些研究发现去匹配他心中的那些问题,看这些新的发现能不能解决那些问题。

You have to keep a dozen of your favorite problems constantly present in your mind, although by and large they will lay in a dormant state. Every time you hear or read a new trick or a new result, test it against each of your twelve problems to see whether it helps. Every once in a while there will be a hit, and people will say, “How did he do it? He must be a genius!

读到这个故事的时候我非常受启发,我觉得的确是要把做的笔记也归纳到自己在思考的问题那里。这样日积月累,我的问题下面的笔记就会越来越多,多个笔记串起来会对我的问题更有帮助。

所以除了 Areas 和 Resources 这两个分类,我还建立了 Questions 这个分类,我把一直在困扰我的一些问题都建起了相应的页面,当我在记笔记时,如果觉得这个笔记和我的某个问题有关,就会链接过去。

Question, 如何做决策

Area

Resources, Prompt Engineering

无论是哪种组织框架,我觉得最重要的是让记下的笔记尽量容易被找到。当我对自己关注的 Areas 和 Resources 有一个大概的梳理后,我对之后记的笔记最后应该流向哪里也有了更清晰的认知。

检索输出

当我开始想要就某主题开始写作的时候,我就会打开 Typora 和 Logseq, 在 Typora 大概列出提纲,然后针对这个主题在 Logseq, Raindrop, Readwise Reader 里面检索相关的资料。

Typora

得益于双向链接,我在 Logseq 检索自己想要的东西的时候还算方便,因为只要你的双向链接和 tag 打得够好,在后期检索的时候你能看到很多不同的笔记互相关联。至于怎么在 Logseq 里面灵活地跳转,不属于这次要讲的范围。之后可能会单独做影片再讲讲。

总结

这就是我最近自己摸索出来的管理我的笔记的方法。你不一定和我用一样的工具,我觉得我想传达的最核心的观点的:列出你自己每天接收信息的来源。找出一个你认为最方便的方法把你从这些信息源读到的有价值的内容保存下来,然后想清楚它最终应该流向什么地方,可能是某个 tag, 可能是 P.A.R.A 里面其中的一个类型,或者是你自己的组织方法。当你有了自己的一套熟悉的方法论,你就会更清楚日后应该怎么找到你想要的东西。

28 岁,我还想生猛下去

今天是我的 28 岁生日。

在 2016 年的生日,我在博客写下了一篇《21 岁我想说的》,那年我进入了「大厂」,获得了世俗意义的「成功」,于是写下了这篇充满雄心壮志的文章。我觉得我离自己想要的「成功」越来越接近,我终于要用技术让别人刮目相看,用技术做点有意义的事情,帮助公司的业务 move fast. 我 13 岁就梦想的事情,在那一年成真了。

很感谢当年的自己写下了那篇文章,记录下来我的年少轻狂。就像王小波在《黄金时代》里所说的:

那一天我二十一岁,在我一生的黄金时代。我有好多奢望。我想爱,想吃,还想在一瞬间变成天上半明半暗的云。

但是直到今天我 28 岁,我才深刻地理解这段话的下半段:

后来我才知道,生活就是个缓慢受锤的过程,人一天天老下去,奢望也一天天消失,最后变得像挨了锤的牛一样。可是我过二十一岁生日时没有预见到这一点。我觉得自己会永远生猛下去,什么也锤不了我。

回想我的 18 岁,2013 年,高二。我很迷茫,课桌边上的《JavaScript 高级程序设计》已经被我翻了不知道多少次。后来某次技术大会,我有幸和译者曹力一起吃了个饭,我说我是看你翻译的书长大的。我们一笑而过。但他应该不明白这本书对我的意义有多大。

周末不能回家,我就去网吧写代码,写开源库,妄想可以在社区做出一点什么。那时我觉得没人懂我的抱负,身边的同学不知道 JavaScript 是什么,他们只知道我成绩不好,不爱学习。这真是冤枉,我比大多数人都爱学习。

我迷茫是因为我不知道我的路应该怎么走。我因为学编程耽误了课业,但我真的有机会靠编程吃饭吗?是我太幼稚、太理想主义了吗?我会不会到最后,课业和编程都做不好?

在那些迷茫的时光,读书和写作拯救了我。我一有空就去学校图书馆看书,读了很多现代作品(钱钟书,胡适,王小波,阿城,等等)。还读到了毛姆,那本《刀锋》改变了我很多。

每次语文考试我都很开心,因为可以写作文。我喜欢借题发挥,格子都不够用。我根本不在意因为离题被打低分,我想表达,我想在文字中创造美。那时的语文老师很懂我,是当时为数不多让我感受到被理解的人,我很感谢他。

但我还是迷茫,有时我宁愿可以像那些「好学生」一样,心无旁骛,家长开心,老师安心。但我做不到。

19 岁那年,2014,我高考。学校照例开「成人礼」,在大堂给大家打鸡血,奋战高考。我觉得可笑,但又觉得没资格,我只是一个连高中课程都应付不来的自以为是的可怜虫。

高三一整年我恶补了我落下了课业,我像自学编程一样自学高一高二的课本,我把整个课本一字不落地读,因为我发现很多考点都出在书本里一些很隐秘的角落。我像在做一个试验,用最有效率的方法尽量提升我的成绩。于是我买了历年高考真题,认真研究每一年的规律,哪些东西一定会考,题的难度是什么样的变化,基于这些规律去定制我的备考策略。

高考完以后,我感受到了一股自由的气息。窗外在木棉树上叫的鸟,对于还在上学的人来说,多美都和她毫无关系,而对于高考完的我来说,我就是那只鸟,我想叫就叫,我想飞就飞。

很幸运,我的备考策略算是成功了。大家都觉得我考不上本科,但我最终还是考上了一所不怎么样的本科。

我不想再回顾我的大学,我在博客说得已经够多了。但有一件事很有趣,我在博客也提到过:

这是一封 2014 年我写给未来的自己的信。当时在上大一,是上职业规划课时老师布置给我们的一个作业,写完我们就交给老师保存,他会在我们大学毕业后返还。

老师通过微信联系我说有封信在他那里,并拍了这张照片。我早已忘掉我曾写过这封信,看完后我就全记起来了。

记起来刚上大学时对未知的憧憬,记起来在教室听无聊的网页设计课,记起来退学那天给宿舍阿姨还了钥匙后坐上中巴回广州市区的时候听的那首《一路向北》。

退学后我正式加入了一家创业公司,严格来说是从退学前的 part time 最后决定了退学做 full time. 没有人给我意见,应该说没有人能给我意见。选择是困难的,因为没有人知道选择的结果一定是什么。我在那一刻意识到我是一个成年人了,不像考试时的题,人生给你出的题是没有固定答案的。选择的意义,是在选择后你做的事赋予的。

那是一段艰难又快乐的日子。

快乐是因为我觉得整个世界的大门为我敞开了,写代码从此不是我「不务正业」的事,我终于来到了我的主场。

艰难是因为我没有钱。我在珠江新城上班,在地铁沿线的番禺大石租了一个 600 块一个月的城中村房子。房子在二楼,没有光线,窗外是墙壁。自来水是有异味的,我只能买桶装水洗漱。出门上班要戴口罩,因为外面路过的都是货车,沙尘滚滚。

只要 600 块的房子,因为要押二付一,刚从学校出来,我付不起。我向老板提前出了下个月的薪水才租下。

那时我开始听《IT 公论》,播客的声音让我觉得,即使我在这昏天黑地的小房子里,我和他们一样,都在这互联网的浪潮里参与其中,我已经不是被困在学校里的人了,我要在其中大展拳脚。

拿到阿里的 offer 完全出于我的意料之外,即使我很喜欢当时的创业公司,以及两位创始人,但在这样的机会面前,我还是选择了「大厂」。

我记得当天面试了几轮,最后跟 HR 见面聊了一下期望,我说了一个数。回家的路上,觉得自己是不是开得有点高了,会不会因为开高了让我错失了这个机会?在我刚坐到家附近的地铁站,竟然通知我有 offer 了。

在之后,我开始面对别人附加给我的光环 —— 年轻、辍学、年少有为。我一点也不以此为荣,我只感到害怕。我不是年少有为,我只是笨鸟先飞了,我在别人还在读大二的时候,我就提前进入了社会。我总是想,我的同学都很聪明,等他们毕业了,也许只要一年,就能学到我提前学到的东西了。

我很享受这样的工作,因为编程就是我的生活,我享受她。当年的我甚至很奇怪,为什么竟然有人不想上班?上班写代码是我最开心的事,她给我带来了成就感,我在用我的能力创造价值。

这鼓热血,真是充满魅力。

时过境迁,那鼓热血已经花光在最该花的年纪了。热血退去,我才明白,编程是我真正想要追求的东西里最表面的一层。我在意的是,我用代码实现了什么,给用户带来了什么价值。我想用我的品味和价值观,创造一些美好的东西解决实际问题,并且做成一种可持续的商业模式,又或者创造一种商业模式。

商业是一种艺术,赚钱的方式也有千万种。我想用体面和美在这个世界上生存以及留下一点什么。

希望我自己会永远生猛下去,什么也锤不了我。

你看,迦摩罗,如果你将一粒石子投入水中,石子会沿着最短的路径沉入水底。恰如悉达多有了目标并下定决心。悉达多什么都不做,他等待、思考、斋戒。他穿行于尘世万物间正如石子飞入水底—— 不必费力,无需挣扎;他自会被指引,他任凭自己沉落。目标会指引他,因为他禁止任何干扰目标的事情进入他的灵魂。这是悉达多做沙门时学到的。愚人们称其为魔法。愚人以为此乃魔鬼所为。其实,魔鬼无所作为,魔鬼并不存在。每个人都能施展法术。每个人都能实现目标,如果他会思考、等待、斋戒。

—— 赫尔曼·黑塞《悉达多》

2022 年终总结

今年的年终总结写得有点晚,2021 年觉得自己一事无成,迟迟没有动笔。如今 2022 年也已经过去了,虽然同样觉得自己在今年一事无成,但转念又想自己应该给自己一个回顾的机会,终于还是动笔了。

琐碎的生活变化

2022 年因为工作的变化,主要都生活在苏州。苏州是一个很「慢」的城市,我算不上特别喜欢,但我很喜欢苏州的湖。我在 ByteTalk 的这期播客节目 里提到过一些我对苏州的看法。

东方之门

苏州的一些区域可以骑摩托车上路,我喜欢摩托,所以买了一辆摩托通勤,周末偶尔也会到不同的湖边溜溜车,还认识了一些摩友。

我的摩托车

意外的插曲

我从多年前开始受情绪问题的困扰,在 2021 年开始变得更严重,到了 2022 年,它变得更加凶猛。

那根一直在紧绷的弦,最终还是断了。我在年中因为焦虑导致了惊恐发作, 这是一种焦虑的躯体化症状。由于没有相关经验,发作时我误以为心脏出现了问题,于是拨打了 120. 这也是我第一次坐上救护车。那一周我住进了医院,做了身体检查,排除了器官问题。

我的情绪问题导致了我在 2021 和 2022 都没有写什么工作以外的代码,我的内心一直在希望自己放松和希望自己多做点事情两者之间不断摇摆。我曾经认为自己是一个很清楚自己想要什么,也很有目标的人,但最近两年,我越来越不知道自己在做什么,要做什么。

零碎的产出

Logseq 分享

我在一个晚上非常即兴地录了一个关于我如何使用 Logseq 的视频,没想到会有这么多的播放量,在 YouTube 上也收到了不少的感谢和鼓励。

CodeSpeedy

发布了一个我自己个人在用的 Code snippet 管理工具 CodeSpeedy. 是我第一个发布的用 Tauri 写的程序。

AMAzingTalk

因为看到 Twitter 上有不少人提供付费咨询服务,所以做了一个页面收录了一些不错的提供咨询的人。目前只是一个简单的静态网页,但我有计划把它做成一个产品。

《兰迪和他的朋友们》

我身边有不少不同领域的有意思的朋友,很想通过访谈和交流的形式把他们各自领域的哲学分享给更多人。于是我开了一个不定期更新的 podcast 《兰迪和他的朋友们》。目前只出了两期,分别是和我的健身教练聊了健身,还有和我的同事兼攀岩岩友聊了攀岩。

新的一年我还计划做一个和香港流行音乐相关的节目,感兴趣的朋友欢迎 Email 和我交流。

攀岩

今年在我的同事 sixian 的带领下接触了攀岩,我也意外地爱上了这个运动。喜欢和岩友一起交流线路的不同爬法和技巧,喜欢用出优雅的脚法的感觉,喜欢到不同的城市拜访当地的岩馆 —— 例如我和 sixian 到 上海攀岩工厂探店 (Bilibili).

攀岩

攀岩是一个力量和技巧结合的运动,我因为有力量训练的基础,让我入门这个运动非常快。但我喜欢学习和应用技巧的部分,我对攀岩的兴趣很大程度上来自于观看了 Jain Kim 的优雅脚法.

感染 Omicron

在 2022 年的尾声感染了 Omicron, 反复发烧 2 天多,咳嗽了近一周。我在开放了一周后才打了第一针科兴。我的症状比较轻,没有失去味觉和嗅觉,也没有所谓的「刀片噪」,只有轻微的扁桃体发炎类似的感觉。

最后

2023 年我就 28 岁了,有时候填表格看到年龄的一项写着 27, 会让我有些感觉陌生。我总是觉得自己距离踏出学校的那天还不是很远,在写做了 6 年程序员,我学到的 10 条经验 时我才发现自己已经工作了 6 年多了。我也偶尔看回 我 21 生日时写的博客, 很多问题在我内心缠绕:我还能做些什么?我想要的到底是什么?我的价值是什么?我接受了自己只是个普通人吗?

我没有答案。

做了 6 年程序员,我学到的 10 条经验

保持一颗解决问题的心

按照我的观察,那些在工作中用技术取胜的人们共同点都在于他们能保持一颗解决问题的心。他们可以率先想到一种更优的手段解决存在的问题(一般是效率问题)。他们不是嗅觉特别灵敏或者技术特别强,而是当他们遇到了问题,不是把它作为抱怨的话题,而是开始思考这个问题为什么没人解决、应该怎么解决,然后把它实现出来。这种心态在职场上特别稀缺。

我在创业公司的时候做一个图文排版的 App, 设计师会设计一些模板,然后交给我来实现。当时我们有很多模板,为了测试这些模板实现在不同的手机屏幕大小会有什么问题,我们要花特别多的精力。可以想象测试的数量 = 模板数量 * 屏幕尺寸的数量。后来我用 puppeteer 写了个自动生成不同屏幕和模板的截图,直接交给设计师一个一个地看。节省了大量的时间。这个事情没什么技术含量,但它解决了很重要的问题。

工作中需要解决的问题不仅仅在代码上,也有可能出现在非技术问题上。工作中我特别喜欢和非技术同事聊天,了解他们的工作。因为我常常觉得影响项目前进的原因不一定出在我们用了不适合的技术或者不够「先进」的技术。了解非技术同事的工作流程让我大有收获,我会发现他们有一些工作是可以通过写一段程序把原本的工作量做到指数级的下降,而通常非技术同事是很难察觉到的。

这样的例子特别多。有次我和一个运营同事聊天,我们当时在开发一个新闻内容的管理后台,他们常常用这个后台捞一些内容做分析。聊天的时候了解到他们有一部份的工作就是在上面按条件查询一些内容,再一条条地粘贴到 excel 里面,他说这常常要花一下午。后来我帮她做了一个一键导出成 excel 的功能。

她觉得这很不可思议,但这在技术的角度来说太简单了。我也因此了解到,对于不是做技术的人来说,他们很难察觉到哪一些事情是可以用技术解决的,所以我们不能希望他们主动地提出一个需求,只能我们作为掌握技术的人主动地去了解他们。

又一次我和我们的测试吃饭,聊到他们怎么做测试。我发现他们会用 mindmap 先梳理出来一些测试流程,然后一个个地做。但是痛点在于他们常常要手动维护一个文档列出这些 case 的测试结果,这些结果包括截屏,以及证明测试通过的请求返回信息等等。不但麻烦,还很难追踪。

于是我做了一个小 demo Web App,他们可以直接上传他们做好的 Mindmap, 通过他们的 mindmap 直接生成出来 case item,在项目的开发环境页面代码里面,只要注入这个 case id,就可以在测试之前开始记录请求日志,结束之后会上传到这个平台,这样在这个平台就能直接看到每一个 case 操作的时候的整个过程的记录。这个小 demo 后来被用于花呗的大部份前端项目,当然听说现在已经做得和我当时做的小 demo 完全不同了。

了解你的用户

我自认为自己还算是一个有那么一些产品思维的程序员,因为经常也会写一些自己的小产品。但在刚出来工作的时候,我在工作中太沉迷于技术本身。把心思都放在了诸如怎么重构,怎么改进构建速度之类的问题。我在阿里 P5 升 P6 的答辩中,我被问了一个我至今印象深刻的问题:你有了解你的用户是怎么用你在做的这个东西吗?

这个问题是我从来没有想过的,我哑口无言。可能它只是一个晋升答辩问题模板中的一个问题,但对我来说这个问题让我清醒了许多。当时我们做的是内部用的新闻内容管理后台,这个后台的用户是一些小编。我们和这些小编有一个群,但基本是用来报 bug 的。我离这些用户这么近,却从来没有了解过他们的使用感受。我想,如果我当时找他们聊一聊,可能也会有意想不到的收获。或许他们会报怨这个后台的加载速度很慢,我们就可以着手解决加载速度的问题,而不是和同事纠结在用哪种前端状态管理库这种无聊事情上。用户并不关心我们用的是 MobX 还是 Redux.

不要拿自己的尺子去度量别人

我刚出来工作犯的最大的错误之一就是拿自己的尺子去度量别人。我因为从小对编程痴迷,写程序对我来说是人生中最大的兴趣,我把几乎所有的时间都花在了技术上。当时我天真地认为所有程序员都应该像我这样,对待技术也应该有一种理想主义,我在互联网上结交的技术朋友都是这样的。所以我当时对我的同事特别苛刻,甚至对那些把写程序只当成工作的人嗤之以鼻。后来回想起来,这是非常错误的想法。每个人有每个人的追求,技术也只是多个兴趣爱好的其中一种。在当时别人的眼里我可能是个「怪人」,甚至有点「装逼」。

保持学习、be open-mind

我每天都会在 Twitter 和 Hackernews 发现很多最新的技术和技术思考,我关注了很多开源库的作者,我可以第一时间了解到他们最近在思考什么,在接触什么。这种主动接收会扩大你的眼界,让你在解决问题的时候有更广的思路。

不要只关注自己的领域。我还关注了很多写 Rust, 写 Go, 写 iOS/Android 的人。学习主要是学习技术背后解决问题的方式,这些解决问题的方式说不准也能应用到你自己的领域。

保持学习一直是和同行拉开差距最重要的一点。

想清楚,再下手写代码

我写代码的速度非常快,因为我已经花了超过十年的时间在写代码了。很多东西想实现,对我来说基本是纯粹的堆代码。导致我非常容易不经过多的思考就开始动手写。我为此吃了不少亏,常常写到一半发现一些没有想到过的问题,导致需要重新设计,重新改写。我的一位前老板很了解我,他也是个多年经验的程序员了。有一次我们在讨论一个新东西,他对我说,「不要着急,想清楚了再写」。这句话我一直记在心里。后来每次动手写代码之前,我都会把整个流程的设计先思考清楚,避免了很多不必要的重写。

敬畏用户

在写自己的一些没什么人用的开源库或者公司内部用的平台的时候,通常不需要过多思考就能把代码发布出去。到了做花呗这种用户基数庞大的产品,才意识到代码发布和以前所体验到的完全不同。

蚂蚁金服有代码发布的「三板斧」,这是从入职培训到实际工作中就会被反复提及的 must-follow rule. 「三板斧」指的是「可灰度」、「可监控」、「可回滚」。在代码发布之前,要先想想自己的代码是不是符合这三个条件。

  • 你的代码发布之后,如果出了问题,是不是可以被监控到的?
  • 你的代码是不是可以灰度发布的,而不是一下子全量被推到线上的?
  • 代码发布以后,出了问题,是不是可以回滚的?如何回滚?

在经历了用户基数如此庞大的产品开发之后,我对代码发布变得尤为审慎。我记得有次只是单纯改了某段 HTML, 但我还是盯着这个 PR diff 看了几分钟,在想这个修改会不会导致什么问题。

虽然蚂蚁的基础建设可以让这三板斧很容易实现(有成熟的发布平台进行灰度和回滚,有成熟的监控库);虽然即使遵守了三板斧,还是会有 bug. 但是这种代码发布的思维模式是好的,即使我到了别的公司,我在代码发布前还是会问自己这三个问题。

跨团队合作是利益交换

在大公司里,有时在做一个事情的时候,需要别的团队一起合作,或许是用到别的团队的接口、或许是需要别的团队开发新的接口,但这通常很难。我以前天真地以为,只要我们做的事情是有利于业务的,别的团队自然就应该一起合作。但实际上,大家更看中的是这个事情对自己的团队有什么好处。

换位思考一下,我们和别的团队合作,对于他们来说,增加了工作量,增加了风险(带来更高的 qps, 写更多的代码会导致更多的维护成本)。决定是否合作,首先取决于这是否是自上而下的要求,其次就是合作对他们的 KPI 有没有好处。

所以我学会了在游说别的团队合作的时候,首先应该想明白合作能给别人带来什么好处,而不是对事情本身夸夸其谈。这样更容易促成合作。

用别人的语言交流,会有意想不到的收获

有研究发现如果你用别人的母语和他交流,他会更容易接受你的观点,对你也会更友好。我发现这个心理同样适用在技术交流中。作为一个前端程序员,在和后端程序员商量技术方案的时候,如果可以更多地使用后端的术语,从后端的角度反推前端的想法,他会更容易接受。

我自己业余是个 full stack 程序员,所以很容易切换到别人的语境,也能从别人的角度去理解他的想法。因此沟通会更加顺畅。

理解前人写的「烂代码」

这里的「理解」不是指理解烂代码的逻辑,而是理解为什么会写成烂代码。我经常会听到同事报怨他看到的旧代码写得如何烂,但是实际上很多烂代码产生的原因不是因为技术不行,而是受限于技术的发展和业务的复杂性。随着自己写的代码越来越多,就越能理解这些「烂代码」的存在。看出来了烂代码,也不要着急去重构,这些代码很有可能藏着一些你不知道的特殊业务需求。如果你不需要碰这些代码,那就尽量别碰。

在技术和工作之间找到平衡点

在刚出来工作的前几年,我特别陶醉在把自己学到的新东西试图用在工作中。我的想法是,只有我把这个技术用到实际的工作中,我才算学习了这个技术。

其实这个想法是不对的,学习技术并不一定要求你把他用到工作中。工作就是工作,学习就是学习。工作的内容是为了业务服务的。我在创业公司工作的时候,曾经因为把一个我刚学习到的库用在业务中,因为一些我不知道的坑导致业务进度出了点问题。老板生气地说:业务不是你的试验田。

我后来遇到很多「后辈」(我竟然也开始有后辈了) 请教我说觉得自己在工作中不能运用到自己平时学习的技术,因此觉得自己技术没什么长进。我认为这种想法不太正确。

能把学习到的技术运用到自己的工作中当然是最好的,但这是可遇不可求的事。但是这不代表没有用在工作中,就等于没有真正学习到这个技术。我认为很多人对技术学习有错误的理解,对我来说,学习技术的精髓在于理解这个技术的 Why, What, How. 和能不能用到工作中没有太大的关系。

举个例子,我在刚接触到 Redux 的时候,我去学习它,除了了解它怎么用以外,我特别关心的是,Redux 的哲学是什么?是什么启发了 Redux 的作者创造了 Redux? 他和别的库有什么不同?顺着这些问题,我就会了解更多的东西,比如我发现 Redux 是受了 Elm 的启发,我就会去了解 Elm -> 了解 Functional Programming -> 了解 Immutable, 然后关注 Dan (Redux 的作者) 的 Twitter, 看他日常会分享什么,看他对自己做的这个东西的理解是什么。即使我没有把 Redux 用在工作中(事实上我从来没有用过 Redux),但我在学习这个库的时候,我学习到的不仅仅是 Redux 本身,还有它背后的更多东西。我可能很快就会忘掉 Redux 的 API, 但那又如何,那些它背后的知识才是最有价值的,是不会被忘掉的。

而工作则相当于是一个真实的场景,是在你学习新的技术的时候,帮助你进行实际思考的场景。你需要有意识地去想,这个技术如果用到我的工作中,它是否适合?它能解决什么问题?它为什么适合?它为什么不适合。当你在学习新技术的时候,结合这个技术,多思考这些问题,这才是真正的学习。

读《九宫格写作法》

九宫格写作法封面

倪爽Twitter 推荐了这本书,对于喜欢写作的我来说很吸引,花了很短时间就把这本书读完了。在这里推荐给大家。这本书主要给认为写不出来文章的人提供一些指南,书的前半部分是方法论,后半部分是写作套路(或者说写作框架)。

九宫格指的是在写作之前,从文章的中心,可以扩散出 8 个方格,填写和这个中心有关的信息,型成一个写作九宫格。九宫格的作用是在写作前迫使你先对文章中心进行思考、提出问题,然后进行填充。是一种帮助你在写作前收集信息的辅助手段。

所以通俗地讲,九宫格写作法的核心在于训练你在写作前的信息收集习惯。书中有一个比较确切的例子:如果你准备一场旅行,打算旅行过后写一篇游记。你就应该在旅行之前就画好一个九宫格,填上这篇游记你想要写所有的要点:

写作九宫格

然后在你旅行的时候,才有目标进行相关信息的采集,然后填充到九宫格里面,这样当旅行结束,你已经收集到了足够的信息,把它们整理成为一篇完整的游记。而不是到了当地漫无目的地游玩,然后回来只能追溯一下往事,已经忘了可以写一些什么,最终写出来的只不过是一些浅显的感受。

九宫格写作法实际上并不要求你只能有九个格子,你可以任意地扩张。所以这个写作方法,中心的思想就是:在写作之前,列出你对于这个写作主题的所有想要讨论的问题,然后再动笔。在列出九宫格的同时,整个文章的结构就自然而然地形成了。

九宫格写作法突出了写作前信息收集对于轻松写出一篇有价值的长文的重要性,这也是为什么作为一个有意向写作的人来说,平时做笔记那么重要。只有平时在信息输入的时候有意识地把价值输入作为笔记存到你的第二大脑(笔记工具)里,将来才可以基于平时的笔记轻松地写出一篇文章。这个过程也叫作「信息内化」。九宫格写作法辅助了你的「信息内化」能力。

这就是创作过程的核心: 在你选择你要写的东西之前,你必须下功夫研究并积累。理想情况下,你应该在创作开始之前就开始研究,这样一旦你决定了一个话题,你就有数周、数月甚至数年的丰富资料可以利用。这就是为什么你要把信息放在「第二大脑」如此重要的原因。

—— 真正的思考技术

我以前也常常觉得很难下笔,就是因为平时即使输入了很多信息,但没有把他们内化成自己的信息。陷入了「写作就是从一张白纸开始」的迷思。几年前机缘巧合下开始使用 Roam Research 并且读了 How to take smart note, 开始把平时摄取的有价值的输入记在了我的笔记工具里,通过双向链接,使我很容易在构思一篇文章的时候得到足够的素材。所以在读这本书的时候我很有共鸣,也对我的写作模式提供了一个更系统的方法论支持。

书的后半部分基本是一些写作的套路(框架),对我来说这部分意义不大。因为我从小就通过在学校的写作和平时写博客中得到了很多实践积累,对于写作的内容(例如哪些应该是文章重点、在文章中如何表达有价值的主观感受等等)我自己已经有了较多的体会。所以这部分我只进行了略读。对于缺少实践的读者来说,这部分也可以细读。

遗憾最小化框架 —— 如何做决定

对于我来说,我一直认为自己缺少做决策的能力。成年人的生活不像学生时代,所有的题目都有固定答案。所有的选择都不存在绝对的对和错,它只是在人的一生 checkout 了一个新的 branch. 我总是不知道应该如何做决定。

刚好看到了 Jeff Bezos 曾经谈到了自己是如何做决定的,他称之为 Regret Minimization Framwork (遗憾最小化框架) —— 他会想象自己到了 80 岁的时候,是否会认为不做这件事情会让自己遗憾。尽量让人生的遗憾数量最小化。即使当时的选择后来看来是错误的,但也不会后悔尝试过。

我很受启发。想起在 4 年前,我曾经问 Cycle.js 的作者 staltz 一个 问题, 我问他是否也会因为自己做的库没有像 React / Vue 那样火起来而感到气馁。他说他偶尔也会感到气馁,但他一直以来做事遵循一个 guideline:

Ask yourself: in 10 years from now, will you be proud of having done this? ... Do things that your future self will be proud, is my motto for the time being.

记得在一年前决定是否接收微软的 offer 的时候我非常犹豫,因为微软给出的 offer 比其它国内「大厂」给的少非常多。但是我回想起初中的时候,读了很多关于比尔盖茨的传记、微软的创业故事,十分向往。那时候的我没有预料到我将来有一天竟然有机会加入这家公司。也不会想到作为一个大学没有毕业的人竟然也得到了这样的机会。我也咨询了很多朋友我是否应该接受这个机会,他们有的会说其实在微软写的代码甚至没有在国内的公司那么 fancy. 但是我想,在加入了微软之后即使在后来可能发现这未必能得到我想象中的东西,但如果在很多年后回想起来我曾经放弃了这个机会,我一定会后悔没有尝试体验一下在这家公司工作。所以最后我决定接受了这个 offer.

再谈辍学

我在退学的那一年写了一篇《你根本用不着退学》来告诫想退学的人慎重选择退学这种方式。今年是写完这篇博客的第五年,我想结合五年来作为一个辍学生遇到的经历,再谈谈退学。

我第一次感受到辍学给我带来的副作用是刚辍学不久的时候,曾经给一些「大厂」投过简历,在 HR 筛选简历的一轮就被筛选掉了。我当时对进「大厂」的心理预期本来就很低,所以这对我来说根本影响不了我的心情,被筛选掉反而是正常结果。我很幸运,阿里的其中一个部门鬼使神差地给我了一个机会,我最终顺利通过了面试。

我辍学后在别人眼里过得一帆风顺,是因为我受到了运气的眷顾。我很幸运,我出生在一个小康家庭,可以让我从小就能接触到我往后一生都热爱的计算机和互联网;我很幸运,在辍学前遇到了李秉骏先生(他们现在在做无人机空间数据应用平台),让我即使辍学了,还可以在一家创业公司尽情发挥自己的技术;我很幸运,阿里的 HR 对我的简历手下留情,也许只不过是当时的团队刚好非常缺人,我刚好投了简历,所以我通过了。但如果我说这只是单纯的幸运,那未免有点虚伪。我很清楚,即使我得到了这样的运气,我也需要有足够的实力和其它同样有运气的人比较胜出。所以这是运气和实力结合的结果,但没有运气,这一切都不会发生。

打过德州扑克的都知道,没有人可以靠运气一直取胜。

在这五年,我就是别人口中「幸存者偏差」的那个「幸存者」。但我从来没有因为我的辍学光环感到一点骄傲。相反,我在退学一年后遇到了严重的抑郁和焦虑症状而不得不求医。除了求医,我还天真地坚信只要我保持旺盛的精力去 "make something", 去不断写代码,去不断地学习,我就能战胜我的情绪病。

但很遗憾,情绪问题是个复杂的问题。我不能说辍学是我情绪病的来源,但它在其中参与了很大的比重。而且这种比重是随着时间的流逝线性增长的。在最严重的时候,我甚至完全无法沉下心做我这辈子最热爱的事 —— 写代码。说出来有点好笑,我甚至会因为在面试的时候分不清 JavaScript 里 class A extends class B 中,A.prototype 和 B.prototype 的关系而陷入严重的自我怀疑。

这是辍学给我带来最大的副作用。它让人把一切成功归因于运气,然后让人从不甘平庸变成不能平庸。

辍学在「幸存者」身上是没用的光环,在不幸的人身上就是噩梦。我能在 V2EX 找到很多这样的例子:

https://www.v2ex.com/t/808601

18 年大学差 5 学分没修完,结业了。 做了两家创业公司,现在 2021 年 10 月份了,想跳槽。

面试了华为、绿盟、建信金科,面试全部通过,全部因为学历拒发 offer 。 中间尝试联系 Boss 上专科要求的岗位,也拒绝了我。。。

https://www.v2ex.com/t/700608

但是技术在当前领域有一定优势。最近开始面试。卡在学历了

在 BOSS/拉钩 /找内推 /猎头 基本上都过不了 HR,基本看到我简历上教育背景后,确认没有双证后直接表示可能不太行。

更惨点的是面试都过了,两级直属领导当面明确表示希望我入职,然后被 HR 用规章制度否了。

https://www.v2ex.com/t/708542

根据招聘网站透漏的信息,HR 筛选候选人的最常用选项的顺序是,学历、年龄、性别、工作年限 哦,忘了说,内推我也试过了,找朋友内推也被挂学历了,一种是他们有硬性要求,二个是能力也没有到无视学历的地步

心里的无助真的很难受,加上心里不断的胡思乱想,真的是有偷偷哭过,甚至一度有人生从来的想法

在这之后的求职经历里,我有因为辍学遭遇到不公吗?我只能说,一个正常的面试官或者 HR, 是一定不会在面试的时候明显地表现出对我学历的顾虑的。但从交流之中就可以感受到,那些对我的学历和辍学经历的「好奇」,其中一定是掺杂着担忧的,至少这它一定不是一个加分项。

程序员是少有可以靠技术发声的群体,也许靠所谓的实力可以在职场混得还不错,但人的一生除了工作,还有太多的事情不是靠技术就能解决那么单纯的。也许是政策上的,也许是流程上的,也许只是别人的偏见。这些都是需要有预期去承受的。你准备好了吗?

就像我五年前写的一样,退学只是一种选择,没有人知道它是好的还是坏的。但它必定附带了很多随之而来的巨大的副作用。不是每一个人都有运气和实力和这些副作用抗衡。这个社会在一定程度上有少数派生存的空间,但社会的很多规则是设计给多数人的。抗衡需要勇气和付出比多数人多得多得多的心血。你准备好了吗?

每个人在漫长的人生中都会面临很多不同选择是否成为那个少数派。可能是辍学,可能是性少数,可能是不婚,可能是丁克。衷心希望在作出了这样的选择之后的人,一定要努力成为那个别人口中的「幸存者」。之所以这么说,是因为这五年来,我见过身边太多有运气但不努力的人,也见过太多既很努力又有运气的人。这让我能感受到,「事在人为」和「何不食肉糜」之间并不能划上等号。用自己的力量去给偏见打一个沉重的巴掌吧。

我给自己设立了每月 $20 的开源捐赠预算

Babel 官方最近发布了一篇博客 称 Babel 团队的 funding 已经不够了。一直以来我零碎地捐赠过一些开源项目和个人,在卖贴纸和 T 恤的时候也把大部分的利润捐赠给了 Vue. Babel 这件事让我有了固定每月捐赠一些开源项目的想法。

目前设定的是每月至少 $20 (约 130 多元人民币) 的预算,这样平均至少可以捐赠 4 个不同的项目。我可以在这个预算内根据我的技术栈灵活地选择我对哪些项目进行捐赠。

我个人会选择一些这样的项目进行捐赠:

  • 我在做 side-project 的时候经常会用的项目
  • 项目没有融资背景(例如我非常喜欢 Prisma, 可是它已经有了 $12M 的 A 轮融资)
  • 做了一些我用到的工具的个人开发者

目前我捐赠的项目和个人

  • EGOIST 在全职做开源的好朋友
  • chakra-ui 我用 React 写 side-project 必用的 UI component 库
  • dayjs 我经常会用的 moment.js alternative
  • react-query 我经常会用的 React 请求库
  • next-auth 非常好用的 Next.js OAuth 库
  • Logseq 开源的 Roam Research alternative

你可以在 OpenCollectiveGitHub Sponsor 看到我对哪些项目和个人进行了捐赠。

国内用户捐赠的方法

国内用户捐赠只要具有以下其中一个条件即可:

  • 注册 Paypal 账号和国内银行卡绑定
  • 持有一张 Visa 信用卡

OpenCollective 支持 Paypal 和信用卡,甚至支持银联的信用卡,因此一般国内用户都能很容易通过 OpenCollective 进行捐赠。GitHub sponsor 不支持 Paypal, 需要 Visa.

除了每月固定的捐助,还可以选择一次性的捐助。

我对开源的看法

我从开放的源代码学到了非常宝贵的知识,这些知识帮助我成为了更好的程序员。功利一些来说,我通过使用这些开源项目得到了可以谋生的岗位,这些开源项目竟然是开放和免费的。

有些人认为,做了一个成功开源项目,即使没有钱,也收获了名气。对我个人来说,我确实也通过做开源项目得到了非常多的机会,但是正是自己做过开源项目,才深知维护一个开源项目的成本不是靠「名气」就可以支撑起来的。

关于这个话题,强烈建议收听牛油果烤面包的这一集 Podcast: 《#70. 和Vue.js的创造者尤雨溪聊开源软件》

也可以读一读这本 Working in Public: The Making and Maintenance of Open Source Software, 里面有很多开源软件维护者面临的问题。

我们可以为开源做什么?

  • 根据自己的经济情况,设立一个捐赠预算($1 也很不错),给自己生活中、工作中用到的开源工具进行捐赠。
  • 和我一样写一篇文章介绍你对哪些项目进行了捐赠,鼓励更多的开发者对开源项目捐赠。
  • 贡献有意义的 PR,即使只是文案的修改。

Svelte 的异步更新实现原理

我对 Svelte 的看法 一文里,我分析了 Svelte 在编译时实现 Reactive 的原理。在这篇文章,我将分析在 Svelte 里更新一个状态 (state) 值后更新到 UI 的这一过程。

阅读本文前,你应该至少:

原理分析

为了保持简单,先从一个和 Svelte 无关的例子讲起:

// 假设我们正在实现一个 counter, 只有一个 state,就是 count, 它是一个 number:
let count = 0

// 我们可以实现一个 setCount, 来改变 count 的值,顺便执行更新 UI:
function setCount(newVal) {
  count = newVal
  updateUI()
}

function updateUI() {
  console.log("update ui with count:", count)
}

setCount(1) //=> update ui with count: 1
setCount(2) //=> update ui with count: 2
setCount(3) //=> update ui with count: 3

这样实现很简单,但是有一个严重的问题:连续的状态更新会连续触发 updateUI, 性能会非常糟糕。解决这个问题的方法是:把同一个事件循环里的所有状态更新造成的 UI 更新统一合并(batch)到一个 microtask 里统一执行。

// 基于 Promise 实现一个把函数放到 microtask 里的函数
function createMicroTask(fn) {
  Promise.resovle().then(fn);
}

let updateScheduled = false;
function scheduleUpdate() {
  if (!updateScheduled) {
    // 当首次 schedule 时,把 updateUI 放到 microtask 中
    createMicroTask(updateUI)
    updateScheduled = true;
  }
}

function updateUI() {
  updateScheduled = false
  console.log("update ui with count:", count)
}

// 在 setCount 时,不再直接触发 updateUI, 而是 schedule 一个 update
function setCount(newVal) {
  count = newVal
	scheduleUpdate()
}

setCount(1)
setCount(2)
setCount(3)
//=> update ui with count: 3

这样,在同一个事件循环里,多个状态更新只会触发一次 UI 更新。

现在假设页面上有一个 h1, updateUI 中会更新它:

let count = 0
const h1 = document.querySelector('h1')

function updateUI() {
  updateScheduled = false
	h1.innerHTML = `${count}`
}

setCount(1)
setCount(2)
setCount(3)
//=> update ui with count: 3 

So far so good. 但是相信不少人年轻的时候曾经写过这样的代码:

setCount(1)
setCount(2)
setCount(3)
console.log(h1.innerHTML) //=> 0

setCount(3) 后, h1.innerHTML 竟不是预期中的 3. 仔细一想,当然了,updateUI 是在同步代码执行完后,开始执行 microtask 队列的时候才触发的啊。

为了可以在 setCount 后拿到更新后正确的值,我们可以把关于 UI 的操作也放到下一个 microtask 才执行。为了方便,我们可以写一个 tick 函数:

function tick() {
  return new Promise.resolve()
}

async () => {
  setCount(1)
  setCount(2)
  setCount(3)
  await tick()
  console.log(h1.innerHTML) //=> 3
}

Svelte 的实际做法

回到 Svelte:

<script>
    let count = 0
</script>

<div>
    <span>{count}</span>
    <button on:click={() => count++}>+</button>
    <button on:click={() => count--}>-</button>
</div>

这个组件会被编译成一个 fragment (你不需要读懂下面的代码):

function create_fragment(ctx) {
	let div;
	let span;
	let t0;
	let t1;
	let button0;
	let t3;
	let button1;
	let mounted;
	let dispose;

	return {
		c() {
			div = element("div");
			span = element("span");
			t0 = text(/*count*/ ctx[0]);
			t1 = space();
			button0 = element("button");
			button0.textContent = "+";
			t3 = space();
			button1 = element("button");
			button1.textContent = "-";
		},
		m(target, anchor) {
			insert(target, div, anchor);
			append(div, span);
			append(span, t0);
			append(div, t1);
			append(div, button0);
			append(div, t3);
			append(div, button1);

			if (!mounted) {
				dispose = [
					listen(button0, "click", /*click_handler*/ ctx[1]),
					listen(button1, "click", /*click_handler_1*/ ctx[2])
				];

				mounted = true;
			}
		},
		p(ctx, [dirty]) {
			if (dirty & /*count*/ 1) set_data(t0, /*count*/ ctx[0]);
		},
		i: noop,
		o: noop,
		d(detaching) {
			if (detaching) detach(div);
			mounted = false;
			run_all(dispose);
		}
	};
}

function instance($$self, $$props, $$invalidate) {
	let count = 0;
	const click_handler = () => $$invalidate(0, count++, count);
	const click_handler_1 = () => $$invalidate(0, count--, count);
	return [count, click_handler, click_handler_1];
}

不要被吓到,一个 Svelte Fragment 实际上是一个函数返回几个必要的方法:

function createFragment(ctx) {
  return {
    // 创建 DOM 的方法
    c(): {},
    // 把 DOM mount 到节点的方法,以及事件绑定
    m(): {},
    // DOM 节点更新的方法
    p(): {},
		// unmount 的方法
    d() {}
  }
}

这里的 p(), 就是类似上文提到的 updateUI.

instance 则是 <script> 之中定义的变量和一些 event handlers. $$invalidate(0, count--, count) 类似上文提到的 setCount. 在真实的 Svelte 中整个状态更新的流程简单地来说就是:

  1. 用户点击 button, 触发 $$invalidate(0, count--, count)
  2. 触发 schedule_update(), 通知框架这个 fragment 需要被更新(make_dirty()),框架会维护一个 dirty_components 的数组
  3. 同步代码执行完后,开始执行 microtask, 触发更新(flush),遍历 dirty_components, 触发每一个 component 的 p()

读《蒂姆·库克传》

书封面

购买链接

我对 Apple 的钦佩除了是因为产品本身的设计和体验,还有 Apple 作为商业巨头,对个人隐私、环保、无障碍等等这些「人文关怀」有着并非门面功夫的执着。读这本书之前我没有想到这些执着其实大部分来自于 Tim Cook.

2014年5月,美国国家公共政策研究中心保守派智囊团的一名成员逼迫库克衡量可持续发展项目会给苹果带来的利益损失,库克拒绝了,他说:“当我们付出努力,为了让盲人也可以使用我们的设备时,我是不会考虑该死的ROI(投资回报率)的。苹果在发起环保倡议、保护员工安全和其他政策时也是如此。“如果你让我做事的时候,唯一关心的就是投资回报率,那我希望你马上抛出苹果的股票!”库克向这位保守投资者吼道。

谈到慈善这件事,乔布斯是出了名的不感兴趣,他说他能做的最大的慈善,就是提升苹果的市值,这样股东们就会有更多的钱投入他们选择的事业里。

《纽约时报》还发表了一份有关富士康工作条件的调查报告,这份报告后来还获得了普利策奖。一向性格谦和的库克对此大发雷霆,在跟员工们分享看法时,他表达出了与乔布斯迥然不同的态度——乔布斯曾被指责在此事上表现冷漠。在一封内部邮件中,库克说看到这份报告后很“气恼”,甚至“火冒三丈”。他开诚布公地写道:“我们的供应链遍及全球,我们关心其中的每一位员工。任何一起事故都令人深感不安,任何有关工作环境的问题都值得关注。任何认为我们对员工漠不关心的想法都完全错误,并且令人反感。你们是最了解真相的人,这种指责与我们的价值观相悖。苹果完全不是这样的。”库克再三强调苹果正在改善成千上万名工人的工作条件,他决心要纠正大众的误解。

这本所谓的「传」实际上是作者把 Tim Cook 的公开言说和资料以及一些他身边的好友的回忆整理在一起,还原出了 Tim Cook 的人生和事业轨迹。相当于是加长版的 Tim Cook 维基百科页。

从 Steve Jobs 重返 Apple 不久开始,Tim Cook 就已经在苹果发挥了很大的价值,解决了苹果在供应链上的问题,这对当时的 Apple 而言是很至关重要的。可是解决供应链问题不像做产品那么 cool, 作为局外人的消费者,很难看到 Tim Cook 的价值所在。「只有当供应链出现了问题的时候,人们才会发现供应链的问题」。从 Tim Cook 解决问题的方式可以看出来,他和 Steve Jobs 一样能做到 Think Different.

书中还记录了很多 Tim Cook 关于人文关怀的事例。我猜测他的这些执着是来源于他从小遇见过的种族主义者,还有他本身作为社会中的少数派(同性恋),让他把尊重多元社会视为己任。有趣的是他在读大学的时候还修过一门「道德课」:

库克在杜克大学读书的时候,修了一门道德课,这门课对他日后产生了深远的影响。一名工程师学习道德课程,这并不常见,但是库克希望拓宽自己的视野,希望自己对工程学和商业有全球化的视角。在职业生涯的早期,库克就在思索,公司可以成为一股向善的力量以推动世界的改变。

购买链接

我对 Svelte 的看法

我在很早前已经听说过 Svelte, 但是一直没有机会在新的项目真正地用上。最近在尝试模仿实现一个 Roam Research 的编辑器,考虑到可能会有大量和 DOM 交互的逻辑,所以我趁这个机会选择了 Svelte, 目前这个编辑器的已经完成了大部分的功能,开源在 https://github.com/djyde/plastic-editor .

在开发的过程中我对 Svelte 非常满意,这篇文章打算就我的体验来说说我对 Svelte 的一些看法。

你需要有使用过任意一个前端框架的经验才能读懂本文。这篇文章不是一篇 Svelte 教程,如果你想学习 Svelte, 请浏览 Svelte 的官方教程本文试图通过告诉读者 Svelte 在编译阶段做了什么,来让只听说过 Svelte 的读者,从浅层的原理和设计的角度,了解到 Svelte 是一个怎么样的框架、她和其它框架的区别是什么

Svelte 是什么?

简单来说 Svelte 是一个在编译时实现了 Reactivity (反应式) 的框架,所以它既是一个框架,同时也是一个 Compiler. 如何理解「编译时实现了 Reactivity」?先说说什么是 Reactivity.

let a = 1
let b = 2
let c = a + b
console.log(c) //=> 3
a = 2
console.log(c) //=> 3, not 4

JavaScript 没有 Reactivity 的能力,所以即使 let c = a + b, 在改变了 ab 的值之后,c 不会因此而改变。真正的 Reactive 与此相反。

拿 Excel 举例,Excel 是天生 Reactive 的,一个 Excel 里面的单元格,可以编写简单的函数组合其它单元格的值,而组合的结果会随着依赖的单元格改变而自动改变。例如单元格 C1 如果是 =SUM(A1:B1) , 那么 C1 值会随着 A1B1 的值而重新计算和改变。

如果你用过 Vue 或者 MobX, 你应该知道它们实现 Reactivity 的原理:它们使用了 defineProperty 或者 Proxy, 在 setter 这一层做了一些手脚,当对象的某个成员被赋值的时候,执行更新逻辑。

const reactive = {}
Object.defineProperty(reactive, 'a', {
  set(value) {
		console.log('a was updated')
  }
})

reactive.a = 'changed' //=> a was updated

这是一种「运行时」的手段,它需要在运行时改变了赋值行为,所以在用 Vue 的时候,你必需把需要 Reactivity 的对象包在 data 里,上文的例子用 Vue 需要这么写:

const yourData = {
  data() {
    return {
      a: 1,
      b: 2
    }
  },
  computed: { // 还有 computed
    c() {
      return this.a + this.b
    }
  }
}

const reactive = new Vue(yourData)
console.log(reactive.c) //=> 3
reactive.a = 2
console.log(reactive.c) //=> 4, not 3

但是我们可以想一想,如果不用 defineProperty, 可以实现类似的功能吗?

当然可以:

let a = 1
let b = 2
let c = a + b

function update() {
  c = a + b
}

console.log(c) //=> 3

a = 2; update()
console.log(c) //=> 4

b = 5; update()
console.log(c) //=> 6

我们只要每次在赋值的时候,手动触发一个 update 函数,那么 c 的值就会重新计算,不就实现了 Reactivity 的效果吗?

但是这样做未免太蠢,要写太多的代码,而且很容易漏掉。不过,我们可以借助 Compiler 帮我们做这些事!在编译时,每当遇到赋值语句,我们就让它在赋值语句的后面自动加一个调用 update 方法的语句。

这就是 Svelte 做的事情。当然实际上的实现要更加复杂一些(比如需要把更新放在同一个 microtask 里),但原理是一样的。

Reactivity 是现代的前端框架的标配,因为从前 MVC 的开发模式对 UI 开发来说,要在 Controller 手动操作 DOM 这个步骤显得有点枯燥和多余,我们希望 UI 是自动随着数据的变化而自动更新的。因此不同的框架除了写法不尽相同外,最主要的区别还是在于框架在检测到数据更新后,如何处理 UI 的更新

以一个 Counter 为例,这是 React 的写法:

function Counter () {
  const [ count, setCount ] = React.useState(0)
  return (
    <>
    	<div>{count}</div>
	    <button onClick={_ => setCount(count + 1) }>+</button>
      <button onClick={_ => setCount(count - 1) }>-</button>
    </>
  )
}

我在之前的文章已经谈过 React 的思想,在这里稍作重复。React 主张「视图」是「状态」的函数:

UI = f(state)

setState 的时候,这个函数会重新执行,因为是新的 state, 因此 UI 是变化的。在 React 里,UI 是 Virtual DOM, 用算法对比两个 DOM 树,来算出哪个真实的 DOM 需要被更新。

React 的实现非常「粗暴」,因为它是真的会重新执行这个函数,在上面的例子中,Counter 会在每次状态变化的时候被重新执行。这使得写 React 组件要多留心性能问题,因为你要避免在函数内部进行不必要的计算:

function Counter () {
  const [ count, setCount ] = React.useState(0)

+ doSomethingHeavy()
  
  return (
    <>
    	<div>{count}</div>
	    <button onClick={_ => setCount(count + 1) }>+</button>
      <button onClick={_ => setCount(count - 1) }>-</button>
    </>
  )
}

在「古典」React 里,你不得不写 shouldComponentUpdate, 在现代 React, 你同样需要引入 useCallbackuseMemo,手动地缓存函数,来避免性能问题。

同样是使用 Virtual DOM 的 Vue 却没有这个问题,因为 Vue 的机制(依赖收集)决定了它不必重新执行整个 UI 函数来换取新的 Virutal DOM 树,当某个状态更新的时候,它明确地知道应该 diff 哪些节点。

如果你是 Vue 用户,而不太清楚 React 的机制, 你可以把一个 React 组件函数想象成是一个 Vue 的 computed 里的成员函数, 你一定知道在 computed 的成员函数里做耗时计算的后果是什么。

现代前端框架倾向于使用 Virtual DOM, 我认为主要出于两点:

  • Virtual DOM 可以 port 到任何除了 Web 以外的宿主环境。
  • Virtual DOM diff 算法足够快,框架把 DOM diff 和 DOM 修改的工作交给了算法,可以把精力花在实现框架的其它功能上。

Virtual DOM 的本质就是找出需要被修改的真实 DOM 节点,难道不用 Virtual DOM 就不能实现吗?当然不是。回到上面的 Counter 的例子,我们如何用 Vanilla JS (原生 JavaScript) 来实现:

const target = document.querySelector('#app')

// state
let count = 0

// view
const div = document.createElement('div')
const countText = document.createTextNode(`${count}`)
div.appendChild(countText)

const button1 = document.createElement('button')
const button1Text = document.createTextNode(`+`)
button1.appendChild(button1Text)

const button2 = document.createElement('button')
const button2Text = document.createTextNode(`-`)
button2.appendChild(button2Text)

target.appendChild(div)
target.appendChild(button1)
target.appendChild(button2)

// event
button1.addEventListener('click', () => {
  count += 1
})
button2.addEventListener('click', () => {
  count -= 1
})

上面的程序生成了 UI, 绑定了点击事件,改变了状态 count 的值。但是显然 UI 是不会随之改变的,所以我们需要写一个 update 函数,让状态在变化的时候,触发特定的 UI 更新逻辑:

const target = document.querySelector('#app')

// state
let state = {
  count: 0
}

// view
const div = document.createElement('div')
const countText = document.createTextNode(`${state.count}`)
div.appendChild(countText)

const button1 = document.createElement('button')
const button1Text = document.createTextNode(`+`)
button1.appendChild(button1Text)

const button2 = document.createElement('button')
const button2Text = document.createTextNode(`-`)
button2.appendChild(button2Text)

target.appendChild(div)
target.appendChild(button1)
target.appendChild(button2)

// event
button1.addEventListener('click', () => {
  update('count', state.count + 1)
})
button2.addEventListener('click', () => {
  update('count', state.count - 1)
})

// update
function update(key, value) {
  state[key] = value
  countText.nodeValue = state[key]
}

现在点击按钮,div 显示的 count 就会变化了,因为我们在 update 函数指明了 UI 更新的逻辑。

我敢保证上面的程序性能一定比 React 版本的更好(当然在这个例子可能只相差 0.0000002ms),因为 DOM diff 再快还是要算,原生 JavaScript 是不需要算的。

但没人愿意这样写程序:

  1. 这样的代码完全丧失了可读性,无法一眼看出 UI 树的结构。
  2. UI 只要一调整,就需要写大量的代码。
  3. 每当有元素依赖一个状态值,就要手动在 update 函数中加上 UI 更新的逻辑。和传统的 MVC 没区别。

Svelte 是一个 Compiler, 帮助你在编译时生成这些 Vanilla JS 的代码,同时收集依赖,生成 UI 更新的逻辑。

<div>hello world</div>

会被编译成:

const div = document.createElement('div')
const text = document.createTextNode('hello world')
div.appendChild(text)

这并不是 Svelte 编译出来的代码,真实的代码经过了封装。这里只是为了方便讲解,但本质上是一致的。

加一个变量:

<script>
  let count = 0
</script>
<div>
  {count}
</div>

会被编译成:

let count = 0
const div = document.createElement('div')
const text = document.createTextNode(`${count}`)
div.appendChild(text)

update() {
  text.nodeValue = count
}

再次强调,这并非 Svelte 编译出来的真实代码。如果你对 Svelte 真实编译出来的代码有兴趣,可以在官方的 REPL https://svelte.dev/repl 写一个简单的 Svelte 组件然后看 JS output. 然后推荐进一步阅读 https://lihautan.com/compile-svelte-in-your-head/

编译器在遇到 {count} 的时候,就可以收集到在 count 变化的时候需要更新哪些元素。也就是说,像 Vue 那样通过 getter 实现的依赖收集,Svelte 通过编译阶段实现了。

一个完整的 Counter:

<script>
  let count = 0
</script>

<div>
  {count}
</div>
<button on:click={_ => { count += 1 }}>+</button>
<button on:click={_ => { count -= 1 }}>-</button>

你可以在这里打开这个程序 https://svelte.dev/repl/cfd45cdafb8a48a88edab6921c69ac0c?version=3

在编译的阶段,只要遇到赋值语句,就可以插入一个语句来安排 UI update (schedule update). 就像本文最初提到的方法一样。

到这里,已经解释了什么是「在编译时实现了 Reactivity」。

Svelte 的特殊语法

Svelte 里有一个比较特殊的语法,值得在这里介绍一下。

回到最初的例子:

let a = 1
let b = 2
let c = a + b

c 依赖了其它变量,如果其中的依赖发生了改变,它应该会被重新计算。在 Vue 里可以通过 computed 实现:

const reactive = new Vue({
  data() {
    return {
      a: 1,
      b: 2
    }
  },
  computed: {
    c() {
      return this.a + this.b
    }
  }
})

Svelte 用了一个特殊的语法实现了类似 computed 的功能:

<script>
  let a = 1
  let b = 2
  $: c = a + b
</script>

<span>{c}</span>

: 其实是一个合法的 JavaScript 语法,https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Statements/label

当然背后同样是在编译时实现的,它在更新视图的函数前会加入执行 a + b 并赋值给 c 的语句。

Svelte 的跨组件通讯

状态管理和组件通讯是前端框架设计比较重要的一块,通常的做法是 Lifting State Up. 也就是两个同级组件如果想要共享状态,那就把这个状态上升到共同的父组件上面去。Svelte 也可以这么做。不过 Svelte 里有 store , 你可以定义一个 writable store, 然后在不同的组件之间进行读取和更新:

// store.js
import { wrtiable } from 'svelte/store'
export let count = writable(0)

// A.svelte
<script>
import { count } from './store.js'
  
let count_value
const unsub = count.subscribe((newValue) => {
  count_value = newValue
})
</script>
<sapn>{count_value}</sapn>

// B.svelte
<script>
import { count } from './store.js'
</script>
<button on:click={_ => count.set(2) }>mutate</button>

每个 writable store 其实是一个 object, 在需要用到这个值的组件里可以 subscribe 他的变化,然后更新到自己组件里的状态。在另一个组件里可以调用 set 更新这个状态的值。

敏锐的读者可能已经发现,上面的代码没有处理组件销毁时 unsubscribe. 当然我可以在 onDestroy() 里调用 unsub(), 但是在 Svelte, 有个更便捷的语法:

// store.js
import { wrtiable } from 'svelte/store'
export let count = writable(0)

// A.svelte
<script>
import { count } from './store.js'
  
</script>
<sapn>{$count}</sapn>

// B.svelte
<script>
import { count } from './store.js'
</script>
<button on:click={_ => $count = 2 }>mutate</button>

Svelte 规定了在 store 前面加一个 $, 会自动 subscribe, 得到它的值,并且在组件被销毁的时候自动帮你 unsubscribe 它。对它进行赋值的时候,其实相当于执行了 .set() 的方法。

不要以为 svelte/store 的 writable 返回的对象是一个黑魔法,其实他不过是一个普通的对象而已,只是 Svelte 在编译的时候对 $ 为首的变量做了一些特殊处理。比如:

<script>
  console.log($name)
</script>

会编译成:

let name_value
const unsub = name.subscribe((newValue) => {
  name_value = newValue
})
console.log(name_value)
onDestroy(() =>{
  unsub()
})

同样这不是 Svelte 实际生成的代码,这里是为了讲解,但本质和 Svelte 的逻辑一致

对一个 store 赋值:

<script>
	$name = 'new'
</script>

会被编译成:

name_value.set('name')

看到这里,你可能已经知道了,其实 store 只不过是一个普通的对象,只是如果你在 Svelte 里通过 $ 符号进行操作的时候,会调用它的一些方法。这些方法就是:

  • subscribe. 返回一个 unsubscribe 方法
  • set

只要任何对象有实现两个方法,就可以用 $ 进行这样的便捷使用。这是一种 Svelte 约定的 store 协议. writable 是一个创建符合 store 协议的对象的捷径,不是什么黑魔法。

如果你用 RxJS, 你会发现 RxJS 天生就兼容 store 协议

我对 Svelte 的看法

我用 React 太久了,在用回不需要关心 rerender 问题的框架,我觉得非常舒服😄。当然 Vue 也不需要,我觉得在某个程度上 Svelte 和 Vue 很像,它和 Vue 的区别是:

  • 实现 Reactivity 的原理都是依赖收集,但 Svelte 是在编译时完成了,Vue 在运行时收集。
  • Vue 用了 Virtual DOM, Svelte 在编译时就知道它应该操作哪个 DOM

因为所有的功能都是在编译时实现的,所以用 Svelte 写的代码非常直白 —— 像正常定义变量一样定义变量,在 HTML 里使用这个变量,修改这个变量(而且没有 this)。运行时的框架无论如何简单,至少需要你写一层 Wrapper,例如在 Vue 里你必须把状态包在 data 函数中返回。

我认为 Write less code 是重要的,在前端开发的领域,我们花了太多精力在处理像 immutable, reactivity 这些 UI 开发标配的特性上面, 我一直认为需要有一门天生带了这些特性的语言用来写 UI 应用,而不是引入第三方包、写一些 boilerplate code.

Svelte 非常接近,而且它做到了不需要你学习新的语法,在 JavaScript 里面就实现了这样的效果(目前只是 Reactivity)。

所以无论是对比哪个框架,我个人觉得 Svelte 对我来说最大的吸引力是可以写更少的代码,而且在写代码的时候感觉是符合直觉的,这一点非常重要,我认为 React 并没有做好这一点。我碰到过太多用 React 的朋友同样遇到过这样的问题:

function Timer () {

  const [ time, setTime ] = React.useState(0)
  
  React.useEffect(() => {
   	const interval = setInterval(() => {
      setTime(time + 1)
    }, 1000)
    
    return () => clearInterval(interval)
  }, [])

  return (
    <div>{time}</div>
  )
}

这个 time 在视图里一直是 1,为什么?谁会在第一次写这样的逻辑的时候就能意识到问题出现在哪里呢?

同样的逻辑在 Svelte 里,就不会有这样的问题(当然在 Vue 里也不会有):

<script>
  let time = 0
	
	const interval = setInterval(() => {
		time = time + 1
	}, 1000)

	onDestroy(() => {
		clearInterval(interval)
	})
</script>

<div>
  {time}
</div>

当然可能有人会说,Randy, 你连在 hooks 里面怎么用 setInterval 都不知道,你一定是 JavaScript 基础不行,回去补补基础吧!对于这样的想法,我只能一笑了之😆。

我没有办法提出一个杀手级的功能吸引没有用过 Svelte 的人来用 Svelte, 我只能说我喜欢 Svelte 是因为她是一个简单的框架,我在用她的时候不用想太多 Why.

Vue 的缺点和优点基本就是 Svelte 的缺点和优点,但 Svelte 从用法上比 Vue 更简单。

至于有人说,Svelte 生成的代码体积小,我认为这确实是一个优势,不过要注意的是用 Svelte 生成的代码,体积是线性增长的。这是我大概画的一个图,表示项目规模和代码体积的关系:

这只是一个大概的趋势,图中的斜率不是一个准确的值。详细在 Github 看相关的讨论 https://github.com/sveltejs/svelte/issues/2546

Svelte 的适用场景

生态是技术选型一个很重要的考虑因素,Svelte 显然不是数一数二的选择,基本不用期望 Svelte 有什么现成的组件库。

我认为 Svelte 非常适合用来做活动页。活动页没有很复杂的交互,以渲染和事件绑定为主。我常常想做简单的活动页还用 React 也太委屈自己了吧。

另外一个很好的用法是用 Svelte 写的 UI 组件,可以包装成给不同框架用的组件。Svelte 暴露了足够的 API 可以适配到其它框架,比如写一个日历组件,然后分别包装成 React 和 Vue 的版本。

结论

我很喜欢 Rich Harris (Svelte 的作者) 的很多想法,在 YouTube 看他的演讲有很大的收获。你可以在下面的延伸链接找到更多有关 Svelte 的我认为不错的视频,十分推荐 Rethinking Reactivity 这个演讲(我在 Bilibili 没有找到搬运,所以只能贴上 YouTube 链接了)。

最后想说的是,学习一个框架或者一个语言,不一定是非要把它用到生产环境才算是有用。我很喜欢看新的技术和学不同的语言,更多地是因为想看看在面对同一个问题的时候,不同的人解决问题的思路是怎么样的,这才是框架和语言真正的魅力。比如说你不一定非要用 Elm, 但是你一定能从 Elm 的设计学到点什么。Svelte 也一样 :)

延伸链接

做这个世界的生产者

偶尔看到这篇名叫 Consume less, create more 的文章,是作者从「消费者」转变为「生产者」的心路历程。他每天在巴士上面用 iPhone 写点什么,最终写出了这篇文章。

我从懂事开始就一直觉得自己在这个世界上与众不同,觉得自己的出生一定是背负了某个特殊的使命。后来发现原来每个人年轻的时候都是这么想的,随着年纪渐长,大多数人开始接受自己只是个普通人的事实。我也不例外。

但唯一没有变过的是我没有因为意识到了自己的平凡而停止了试图变得不平凡。其中的一个表现就是我希望在这个世界上,我是作为一个生产者存在的,而不只是一个消费者。

写博客、编程、做音乐都是我的生产,即使我写的博客没有让我成为几十万粉丝的 KOL, 我写的代码没有几万的 star, 唱的歌的观看数只是几百,我还是保持生产,因为对于这个世界,能让我有那么一点与众不同的是我的生产,而不是我的消费。

我讨厌抖音,不是因为它的内容,是因为它默默地把人驯化成了贪婪的信息消费者。如果我什么都不创造,那么我每天看 Hacker News 和别人每天刷抖音没有任何区别。

这个世界的每个人都是消费者,但不是每个人都是生产者。如果你也希望为这个世界留下点什么,就去创造,别担心你创造的是不是垃圾,只要创造了,是一定会有收获的。

2020 年终总结

COVID-19 疫情最严重的时候我在广州,记得我 12 月底从杭州飞回广州的时候,根本没有想到事情会发展得和 SARS 一样严重。印象中那个时候的关键词是:安静,冷空气,未知。不知道这样的情况会持续多久,每天关注着 Telegram 的 Broadcast 看新增的感染有多少。同时还远程办公着,做着「花呗来电」的需求。没错,就是那个让你付钱还要每月打电话提醒你还花呗的服务。

没想到多年前我们都在探索的远程办公,会因为一场传染病让更多人体验到了。但显然,我体验到的只是畸形的远程办公,他披上了远程办公的外衣,让我们每个人变成了 On call 24 小时 —— 比如我甚至在炒菜的时候还要接入电话会议。

我思考了很多,在 4 月份决定离开阿里巴巴。后来我面试了一些公司,那些你能说得出名字的公司我应该面了有一半。我很奇怪现在的「大厂」招聘到底是什么标准,问的问题到底有什么意义,让我一度严重怀疑我的能力。当然,有些面试者可能也觉得奇怪,为什么这样一个在博客上侃侃而谈的人,连浏览器如何验证 HTTPS 证书的都不知道。

后来落实了现在的工作,不过我现阶段不打算过多的提及我的新工作。但在这个新的环境,遇到的新的人们,对我一些固有的想法有了不少的冲击,让我有了一些新的想法和反思:

  • 优秀的程序员和普通的程序员之间很多时候只隔了一层好奇心,对「有没有更好,更有效率,更聪明的做法」的好奇心。在这个时代,「代码」是一种生产资料的,而大量的程序员们却自己忽略了这一点,或者自愿放弃了这点
  • 把技术作为兴趣的人是少数,只是互联网让我们有机会聚集到了一起,这让我很长一段时间误以为这就是程序员的常态。现在每当我因一个程序员缺乏专业素养而内心愤怒的时候, 我都会提醒自己,不是每个人都像我一样愿意(或者有条件)把技术带到日常生活中去。 我每天吸收的资讯、读的书大部分都和技术有关,这只是因为我的兴趣和职业都恰好是技术,但别人不是。
  • 我在很多人的眼里是幸运的 —— 我有条件在很小的时候接触计算机,我「幸运地」在退学以后还能进「大厂」,等等等等。我承认我是幸运的,但我一直在思考,我所享受到的幸运,可以如何为那些不那么「幸运」的人带去一些有用的东西呢?博客是我一直在做的,也能稍微达到目的,但这远远不够。

技术

  • 在一个内部平台用了 hapi, 我非常喜欢 hapi/boom 的设计。hapi 是我目前用 Node 写 API Server 的首选。
  • 学了一段时间 Go, 写了 snp. 目前还没有场景用到 Go 的更高阶的特性。以后我会尽量把和前端无关的工具用 Go 写。
  • 我仍然一直在关注 ReScript (也就是 ReasonML), 我认为 ReScript 是所有编译到 JavaScript 的方言里最优雅的语言。她的特性弥补了用 ECMAScript 写 React 的缺点。

我今年最喜欢的技术

Best buy

(*排名分先后)

我也对我买过的数码产品进行了反思,觉得有些对我来说是性能过剩的:

iPad Pro

如果现在让我选,我肯定会选更便宜且支持二代 Apple Pencil 的 iPad Air. 我目前对 iPad 的需求只有:

  • 支持二代 Apple Pencil
  • 支持蜂窝

MacBook Pro 32GB 内存

我在买这台 MacBook 的时候特意把内存加到了 32GB, 但这些年用下来,我发现我对 laptop 的需求瓶颈不在内存,而是 CPU, 运算速度,续航和发热量,而这些东西在这台 MacBook Pro 都没有做得好。

我可能会在一年后换成 M2 的 MacBook Air 取代这台 MacBook Pro.

Apple Watch

Apple Watch 对于有氧训练来说确实非常好用,但对于我大多数情况下都是力量训练的人来说并没有太大的用处,唯一的用处只有一些健身记录的 App 可以通过 Apple Watch 在训练的时候进行记录,或者组间计时。而我更愿意用小本子来记(因为记录一些训练感受的备注更方便),我的卡西欧手表计时也足够方便。

我也用 AutoSleep 记录过一段时间的睡眠时间和质量,但我本身睡眠就没有什么问题,所以这些数据对我没有什么帮助。

工具

今年用到了两个新的工具,值得分享给大家。

Roam Research

我在试用了 30 天 Roam Research 以后就发现我离不开它,所以咬着牙订阅了它。我记的想法或者笔记通常是零散的,没有组织的,但在 Roam Research 里可以通过 tag 和 backlink 把这些零散的记录关联到一起,可以很好地辅助我的写作。

比如在我的 Roam Research 里有提到关于 Roam Research 的记录都能直观地看到:

有时候我在写作的时候会惊喜地发现有些有关联的笔记我自己都已经忘了,如果不是 backlink 我可能永远都不会想起它。所以它就像是我第二大脑 :我们虽然暂时还无法复制我们自己的大脑,但是我们可以通过文字笔记的方式创造一个我们大脑的子集。它记忆了自己曾经学习过的、思考过的东西,永远不会忘记。在需要的时候,随时可以翻查这个第二大脑的记忆,而且他是网状的,拥有一定程度上的「联想」。

我之后会详细地分享我是如何用 Roam Research 的。

Cubox

https://cubox.pro

我以前不用书签管理工具,有些曾经读过的文章和工具,在之后想找也找不到了。在用了 Roam Research 之后,感受到了 tag 的强大力量,所以我开始用书签管理工具, 给每一个书签加上标签,日后我可以通过 tag 的组合找到忘掉的东西。

  • Cubox 也替代了我其它的 Read it later 工具,我现在看到无法短时间消化的文章,都可以收藏到 Read it later 的文件夹里,到晚上统一处理。
  • 在移动端用 share sheet 也可以很方便把移动端上看到的页面收藏到 Cubox

阅读

《精通正则表达式》

我是在一次面试的时候被问到过一个用正则表达式提取一个固定语法的句子里的信息的问题,之后我就读到了这本《精通正则表达式》,前三章已经让我收获非常大。这个收获不是那些正则表达式的基本语法,而是这本书想要传达给读者的思想 —— 把正则表达式作为一种工具去运用它。

我在读了这本书之后再也没有了以前对正则表达式的那种恐惧,而且还因为正则表达式学习了很多关于 Parser 的知识,也在日常开发中惊喜地发现有很多问题可以用正则的方式解决。

《精通正则表达式》是我今年读的最有价值的一本书。

How to make smart notes

因为用 Roam Research 所以读的一本书,讲的是做笔记的方法论。

Even the best tool will not improve your productivity considerably if you don’t change your daily routines the tool is embedded in, just as the fastest car won’t help you much if you don’t have proper roads to drive it on. Like every change in behaviour, a change in working habits means going through a phase where you are drawn back to your old ways.

《整洁架构之道》

我最早的职业规划是成为一个架构师,在工作以来也用自己的方法「设计」过很多系统和工具,这本书是讲真正的软件架构,里面解答了很多我想要知道的答案,软件架构的目标是什么,如何做软件架构,如何做技术选型。还有一些真实的架构案例可以参考。

这本书我要再读一两遍才能详细地和大家分享。

新的一年想做的

如果有机会,我希望可以在今年做一个小而美且有人愿意购买的产品,她会是好用的、体验好的、克制的、能带给用户更深层次的价值的。就像印刷机,他表面上看上去只是一个工具,但他对于人类知识文化的传播是功不可没的

离开阿里巴巴

西湖

这个月我将离开阿里巴巴,结束至今 4 年的阿里生涯。

在阿里的 4 年我体验到非常多的东西,了解到了作为一个「职业程序员」的工作状态,认识了很多很靠谱的同事。尝试了一些东西,创造了一些东西,也吸收了一些东西。

我很幸运,这几年我遇到的老板都对我非常好。在 UC, 甄子给了我很大的自由度,让我得以在技术上做很多的探索 。在花呗,完颜和我一起做了很多次难忘的乐队演出。我很喜欢 花呗前端团队 ,这里的同事很有趣,在一起工作非常快乐,他们在面对复杂且繁忙的业务时展现的专业性是我难以企及的。

Why leave?

在大公司,成为一个优秀的业务型程序员要做的更不仅仅是理解业务需求。而我更擅长的是用技术解决效率问题、通过设计降低软件复杂度(Programming complexity)。但在业务 deadline 面前,这些仿佛是次要的。很少人有精力在 deadline 和加班面前能保持追求代码质量和软件设计,但这却是保证软件稳定和快速迭代的基础。为了快速上线而忽视代码质量的代价是很沉重的,但没人会指出这些技术债的根本原因就是太着急了,整个环节都太着急了。

我也不擅长画大图,把事情说得很宏大。阿里是一个很大的公司,有很多内部系统(所谓的「中台」)已经很完善,不是每个人都有机会遇到「大」问题。但是如果不做点什么大事,在阿里的仕途不会很顺利。所以我目睹了许多因为 KPI 和职级晋升产生的内耗。它潜移默化地使人做事变得功利,它就像一把声音在你做事的时候默默地提醒你:要把事情做大。

我从 Apple 和乔布斯身上理解到了在做同一件事时,不同的目的会导致做出来的东西天差地别。钱很重要,但如果乔布斯做产品的目的是钱而不是用户体验,那么 Apple 不会是今天的这个 Apple. 我认为做技术产品也是一样的道理,「做好」应该是目的。但能遇到适合「做大」的技术产品的场景是很靠运气的,在这样的情况下,绩效和晋升的压力会让人不得不把「做大」变成了目的,这就导致了:

  • 简单的事情复杂化,增加使用者的理解成本( Cognitive Load
  • 能使用现有的技术,偏要自己再做一套。由于「做大 -> 晋升」是目的,这样的产品有很大的机率在达成了目的后被放弃。
  • 做事态度变成「能用就行」,不关心用户体验。

这样的做事方式不适合我,也违背了我做技术的理念。

What’s next

我还没有一个很确切的计划,但我希望能去一个地方,定义我价值的不仅仅是因为我用了多牛逼的技术,做了一个多大的「平台」,而是我用技术的手段,给用户创造了什么他们觉得有价值的东西,如何改进了用户体验。又或者我用技术解决了哪些效率问题。

更具体的目标是成为 Tech Lead 的角色。同时我对 Growth Hacking 有很大的兴趣,希望可以结合技术和数据找出产品增长的瓶颈、把产品做得更好,帮助产品实现增长。

我会回到广东,离开杭州。

Conclusion

离职不是什么苦大仇深的事,我对阿里没有任何的怨念,阿里有很多很好的地方,只不过不属于这篇文章要讨论的范围。无论在哪个地方,我想要的都是和一群有想法的人一起打磨一个有价值的产品,而不是不同职能的团队都只做对自己 KPI 有利的事。每个公司都有其独特的生存法则,但这个生存法则也应该让那些务实和纯粹的人能很好地生存下去。也许这就是我作为一个技术人的理想主义吧。

说说 React 和 Vue

争论框架是个无聊的事情,有一天在开车的时候我突然想到选框架和选车是一样的。

React 就像手动挡,Vue 就像自动挡。你不能因为你会开手动挡就看不起开自动挡的,我想正常的驾驶员在城市代步都会选择开自动挡。我只想代个步,为什么不把复杂的事情交给变速箱呢。

当你要下赛道,你可能要选择手动档。因为你相信你的人脑比变速箱要聪明得多。你需要由你自己的大脑控制最重要的事 —— 赛车里是速度,软件开发是性能。useMemo 或者shouldComponentUpdate 就是你车里面的那根档棍。

用手动档的没有必要看不起用自动档的,人家根本不在意这车能不能开到极速,只想安心开开车顺便摸摸副驾驶的大腿;用自动档的也没必要在路上看到别人开手动档的就骂别人傻逼,人家一个老司机开手动档多年,可能从心智上开手动档开得跟你开自动档一样轻松,自己也享受手动档的乐趣。也有可能是因为他想开的车只有手动档的 —— 比如他因为想用 Ant.Design 所以选了 React.

就像选择自动 GC 和手动 GC 的语言,作为一个成年人,应该知道什么是 trade-off.

作为专业的程序员,更重要的是知道你面对的是赛道还是公路,你的伙伴到底会不会开手动档。

最怕的是硬要让只会开自动档的人开手动档的车,轻则红绿灯起步死火被疯狂按喇叭,重则…

好产品帮助用户变得更好 - 读 Badass: Making Users Awesome

如何让用户愿意花钱购买你的软件 / 服务 ?

如果你也在思考这个问题,那么你一定要读一读 Kathy Sierra 写的 Badass: Making Users Awesome (中文译作《 用户思维+ 好产品让用户为自己尖叫 》)

想象一下,有一天,你看到有人在网上写了一篇如何记账和理财的文章,读罢,备受鼓舞,文章的作者在里面提到了如何用一个 App 高效做记账,于是你买下了这个 App.

背后驱使你买这个 App 的原因是什么?是因为你觉得这个 App 很不错?不,是因为你想成为那个善于理财的人。

这就是这本书的观点 —— 用户购买你的产品,他在意的是用了你的产品,他成为了什么样的人。所以,如何打造一个可以持续获得用户的产品,秘诀在于如何让用户通过你的产品成为他想成为的人。

这个观点让我耳目一新,我可以从自己或者从别人的购买行为看出这样的本质。我忽然发现,以往在思考如何打造一个用户觉得有价值的产品,我太专注在思考工具本身的功能应该怎么做,完全忽略了我的产品如何帮助我的目标用户变得更好。

例如,我总是在想着做一个博客平台,读完 Kathy 的这本书,我发现,我太过于思考从功能上,我要提供给用户什么。我想,我要提供 Podcast 托管,我要提供最快的 CDN, 我要支持自定义域名。但是,整个互联网,博客平台已经有很多,这些功能,别的平台可以提供。用户为什么选择我?

读完这本书,我会这样去思考:我如果做一个新的博客平台,用户可以因为我的平台,成为一个什么样的人?这样一来,问题就变成了:我的博客平台如何让用户成为一个优秀的独立博客主?我的博客平台如何让用户成为一个成功的 Podcast 节目主?

我们总是提供工具,但是往往忽略了如何让用户用好我们的工具,使他们变得 Awesome.

让用户用好我们的工具,是零经济成本但最有效的宣传工具。因为这一些 Badass User (通俗来说就是「玩得溜」的那些用户,我姑且称为「用家」),他们自己会帮你宣传。

当一个玩摄影的朋友告诉你,「这个镜头太牛逼了,你看我用它拍的这些照片,太好看了。」你可能会对这个镜头心动,晚上就回家搜一搜这个镜头,然后下单。

这位朋友嘴上说的是「这个 xx 太牛逼了,我用它 xxx」,事实上他想表达的是「我太牛逼了,我用它 xxx」。

只要你帮助你的用户通过你的产品变得更「牛逼」,就会有越来越多这些「用家」帮你宣传,就会有更多人购买你的产品,希望自己变得「牛逼」。也就是常说的「种草」。

然而他们宣传的方式不一定是直接跟身边的人说这个工具有多好,而是自己如何使用这个工具,做出了什么样的事。如果你有关注「少数派」这个媒体,少数派是这种案例的典型。你会发现上面几乎所有的文章,比如关于 Ulysses 这个 App 的文章,这些作者不会单纯去写这个工具怎么样,而是:

  • 《为什么我选择用 Ulysses 写了一本小说》(我如何用 Ulysses 写了一本小说)
  • 《搭建 Ulysses 学术写作之公式输入》(我如何用 Ulysses 学术写作)

越来越多的人会因为看到这些分享,希望自己可以用 Ulysses 也成为像他们这样成为一个写作者。

简单地来说,就是想一想,你的产品如何让你的用户在和朋友聊天的时候体现自己的 Awesome.

这本书也提到了在帮助用户成为「用家」的过程中遇到的问题以及解法,还从如果成为某个领域的专家作了一些科学上的分析(但我觉得这一部分的知识可以通过读 Atomic Habits 学到更多)。

我知道这本书是在翻 Egghead.io 的创始人 John Lindquist 的这条 Retweet无意中看到的,我觉得一定是本很好的书,他才会给每一个 Egghead 讲师送这本书。

我早在读 Egghead 的讲师指南的时候就发现 Egghead 会直接给每个讲师一套完整的录音设备(因为做 screencast 录音质量很重要)。我当时震惊于 Egghead 的阔绰。读完这本书,我就明白,即使需要成本,一套好的录音设备可以让这些用户(讲师)录制高质量的 screencast. 创始人就在践行这本书的理念。

注:本书的作者 Kathy Sierra 也是 Head First Java 的作者。

从「后端现在已经看不懂前端了」说起

V2EX 这两天有一篇这样的帖子:《说句实话,我后端现在已经看不懂前端了,太难了》

原文内容是:

看不懂惹,肿么办,最近在学 react,完全抓瞎。

然后又附加了内容:

一直用的 jquery,手动找位置然后对应 dom 操作,现在各种前端各种依赖,一会 route 一会 webpack 的 给爷整蒙了都。

类似这样的帖子多年来数不胜数,但这次评论区的评论比较典型,所以我决定这次不再沉默。当然,本文并非针对帖子作者。

对于那些抱怨前端「看不懂」的网友,我们应该问他们一个问题:为什么前端就应该是谁都能看懂?

这些网友从来不会抱怨别的领域的代码看不懂,比如他们不会说「我一个前端已经看不懂后端了」,或者「我一个后端已经看不懂 iOS」了。

所以在这些网友心中,前端代码就是要谁都看得懂才是正常的。因为谁都应该看得懂 HTML, 谁都看得懂 CSS.

显然,这群人对前端的认知还活在过去,他们一边享受着用 Web 技术做出来应用,一边意淫着这些包含复杂业务逻辑的 Web 应用靠 HTML 和 Vanilla JS (原生 JS) 就能写成。

换句话说,这群人最大的问题在于,他们意识不到 GUI 开发 (Graphic User Interface Programming) 是一个从图形界面被发明开始就一直在被研究的独立领域,而 Web 前端只是 GUI 开发的其中一个分支而已。

这群人没有意识到到底什么是「前端」。前端就是写 HTML / JavaScript / CSS 吗?不,前端应该泛指那些需要编写 GUI 的技术。在我看来,写 iOS / Android 应用和写 Web 前端没有本质上的区别,区别只是语言和平台,背后处理的问题是一模一样的:如何实现视觉和交互 -> 交互如何触发逻辑 -> 逻辑如何影响视觉的变化

所以,如果认为浏览器识别的是 HTML 所以认为前端应该谁都能看懂、门槛低,进而得出前端目前的工作流是问题复杂化的结果,就如同因为 Android 的 UI 是用 XML 写的就认为 Android 门槛低一样不成立得可笑

对 GUI 开发难点的不了解是导致这种误解的最主要原因,以网友「 avastms 」提出的评论为典型:

这是前端圈长期逆淘汰的结果。

模板混代码里这种 PHP 宗教行为就别提了。

个人认为因为 javascript 语言的部分原因,前端很少有人懂什么是继承,什么是类,实例到底啥意思,更别谈良好维护引用了。

什么 redux,什么 vuex 根本就是莫名其妙,不就是事件监听吗,像 Node.js 那样 EventEmitter 多么清晰,非得自己造概念。

画虎不成反类犬,凡是有这套什么状态管理逻辑的,代码完全不可读。

前端自己也是不争气,不用别人的这些狗概念,自己就不会写代码了吗?

没有什么 redux,自己就不会管理自己的属性了吗?

他说:「模板混代码里这种 PHP 宗教行为就别提了」,想必他在说 JSX. JSX 刚开始出来就充满争议,但人们最终会发现,JSX 是函数式 GUI 开发最易读写的方案。实际上,他完全可以选择不使用 JSX,这样去写代码:

const Component = h(‘div’, null, [h(‘p’, null, ‘hello world’), h(‘p’, null, ‘I am Randy’)])

但是,这位网友大概更愿意读这样的代码:

const Component = (
	<div>
		<p>hello world</p>
		<p>I am Randy</p>
	</div>
)

觉得这样的写法恶心,多数是因为不知道 JSX 这个语法糖的背后是什么。

事实上没有人阻止任何人不用 JSX, 或者看看 Flutter 就大概知道没有 JSX 的 React 会是什么样子。

作为补充,这是希望 Flutter 引入 JSX 的相关讨论:Consider JSX-like as React Native · Issue #15922 · flutter/flutter · GitHub

至于提到的 Redux 和 Vuex, 证明这位网友根本不知道 EventEmitter 和 Reactive 的区别。这里就不展开讨论了。

为什么我们宁愿用 JSX 这种折中方案也不愿意放弃函数式编程,是因为声明式(Declarative) 是编写 GUI 最好的方法。复杂的 GUI 应用包含复杂的状态,人脑在处理复杂的状态绝对不比计算机在行,我们尽可能地把这些状态交给计算机做,函数式编程就是我们实现这个目标的工具。

Elm 的作者写过一篇名叫 Concurrent FRP 的论文,讲述了函数式响应式编程 (Functional Reactive Programming) 在 GUI 开发中的历史背景和应用。

那些喜欢说「jQuery 一把梭」的网友,要么遇到的业务很简单,要么是在自虐。我见过写外挂用易语言一把梭的,但没听说过 Adobe 写 PhotoShop 用 Visual Basic 一把梭。

有些人,能接受 Maven, Gradle 的不完美,也知道什么场景应该用什么技术栈,但一旦开始写点前端代码,就认为应该有一个神一样的、完美的、还没有学习成本的构建工具,然后 Twitter, V2EX 抱怨一番,却不愿意认真学一学前端这个领域的知识。

不要妄想有一个睡醒就突然会用的构建工具。说实话,Webpack 不需要任何配置就能用,再不行试试号称 zero configuration (零配置) 的 Parcel, 用 TypeScript, 引入 CSS / LESS / SASS 通通不需要配置,跑起来就行。但那又如何呢,那些不愿意学习又想出活的网友们不懂的不是运行 webpack dev 这个命令,而是根本不懂这个命令背后解决的是什么问题。

一个人要锤子锤个钉子,结果大家都说雷神的锤子很牛逼。这个人听了,上来就要用雷神的锤子,结果拿都拿不起来。然后网上发个贴:「根本不懂为什么雷神要搞个这么重的锤子,给爷整蒙了都」。底下评论:「我工具锤一把梭!」楼下再附和:「雷神搞这个锤就是为了装逼」。

希望各位把抱怨转化成求知,没有一个领域是不需要学习成本的,不管是做技术还是技术以外的领域,是分工和市场经济让各位觉得一切理所当然,然而像磨刀这样看上去简单的动作,也不是每个人都能磨好一把刀。

2019 年终总结:慢慢变成别人眼中「食古不化」的「怪人」

总结

2019 年 3 月我来到杭州,在蚂蚁金服开始了新的工作。这是我第一次在广东省外的地方生活。

杭州的公路

新的环境,新的工作,对我来说是一个很大的挑战。挑战不在于如何适应一个新的环境,而在于在一个技术基建完备的环境里,如果业务难关不是技术可以解决的,那我的价值在哪里?在这个方面,我让很多对我抱有期望的人失望了。

「我的价值是什么」是我不断寻找答案的命题,我不满足只作为一个消费者活在世上。只有作为生产者创造自己独特的价值,才让我觉得自己是这个世界的一员。

我创造了很多,但好像只有写博客算是对别人来说稍微有价值的东西。现代的人又不怎么喜欢认真地看文字了,写好一篇文章的阅读量也不比同样内容的一个视频高。就连我一个关注了很久的独立博客博主,也转了型做视频。

「写博客」似乎已经是一件看上去「食古不化」的事,而我竟然越来越「食古不化」了。有时候到一家餐厅,被告知没有餐牌,只能扫码点餐,我会反问,「如果我没有手机呢?」。

我想,如果旁边正好有一位比我年轻十岁的客人听到我这样问,他心中也许会暗想,「x, 这个食古不化的傻x」。

今年下半年我又因为不能忍受国内社交网络的反智内容和评论,微信和微博都变成了只写的状态,除非收到消息,微信几乎不怎么打开。此举极大地改善了我的心理健康。但在别人眼里,就是一个不合群的怪人。

告别 2019,我就 25 岁了。这是一个不算年轻,也不算不年轻的岁数。如果开始「食古不化」是变老的征兆,那么我已经开始变老了。但如果不懒惰、不犬儒、不圆滑、不反智就是年轻,那么我将会永远年轻下去。

音乐

今年做了 6 首翻唱,上传在 YouTubeBilibili:

播放量最高的是《奉献》和《黄昏》。

阅读

今年读的书里有以下几本想推荐给大家:

《学会提问:批判式思维指南》(豆瓣 | 购买

《非暴力沟通》(豆瓣 | 购买

《颓废与沉默》(豆瓣 | 购买

消费

  • Kindle Oasis ( 购买 ) 替换了我的 Kindle Paperwhite. 手感很好,放裤袋很轻松。是我今年买得最值的产品。
  • 黑卡 3 ( 购买 ) 替换了我的黑卡 1, 有可以旋转的屏幕,自己拍视频的时候轻松多了。
  • iPad Pro ( 我的评测 ) 替换了我的 iPad mini 2. 主要用来看微信读书,睡前浏览各个咨询网站。最后悔是没有买蜂窝版。

每天都看的内容

放空的时候看的内容

关于 2020

创造更多价值吧。

为了你爱的人,读一读《非暴力沟通》

我和父母的关系很糟糕,在小时候,我们之间的「交互」方式大部分是命令、教训,严重的时候甚至打一顿。我很讨厌被命令去做家务。

成年以后,即使脱离了原生家庭,但无论是工作伙伴之间的合作,还是处理亲密关系,都偶尔会出现矛盾,形成无效的沟通,甚至产生情绪。

很幸运我读到了《非暴力沟通》这本书,让我发现我在日常接触到的沟通中,多少隐藏着无形的暴力。

全书围绕非暴力沟通的四个要素进行讲解:观察、感受、需要、请求。如果沟通没有这些要素,那么这些沟通最终的结果很可能是情绪化的收场,又或者成为了压死骆驼前的每一根稻草,总有一天爆发。

在一段亲密关系中,我们很容易听到或自己说到过这样的话:「你怎么每次都这样」。这样的一句话,很容易使对方产生抵触情绪,导致情绪化的争吵。因为这句话也只是单纯的情绪发泄和评价,不能指望用这样情绪化的沟通去解决实际存在的问题。它缺少了非暴力沟通的四个要素。

如果读完《非暴力沟通》,你可能再也不会说出「你怎么每次都这样」这样的话。你会先说出你的观察:在这个月,你已经是第 3 次这样做了。然后说出你的感受:我觉得你这样做让我觉得你不是一个靠谱的人。再说出你的需要: 我想要一个靠谱的另一半而不是这样的人。最后说出你的请求:你可以不再这样了吗?

很多人没有区分「观察」和「评论」,像有人常说「那个产品经理真是个脑残」,这是一种评论。评论对于沟通是没有帮助的,他只会让别人厌恶,抵触。我们应该说出具体的观察:那个产品经理做的大部分需求都会改很多次,而且功能上线后用户基本都是负面反馈。

我们也常常忽略、或者故意隐藏了自己的感受和需要。在沟通中,让对方清楚自己的感受和需要是很重要的。最常见的就是在亲密关系当中,一方总会把自己的感受和需要埋在心中自己消化,另一方事实上并不知道对方的感受,潜移默化就造成了关系破裂的危机。

暴力的根源在于人们忽视彼此的感受与需要,而将冲突归咎于对方。

非暴力沟通的第四个要素:请求,也很重要。对方即使清楚你的感受和需要,但不一定知道应该怎么做。恋爱中,一方需要陪伴,那么应该直接说出具体的请求:工作日每晚至少花两个小时在一起。

我们将自己想要的回应讲得越清楚,就越有可能得到理想的回应。

在工作中「请求」同样重要。如果在会议中,发言的人在最后不明确说出想要得到与会人员怎么样的反应,只问大家「有什么想说的」,那很可能大家就没有什么想说的。但如果明确地问:「对于这个方案,大家有没有想到可能存在的缺点?」,那么就更有可能得到你想要的回应。

但如果不说出感受和需要,直接说出请求,就会像一种命令。例如我小时候母亲让我做家务的时候,对我只有一句话:「快去做清洁!」。对我来说是一种不得不服从的命令,服从只是因为她是我母亲,而不是她的感受和她的需要,会让我十分抵触。

除了非暴力沟通的四个要素,书里最后也提到了充分表达感激的重要性。对别人表达感激也有三个要素:说出对我们有益的行为;我们哪些需要得到了满足;我们是什么样的心情。

这就是为什么对于我来说,比起别人说「你真牛逼」,我更愿意看到博客读者来信告诉我,我的哪篇文章的哪一个点,让他得到了什么样的启发。

有一次我在看 React Conf, 很喜欢其中一位讲师,在 Twitter 上提到了他,他说「That means a lot to me」, 我意识到,不能吝啬表达自己的感激。

我想,如果在我的成长环境中,我的父母也掌握了非暴力沟通的技巧,我们彼次都懂得表达彼此的观察、感受和需要。也许不至于让我对原生家庭有那么多负面的情绪。

所以,为了你爱的人,应该读一读《非暴力沟通》

来自 2014 年给未来的我的一封信

这是一封 2014 年我写给未来的自己的信。当时在上大一,是上职业规划课时老师布置给我们的一个作业,写完我们就交给老师保存,他会在我们大学毕业后返还。

老师通过微信联系我说有封信在他那里,并拍了这张照片(图为我用 Office Lens 处理后)。我早已忘掉我曾写过这封信,看完后我就全记起来了。

记起来刚上大学时对未知的憧憬,记起来在教室听无聊的网页设计课,记起来退学那天给宿舍阿姨还了钥匙后坐上中巴回广州市区的时候听的那首《一路向北》。

为什么无法坚持写博客

如果你被本文的标题吸引,那么你应该已经试过很多次雄心勃勃地开始选择你喜欢的静态网站生成器 (Static Site Generator), 然后精心挑选或者自行手写一个你钟意的主题,写下一篇题为 “Hello World” 的博客,宣称从今天起重新开始写博客。几个月后就再也不更新了。

为什么你总是无法坚持写博客?和很多「为什么无法坚持做某事」的问题一样,我们得先弄清楚为什么要写博客。

我听过很多人想建博客的理由是想把博客当成笔记本,记录自己学到的东西、遇到的问题和解决方法。这是一个让博客最终走向荒废的理由,因为你在笔记本就可以做到类似的事,除非你记录的问题有你自己独到的思考,否则网友们更愿意去看 StackOverflow.

写博客的第一个意义是让陌生人通过博客了解到你是什么人、你在想什么、你做了什么。不妨试想一下,如果你正准备找工作,一位素不相识的 HR 如果要了解你,光是一份简历就能让 HR 知道你的价值吗?如果你有一个精彩的博客,HR 也许能通过你的博客了解你对技术的属于自己的思考、你在业余时间在想的事情是什么。这比简历本身更像一份立体的简历。

以下是一些你可以选择的题材:

  • 你对某个技术的看法
  • 你读了某本书的书评
  • 你职业之外的一些生活感悟和记录
  • 你的对于生活、职业的哲学思考

这些题材的共同点是它们都是属于你的独特的内容。

当然,这一切的前提是你本身需要有一定的表达欲,你才会想不断地去写博客。博客只是其中一个表达渠道。我自己是一个很有表达欲的人,我乐意分享我的想法,我的生活。然而,试图通过建立博客去培养表达欲不是一个行之有效的方法,这就像很多本身不喜欢读书的人试图通过 Kindle 培养自己的读书习惯,大多数都是失败收场。你应该是因为喜欢读书所以买 Kindle, 正如你应该是想表达你的东西所以你写博客。

不是每个人都有表达欲,但很多有表达欲的人不敢表达,因为在担心一些不应该担心的问题。

文章没有深度

除非你的博客的定位就是追求思想深度,否则不必苛求每篇文章都有所谓的深度,有时候即使是日常生活的小记也有其价值,读者能从中发现很多自己不知道的东西。例如 JustZht一些生活小记 .

记得多年前我刚开始写博客的时候知道了罗磊和 罗磊的博客。虽然当时罗磊是一个前端工程师,但博客大多数文章都不是技术,而是一些数码产品和生活方式,例如 他跑马拉松的记录

这些内容没有所谓的「深度」,但让我看到了不同的生活方式。每个人都有自己独特的生活方式,不妨把它分享出来让更多人从中获得借鉴。

文章写不长

并非写长文章才叫写博客,李如一的 一天世界 也不见得每篇文章的篇幅都长,但内容有其独特的思维方式,也能引发思考。这就够了。

没什么人看

写独立博客和写公众号不同,你不必追求「十万加」。你要追求的是一个陌生人只要进入一个网站就能知道你是什么人。你不靠写博客吃饭。人们总说 You are what you read, 当人们无法得知你在读什么的情况下,那么我更要说: You are what you write.

文章写完,分享给你喜欢的圈子,论坛,回复一些评论。慢慢地就会在你的圈子积累一些读者。

做不到一直更新

JerryQu 的博客 已经两年没有更新,但两年后的现在依然可以说这是一个很好的博客。

你的博客不是资讯网站,不必在意更新频率。

希望本文可以引导各位潜在的博客作者决心建立自己的独立博客。独立博客有免于献媚的自由,有排版自由,有修改删除自己说了算的自由。维护一个有意义的博客不是一件难事,用现成的平台或用框架,在网站主页写上你的个人简介,然后保有分享生活和想法的习惯,不限篇幅,不限频率(即使是一年一篇),按时续费域名,保持网站正常访问,

记住,一个博客的死掉不是不再更新,而是无法访问。

延伸链接

我在 UC 做的前端工程化探索

我在 2016 年初加入 UC 的国际业务部,负责为 UC News 搭建运营后台。随着前端团队人数越来越多,我开始推动团队的前端技术栈统一以及前端工程化,开发了一个叫 Cans 用于快速搭建中后台前端应用的框架。直到了 2019 年我离开 UC, Cans 仍然服务于 UC 的国际业务。

本文主要记录了我如何从 UC News 运营后台孵化出这个内部框架,以及其背后的设计理念。虽然 Cans 没有开放源代码。但我认为相比这些工具的源码,那些我在开发这些工具背后的理念、思考,更有被分享的价值。也算作是对我在 UC 工作的一个总结。遂有此文。


我们在做 UC News 运营后台的时候,面临的最大问题是:当有新的业务需要我们团队支撑开发一个新运营后台的时候,我们应该怎么做?

这种情况在不断地发生。2017 年我们的 UC News 运营后台趋于成熟,业务需求响应的速度在不断提高。越来越多业务方问我们同一个问题:「我们 xxx 业务想有一个运营后台,我们可以快速接入吗?」

如何让 UC News 运营后台的开发经验可以被快速地复制出去,帮助更多业务?在回答这个问题之前,我想先说说我是怎么设计 UC News 运营后台的前端工程以提高需求响应速度的。

第一点是保证业务开发尽量不被构建环节干扰,只须关注业务逻辑本身。这一点很好保证,因为构建配置基本是不需要更改的。

第二点是降低新页面接入的成本。所谓的成本指的是:

  • 页面绑定对应的路由
  • 在左侧菜单栏添加菜单项并指向对应的路由

我把这两点变成配置项,可以直接在项目里的 routes 文件配置。这样新页面接入只需配置菜单项的标题和路由,以及对应的页面组件即可。

第三点是规范项目目录结构。这一点我在 Egg 上受到很大的启发。在内部,我们的 Node.js 系统都使用 Egg. 我借鉴 Egg 把项目配置、路由配置、应用启动代码等作了规范:

要用这一套模式赋能更多不断冒出的新业务,首先要做的是统一。

在社区上做开源软件和做公司内部的软件不同,前者通常需要考虑兼容性。处理兼容性的代价是越来越复杂的配置项。但做内部软件,则可以通过「统一」简化配置(甚至无需配置)。例如,两个业务如果分别使用 less 和 sass, 那么在新建项目时,两个业务都要各自配置。相反,如果早已约定所有业务都使用 less, 那么 less 的配置可以固化到统一的工具里,两个业务都不需配置。

首先是技术栈的统一:

  • React
  • 组件库:Ant.Design
  • 构建工具:Webpack
  • 语言:TypeScript
  • css 预编译:Less

技术栈的选型背后没有什么高深的思量,不过是这一套在 UC News 运营后台经过了考验:Ant.Design 的组件覆盖了 90% 的需求;纯 EcmaScript 写运营后台这种复杂应用是灾难,尤其在新人接手的时候;

技术栈的统一带来的是构建配置可以全部收敛,当然除了技术相关的工具,还有业务层面的工具可以统一收敛:

  • 构建产物发布到 cdn
  • 打点、错误监控的封装
  • 网络请求库统一
  • i18n 方案统一

做好了这些统一的准备,我开始开发一个叫 Cans 的前端解决方案,它的宗旨是快速搭建中后台类的项目。

框架设计

「开箱即用,没有多余的东西」是我从 zeit.co 领会到的软件设计哲学。如果说一个框架设计得美,那么其实是说它用最简单且符合直觉的接口封装了最复杂的逻辑

和 next.js 一样,我希望开发者只要用 cans start 这一条命令,就能开始开发一个页面。

在最基本的应用,cans start 的背后会做这样的工作:

也就是收集所有运行时将要用到的数据,然后生成一个入口 js 文件。

另外,在构建中还引入了 tree shaking, code splitting (with react-loadable) 等等优化手段。

构建时分析的应用数据,可以在运行时、页面中通过 import app 实例来获取:

这种注入机制是为了提供扩展性,为了通过这种扩展性建立「生态」,Cans 引入了 addon 机制,以一个打点 addon 为例:

开发者可以在 npm 发布以 cans-addon- 为前缀的库贡献生态。

Cans 自带了一些业务常用的 addon:

  • cans-addon-cookies
    • 通过app.cookies.getapp.cookies.set 读写 cookies
  • cans-addon-storage
    • 通过app.storage 做离线数据持久化(如本地数据缓存)
  • cans-addon-http
    • http 请求方法封闭

虽然中后台的布局千篇一律,但为了尽量覆盖所有定制化的需求,Cans 也开放了 theme, 开发者可以定制自己的主题,用 app.theme() 引入。也可以在 npm 上贡献主题。像我们最常用的运营后台 theme:

而在业务层,尽可能将可复用的业务组件封装出去,让更多业务可以使用。如 antd-data-table 这个业务组件,就是从 UC News 运营后台独立出来的。

以上是 Cans 这个框架的大概模样, 或有其它细节,但不是本文的重点。

在设计这个框架的时候,我无时无刻在考虑的是如何使开发者要写的代码越来越少,同时开放的接口是要符合开发者直觉的。就像「建立一个页面就是创建一个页面文件然后运行 cans start 一样简单。

几年前我读了《乔布斯传》,使我对用户体验有了极致的追求,也把这种追求带到了软件开发。这种体验不是一句 Simple is better 就可以说明的。你在同时面对一堆电路板和一台 Macintosh 的时候可能才会体会到所谓的「科技与人文的十字路口」,但,软件设计同样有这样的十字路口,它可能出现在一份完整、漂亮的文档(像 Vue), 可能出现在一个屏蔽了所有复杂细节 (and it just works!) 的命令(像 next.js), 可能出现在它的小而美,可以接入到任何地方。

后来蚂蚁金服发布了 Umi, 比 Cans 做得更全面,但两者的思路都如出一辙。我想这证明了这套模式背后的价值所在。最近看到《Umi 架构、生态和未来》 ,让我更确信当初的想法和 Umi 在做的是契合的。推荐各位在下一个项目可以试试 Umi.

记得有一次面试的时候,我提到我主要是做一些前端基建的工作,面试官问我,觉得什么样的前端基建是做得好的基建?我回答说,如果我做的基建可以让同事少加班,那么我做的基建就是好的

回顾了一些读者来信

从 2014 年开始写博客以来,因为不设评论区,我收到不少读者的 Email 来信。透过 Email, 来信的人比评论区直接评论要更严谨一些。来信中的大部分是请教问题,其它则是一些感谢的话。

几天前我购买了一块软木板挂在自己的房间,想到可以把一些有意思的读者来信打印出来钉到软木板上。于是在收件箱整理了 2014 年至今 5 年来收到的所有读者来信。

其中我发现一封 2015 年的已经被我淡忘的邮件:

这是一位在国外当老师的妈妈,特意来信想为她的儿子寻问关于编程的问题。当时我回信尽可能地写了很多浅薄的意见和经验分享,后来就再没有通信。这位母亲让我感动。

时隔几年再看来往的信件,无论是提到的这位母亲,还是其它特意发来感谢的读者,他们让我收获到的是,我写的文字确实帮助了一些人;五年来我坚持写独立博客是有意义的;这些来信让我意识到我活在世上还是有一点微不足道的贡献。所以我最开心的是看到来信里面提到的,我对读者的影响是什么。

对我来说,有没有活下去的勇气,取决于我在世界上有没有创造价值。这些读者来信是我写博客收获的最宝贵的财富。

所以这些人呢,不管是什么借口,最终选择了去做恶心的成年人社会中的一个恶心的人,最可气的是他们之后还产生了幻觉,说这就是“成熟”。于是又过来毒害年轻人,跟他们说,你看,我年轻的时候也像你这样,现在我这叫成熟,你这叫幼稚。

我现在看到很多我的同龄人变成这样,很多人都是我小时候的好朋友,也曾经充满了理想,充满了美好的这些东西,然后相信自己可以改变世界,最后却变成了这种恶心的人。

我回家吃饭心情好就敷衍一下,有时候心情不好就忍不住当场戳穿他们。我说:“你看你们这帮兔崽子,年轻时候我们在一块儿聊,都说要改变世界,现在你,你改变个屁了。”然后他们就有点不好意思,说:“哎呀,行了,老罗,咱们那时候不是幼稚吗?谁能改变世界?谁也改变不了世界。”我就跟他们说:“你别客气了,你已经改变这个世界了,因为你变成了一个恶心的人,这个世界多了一个恶心的人,因此它变得恶心了一点点。”

每一个生命来到世间,都注定改变世界,这是你的宿命,你别无选择。你要么把世界变得好一点,要么把世界变得坏一点。

你如果走进社会,为了生存或是为了什么不要脸的理由, 变成了一个恶心的成年人社会中的一员,那你就把这个世界变得恶心了一点点。 如果你一生耿直,刚正不阿,没做任何恶心的事情,没有做任何对别人造成伤害的事情,一辈子拼了老命勉强把老婆、孩子、老娘,把身边的这些人照顾好了,没有成名,没有发财,没有成就伟大的事业,一生正直,最后梗着脖子到了七八十岁死掉了,你这一生是不是没有改变世界?你还是改变世界了,你把这个世界变得美好了一点点。因为你,这个世界又多了一个好人。

—— 罗永浩

爱情是愿意为你放弃寻新鲜

昨天在回杭的飞机上看完《Newness》, 一部探讨开放性关系的电影。男女主角都喜欢新鲜感,最后因为约炮堕入爱河,但很快开始感到关系平淡和厌倦,于是尝试开放性关系. 各自一番经历后最终决定放弃自由,认真经营这段感情。

我很喜欢探讨这种两性哲学问题,因为我在看待两性关系中有些犬儒主义 —— 我不相信人性,不相信忠诚,世界上充满诱惑。更愿意相信大多数的婚姻是一场交易。

当两人初试开放性关系时,遇到一位专家在谈论「外遇」和「开放性关系」的成因,她说:

When I go to look for someone else, it’s not always because I wanna get away from you, it’s because I wanna get away from the person that I have myself become. And it’s not that I want to find somebody else, but I want to find another self.

当我开始想寻找另外一个人的时候,不是因为我想离开你,而是想逃离这个状态的我。我想寻找的不是另一个人,而是另一个我。

我想,这就是除了厌倦、诱惑之外,外遇的核心原因 —— 无法接受这个安定的自己。

有意思的是,在片中,男主角对开放性关系的底线,是只有自己可以令女主角高潮。所以每次女主角回家,他都会问女主角 Did you come (你高潮了吗)? 后来,女主角被一位成熟、事业有成的大叔深深吸引,她在他身上得到高潮后,男主角才感到他们的这段关系,已经彻底崩坏。

片尾,男女主角决定承受平淡、怨怼,回到对方身边。因为在各自经历过后,更加清楚彼此为何在一起。

https://movie.douban.com/photos/photo/2512608863

或许最可靠稳固的关系就是双方愿意为对方失去那个原本的自己,而且是亲身经历告诉他们为何值得这样牺牲。

正如片中的两性专家所说,「开放性关系只是一个过程,千万不要把它作为你们的终点」。

Think of it not as the destination, but the layover.

未来属于声明式编程

声明式编程 (Declarative Programming)是一种编程范式。现实世界中,我们大部分编码都是命令式的。

举个最常见的例子,对于用 JavaScript 来构建 UI, React 是声明式的。

// 普通的 DOM API 构建 UI

const div = document.createElement('div')
const p = document.createElement('p')
p.textContent = 'hello world'
const UI = div.append(p)
// React 构建 UI
const h = React.craeteElement
const UI = h('div', null, h('p', null, 'hello world'))

所有的 DSL (HTML, XML, SQL) 都是声明式的,你写出一条 SQL 语句,只是为了告诉数据库你要什么,然后数据库就会给你对应的数据,而不是通过数据库的 API 去取。

SELECT * FROM Products WHERE name='Alipay'

Apple 在今年 (2019 年) 也推出了 Swift UI, 在 WWDC 的 Swift UI 相关的 Session 里也多次提到声明式 UI 开发的威力

声明式编程的潜力在于:

解放人力成本,你只要「声明」你要做什么,具体怎么做,由运行时解决。

函数式编程就是声明式编程的一种,在函数式编程里的尾递归性能,就取决于运行时,而不是靠程序员去手动优化。React 里你只要描述你的 UI, 接下来状态变化后 UI 如何更新,是 React 在运行时帮你处理的,而不是靠程序员优化 diff 算法。

我们可以认为 Serverless (尤其是函数计算) 在运维领域获得了声明式的好处 —— 我们定义好了函数,我们只要告诉平台我们需要调用这个函数,那么如何进行计算资源分配、如何对代码做分布式部署,都不需要程序员考虑。

运行时帮你完成工作,除了可以节省人力成本外,还降低了程序员出错的概率 —— 因为写的代码越少,出错的概率就越小。人是最不可靠的,我们应该尽量把工作交给计算机。

「声明」是「描述」而不是真正「执行」

在纯函数式编程语言里面,一切都是声明式的,是纯 (Pure) 的,没有副作用(Side Effect)的。

Haskell 是一个纯函数式的语言,像在控制台输出文本这种方法(putStrLn)就是一种副作用。在 Haskell 里 putStrLn "Hello World" 本身不会真正地输出 “Hello World“, 而是返回一个 IO 类型,来说明他是一个副作用。但它如何被执行,取决于运行时。

Elm 和 Haskell 一样,副作用也只是返回一种类似 Haskell 中的 IO 类型。在 Elm 中叫做 Cmd.

以上说的这些,可能太过抽象。所以我用前端的同学们应该都知道的 redux-saga 对此作更具象的解释。也可以解答为什么我虽然不喜欢 Redux, 但认为 redux-saga 是一个的很不错的库。因为他利用 redux 的 middleware 机制和 generator 巧妙地实现了类似 Haskell 的 IO.

下面我将用 官方文档的例子 做解释。

比如,以下是一个有副作用的函数:

import { call } from 'redux-saga/effects'

function* fetchProducts() {
  const products = yield call(Api.fetch, '/products')
  // ...
}

显然,Api.fetch() 是副作用,它会发送网络请求。但是,在 redux-saga 里面,你不应该直接执行这个函数,而是使用 call 告诉 redux-saga —— 你要执行 Api.fetch , 参数为 /products.

所以,事实上这个函数没有被命令式地被执行,而是由 redux-saga 决定如何执行。

如果你在外部直接调用 fetchProducts(), 你会得到一个 Generator Iterator. 然后通过 next() 得到你 yield 的值。所以你可以这样去测试你的程序:

const iterator = fetchProducts()

// expects a call instruction
assert.deepEqual(
  iterator.next().value,
  call(Api.fetch, '/products'),
  "fetchProducts should yield an Effect call(Api.fetch, './products')"
)

也就是说,你要测试的是「你有没有告诉程序你要执行的副作用,以及执行的参数是什么」。和命令式编程不同,因为命令式的程序在你执行函数时会真实地执行这个 Api.fetch,你必须用测试框架里类似 mockFn 的手段去 mock 这个函数进行测试。

fetchProducts() 只有在 Redux 环境里,才会真正地执行副作用(在这里就是 Api.fetch 发送的网络请求)。

所以,声明式的编程是非常易于测试的

可视化编程是一种声明式编程

我们探索可视化编程,是因为我们一直期望通过拖拽就能完成开发,其实就是期望我们完成任务仅仅需要通过声明,而不是写命令式的代码。当然这是一种理想的状态。

DSL 是最常见的声明式编程形式。我一直在布道 GraphQL, 因为它把网络请求变得声明式了:

query {
	posts {
		id, title, content
  }
}

把网络请求变成声明式的好处有很多,其中一个就是它可以被放到各种各样的环境被执行。想象一下,我们可以打造一个可视化的应用搭建工具,在命令式编程的场景下,我们如果要做出如「点击按钮发送请求,得到响应后触发另一个 UI 更新」,就需要编写命令式的代码:

async function onClickButton() {
	// 手动发送请求
	const result = await fetch('/api')
	// 手动更新 UI
  table.dataSource = result
}

如果是 GraphQL, 我们可以把每一条 GraphQL 语句单独看作一个对象,他可以被任何组件触发,它的结果也可以被任何组件订阅。这样一来,在可视化的搭建工具里,程序员要做的是:

  1. (声明式地)编写 GraphQL 查询语句
  2. (声明式地)为组件(比如某个按钮)绑定 onClick 事件为触发某条查询语句
  3. (声明式地)为组件(比如某个表格)绑定某条查询语句的响应值对应哪些组件的属性值

当然现实世界的应用不是那么简单,但已经是跨出了很大一步。

Conclusion

未来为什么属于声明式编程,因为我们在不断地努力提高开发效率,声明式编程显然是提效的最佳手段。React, Flutter, SwiftUI, GraphQL 的出现是最好的证明。最近听到内网太多人在提 Serverless, 我想说,提升开发效率,我们应该去想如何尽量让开发者声明式地编写代码,而不是只去想我们在 Serverless 上能做什么。

井底之蛙的一片天空

有次和朋友提到移民的想法,他说,为什么不考虑日本呢?我的回答是,我对日本没有特别的情感。朋友说他正相反。然后他问道:难道你的童年没有看日本动画吗?

我的童年除了阅读,大部分信息的获取来源于电视机。我从电视机看过的日本动画不少 —— 在 CCTV 少儿频道看《四驱兄弟》,在 TVS (南方卫视) 看《哆啦 A 梦》,在华娱卫视《通灵王》、《军曹》,在本港台看《游戏王》,在翡翠台又看了不计其数的,就不一一列举。

我从这些动画片里感受不出日本文化,一是因为都是中文的配音,二是基本上我看的都是虚构类作品,无法从中了解到日本的文化生活。只有翡翠台偶尔播出一些配音的日本真人电视节目,让我了解到日本的街道和风景是如此模样,但仅此而已。

不过我却很能理解我的那位朋友,因为我对另一片土地 —— 香港 —— 有一样的感受。

电视机把我和香港的距离拉得很近。白天有「卡通片」,傍晚有儿童节目,每晚六点半,可以看《六点半新闻报道》,知道香港、国际在发生的事。7点半后,有《东张西望》看娱乐新闻。紧接下来就是八点档和九点档的电视剧。

我是看香港电视剧和电影、听「广东歌」长大的。聊起亚视的「柒事」,我们一样会大笑。现在不看电视,但还有 YouTube. 走在香港街头,我一个广东人,和香港人又有什么太大的区别呢。

最大的区别可能是,我很羡慕香港人。香港人可以有不同的政见,可以自由表达自己的观点,有权发声争取自己(或别人)应有的权利,可以大声对别人讲:「香港系有言论自由咖」!不用担心「讲错嘢」。而我不能。

香港不能失去这些。我愿意在这样的地方生活。香港是我的精神家园。

撇开身份认同,在政治上,香港回归早已经是国际社会公认的。我自己作为中国人,一直为中国有这样的香港感到庆幸。因为这代表中国人还能有另一种作为中国人的方式。

而我,很惭愧,虽同讲广东话,但在内地,也只不过是一只知道自己是井底之蛙的井底之蛙罢了。

王小波说「井底之蛙也拥有一片天空」,对我来说,这片天空就是香港。

衷心祝福香港。

我想得到它,因为它是好的

王小波在《跳出手掌心》里有这么一段话,初读时使我似醍醐灌顶:

我上大學時,有一次我的數學教授在課堂上講到:我現在所教的數學,你們也許一生都用不到,但我還要教,因為這些知識是好的,應該讓你們知道。這位老師的胸襟之高遠,使我終生佩服。我還要說,像這樣的胸襟,在中國人文知識分子中間很少見到。

把「知识」换成「普世价值」,我认为道理同样成立:

我们追求普世价值,是因为普世价值是好的,是对的,只要你是人类,你就应该拥有他。如果有人不幸失去,那么就应该帮助他。

令人难过的是,由于一系列客观条件,一部分人无法对此感同身受。普世价值和民族无关,和国籍无关,它属于全人类。一旦能意识到一点,那么这些人就很容易能识破一些延用了近半个世纪的可笑的借口和谎言,他们的被害妄想症也理应会被治愈。

很遗憾,我得不到它,我本应该能为此不断地写,不断地写,但我不能。这让我难过了很多年,一直到现在。

愿不在意的人能一直不在意,以免遭受这种难过。

人生是不断取舍的过程

如果问工作以来我悟出的最重要的观念是什么,那就是,人生是一个不断取舍的过程。

我们总是在作自己未来的规划和目标的时候说,我想要什么,而忽略了问自己一个问题,「我愿意为此舍弃点什么」。我们逃避了残酷的事实 —— 我们每天只有 24 小时,我们什么都想要,但这是不可能的。

意识到这一点,有利于我们做很多的决策,而且对很多让你感到可惜的事都释怀了。

有人总是会在年初列下自己的读书清单,但直到年底,发现自己一本都没有读完。这是很多人懊恼和焦虑的来源 —— 为什么我总是无法履行自己的规划?

过去我总会把原因归咎于没有做好时间管理,但后来我顿悟了,在列下读书清单时,并没有问自己这样的问题 —— 你愿意为读书带来的精神收益,而舍弃点什么?

如果你在制定任何规划时都这样问自己,那么,你就会在每一次决策前明确这一点:这件事于你而言到底有多重要?如果阅读于你而言比看美剧重要,你就要作出取舍:把你看美剧的时间,花在阅读上。

如果你认为做得到,那么就让它正式成为你的规划。如果你做不到,那么就不要让它成为你的规划。何必为自己制定一个无法完成的计划呢?它除了为你带来无意义的焦虑,无一用处。

宁愿看美剧而放弃阅读并不可耻,活得明白是最重要的。只要这是你权衡后作出的取舍,那么你就不会为此而感到焦虑。

我在《健身一年》里提到,一年前我下定决心开始健身,当时我问自己:我能为健身舍弃什么?

健身需要耗费每周四天每次 2 小时的时间,这些时间我可以用来写代码,做音乐,阅读。我愿意舍弃他们吗?

经过权衡后,我决定舍弃他们。于是我获得了一些健身的成果,同时我再也不为放弃了大量的阅读、写代码、做音乐的时间而焦虑。因为我早就知道,如果我想要获得健身的成果,这些是必须放弃的。

不必为「我没有时间 xxx」而感到可惜,因为你没时间做的事,不过是在为你更想做的事让路而已。(除非你把时间都花在偷懒上)

每个人有不同的活法,对待事件的优先级也不尽相同。只要认识到「吾生也有涯」的局限,你就可以从每一年底的「责怪自己没有做到什么」转变成「为自己做到了什么而感到高兴」。人生因此也变得简单得多 —— 你只要保证自己不是在虚度光阴即可。

就像即使我现在惧怕婚姻,若是将来某一天也选择了婚姻,那一定是比起婚姻让我失去的,我更想得到婚姻给我带来的。

健身一年

本文不提供训练计划 本文不提供饮食计划

我在《2018 年终总结》里提到了我健身的成果,在这篇文章里,我将分享我健身的过程,以及身体上、心智上的一些成长。

接触健身的经历

说来惭愧,我第一次接触健身是 2015 年。当时刚出来工作,有点闲钱,看到镜子里的那只瘦猴,决定开始去健身房增肌。于是在知乎搜索了很多关于增肌的回答,选了住处附近的一家健身房,就开始瞎练了,没有计划,也不知道怎么做计划。

那时我身高 165, 体重 46kg. 想做卧推,空杆(20kg)都推不起;想做划船,动作做不好。这时候在健身房确实很大压力,什么都做不好,还怕占着别人地方。

这样坚持了 2 个月,没什么效果。自然就放弃了。过了一段时间,我决定花钱请一个私教,重新开始我的增肌计划。那时已经是 2016 年了,我买了 10 节私教课。商业健身房私教的惯用套路——先去跑步机热身,然后教我做些动作。

10 节课下来我没学到什么东西,因为教练只告诉你做什么动作,怎么做,有什么注意的,却没有讲为什么。除了上课,我也在 YouTube 看很多关于健身的视频。坚持了 3 个月左右,没太大的效果,又一次放弃了。

之后很长一段时间,偶尔 YouTube 会根据我的用户画像给我推荐一些健身类的视频,我每次看到都觉得很羞愧——我无法面对这个坚持不下去的自己。我那里常常想,如果我当初坚持下来了,那么我已经有很大变化了吧。

虽然我很矮,但是说实话,我很少因为我的身材自卑,除了限制了我追女生的范围以外,我的身高没有给我带来什么不好的事。但是我总是隐约地觉得自己很孱弱,我觉得我不能保护我喜欢的人,不能给人安全感。夏天穿衣服像一个毒友似的。这些倒是我比较在意的。

导火线是 2017 年年底,我在某个停车场被一个保安言语暴力(过程不表),我十分生气,当时我想:是不是因为我看上去太弱了,所以这个保安敢这么对我说话?如果我强壮一些,或许就不需要和傻x讲道理了。我自己都缺乏安全感,怎么让别人有安全感呢。

回到家我想了很久,决定认真对待健身这件事。**我问自己一个最重要的问题:我能为健身付出多少时间?**如果我每周训练 4 天,那么我将会大量减少自己业余的代码时间和玩音乐的时间,我是否能接受?

当然,我最后选择了接受,于是才有了今天的一点点成果。

关于请教练

和前两次不同的是,我不再选择商业健身房,而是去找一些更专业些的以授课为主的健身工作室。恰好当时看中的一家工作室在做活动,很优惠,就买了 10 节课,开始了我的第三次健身...

为了避免广告嫌疑,就不说是哪家工作室了。

以我的经历,工作室和商业健身房比,私教的价格没有相差太多,买的课少(10节以内)可能相差 100 左右。买得多一些,差距就明显了。但是我是宁愿多花 100 上 10 节我能学到东西的课,也不想便宜 100 块上 10 节没有意义的课。

严谨地说,在商业健身房遇到靠谱教练的概率比在工作室要低得多。「靠谱」体现在:

  • 对健身的热情
  • 丰富的理论知识
  • 能和客户建立真正的有效关系

至于可不可以不请教练,我觉得天下间没有什么是不可以的。很多人一直自己练也练得很好。自己练的风险主要在于,你可能需要花更长的时间去探索正确的动作,也可能出现受伤的情况。所以我觉得请不请教练,是看你愿不愿意花钱规避这些风险罢了。

我自己主张的是有条件就尽量找教练(前提是靠谱的教练),因为我分析自己之前放弃的最大的原因就是不相信自己的训练,我不知道自己的动作到底有没有效。新手最大的问题是没有什么感受度——你根本感受不到自己的背部肌肉,怎么知道自己划船有没有划对呢。有教练的最大好处,就是他知道你有没有做对。这样就就不会怀疑自己是不是做了无用功了。

应该买多少节课呢。我当时买了 30 节课(做活动买的 10节 + 之后买的20节),后来我觉得 20 节课其实就足够了。我的训练频率是一周 4 练,上到 20 节课的时候,我基本已经可以自己独立训练了,也能感受得到自己的肌肉。最后 10 节课我都是专门让我教练辅助我练习我的弱项深蹲的。

最后关于跟着教练训练很重要的一点是,根据教练为你制定的训练计划去做没有问题,但你一定要自己理解这个训练计划,不要天天无脑地教练说做什么就跟着做,**多问教练为什么要这样安排?这样安排的好处是什么?同样是练这个肌群的动作,这个动作和另外一个动作有什么区别?**时刻提醒自己,这是你自己的训练,教练不是你的秘书(除非你这辈子都一直请教练),想想看,离开教练了以后,你能不能制定适合自己的计划?你从教练身上学到了什么东西?

我从我的教练身上学到的除了基本的动作模式以外,还学到的是,不要让客观条件限制了你的训练。训练动作不是死的,不是说没有平板凳、没有杆,就不能练胸了。多看看你现在有什么,能不能用现有的东西,去创造一个新的训练方式(即使效果不是最好的)。这是建立在你真正了解你的肌群是怎么工作的基础下才能做到的。所以我自己在业余的时间,也会去了解解剖学方面的知识。

当你在工作室上完课了,再转到费用更低的商业健身房也不迟。

关于饮食

「三分练,七分吃」,这句话都被说滥了。我个人觉得饮食比训练难太多了。特别是我这种所谓的「怎么吃都不胖」的瘦子。

其实根据观察,很多瘦子说的「怎么吃都吃不胖」,主要的原因还是吃太少,只是以为自己吃很多。像我,没健身的时候,对食物是很任性的——想吃就吃,不想吃就算了。我甚至认为人不应该浪费时间去吃饭,因为不吃也不会饿。吃饭的时候,一盘子的饭,我吃半盘就觉得太饱了,觉得自己吃了很多东西。但我后来观察那些稍胖的人吃饭,饭量起码是我的几倍。

健身以后,每天得强迫自己加餐,鸡蛋、坚果、鸡胸肉等等。刚开始训练的时候,我牛奶是买一升的那种,每天一升。

我不是专业的营养师,但我可以在这里科普一下,健身的时候应该怎么安排饮食。

不同的训练目的有不同的饮食方案:

  • 增肌:维持热量冗余
  • 减脂:维持热量缺口

这里所说的热量,指的是一个人正常活动时所消耗的热量(BMR)。所以从数学上来看,增肌和减脂都是很简单的——了解自己的 BMR, 然后针对需求调节自己的热量摄入。

如何控制呢?从控制提供热量的三个元素入手:脂肪、蛋白质、碳水化合物。

我不打算在这里大开篇幅去讲怎么去计算,因为 Google 一下你马上就知道怎么算了。我想说的是,一定要计算你的摄入量。例如增肌,如果你不去估算你自己今天吃有大概多少的热量,那么 99% 你是吃不够的。

肌肉合成需要蛋白质,所以增肌要保证足够的蛋白质摄入。关于摄入多少,众说纷云。我自己是按照自己体重(kg)乘以 2 换算成克 (g) 为单位的量。例如,体重是 50kg 时,每天至少需要摄入 50 x 2 = 100g 的蛋白质。然后用什么食物去满足这个量,就是你自己的决定了。

关于更详细的饮食计划,可以看看卓叔的 这篇文章

关于蛋白粉

能不能喝?有没有副作用?

能。没有(除非你的肾本来就有病变)。

喝多少?是不是每天都要喝?

上面已经提到了,你需要计算你每天要摄入多少热量。如果通过三餐或补餐你还是吃不够,那就用蛋白粉(或增肌粉)补够。蛋白粉不是药物,是补剂。这是很简单的数学问题。

关于训练

我的训练计划是每周 4 练:腿(+胸)、胸(+背)、背(+胸)、肩。

我不打算在这里详细列出我正在使用的训练计划,也不会教你怎么制定计划(这应该是你教练的任务),而是分享我一直训练以来的一些小经验。

  • 深蹲、硬拉、卧推这三大项是必须学习的,无论是健力还是健美,三大项都是基础。我在商业健身房练了这么久,没有亲眼目睹有教练教深蹲的。所以还是那句话,有条件,尽量选择健身工作室。
  • 如果你是瘦子,没有太大的必要去做有氧。除非你对心肺能力有要求。
  • 多做自由力量。固定器械除了安全,对新手没有多大的好处。卧推都没学会,就别去夹胸了。
  • 学习卧推时,瘦子可能(反正我当初是)连空杆(20kg)都推不稳,建议可以找更轻的杆(我们当时工作室有粉红色的 8kg 的杆)去训练,再慢慢地增加重量。
  • 记住,新手期,大重量不是你的目标,你的目标是熟悉动作模式。
  • 新手期,没有必要特别去练你的小肌群,例如手臂(肱二头、三头),手臂的力量会随着三大项成绩的提升而慢慢提升。
  • 刚开始背部没有感觉,可以选择相对简单一点的动作,例如反向划船。也可以选择感受度高一点的动作,对我自己来说,练背最有感受度的动作是哑铃单臂划船、对握引体向上、固定轨迹的高位下拉。
  • 关注一下你的关节活动度。这是很多人忽略的。可以问问你的教练,对你的关节灵活度做一个测试。活动度如果不足,要做一些针对性的动作解决一下。不然会一定程度影响训练。我的肩关节活动度受限就很影响我的训练。
  • 健身先健脑,健身是一门复杂的学问(营养学、解剖学、运动生理学),像你做其它事一样,不断地学习,才会越做越好。

还有很重要的一点是:一定要对自己的训练做记录。 If you don't mesure it, how can you improve it? 记录你计划里的所有动作,重量、组数、做完后的感受(这很重要,下一次训练是否增加重量需要参考上一次的感受,比如,如果累的程度是 10,这一次训练完的程度是多少?)。如果没有训练记录,那么你到健身房可以做的动作只有——闲逛。

每一次踏入健身房,你的目标就是,能不能比上一次训练推更大的重量。如果不能,分析自己在哪个环节出了问题——吃不够?还是睡不够?然后去修正自己的问题——睡不够,那就早点睡;吃不够,就吃更多。

我很害怕健身房里其它人的目光,怎么办?

其实没有什么人在意你。大家的目光一般是在漂亮的女性,或练得很好的人身上的。我自己在健身房里见瘦子,我的脑回路是这样的:

  1. 啊,我以前也是这样的
  2. 很期待看到他坚持下去之后的变化
  3. 他需不需要帮助呢

怎么选健身房

一般来说,健身工作室的设备不会有太大的问题。如果你去找商业健身房,要考虑的因素有:

  • 地理位置。这是最重要的,选一个在你能接受的范围内的。我现在去的健身房,从公司骑车要 10 分钟左右。
  • 进去先直奔自由力量区:有没有足够的哑铃?有没有足够的杠铃片?最重要的,**有没有自由深蹲架?**没有深蹲架基本可以走了。
  • 有没有可以让你做硬拉的区域?有的健身房是有一个硬拉区域的(或者在深蹲架里也可以硬拉)。

关于坚持

怎么去坚持健身?这是很多人问的问题。

首先是问自己,为什么开始健身?找到你的源动力。对我自己来说,源动力就是想变得强壮,遇到傻x的时候能自信地面对。

其次,一定要设立一个目标。而且必须是一个明确的目标。别说什么「我的目标是身体健康」,定一个可以量化的目标。我最初给自己的目标就是:2018 年底至少要体重要达到 60kg (当然我失败了)。如果没有一个可以量化的目标,在你没有爱上健身这个运动之前,你很容易会放弃的。

然后是不要和别人宣称你在健身,这对你的训练没有任何帮助,甚至会被嘲讽。更加不要傻到在朋友圈打卡——在你没有取得成果之前,没有人在意你的过程。打卡的唯一收获只会是更多人把你屏蔽了。

当你健身了一段时间,在吃、睡、练三件事都做好的基础上,第三个月绝对会有明显的效果,这是新手的福利期。很快会有人问你,咦?你是不是健身了,怎么壮了那么多(这是我健身 4 个月左右的时候听得最多的一句话)。这时你获得了成就感,享受了健身给你带来的改变,使你继续坚持下去。一直到坚持半年了,你会发现,健身渐渐地变成了你生活中的一部份。

当健身成为了你的一种生活方式时,哪有什么坚持不坚持。

我很多次洗澡脱光衣服看到自己身体的变化,都很感谢一年前的我坚持下来了。我再也不用对自己说「如果我当时...」

如果你说,「我很想健身,但是我没时间啊!」

据我观察,很多人所谓的没有时间,是不愿意花时间在健身上面而已。一天只有 24 个小时,除去上班的时间,我的时间很少,但为了健身,我减少了我写代码和玩音乐的时间,这是我付出的代价。如果你不愿意留你最多 2 小时的时间给健身,只能说明健身这件事在你的生活里优先级不够高罢了

不愿意把时间花在健身上面并不可耻,这再正常不过了。每个人有每个人的生活方式。如果你用「没有时间」作为借口,那么你相当于在说,我们这些健身的,都是闲得慌的。

2018 年终总结

大人热爱数字。如果你跟他们说你认识了新朋友,他们从来不会问你重要的事情。他们从来不会说:“他的声音听起来怎么样?他最喜欢什么游戏?他收集蝴蝶吗?”

他们会问:“他多少岁?有多少个兄弟?他有多重?他父亲赚多少钱?”只有这样他们才会觉得他们了解了他。

如果你对大人说:“我看到一座漂亮红砖房,窗台上摆着几盆天竺葵,屋顶有许多鸽子……”那他们想象不出这座房子是什么样子的。你必须说:“我看到一座价值十万法郎的房子。”他们就会惊叫:“哇,多漂亮的房子啊!”

—— 《小王子》

2018 对我自己来说,是灰心的一年。这一年除了和往年一样的焦虑以外,也没有做成什么我认为了不起的事。很大的原因是我对技术失去了信心。

小时候,我最大的理想是「用技术改变世界」,但我早在我的 2017 年终总结 里提到:「技术并不是那么重要,它只不过是一种手段而已」。今年我更加确信这一点,我们做技术的人,永远只能间接地改变世界。一个可以改变人们生活方式的产品,技术虽是不可缺少的部份,但也不是起决定性作用的部分。

于我而言这是灾难性的信仰崩塌。

有时夜里我也会反思,是不是对自己的要求太高了,又或者是,我太想向别人证明自己,害怕自己变得平庸。而在今年,我意识到自己真的变得平庸了,所以我把他称为灰心的一年。

而今年最主要的心态变化,则是随着年龄的增长和财富的积累,我不免也会想到未来的现实生活。婚姻、家庭、置业,等等。我觉得这些东西不能带给我快乐,它们不是我想要的。但是为什么每一个人都要「善意」地提醒我,你应该这样,应该那样呢。我不买房,便错么?成年人的眼里,为什么都只有所谓的「保值」、「升值」呢。

我们总说大人们不关心孩子们快不快乐,只关心孩子们的成绩。原来大人们也不关心大人们快不快乐啊。

工作

今年除了支撑了几个业务的后台管理系统以外,主要在思考的东西是如何利用 GraphQL 帮助开发者更轻松地应对中后台管理系统的开发。

在内部做了很多基建以后我有一个感悟,我们做基建的初衷是提高开发者的效率,但是实际上,影响开发者效率的因素,很大程度是开发者本身。基建能做的很有限。举例来说,Redux 这么一个「简单」的库,却有很多人用不好。在项目里,很多可以简单实现的地方,由于编程水平、经验、对库/框架的了解程度等等条件的不足,开发者就把简单的问题复杂地解决了。

即使给人一台最好的单反,也不一定拍得出最好的照片——因为重点还是镜头背后的那颗脑袋。

所以在新的一年,我的目标是多输出一些理论层面的东西。

消费

2018 年依然是没有节制地消费,不过买的东西已经不多了(该买的都买过了)

  • 把 Yamaha 的电子琴换成了 Korg Kross1
  • 搬到了一个 43 平米的一房一厅
  • 买了 TASCAM 录音笔
  • 买了 Scarlett 2i2 音频介面 (用来录些 cover)

(不得不提欧德堡全脂牛奶是大概一周一箱...)

输出

今年尝试性地做了一集技术的短视频内容:《解读 The State of JavaScript 2018》

生活

2018 年我做了决定,我的生活不再是 80% 的 coding time 了,我把更多的时间,花在了别的地方——音乐、健身、读书。这是一个巨大的变化,因为在以前,代码几乎是我生活的全部。我意识到,如果我稍微放下一点点的代码时间,生活里还可以拥有更多有趣的东西。

健身

gym 健身可以说是 2018 年唯一一件做成了的事情。我从 2018 年 1 月份开始健身,每周训练 3-4 天,控制饮食。直到现在刚好整整一年,达到了从 46kg 到 54 kg 的变化。

如果问我是怎么坚持下来的,我认为无论是坚持什么,都是:因为相信,所以咬着牙坚持试试,坚持了一段时间后,它就成了你的一种生活方式。当它成为了你的一种生活方式的时候,就无所谓坚持和不坚持了。就像你不会去问别人是怎么「坚持」看美剧的一样。

关于更多我健身的经验,我会在之后单独写一篇文章分享。

德州扑克

今年学会了德州扑克,和朋友打了很多场。我非常喜欢这个扑克游戏,他让我更了解我的牌友,更重要的是,在牌局里,我意识到了自己的缺点:冒进、喜欢承受高风险高回报、贪婪、充满侥幸心理。每次打完,通过回顾自己的打法,我更加了解我自己,我会反思:

  • 是不是贪婪让我失去了我的筹码?
  • 下一次我再遇到相同的情况时,我如何控制自己的欲望?
  • 我应该付出多少的筹码,才是一次价值下注,才能击败我的对手?
  • 面对失败时,我是否能做到及时止损,调整心态?
  • 当我读到对方的牌有 80% 的概率是比我强的时候,我是否可以克服自己的侥幸心理而不去跟注?

德州扑克结合了运气、心理、概率、演技,它不仅是人与人之间的博弈,也能让你有不妥协于运气的机会——你拿一手烂牌,仍然能打好(当然,运气有时候也会打败你,本来在转牌时胜利在望,河牌却是别人的翻身之牌)。

我还转载排版了世界扑克巡回赛(WPT)总决赛冠军老邱的小传记 《赌士列传: 老邱传奇》。相信你可以通过这篇传记感受到德州扑克的魅力。

博客成绩

在这一年收到了挺多的读者来信,向我询问建议,我都尽量抽时间一一回复。还有一些文章收到的打赏留言里提到的因为我的文章而有所收获,让我知道我写的博客的确有他的价值所在。

关于 2019

希望把生活过得更有趣一些。

我在学校学到的东西

今年身边的朋友陆陆续续毕业出来工作了,偶尔有机会的时候会聚在一起聊聊,然后谈到各自的就业情况。我发现有很多朋友还不知道自己想从事的职业是什么,因此也不知道自己未来会怎么样,先做着现在能做的。

因为我的经历特殊,有人也会对我开玩笑说真不知道读书有什么用,还不如早点出来工作。我知道以我朋友的受教育程度来说,这肯定只是玩笑,他们不可能是「读书无用论」者,所以我也会附和着笑。

虽然这只是句玩笑话,但是确实令我回顾了一下我从我的在校生涯中得到了什么东西。

我首先想到的是英语。我的小学是从一年级开始就有英语的课程,所以我接触英语的时间很早。我自认小学四年级以前我还是个很乖的学生,上课认真听讲,作业按时完成。所以我的语数英成绩都很不错。从小培养了「语感」,使得我一直以来可以低障碍地读写英文。这对我的编程学习、职业生涯都有很大的意义。懂英文让我可以亲自融入整个开源社区、技术社区,可以直接在 YouTube 听别人在 Conference 的 Talks, 吸取别人的思想。而不是等别人翻译过来。我认为能低障碍地读写英语是技术人成长很重要的条件。

尽管我对现阶段的英语教育方式仍然存疑,我还是认为在基础教育里,英语是必不可少的。

我的价值观和人生观,是我于在校生涯之中建立的。不过不要误会,这些都不是学校的课程直接赋予我的,而是学校这个「象牙塔」,让我可以有充分的时间去读书、学习和思考。我虽然在班上是「差生」,但我敢说我是最爱学习和读书的。这句话要是被我当年的老师看到,估计他要笑个半死,但这是事实,不可否认。我在学校不用供车供房工作赚钱,我可以把一整天的时间花在读书上,我在我读的所有书里懂得了我应该成为一个什么样的人、我应该努力让自己拥有什么样的品格才能算是一个「好人」。

另外,我很多技术书籍都是在学校读的,比如《JavaScript 高级程序设计》,我读了很多遍。不是我自己想读那么多遍,而是当时我带回学校的就只有这一本书,我一个月才回家一次。那一个月我只能天天都读他。这本书我每读一遍都有新的发现。

出来工作以后我还能像这样把一本好书读这么多遍又读这么认真吗?可以,但是很难了。

我高中的时候,最喜欢上语文课。语文课本选的文章其实挺好的,比如说有《棋王》,以及很多《古文观止》里面的文言散文。偶尔发个新试卷,我也很开心,因为一张语文试卷有两篇阅读理解。我高中读的很多书,都是在试卷里和练习册里发现的,新的练习册一发下来,我就把所有的阅读理解都看一遍,看到哪篇我觉得不错的,我就看是节选哪本书,然后就去图书馆借回来。另外,写作文大概是我在学校里唯一的思考输出的方式了,因此我很珍惜每次写作文的机会,我把自己的所思所想全写进文章,以至于每次都很低分,因为离题万里。我的语文老师对我很包容,他知道我想什么,我很感谢他。

有人会说,你把学校说得那么好,你自己怎么就辍学了呢。

我认为,一个人在结束学校生涯以前,必须先弄清楚两个问题。一个是,**在离开学校以后,你在社会上是以一个什么样的角色存在?**另一个是,**你是否已经为迎接这个角色做好了充分准备?**学校的意义就是给你时间想清楚问题一,然后给你条件完成问题二。

我在学校里,早已弄明白了这两个终极问题。我在社会上,将会以软件工程师的角色存在,并且我已经有能力胜任这个职位。往小的说,我能为我所在的公司创造价值,往大的说,我要为整个社区作出贡献。所以(在一所不怎么样的)大学对我来说已经不那么重要了,我希望早点投身社会创造价值。

很多人就是因为在学校的时候从来不思考这两个问题,所以是「光着身」离开学校的。你是想创造价值,还是想做着不喜欢的工作然后满足于 奶头乐 ,其实都由你自己决定。

以上,就是我在学校学到的东西。

延伸阅读

多研究些问题,少谈些框架

当我问应聘者「你有什么问题想问」的时候,大多数应聘者会问我前端应该怎么学。我每次都会很真诚地按照面试过程对他们的了解尝试给他们一些建议。从多次的交流当中,我倒是慢慢地了解到了前端领域里很多普遍的问题。

前端有很多不同的框架和库。 我上大一的时候,有一门课叫网页设计,前几个课时教 HTML, 又教一个课时 CSS, 再教一个课时 JavaScript. 然后是 jQuery. 当时我很气愤,学生连 DOM 是什么都还没搞清楚,就要他们用 jQuery,这不是误人子弟吗。我后来想明白,学校大概已经把 jQuery 看作是饭碗了。

把库和框架看作饭碗是无可厚非的, 我们不能苛刻地要求所有程序员都对软件开发抱有发自内心的热爱。但是急功近利地学习框架,绝对是举步维艰的。胡适说「多研究些问题,少谈些主义」,在编程上,我认为需要多研究些问题,少谈些框架。

框架是我们达到目的的便捷手段,我们使用 React, 更要知道为什么使用它?它的本质是什么?它解决了什么问题?所谓「格物致知」,不「格物」,如何「致知」呢?

2017 年终总结

Preface

我的 2017 过得很平淡,可能是因为出来工作已经是第二年了。

今年比较特别的是身边的朋友都大四了,参加了一些毕业拍照。大家都陆陆续续地开始找工作,面试。偶尔在朋友圈看到朋友拿到 offer, 也会替他们感到开心。

有很多朋友开始请教我一些出来工作的经验,面试的技巧、offer 怎么选择、租房的经验等等。被问到这些问题的时候我也会很开心。因为以前上学的时候,学业成绩不好,我在班上除了搞搞笑,对身边的同学来说并没有多大的「用处」。

4 月份的时候上台拿了 16 年的优秀新人奖,但是 17 年整一年没有做出很出色的成绩。慢慢失落地发现在很多事情上,技术并不是那么重要,它只不过是一种手段而已。

优秀新人奖

在年底,和 @EGOIST 一起创办了 StickerOverflow. 成功地让国内的开发者能买到高质量的技术贴纸。

StickerOverflow

今年想得比较多的是应该如何用自己的能力去帮助更多的人。想过去做培训,想过回大学给以前班的人开个交流会,想过写书。最后都搁置了,因为我后来发现,「助人」有时是一件很一厢情愿的事。

除了技术外,这一年有很多新的尝试,上台唱歌纪念张国荣,参加了唱歌的比赛,做了一期音乐电台节目。在新的一年,还想尝试去经营一个音乐博客,让更多人发现被忽略的优秀粤语流行音乐和歌手。

新年计划

2018 年的计划是多写写代码,多玩玩音乐,读更多书,换一间大些的房子,买一辆车。

有次和朋友聊天,她说,你知道自己想要做什么,又能把它做到,还能靠它养活自己。你千万不能变得世俗啊,如果你也变得世俗,我会觉得这个世界,真的就是那样了。

高中时的地下刊物

一个月前一位初中朋友 Bunny 发了一篇 《年少的文字》,讲起了初中的我们搞地下刊物的事,让我想起了那段时间这本刊物的一些点滴。

这份刊物叫《新视觉》。当年要做这份刊物,是因为那段时间,我读了很多民国时期的文学作品,还读了李开复的那本《世界因你而不同》,使我开始对政治、教育有了不一样的看法。

当时的我认为文字只有背负了政治的责任,才能称得上文学。所以看不起很多「当代文学」。我对当代文学的定义是 —— 1990 年以后出现的文学。

班里一个女性朋友很喜欢郭敬明,我却对此嗤之以鼻。于是我给我朋友传教似地讲了很多诸如为什么我们需要关心政治、我们应该读些什么书、我们需要什么样的教育等等这样的话题。但是她不以为然,认为我越俎代疱。

我想,我应该做一份刊物,来传达我的理念。因为「不识字的上人的当,识字的的上印刷品的当」。

就这样,我拿了一沓我们学校标准的信签纸,开始做了起来。我把这份刊物叫做《新视觉》,是源于白话文运动时,陈独秀所创的《新青年》。我认为这份刊物的目标,是为了提倡新思想,以摘抄严肃文学作品的形式,启发班上的同学,应该从现在起,去寻找自己的目标,自己想要的是什么,未来想做的是什么。而不是把所有时间,都花在试卷和娱乐上。

为了传达这份理念,我在首期的《新视觉》的第一页,认真地誊写了 Robert Frost 那首诗 —— The Road Not Taken.

我现在已经记不清当时摘抄了哪些具体的文字了,只记得抄过王小波、李敖、韩寒、胡适等等。

这份全手写的刊物开始在班上互相传阅,并不是因为内容深刻,而是因为形式新鲜罢了。我为了这份刊物可以维持下去,「聘请」了上文提到的女同学作为编辑,也接受所有同学的来稿。

接受同学的来稿后,刊物渐渐成了班级的一部分,甚至每一期都会有人催我「出版」。当时的「出版」形式,则是收集来稿,用钉书机装钉成册。

同学的来稿很有意思,有人开始做起了小说连载。而我还是做一些摘抄,顺便构思一些虚构类的讽刺文章。同时 Bunny 开始把手写的刊物改革成了真正的印刷品——每周回家录入然后打印出来。

因为刊物带有媒体属性,班级上一些事件也会被刊登到里面去。例如校运会,我们做了一份校运会特刊,报道了校运会的情况、现场照片。

虽然刊物在班上火了,但我并不是太开心,因为这本刊物慢慢偏离了我的初衷。我向 Bunny 提出要做一本《新视觉》的子刊物,就叫做「16 度」(我们是 16 班),把小说连载、读者来信这些内容都放到里面,《新视觉》只做严肃文学。

于是 Bunny 开始打造《16 度》,而我继续负责《新视觉》。发展到后来,《16 度》其实有很多地方和《最小说》变得很相似,是同学们饭后的话题、减缓考试压力的地方。很多人在上面连载小说。不生产的内容的,也会来留言,说自己对某篇文章的读后感。而这份带有意识形态的《新视觉》很快就没有再连续出版下去了。

以上是我对这份刊物仅剩的几乎所有回忆,手写的稿早就在初中一次搬课室的过程中丢失了。谁会想到这些东西未来会被这样津津乐道呢。这确实是一个遗憾。

从手写改革到印刷的头几期

校运会特刊的「发布会」

校运会,「小编」们的一次合照

养猫随想

我是个很怕负责任并且不感性的人,养一只猫对我来说,是一种很大的责任。

传闻说猫的智商相当于 2~3 岁的小孩,所以我会把养她当作是在养我的小孩,每个月要花钱换猫砂,去超市要顺便买些罐头,偶尔也想去宠物店看看有什么好玩的玩具。养猫和养小孩的区别,我想大概就在于前者的过程中不需要担负教育的责任。教育很难。

我外甥女今年刚好是 3 岁,有时候我也觉得和猫很像:见到什么都要摸摸看,甚至打翻。

对于猫打翻东西,一般来说,主人都会生气。但是我想,3 岁的小孩打翻了东西,其实是监护人的责任。既然家里多了这个不懂事的小孩,易打翻的东西就不该让她碰到。出现事故,自己要先反思。

她不会完全信任我,她吃东西,我路过,她有时还是会立刻跑开。帮她滴清耳液的时候,她看到我拿着瓶子也会跑开。但有时候我觉得她也很信任我,每次回家,不管她的时候她都要一直叫,非要爬过来腿上让我摸才停。养猫有趣的地方就是这里,你要向一种时刻保持警惕的生物身上获得信任。

来我家第二天,对我的第一次试探

第一次带她去医院打疫苗,在医院里我和几个素味谋面的猫主人狗主人竟然可以自然地打成一片,互相交流育宠经验,然后让自己的宠物见一见对方的宠物。大家都像妈妈们互相交流育儿经验似的。

有时候在路上看到走过的流浪猫,会想要不要把它养了,让她有个伴。但是想想,我确实还没有做好二胎的准备。

前段时间发烧,在搞保险报销的时候,我想如果可以给宠物也买保险,我也会给她买一份。

养一只猫不容易,何况养一个人。所以养猫之后,有时我会留意街上怀孕的路人,想她们是不是做好了准备,去承担这份责任呢。这需要一份勇气和担当,还有深思熟虑。

鲁迅在《热风》里谈到孩子,他说,「中国的孩子,只要生,不管他好不好,只要多,不管他才不才。生他的人,不负教他的责任。虽然“人口众多”这一句话,很可以闭了眼睛自负,然而这许多人口,便只在尘土中辗转,小的时候,不把他当人,大了以后,也做不了人。」

因此我很怕,我没有准备好,没有想好要怎么做教育。我要先把猫照顾好,才有信心去养一个人。

以上。

我的编程经历

我接触编程的时间比较早,是 13 岁。我为什么记得是 13 岁,因为我六年级那年正好 13 岁,我是六年级开始编程的。

很多人会问我,你是怎么会想到要编程的。其实是因为当时班上有一个和我很要好的同学,他在写 PHP。我小学上的是私立学校,每个教室讲台都配了一台用于播放教学 PPT 的电脑。有一天他告诉我他写了一个博客,用的是 PHP。然后在讲台的电脑打开了他博客的后台,改了几句 CSS, 博客的颜色就变了。我当时很震惊,觉得我自己也要学会。

但是我没有去学 PHP,原因是 PHP 的书太贵了。我拿着我交学杂费的 20 块找零,到购书中心买技术书,最终找到了一本 19 块的《C 语言程序设计》,不是谭浩强的,是一本中专教材。

当时的我对编程没有什么概念,对语言当然也没有概念。回到家,跟着书上的步骤,安装了 Turbo C, 写一些四则运算。

入门编程最难的其实就是这个阶段,面对白字黑底的命令行,写的是代码,做的是数学题(算水仙化数,算 Fibonacci)。

我是个数学不好的人,小学教到了除法以后,我的数学成绩就没上过 85 分。因为我花了很长的时间理解除法,到我理解了以后,教学进度已经很往后了。数学对程序员来说重要吗?我觉得很重要,因为数学决定了一个工程师的上限。但我实在太笨,无论如何也学不好。

于是我对 C 语言开始失去兴趣,开始搞 GUI 了。

我是 2009 年开始搞 GUI 的,那年刚上初一,读了李兴平的经历,备受鼓舞。李兴平是 hao123 的作者,2004 年的时候被百度收购了。我决心也要做一个网址导航。

因为这个网址导航的目标,我正式接触了前端开发。从一本教 Dreamweaver 的书里面学了 HTML, 在 w3school 学了 DIV + CSS 布局。很快我就写出了第一个网址导航的 demo, 但是是静态的页面,里面所有的网址栏位都是自己手动编辑的。

这段经历对我来说收获很大,因为通过编写一个网址导航,掌握了很多 HTML, CSS 的技巧。这些技巧是无法通过书本系统习得的。

做技术不是照本宣科,因为语法是有限的,但是想象力和智慧是无限的。举个例子,你可能从文档和书本上了解了 float 属性,但是只有当你开始写一个横向导航的时候,才会思考到,可以用 float 把 ul 中的纵向的 li 变成横向。

另外一个例子是 TJ 的 co. 可能大多数的人都知道 Generator, 但是 TJ 想到了基于 Generator 把异步写得像同步。这是一种想象力。所以为什么要读源码,因为语法是有限的,想象力是无限的。

这使得我多年来一直保持一种习惯,来保持我的想象力。就是每当我看到一些有趣的网页效果的时候,都会先思考,如果我来写,我会怎么写。最后审查元素,看看我想的和具体实现是不是不同?如果不同,是不是别人的实现比我更好?是不是用到了开源库?如果没有,那么我是不是可以把这种效果封装起来?

文档只会告诉你 border-radius 的特性,但是人们的想象力可以用其特性实现一个三角形。在人类的进化当中,是想象力使智人到达了食物链的顶端。在软件开发上,我认为是智慧和想象力使工程师达到卓越。

在掌握了基本的网页开发技能以后,我的重心转移到了桌面软件的开发上。因为当时我错误判断了桌面软件的前景,认为桌面软件才是未来。所以我尝试了 Delphi, 因为 Delphi 而学习了 Pascal.

其中一个很有趣的经历是我学过 Delphi 以后意外地得知了易语言。易语言号称中文开发,所有语句都是中文的。易语言内置的控件库非常丰富,是当时我被吸引的最重要的原因。

学习了一段时间后,我开发了第一个上线到软件站的软件,叫做「网帝系统清理助手 2009」,至今还能 Google 出来。

网帝系统清理助手

我那时非常中二,成立了自己的虚拟软件工作室,取名「网帝」。这是一个充满抱负的名字,因为他的含义是「互联网帝王」。

软件上线了以后,在各个软件站的下载量很不错,于是我又马不停蹄地开发了第二个软件,叫「网帝 QQ2009 去广告精灵」,用来去 QQ2009 里的广告的。

网帝 QQ2009 去广告精灵

我那时候开始泡易语言的论坛,结识了很多论坛里的大牛,那时候称「大虾」。我记得有一个网名叫「凝逸」的版主,用易语言开发了一个叫「凝逸反病毒」的杀毒软件。我很震惊,因为我非常好奇杀毒软件的工作原理。于是我在他的帖子里询问,但是他没有回过我一次。

我又通过自己的摸索,做了一个可以根据病毒库查找病毒、保护 IE 主页的杀毒软件,「极光反病毒」。

极光反病毒

以上三个软件是我做桌面软件时的产出,当时还在读初一。做完极光反病毒的第一版后,我觉得这种反病毒太傻,要依赖特征库去匹配病毒。病毒稍微变种,就查不出来了。于是我想要做一个防御式的反病毒工具,根据文件的行为,来判断它是不是病毒。比如如果这个文件在不断复制自己,那么它肯定是病毒。

易语言没有文件监控的 API, 我只好学习一个微软的亲生儿子,C#.NET 来做这件事。不过后来学到一半,由于学业压力,没有继续再做下去。

2010 年年底有一部电影对我影响很大,讲 Zuckerberg 创立 Facebook 的经历,叫《社交网络》。

The Social Network

这部电影我反复看了 7, 8 遍,我觉得用技术做出有很多人喜欢的产品太酷了,我很想成为这样的人,应该是我终身奋斗的目标。这部电影,直接导致我放弃了桌面软件开发,正式转向了 Web 开发。

我的 PHP 就是这段时期学的,因为 Facebook 是用 PHP 写的。我买了一本蓝色封面的书,《PHP + MySQL 程序设计》。这本书的内容讲得头头是道,但是我一点都看不懂。我对 Web 开发除了网页设计以外一窍不通。结果我又去买了一本国产的书,《PHP 兄弟连》。

这本书的作者是专门搞 PHP 培训的,讲得比较浅显易懂,成了我的救命宝典。从那本书里,我对 PHP Web 开发有了一个大概的了解,知道 LAMP 架构,知道一个完整的 Web 系统应该有哪些基本的部分,知道什么是模板引擎,更重要的是,开始接触 Linux。读完读那本《PHP + MySQL 程序设计》,犹如打通了任督二脉。

后来我用 PHP 做了一个我们校内的非常精简的社交平台,不是说我那时就开始崇尚极简,而是我的能力就到这了。于是脑抽风去找学校的副校长谈,结果副校长说这个不错,但是我们不主张校内建的讨论区。

我深受挫败,然后就学 Python 去了。

学 Python 是因为一个 Python Web 框架 Tornado. 看多了 PHP, 会觉得 Python 非常美。Tornado 本身也很美,尤其是路由的设计,和 Web.py 一样美。我从此再也没写过 PHP。

我没有用 Python 写过什么有效的产出,但是从学习 Tornado 的过程中,我了解了异步的概念。Tornado 是个异步 IO 的 Web 框架,这对我之后学习 Node.js 十分有帮助。

整个初中大概就是这么过去的,初三那段时间是一段技术真空期,因为有升学压力,并且开始沉迷文学作品。

升上高中以后是我技术上一个很重要的转折点,因为我鬼使神差地从一个成绩优异的学生,变成了一个老师眼中的差生。

这对我来说意义非凡,因为我由此决定放弃学业,专攻编程。那时候 2011 年年底,刚好是 Node.js 最火的那一年。

我是在那年开始读《JavaScript 高级程序设计》的。我并非毫无基础地读这本书,我在写网址导航的时候,已经读过几遍《JavaScript DOM 编程艺术》了。

所以很多人到后来会问我,我想学前端,有什么书推荐?我都会给他推荐这两本书。先读完《JavaScript DOM 编程艺术》,再反复读《JavaScript 高级程序设计》。为什么是强调反复读,因为就我来说,这本书,我每读一遍,都有新的收获。甚至工作以后遇到问题,我也会翻一翻这本书,重新复习里面的原理。

和我接触过的人,不多不少都知道我是一个相信社区的人,因为我算是见证着 JavaScript 在社区发展的,一群人是怎么样通过社区创造生态的。没有社区,一切都不可能。

所以我也积极参与到社区里面去。很多人害怕社区,认为自己的水平不够,其实是种错误的想法。参与社区不仅仅代表贡献代码。阅读源码,发表源码分析的文章,这是参与社区;阅读代码,动手修改 bug, pull request, 这是参与社区;不读代码,帮忙解决 issue, 这是参与社区;不读代码,关注一个项目的进展、规划、出谋划策、提意见,这也是参与社区。这样的水平要求高吗?

学 Node.js, 我用 Express, MongoDB 写了一个可以提交 Chrome 插件的插件商城。这个项目对我自己的意义在于,整个产品都由我自己一手包办,部署、项目架构、数据库设计、文案,等等。一手包办的意义在于,我知道链条中的每一部分是怎么工作的,前端和后端是如何配合的,这使得我往后在团队合作的时候,和别人的沟通十分流畅。

在高中的最后一个阶段,我学习了 Angular, 双向绑定那时在前端是一个新鲜事,颠覆了以往以手动操作 DOM 节点为核心的开发模式。那段时间印象最深刻的是,我花了大量时间,来把自己的思维从 MVC 的模式,转换到 MVVM 的模式。

高考完的暑假,我入手了人生第一台 Android 手机 Nexus 5, 我开始学 Android 开发。在学 Android 开发以前,我是一个 Java 无脑黑。认为 Java 罗嗦,严格类型。但是从 Android 开发的经历,我对 Java 逐渐改观。很多在我看来的无意义的、罗嗦的代码里,换来的是工程上的健壮性,可维护性。我会思考,如果可以选,我愿意用 JavaScript 去写 Android 的 Application 层吗?我不愿意,因为 JavaScript 不适合。

虽然我后来也上架了些 Android App, 但是学会辩证地看待每一门语言却是我学习 Android 最宝贵的财富。

上完大一我就退学了,原因简单来说就是旷课太多。整个大一我进步很大,因为 Android 开发和前端开发我是并行学习的。退学以后,就开始了职业生涯。

其实我没有想过我会以前端工程师的身份进入这个行业,因为我高考后是立志成为一名优秀的 Android 工程师的。前端于我而言,只是一个基本技能。但是找工作迫在眉睫,又不能拿我半桶水的水平去应聘 Android 开发。

所以我的意思是,无论我现在的职位方向是什么,比如我现在是前端工程师,但我首先是一个软件工程师,其次才是前端工程师。语言只不过是你的工具而已。

如果要我说我学习编程有什么技巧,我认为是首先要清楚,你想做的是什么。再去想,你用什么去做。

最后再分享一些一路以来的点滴:

我家人都是很传统的一代,不理解我学习编程最终能做出什么,他们主张先完成学业,再做这件事。但是我很清楚我自己的智商,不足以多线程处理不同的大领域,所以我顶着压力,努力地做出来他们能看出来的「成绩」,才能换来他们的理解。所以,如果你通过你的理性分析,坚持认为某件事情是对的,就努力的去做,不要放弃了以后看到另一个人做了你曾经想做的东西然后感慨当初应该怎么样怎么样。我尤其感谢我的父母,没有因为自己的不理解而采取强制措施阻止我沉迷计算机,例如拔网线。

第二件事是我初中的时候读的是私立封闭式的学校,两周才能回家一趟。我当时是怎么学编程的呢?每次回到家,我都把要读的教学,存到我的 U 盘里。回到学校,趁老师不在的时候,就偷偷用讲台上读。在家里的时候,玩电脑时间被限制在 2 小时,我会用这两小时,把要读的教学,排版成 txt,然后放在 U 盘,插到电视上(当时的电脑可以播 txt),在电视上读 txt. 我的 Pascal 就是这么学的。

相对于我那时的情况,其实大家能学习的时间和机会都要多得多,如果你真正热爱这一件事,我想,无论什么样的情况,你都能抽得出精力和时间,去使你自己变得更好。

做技术要自信

我很久以前就把博客的评论直接去掉了,原因是大多数评论毫无营养,如果我发技术类的文章,评论都是「围观」,「膜拜」之类的内容。偶尔有人会通过微博私信问我技术,开头都是很客气,称呼我「大神」,然后声称自己很「菜」。总的来说,就是把自己的地位拉得很底,误以为这是一种谦虚。

「大神」这类的称呼一开口,会把两个人的距离感拉得特别长,就像闰土叫了迅哥儿一声「老爷」。我没有觉得自己很厉害,反而是越学越自卑了。我总结了这些视我为「大神」的网友,不过是经验比我少一些, 或者思考的东西比我少一些而已。

于是我回想自己刚接触编程的时候,也很喜欢逛 BBS, 看一些版块上有些许知名度的网友,发自己的新作品,心里很是羡慕,觉得他们技术很强,是无法触及的。我只能静静地看着,不断地思考他们做出来的功能是怎么实现的,然后翻书找。实在找不到,也会回回贴,发发站内信,请教一下实现的思路,不过基本是没有人会回复我的,因为那时年轻,提问的方式很蠢。渐渐地,我的经验也因为自己做的 side project 日积月累了出来,慢慢地也成了一些人口中的「大神」。

如果说这其中的成长有什么秘诀,我认为是自信:遇到自己不懂的问题时,永远相信自己可以通过不断的学习和查阅资料得到解答,并使解决问题的经验成为自己的一部份。

妄自菲薄会让人变懒,认为自己永远达不到别人的高度而放弃努力。很多人因为不自信,开始依赖别人,把自己活成配角,又苦恼自己不成长。我通常会建议他读一读《提问的智慧》,学会自己先思考问题。我从前也很害怕问比我富有经验的开发者问题,因为我害怕自己的提问很蠢。因此每次提问前,会先梳理好问题梗概,我做了什么去解决这个问题,最后才小心翼翼地把问题发出去。其实往往在梳理问题的过程中,自己就把问题想通了。

所以做技术要自信,不要做伸手党,要多思考。开源不是所谓的「大神」才能参与的,你也可以参与,从读代码开始。每一个你眼中的「大神」都不是生下来就会编程,他们只是善于自己解决问题,并坚信自己能解决问题,最后成功解决问题。

从 React 谈 Web UI 开发

此前我在 Twitter 上这样表达过对 React 的理解,但是 Twitter 篇幅有限,所以在这篇文章里,我要做更详尽的阐述。

我从前不喜欢 React, 是因为写 React 的 render function 不像写 template 一样方便,尤其是存在复杂的判断渲染的时候,Vue 的 template 一个 v-if 就搞定了。而在 React 里写,需要把这个判断写成 function, 然后条件判断 return 哪一个 view. 这是我最初对 React 的偏见所在之一。

然而经过自己的实践和思考,加上阅读一些文章,我发现以前的想法是错的。我在使用 React 的时候,没有做到 Thinking in React. 从而对 React 产生了不解和困惑。

有很多人把 React 当成框架来用,这是用不好 React 的根本原因。很少人认真思考 A JavaScript library for building User Interface 背后的含义,把 React 用得一团糟。

何谓 For Building User Interface? 意思就是,这个库仅仅是用于构建 UI 的,这是 React 本质要解决的问题。我甚至和很多人说,事实上 React 本身是不是 React 已经不重要了,重要的是我们写 UI 的思维。React 这个 library 本身仅仅是用来实现这个思维的手段。React 提供的,是优秀的 DOM diff 算法,和一套 Component system。换成代码来说,也就是:

(state) => View

这是 React building UI 的核心思想,所有的组件,就是接受 state, 返回一个 View. 这样看上去比较抽象,比如我们有一个 Clock 组件:

const Clock = (time) => `
<div id='clock'>
	<span>It's now: </span>
	<span>${time}</span>
</div>
`

Clock 是一个 function, 接受一个 time 参数,返回的是一串 HTML String. 在程序里,我们可以给一个 Interval, 每秒传一个当前的 time, 得到一个新的 HTML String, 然后 apply 到某个 DOM 上。

const $app = document.getElementById('app')

setInterval(() => {
  $app.innerHTMl = Clock(Date.now())
}, 1000)

这样的实现是能达到目的的,但是问题在于,每次 innerHTML 时,整个 #app 的 DOM 树会被重新渲染。

我们都知道,DOM 更新的花费是昂贵的。整个 DOM 树,实际上只是一个 span 在不断变化,所以我们需要 DOM diff 算法来得知到底哪一个 DOM 节点才需要被更新,从而节省开销:

const Clock = ({time}) => (
  <div id='clock'>
    <span>It's now: </span>
    <span>{time}</span>
  </div>
)

const $app = document.getElementById('app')

setInterval(() => {
  ReactDOM.render(<Clock time={Date.now()} />, $app)
}, 1000)

在 React 里,把 props 传入,返回一个类似 HTML 的结构,然后 render 到指定的 DOM 节点上。这里 React 会算出哪个节点应该被更新:

我们这样手动去 setInterval 然后 render 未免有点傻,我们可以更改 state (也就是通常用到的 setState) 自动地让 React 随着 state 的改变而重新 render. 这里的 time 就是一个 state. 这叫做 Reactive.

Functional Programming 里有 Pure Function 的概念。Pure Function 之所以 Pure, 是因为不存在 side effect. 举个例子,我们写一个求和 function:

function add (a, b) {
  return a + b
}

这个求和函数就是一个 pure function. 因为函数内部没有对 input 做任何改变,并且返回一个新的值。我传 1 和 1,得到的永远是 2.

Pure Function 的好处是利于维护和测试。要测试一个 Pure Function, 仅仅是传不同的值,预言对应的返回值。

现在回头看 React 的 Component, 也可以算是一个 Pure Function——接收不同的 props, 然后 render 对应的 View. 上面 Clock 的例子,props 和返回的 View 是映射关系。

光是 state => View 还不够,在构建 UI 的时候,我们希望 state 改变的时候,立即 rerender 整个 View, 也就是我们经常用到的 setState().

这样就很容易理解为什么我说 React 仅仅是实现构建 UI 思想的手段,因为构建 UI 的思想总结起来就是:

  1. State 是 Reactive 的 (比如 React 的 setState)
  2. state => View (依靠 DOM diff)
  3. View 组成 Component
  4. 管理 state (依靠第三方的 state manager)

无论是 React 还是 Vue, 大抵都是这样的思想。Vue 1 还不完全是,Vue 2 就更接近了,只是后者写法既可以写得像 template, 又可以写直接写 vdom.

而开发者常常感到困难的地方实际上是上面的第 4 点——管理 state. 写 React 写得痛苦,大部分原因是用把 library 当成 framework 去用,把处理 state 的逻辑瞎写到 View 层中去,也就是所谓的 Dump Component.

改变 state 是 side effect, 我们应该把它从 View 层中分离出去。我多次提到,View 层真正要做的,仅仅是根据 state 返回对应的 View. state 的变化逻辑,应该在给 state manager 库去做,例如 Redux, Mobx. 下面我用 Mobx 作为例子:

https://jsbin.com/fumerup/edit?js,output

如果没有接触过 Mobx 不用慌张,只需要知道,Mobx 的 Observable 变化时,被 observer 包装的 React 组件会重新渲染。使用 state manager, 明显地分离了 View 和 side-effect:

测试这样的程序,首先为 side effect 的逻辑做测试,再为 View 做测试。View 的测试在这里就十分简单了,给他传一个 store 实例,借助 enzyme 之类的 testing utilities 预言不同的 action 得到的返回 View.

React 是 Reactive Programming 在 Web User Interface 上实现的手段,它只不过是一个库,提供了reactive render, component system 和降低开销的 DOM diff 算法. 把 React 换掉,只要不是手动操作 DOM, 其它的框架也不过大同小异。重要的是理解它背后的思想。说到底,前端开发在解决什么问题,用什么样的方式解决问题,在使用任何框架和库之前先把这两个问题思考明白,就不会认为前端难学了。

2016 年终总结

博客篇

开篇讲讲这个博客这一年的「成绩」:

访问量

因为今年年中进行过博客迁移,从 github pages 迁到了阿里云,还换了整个 blog platform. 所以新旧博客的统计要结合起来看。

旧博客统计:

统计

新博客统计:

统计

总计: PV 49,406,UV 15,800

访问设备

iPhone 用户仍然排第一,第二是小米。

来源浏览器

文章排行

上半年一个 Vue 和 Webpack 系列访问量比较大。下半年给自己的博客定位不是单纯的技术博客,访问量比较大的是关于退学的两篇文章。

坚持做独立博客很难,坚持写博客更难。我没有开公众号,因为开公众号会让我感到有压力,更会让我「为了推送而写文章」。也是因为赞同陈皓的观点

技术篇

今年因为工作原因,把大部分的精力花在了 React 上,Vue 反倒没什么机会写了。写 React 的过程中对 Thinking in React 有了不同的看法。我从前不喜欢 JSX, 不喜欢 setState, 但是慢慢地开始思考 Functional Programming, Reactive Programming. 后来发现,React 是不是 React 已经不重要了,因为 React 只是实现它思想的手段,更重要的是在其背后的,UI Development 的观念 —— (state) => View。无论如何争论 Functional Programming,它的的确确改变了我对程序开发的想法,我开始追求 Pure Function,开始讲究函数的 side-effect. 我想下一年我还会对 FRP 做更深入的研究。关于这方面的感悟,我会单独写成文章。

比较快乐的是在一些项目里使用了 TypeScript. 自己也写了文章,录了视频, 来表达我对这个语言的看法。

这一年参加了两个 Talk, 一个是珠三角技术沙龙,讲的是 Vue 和 Native. 另一个是 Node 地下铁,讲了 TypeScript. 关于技术分享,我在知乎有一篇回答 讲了我对国内技术分享会议的看法。希望我自己在接下来的一年能有更大的长进,然后用自己的行动去告诉大家技术分享应该怎么做。

比较遗憾的是工作之后减少了写开源项目的时间,今年对开源社区最大的贡献就只有给一本TypeScript 书 贡献了些内容。

工作篇

Gitlab Summary

从 3 月份入职,完成了一个项目的重构,帮助了一些还有些迷茫的朋友,用自己的热情感染了团队的技术氛围,是在这一年工作上让我自己感到满足的事情。

读书篇

Just for Fun: The Story of an Accidental Revolutionary

Linux 创始人 Linus 的自传,记录了 Linus 的少年时期和 Linux 的诞生,之中还夹杂一些对开放源代码的观念。读完以后很受鼓舞,能像 Linus 一样是我做软件开发的终极目标。

Soft Skills: The software developer's life manual

中文名叫《软技能:代码之外的生存指南》。我很不喜欢这类教别人做人的书,但是受人推荐,还是读完了。

书里有几个章节我印象比较深刻。比如谈到大公司、中等规模公司、创业公司之前的区别:

在大公司工作令人沮丧,因为他们感到他们个人的贡献无足轻重

为大公司工作的一个显而易见的事情就是成长机会

结合自身条件和自己喜欢的工作环境进行职业选择,是需要深思熟虑的事情。

书中还谈了个人品牌的打造,学历问题等等。都值得一读。

The Art of UNIX Programming

中文名是《UNIX 编程艺术》。万幸我用 macOS 也算是 UNIX 环境的重度用户,所以在读这本书的时候不会感到吃力。这本书实际上和 UNIX 源代码没有什么关系,讲的是 UNIX 下的程序(比如 grep),这些程序的设计哲学让我对软件有了新的思考。

编写复杂软件又不至于把自己搞混乱的方法是降低软件整体的复杂度。软件本身的复杂度不会因为实现方式和代码组织的优秀而降低,但是这能使整体复杂度降低。降低整体复杂度的方法是用清晰的接口把复杂的软件分解成若干个简单的模块

每把剃刀都自有其哲学,更何况是软件开发呢。即使是开发一个小函数,它的输入和输出也是需要讲究的。

Becomming Steve Jobs

我没有读《乔布斯传》,而是选了这本《成为乔布斯》,是听说这本传记记录的乔布斯要更真实。事实上通过很多途径都已经宏观上对乔布斯有了很大程度上的了解,所以读传记的时候已经没有对某些事件产生触动。反而触动我的是一些小细节上,比如书中提到乔布斯父亲的话:

对于一个橱柜来说,别人看不到的底面与表面的抛光一样重要;对于一辆雪佛兰汽车来说,别人看不到的刹车片和汽车的油漆一样重要。

容忍与自由

我求学时期读了很多民国作家的书,唯独胡适先生的书读得不多。今年读了这本文集,意犹未尽,还想读他的《中国的哲学》,但看来要等下一年了。

Elon Musk

硅谷「钢铁侠」Elon Musk 的传记。十分羡慕财富自由又有想法的人。

A brief history of humankind

推荐这本有趣的《人类简史》,看人类是如何从原始人进化过来的。

游戏篇

今年买了 PS4, 人生第一台游戏机。玩了几款大作,《最后的生还者》、《神秘海域》、《GTA5》、《看门狗》。印象较深的是《最后的生还者》,没有 PS4 的也建议视频通关。

下半年换了 Macbook Pro 也入了 Steam 的坑,Steam 的游戏和 PS4 比简直就是白菜价。沉迷了一段时间 Don't Starve. 通关了 Firewatch.

自我总结

这一年的成长比较横向,不仅仅在技术,还学习了音乐,健身等等。遗憾是改不掉一直以来喜欢「急」的缺点,急于完成事情,不多加考虑。甚至容易使自己在快要完成一件事情的时候,容易由于太急,最后烂尾,比如

我看牙医的经历

今年断断续续从 3 月到 11 月我都在牙诊接受牙齿修复,直到今天,修复已经完全做好了。在这个过程中我对牙科有了全新的认识,于是我希望通过分享我这一段经历,让更多人重视牙齿健康。

如果不想看长文,可以直接跳到本文末尾的 TLDR.

我的牙齿属于牙釉质发育不全,虽然从刚换牙的时候我就发现我的牙和别人不一样,但还是在今年 3 月初才准确知道的。那时我刚好智齿在发炎,去医院打算拔掉,一个医生看了我的牙,说你的牙齿烂得很厉害,釉质基本没了,要做牙冠保护,不然再过几年牙就没了。

听了以后我心情复杂,我说我还这么年轻啊。医生说,你的牙用 1 年等于正常人用 5 年的啊。

做牙冠的费用不少,我就回家跟家人说了一下情况,然后又去附近一家牙诊所咨询意见。牙诊所的牙医一看,问我是哪里人,因为他不确定这是釉质发育不全还是氟斑牙,而广东这边的水质一般不会出现氟斑牙的情况。我说我是广东中山的,于是他就基本确定是釉质发育不全了。

他再仔细看我牙齿的情况,最后说了一句:你还是去省口腔医院吧,你这些牙的磨损程度太高,咬合又低,我们这里的设备没有办法做到很高,你给钱我们也不做。

我开始感到情况似乎越来越悲观,后来我姐姐给我推荐了广州某口腔诊所的熊医生,于是我到了广州找他。

熊医生看了我的牙以后,说磨损的确挺严重,但是牙冠还是能做,只不过要做很多颗而已。他和另外几位医生会给我做一个方案。

到底什么是釉质发育不全,首先要清楚一只牙齿的结构:

牙齿结构

这是我随手画的一张牙齿抽象图。一颗牙的最外层就是所说的釉质(也称「珐琅质」),而黄色这一层就是牙本质。牙釉质是牙齿最坚硬的部分,也是哺乳动物体内最坚硬的组织,用于咬碎食物,以及保护牙本质。他是不可再生的,所以蛀掉的牙一定不会自愈。牙本质是黄色的,而釉质是白色半透明的,因此正常人最自然的牙齿是偏黄色的。而当釉质被磨损越严重时,牙本质几乎被暴露,才会越来越显非正常的黄。

牙齿磨损

这是我在后来修复过程中做的下排牙模,可以明显看出,发育不全的釉质比较脆弱,两边的后牙磨损严重,甚至快要磨到底了。在咬合的时候,基本只能靠磨损出来的几个尖尖的点来咬食物。

熊医生给我的方案是,8 颗后牙做根管治疗然后做牙冠,顺便在做牙冠的过程中重整咬合。

为什么需要重整咬合呢?因为我的后牙几乎完全没有咬合。

修复前和修复后的侧面对比

这是修复前和修复后的侧面对比,可以看出修复前的后牙基本没咬合。

于是整个修复流程开始。而根管治疗是整个过程中最漫长和痛苦的步骤。即使在手术过程中会注射麻药,疼痛还是存在。

根管治疗流程图

上面维基百科上的根管治疗流程图。简单来说,就是从牙齿中间钻开,然后人为把牙神经杀死。牙神经被杀死后,牙齿就不会再有感觉。一般来说,根管治疗都是用于治疗牙髓坏死或牙根感染。而我的情况是做牙冠需要磨一部分牙,如果不把牙神经杀死,磨牙磨到牙本质的疼痛是无法忍受的。

杀死牙神经后,还需要等待一个星期,然后填充药物,最后再补牙。

经过几乎两个月的时间,两边的根管治疗完全做好了。由于经济问题,我拖了很长时间才开始做牙冠。

做牙冠首先要把牙齿磨小一圈,留位置套上新牙冠。磨小以后,咬硅胶、做牙模,然后发给牙冠制造厂家做。这个过程大概需要一周,也就是说,我两边分开做,起码有两周时间需要单边咀嚼。

修复前、过程中、修复后的状态

在修复右边时,有一段小插曲。我的右下智齿阻生并且已经萌出,影响到做牙冠的位置,所以要先把智齿拔掉。拔掉智齿的过程用了两分钟。

智齿

TLDR

在做牙齿修复的这段时间,我向牙医学习了很多关于牙齿的常识。虽然我的牙齿问题并非由于后天影响,但偶尔会看见一些人来做牙齿治疗,有很多都是没有太把问题当一回事,到问题严重才会来看牙医。都以为牙疼忍忍就过去了,然而等到再疼下去,等到把牙髓坏死的时候才来找医生,也就只能做根管治疗了。

也有很多人,蛀了牙也不补,最后疼了,有的人也不管,眼看整个牙都要蛀掉了,来看牙医,也就只能种牙了。

大多数人都不知道,补牙能够解决问题是有多幸福,然而大多数人都只会拖着。没有人想要花钱买难受,做根管治疗又贵又疼,真是心疼肉也疼。有好几次我在做根管的过程中都要疼得叫出来,但是又有什么办法呢,这是发育不良,没有办法改变。但是在读这篇文章的各位,你们都要比我幸运得多,你们天生就有一排好牙,希望你们不要浪费。在这里我有一些微小的建议:

  • 如果你从没有洗过牙,打开团购 app, 找离你最近的牙诊所,预约一个洗牙,不贵,也不疼。然后坚持半年或一年洗一次。
  • 把手动牙刷换成电动牙刷,有条件的话,帮你的父母也换上。然后上网找刷牙的视频,学会正确的刷牙方法。
  • 如果你有牙已经蛀了,不要等,立刻去补。
  • 如果你很穷,又不想遵循以上建议的话。呵呵。

你根本用不着退学

近一年来我收到过很多邮件,说看了我的经历和文章,也想退学了。大意都是「看了你的经历,我觉得我和你一样,所以我也想退学了」,要我给他们一些意见。

我的意见向来是都是劝回学校好好读书,好好拿个学位证,反对一切没有铺好后路的退学行为。直到越来越多这样的人来找我问这样的意见,我意识到我应该认真的写一写我对退学的看法。

选择退学代表放弃学位(也就是所谓的文凭),这是一个沉重的代价,因为学历很重要。

学历像是人的脸。对于样貌,大家都说,「长得好看没有用,内在美最重要」。乍听上去是这么一回事,但是如果长得不好看,茫茫人海里,会有多大的概率有人主动了解你的内在呢。不是说内在没有样貌有用,而是让人愿意了解内在的成本太高了,比有样貌的成本高得多,要付出的努力也要多得多。

很多人看不起别人整容,然而读书(Schooling)其实就是一种整容。谁都会有这种感觉:这个人读重点大学,实际上也没比我能力高多少,但是他处处受欢迎,拿到的 offer 也多。你觉得这样不公平,其实这公平得很。别人为了这个学历付出了多少的努力(不管这种努力在你眼里看来有没有「意义」),而你又为了做好不拿文凭的准备做过多少努力呢?

我不知道别的行业对文凭的重视程度,所以我拿我的本行——软件开发来说。如果打算放弃学位而提前从事软件开发行业,请先问自己一个问题:

你用什么来让别人相信你会写代码?

如果你没有学历,你就需要靠经验来证明自己的能力。

然而大多数来邮件求建议的朋友,大多没有什么经验,却又声称「和你的经历相似」、「和你一样」。其实我们完全不一样,我在退学前就已经写了 7 年代码。

不是说一定要有这么长时间的经验才能离开学校,而是 一定要已经拥有解决问题的能力的时候,才有资格考虑退学这件事。我常常拿我朋友 Drakeet 给身边的人做例子,他是大二退学的,退学时虽然写代码才两年,但是他当时的能力就已经足以独当一面了。

曾经有一位朋友的朋友问我关于退学的意见,他说觉得学历不重要。我问他你会做什么,他说「虽然我现在能力很弱,但是我学习能力强,想进一些大公司跟着大牛学习,等技术可以了,就可以弥补学历的缺陷。」到底是哪来的勇气让他认为大公司的大牛会因为「学习能力强」愿意让他跟着学习。

我不知道为什么那些还没有什么能力的人会认为学校不能教会他们什么,我想大多是人云亦云。他们往往把我退学的原因归咎于「大学教育的落后」(当然这确实是其中一个原因),然后拿我的例子来证明他们的观点,却没有认真思考过我为什么认为(我所在的)大学落后,那是因为学校在教的大多数内容我在好几年前已经滚瓜烂熟了。如果一个人连这些学校在教的基础知识都不会,又何来的底气声称「学校不能教会他们什么」。

当你有信心对老师说「你下来吧,让我来教」的时候,你才需要考虑退学这件事。

大学是一个很好的避风港,能有如此长的一段时间可以没有顾虑地学习自己想学的知识在一生中难能可贵。想研究 V8 内核就去研究,想了解机器学习就找机器学习相关的书慢慢读。

我知道很多打算退学的人心里都盘算着退学后在家自学一两年,然后出去找工作。我不看好这种规划,因为风险大,并且在家自学不见得比在学校自学好到哪里去。除非在这一两年里能在开源社区带来一些什么,否则到最后不会是你理想中的结果。

马克吐温说过一句话:"I have never let my schooling interfere with my education." 在上大学之前,我学习编程的时间很少。每周只能回家一次,用电脑的时间又不多。拿学杂费的找零买些书,上课看,晚修看,把草稿纸当成编辑器,周末回家用电脑实践。我想所有人的大学比那时的我自学条件要好得多,又有什么理由放弃这种条件呢。

希望所有打算退学的朋友能深思熟虑,退学不是一件好事也不是一件坏事,它只是一种选择,取决于哪种选择更适合自己的处境。

虽然我从来没有后悔自己做出的选择,但是我总会羡慕那些有机会读好学校、受好教育的朋友。我是个很爱学习的人,我也想在大学学编译原理,线性代数,概率论,但我这个人比较蠢,没有办法同时兼顾我喜欢的和我不喜欢的课程,又疲于应付功课,所以才会选择退出,然后付出比别人更大的努力,跑在别人的前面。

比我聪明的人很多,而这些人,根本用不着退学。

离开大学这一年

写这篇文章是想说说退学一年后的一些想法。想看技术总结请移步。如果不想看这么多的文字,可以直接拉到最下面,看写在最后的内容。

一年可以发生很多事。

回想起来,大一的一整年我旷了很多课,待在宿舍看书和写代码,周末总是往广州市区跑,参加各种各样的社区活动和聚会。学院规定旷课 20 课时以上就要处分,我很不出意外地被处分了。

签处分的时候,辅导员告诉我,如果这个处分不撤销,是拿不到学位证的。如果想要撤销,要保证在大二整个学年不旷课,也不挂科。我说,那我是完全没戏了。

辅导员让我好好考虑一下。我表达出了离开学校的意愿,她说好好想想,暑假和家人商量出一个结果,开学再找她。

其实我已经想了很久。在学校的一年里,我过得极其难受。我必须浪费很多很多的时间,去上一些只是听上去很有意义的课程,和那些在课室里只会玩手机和睡觉的同学处在同一个课室里,连续待一个半小时。 而我最这个人痛恨的事情,就是别人浪费我的时间。

我父母是很传统和保守的人,我不敢在他们面前提关于离开学校的事情。于是在那个暑假,我度过了人生中最抑郁的两个月。

大二开学,辅导员知道我并没有和家人商量,就打给了我家人。本以为上了大学,就不会再有「打电话通知家人」的破事。

家里人和我想象中不一样,他们很冷静。周末回家,我说出了我的意愿,他们表示让我自己做决定。他们说,你是个成年人了,读大学是自己的事情,如果你要读,我们愿意继续花钱供你读,如果不想读,你自己要为一切后果负责。

于是,在一年前的今天,我回到了学校,找辅导员签下了休学一年的协议。辅导员问我会不会后悔,我说不会。她说那你要加油,希望你能成功。我很开心,因为她是笑着说的。我辅导员笑的时候很漂亮。

和学校没有任何关系以后,我租了一个算是能住的房子,正式开始了职业生涯。

我的第一份工作入职的是一家创业公司 (Kiwi Inc.),是暑假的时候就已经入职的。暑假前我在某群表示想在广州找一份能在暑期做的工作,李秉骏 很快就跟我联系。我从学校坐了趟车到他的公司,一番愉快地交流之后,工作就落定了。

我在 Kiwi Inc. 负责的是前端开发。因为整个开发团队只有我一个前端,而公司主要的产品又有很大的比重在前端,所以进去以后,我过得很充实,因为自己 do the things that matter. 团队因为人不多,大家都很融洽。

我后来听很多人对应届生的「忠告」,都说千万不要去创业公司,大多数创业公司都是坑人的。有条件的,都争取去去大厂,不要在创业公司浪费时间。

对此我保持不同的意见。我非常庆幸自己在创业公司待过(当然也是因为我待的公司不坑人)。在创业公司,我们可以自己选择用最好的工具,可以用 Slack, Trello 以及第三方的 PaaS 等等等等。正因为我们能自由地选择最好的工具,我们可以亲身感受和思考这些产品本身。这些产品优秀的地方会渐渐地成为自己做产品时对产品的品位和追求。并且在创业公司,我们没有无意义的会议,可以把时间用在干实事本身。关键是,我们每个人在公司里,都能感受到自己是极其重要的一员

在 Kiwi Inc. 写前端的过程,是我自己建立前端工程概念的过程。从开发到测试再到部署,从代码规范到构建,都希望把软件工程的思想带到前端中去。虽然现在看来习以为常,但是对于当时只做过自己的 side project 的我来说,这是极大的经验和收获。

在 Kiwi Inc. 待了半年后,出于对自己更高的要求,我离开了这个靠谱的创业团队。

崇尚工程师文化的我一直认为自己会和国内的大厂无缘,大厂不会选择我,我也不会选择大厂。然而我却在农历新年后通过了阿里巴巴移动事业群 (也就是 UC) 的面试。在 2016 年的 3 月加入了阿里。

大公司和创业公司不同,一个简单的问题会牵扯到不同的组,比如一个字段出了问题,要去追踪到底是架构组的问题还是算法组的问题。在大公司,每一个需求要有排期,版本上线要提测,要面对大量用户的反馈。

其实离开了学校开始工作,自己的时间就很少了。一本不厚的《设计模式》,原本可以花两三天粗略地读完,现在会有不知道什么时候才能读完的感觉。这种情况下,很容易使自己在选择读一本书前出现「读这本书对我的工作有实际帮助吗」这种错误的考量。

不够用的时间常常使我处于不安、烦躁和轻度抑郁中,因为我害怕被别人追上。每个认识我的人无论出于真心还是客套都会说我「年轻有为」,但是没有人知道这是一场看不到终点的长跑,我的特立独行让我必须跑得比别人快得多,才能弥补我们之间的差距(学历、智商等等)。这使我感到害怕,尤其是同龄人渐渐都出来实习工作的时候。但又没有任何办法。

工作以后有了稳定并且还不算少的收入,我能用来买自己喜欢的数码产品,也花了不少钱在自己的身体上 (比如健身和吃)。

我不存钱,我希望在我还年轻的时候,在我不需要供这个供那个的年龄,能没有顾虑地花钱让自己快乐。快乐太难又太容易错过了。就像小时候想有一台 Gameboy 但不得愿,现在即使可以没有压力地买下一台 PS4, 也不见得有多快乐。

写在最后

我有很多还在读书的朋友,我们出去聚会聊天的时候,我都会看得出他们的迷茫和无奈。这些迷茫和无奈可能很大程度上来源于教育体制的缺陷,但是,在这个时代,学习早已不仅仅只局限于学校,它在任何一个你可以掌控的角落。它在互联网、在每一本有价值的书籍上。如果学校没有满足你,就去这些地方找。珍惜时间,总有一天会找到它,并能让你过上你希望过上的日子。如果迷茫是出于你的懒惰,就请不要埋怨教育,也不要埋怨任何体制、任何人。

我还有很多已经在工作的朋友,尤其是和我一样在写着代码的朋友。我没有特别要说的,我想说,嘿,我来了。

21 岁我想说的

在我很小的时候,在香港电视剧的影响下,我最初的理想是当一名警察。但是很不幸,当我向着全班描述这个理想的时候,班上的同学都哈哈大笑,甚至老师也对我说,你这样的身材,能打得过小偷么。

于是这个理想再也没有跟任何人提起。但是我很俗套地感谢那位老师,因为从那以后我决定改变我的理想,成为一名科学家。

之后我读过很多科普书籍,奇怪的是,这种给青少年看的科普书,大部分的内容都是有关化学的。以至于我有很长一段时间都在意淫我成为化学家的情景。

六年级转学后,我遇到一位朋友,他给我展示了他自己写的一个博客网站。当时我很吃惊,于是和他一起自学编程。我没有零花钱,只能用学杂费的一些找零买了一本 20 块钱的 中专教材《c语言程序设计》。

和老罗一样,从那时起我就有种被上帝选中的感觉,我觉得我这辈子就是搞这个的。我从意淫自己成为化学家,变成了意淫自己天天写代码。

在这个过程中,我遭受了很多不被人理解和看好的压力,甚至被嘲笑。念高中的时候,由于在一篇周记里表达了自己的理想主义,晚修就被班主任拖出去批判了一番。

直到这一年,经历了足足 8 年,我离开了学校,真正成为了一名软件工程师,曾经意淫的场景变成了现实,和『科学家』这个理想也算占了点边。理想主义万岁。

21 岁,我希望我可以继续写代码,离自己的终极目标更加接近。而那些曾经看不起我和嘲笑过我的人,我只想说,你们这帮孙子,我去你妈的。

我学五笔的经历

最近总是看到社区有人讨论输入法,有朋友安利双拼,有人想 开始学五笔,看了一些回帖,作为一个打了 6,7 年五笔的年轻人,我很有感触,所以就写写我学五笔的经历,给大家借鉴借鉴。

我第一次接触五笔是五年级的电脑课,那时鼠标还是有滚球的。当时电脑老师知道我很喜欢电脑,就告诉我学校准备搞一个电脑打字比赛,希望我学五笔去参赛。我那时只会打全拼,压根没有想过学五笔,但是老师告诉我,如果参加,午睡的时间可以到机房练习。听到这个『福利』之后我立马答应参赛。

之后的一个星期的午睡时间我都去机房练打字,不过我的电脑老师并没有手把手教学,而是帮我装了个叫 "wt" 的软件,是个 DOS 程序,专门用来练五笔的。(我一直没搞懂它为什么叫做 wt,最近又搜了一下,这货中文名叫『明伦五笔』)电脑老师就让我按照这个软件的进度去练。然后又给我一张字根口诀表,什么『王旁青头戈五一』,让我背一下。

刚开始我练的是很认真的,看着一排字,对照着键码提示敲。老师也没管我,自己在讲台玩电脑。于是几天以后我也就没怎么练五笔了,开始玩金山打字通,玩打字游戏,打英文。

这就是我第一次接触五笔,没有背字根,只勉强记住了一些一级简码,能打出来的词语只有『中国人』(k l w)。这段时间最大的收获其实是学会了盲打,靠金山打字通掌握了正确的打字指法。

第二次接触是我姐买了一台步步高电子词典,这部词典相当牛逼,可以用数据线连到电脑,然后安装很多游戏,我经常上厕所的时候拿去玩。后来我姐就生气了,一怒之下把游戏全删了。当时我很失落,但是我还是得上厕所,还是得拿电子词典去玩。我把所有功能都按了一遍,发现没什么好玩的,唯一算得上具有人机交互功能的程序就只有一个练五笔程序。

我就开始玩这个,但是我几乎什么字都不会,我选了四级全码的练习。四级全码的意思是同一个字母打四次就会出现的字。程序是这样的:右上角出现一个字,我得输入四个字母然后按确认看对不对。我一开始什么都不会,只会一级简码。所以我就开始一直蒙,蒙到最后我就把所有全码几乎都记住了。我现在还记得我记住的第一个全码是『火』,四个 o。

第三次是六年级毕业后的暑假,那时我的上网时间是被限制的。那段时间几乎每天在我爸上班的地方,有一台没有联网的电脑,除了 Microsoft Office, 就只有金山打字通。那时拼音输入法没有现在智能,会五笔仍然是招聘的一个加分项,所以我爸妈都希望我们学五笔。我姐是打五笔的,打字非常快,虽然和我当时打拼音的『敲打速度』差不多,但是五笔重码率非常低,不需要像拼音那样,打出来,还得看看是哪个选项。加上那时我也闷得慌,就天天用金山打字通练五笔。

很多人说学五笔得背字根表,记口诀。我从小就讨厌背诵,所以一直没有去背那个鬼东西,导致我一直非常容易忘。后来我『牺牲』了自己一些有限的上网时间,搜了一下字根排布有没有什么规律。

然后我发现果然是有的,五笔字根在键位上的排布是有规律的。

五笔的规律

首先来看一下整个字根的分布图:

五笔字根分布图

对于新手来说肯定是非常吓人的,这也是很少人学五笔的原因。但是其实它是有规律的。

第一是整体上的划分,整个布局总共只有五个区,分别是撇(QWERT)、点(YUIOP)、横(ASDFG)、竖(HJKL)和折勾(XCVBNM)。这也许就五笔叫做五笔的原因。只要记住这五个区的分布就十分容易定位某个字根所在的键位了。比如你忘了『蝗』字的『虫』字旁在哪个键位,但是你知道它肯定是在 HJKL 其中一个键上。

再来深入地看某一个区中的规律。例如『刘』立刀旁,他是竖的,它肯定在 HJKL 其中一个键位上,那到底是哪一个键呢?如果你仔细看一下,你会发现,H 是一条竖,J 是两条竖,K 是三条竖,L 是四条竖。立刀旁是两竖,那它就是在 J 上了。

当然,还有很多其它的字根并不遵循这些规律,到底是为什么,我也不知道,你得问王永民先生。打多几次,就能记住。

记着这两个规律,自己再读一遍字根图,摸索一下,再打个一星期,正常人来说,是肯定能学会的。但是熟练还是得靠多打。五笔是十分靠肌肉记忆的,打到熟练的时候,你已经不会再去这个字应该怎么拆的。比如我就不会去想『应该』这两个字怎么拆,但是我知道它肯定打 YIYY

五笔快还是拼音快?

现在社区上一般讨论的是五笔快还是拼音快。常年打拼音的人肯定会说现在的拼音输入法已经相当智能,不怎么需要去考虑重码。事实确实是这样的,以前智能 ABC 的时代,五笔是完全可以虐拼音的。但现在,起码在这个点上,五笔的优势已经不再明显了。也就是说,五笔最能体现出优势是在脱离词库的场景。这有点像 vim 和 sublime 的比较,离开了 GUI,vim 的优势就迅速体现出来了。

根据我多年的经验,实际上,拼音不是最快的,五笔也不是,最快的输入方式是拼音五笔混输。说实话,打了这么多年五笔,我仍然不清楚『尴尬』两个字怎么拆,当我打到这两个字的时候,我只要输入"ganga",就立刻打出来,这是最好的,非常舒服和自然。这一段文字,除了尴尬两个字,其它全是五笔打出来的,难拆的字一点也没影响到我的打字速度。所以,还是那句话,最快的输入方式是拼音和五笔混输。

那我现在学不学五笔好?

无论在什么情况,能学习的东西,学了肯定是好的,不管它是不是『有用』的。就像 Steve Jobs 说的,connecting the dot. 有没有必要学,花多少时间去学,那是你自己要考虑的事情,作为旁人,没有办法给意见。只能说的是,学了以后不会在短时间内给你带来什么,不学你也不会失去什么

人越长大,就越少机会做些能让自己感到开心的『无用』的事情了。

最后推荐个输入法?

关于输入法,我在 Mac 上用的是 QQ 五笔,基础词库很足,再加上流行语词库,就十分够用了。打五笔,词库只有嫌多,没有嫌少的。至于手机,我在手机是不打五笔的,因为没有手感。之前已经说了,打五笔到后期基本是靠肌肉记忆的,也就是说,离开了实体键盘,很难有同样流畅的手感。我在手机上比较奇葩,用的是笔划输入,是学我姐的。最近也在玩 Google 的划动输入,所以偶尔也打打拼音。

写给即将学习编程的大学新生

写下这篇文章,是由于我想和即将进入大学有意学习编程的同学谈谈关于编程的事,因为你可能需要一些建议,来步入这个新的阶段。

编程是非常有趣的一件事情,学会编程就像成为魔术师,你可以创造任何你想创造的东西。在我出生到现在的这 20 年里,除了文学,没有比编程更吸引我的活动。就像 Zed Shaw 所说的,『编程作为一项智力活动,是唯一一种能让你创建交互式艺术的艺术形式。你可以创建项目让别人使用,而且你可以间接地和使用者沟通。没有其他的艺术形式能做到如此程度的交互性。电影领着观众走向一个方向,绘画是不会动的。而代码却是双向互动的。』

一些困惑

但是当你开始接触(我所在的)大学的计算机相关课程,你会发现,所谓的编程学习,无非是老师教一章内容,要求你用学到的语法去解一些数学题。于是很多人选择放弃,认为编程乏味无趣,用很长的课时学习,却只能写出简单的四则运算,或者解数学题。

然而这并非编程的全部,因为这恰恰能生动地表明编程语言只不过是一种工具,你可以用它来解数学题,也能做其它更有趣、更实用的程序。

在(我所在的)大学,计算机相关课程教 C/C++。有一次,一位大三的朋友问我说,『我学 C 学了一段时间,为什么我还是感觉很没用?』我是这样回答他的:

你学 C 学了一段时间感觉没有用处并非你个人的问题,我无法跟你解释为什么,不过我可以告诉你的是,Linus Torvalds 用 C 写了 Linux 和 Git; Antirez 用 C 写了数据库 Redis. 你可能连 Git 是什么都不知道,如何用 C 写出这种水平的程序呢?

所以,当你学习 C/C++ 基础后进入困惑期时,不要因为用它写不出实用的程序而止步不前,而应该开始接触其它的编程语言。当你所学和所做的东西越来越多的时候,你慢慢就会发现,编程语言只不过是一种工具,重要的在于你想做什么,用什么语言合适去做(华软的校内游戏直播网站 SISE Game 就是例子,网站的后端用 Ruby on Rails 写,但实时聊天是用 Node.js (JavaScript) ,因为相对于 Ruby, Node.js 更加适合处理 WebSocket)。 你之所以感觉 C/C++ 没用,是因为你暂时还没有足够的眼界和能力去使用他去进行创造

应该去学什么?我的建议是一门严谨的强类型语言(如 Java)和一门愉快的脚本语言(如 JavaScript, Ruby, Python)。当然了,如果你想学一些能立竿见影的技能,你也可以写写前端(HTML, CSS)。

So, How?

对于应该如何去学习,我不打算在这里论述,我只想列出一些有用的建议:

  1. 无论你要学什么,你读的第一篇文章应该是《提问的智慧》

  2. 用 Google 搜索技术相关的信息

  3. 购买付费可靠的科学上网服务,不要吝啬一顿饭的钱。

  4. 不要害怕英文,英文非常重要。

几乎所有最新的技术在刚出来的时候只有英文文档,优质的 Tutorial 大多数也是英文的。

  1. 参与一些质量高的社区,关注一些值得关注的人。

这一点很重要。学校所教的内容是陈旧、过时的(虽然这也许不是学校的错),你必须和社区保持同步,不断接触和学习新的技术。AngularJS 发布至今都有 6 年了,但在我们学校仍然在教 ExtJS,很多学生甚至毕业出来找工作的时候,连 AngularJS 都没听说过。

国内的 V2EX 和国外的 Hackernews 都是不错的社区。

一些现状

你并不能指望大学的课程或者大学老师能给你带来什么,一切都应该靠你自己。无论在多优秀或者多差劲的学校,学校本身能影响你的是辅助性的,而不是决定性的。在我的学校,几乎都在打游戏和应付考试,据我所知,即使是重点大学,这样的学生也大有人在,我渐渐明白,『你今后人生的艰难,恰恰不是因为你没能考上一个满意的学校,而是在这所学校里,在一个能让自己自由充分成长的黄金四年里,把自己给荒废了』(采铜的回答)。

为未来铺路

如果你以后有意从事编程的相关工作,你应该要知道,在找工作时和其它竞争者拉开差距的,并非你从哪里毕业,在学校当什么干部,而是你有足够的能力,并且能清楚地让别人看到你的能力。

让别人看到你能力的方法是开源社区和博客,所以我建议你在学习的过程中,多为开源做贡献,甚至可以维护一个自己的开源项目。另外还可以把自己的所学记录和总结到独立博客中。一个好的 Github Account 和好的独立博客是一份最好的简历。也许你也只有在大学这自由的四年才能有时间做这两件事情。

最后

我在广州大学华软软件学院读书已经有一年了,这一年里我遇到过许多对现在对未来感到迷茫的朋友,他们对教育抱有希望,对知识怀有渴望。我曾经试图做一些事情去改变现状,但是效果都不大,该混日子的还是继续混日子,到毕业后才开始抱怨就业难。我呸,互联网行业都缺人缺到什么地步了你跟我说就业难?

不过,只要我还没被学校开除,我还是会尽我所能地为迷茫的朋友做一些事情。至于做些什么,我还没想好,我不知道应该怎么做,如果你有好的建议,欢迎联系我。

如果你看完这篇文章,还有问题可以联系我的 Email: randypriv@gmail.com,只要你读过《提问的智慧》,问的问题经过自己思考,我都会尽快地详细地回复。

『你会编程。他们不会。这真他妈的酷。』

2014 年终总结

2014 年因为有高考所以比往年过得快了些,大学的一个学期转眼也过了。有时候甚至缓不过神来,甚至让我常常有种错觉,2014 它早就过了,但 2015 它迟迟未来。

今年的 1 月我还在为化学烦恼,今年的 12 月现在的我已经过上了那时梦寐以求的自由的生活。但是生活哪有什么所谓的自由,高考只不过给我开了一个闸,从一个小笼子,滚进了一个更大的笼子而已。而这个更大的笼子比以前按部就班的生活更让我感到害怕,就像《海上钢琴师》里 1900 放弃离开轮船后 对 Max 说的一样,让我害怕的不是我看到的东西,而是我所无法看到的东西,这里什么都有,可是唯独没有尽头。

还好上了大学以后,能遇到几个让我又有了寄托的朋友,我和他们一起做事,非常地快乐。

技术方面,哈哈,就不说了,我仍然迫切的希望能用技术给这个社会带来一点好的改变。

遗憾的事是好像很难再找到能聊聊天的朋友,而且好像变得不太爱说话了,所以有时候我还是挺怀念高一时候的我。最遗憾的事是 Google 还没有解封,当我以为『她』正在慢慢变好的时候,却再一次让我失望了。

新年愿望是,1,所有人都好 2,能继续写代码 3,生活的这片土地能再进步一些,我知道这的确需要一个过程,但我想自己,和我身边的人,还有未来的儿子和女儿,都能在这里活得快乐些。

最后附上 1900 对 Max 说的那段话,说的真好:

All that city. You just couldn't see the end to it. The end? Please? You please just show me where it ends? It was all very fine on that gangway. And I was grand too, in my overcoat. I cut quite a figure. And I was getting off. Guaranteed. There was no problem. It wasn't what I saw that stopped me, Max. It was what I didn't see. You understand that? What I didn't see. In all that sprawling city there was everything except an end. There was no end. What I did not see was where the whole thing came to an end. The end of the world...

新 MacBook 的设置和软件

趁着国补,把手头用了 5 年的 MacBook Air M1 换成了 MacBook Pro 14 寸 M4. 顺便手动重新配置新电脑,在此记录一下每次设置新电脑时我会做的一些设置和必装的软件。

设置

把点按切换成轻点:

我不喜欢用触摸板点按拖动来移动窗口,觉得有点费力,所以我会调整为三指拖移。旧版本的 OS X 可以直接在系统设置中调整,新版本的 macOS 竟然把它归类到辅助功能里了:

把 Control 键和 Caps Lock 键互换。因为我需要频繁使用 Control 键,Caps Lock 的位置是最合适的,这也是 HHKB 的默认布局:

取消 Spotlight 的所有索引,因为我用 Raycast, Spotlight 的索引会浪费计算资源:

软件

  • Microsoft Edge: 用了很多年的主力浏览器
  • 清歌五笔输入法: 最好用的五笔输入法
  • Raycast: 不必多说
  • Cursor: 目前主力 IDE, 也是 Cursor 长期的订阅用户
  • CleanShotX: 最好用的截图软件,除了截图还有很多好用的小功能(比如 OCR, 录屏)
  • 1Password: 密码管理
  • Warp: 主力 Terminal Emulator, 已经离不开通过 AI 写复杂的命令
  • iStat Menus: 在 Menu bar 显示系统信息,我用来实时看到网络传输速度和内存使用情况

2024: 在夜海中观望浪潮

高中时我读吴军的《浪潮之巅》,觉得很可惜,我出生太晚,错过了一个又一个的浪潮。我总是想,如果我身处这些浪潮,是否也会造就一点什么?我是不是也能创造一家了不起的企业而为人常道?

那时我才 17 岁,我在沙滩上观望浪潮,抱怨生不逢时。而现在,我 29 岁,已经在海中游泳多时。就在这几年,OpenAI 发布 ChatGPT 后,这片海似乎又卷起了一波巨大的浪潮,又有人站在了浪潮之巅,但我还没有。我已经无法再抱怨生不逢时,因为我已经在海里了。在很多个深夜,我差点就想承认,我这辈子只能是一个普通人,我太高估自己。但到了第二天,我又开始不服气,还是不能接受作为一个普通人活着,想做出伟大的产品,想做一家伟大的公司,想在世界上留下一点美妙的痕迹。

知道答案的人只有未来的我自己,所以每当这些迷茫时刻,我总会想起昨夜派对的《夜海的秘密》:

生命中所有来去 都是月亮的潮汐

只有闭上眼才能看到 夜海的秘密

你会看到有船从身旁驶去

船上站着来自未来的自己

他逆着霞光 看不清模样

如一尊塑像 带着先知般的哀伤

只是沉默望着你 一句未讲

你蓦然落泪 想一路追上

跟随他去往 那迢遥未知的远方 可是未来还未来 唯有此刻 是真实

昨夜派对的歌的歌词,就像诗一样,能给我一点疗愈。我习惯通过文学作品自我疗愈。别人问我,为什么还能保持阅读的习惯?我都会回答:如果你发现我某一段时间读特别多的书,那就是因为我这段时间过得非常不好,我需要从书中寻找答案。

2024,我读了 30 多本书。

因为想做一家伟大的公司,我读了柯林斯的《基业长青》和《卓越基因》,研究那些成功的企业是怎么走过来的。

因为想做出伟大的产品,我读了梁宁的《真需求》,lululemon 创始人的《lululemon 方法》,张小龙的《微信背后的产品观》。

其中一本叫做《做难而正确的事》(英文名是 Fall in Love with the Problem, Not he Solution) 让我印象很深刻,它不仅是一本给我打兴奋剂的创业鸡汤,它还教会了我,什么是真正的 Make something people want.

这些书让我对做产品有了另一个体会:如果我想做一个影响范围非常大的产品,我就需要做让人上瘾的「毒品」,要做满足用户「贪嗔痴」的产品。因为 PMF (product-market fit) 的最重要的指标是用户留存率。

恰好我读到了一本《玩家思维:游戏设计师的自我修养》,了解到了游戏让人沉迷的原理,大概也能运用到产品中去。

今年上半年我的朋友启师傅来佛山,我们在先行书店见了一面,期间我们聊到,做互联网产品有点厌倦,想做一些真正看得见摸得着的东西,能让用户在手上把玩,然后赞叹一句 wonderful 的东西,而不是拿着手机给别人看,说「你看我写的这个 App…」。

于是我想到 18 年我曾经卖过一段时间的程序员贴纸,那时我发现国内没有人在做,在国外买又非常贵,动辄一张几美元。那时我联系了一家质量非常好的厂家,做了一批质量很好的开源项目 logo 贴纸,卖得非常不错。更重要的是,中国的程序员终于可以在国内买到质量又好又便宜的贴纸。不过因为我还在上班,有点忙不过来,最后关掉了店铺。

后来我在杭州参加 AdventurX 的活动,趁着机会又见了多年的网友 Linmi, 他是 Notion 中文社区的负责人。我们也聊到了这件事。他鼓励我重新把这件事做下去。

回到家后,我重新认真思考了这件事。我确实应该重新捡起来卖贴纸这门生意,但不能像几年前一样只卖贴纸。我相信程序员(或者泛互联网从业者)需要的不止是贴纸,我们有我们独特的文化,我们有我们真正发自内心热爱的东西,我们乐意把它展示出来。

所以我决定把这件事用经营一个品牌的方式重新去做,贴纸只是其中一个产品。无论在后来是卖贴纸还是卖其它东西,我希望能聚集一群有相同文化认同的朋友,做出这个群体喜欢的产品。我想了很久品牌的名字,最终还是冒了一个风险,取了一个只有程序员会懂的名字 PaddingLeft, 这是一个 CSS 属性的名字。幸运的是,我居然能买到 padding-left.com 这个域名。

我花了些时间选择工厂,甚至跑到了工厂观察他们的机器,和工厂聊价格排期。终于重新上线了店铺。

贴纸卖了一段时间后,其实没有赚到多少钱。期间我一直在想下一个单品应该做什么。恰好那段时间我一直想给自己买一条项链,何不就做项链?

起初我跟一些人聊这个想法,他们都不认可做项链,认为程序员没有戴项链的习惯。这个观察其实不无道理,可是在做这个品牌的时候,我其中的一个想法是可以通过 PaddingLeft 的产品,让用户在使用我们的产品的同时能增加自己的时尚属性,摆脱大众对互联网工作者的刻板印象。而且项链可以做得非常精致,也适合送礼。

这个想法有点自大,但我认为值得尝试。

第一批项链做出来后,卖得也不错。

做 PaddingLeft 的这段经历,虽然也和互联网有点关系,但后来我才慢慢意识到,我在做和做软件截然不同的东西。我对供应链、库存有了新的认识,这些是在做软件的时候不需要处理的问题。和传统行业的人打交道,沟通的方式也和在互联网公司里沟通的方式截然不同。关于这些,我想在以后可以专门再写一篇和大家分享。

在开单的喜悦过后,我又开始想,这到底是一门多大规模的生意?18 年我卖贴纸,我只是把它当作赚外快来看待,如果一个月能带来 2000 块的收入,我会觉得是不错的零花钱钱。但是现在,我是在为自己的生计赚钱,我要思考的问题变成了,从商业上来讲,这是一个值 2 万人民币的 market, 还是值 5 万,还是值 10 万?如果它只是一门值 5 万的生意,我有必要做下去吗?或者说,它有可能发展成价值 20 万规模的生意吗?它是一个可以「流动」的生意吗?

就在刚刚,又有人在店里下单买了些程序员贴纸,于是我走到工作台一张一张地放进包装袋。我边发货,边盘算这单赚了多少钱,得到的数字是 8 块。我忍不住笑了。我曾经的日薪是这一单的 100 多倍。我做的这件事情,它真的有意义吗?

没有人知道答案,我只能安慰自己,这件事总得有人做,而我适合做。我了解程序员、设计师、产品经理这些人喜欢什么,因为我就是程序员。每卖出一张贴纸,我都会给对应的开源项目捐一块钱,以现在的定价,我基本是在做慈善。现在也有很多店做程序员贴纸,但没有人这么做过,因为我做过开源,我知道开源项目的窘境,这就是文化的一部份,也是做这个品牌的意义。如果你曾经买过 PaddingLeft 的贴纸,你会发现我用的自封袋和普通的透明袋有点不同,因为我用的是环保可降解的自封袋。这对用户来说没有丝毫影响,但我挺享受这种自我感动,这也是其中的意义。


直到现在,我还不知道,我还要在海里游多久,才能游到浪潮之巅。我想起曾经的一场梦,梦到在一个饭局,张一鸣就坐我旁边,我说我会有一天像你这样的。张一鸣笑了笑,我也笑了笑,然后梦就醒了。

我的这些执着在多年后是否会成为笑话?Think different 广告中的那句「只有那些疯狂到认为自己能改变世界的人,才真正改变了世界」这句话,到底是不是真的?妈妈,你 10 年前和我说的,算命的说我 30 岁会创业成功,到底是不是真的?

AI 和写作

Sam Altman 最近有一个关于 AI 和写作的访谈,让我开始思考 AI 辅助写作这个课题。

在辅助生产代码方面,Cursor 和 GitHub Copilot 已经证明了 AI 和人类在编程方面的协作非常有效。通过代码的上下文和注释,加上人类显示声明需求,AI 能很好地完成写代码任务。

我在思考在写作的时候,是否也能有一个 Copilot 辅助人类更好地写作。

大部分人不会进行深度写作,根据二八法则,80% 的人在消费 20% 的人生产的内容。这 80% 的人,偶尔进行浅度写作,例如写社交网络的动态、写用于工作中用于信息同步的文档。这类写作,我认为 AI 可以辅助的地方非常有限,用户基本不会主动为这样的场景寻找专门的写作工具或到 ChatGPT 这样的聊天窗口专门改写润色。这样的场景将来会被系统级的 AI (如 Apple Intelligent) 解决。

有的人希望写作,但不知道应该写什么。我曾经在书店看到过一本书,叫《642件可写的小事》,如标题所言,里面有 642 个开头,可以随便选一个续写。现在也有一些这样类型的 App. 这些书和 App 乍看起来很有意思,能解决不知道写什么的问题,但用起来我发现我根本不想写,因为我对里面的话题毫无兴趣。

我认为 AI 能很好地解决这个问题,因为我们可以在 prompt 中告诉 AI 自己的爱好和关注的领域,来定制化地生成自己会感兴趣的创意写作话题。

Notion 流行后,很多笔记工具开始标榜自己是「思维的工具」(Tools for thought). 其实写作这个行为本身就是「思维的工具」。Sam Altman 在访谈中说「写作是思考的外化」(writing is externalize thinking),而且「写作是模糊想法的放大器」。应该有不少人有同样的感觉,我们每天有很多想法和灵感,我们以为我们对这些想法非常清晰,但其实如果试着把想法写下来(或说出来),才发现很多思考在头脑内部其实处于非常混沌的状态。只有通过思考的外化(交流、写作),才会发现思维的漏洞、连结零散的思维。在修补思维漏洞的过程中,混沌的想法会逐渐变得健壮 (robust). 思考和灵感就像一颗种子,它非常迷人,但在播种前,它什么也不是。写作就是播种,认知是你的土壤,它会长出你意想不到的枝叶,然后又生长出新的枝叶,这就是为什么「写作是模糊想法的放大器」。

造成在头脑中的想法非常混沌的原因,是人脑无法承受过高的「认知负荷」 (John Sweller 提出的理论) 。借助书写可以把信息扩展到外部记忆,使得人在处理复杂信息时能更好地理解和思考。

很多人害怕写作,认为写作有门槛,但我不这么认为。我认为只要会思考,就会写作;只要能和人正常沟通,就会写作。写作只有把文字变成作品被品味被评价才是一种有门槛的艺术。我们受学校教育的影响,把写作完全视作一种文学艺术,才让人对写作感到害怕。却没有人教育我们,写作是一种思维的工具,写作可以帮助我们思考。我们其实可以只为自己写作,它是没有门槛的。把写作视作和自己对话,你就会发现写作也就是那么一回事儿。和自己对话需要遣詞造句吗?不需要。需要遵循什么文章结构吗?不需要。

把写作看作是思维的工具后,我对 AI 辅助写作有了全新的思考角度。从表面看,我们似乎需要的是辅助我写作的 Copilot, 但在本质上我们需要的是辅助思考的 Copilot.

现在很多文本编辑器都有 AI 功能:自动补全、自动总结、选中文本 Ask AI, 重写、润色等等。但我对这些功能在写作中的作用非常失望,真正在写作时,这些功能都是非常 annoying 的,而这不止我一个人有这样的感受:

这些功能,完全不能称作写作辅助,它们只不过是文本处理辅助罢了。它们对写作毫无用处,甚至有反作用。它们对思考也毫无用处,无法「放大」我的思考。

当写作作为一种思维工具时,我不需要 AI 辅助我自动补全,甚至有些可以生成一个段落的文字。这不是写作,这是在制造垃圾。

到底什么样的形式是一个好的写作 Copilot, 我也没有答案,但我认为它一定不能是侵入式的。它应该在一旁默默地观察我写出来的想法,然后在合适的时候告诉我它对此的观点。它有比我多得多的知识量,可以做到:

  • 在我提出一个观点时,找出其中可能的逻辑漏洞,帮助我更完整地思考。
  • 在我提出一个观点时,找出其在学术研究中对应的名词可以解释这个观点,甚至找出对应的科学实验、论文、现实中的案例。这在非虚构类写作中十分有帮助。我最近在读 Ali Abdaal 的 Feel good productivity 一书,里面十个观点有八个都能举出一个科学实验来论证,我基本可以单方面认为 Ali 一定是在用 GPT 来辅助他写这本书的。
  • 找出我在写作风格中的缺点,比如过于冗长,信息密度过低,句子难以理解等等。

也就是说,写作除了和自己对话以外,增加了和 AI 对话的一层。它可以提供灵感、帮助做 research, 写作风格指导等等的帮助。

虽然我希望有这么一个写作 Copilot 出现,但我觉得很难做出一个这样的 Copilot 出来。它的难点在于,不同类型的写作有着截然不同的需求,如果需要匹配不同的需求,最终还是会变成一个 Chat IM 的形态。上面提到的需求,其实直接使用对话也能解决,能进一步做的无非是在文本编辑器里加上选中文字展开对话,预设一些跟写作相关的 prompt 而已。

至于 AI 是否会扼杀写作,我不这么认为。Sam Altman 在访谈中也提到了这点:当你读到很好的作品,你会想去了解作者,这个作者经历的事情,他的思考方式,慢慢地和作者形成一个连结。这是 AI 做不到的。

「代码艺术家」不会被 AI 取代

最近大量地使用 Cursor 替代了 VS Code, 开始习惯直接在编辑器里告诉 AI 我的需求,让它来代替我写出代码段。

请注意,我用了「代码段」这个词,而不是「代码」,因为我想做一个区分 —— 按照我目前的经验来看,生成式 AI 非常擅长生成一段内聚的代码,而不是一整个应用程序。

在没有 AI 生成代码前,我写代码也是这样的一个流程:

  1. 思考整个应用的架构、模块
  2. 选择适合的技术栈
  3. 开始写代码,设计目录结构、抽象
  4. 真正开始实现实现

AI 也许能为第 1 和第 2 点提出建议,但我目前不需要。第 3 点我认为对于稍微复杂一点的生产级应用, AI 还做不到把这一块也做到。可能很多人看到现在 Claude 直接能写出一个全功能的 Todo List 就惊叹 AI 要取代程序员了,我觉得真正写过一个完整的给用户使用的「应用」的朋友对此都会很淡定。

对于我来说,只有在第 4 步(实现) 的阶段才真正能杠杆 AI 的能力。我会尽可能地描述清楚我的需求,让 AI 能理解我要做的任务,让它来生成满足我的抽象的代码,或修改现有的代码。

这里我提到的描述清楚我的需求是用 AI 生成代码中最重要的一点。所谓的「需求」不仅仅是描述这个函数需要做什么事情,还需要包含这个函数应该接收什么参数,返回的是什么数据结构。

例如,我在做的一个应用,其中我需要一个上传文件到 S3 的函数。在这个需求中,如果我单纯告诉 AI它要做什么,那我很有可能得到一个可以实现功能但不适合我调用的函数,因为 AI 没有上下文去确定我可以传哪些参数给它。

在深度和 AI 「结对编程」后,我对于「AI 是否能取代程序员」这个问题有了更深刻的思考。

有了 AI, 我现在写代码花的精力主要是在「设计」上,例如思考这个应用的交互设计,例如整个应用的架构设计。所谓的架构设计,一部分的工作是决定这个系统里要有什么模块,一部分的工作是决定这些模块如何串联在一起。而这些设计工作恰恰是我写代码的时候最喜欢做的,对我来说,写代码就应该是一个设计的过程,设计出优雅、易用、易扩展的接口是一件很有成就感的事。这也是我当初看 Head First Design Patter 这本书时的感受。
 如果写软件变成了一个只需要花精力在设计而不是实现上的过程,那么写软件的人就是「代码艺术家」了。我觉得「代码艺术家」是不会被 AI 取代的,因为设计的起点和终点都是人类,AI 可以给你 100 个设计上的答案,但只有人类最终能感知到现实和当下的环境和信息,创造出能触动另一群人类的产品。

如果你从现在开始,开始把 AI 当作是你的员工,就像某一天你突然只需要 $20 一个月就能招无数多愿意帮你打工的人,你很快就会发现,你最终会面临两种局面:

局面1:你将手足无措,你突然发现如果你不是实现函数的那个人,你就不知道你应该做什么了。从前你沾沾自喜的手写快排,手写红黑树突然变得一文不值,无处施展。

局面2:你将如虎添翼,你突然发现你曾经有很多想法没有精力和时间去实现,现在突然有这么多廉价劳动力将不厌其烦地帮你写代码,而你要做的只是设计好整个系统的结构,把具体实现外包给 AI. 然后把产品推出市场,去碰壁,去失败,去成功。显然,AI 不能替代你去碰壁,去失败,去成功,但真正让你变得强大的不是你手写快排有多烂熟于心,而是去碰壁之后学习到的东西

AI 不会替代「代码艺术家」,因为 AI 是「代码艺术家」的喷射机

读到这里,可能有人要说,Randy, 你飘了,你开始技术虚无主义了。在这里我要申明,这篇文章我是写给有一定经验的程序员看的。对于没有什么经验的程序员,多写点代码总是好的(至少目前来看)。AI 能力的上限是由用的人的上限决定的。无论是任何行业,充分掌握领域知识后配合 AI 才是最好的做法。

就像下面这个例子,我只要说一句 add tanstack query provider 就能让 AI 帮我把 @tanstack/query 加到我的程序里。我自己会写,但我自己写可能要花一两分钟,但 AI 一下子就好了。

但如果你没有任何代码经验,你连 tanstack query 是什么都不知道,也不知道要放在程序的哪个地方,那用 AI 还是有点困难。

写下这篇文章是因为最近用 Cursor 有感,加上刚好看到 Daniel Nguyen 发了一篇 Software is Art, 有感而发,不吐不快。在此粗浅翻译(非 GPT),作为结尾:

I realize the reason I like building is not just because I’m a builder.

我意识到我一直喜欢创造点东西的原因不只是因为我就是个创造者.

It’s because software products are how I express my creativity.

而是因为写软件产品是我表达我的创意的一种方式

It’s like a poem to a poet, a song to a songwriter, a painting to a painter…

就像诗人的诗,歌手的歌,画家的画

Software is my art form, my medium of expression.

软件是属于我的一种艺术形式,是我表达(创造力)的媒介。

不上班的第一年

2023 年的 5 月,我离开了微软,开始了自己做产品的旅程。到现在刚好满了一年,这一年发生了不少事情,有些在 2023 年终总结里提到过,这一篇我想更详细地列出在这一年我具体做了什么、对自由职业的思考、对做产品的一些思考等等。

首先是我的产出,这一年我的产出主要是两个产品、一个播客。

两个产品

Notepal

离职后第一个认真做的产品是 Notepal, 这是一个用于把微信读书笔记同步到各个笔记应用的插件。我在这篇文章详细写过这个产品的起源和在做这个产品的时候的一些思考。我在做这个产品的时候完全没有想到,这个只卖 50 块的插件,在这一年帮我付了我一整年的房租。

EpubKit

EpubKit 是另外一个我投入比较多的产品。这是一个把网页转成 ePub 电子书的工具。正式上线不到一个月,收获了几十个付费用户。

一个播客

刚好今年的 5 月份也是我和 GeekPlux 一起做的播客节目《代码之外》 的一周年。起初我们只是玩票性地做闲聊节目,后来因为有了听众来信的栏目,我们慢慢会讲更多职业发展方向的主题,这些主题意外地无形中对很多听众有不少的帮助。后来还邀请我刚工作就很欣赏的前辈勾股作为来信栏目的常驻嘉宾,为节目带来了很多很好的观点。今年在参加一些线下活动时,有些听众会跟我说从节目中得到了启发。从统计数字上看,我们好像的确是做了一件影响力不算小的事:

  • 小宇宙订阅数 6000+, 总播放量 11万+
  • Bilibili 粉丝 6000+,总播放量 13万+
  • YouTube 订阅数 2500+, 总播放量 3万+

在这一年,我对于做产品和生活都有了很多思考。对于不上班这件事,有人羡慕,有人好奇,刚好在这篇总结里,我想跟大家分享不上班的好处和缺点。

不上班的好处

时间自由

不上班的好处对我来说最明显的是时间自由。我是一个喜欢晚睡晚起的人,所以对我来说,准时上班是一件很难的事。不上班可以让我睡到自然醒,有充足的睡眠时间。

认识不同的人

上班的时候基本只能认识职场环境里面的同事朋友,或者在网络上交流的朋友。但是在自己做产品后,我开始认识一些同样是在做产品的朋友,和这些朋友可以聊得更深入。这些人往往对很多事情都有自己独特的思考。我从他们身上学习到非常多。我经常觉得如果我只是和以前一样在公司上班,业余写写博客和开源,我可能很难和这些朋友有共同话题,最多也只是互相点赞之交。

赤裸地直面市场

不上班后只能完全依靠自己赚钱,这代表我需要很赤裸地直面市场。

第一层赤裸,指的是脱离公司,作为一个个体,我可以给消费者提供什么他们认为值得为我付费的价值?

第二层赤裸,指的是一个产品如果脱离大公司本来就有的入口红利,我靠什么给我的产品带来自然流量?

这些都是很难的问题,但一旦开始学习,收获都是巨大的,而且都是属于自己的。在公司里最可怕的地方在于,我们很容易把公司给的平台误以为是自己的能力,很容易把流量看成是理所当然会有的。

也正因为如此,在这个过程中,我可以学习到很多以前不会主动学习的技能。

学习到更多技能

自己做产品的时候,没有公司平台给的入口,首先需要学习的就是如何做 marketing. 也是只有在自己做产品的时候,才会发现要别人发现你的产品是一件多么难的事。回想以前在阿里做业务,我们写一个营销活动页面,只要把入口放到某个栏位,基本不需要担心没有流量。

为了获客,我学会了研究 SEO, 学会了怎么做小红书,读了很多关于 marketing 的书。这让我学习了很多技术之外的知识,我觉得这些知识是终生受用的,而且它们不仅可以在互联网行业受用。

不上班的缺点

没有固定收入

不上班最大的缺点就是没有固定收入,这是很现实的问题。坦率地说,我以前买东西基本不看价格,只要觉得有价值,我就会买。没有固定收入之后,我买东西变得更加谨慎了。比如最近我很想买一台 Studio Display, 换作以前,无需多想,直接下单。但是现在,我会想我要卖多少份软件才能 cover 这台显示器,想想还是算了。

有时候我的朋友说羡慕我不用上班,我就跟他们说我这一年的收入还没有你们一个月赚得多,他们心理也就平衡了🤣。

孤独

可能有些人就是喜欢不用和别人交流,但对我来说,我是喜欢社交的,我在适当的社交中可以获取能量。不上班的时候,大部份的时间都是在家里写代码,看书,在网上和朋友聊天。有时候一整天都不需要开口说一句话。到后来我有点受不了,开始到外面的咖啡厅办公,只要能见到旁边有活人,就能有所缓解这种孤独感。

不确定性

上班有上班的不确定性要面对,不上班也有不上班的不确定性要面对。你无法确定这种生活可以维持多久,你的产品是否能卖得出去,就算卖得出去,它是否能养活自己,这些都是要面对的问题。

这些就是我体会到的不上班的一些优缺点,接下来,我想讲讲这一年在做产品的过程中我的一些思考和感受。

思考和感受

独立开发?

「独立开发」这个词在这一年非常火,用来标签像我这样自己做产品的开发者。其实我一直不称自己为「独立开发者」,因为我根本不想「独立」开发。我想做 scope 更大的事情,只是现在还没有条件。如果可以,我更愿意和两三个志同道合的人一起做产品。就像 61 的谜底科技,像少楠的 flomo.

而且自己做产品这一年,让我深刻地意识到,真正要做「独立」开发是很难的,因为没有人能擅长所有技能

没有人擅长所有技能

曾经我觉得只要我愿意去学,我就能做好。比如设计。我很注重产品的 UI/UX 设计,但我一直没有经过认真的设计学习和训练。我读了些关于 UI/UX 的书,以为就可以成为一个设计还不错的程序员。后来发现我错了,理论和实践之间原来有一条很大的鸿沟我虽然知道很多设计和用户体验的理论,但是一旦真正动手做页面,这些理论完全无法转换成实际的设计。

这是因为设计和写代码一样,是需要长期积累的。优秀的设计师可能有审美天赋,但他们一定也是每天都观察大量的设计,在自己的脑中内化了很多设计的模式(我不知道有没有专业的术语来形容),这些积累使他们可以面对一个新的需求的时候,根据自己内化的东西产生新的设计,这是我这种只读过一些理论的人无法做到的。正如会写代码的设计师,可能可以写一点能跑的代码,但缺乏多年的代码实践经验,是不可能像真正的专业程序员一样根据经验做好技术选型和想应该用什么设计模式(design pattern)的。

不过,我还是学会了在看到一些设计的时候,比以往更深入地去思考这个设计,这些元素为什么会这么摆放,颜色是怎么运用的,等等。

人的时间和精力是有限的

Notepal 和 EpubKit 同时做,让我更能体会到人的时间和精力是有限的。把时间放在一个产品 4 小时,那么另外一个产品投入的时间就永远少了 4 小时。而且上下文切换(context switch)是非常耗精力的。比如你正在修产品 A 的 bug, 这时用户报了产品 B 的 bug, 要从产品 A 跳转到产品 B 的开发,是很难一下子切换过来的。

所以不是自己一个人做产品就不需要项目管理,还是得学会充当自己的 manager 管理自己。给项目列好 roadmap, 排好需求优先级。

关于这个问题我还专门请教了图拉鼎,他告诉我要把自己当成不同的角色来用,比如规定上午作为客服,专门处理用户反馈,下午是程序员,专注写代码。这个方法也让我很受用。

自己做产品,同时要充当开发、marketing、客服、产品设计,我觉得这是「独立」开发的最大挑战。

学会专注

开发完 Notepal 后,我曾经陷入了很长时间的 burnout. 眼下赚的钱也不多,但又不知道应该做什么。期间有一些零星的 idea, 开发完后也不了了之。后来开发了第一版 EpubKit, 有了一些用户,之后又 burnout 了,因为开始觉得它做得不好,也很小众,渐渐又想放弃,想做点别的试试。

后来一些朋友「批评」我说我不够专注,我也开始反思,其实自己做的东西不算很糟糕,只是自己太着急,没有把它们都做得极致就失去耐性。经过反思,我决定好好打磨自己现有的产品,才有了现在这一版自己比较满意的 EpubKit.

乔布斯说专注不是指只做最好的,而是对其它也很好的东西 say no.

另外,缺乏专注很有可能会消耗用户对你的信任。

警惕「快速试错」

「快速试错」、「快速验证想法」是很多人做产品的信条,我相信快速验证想法是重要的,但是更重要的是交给用户的这个产品不应该是一个半成品,它最好是一个 Finished software. 也就是说,即使你不再维护这个产品,它还依旧是个可用的软件。

我曾经也「快速试错」,发布了一些产品,最终不再维护,甚至还有几个用户付费了。我对这种「试错」是愧疚的,因为这很有可能伤害支持你的用户。因此我现在反而发布新产品会更加谨慎,要把用户的感受放在第一位。

这篇文章很好地表达了这个观点。

总结

以上就是不上班的一年我对自己的一些思考的总结,希望对读到的朋友有帮助。这一年我其实算是比较幸运的,虽然收入微薄,但做产品也算是能卖出去,能解决很多人的需求。但同时我还是处于迷茫的阶段,认为自己做的产品还是小打小闹,scope 太小。我还是希望将来能做出满足更大众需求的产品,覆盖面能更广。

我的这些观点不一定对,但也能让读者感受到一个程序员脱离公司的其中一种可能性。

在 Electron 中使用 SQLite 的最好方式

上周刚刚发布了一个用 Electron 的应用 EpubKit. EpubKit 是一个把网页制作成电子书的工具。在 EpubKit 里,我需要一个数据库来存储内容,最好的选择是 SQLite.

但是,由于 Electron 有 renderer 和 main 区分开的机制,所以在 Electron 中使用 SQLite 会非常麻烦 —— SQLite 的执行要在 Main process, 但调用要在 Renderer process. 在 Electron 里,Renderer 和 Main process 之间的通信是通过 IPC (Inter-Process Communication) 实现的。也就是说,我可能需要把每一个有关数据库操作的业务逻辑单独写成一个 IPC 通信的事件,然后在 Renderer 里调用这些事件。

我想要做到的是,我在 Renderer process 中直接调用 ORM, 但实际的执行是在 Main process 中。这样一来我就不需要单独地写很多个 IPC 事件了。

例如:

万幸的是 drizzle 居然有一个 HTTP Proxy 的机制。这个机制能让你所有的 ORM 操作都流到一个地方,在这个地方你能拿到最终生成的 sql 语句,然后你可以自己决定怎么执行这个 sql 语句。

也就是说,我可以在这个 proxy 里,把 sql 语句通过 IPC 发送到 Main process, 然后在 Main process 里执行这个 sql 语句。

接下来我会简单描述一下我在 EpubKit 里是怎么做的。

编写 Schema

在你的项目里找一个地方,把 drizzle schema 写下来:

import { int, sqliteTable, text } from "drizzle-orm/sqlite-core";

export const posts = sqliteTable('posts', {
  id: int("id").primaryKey().default(0),
  title: text("title").notNull().default(""),
})

在 Renderer 里创建一个 drizzle database 实例

根据 文档, 在创建 drizzle db 实例的时候,可以传入一个函数,这就是 proxy 的本体。我们要做的是在这个 proxy 里,拿到 ORM 最终生成的 sql 语句、执行方法、变量,然后通过 IPC 发送到 Main process.

export const database = drizzle(async (...args) => {
  try {
    // 通过 IPC 把 SQL 发送到 Main process
    const result = await window.api.execute(...args)
    return {rows: result}
  } catch (e: any) {
    console.error('Error from sqlite proxy server: ', e.response.data)
    return { rows: [] }
  }
}, {
  schema: schema
})

这里有一个 window.api.execute(), 是怎么来的呢?其实是在 preload 进程里面定义然后暴露的, 它的作用就是通过 IPC 发送 sql 语句到 Main process:

// preload.ts

const api = {
  execute: (...args) => ipcRenderer.invoke('db:execute', ...args),
}

也就是说,实际上我们以上做的事情就是,通过 proxy, 把 SQL 语句通过 Main process 里的 db:execute handler 最终执行。

Main process

在 Main process, 我们创建一个 IPC handler:

// main.ts
ipcMain.handle('db:execute', execute)

这里的 execute 就是 Main process 里最终执行 SQL 语句的函数。

import { drizzle } from 'drizzle-orm/better-sqlite3'
import Database from 'better-sqlite3'
import { migrate } from 'drizzle-orm/better-sqlite3/migrator'
import * as schema from '../renderer/src/db/schema'
import fs from 'fs'
import { app } from 'electron'
import path from 'path'

// 初始化 sqlite
const dbPath = '../databse.db'

fs.mkdirSync(path.dirname(dbPath), { recursive: true })

const sqlite = new Database(
  dbPath
)

// 创建 drizzle 实例
export const db = drizzle(sqlite, { schema })

// 这里是 execute 方法
export const execute = async (e, sqlstr, params, method) => {
  // 得到执行需要的参数后,用 better-sqlite3 执行
  const result = sqlite.prepare(sqlstr)
  const ret = result[method](...params)
  return toDrizzleResult(ret)
}

function toDrizzleResult(row: Record<string, any>)
function toDrizzleResult(rows: Record<string, any> | Array<Record<string, any>>) {
   if (!rows) {
    return []
  }
  if (Array.isArray(rows)) {
    return rows.map((row) => {
      return Object.keys(row).map((key) => row[key])
    })
  } else {
    return Object.keys(rows).map((key) => rows[key])
  }
}

在上面的代码中,我额外实现了一个 toDrizzleResult 的方法,是为了把 better-sqlite3 的返回值按照 drizzle 需要的结构返回。

到这里,你就已经可以在 Renderer process 里直接用 drizzle 了:

function App(): JSX.Element {

  const [postList, setPosts] = useState([] as any[])

  useEffect(() => {
    database.query.posts.findMany().then(result => {
      setPosts(result)
    })
  }, [])

  return (
    <div>
      <div>
        <form onSubmit={async e => {
          e.preventDefault()

          const formData = new FormData(e.target as HTMLFormElement)
          const title = formData.get('title') as string
          if (title) {
            await database.insert(posts).values({
              id: Math.floor(Math.random() * 1000),
              title
            })

            // refetch
            const result = await database.query.posts.findMany()
            setPosts(result)
          }
        }}>
          <input name="title" type="text" placeholder="title" />
          <button>add</button>
        </form>
      </div>
      {postList.map(post => {
        return (
          <div key={post.id}>
            {post.title}
          </div>
        )
      })}
    </div>
  )
}

export default App

但这时候执行,会报错。原因是我们还没有初始化数据库。我们需要在 Main process 里初始化数据库。

首先需要用 drizzle-kit 生成 migration 文件。在 drizzle.config.ts 中指定了 migration 文件的地址:

// drizzle.config.ts
import type { Config } from 'drizzle-kit'

export default {
  schema: './src/db/schema.ts',
  out: './drizzle',
  driver: 'better-sqlite'
} satisfies Config

然后写一个 runMigrations 方法,用来初始化数据库:

export const runMigrate = async () => {
  migrate(db, {
    // 在 drizzle.config.ts 里指定的路径
    migrationsFolder: path.join(__dirname, '../../drizzle')
  })
}

这个方法需要在 Main process 启动时执行的:

async function createWindow() {
  // ...

  await runMigrate()
  createWindow()

  //...
}

实例源码

你可以在 这里 找到这个示例的完整源码。

特别感谢 EGOIST 提供灵感。

读《岩田先生:任天堂传奇社长如是说》

虽然我有 Switch 游戏机, 但我不是游戏的发烧友,对任天堂及其社长岩田聪更不甚了解。前些天听《半拿铁》的《任天堂往事》系列,对任天堂的发展有了一些了解,也听闻岩田聪同时是一个非常出色的程序员,所以开始对岩田聪产生了一些兴趣,于是找到了这本收集了岩田聪说过的话的书,希望能一睹这位天才程序员对管理企业和写程序有哪些独特的见解。

这本书不长,读完后我对岩田聪确实有了更多了解。首先是从他身上学到了一些管理企业的方法。

岩田聪的管理风格非常务实和「接地气」,他讲到他喜欢和员工一对一面谈:

我在与全体员工谈话的过程中,发现了许多“经过面谈才头一回意识到的事情”。就算对方是一向保持着沟通的人,也有在一对一的场合才讲得出口的话。这样说或许不太恰当,但我重新认识到:“假如不制造推心置腹的机会,人是不会敞开心扉的。”

在书的最后有宫本茂和糸井重里谈岩田先生的追忆文章,其中糸井重里回忆说:

虽然是第一次见面,但他的话语让人觉得值得信赖。岩田先生曾回忆说:“第一次见面时我也相当紧张。”但我一点也没看出来。“在已有基础上修改,还是从头做起?”这句话单从字面上看会让人觉得不够谦逊,但岩田先生完全没有居高临下的姿态,而是传递出十分看重对方自由选择的权利的感觉。怎么说呢,原本是我们请他来帮忙,但在技术问题之外,岩田先生工作方式的魅力也令我们深受感触,见面次数越多就越发信赖他。

结合我从前被管理的感受来看,这让我意识到,管理者能给被管理者足够的信任感是非常重要的,而信任感可以来自于谦逊和务实。

岩田聪还对公司的会议规定必须有一位「会议统筹」:

所谓“会议统筹”,就是让会议良性运转的人。如果会议中缺少创意,就负责添加创意;如果创意太多,导致过于发散,就负责归纳总结。简言之,就是会议的指挥。无论什么会议,都必须有一个决心“要在会议上得出答案”的统筹者。

我们在厂里工作过的都知道,很多时候会参加一些没有结论的无用会议,如果每次会议都能指派一个这样的「会议统筹」,相信大家的效率会提高很多。

从岩田聪的谈话中,我感觉岩田聪是一个非常能够洞悉到他人内心感受的人,所以在推进项目的时候,也会非常照顾同事的感受:

世界上许多改革都是运用否定现状的逻辑推行,但这样的做法会让很多人陷入不愉快。因为当下的现状正是许多人的心血、诚意、热情构成的。如果现状由不诚实的东西构成,去否定也无妨,但由诚实的产出构建的现状,不应该被否定。

也许正是这样的性格,才能让岩田聪清楚洞悉玩家的感受,从而做出改变世界的游戏。同时,也是因为他是真正有「给别人带来快乐」的强大 passion:

岩田先生发自内心地喜欢看到大家的笑容。他也将这个目标列入任天堂的经营理念。的确,他是一个希望给世界创造更多快乐的人。并且,他是一个为了实现这个目标不惜舍身奉献的人。他喜欢帮助他人,喜欢钻研各种事物,也喜欢为此而沟通交流的过程。所以说,每周一与宫本先生一起吃午饭的时间,凝聚着岩田先生所喜爱的一切。因为他们一起讨论着游戏创意,说着“我懂了”,试图给自己与玩家都带来笑容。

我一直觉得,能成大事的人,都是那些内心有一种很强大的 passion 的人。我虽然不是资深玩家,很难从游戏中感觉到快乐,但从岩田先生的谈话中,我还是为他的这种 passion 所感动。

如果你本身是一个玩家,可能会对这本书有更深刻的理解。

最后,用岩田先生的话结束这篇读后感:

在我的名片上,我是一名社长。在我的头脑中,我是一名游戏开发者。但在我心里,我是一个玩家。

On my business card,I am a corporate president.In my mind,I am a game developer.But in my heart,I am a gamer.

名刺のうえでは、わたしは社長です。頭のなかでは、わたしはゲーム開発者。しかし、こころのなかでは、わたしはゲーマーです。

读 React 18 文档有感

昨天 Sixian 提到了 React 的新版官方文档,之前一直没有去读,今天抽空读了其中 Escape Hatches 的部分,有一些收获,也有一点感想,在这里简单分享一下。

声明:我经常使用 React, 但我不是 React 的专家,我也不再是一个对技术会进行非常深入探究的人了。所以对于 React 最新的一些 API, 可能对某些人来说是老生常谈,但对我来说是新鲜的。所以本文只是简单分享一下我看到的之前不知道的一些 API.

跑两次的 useEffect

开发环境 useEffect 跑两次是故意的,是为了帮助开发者在开发环境中发现你是否正确地给你的 effect 做了 teardown.

The right question isn’t “how to run an Effect once”, but “how to fix my Effect so that it works after remounting”.

如果你的代码因为 useEffect 跑两次所以出问题,很可能你用错了 useEffect.

讨论这样的特性是否是一种傲慢对我来说意义不大,重要的是我理解了其动机。

flushSync API

React 有一个 flushSync API, 可以强制 update DOM. 在以前我们一般是把希望在 DOM 更新后才执行的代码放在一个 setTimeout 里.

如果想重置一个 Component, 直接给它一个新的 key

如果希望某个 component 的 props 被改变的时候做一些重置,应该直接给这个 component 赋一个新的 key, 使 React 直接重建整个 DOM, 而不是用 useEffect 手动做重置工作。

其实以前我也经常这么干,比如 Modal 在关闭和打开后重置里面 Form 的值,我会直接给 Modal 一个新的 key. 一直不知道这是否是一个 best practice, 现在得到了官方认证。

useSyncExternalStore

这是一个我不知道的 Hook, 看起来很适合用于把别的库移植到 React 时使用,而且它对 SSR 非常友好。

一些感想

很多人觉得 React 繁琐,心智负担特别大,我也这么认为。但我一直觉得,这不是 React 本身的问题,而是 JavaScript 的问题。

React 是一个特别函数式编程思维的框架,但很可惜,JavaScript 只是一个半吊子的函数式编程语言,它只是在被设计的时候学了一点 Lisp 的皮毛,也提供了一点点函数式的 API, 但它没有很多真正的函数式编程语言应该有的基本特性,才导致了我们要写额外的代码来解决一些问题。

例如,Memoization 在一些函数式编程语言里是标配,但 JavaScript 里没有,所以你需要给函数手动套一层 React.useCallback.

函数式编程语言里, Immutable 也是标配,而在 JavaScript 里,需要用 Immer 之类的第三方库。

所以这就是为什么在多年前我很看好 ReasonML (现在叫 ReScript) 这个语言,因为它本身就是真正的函数式编程语言(它是基于 OCaml 的 JavaScript 方言),你会发现,用它来写 React 是多么舒服的一件事,因为语言本身就提供了你在 JavaScript 里需要调 API 才能实现的功能。有趣的是,React 在刚开始设计的时候用的就是 OCaml.

成也 JavaScript, 败也 JavaScript.

2023 年终总结: 和自己对话

可在小宇宙收听本文音频版

变化

2023 年初,持续了 3 年的疫情封控结束了,整个社会迎来了一个转变的开端,没人知道会变好还是变更糟糕。随后 3 月,我决定离开微软,我自己也迎来了一个转变的开端,我也不知道会变好还是更糟糕。

我财务自由了吗?如果财务自由有一个绝对值,那么无论是多少,我肯定都没有达到。离职后有很多朋友问过我这个问题,现在刚好可以在博客也跟我的博客读者分享:当我在考虑不工作时,我在思考什么?

1. 如果继续现在的状态,那么 3-5 年内我会有什么不同?

这是我在考虑是否入职微软之前用的同一个方法,是我朋友教我的。当年我觉得如果不入职微软见识见识,我一定会后悔,留在原来的公司也不会有太大的变化。今天即使我已经离职了,但我很感谢我有选择微软,我的收获非常多。所以我运用同一种决策方式来思考,即使我在微软继续工作 10 年,很大可能我得到的只是 title 和薪水的不同,这和我的目标背道而驰。这也引出了我的第二层思考:

2. 我正在做的事和我的长远目标是否对齐?

过去我会担忧和焦虑我达不到目标,后来我醒悟:没有人可以控制结果,只要我正在做的事是朝向我的长远目标就可以了。继续工作显然不是。

3. 没有固定收入怎么生活?

我有一点积蓄,虽然不多。很多人存了钱,买了房子。而我用我的积蓄买一段可以不工作的时间,我觉得还是很值得的。而且我不工作也不是天天躺着什么都不做,只要朝着我的目标在努力,这也算是一笔投资。

幸福之道

离开公司后,我的心情很复杂。我觉得我一直想寻找一个答案,但实际上我连问题是什么都不知道。我想起了年轻时读的《刀锋》,应该像书中的拉里一样去寻找,于是 7 月份我去了泰国,得到了一段出乎意料的佛教哲学之旅

创造

今年的输出比我预期的要更丰富一些。

视频创作

继 2022 年的 Logseq 分享视频,今年又做出了一个超 50,000 播放量的视频《我如何做笔记》

《代码之外 Beyond Code》播客

今年终于和 GeekPlux 一起把盘算了多年的项目真正做了出来,意外地得到了很不错的反响。甚至能在线下聚会的时候听到一些我们的听众表达的感谢。

其实节目的制作形式受到了很多我喜欢的不同的播客节目,我由此也更深刻地感觉到,一个人能做出什么样的东西,很大程度上取决于其接触、输入的东西。只有吸收优秀的东西,才有机会做出优秀的东西。

这跟做产品也很像,如果一个人从来没有体验过真正优秀的产品,那就很难做出优秀的产品。所以我总是鼓励一些朋友,不要因为一个产品要花钱所以不去用它,就算只买一个月,也要去体验一下。

Randynamic Studio

离职后我就计划把我之后做的所有小工具统一到 Randynamic Studio 的名下. 很幸运,它的真正意义上的第一个产品 Notepal 算是非常成功。它没有为我赚巨额的钱,但它解决了很多人的需求,并且这些人愿意为它付费,这是我写在博客首页很多年的一个目标,在今年终于实现了。

多年来我把 Y Combinator 的 slogan 一直放在心中,有很长一段时间我每天睡醒后心里就会自动播放这句话:

Make something people want.

很幸运我朝着这一个目标又迈进了一步。

线下聚会

解除封控后,终于可以参加一些技术圈子的线下聚会了,也有机会见到一些相识多年但从未谋面的网友。

先是参加了佐玩开发者交流会,见到了很多很年轻的后辈,更惊喜的是很多是《代码之外》的听众,他们都说在节目中收获到了很多。在那一瞬间我觉得我做的很多事情都是有意义的。

然后我到杭州找了图拉鼎,见识了数字游民的聚合地「玉鸟集」。在那里还认识了非常多的独立开发者和做产品的人,大家都很有自己的想法,对自己做的事情有自己的理解,有自己的观点,自己的品味。这种类型的朋友是很难在工作的时候遇到的。

总结

2023 是我和自已对话的一年,我开始了解自己想要的是什么,了解自己的情绪,了解自己擅长的是什么,不擅长的是什么,了解自己追求的是什么,等等。在清迈,我学会了倾听内心的声音,我也意识到,在真正了解自己的情况下,做决定似乎没有那么难了。

展望

我其实很希望在 2024 年能做一些 scope 更大的事情,同时也需要找到和我有相同 passion 的人。我一直不称自己为「独立开发者」,一是我觉得我还没达到,二是这也不是我的目标。我的目标一直是两三个有同样追求的人组成一个小 team 做一个美且在商业上可行的产品。

希望自己能往目标再迈进一大步。

开发一个浏览器插件在第三天卖出 1000 元

写下这篇文章是我的插件 Notepal 发布的第三天,目前收获了几十名付费用户,在这短短几天已经有 5000 多条微信读书笔记被同步到 Readwise, flomo 或 Obsidian 中。

(28 号没有数据是因为那天出了事故,日志没有进来。)

很感谢点进这篇文章的你没有因为 1000 元是个小数目所以选择无视,这个数目确实很小,它甚至没有很多程序员坐在办公室待一天赚得多,但「有人愿意购买」的意义远比数目的大小对目前的我来说意义更大,而且我在开发和上线整个过程中学习到了不少,也实践了不少我读的一些关于 start a business 的书,我发现在亲身经历了这个过程后,再重读这些书,体会更加深刻,所以我很想在这篇文章和大家分享我学到的,以及我所采用的一些方法论。

最初的需求

我在差不多半年前因为想把微信读书的笔记同步到笔记软件 Readwise, 所以写了一个很简单的网站(Notepal 的前身),可以把微信读书 App 里复制出来的笔记,解析成自定义的格式,也可以解析成 Readwise 能实别的 csv 格式。

虽然这种方式还是有点繁琐,而且 csv 格式其实很不稳定,很容易被笔记里的一些字符导致导入失败,但在此之前我想基本没有人可以这么「自动化」地做到这件事了。

我把它发布到了 V2EX, 写了点介绍,并且代码是开源的。

但是,在当时,我隐约觉得这个需求会有人愿意买单,因为我相信绝对不止我一个微信读书和 Readwise 的共同用户有这个需求。但我当时刚好在读 Sahil Lavingia 的 The minimalist entrepreneur 这本书(在接下来的内容我会多次提起这本书),里面有一个让我感触很大的观点—— start with community, 创业从社区开始。

什么意思呢,如果你觉得你能解决一个很多人都有的需求,首先要做的不是急着做软件做产品,而是先找到这群人,在这个群体里帮助他们。

所以当我隐约认为我能解决一部分人的需求且他们有可能为此付费时,我做了一件和开发完全没有关系的事——建用户群,然后放到网站上,我希望把有这个需求的人先聚集起来,等有一天我把这个工具做得比较稳定的时候,有一个卖出去的机会。

后来加进群的一共 50 多个人,这些人其实也没怎么说过话,有些人提了一些使用问题,然后群就基本死了。

大部份的人反馈的问题都是 csv 出问题,这个问题很难解决,我觉得如果要稳定,必须通过调用微信读书的接口,拿到 json 格式的笔记,然后通过 API 同步到 readwise. 所以我一直想把这个需求用浏览器插件来解决,我在群里也提了一句「我准备写一个浏览器插件」,但是也没人理。

于是我就把开发插件的事情搁置了。

转折,冷启动

从这个工具发布一直过了半年,突然有位推友发了一条推,对我这个工具表达了感谢:

这不足以让我惊讶,最让我惊讶的是这条推的数据:113 转推6 引用373 喜欢次数237 书签。

这让我有了动力去把我曾经想实现的插件写出来,我决定先快速做一个 MVP, 只要实现在微信读书页面展示有笔记的书,并且用 Readwsie 的 API 一键同步笔记,最后展示同步到 Readwise 后的笔记。

为什么要这么做?因为我需要做一个「半冷启动」,我知道把插件打磨到可以上线并收费的工作量不算小,但我需要做一个流畅的视频,来展现这个 idea, 并且是一个真实的实现。把这颗小石头抛出来,看能引起多大的水花。

把视频发出来后,引起了不少的回复:

这时我决定要做出来一个真正值得被这些人购买的产品,解决他们的需求,让他们心甘情愿地因为产品解决的问题和体验而买单。

从冷启动到正式上线

在写正式的产品前,我问自己一个问题,做到什么程度可以发布第一个版本。我心中的答案是这样的:

  1. 为了尽快上线,只做同步到 Readwise 的功能
  2. 上线 day one 就要开始收费,可以提供免费试用,但一定要有收费入口
  3. 体验一定要最直接——填 API token -> 选书 -> 同步 -> 查看同步到 Readwise 的笔记,一气呵成
  4. 要记录一些非隐私日志,例如同步笔记的量,错误日志

尽快发布

第一点,很多人肯定会觉得,Readwise 这么小众,同时用微信读书和 Readwise 的更小众,会不会太少人用了。这是正确的判断,但是我认为我的当务之急是把产品 ship 出来,解决我能真实看到的那不到 10 个人的需求,看能不能从如此利基(niche)的圈子赚到钱,再谈扩张用户量(而且我很快在后面的版本支持了 flomo 和 obsidian)。

Day One 就要收费

第二点,有人会说,这么小众的需求,真的能赚到钱吗?我认为,可以,但看你是想赚大钱还是小钱,小众需求难赚大钱,但越小众的人越愿意付费。我已经对这个产品有了预期——我不认为这是能赚大钱的产品,我对这个产品的目标是:解决小部分人的需求,让他们为产品提供的价值付费。

为什么要赚钱?其实我大可以免费发布,我也完全不能靠这个产品吃饭。但和多年前的我不一样,我现在觉得「用户掏钱买你的产品」是验证你的产品价值最有用的方法,无论是便宜还是贵,即使是 1 块钱,就和免费完全有不同的意义。就像打德州扑克,用你真实的钱打和用游戏币打完全是两回事

既然要收费,就有两个问题:一是收多少,二是怎么收。

收多少的问题,首先有两个选择,一次性买断和订阅制。不过像这样的产品,我觉得理智的人都不会觉得适合订阅制。谁会为了一个低频需求订阅一个插件?

如果是买断,多少钱合适呢?我没有做什么调研,就拍脑袋觉得 30 块是个不高也不低的价格,而且是一次性收费。如果用户觉得好用,还是比较大概率选择购买的。

最难的问题其实是怎么收,尤其是这个产品面向的是国内的用户。众所周知,国内的付费渠道接入门槛是相当高的。可能做这个插件也就几天的事,但接入收费渠道就要几周。这是不能接受的。就像 Paul Garahm 说的:Do things that don’t scale.

为此我想了很久,于是想出了一个办法。这也是在 The minimalist entrepreneur 这本书受到 Sahil Lavingia 的启发,他说 Gumroad 第一个版本是他自己每个月手动给 creator 发钱的。

我大受震撼,我想到,我这个产品,能不能卖得出还是个问题,即使能卖,也不可能卖很多,很频繁地交易,我何不像路边滩一样挂个收款码解决呢,只是我把收款码放到一个「购买页面」而已。

于是有了这个购买页面

但是,即使我能通过收款码收钱,那如何和我的插件联动,在用户付钱后解锁功能呢。我想到最传统的软件售卖方式——卖激活码,用户通过付款码值钱备注邮箱,当我收到款后,往他邮箱发送激活码,就完成了。

流程有了,但激活码怎么来?没有办法,这个还是要自己写点代码。我花了半个小时,用 Next.js + PostgreSQL 写了一个最简单的激活码生成和验证的 API service.

激活码机制也有了,但手动发激活码还是麻烦。所以我用写了一个「快捷指令」,只要填邮箱发到我的激活码 API service, 就会自动完成从创建激活码到发送邮件一整个步骤。虽然还是手动,但我的工作就变成了收到钱,运行「快捷指令」填邮箱。

读到这里,读者应该可以深刻地体会到,Paul Garahm 所说的 Do things that don’t scale 是什么意思了。我觉得在做产品的时候,一定要瞄准最重要的事情——你给客户提供的价值是什么,而不要在其它地方花太多的精力,在这些地方甚至可以先手动做一些事情,在成功了之后再把他自动化,而不是一开始就自动化。

读者应该看过一个 meme 图,一个 ATM 机,其实背后是一个人手工把钱塞出来。这个图有点搞笑,但这是一个完美的 Do things don’t scale 的例子,客户需要的是拿钱,用户并不关心你背后是多简单或多复杂的流程。新产品上线,最重要的是解决客户的问题,而不是怎么解决得比较优雅。很多工程师很容易陷入一种「自动化」的诱惑,想把一切都做得最自动化,然后忽略了真正在解决的是什么问题。

自动化可以做也必须做,但不是从 day one 就开始做。

用户体验

对于体验,我在上文从冷启动的视频到上线版本都强调了「查看同步到 Readwise」的笔记,很多人可能觉得这是一个可做可不做的功能,而且这是一个从技术上来说非常简单的功能。但我觉得这是整个产品最重要的 aha moment, 我要让用户能看到笔记一键就被放到了笔记软件里面的样子,而不是只是提示同步成功,让他自己去笔记软件查。这个体验,做和不做在技术上没多大区别,但对于体验来说是有天壤之别的。

记录日志

记录日志也相当重要,尤其是错误日志,在上线以后,有一些我自己的场景没有 cover 到的 case, 都通过用户反馈然后我看错误日志成功解决了。另外,行为日志也蛮重要,可以看到自己的产品有没有被使用,使用的量有多大,从而帮助自己做一些产品决策,也是一种对自己的激励。

上线,卖出去

如果是以前的我,我一定会有一种迷思,认为产品发布上线后,就等着她被传播,如果运气好,被大 v 传播,就有流量,就能变现,如果运气不好,可能就是没人有这个需求,这个产品就失败了。

但读完 The minimalist entrepreneur (我又提到这本书了),我对销售有了很不一样的理解。书里提到,大部分的产品,头 100 个付费用户是一个一个谈来的,只有用户足够多以后,才会出现口口相传的情况。而且广告是没用的(如果不是一个以流量为核心的产品)。

Eventually strangers will buy your product, but mostly because your customers are spreading the gospel of your business and product, not because they saw an ad. But it will take time to get there. It’s not something you hit on day one.

所以我这次上线没有被动地等待,而是主动出击,在互联网上搜索关键词,找到和我有一样需求的人,评论和私信他们,告诉他们我也有一样的需求,并且做了一个十分方便的插件,请他们试一试。

刚开始这样做的时候,我还是有点羞耻,觉得自己是在互联网上发布垃圾广告,但客观来说,我是作为有同样需求的人,分享我自己做出来的可以解决他们需求的工具,好像也不算垃圾广告。而且让我惊喜的是,很多人回复都很友善,并且感谢或最终购买了我做的产品。

这也让我想到,做产品,首先要想的不是怎么做,而是卖给谁。这是最重要的问题。如果连自己都不知道目标用户,又谈何 marketing 呢?

总结

一口气写了很多,从发布上线到现在其实也才三天,其中有一天还发生了一些事故,但还是给我自己上了很重要的一课,相信我分享出来的这些也能给大家一些启发。

虽然赚的不是什么大钱,但这对我来说是非常有意义的一次体验。就像我在博客的首页写的,小时候我的理想是改变世界,现在长大了,发现能做出用户愿意付费的有为用户提供价值的产品已经很不容易了。这是我第一个发布的付费产品,而且还收获了愿意付费的一些用户。我读了很多关于创业、产品、用户体验、营销的书,等这一刻已经等了很久了。

第一次听到到账 30 元的通知的那种激动,我想会是一辈子都不会忘记的。在厂里拿 30k, 代表我面试表现还不错,也能写出公司愿意付钱的代码,但卖产品拿 30 块,代表我洞悉了需求、直面了市场(这是说出来简单,但做的时候很难的事)、为付费用户解决了问题。

非常感谢这些用户,这对我来说是很大的激励。谨以此文作为我对大家的回报。

在清迈冥想学习 7 天后,我不再「追求」幸福

在清迈结束了一周的禅修学习,想把自己的体验和感悟写下来,才发现这些我感觉不可思议的体验,用文字和图片表达出来是多么的苍白。

事缘起年初意识到自己的心理健康状态不再适合继续工作,遂辞职休息。刚好看到早见小姐分享了她在巴厘岛冥想的点滴,才发现泰国清迈有寺庙提供冥想学习。于是当晚就找好了寺庙,订下去泰国的机票。

我在出发前一直在想这次远行学习到底能不能符合我的期望学习到「真正的」冥想技术。一周下来,我的感觉是这一周学习到的和体验到的远远超出我的期望。我不仅在老师的带领下练习冥想,还对佛教有了新的认识。

通往幸福的道路在哪里

我到的第一家寺庙是 Wat Umong, 中文应该译作乌蒙寺。我在这里待了五天四夜。Umong 基本是靠自己练习,没有系统的教学。这家寺庙在丛林里,所以特别贴近自然。

寺庙虽然没有强制不使用手机,但我在第一天开始就尽量不用手机了,只限制自己一些时间查看是否有重要信息,其余时间都是飞行模式,把手机当作相机用。

当身边没有任何可以让你转移注意力的介质(手机、电脑、互联网)的时候,内心就像被放了一个扩音器,一点微小的想法都会被放大和延伸。这让我真正地了解了自己内心的声音。

在离开 Umong 的前一晚,我和另一个中国人和一个俄罗斯人一起在黑夜中喝茶。这位俄罗斯朋友带了普洱和茶壶,跟我们两个中国人科普茶道。我们从 9 点聊聊到凌晨一点,聊了关于冥想的体验,我们各自的烦恼。我说当我在冥想的时候,我总是会想到未来的事,在这几天的冥想练习,我才发现原来我的内心一直纠结在我想像的未来,我一直找不到我的快乐和幸福在哪里,于是我很痛苦。

俄罗斯朋友(大意是)说:Don’t think too much. Focus on where you are, what you have, and what you can do with what you have.

我从他的回答得到了很大的启发。我一直在寻找快乐和幸福,以为它们在前方,我需要走一些路去找到它们。但我忽略了「当下」,在我已经拥有的东西,和当下我做出来的事情中感受它们。我也终于理解了寺庙里其中一棵树上挂的一句话的含义:There is no path to happiness, happiness is the path.

那晚我们还聊了很多,那一刻我希望时间可以过得更慢一点,可以在这样宁静的夜晚多待一下。萤火虫在我们身边飞来飞去,远处还有虫鸣,还有壁虎的叫声(我第一次听到壁虎也有叫声)。

第二天早上我们一起到寺庙的洞穴里面,那时还没有游客进来,我在洞穴里冥想了一会儿。然后俄罗斯朋友带我们去了附近的一个动物园,他说那里有一只会讲泰文的鸟。我不知道这种鸟叫什么(应该不是鹦鹉),我们听到它竟然会讲几句不同的话,还会唱一段相同的旋律。

临走前,俄罗斯朋友竟然送了我一个茶杯。

下午我就离开了 Wat Umong, 出发到 Wat Suan Dok.

介于哲学和信仰之间的佛教

到达 Wat Suan Dok 的 Monk chat office, 首先是我们接下来三天课程的老师 Phra K.K. 跟我们教授关于冥想和佛教的基本知识。K.K. 讲了差不多一个半小时,我学到非常多。后来在去冥想中心路上我和其它学员聊天,我们都一致认为我们学到了超出了我们预期的知识。这也让我们对接下来三天的学习很有信心。我们这一期一共有 15 个学员,来自世界各个地方,英国、美国、中国、西班牙、印度、新加坡、韩国等等。

在冥想中心,我们被要求一切都要慢下来。走路要慢,即使敲钟集合,一定不能急着过来,必须慢慢走。吃饭的时候,需要 eating mindfully (中文应该就是「正念饮食」),慢慢地把食物送到嘴里,慢慢地咀嚼,感受食物。

这是个小小的改变,但是三天下来,我的内心状态因此有了翻天覆地的变化。远离社交网络、做事慢下来,让我得到了内心的平静。我们从小就被鞭策一切都要快,要「抓紧时间」—— 上课铃响,要赶紧跑回教室;在学校吃饭,每次都要被老师催要吃快点回宿舍休息;走在路上,明明没有很赶时间,步伐也要很快。

当我开始把步伐慢下来的时候,就发现肢体上的缓慢,会使得内心变得平静。反过来,当内心开始胡思乱想,肢体动作就会开始不自觉地变快。有好几次,我在缓慢行走时开始分神,回过神来发现自己走得越来越快。

在第二天的讨论环节,K.K. 解答了我们很多问题,正是这些讨论,让我对佛教有了全新的理解。

我以前认为佛教是一种迷信,因为身边接触到的跟佛相关的东西,无非是「求神拜佛」。现在我才知道,佛教反对迷信。佛教不解释人是如何被创造的,没有像上帝这样创世的神。佛教是教人如何摆脱痛苦的。佛教让你相信你自己,而不是相信别人,也不是相信佛。只有你自己可以帮你自己摆脱痛苦,而不是相信一个神可以帮你。只有你自己去体验过,你才能去相信。

老师说,每个人都可以顿悟,只要持续练习(冥想)。佛陀叫作 Buddha, 意思是觉醒的人(the awakened one),每个人都能是 Buddha.

佛教是介于哲学和信仰之间的东西。我被其哲学的部分深深吸引。

知名投资人 Naval 把自己的生活哲学称为「理性佛教」,这也是我今后打算不断探索的一块。我想学习佛教中对人生、痛苦的理解,形成自己的生活方向盘。

对我来说,理性佛教意味着理解佛教所倡导的内在修行,以此让自己变得更快乐、更富有、更能活在当下、更能控制自己的情绪——成为一个更好的人。

所以,我的人生哲学就包含这两个方面:一方面是进化论,进化论是约束性原则,因为它解释了关于人类的诸多问题;另一方面是佛教,佛教是关于我们每个人内心状态的精神哲学,是最古老、最经得起时间考验的哲学。我认为这两点并不矛盾,可以相辅相成,互为补充。

—— Naval

出家的佛教徒需要遵循很多戒律,因为佛教认为痛苦来源于欲望。你的欲望越多,你将拥有的痛苦就越多(The more you desire, the more you suffer)。今天你买卡西欧,明天你就想要劳力士,于是你痛苦。有一天你得到了劳力士,就会发现不过如此,你的欲望让你还想要别的,于是你痛苦。

当然我只是一个普通人,一个带点野心活着的普通人,我不会遵守严苛的戒律,但通过冥想练习,我似乎更有能力从客观的角度观察(aware of)到我此刻的冲动的来源,从而冷静地思考是否要做出下一步的行为。

智慧是一种知道个人行为的长期后果的思维能力。 —— Naval

使人直面内心的冥想

在冥想的一周里,我基本没有怎么说话。但当我在进行冥想练习时,我实则和自己的内心对话了数千次。冥想能锻炼我们对情绪的觉察能力。我在头几天的冥想练习里,头一回通过观察我走神时不自觉思考的内容,清晰地观察到我的内心世界。在第三天,我就在我的日记本中写下了一句:「我太纠结于我想象中的未来」。

我和一起学习的一些学员交流我们的冥想体会,有的人会想起很多过去的事,而我会想到很多还没发生的,我想象的事:我接下来应该做点什么?别人会如何评价我的行为?我接下来要读什么书?要学点什么?等等等等。

佛教也认为痛苦来自对过去和未来的纠结,过去的已经过去,未来的还没发生,我们拥有的只有当下,我们应该在享受当下的幸福。而我意识到,我一直在未来寻找幸福 —— 读这些书会幸福、买这些东西会幸福、写这些东西会幸福。但是,一旦真正实现,我还是觉得不幸福,因为未来已来的时候,我又从未来的未来寻找幸福在哪里。我一直忽略了感受当下。

人们总是用一生来等待开始新的生活,这是很常见的现象。等待是一种思维状态,意味着你需要未来,而不要现在;你不要你所拥有的,而要你所没有的。任何一种形式的等待,都让你无意识地在你的此时此刻创造了一种内心的冲突:你不要此时此刻,你把希望寄托于未来。丧失对当下时刻的意识,会大大降低你的生命质量。

设定目标并努力去实现目标本身并没有错,错误的是你将它看成是你对生命和对本体的感受的替代品。

—— 《当下的力量》

在这一周的冥想,我只记得就只有短暂的 2 秒钟,我仿佛真正地感受到了当下。我不再想过去,也不再想未来,我只感受到当下,听到鸟叫,虫鸣,风吹过脸上。有一瞬间,我感觉被一个不知名的气泡笼罩,在这个气泡里有很大的安全感,我感觉我在此刻是无敌的,因为过去已经过去,未来的我不再去想,当下我可以做任何事。

你还可以通过将你的注意力集中在当下这一刻,从而在思维中创造那种空白。就是全神贯注于当下的时刻。这是一件非常值得去做的事情。通过这种方式,你可以将意识从思维活动中引开,并创造一种无思维的空白。在这种空白中,你高度警惕,注意力高度集中,但是你没有在思考。这就是冥想的本质。

—— 《当下的力量》

最后

一周的时间很短暂,但我想我这辈子都会记得这一周。离开寺庙,我还是比以往更慢地做一切事情。慢慢走、慢慢吃,没有事情是那么的紧急,手机并没有那么着急去看。不必用尽一切去填满我的时间,吃饭不看手机并不会损失任何东西。保持冥想练习,保持对自身情绪的觉察力。

最后三天我只用手机在离开前和我的老师拍了张合照。K.K. 是个非常棒的老师,跟我们讲了很多佛学知识,带我们做冥想练习,在傍晚带我们在外面欣赏晚霞和自然的宁静,在大雨的清晨 5 点多带我们听着雨声和鸟叫冥想,最后一天还送给我们每人一串带上他的祝福的珠链。

他说,monks get no salary in Thailand, I’m teaching you for free. 离开前我把我身上所有现金都 donate 给了他。

这是他在 YouTube 上曾经接受过的采访:https://www.youtube.com/watch?v=3g7TUT1kjC8

FAQ

教学用的语言是什么?

英语。我认为高中英语成绩不太差的水平就能听懂。

在哪里可以报名?

Wat Umong 不需要提前报名,每天早上 8 点半前报到既可。

Wat Suan Dok 的 monkchat 在官网上可以预约。3 days 的每月都有一次。虽然有 2 days, 但我建议 3 days.

复读和命运

冯大辉 Fenng 发了一条 Tweet

对于各位高考过来人,如果成绩只能上二本,要不要复读?

以下是我的想法。

我当时考到的是二本,除了学费贵一点之外,我觉得都挺好的。我个人的浅见是,命运是没有人能预测的,复读和不复读是两个不同的分支。当然这说了等于没说。所以我来试试纸上谈兵一下:

我们假设复读一定能考上一本,那么我们可以把问题简单化为一个博弈:是否用人生的一年时间去换这个一本学历?

我有一个高中同学选择了复读,最后考到了比不复读更好的学校,但是在我对他的理解和我的个人认知里,他复读和不复读,并不会有很大的区别。

在我有限的经验观察里,人和人的距离,一本和二本的差距只占了非常非常小的部分,最重要的是人本身,他是否能客观地认知自己,自己的目标在哪里,想要的生活是什么,追求的东西是哪些。再以此为前提,是否能为了目标去努力地寻找资源,运用自己的主观能动性,去趋向自己的目标。然后在运气来临的时候抓住它。

就像有些人对我的评价,我「只不过是每次赶上了末班车」。我觉得这个评价很正确。我原本是 2018 年的大学毕业生,但我 2015 年就退学了。没有人知道我退学之后是好还是坏,我也不知道,但我们马后炮地回看,当时的时间点退学,让我赶上了互联网发展的「末班车」,如果我等到毕业,在这鼓浪潮开始退去的时候,我这个二本学生,不一定能混到我现在的状态,这辆「末班车」早就跑了。

我的例子不值得效仿,能不能坐上「末班车」,除了时机,还有我早就付出过的那些别人不一定看得到的努力。但我想表达的是,在做选择的时候,不能轻视了「时间」这个非常重要的因素。早一年和晚一年,会认识不同的人(这决定了人脉),会影响你身处在浪潮哪个位置,等等等等。

这个世界就是一连串随机事件的结果,它没有好坏之分,好坏都是靠自己的努力去定义的。如果这位朋友对自己的目标并不明确,那么早一年晚一年,好像也不会有什么本质上的区别,只要自己复读的一年里心理状态可以保持得很好,那复读也不是一个坏选择。

只有在「有目标 + 肯努力」的状态下,才能把人生这张彩票玩成一场能拿一手烂牌打赢的德州扑克。

我的笔记管理法

本文视频版:

作为一个程序员,我每天都会消化大量的信息。我以前总是会担心我花了这么多时间读各种各样感兴趣的内容但最后都派不上用场,或者不知道怎么找到想要的东西。所以我喜欢学习一些关于知识管理的方法论,然后结合自己的情况慢慢摸索出一套比较适合自己的笔记方法。在这个影片我就要给大家分享一下我从浏览信息到记录笔记再到输出的整个过程是怎么样的,我是怎么在我的笔记工具里整理我的笔记的。

其实与其说是在「记笔记」,我觉得更贴切的说法是我们在构建我们自己的「第二大脑」。我们本身的大脑的长处不是记东西,而是思考和创造。所以我们需要借助计算机创造一个我们的「第二大脑」,把我们见过的有价值的东西一直记住,等我们需要的时候再从里面找出来用。

当新的一天开始,信息就会不断地进入我们的大脑。如果我们想把我们接收的信息和知识好好地进行管理,我们必须先想清楚我们的信息来源到底有哪些。

信息输入

以我为例,信息来源主要来自:

  • 脑子里突然出现的想法、灵感。
  • 我关注的社交媒体的信息流。
  • 读书、听播客。

想法、灵感

对于脑子里突然出现的想法和灵感,如果我在用电脑,我会写在 Logseq 的 Journal 里;如果我在外面,我会立刻用手机写下来。我自己用的是 Drafts 这个 App, 因为我觉得它的 tag 比 Apple Notes 的要好用。但是你可以用任何自己喜欢的 App, 可以是自带的 Apple Notes, 可以是 flomo 等等。无论是什么 App, 最重要的是找一个你能点开就开始写的 App.

至于记下来之后要怎么处理它,在后面我会在讲组织笔记的方法时详细说明。现阶段你要知道的就是,你必须找一个点开就能写的工具帮助你抓住那一刹那的灵感。

关注的信息流

我每天起床洗漱完开始打开电脑后,都会开始进入我的「快速浏览」模式,这个过程的主要目的是从我固定的一些信息获取渠道快速地了解有什么事情正在发生。

比如我每天会固定浏览这几个信息来源:

  • Twitter
  • HackerNews
  • YouTube

我在 Twitter 上主要是关注了很多国内外的程序员、设计师、产品经理、独立开发者,还有像 Paul Graham, Naval 这样的神级人物。我每天可以从里面看到我关注的这些我很欣赏的人他们在关注什么事情,有什么最新的技术,有什么最新的观点,他们在读什么书等等。

另外一个是 HackerNews, 应该算得上是世界上最大的程序员社区了,在上面可以让我发现技术圈目前最被关注的事件是什么,当然还会有很多技术之外的好文章。

还有就是 YouTube, 我在 YouTube 除了关注一些娱乐的内容之外,还会关注一些关于生产效率、技术等等的一些偏严肃的内容。我只会在晚上睡前刷 YouTube, 早上一般来说不会主动打开 YouTube.

「快速浏览」的关键在于要把重点放在「发现」而不是「吸收」上面。因为前者花的时间很短,而后者会很长,最有效率的做法是,看到你感兴趣的,就把他扔在一个统一的地方,然后忘掉,去看发现下一个。等刷完你的时间线后,再开始「吸收」刚刚扔进来的一堆信息。这有点像你在 shopping, 把你想要的都放在购物车上,然后回家再把这一车的东西吸收整理。

应该统一放在什么地方呢?我个人在不同的情况下会用不同的工具。我感兴趣的内容一般会有以下几种:

  • 新的语言、框架、库
  • 我感兴趣的文章、视频
  • 新的产品

对于我感兴趣的文章和视频,我会首先打开它们快速地略读,如果确定我有兴趣深入地读,我就会把他保存到 Readwise Reader 这个稍后读工具里。无论是文章还是视频,我在浏览器只要点一下就可以收藏。

这里要注意的是,很多人用稍后读工具,就是点一下保存之后就把页面关掉。而我会做得更多一些,我会根据这篇文章的内容打一些 tag, 这样等到将来我需要写关于某个主题的文章时,可以在 Readwise Reader 里通过标签直接找到关于这个主题的文章。

我还会在这篇文章加上注释 —— 为什么我想读这篇文章?我想从这篇文章里得到什么?我会强迫自己添加一篇稍后读的文章的时候思考这个问题,并且用十几个字简单地描述。这样当我在之后读这篇文章的时候,我可以带着我的问题去阅读,这样会更有效率。

如果看到的是新的产品,新的技术,新的开源库之类的,我会点进去快速地了解一下这个东西,如果我觉得可能在日后我会用到它,我就会把它保存到 Raindrop 这个书签管理服务。首先是给这个东西打上合适的 tag. 然后如果我觉得我需要更深入地学习这个技术,我会把它存到一个叫作 Inbox 的文件夹,我会在「消化」内容的阶段检查我的 Inbox 文件夹。

总的来说,第一个阶段就是快速地把我感兴趣的内容捕捉下来,根据不同的内容类型分别存到 Readwise Reader 和 Raindrop 里面。

读书笔记

我近一年最主要的读书渠道就是微信读书,我会在书里面直接划线和写想法。可惜微信读书没办法同步到 Readwise, 所以我写了个叫 NotePal 的工具帮我把微信读书的笔记转换到 Readwise 的格式,然后我会在读完一本书之后手动地同步到 Readwise.

内容消化

我一般会在晚上下班后的闲暇时间开始我的「内容消化」阶段。这个阶段主要的任务是把我在「快速浏览」时捕捉到的东西认真地消化。所谓的「消化」,其实指的是我试图从这些内容里面:

  • 学到新的东西
  • 得到新的灵感、想法
  • 得知新的工具,将来解决某些问题的时候可以搜到。

在 Readwise Reader 里,我会在读到让我有所收获的句子下面划线,然后打 tag, 有时候还会写下我的想法。我比较喜欢用 Readwise Reader 是因为他还包含了 RSS 订阅,我可以直接把它当作 RSS 阅读器读到我订阅的博客,然后直接在上面做笔记。而且如果保存了一个 YouTube 视频,它会把字幕显示出来,可以直接在字幕上划线做笔记。这些笔记会自动同步到我的 Logseq 里面。

稍后读的内容过完了之后,我就会检查 Raindrop 的 Inbox, 如果我被某个产品或者技术启发出来了新的想法,我会直接记在 Logseq 里。

笔记组织

把东西记在笔记软件里只是第一步,更重要的一步是怎么去组织这些笔记。组织笔记的目的不是为了满足强迫症,而是尽可能地让我记下来的笔记更容易在我需要的时候被我发现。我一般是在把笔记写进 Logseq 的同时做好组织的,因为在 Logseq 组织笔记的形式无非在关键词里加上双向链接,或者打上合适的 tag.

但我后来发现如何打好 tag 是一个学问。在 Building a Second Brain 这本书里,Tiago Frante 对如何组织笔记提出了一个框架,叫 P.A.R.A. 也就是 Projects, Areas, Resources, Archives. 这个框架主张把笔记分这四种不同的类型存放。

  • Projects 指的是正在做的项目,比如正在学习的某一门课,正在开发的某个产品。
  • Areas 指的是你感兴趣的领域,比如理财、编程、职业规划、商业等等。
  • Resources 指的是在未来可能会有用途的东西,我的理解是那些你可能会用到的,将来可以立刻作为参考的东西。比如最近我会看到很多关于 Prompt Engineering 的东西,虽然这不是我的领域,但我觉得将来会有用,所以我的 Logseq 里会有 Resources/Prompt Engineering 这个页面,跟 prompt 相关的笔记都会带上这个 tag.
  • Archives 指的是你已经完成的项目。

我觉得 P.A.R.A 这个框架给了我很大的启发,我在 Logseq 里实践了这个框架,并且根据我自己的情况,我对这个框架进行了一些适用于我的改造。

  1. 我基本不需要 Projects 这个分类,因为我有用其它任务追踪工具。
  2. 同样地,我不需要 Archives.

因此我只保留 P.A.R.A 里面的 Areas 和 Resources 两个分类。

另外,在 Building a Second Brain 这本书里有提到关于物理学家费曼的一个故事。很多人问费曼是如何做到在这么多领域都能获得成就的,费曼分享了他的一个技巧:他说他会在心中记住十几个他很感兴趣的问题,即使这些问题他不会主动拿出来钻研,但当他每次看到一些新的发现,新的研究成果的时候,都会把这些研究发现去匹配他心中的那些问题,看这些新的发现能不能解决那些问题。

You have to keep a dozen of your favorite problems constantly present in your mind, although by and large they will lay in a dormant state. Every time you hear or read a new trick or a new result, test it against each of your twelve problems to see whether it helps. Every once in a while there will be a hit, and people will say, “How did he do it? He must be a genius!

读到这个故事的时候我非常受启发,我觉得的确是要把做的笔记也归纳到自己在思考的问题那里。这样日积月累,我的问题下面的笔记就会越来越多,多个笔记串起来会对我的问题更有帮助。

所以除了 Areas 和 Resources 这两个分类,我还建立了 Questions 这个分类,我把一直在困扰我的一些问题都建起了相应的页面,当我在记笔记时,如果觉得这个笔记和我的某个问题有关,就会链接过去。

无论是哪种组织框架,我觉得最重要的是让记下的笔记尽量容易被找到。当我对自己关注的 Areas 和 Resources 有一个大概的梳理后,我对之后记的笔记最后应该流向哪里也有了更清晰的认知。

检索输出

当我开始想要就某主题开始写作的时候,我就会打开 Typora 和 Logseq, 在 Typora 大概列出提纲,然后针对这个主题在 Logseq, Raindrop, Readwise Reader 里面检索相关的资料。

得益于双向链接,我在 Logseq 检索自己想要的东西的时候还算方便,因为只要你的双向链接和 tag 打得够好,在后期检索的时候你能看到很多不同的笔记互相关联。至于怎么在 Logseq 里面灵活地跳转,不属于这次要讲的范围。之后可能会单独做影片再讲讲。

总结

这就是我最近自己摸索出来的管理我的笔记的方法。你不一定和我用一样的工具,我觉得我想传达的最核心的观点的:列出你自己每天接收信息的来源。找出一个你认为最方便的方法把你从这些信息源读到的有价值的内容保存下来,然后想清楚它最终应该流向什么地方,可能是某个 tag, 可能是 P.A.R.A 里面其中的一个类型,或者是你自己的组织方法。当你有了自己的一套熟悉的方法论,你就会更清楚日后应该怎么找到你想要的东西。

28 岁,我还想生猛下去

今天是我的 28 岁生日。

在 2016 年的生日,我在博客写下了一篇《21 岁我想说的》,那年我进入了「大厂」,获得了世俗意义的「成功」,于是写下了这篇充满雄心壮志的文章。我觉得我离自己想要的「成功」越来越接近,我终于要用技术让别人刮目相看,用技术做点有意义的事情,帮助公司的业务 move fast. 我 13 岁就梦想的事情,在那一年成真了。

很感谢当年的自己写下了那篇文章,记录下来我的年少轻狂。就像王小波在《黄金时代》里所说的:

那一天我二十一岁,在我一生的黄金时代。我有好多奢望。我想爱,想吃,还想在一瞬间变成天上半明半暗的云。

但是直到今天我 28 岁,我才深刻地理解这段话的下半段:

后来我才知道,生活就是个缓慢受锤的过程,人一天天老下去,奢望也一天天消失,最后变得像挨了锤的牛一样。可是我过二十一岁生日时没有预见到这一点。我觉得自己会永远生猛下去,什么也锤不了我。

回想我的 18 岁,2013 年,高二。我很迷茫,课桌边上的《JavaScript 高级程序设计》已经被我翻了不知道多少次。后来某次技术大会,我有幸和译者曹力一起吃了个饭,我说我是看你翻译的书长大的。我们一笑而过。但他应该不明白这本书对我的意义有多大。

周末不能回家,我就去网吧写代码,写开源库,妄想可以在社区做出一点什么。那时我觉得没人懂我的抱负,身边的同学不知道 JavaScript 是什么,他们只知道我成绩不好,不爱学习。这真是冤枉,我比大多数人都爱学习。

我迷茫是因为我不知道我的路应该怎么走。我因为学编程耽误了课业,但我真的有机会靠编程吃饭吗?是我太幼稚、太理想主义了吗?我会不会到最后,课业和编程都做不好?

在那些迷茫的时光,读书和写作拯救了我。我一有空就去学校图书馆看书,读了很多现代作品(钱钟书,胡适,王小波,阿城,等等)。还读到了毛姆,那本《刀锋》改变了我很多。

每次语文考试我都很开心,因为可以写作文。我喜欢借题发挥,格子都不够用。我根本不在意因为离题被打低分,我想表达,我想在文字中创造美。那时的语文老师很懂我,是当时为数不多让我感受到被理解的人,我很感谢他。

但我还是迷茫,有时我宁愿可以像那些「好学生」一样,心无旁骛,家长开心,老师安心。但我做不到。

19 岁那年,2014,我高考。学校照例开「成人礼」,在大堂给大家打鸡血,奋战高考。我觉得可笑,但又觉得没资格,我只是一个连高中课程都应付不来的自以为是的可怜虫。

高三一整年我恶补了我落下了课业,我像自学编程一样自学高一高二的课本,我把整个课本一字不落地读,因为我发现很多考点都出在书本里一些很隐秘的角落。我像在做一个试验,用最有效率的方法尽量提升我的成绩。于是我买了历年高考真题,认真研究每一年的规律,哪些东西一定会考,题的难度是什么样的变化,基于这些规律去定制我的备考策略。

高考完以后,我感受到了一股自由的气息。窗外在木棉树上叫的鸟,对于还在上学的人来说,多美都和她毫无关系,而对于高考完的我来说,我就是那只鸟,我想叫就叫,我想飞就飞。

很幸运,我的备考策略算是成功了。大家都觉得我考不上本科,但我最终还是考上了一所不怎么样的本科。

我不想再回顾我的大学,我在博客说得已经够多了。但有一件事很有趣,我在博客也提到过:

这是一封 2014 年我写给未来的自己的信。当时在上大一,是上职业规划课时老师布置给我们的一个作业,写完我们就交给老师保存,他会在我们大学毕业后返还。

老师通过微信联系我说有封信在他那里,并拍了这张照片。我早已忘掉我曾写过这封信,看完后我就全记起来了。

记起来刚上大学时对未知的憧憬,记起来在教室听无聊的网页设计课,记起来退学那天给宿舍阿姨还了钥匙后坐上中巴回广州市区的时候听的那首《一路向北》。

退学后我正式加入了一家创业公司,严格来说是从退学前的 part time 最后决定了退学做 full time. 没有人给我意见,应该说没有人能给我意见。选择是困难的,因为没有人知道选择的结果一定是什么。我在那一刻意识到我是一个成年人了,不像考试时的题,人生给你出的题是没有固定答案的。选择的意义,是在选择后你做的事赋予的。

那是一段艰难又快乐的日子。

快乐是因为我觉得整个世界的大门为我敞开了,写代码从此不是我「不务正业」的事,我终于来到了我的主场。

艰难是因为我没有钱。我在珠江新城上班,在地铁沿线的番禺大石租了一个 600 块一个月的城中村房子。房子在二楼,没有光线,窗外是墙壁。自来水是有异味的,我只能买桶装水洗漱。出门上班要戴口罩,因为外面路过的都是货车,沙尘滚滚。

只要 600 块的房子,因为要押二付一,刚从学校出来,我付不起。我向老板提前出了下个月的薪水才租下。

那时我开始听《IT 公论》,播客的声音让我觉得,即使我在这昏天黑地的小房子里,我和他们一样,都在这互联网的浪潮里参与其中,我已经不是被困在学校里的人了,我要在其中大展拳脚。

拿到阿里的 offer 完全出于我的意料之外,即使我很喜欢当时的创业公司,以及两位创始人,但在这样的机会面前,我还是选择了「大厂」。

我记得当天面试了几轮,最后跟 HR 见面聊了一下期望,我说了一个数。回家的路上,觉得自己是不是开得有点高了,会不会因为开高了让我错失了这个机会?在我刚坐到家附近的地铁站,竟然通知我有 offer 了。

在之后,我开始面对别人附加给我的光环 —— 年轻、辍学、年少有为。我一点也不以此为荣,我只感到害怕。我不是年少有为,我只是笨鸟先飞了,我在别人还在读大二的时候,我就提前进入了社会。我总是想,我的同学都很聪明,等他们毕业了,也许只要一年,就能学到我提前学到的东西了。

我很享受这样的工作,因为编程就是我的生活,我享受她。当年的我甚至很奇怪,为什么竟然有人不想上班?上班写代码是我最开心的事,她给我带来了成就感,我在用我的能力创造价值。

这鼓热血,真是充满魅力。

时过境迁,那鼓热血已经花光在最该花的年纪了。热血退去,我才明白,编程是我真正想要追求的东西里最表面的一层。我在意的是,我用代码实现了什么,给用户带来了什么价值。我想用我的品味和价值观,创造一些美好的东西解决实际问题,并且做成一种可持续的商业模式,又或者创造一种商业模式。

商业是一种艺术,赚钱的方式也有千万种。我想用体面和美在这个世界上生存以及留下一点什么。

希望我自己会永远生猛下去,什么也锤不了我。

你看,迦摩罗,如果你将一粒石子投入水中,石子会沿着最短的路径沉入水底。恰如悉达多有了目标并下定决心。悉达多什么都不做,他等待、思考、斋戒。他穿行于尘世万物间正如石子飞入水底—— 不必费力,无需挣扎;他自会被指引,他任凭自己沉落。目标会指引他,因为他禁止任何干扰目标的事情进入他的灵魂。这是悉达多做沙门时学到的。愚人们称其为魔法。愚人以为此乃魔鬼所为。其实,魔鬼无所作为,魔鬼并不存在。每个人都能施展法术。每个人都能实现目标,如果他会思考、等待、斋戒。

—— 赫尔曼·黑塞《悉达多》

2022 年终总结

今年的年终总结写得有点晚,2021 年觉得自己一事无成,迟迟没有动笔。如今 2022 年也已经过去了,虽然同样觉得自己在今年一事无成,但转念又想自己应该给自己一个回顾的机会,终于还是动笔了。

琐碎的生活变化

2022 年因为工作的变化,主要都生活在苏州。苏州是一个很「慢」的城市,我算不上特别喜欢,但我很喜欢苏州的湖。我在 ByteTalk 的这期播客节目 里提到过一些我对苏州的看法。

苏州的一些区域可以骑摩托车上路,我喜欢摩托,所以买了一辆摩托通勤,周末偶尔也会到不同的湖边溜溜车,还认识了一些摩友。

意外的插曲

我从多年前开始受情绪问题的困扰,在 2021 年开始变得更严重,到了 2022 年,它变得更加凶猛。

那根一直在紧绷的弦,最终还是断了。我在年中因为焦虑导致了惊恐发作, 这是一种焦虑的躯体化症状。由于没有相关经验,发作时我误以为心脏出现了问题,于是拨打了 120. 这也是我第一次坐上救护车。那一周我住进了医院,做了身体检查,排除了器官问题。

我的情绪问题导致了我在 2021 和 2022 都没有写什么工作以外的代码,我的内心一直在希望自己放松和希望自己多做点事情两者之间不断摇摆。我曾经认为自己是一个很清楚自己想要什么,也很有目标的人,但最近两年,我越来越不知道自己在做什么,要做什么。

零碎的产出

Logseq 分享

我在一个晚上非常即兴地录了一个关于我如何使用 Logseq 的视频,没想到会有这么多的播放量,在 YouTube 上也收到了不少的感谢和鼓励。

CodeSpeedy

发布了一个我自己个人在用的 Code snippet 管理工具 CodeSpeedy. 是我第一个发布的用 Tauri 写的程序。

AMAzingTalk

因为看到 Twitter 上有不少人提供付费咨询服务,所以做了一个页面收录了一些不错的提供咨询的人。目前只是一个简单的静态网页,但我有计划把它做成一个产品。

《兰迪和他的朋友们》

我身边有不少不同领域的有意思的朋友,很想通过访谈和交流的形式把他们各自领域的哲学分享给更多人。于是我开了一个不定期更新的 podcast 《兰迪和他的朋友们》。目前只出了两期,分别是和我的健身教练聊了健身,还有和我的同事兼攀岩岩友聊了攀岩。

新的一年我还计划做一个和香港流行音乐相关的节目,感兴趣的朋友欢迎 Email 和我交流。

攀岩

今年在我的同事 sixian 的带领下接触了攀岩,我也意外地爱上了这个运动。喜欢和岩友一起交流线路的不同爬法和技巧,喜欢用出优雅的脚法的感觉,喜欢到不同的城市拜访当地的岩馆 —— 例如我和 sixian 到 上海攀岩工厂探店 (Bilibili).

攀岩是一个力量和技巧结合的运动,我因为有力量训练的基础,让我入门这个运动非常快。但我喜欢学习和应用技巧的部分,我对攀岩的兴趣很大程度上来自于观看了 Jain Kim 的优雅脚法.

感染 Omicron

在 2022 年的尾声感染了 Omicron, 反复发烧 2 天多,咳嗽了近一周。我在开放了一周后才打了第一针科兴。我的症状比较轻,没有失去味觉和嗅觉,也没有所谓的「刀片噪」,只有轻微的扁桃体发炎类似的感觉。

最后

2023 年我就 28 岁了,有时候填表格看到年龄的一项写着 27, 会让我有些感觉陌生。我总是觉得自己距离踏出学校的那天还不是很远,在写做了 6 年程序员,我学到的 10 条经验 时我才发现自己已经工作了 6 年多了。我也偶尔看回 我 21 生日时写的博客, 很多问题在我内心缠绕:我还能做些什么?我想要的到底是什么?我的价值是什么?我接受了自己只是个普通人吗?

我没有答案。

做了 6 年程序员,我学到的 10 条经验

保持一颗解决问题的心

按照我的观察,那些在工作中用技术取胜的人们共同点都在于他们能保持一颗解决问题的心。他们可以率先想到一种更优的手段解决存在的问题(一般是效率问题)。他们不是嗅觉特别灵敏或者技术特别强,而是当他们遇到了问题,不是把它作为抱怨的话题,而是开始思考这个问题为什么没人解决、应该怎么解决,然后把它实现出来。这种心态在职场上特别稀缺。

我在创业公司的时候做一个图文排版的 App, 设计师会设计一些模板,然后交给我来实现。当时我们有很多模板,为了测试这些模板实现在不同的手机屏幕大小会有什么问题,我们要花特别多的精力。可以想象测试的数量 = 模板数量 * 屏幕尺寸的数量。后来我用 puppeteer 写了个自动生成不同屏幕和模板的截图,直接交给设计师一个一个地看。节省了大量的时间。这个事情没什么技术含量,但它解决了很重要的问题。

工作中需要解决的问题不仅仅在代码上,也有可能出现在非技术问题上。工作中我特别喜欢和非技术同事聊天,了解他们的工作。因为我常常觉得影响项目前进的原因不一定出在我们用了不适合的技术或者不够「先进」的技术。了解非技术同事的工作流程让我大有收获,我会发现他们有一些工作是可以通过写一段程序把原本的工作量做到指数级的下降,而通常非技术同事是很难察觉到的。

这样的例子特别多。有次我和一个运营同事聊天,我们当时在开发一个新闻内容的管理后台,他们常常用这个后台捞一些内容做分析。聊天的时候了解到他们有一部份的工作就是在上面按条件查询一些内容,再一条条地粘贴到 excel 里面,他说这常常要花一下午。后来我帮她做了一个一键导出成 excel 的功能。

她觉得这很不可思议,但这在技术的角度来说太简单了。我也因此了解到,对于不是做技术的人来说,他们很难察觉到哪一些事情是可以用技术解决的,所以我们不能希望他们主动地提出一个需求,只能我们作为掌握技术的人主动地去了解他们。

又一次我和我们的测试吃饭,聊到他们怎么做测试。我发现他们会用 mindmap 先梳理出来一些测试流程,然后一个个地做。但是痛点在于他们常常要手动维护一个文档列出这些 case 的测试结果,这些结果包括截屏,以及证明测试通过的请求返回信息等等。不但麻烦,还很难追踪。

于是我做了一个小 demo Web App,他们可以直接上传他们做好的 Mindmap, 通过他们的 mindmap 直接生成出来 case item,在项目的开发环境页面代码里面,只要注入这个 case id,就可以在测试之前开始记录请求日志,结束之后会上传到这个平台,这样在这个平台就能直接看到每一个 case 操作的时候的整个过程的记录。这个小 demo 后来被用于花呗的大部份前端项目,当然听说现在已经做得和我当时做的小 demo 完全不同了。

了解你的用户

我自认为自己还算是一个有那么一些产品思维的程序员,因为经常也会写一些自己的小产品。但在刚出来工作的时候,我在工作中太沉迷于技术本身。把心思都放在了诸如怎么重构,怎么改进构建速度之类的问题。我在阿里 P5 升 P6 的答辩中,我被问了一个我至今印象深刻的问题:你有了解你的用户是怎么用你在做的这个东西吗?

这个问题是我从来没有想过的,我哑口无言。可能它只是一个晋升答辩问题模板中的一个问题,但对我来说这个问题让我清醒了许多。当时我们做的是内部用的新闻内容管理后台,这个后台的用户是一些小编。我们和这些小编有一个群,但基本是用来报 bug 的。我离这些用户这么近,却从来没有了解过他们的使用感受。我想,如果我当时找他们聊一聊,可能也会有意想不到的收获。或许他们会报怨这个后台的加载速度很慢,我们就可以着手解决加载速度的问题,而不是和同事纠结在用哪种前端状态管理库这种无聊事情上。用户并不关心我们用的是 MobX 还是 Redux.

不要拿自己的尺子去度量别人

我刚出来工作犯的最大的错误之一就是拿自己的尺子去度量别人。我因为从小对编程痴迷,写程序对我来说是人生中最大的兴趣,我把几乎所有的时间都花在了技术上。当时我天真地认为所有程序员都应该像我这样,对待技术也应该有一种理想主义,我在互联网上结交的技术朋友都是这样的。所以我当时对我的同事特别苛刻,甚至对那些把写程序只当成工作的人嗤之以鼻。后来回想起来,这是非常错误的想法。每个人有每个人的追求,技术也只是多个兴趣爱好的其中一种。在当时别人的眼里我可能是个「怪人」,甚至有点「装逼」。

保持学习、be open-mind

我每天都会在 Twitter 和 Hackernews 发现很多最新的技术和技术思考,我关注了很多开源库的作者,我可以第一时间了解到他们最近在思考什么,在接触什么。这种主动接收会扩大你的眼界,让你在解决问题的时候有更广的思路。

不要只关注自己的领域。我还关注了很多写 Rust, 写 Go, 写 iOS/Android 的人。学习主要是学习技术背后解决问题的方式,这些解决问题的方式说不准也能应用到你自己的领域。

保持学习一直是和同行拉开差距最重要的一点。

想清楚,再下手写代码

我写代码的速度非常快,因为我已经花了超过十年的时间在写代码了。很多东西想实现,对我来说基本是纯粹的堆代码。导致我非常容易不经过多的思考就开始动手写。我为此吃了不少亏,常常写到一半发现一些没有想到过的问题,导致需要重新设计,重新改写。我的一位前老板很了解我,他也是个多年经验的程序员了。有一次我们在讨论一个新东西,他对我说,「不要着急,想清楚了再写」。这句话我一直记在心里。后来每次动手写代码之前,我都会把整个流程的设计先思考清楚,避免了很多不必要的重写。

敬畏用户

在写自己的一些没什么人用的开源库或者公司内部用的平台的时候,通常不需要过多思考就能把代码发布出去。到了做花呗这种用户基数庞大的产品,才意识到代码发布和以前所体验到的完全不同。

蚂蚁金服有代码发布的「三板斧」,这是从入职培训到实际工作中就会被反复提及的 must-follow rule. 「三板斧」指的是「可灰度」、「可监控」、「可回滚」。在代码发布之前,要先想想自己的代码是不是符合这三个条件。

  • 你的代码发布之后,如果出了问题,是不是可以被监控到的?
  • 你的代码是不是可以灰度发布的,而不是一下子全量被推到线上的?
  • 代码发布以后,出了问题,是不是可以回滚的?如何回滚?

在经历了用户基数如此庞大的产品开发之后,我对代码发布变得尤为审慎。我记得有次只是单纯改了某段 HTML, 但我还是盯着这个 PR diff 看了几分钟,在想这个修改会不会导致什么问题。

虽然蚂蚁的基础建设可以让这三板斧很容易实现(有成熟的发布平台进行灰度和回滚,有成熟的监控库);虽然即使遵守了三板斧,还是会有 bug. 但是这种代码发布的思维模式是好的,即使我到了别的公司,我在代码发布前还是会问自己这三个问题。

跨团队合作是利益交换

在大公司里,有时在做一个事情的时候,需要别的团队一起合作,或许是用到别的团队的接口、或许是需要别的团队开发新的接口,但这通常很难。我以前天真地以为,只要我们做的事情是有利于业务的,别的团队自然就应该一起合作。但实际上,大家更看中的是这个事情对自己的团队有什么好处。

换位思考一下,我们和别的团队合作,对于他们来说,增加了工作量,增加了风险(带来更高的 qps, 写更多的代码会导致更多的维护成本)。决定是否合作,首先取决于这是否是自上而下的要求,其次就是合作对他们的 KPI 有没有好处。

所以我学会了在游说别的团队合作的时候,首先应该想明白合作能给别人带来什么好处,而不是对事情本身夸夸其谈。这样更容易促成合作。

用别人的语言交流,会有意想不到的收获

有研究发现如果你用别人的母语和他交流,他会更容易接受你的观点,对你也会更友好。我发现这个心理同样适用在技术交流中。作为一个前端程序员,在和后端程序员商量技术方案的时候,如果可以更多地使用后端的术语,从后端的角度反推前端的想法,他会更容易接受。

我自己业余是个 full stack 程序员,所以很容易切换到别人的语境,也能从别人的角度去理解他的想法。因此沟通会更加顺畅。

理解前人写的「烂代码」

这里的「理解」不是指理解烂代码的逻辑,而是理解为什么会写成烂代码。我经常会听到同事报怨他看到的旧代码写得如何烂,但是实际上很多烂代码产生的原因不是因为技术不行,而是受限于技术的发展和业务的复杂性。随着自己写的代码越来越多,就越能理解这些「烂代码」的存在。看出来了烂代码,也不要着急去重构,这些代码很有可能藏着一些你不知道的特殊业务需求。如果你不需要碰这些代码,那就尽量别碰。

在技术和工作之间找到平衡点

在刚出来工作的前几年,我特别陶醉在把自己学到的新东西试图用在工作中。我的想法是,只有我把这个技术用到实际的工作中,我才算学习了这个技术。

其实这个想法是不对的,学习技术并不一定要求你把他用到工作中。工作就是工作,学习就是学习。工作的内容是为了业务服务的。我在创业公司工作的时候,曾经因为把一个我刚学习到的库用在业务中,因为一些我不知道的坑导致业务进度出了点问题。老板生气地说:业务不是你的试验田。

我后来遇到很多「后辈」(我竟然也开始有后辈了) 请教我说觉得自己在工作中不能运用到自己平时学习的技术,因此觉得自己技术没什么长进。我认为这种想法不太正确。

能把学习到的技术运用到自己的工作中当然是最好的,但这是可遇不可求的事。但是这不代表没有用在工作中,就等于没有真正学习到这个技术。我认为很多人对技术学习有错误的理解,对我来说,学习技术的精髓在于理解这个技术的 Why, What, How. 和能不能用到工作中没有太大的关系。

举个例子,我在刚接触到 Redux 的时候,我去学习它,除了了解它怎么用以外,我特别关心的是,Redux 的哲学是什么?是什么启发了 Redux 的作者创造了 Redux? 他和别的库有什么不同?顺着这些问题,我就会了解更多的东西,比如我发现 Redux 是受了 Elm 的启发,我就会去了解 Elm -> 了解 Functional Programming -> 了解 Immutable, 然后关注 Dan (Redux 的作者) 的 Twitter, 看他日常会分享什么,看他对自己做的这个东西的理解是什么。即使我没有把 Redux 用在工作中(事实上我从来没有用过 Redux),但我在学习这个库的时候,我学习到的不仅仅是 Redux 本身,还有它背后的更多东西。我可能很快就会忘掉 Redux 的 API, 但那又如何,那些它背后的知识才是最有价值的,是不会被忘掉的。

而工作则相当于是一个真实的场景,是在你学习新的技术的时候,帮助你进行实际思考的场景。你需要有意识地去想,这个技术如果用到我的工作中,它是否适合?它能解决什么问题?它为什么适合?它为什么不适合。当你在学习新技术的时候,结合这个技术,多思考这些问题,这才是真正的学习。

每个人都能用点正则表达式

有一个关于正则表达式的笑话是这么说的:我们遇到了一个问题,用正则表达式解决了,现在变成了两个问题。

这个笑话说明了正则表达式的复杂,有时还难以维护。如果我们刚接触一个现有的项目,里面出现了正则表达式,我们的第一反应肯定是头疼。因为正则表达式不易读,通常要读好几遍才能稍微弄懂他的意图。

包括我也一样,在很长一段时间里,我对正则表达式的态度是能不用就不用。直到几年前我读完了《精通正则表达式》这本书,我对正则表达式的态度发生了巨大的变化。

这本书的第一章的第一节名字是「解决实际问题」,我在这第一节就领略到了正则表达式的魅力。也让我意识到,你不一定需要正则表达式,但你必需掌握正则表达式的基础,克服对它的恐惧,把它转化成你的工具之一。 在以后,当你又遇到一个问题,你就会意识到正则表达式是其中的一种优雅的解法。当你把正则表达式作为自己的一种工具后,你会发现一些以前你认为需要写一段程序去解决的问题,其实只需要一行正则表达式就能解决。

例如,我通常想要把一个 JSON 文件转换成一个 JavaScript 对象,我想要把 JSON 里的 key 的双引号去掉。以前我可能会先 Google 一下 “JSON to JavaScript object” 看看有没有现成的工具,而现在我首先就会想到,这个问题用正则表达式配合编辑器自带的替换功能就能解决。

假设这是我们想要转换的 JSON:

{
  "name": "Randy",
  "children": [
    {
      "name": "Sandy"
    },
    {
      "name": "Mandy"
    }
  ]
}

我的思路会是:首先我要匹配出所有的 key, 而 key 的特征是以双引号开头,以双引号结尾,后面一定会带上一个冒号,所以我可以用这样一句正则把所有的 key 找出来:

".+":

这里的 . 表示任意字符,+ 则表示匹配一个或多个。

正则表达式其中一个强大的特性是可以把某一个匹配用括号包起来,让它变成一个 group, 这些 group 会以数字编号,从 1 开始。这样我们可以在匹配后,用 $1 这样的形式把匹配到的 group 的值拿出来再处理。在这个例子中,我们用 VS Code 的替换功能,把 key 匹配出来,然后把它放在一个 group 里,在替换的时候,再用 $1 得到 key 的值,用作替换:

"(.+)":

还有很多例子,读者可以举一反三。这篇文章的目的不是教大家正则表达式的用法,而是让大家能意识到,正则表达式是一个很实用的工具,不一定要在项目中用到,在日常的一些文本处理任务中,它能发挥出你意想不到的作用。而正则表达式的基本用法不难记,多用几次就会烂熟于心。当然更高级的用法,也是需要多翻手册的。

读《九宫格写作法》

倪爽Twitter 推荐了这本书,对于喜欢写作的我来说很吸引,花了很短时间就把这本书读完了。在这里推荐给大家。这本书主要给认为写不出来文章的人提供一些指南,书的前半部分是方法论,后半部分是写作套路(或者说写作框架)。

九宫格指的是在写作之前,从文章的中心,可以扩散出 8 个方格,填写和这个中心有关的信息,型成一个写作九宫格。九宫格的作用是在写作前迫使你先对文章中心进行思考、提出问题,然后进行填充。是一种帮助你在写作前收集信息的辅助手段。

所以通俗地讲,九宫格写作法的核心在于训练你在写作前的信息收集习惯。书中有一个比较确切的例子:如果你准备一场旅行,打算旅行过后写一篇游记。你就应该在旅行之前就画好一个九宫格,填上这篇游记你想要写所有的要点:

然后在你旅行的时候,才有目标进行相关信息的采集,然后填充到九宫格里面,这样当旅行结束,你已经收集到了足够的信息,把它们整理成为一篇完整的游记。而不是到了当地漫无目的地游玩,然后回来只能追溯一下往事,已经忘了可以写一些什么,最终写出来的只不过是一些浅显的感受。

九宫格写作法实际上并不要求你只能有九个格子,你可以任意地扩张。所以这个写作方法,中心的思想就是:在写作之前,列出你对于这个写作主题的所有想要讨论的问题,然后再动笔。在列出九宫格的同时,整个文章的结构就自然而然地形成了。

九宫格写作法突出了写作前信息收集对于轻松写出一篇有价值的长文的重要性,这也是为什么作为一个有意向写作的人来说,平时做笔记那么重要。只有平时在信息输入的时候有意识地把价值输入作为笔记存到你的第二大脑(笔记工具)里,将来才可以基于平时的笔记轻松地写出一篇文章。这个过程也叫作「信息内化」。九宫格写作法辅助了你的「信息内化」能力。

这就是创作过程的核心: 在你选择你要写的东西之前,你必须下功夫研究并积累。理想情况下,你应该在创作开始之前就开始研究,这样一旦你决定了一个话题,你就有数周、数月甚至数年的丰富资料可以利用。这就是为什么你要把信息放在「第二大脑」如此重要的原因。

—— 真正的思考技术

我以前也常常觉得很难下笔,就是因为平时即使输入了很多信息,但没有把他们内化成自己的信息。陷入了「写作就是从一张白纸开始」的迷思。几年前机缘巧合下开始使用 Roam Research 并且读了 How to take smart note, 开始把平时摄取的有价值的输入记在了我的笔记工具里,通过双向链接,使我很容易在构思一篇文章的时候得到足够的素材。所以在读这本书的时候我很有共鸣,也对我的写作模式提供了一个更系统的方法论支持。

书的后半部分基本是一些写作的套路(框架),对我来说这部分意义不大。因为我从小就通过在学校的写作和平时写博客中得到了很多实践积累,对于写作的内容(例如哪些应该是文章重点、在文章中如何表达有价值的主观感受等等)我自己已经有了较多的体会。所以这部分我只进行了略读。对于缺少实践的读者来说,这部分也可以细读。

遗憾最小化框架 —— 如何做决定

对于我来说,我一直认为自己缺少做决策的能力。成年人的生活不像学生时代,所有的题目都有固定答案。所有的选择都不存在绝对的对和错,它只是在人的一生 checkout 了一个新的 branch. 我总是不知道应该如何做决定。

刚好看到了 Jeff Bezos 曾经谈到了自己是如何做决定的,他称之为 Regret Minimization Framwork (遗憾最小化框架) —— 他会想象自己到了 80 岁的时候,是否会认为不做这件事情会让自己遗憾。尽量让人生的遗憾数量最小化。即使当时的选择后来看来是错误的,但也不会后悔尝试过。

我很受启发。想起在 4 年前,我曾经问 Cycle.js 的作者 staltz 一个 问题, 我问他是否也会因为自己做的库没有像 React / Vue 那样火起来而感到气馁。他说他偶尔也会感到气馁,但他一直以来做事遵循一个 guideline:

Ask yourself: in 10 years from now, will you be proud of having done this? … Do things that your future self will be proud, is my motto for the time being.

记得在一年前决定是否接收微软的 offer 的时候我非常犹豫,因为微软给出的 offer 比其它国内「大厂」给的少非常多。但是我回想起初中的时候,读了很多关于比尔盖茨的传记、微软的创业故事,十分向往。那时候的我没有预料到我将来有一天竟然有机会加入这家公司。也不会想到作为一个大学没有毕业的人竟然也得到了这样的机会。我也咨询了很多朋友我是否应该接受这个机会,他们有的会说其实在微软写的代码甚至没有在国内的公司那么 fancy. 但是我想,在加入了微软之后即使在后来可能发现这未必能得到我想象中的东西,但如果在很多年后回想起来我曾经放弃了这个机会,我一定会后悔没有尝试体验一下在这家公司工作。所以最后我决定接受了这个 offer.

再谈辍学

我在退学的那一年写了一篇《你根本用不着退学》来告诫想退学的人慎重选择退学这种方式。今年是写完这篇博客的第五年,我想结合五年来作为一个辍学生遇到的经历,再谈谈退学。

我第一次感受到辍学给我带来的副作用是刚辍学不久的时候,曾经给一些「大厂」投过简历,在 HR 筛选简历的一轮就被筛选掉了。我当时对进「大厂」的心理预期本来就很低,所以这对我来说根本影响不了我的心情,被筛选掉反而是正常结果。我很幸运,阿里的其中一个部门鬼使神差地给我了一个机会,我最终顺利通过了面试。

我辍学后在别人眼里过得一帆风顺,是因为我受到了运气的眷顾。我很幸运,我出生在一个小康家庭,可以让我从小就能接触到我往后一生都热爱的计算机和互联网;我很幸运,在辍学前遇到了李秉骏先生(他们现在在做无人机空间数据应用平台),让我即使辍学了,还可以在一家创业公司尽情发挥自己的技术;我很幸运,阿里的 HR 对我的简历手下留情,也许只不过是当时的团队刚好非常缺人,我刚好投了简历,所以我通过了。但如果我说这只是单纯的幸运,那未免有点虚伪。我很清楚,即使我得到了这样的运气,我也需要有足够的实力和其它同样有运气的人比较胜出。所以这是运气和实力结合的结果,但没有运气,这一切都不会发生。

打过德州扑克的都知道,没有人可以靠运气一直取胜。

在这五年,我就是别人口中「幸存者偏差」的那个「幸存者」。但我从来没有因为我的辍学光环感到一点骄傲。相反,我在退学一年后遇到了严重的抑郁和焦虑症状而不得不求医。除了求医,我还天真地坚信只要我保持旺盛的精力去 “make something”, 去不断写代码,去不断地学习,我就能战胜我的情绪病。

但很遗憾,情绪问题是个复杂的问题。我不能说辍学是我情绪病的来源,但它在其中参与了很大的比重。而且这种比重是随着时间的流逝线性增长的。在最严重的时候,我甚至完全无法沉下心做我这辈子最热爱的事 —— 写代码。说出来有点好笑,我甚至会因为在面试的时候分不清 JavaScript 里 class A extends class B 中,A.prototype 和 B.prototype 的关系而陷入严重的自我怀疑。

这是辍学给我带来最大的副作用。它让人把一切成功归因于运气,然后让人从不甘平庸变成不能平庸。

辍学在「幸存者」身上是没用的光环,在不幸的人身上就是噩梦。我能在 V2EX 找到很多这样的例子:

https://www.v2ex.com/t/808601

18 年大学差 5 学分没修完,结业了。 做了两家创业公司,现在 2021 年 10 月份了,想跳槽。

面试了华为、绿盟、建信金科,面试全部通过,全部因为学历拒发 offer 。 中间尝试联系 Boss 上专科要求的岗位,也拒绝了我。。。

https://www.v2ex.com/t/700608

但是技术在当前领域有一定优势。最近开始面试。卡在学历了

在 BOSS/拉钩 /找内推 /猎头 基本上都过不了 HR,基本看到我简历上教育背景后,确认没有双证后直接表示可能不太行。

更惨点的是面试都过了,两级直属领导当面明确表示希望我入职,然后被 HR 用规章制度否了。

https://www.v2ex.com/t/708542

根据招聘网站透漏的信息,HR 筛选候选人的最常用选项的顺序是,学历、年龄、性别、工作年限 哦,忘了说,内推我也试过了,找朋友内推也被挂学历了,一种是他们有硬性要求,二个是能力也没有到无视学历的地步

心里的无助真的很难受,加上心里不断的胡思乱想,真的是有偷偷哭过,甚至一度有人生从来的想法

在这之后的求职经历里,我有因为辍学遭遇到不公吗?我只能说,一个正常的面试官或者 HR, 是一定不会在面试的时候明显地表现出对我学历的顾虑的。但从交流之中就可以感受到,那些对我的学历和辍学经历的「好奇」,其中一定是掺杂着担忧的,至少这它一定不是一个加分项。

程序员是少有可以靠技术发声的群体,也许靠所谓的实力可以在职场混得还不错,但人的一生除了工作,还有太多的事情不是靠技术就能解决那么单纯的。也许是政策上的,也许是流程上的,也许只是别人的偏见。这些都是需要有预期去承受的。你准备好了吗?

就像我五年前写的一样,退学只是一种选择,没有人知道它是好的还是坏的。但它必定附带了很多随之而来的巨大的副作用。不是每一个人都有运气和实力和这些副作用抗衡。这个社会在一定程度上有少数派生存的空间,但社会的很多规则是设计给多数人的。抗衡需要勇气和付出比多数人多得多得多的心血。你准备好了吗?

每个人在漫长的人生中都会面临很多不同选择是否成为那个少数派。可能是辍学,可能是性少数,可能是不婚,可能是丁克。衷心希望在作出了这样的选择之后的人,一定要努力成为那个别人口中的「幸存者」。之所以这么说,是因为这五年来,我见过身边太多有运气但不努力的人,也见过太多既很努力又有运气的人。这让我能感受到,「事在人为」和「何不食肉糜」之间并不能划上等号。用自己的力量去给偏见打一个沉重的巴掌吧。

开着特斯拉从广州到苏州的小记

由于工作变动的关系,我需要从广州搬到苏州。于是趁着这个机会体验了一下开纯电动车长途,是不是大家说的如此不堪。这次的旅途预料之外地非常顺利,在这里简单地记录一下过程和感受。

行程安排

广州到苏州的行驶距离大概是 1500 公里,理论上如果一直开,至少也需要十几个小时。研究了一下地图后,我把整个行程分成了 3 天:

  • 第一天:离开广东(广州)到达江西
  • 第二天:离开江西到达杭州
  • 第三天:离开杭州到达苏州

其实第二天有足够的时候直接到达苏州,但是想在杭州和老朋友叙叙旧,所以安排在杭州多逗留一晚,第二天再出发。这样安排非常合理,每天只需开 400-500 公里。

接着需要规划充电的路线。我使用了「加电」这个 App, 可以查看行程沿途有哪些充电站,以及相隔的距离,这样可以根据自己的车的续航能力,提前安排好在什么地方开始充电。从 App 的显示来看,充电站的数量非常充足,我想应该不会出现太大的意外。

行程中

出发那天天气非常好,蓝天白云,晴空万里。

一直弄到傍晚,感受天色的变化。

第一天晚上到达了江西鹰潭,住了一晚,这是第二天酒店的窗外:

进入浙江后,天气变得糟糕:

充电

出发后,我打算在续航还剩 100 公里时开始进入最近的充电站。虽然我提前规划了大概会去哪个充电站,但我发现在用「百度地图」导航的时候,会在下一个服务站的提示牌上显示这个服务区有没有充电桩,非常方便。

整个行程下来,我发现基本每一个高速服务区都有国家电网的充电桩,所以在高速上基本只要是能加油的地方,基本都能充电。所以在后来我已经不再提前找下一站的充电站,在需要充电的时候直接进下一个服务区即可。并且充电速度的中位数是 60kw. 我的车满电续航是 510+ 公里,我一般只剩 100 公里的时候开始充电,充到大概 500 的时候就继续出发,充电时间在半小时左右。恰好我一般是在饭点的时间到达服务区,所以正好在服务区吃点东西后,就刚好充好电可以出发了。

在服务区充电的电动车非常少,没有遇到过要排队的情况。只在一个服务区出现了坏桩的情况,不过好在只坏了四个中的一个。所以我在出发前对充电的担忧是多余的。

进入到浙江后,国家电网的覆盖就变得很少了,两个有国家电网的服务区相隔差不多两百公里。不过在这之间的服务区有「小桔充电」。

辅助驾驶

这段旅途一路上都是在高速路上往北开,我基本上都是开着 Autopilot, 在车上要做的只是看着路面,握好方向盘。所以这三天的路程并没有因为开车觉得累,不过是太无聊了。体感就像坐了三天的车,但是什么都不能干。我特意在途中尝试了不用 Autopilot, 自己开了一个多小时,最大的感受是:人类长时间开高速真是个危险的事情 😆。

充电费用

整个行程一共充了五次电,总共花了 300 元左右。国家电网的电费大概是 1.3元/度。

如果你也准备购买特斯拉,可以使用我的推荐码:https://aka.taonan.lu/tesla 我们都能获得 1500 公里免费超充。

Xbox Series S 一个月使用感受

把这台 Xbox Series S 抱回家已经有一个月了,当时在淘宝搜索了现货,正好店家就在广州,直接把它自提回家。

这是一台欧版的机器,人民币 2700 入手。

开箱

箱子打开,主机比想像中要小得多:

然后开机。第一件事当然是开通 Xbox Game Pass 啦。一个月 79 港币,第一个月还免费,还是很划算的:

你可以在 https://www.xbox.com/zh-HK/xbox-game-pass 看到 Xbox Game Pass 的所有游戏。

外观

这是我第一台 Xbox, 之前买过 PS4, 现在也有 Switch. 我不是一个很硬核的玩家,我看中 Xbox 的首先是它的外形,非常立体有棱角:

既能竖放,也能横放:

游玩体验

我在拿到机器之后下载了《地平线4》和《光环:士官长合集》, 我自己目前用的电视比较垃圾,只支持 1080 60fps, 所以也感受不出画面的质量,而且 Series S 也不支持 4K. 在普通的显示器我认为比较满足我这种非画面党。

《极限竞速:地平线4》非常不错:

不得不说的是传说中的 Quick Resume, 可以在不同的游戏之前快速切换,而不像之前玩其它主机一样玩另一个游戏需要先关闭目前在玩的游戏:

花了 4 个多小时通关了《瘟疫传说》,个人认为在游戏性上比《最后的生还者》好玩一些:

在 Xbox 的 App 上可以直接获取游戏时截取的图片和视频(比 Switch 好得多了):

比较不爽的点是手柄是用两块电池供电的。原装配的电池大概用了 10 个多小时就没电了。测试了一下连着 TypeC 的线,可以有线供电,但无法充电。

另外,这台机器完全没有声音(或许是我玩的游戏不够硬核?),比我的 NAS 还安静。

存储空间

相信除了性能之外,Xbox Series S 最让人难以下手的应该是存储空间 —— 它只有 500G 的 SSD. 这是什么概念呢,以下是我玩的一些游戏和它们的体积:

  • 《瘟疫传说》 40+GB
  • 《战地1》75GB
  • 《光环:士官长合集》95GB
  • 《巫师3》40GB
  • 《荒野大镖客》120GB
  • 《地平线4》66GB

平均每个游戏 50GB 来算,能装大概 8,9个游戏,这是比较乐观的算法。对于非重度的游戏玩家,我觉得还可以接受。但是,轻度玩家如我,事实上居然也差不多装满了。虽然也不是每个游戏都在玩,有些游戏是留着突然有空的时候才玩一下,如果空间不够所以删了,想再玩又要下载一遍,就非常难受。

当然在主机的背后有一个 SSD 的扩展插槽,但是官方推荐的希捷的 1TB SSD 要 2688 人民币(应该不会真的有人买吧)。

决定买 Series S 之前我也有这样的犹豫,但是,在机器到手后,我发现了一个在很多评测文章都没有提到的信息:主机也支持接入 USB3.0 的移动硬盘!

细心的朋友也许会发现,在上面的图中,我的 Xbox 旁边出现了一块移动硬盘:

是的,没错。我当初抱着试一试的心态,把我闲置的 1TB 的移动硬盘,插入到了 Xbox, 根据系统提示格式化之后,就可以作为外置存储正常使用:

你可以在系统设置下载的游戏默认下载到外部存储。

然后可以把外部存储的游戏移动到内部存储,或者反过来:

而且神奇的是,储存在外部存储的游戏,是可以直接启动的。根据我的测试,我玩的游戏在外部存储和内部存储启动的情况下,没有感觉到运行速度有什么区别。而且 Quick Resume 的功能也同样能用。这个 300 块钱的移动硬盘为我的 Xbox 扩充了 1TB 的存储。

总结

对于我这种轻度游戏玩家来说,Xbox Series S 算是一台很高性价比的游戏主机了。而且 Xbox Game Pass 提供的游戏也很不错(我最期待的飞机模拟 7 月 27 就要上线 Xbox 了),价格也不算贵,在特别忙没时间玩游戏的时候,可以停止续订,等有空的那个月才订阅。如果不是对画面有要求,我觉得 Series S 是个不错的选择。

我给自己设立了每月 $20 的开源捐赠预算

Babel 官方最近发布了一篇博客 称 Babel 团队的 funding 已经不够了。一直以来我零碎地捐赠过一些开源项目和个人,在卖贴纸和 T 恤的时候也把大部分的利润捐赠给了 Vue. Babel 这件事让我有了固定每月捐赠一些开源项目的想法。

目前设定的是每月至少 $20 (约 130 多元人民币) 的预算,这样平均至少可以捐赠 4 个不同的项目。我可以在这个预算内根据我的技术栈灵活地选择我对哪些项目进行捐赠。

我个人会选择一些这样的项目进行捐赠:

  • 我在做 side-project 的时候经常会用的项目
  • 项目没有融资背景(例如我非常喜欢 Prisma, 可是它已经有了 $12M 的 A 轮融资)
  • 做了一些我用到的工具的个人开发者

目前我捐赠的项目和个人

  • EGOIST 在全职做开源的好朋友
  • chakra-ui 我用 React 写 side-project 必用的 UI component 库
  • dayjs 我经常会用的 moment.js alternative
  • react-query 我经常会用的 React 请求库
  • next-auth 非常好用的 Next.js OAuth 库
  • Logseq 开源的 Roam Research alternative

你可以在 OpenCollectiveGitHub Sponsor 看到我对哪些项目和个人进行了捐赠。

国内用户捐赠的方法

国内用户捐赠只要具有以下其中一个条件即可:

  • 注册 Paypal 账号和国内银行卡绑定
  • 持有一张 Visa 信用卡

OpenCollective 支持 Paypal 和信用卡,甚至支持银联的信用卡,因此一般国内用户都能很容易通过 OpenCollective 进行捐赠。GitHub sponsor 不支持 Paypal, 需要 Visa.

除了每月固定的捐助,还可以选择一次性的捐助。

我对开源的看法

我从开放的源代码学到了非常宝贵的知识,这些知识帮助我成为了更好的程序员。功利一些来说,我通过使用这些开源项目得到了可以谋生的岗位,这些开源项目竟然是开放和免费的。

有些人认为,做了一个成功开源项目,即使没有钱,也收获了名气。对我个人来说,我确实也通过做开源项目得到了非常多的机会,但是正是自己做过开源项目,才深知维护一个开源项目的成本不是靠「名气」就可以支撑起来的。

关于这个话题,强烈建议收听牛油果烤面包的这一集 Podcast: 《#70. 和Vue.js的创造者尤雨溪聊开源软件》

也可以读一读这本 Working in Public: The Making and Maintenance of Open Source Software, 里面有很多开源软件维护者面临的问题。

我们可以为开源做什么?

  • 根据自己的经济情况,设立一个捐赠预算($1 也很不错),给自己生活中、工作中用到的开源工具进行捐赠。
  • 和我一样写一篇文章介绍你对哪些项目进行了捐赠,鼓励更多的开发者对开源项目捐赠。
  • 贡献有意义的 PR,即使只是文案的修改。

Svelte 的异步更新实现原理

我对 Svelte 的看法 一文里,我分析了 Svelte 在编译时实现 Reactive 的原理。在这篇文章,我将分析在 Svelte 里更新一个状态 (state) 值后更新到 UI 的这一过程。

阅读本文前,你应该至少:

原理分析

为了保持简单,先从一个和 Svelte 无关的例子讲起:

// 假设我们正在实现一个 counter, 只有一个 state,就是 count, 它是一个 number:
let count = 0

// 我们可以实现一个 setCount, 来改变 count 的值,顺便执行更新 UI:
function setCount(newVal) {
  count = newVal
  updateUI()
}

function updateUI() {
  console.log("update ui with count:", count)
}

setCount(1) //=> update ui with count: 1
setCount(2) //=> update ui with count: 2
setCount(3) //=> update ui with count: 3

这样实现很简单,但是有一个严重的问题:连续的状态更新会连续触发 updateUI, 性能会非常糟糕。解决这个问题的方法是:把同一个事件循环里的所有状态更新造成的 UI 更新统一合并(batch)到一个 microtask 里统一执行。

// 基于 Promise 实现一个把函数放到 microtask 里的函数
function createMicroTask(fn) {
  Promise.resovle().then(fn);
}

let updateScheduled = false;
function scheduleUpdate() {
  if (!updateScheduled) {
    // 当首次 schedule 时,把 updateUI 放到 microtask 中
    createMicroTask(updateUI)
    updateScheduled = true;
  }
}

function updateUI() {
  updateScheduled = false
  console.log("update ui with count:", count)
}

// 在 setCount 时,不再直接触发 updateUI, 而是 schedule 一个 update
function setCount(newVal) {
  count = newVal
	scheduleUpdate()
}

setCount(1)
setCount(2)
setCount(3)
//=> update ui with count: 3

这样,在同一个事件循环里,多个状态更新只会触发一次 UI 更新。

现在假设页面上有一个 h1, updateUI 中会更新它:

let count = 0
const h1 = document.querySelector('h1')

function updateUI() {
  updateScheduled = false
	h1.innerHTML = `${count}`
}

setCount(1)
setCount(2)
setCount(3)
//=> update ui with count: 3 

So far so good. 但是相信不少人年轻的时候曾经写过这样的代码:

setCount(1)
setCount(2)
setCount(3)
console.log(h1.innerHTML) //=> 0

setCount(3) 后, h1.innerHTML 竟不是预期中的 3. 仔细一想,当然了,updateUI 是在同步代码执行完后,开始执行 microtask 队列的时候才触发的啊。

为了可以在 setCount 后拿到更新后正确的值,我们可以把关于 UI 的操作也放到下一个 microtask 才执行。为了方便,我们可以写一个 tick 函数:

function tick() {
  return new Promise.resolve()
}

async () => {
  setCount(1)
  setCount(2)
  setCount(3)
  await tick()
  console.log(h1.innerHTML) //=> 3
}

Svelte 的实际做法

回到 Svelte:

<script>
    let count = 0
</script>

<div>
    <span>{count}</span>
    <button on:click={() => count++}>+</button>
    <button on:click={() => count--}>-</button>
</div>

这个组件会被编译成一个 fragment (你不需要读懂下面的代码):

function create_fragment(ctx) {
	let div;
	let span;
	let t0;
	let t1;
	let button0;
	let t3;
	let button1;
	let mounted;
	let dispose;

	return {
		c() {
			div = element("div");
			span = element("span");
			t0 = text(/*count*/ ctx[0]);
			t1 = space();
			button0 = element("button");
			button0.textContent = "+";
			t3 = space();
			button1 = element("button");
			button1.textContent = "-";
		},
		m(target, anchor) {
			insert(target, div, anchor);
			append(div, span);
			append(span, t0);
			append(div, t1);
			append(div, button0);
			append(div, t3);
			append(div, button1);

			if (!mounted) {
				dispose = [
					listen(button0, "click", /*click_handler*/ ctx[1]),
					listen(button1, "click", /*click_handler_1*/ ctx[2])
				];

				mounted = true;
			}
		},
		p(ctx, [dirty]) {
			if (dirty & /*count*/ 1) set_data(t0, /*count*/ ctx[0]);
		},
		i: noop,
		o: noop,
		d(detaching) {
			if (detaching) detach(div);
			mounted = false;
			run_all(dispose);
		}
	};
}

function instance($$self, $$props, $$invalidate) {
	let count = 0;
	const click_handler = () => $$invalidate(0, count++, count);
	const click_handler_1 = () => $$invalidate(0, count--, count);
	return [count, click_handler, click_handler_1];
}

不要被吓到,一个 Svelte Fragment 实际上是一个函数返回几个必要的方法:

function createFragment(ctx) {
  return {
    // 创建 DOM 的方法
    c(): {},
    // 把 DOM mount 到节点的方法,以及事件绑定
    m(): {},
    // DOM 节点更新的方法
    p(): {},
		// unmount 的方法
    d() {}
  }
}

这里的 p(), 就是类似上文提到的 updateUI.

instance 则是 <script> 之中定义的变量和一些 event handlers. $$invalidate(0, count--, count) 类似上文提到的 setCount. 在真实的 Svelte 中整个状态更新的流程简单地来说就是:

  1. 用户点击 button, 触发 $$invalidate(0, count--, count)
  2. 触发 schedule_update(), 通知框架这个 fragment 需要被更新(make_dirty()),框架会维护一个 dirty_components 的数组
  3. 同步代码执行完后,开始执行 microtask, 触发更新(flush),遍历 dirty_components, 触发每一个 component 的 p()

读《蒂姆·库克传》

购买链接

我对 Apple 的钦佩除了是因为产品本身的设计和体验,还有 Apple 作为商业巨头,对个人隐私、环保、无障碍等等这些「人文关怀」有着并非门面功夫的执着。读这本书之前我没有想到这些执着其实大部分来自于 Tim Cook.

2014年5月,美国国家公共政策研究中心保守派智囊团的一名成员逼迫库克衡量可持续发展项目会给苹果带来的利益损失,库克拒绝了,他说:“当我们付出努力,为了让盲人也可以使用我们的设备时,我是不会考虑该死的ROI(投资回报率)的。苹果在发起环保倡议、保护员工安全和其他政策时也是如此。“如果你让我做事的时候,唯一关心的就是投资回报率,那我希望你马上抛出苹果的股票!”库克向这位保守投资者吼道。

谈到慈善这件事,乔布斯是出了名的不感兴趣,他说他能做的最大的慈善,就是提升苹果的市值,这样股东们就会有更多的钱投入他们选择的事业里。

《纽约时报》还发表了一份有关富士康工作条件的调查报告,这份报告后来还获得了普利策奖。一向性格谦和的库克对此大发雷霆,在跟员工们分享看法时,他表达出了与乔布斯迥然不同的态度——乔布斯曾被指责在此事上表现冷漠。在一封内部邮件中,库克说看到这份报告后很“气恼”,甚至“火冒三丈”。他开诚布公地写道:“我们的供应链遍及全球,我们关心其中的每一位员工。任何一起事故都令人深感不安,任何有关工作环境的问题都值得关注。任何认为我们对员工漠不关心的想法都完全错误,并且令人反感。你们是最了解真相的人,这种指责与我们的价值观相悖。苹果完全不是这样的。”库克再三强调苹果正在改善成千上万名工人的工作条件,他决心要纠正大众的误解。

这本所谓的「传」实际上是作者把 Tim Cook 的公开言说和资料以及一些他身边的好友的回忆整理在一起,还原出了 Tim Cook 的人生和事业轨迹。相当于是加长版的 Tim Cook 维基百科页。

从 Steve Jobs 重返 Apple 不久开始,Tim Cook 就已经在苹果发挥了很大的价值,解决了苹果在供应链上的问题,这对当时的 Apple 而言是很至关重要的。可是解决供应链问题不像做产品那么 cool, 作为局外人的消费者,很难看到 Tim Cook 的价值所在。「只有当供应链出现了问题的时候,人们才会发现供应链的问题」。从 Tim Cook 解决问题的方式可以看出来,他和 Steve Jobs 一样能做到 Think Different.

书中还记录了很多 Tim Cook 关于人文关怀的事例。我猜测他的这些执着是来源于他从小遇见过的种族主义者,还有他本身作为社会中的少数派(同性恋),让他把尊重多元社会视为己任。有趣的是他在读大学的时候还修过一门「道德课」:

库克在杜克大学读书的时候,修了一门道德课,这门课对他日后产生了深远的影响。一名工程师学习道德课程,这并不常见,但是库克希望拓宽自己的视野,希望自己对工程学和商业有全球化的视角。在职业生涯的早期,库克就在思索,公司可以成为一股向善的力量以推动世界的改变。

购买链接

我对 Svelte 的看法

我在很早前已经听说过 Svelte, 但是一直没有机会在新的项目真正地用上。最近在尝试模仿实现一个 Roam Research 的编辑器,考虑到可能会有大量和 DOM 交互的逻辑,所以我趁这个机会选择了 Svelte, 目前这个编辑器的已经完成了大部分的功能,开源在 https://github.com/djyde/plastic-editor .

在开发的过程中我对 Svelte 非常满意,这篇文章打算就我的体验来说说我对 Svelte 的一些看法。

你需要有使用过任意一个前端框架的经验才能读懂本文。这篇文章不是一篇 Svelte 教程,如果你想学习 Svelte, 请浏览 Svelte 的官方教程本文试图通过告诉读者 Svelte 在编译阶段做了什么,来让只听说过 Svelte 的读者,从浅层的原理和设计的角度,了解到 Svelte 是一个怎么样的框架、她和其它框架的区别是什么

Svelte 是什么?

简单来说 Svelte 是一个在编译时实现了 Reactivity (反应式) 的框架,所以它既是一个框架,同时也是一个 Compiler. 如何理解「编译时实现了 Reactivity」?先说说什么是 Reactivity.

let a = 1
let b = 2
let c = a + b
console.log(c) //=> 3
a = 2
console.log(c) //=> 3, not 4

JavaScript 没有 Reactivity 的能力,所以即使 let c = a + b, 在改变了 ab 的值之后,c 不会因此而改变。真正的 Reactive 与此相反。

拿 Excel 举例,Excel 是天生 Reactive 的,一个 Excel 里面的单元格,可以编写简单的函数组合其它单元格的值,而组合的结果会随着依赖的单元格改变而自动改变。例如单元格 C1 如果是 =SUM(A1:B1) , 那么 C1 值会随着 A1B1 的值而重新计算和改变。

如果你用过 Vue 或者 MobX, 你应该知道它们实现 Reactivity 的原理:它们使用了 defineProperty 或者 Proxy, 在 setter 这一层做了一些手脚,当对象的某个成员被赋值的时候,执行更新逻辑。

const reactive = {}
Object.defineProperty(reactive, 'a', {
  set(value) {
		console.log('a was updated')
  }
})

reactive.a = 'changed' //=> a was updated

这是一种「运行时」的手段,它需要在运行时改变了赋值行为,所以在用 Vue 的时候,你必需把需要 Reactivity 的对象包在 data 里,上文的例子用 Vue 需要这么写:

const yourData = {
  data() {
    return {
      a: 1,
      b: 2
    }
  },
  computed: { // 还有 computed
    c() {
      return this.a + this.b
    }
  }
}

const reactive = new Vue(yourData)
console.log(reactive.c) //=> 3
reactive.a = 2
console.log(reactive.c) //=> 4, not 3

但是我们可以想一想,如果不用 defineProperty, 可以实现类似的功能吗?

当然可以:

let a = 1
let b = 2
let c = a + b

function update() {
  c = a + b
}

console.log(c) //=> 3

a = 2; update()
console.log(c) //=> 4

b = 5; update()
console.log(c) //=> 6

我们只要每次在赋值的时候,手动触发一个 update 函数,那么 c 的值就会重新计算,不就实现了 Reactivity 的效果吗?

但是这样做未免太蠢,要写太多的代码,而且很容易漏掉。不过,我们可以借助 Compiler 帮我们做这些事!在编译时,每当遇到赋值语句,我们就让它在赋值语句的后面自动加一个调用 update 方法的语句。

这就是 Svelte 做的事情。当然实际上的实现要更加复杂一些(比如需要把更新放在同一个 microtask 里),但原理是一样的。

Reactivity 是现代的前端框架的标配,因为从前 MVC 的开发模式对 UI 开发来说,要在 Controller 手动操作 DOM 这个步骤显得有点枯燥和多余,我们希望 UI 是自动随着数据的变化而自动更新的。因此不同的框架除了写法不尽相同外,最主要的区别还是在于框架在检测到数据更新后,如何处理 UI 的更新

以一个 Counter 为例,这是 React 的写法:

function Counter () {
  const [ count, setCount ] = React.useState(0)
  return (
    <>
    	<div>{count}</div>
	    <button onClick={_ => setCount(count + 1) }>+</button>
      <button onClick={_ => setCount(count - 1) }>-</button>
    </>
  )
}

我在之前的文章已经谈过 React 的思想,在这里稍作重复。React 主张「视图」是「状态」的函数:

UI = f(state)

setState 的时候,这个函数会重新执行,因为是新的 state, 因此 UI 是变化的。在 React 里,UI 是 Virtual DOM, 用算法对比两个 DOM 树,来算出哪个真实的 DOM 需要被更新。

React 的实现非常「粗暴」,因为它是真的会重新执行这个函数,在上面的例子中,Counter 会在每次状态变化的时候被重新执行。这使得写 React 组件要多留心性能问题,因为你要避免在函数内部进行不必要的计算:

function Counter () {
  const [ count, setCount ] = React.useState(0)

+ doSomethingHeavy()
  
  return (
    <>
    	<div>{count}</div>
	    <button onClick={_ => setCount(count + 1) }>+</button>
      <button onClick={_ => setCount(count - 1) }>-</button>
    </>
  )
}

在「古典」React 里,你不得不写 shouldComponentUpdate, 在现代 React, 你同样需要引入 useCallbackuseMemo,手动地缓存函数,来避免性能问题。

同样是使用 Virtual DOM 的 Vue 却没有这个问题,因为 Vue 的机制(依赖收集)决定了它不必重新执行整个 UI 函数来换取新的 Virutal DOM 树,当某个状态更新的时候,它明确地知道应该 diff 哪些节点。

如果你是 Vue 用户,而不太清楚 React 的机制, 你可以把一个 React 组件函数想象成是一个 Vue 的 computed 里的成员函数, 你一定知道在 computed 的成员函数里做耗时计算的后果是什么。

现代前端框架倾向于使用 Virtual DOM, 我认为主要出于两点:

  • Virtual DOM 可以 port 到任何除了 Web 以外的宿主环境。
  • Virtual DOM diff 算法足够快,框架把 DOM diff 和 DOM 修改的工作交给了算法,可以把精力花在实现框架的其它功能上。

Virtual DOM 的本质就是找出需要被修改的真实 DOM 节点,难道不用 Virtual DOM 就不能实现吗?当然不是。回到上面的 Counter 的例子,我们如何用 Vanilla JS (原生 JavaScript) 来实现:

const target = document.querySelector('#app')

// state
let count = 0

// view
const div = document.createElement('div')
const countText = document.createTextNode(`${count}`)
div.appendChild(countText)

const button1 = document.createElement('button')
const button1Text = document.createTextNode(`+`)
button1.appendChild(button1Text)

const button2 = document.createElement('button')
const button2Text = document.createTextNode(`-`)
button2.appendChild(button2Text)

target.appendChild(div)
target.appendChild(button1)
target.appendChild(button2)

// event
button1.addEventListener('click', () => {
  count += 1
})
button2.addEventListener('click', () => {
  count -= 1
})

上面的程序生成了 UI, 绑定了点击事件,改变了状态 count 的值。但是显然 UI 是不会随之改变的,所以我们需要写一个 update 函数,让状态在变化的时候,触发特定的 UI 更新逻辑:

const target = document.querySelector('#app')

// state
let state = {
  count: 0
}

// view
const div = document.createElement('div')
const countText = document.createTextNode(`${state.count}`)
div.appendChild(countText)

const button1 = document.createElement('button')
const button1Text = document.createTextNode(`+`)
button1.appendChild(button1Text)

const button2 = document.createElement('button')
const button2Text = document.createTextNode(`-`)
button2.appendChild(button2Text)

target.appendChild(div)
target.appendChild(button1)
target.appendChild(button2)

// event
button1.addEventListener('click', () => {
  update('count', state.count + 1)
})
button2.addEventListener('click', () => {
  update('count', state.count - 1)
})

// update
function update(key, value) {
  state[key] = value
  countText.nodeValue = state[key]
}

现在点击按钮,div 显示的 count 就会变化了,因为我们在 update 函数指明了 UI 更新的逻辑。

我敢保证上面的程序性能一定比 React 版本的更好(当然在这个例子可能只相差 0.0000002ms),因为 DOM diff 再快还是要算,原生 JavaScript 是不需要算的。

但没人愿意这样写程序:

  1. 这样的代码完全丧失了可读性,无法一眼看出 UI 树的结构。
  2. UI 只要一调整,就需要写大量的代码。
  3. 每当有元素依赖一个状态值,就要手动在 update 函数中加上 UI 更新的逻辑。和传统的 MVC 没区别。

Svelte 是一个 Compiler, 帮助你在编译时生成这些 Vanilla JS 的代码,同时收集依赖,生成 UI 更新的逻辑。

<div>hello world</div>

会被编译成:

const div = document.createElement('div')
const text = document.createTextNode('hello world')
div.appendChild(text)

这并不是 Svelte 编译出来的代码,真实的代码经过了封装。这里只是为了方便讲解,但本质上是一致的。

加一个变量:

<script>
  let count = 0
</script>
<div>
  {count}
</div>

会被编译成:

let count = 0
const div = document.createElement('div')
const text = document.createTextNode(`${count}`)
div.appendChild(text)

update() {
  text.nodeValue = count
}

再次强调,这并非 Svelte 编译出来的真实代码。如果你对 Svelte 真实编译出来的代码有兴趣,可以在官方的 REPL https://svelte.dev/repl 写一个简单的 Svelte 组件然后看 JS output. 然后推荐进一步阅读 https://lihautan.com/compile-svelte-in-your-head/

编译器在遇到 {count} 的时候,就可以收集到在 count 变化的时候需要更新哪些元素。也就是说,像 Vue 那样通过 getter 实现的依赖收集,Svelte 通过编译阶段实现了。

一个完整的 Counter:

<script>
  let count = 0
</script>

<div>
  {count}
</div>
<button on:click={_ => { count += 1 }}>+</button>
<button on:click={_ => { count -= 1 }}>-</button>

你可以在这里打开这个程序 https://svelte.dev/repl/cfd45cdafb8a48a88edab6921c69ac0c?version=3

在编译的阶段,只要遇到赋值语句,就可以插入一个语句来安排 UI update (schedule update). 就像本文最初提到的方法一样。

到这里,已经解释了什么是「在编译时实现了 Reactivity」。

Svelte 的特殊语法

Svelte 里有一个比较特殊的语法,值得在这里介绍一下。

回到最初的例子:

let a = 1
let b = 2
let c = a + b

c 依赖了其它变量,如果其中的依赖发生了改变,它应该会被重新计算。在 Vue 里可以通过 computed 实现:

const reactive = new Vue({
  data() {
    return {
      a: 1,
      b: 2
    }
  },
  computed: {
    c() {
      return this.a + this.b
    }
  }
})

Svelte 用了一个特殊的语法实现了类似 computed 的功能:

<script>
  let a = 1
  let b = 2
  $: c = a + b
</script>

<span>{c}</span>

: 其实是一个合法的 JavaScript 语法,https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Statements/label

当然背后同样是在编译时实现的,它在更新视图的函数前会加入执行 a + b 并赋值给 c 的语句。

Svelte 的跨组件通讯

状态管理和组件通讯是前端框架设计比较重要的一块,通常的做法是 Lifting State Up. 也就是两个同级组件如果想要共享状态,那就把这个状态上升到共同的父组件上面去。Svelte 也可以这么做。不过 Svelte 里有 store , 你可以定义一个 writable store, 然后在不同的组件之间进行读取和更新:

// store.js
import { wrtiable } from 'svelte/store'
export let count = writable(0)

// A.svelte
<script>
import { count } from './store.js'
  
let count_value
const unsub = count.subscribe((newValue) => {
  count_value = newValue
})
</script>
<sapn>{count_value}</sapn>

// B.svelte
<script>
import { count } from './store.js'
</script>
<button on:click={_ => count.set(2) }>mutate</button>

每个 writable store 其实是一个 object, 在需要用到这个值的组件里可以 subscribe 他的变化,然后更新到自己组件里的状态。在另一个组件里可以调用 set 更新这个状态的值。

敏锐的读者可能已经发现,上面的代码没有处理组件销毁时 unsubscribe. 当然我可以在 onDestroy() 里调用 unsub(), 但是在 Svelte, 有个更便捷的语法:

// store.js
import { wrtiable } from 'svelte/store'
export let count = writable(0)

// A.svelte
<script>
import { count } from './store.js'
  
</script>
<sapn>{$count}</sapn>

// B.svelte
<script>
import { count } from './store.js'
</script>
<button on:click={_ => $count = 2 }>mutate</button>

Svelte 规定了在 store 前面加一个 $, 会自动 subscribe, 得到它的值,并且在组件被销毁的时候自动帮你 unsubscribe 它。对它进行赋值的时候,其实相当于执行了 .set() 的方法。

不要以为 svelte/store 的 writable 返回的对象是一个黑魔法,其实他不过是一个普通的对象而已,只是 Svelte 在编译的时候对 $ 为首的变量做了一些特殊处理。比如:

<script>
  console.log($name)
</script>

会编译成:

let name_value
const unsub = name.subscribe((newValue) => {
  name_value = newValue
})
console.log(name_value)
onDestroy(() =>{
  unsub()
})

同样这不是 Svelte 实际生成的代码,这里是为了讲解,但本质和 Svelte 的逻辑一致

对一个 store 赋值:

<script>
	$name = 'new'
</script>

会被编译成:

name_value.set('name')

看到这里,你可能已经知道了,其实 store 只不过是一个普通的对象,只是如果你在 Svelte 里通过 $ 符号进行操作的时候,会调用它的一些方法。这些方法就是:

  • subscribe. 返回一个 unsubscribe 方法
  • set

只要任何对象有实现两个方法,就可以用 $ 进行这样的便捷使用。这是一种 Svelte 约定的 store 协议. writable 是一个创建符合 store 协议的对象的捷径,不是什么黑魔法。

如果你用 RxJS, 你会发现 RxJS 天生就兼容 store 协议

我对 Svelte 的看法

我用 React 太久了,在用回不需要关心 rerender 问题的框架,我觉得非常舒服😄。当然 Vue 也不需要,我觉得在某个程度上 Svelte 和 Vue 很像,它和 Vue 的区别是:

  • 实现 Reactivity 的原理都是依赖收集,但 Svelte 是在编译时完成了,Vue 在运行时收集。
  • Vue 用了 Virtual DOM, Svelte 在编译时就知道它应该操作哪个 DOM

因为所有的功能都是在编译时实现的,所以用 Svelte 写的代码非常直白 —— 像正常定义变量一样定义变量,在 HTML 里使用这个变量,修改这个变量(而且没有 this)。运行时的框架无论如何简单,至少需要你写一层 Wrapper,例如在 Vue 里你必须把状态包在 data 函数中返回。

我认为 Write less code 是重要的,在前端开发的领域,我们花了太多精力在处理像 immutable, reactivity 这些 UI 开发标配的特性上面, 我一直认为需要有一门天生带了这些特性的语言用来写 UI 应用,而不是引入第三方包、写一些 boilerplate code.

Svelte 非常接近,而且它做到了不需要你学习新的语法,在 JavaScript 里面就实现了这样的效果(目前只是 Reactivity)。

所以无论是对比哪个框架,我个人觉得 Svelte 对我来说最大的吸引力是可以写更少的代码,而且在写代码的时候感觉是符合直觉的,这一点非常重要,我认为 React 并没有做好这一点。我碰到过太多用 React 的朋友同样遇到过这样的问题:

function Timer () {

  const [ time, setTime ] = React.useState(0)
  
  React.useEffect(() => {
   	const interval = setInterval(() => {
      setTime(time + 1)
    }, 1000)
    
    return () => clearInterval(interval)
  }, [])

  return (
    <div>{time}</div>
  )
}

这个 time 在视图里一直是 1,为什么?谁会在第一次写这样的逻辑的时候就能意识到问题出现在哪里呢?

同样的逻辑在 Svelte 里,就不会有这样的问题(当然在 Vue 里也不会有):

<script>
  let time = 0
	
	const interval = setInterval(() => {
		time = time + 1
	}, 1000)

	onDestroy(() => {
		clearInterval(interval)
	})
</script>

<div>
  {time}
</div>

当然可能有人会说,Randy, 你连在 hooks 里面怎么用 setInterval 都不知道,你一定是 JavaScript 基础不行,回去补补基础吧!对于这样的想法,我只能一笑了之😆。

我没有办法提出一个杀手级的功能吸引没有用过 Svelte 的人来用 Svelte, 我只能说我喜欢 Svelte 是因为她是一个简单的框架,我在用她的时候不用想太多 Why.

Vue 的缺点和优点基本就是 Svelte 的缺点和优点,但 Svelte 从用法上比 Vue 更简单。

至于有人说,Svelte 生成的代码体积小,我认为这确实是一个优势,不过要注意的是用 Svelte 生成的代码,体积是线性增长的。这是我大概画的一个图,表示项目规模和代码体积的关系:

这只是一个大概的趋势,图中的斜率不是一个准确的值。详细在 Github 看相关的讨论 https://github.com/sveltejs/svelte/issues/2546

Svelte 的适用场景

生态是技术选型一个很重要的考虑因素,Svelte 显然不是数一数二的选择,基本不用期望 Svelte 有什么现成的组件库。

我认为 Svelte 非常适合用来做活动页。活动页没有很复杂的交互,以渲染和事件绑定为主。我常常想做简单的活动页还用 React 也太委屈自己了吧。

另外一个很好的用法是用 Svelte 写的 UI 组件,可以包装成给不同框架用的组件。Svelte 暴露了足够的 API 可以适配到其它框架,比如写一个日历组件,然后分别包装成 React 和 Vue 的版本。

结论

我很喜欢 Rich Harris (Svelte 的作者) 的很多想法,在 YouTube 看他的演讲有很大的收获。你可以在下面的延伸链接找到更多有关 Svelte 的我认为不错的视频,十分推荐 Rethinking Reactivity 这个演讲(我在 Bilibili 没有找到搬运,所以只能贴上 YouTube 链接了)。

最后想说的是,学习一个框架或者一个语言,不一定是非要把它用到生产环境才算是有用。我很喜欢看新的技术和学不同的语言,更多地是因为想看看在面对同一个问题的时候,不同的人解决问题的思路是怎么样的,这才是框架和语言真正的魅力。比如说你不一定非要用 Elm, 但是你一定能从 Elm 的设计学到点什么。Svelte 也一样 :)

延伸链接

做这个世界的生产者

偶尔看到这篇名叫 Consume less, create more 的文章,是作者从「消费者」转变为「生产者」的心路历程。他每天在巴士上面用 iPhone 写点什么,最终写出了这篇文章。

我从懂事开始就一直觉得自己在这个世界上与众不同,觉得自己的出生一定是背负了某个特殊的使命。后来发现原来每个人年轻的时候都是这么想的,随着年纪渐长,大多数人开始接受自己只是个普通人的事实。我也不例外。

但唯一没有变过的是我没有因为意识到了自己的平凡而停止了试图变得不平凡。其中的一个表现就是我希望在这个世界上,我是作为一个生产者存在的,而不只是一个消费者。

写博客、编程、做音乐都是我的生产,即使我写的博客没有让我成为几十万粉丝的 KOL, 我写的代码没有几万的 star, 唱的歌的观看数只是几百,我还是保持生产,因为对于这个世界,能让我有那么一点与众不同的是我的生产,而不是我的消费。

我讨厌抖音,不是因为它的内容,是因为它默默地把人驯化成了贪婪的信息消费者。如果我什么都不创造,那么我每天看 Hacker News 和别人每天刷抖音没有任何区别。

这个世界的每个人都是消费者,但不是每个人都是生产者。如果你也希望为这个世界留下点什么,就去创造,别担心你创造的是不是垃圾,只要创造了,是一定会有收获的。

2020 年终总结

COVID-19 疫情最严重的时候我在广州,记得我 12 月底从杭州飞回广州的时候,根本没有想到事情会发展得和 SARS 一样严重。印象中那个时候的关键词是:安静,冷空气,未知。不知道这样的情况会持续多久,每天关注着 Telegram 的 Broadcast 看新增的感染有多少。同时还远程办公着,做着「花呗来电」的需求。没错,就是那个让你付钱还要每月打电话提醒你还花呗的服务。

没想到多年前我们都在探索的远程办公,会因为一场传染病让更多人体验到了。但显然,我体验到的只是畸形的远程办公,他披上了远程办公的外衣,让我们每个人变成了 On call 24 小时 —— 比如我甚至在炒菜的时候还要接入电话会议。

我思考了很多,在 4 月份决定离开阿里巴巴。后来我面试了一些公司,那些你能说得出名字的公司我应该面了有一半。我很奇怪现在的「大厂」招聘到底是什么标准,问的问题到底有什么意义,让我一度严重怀疑我的能力。当然,有些面试者可能也觉得奇怪,为什么这样一个在博客上侃侃而谈的人,连浏览器如何验证 HTTPS 证书的都不知道。

后来落实了现在的工作,不过我现阶段不打算过多的提及我的新工作。但在这个新的环境,遇到的新的人们,对我一些固有的想法有了不少的冲击,让我有了一些新的想法和反思:

  • 优秀的程序员和普通的程序员之间很多时候只隔了一层好奇心,对「有没有更好,更有效率,更聪明的做法」的好奇心。在这个时代,「代码」是一种生产资料的,而大量的程序员们却自己忽略了这一点,或者自愿放弃了这点
  • 把技术作为兴趣的人是少数,只是互联网让我们有机会聚集到了一起,这让我很长一段时间误以为这就是程序员的常态。现在每当我因一个程序员缺乏专业素养而内心愤怒的时候, 我都会提醒自己,不是每个人都像我一样愿意(或者有条件)把技术带到日常生活中去。 我每天吸收的资讯、读的书大部分都和技术有关,这只是因为我的兴趣和职业都恰好是技术,但别人不是。
  • 我在很多人的眼里是幸运的 —— 我有条件在很小的时候接触计算机,我「幸运地」在退学以后还能进「大厂」,等等等等。我承认我是幸运的,但我一直在思考,我所享受到的幸运,可以如何为那些不那么「幸运」的人带去一些有用的东西呢?博客是我一直在做的,也能稍微达到目的,但这远远不够。

技术

  • 在一个内部平台用了 hapi, 我非常喜欢 hapi/boom 的设计。hapi 是我目前用 Node 写 API Server 的首选。
  • 学了一段时间 Go, 写了 snp. 目前还没有场景用到 Go 的更高阶的特性。以后我会尽量把和前端无关的工具用 Go 写。
  • 我仍然一直在关注 ReScript (也就是 ReasonML), 我认为 ReScript 是所有编译到 JavaScript 的方言里最优雅的语言。她的特性弥补了用 ECMAScript 写 React 的缺点。

我今年最喜欢的技术

Best buy

(*排名分先后)

我也对我买过的数码产品进行了反思,觉得有些对我来说是性能过剩的:

iPad Pro

如果现在让我选,我肯定会选更便宜且支持二代 Apple Pencil 的 iPad Air. 我目前对 iPad 的需求只有:

  • 支持二代 Apple Pencil
  • 支持蜂窝

MacBook Pro 32GB 内存

我在买这台 MacBook 的时候特意把内存加到了 32GB, 但这些年用下来,我发现我对 laptop 的需求瓶颈不在内存,而是 CPU, 运算速度,续航和发热量,而这些东西在这台 MacBook Pro 都没有做得好。

我可能会在一年后换成 M2 的 MacBook Air 取代这台 MacBook Pro.

Apple Watch

Apple Watch 对于有氧训练来说确实非常好用,但对于我大多数情况下都是力量训练的人来说并没有太大的用处,唯一的用处只有一些健身记录的 App 可以通过 Apple Watch 在训练的时候进行记录,或者组间计时。而我更愿意用小本子来记(因为记录一些训练感受的备注更方便),我的卡西欧手表计时也足够方便。

我也用 AutoSleep 记录过一段时间的睡眠时间和质量,但我本身睡眠就没有什么问题,所以这些数据对我没有什么帮助。

工具

今年用到了两个新的工具,值得分享给大家。

Roam Research

我在试用了 30 天 Roam Research 以后就发现我离不开它,所以咬着牙订阅了它。我记的想法或者笔记通常是零散的,没有组织的,但在 Roam Research 里可以通过 tag 和 backlink 把这些零散的记录关联到一起,可以很好地辅助我的写作。

比如在我的 Roam Research 里有提到关于 Roam Research 的记录都能直观地看到:

有时候我在写作的时候会惊喜地发现有些有关联的笔记我自己都已经忘了,如果不是 backlink 我可能永远都不会想起它。所以它就像是我第二大脑 :我们虽然暂时还无法复制我们自己的大脑,但是我们可以通过文字笔记的方式创造一个我们大脑的子集。它记忆了自己曾经学习过的、思考过的东西,永远不会忘记。在需要的时候,随时可以翻查这个第二大脑的记忆,而且他是网状的,拥有一定程度上的「联想」。

我之后会详细地分享我是如何用 Roam Research 的。

Cubox

https://cubox.pro

我以前不用书签管理工具,有些曾经读过的文章和工具,在之后想找也找不到了。在用了 Roam Research 之后,感受到了 tag 的强大力量,所以我开始用书签管理工具, 给每一个书签加上标签,日后我可以通过 tag 的组合找到忘掉的东西。

  • Cubox 也替代了我其它的 Read it later 工具,我现在看到无法短时间消化的文章,都可以收藏到 Read it later 的文件夹里,到晚上统一处理。
  • 在移动端用 share sheet 也可以很方便把移动端上看到的页面收藏到 Cubox

阅读

《精通正则表达式》

我是在一次面试的时候被问到过一个用正则表达式提取一个固定语法的句子里的信息的问题,之后我就读到了这本《精通正则表达式》,前三章已经让我收获非常大。这个收获不是那些正则表达式的基本语法,而是这本书想要传达给读者的思想 —— 把正则表达式作为一种工具去运用它。

我在读了这本书之后再也没有了以前对正则表达式的那种恐惧,而且还因为正则表达式学习了很多关于 Parser 的知识,也在日常开发中惊喜地发现有很多问题可以用正则的方式解决。

《精通正则表达式》是我今年读的最有价值的一本书。

How to make smart notes

因为用 Roam Research 所以读的一本书,讲的是做笔记的方法论。

Even the best tool will not improve your productivity considerably if you don’t change your daily routines the tool is embedded in, just as the fastest car won’t help you much if you don’t have proper roads to drive it on. Like every change in behaviour, a change in working habits means going through a phase where you are drawn back to your old ways.

《整洁架构之道》

我最早的职业规划是成为一个架构师,在工作以来也用自己的方法「设计」过很多系统和工具,这本书是讲真正的软件架构,里面解答了很多我想要知道的答案,软件架构的目标是什么,如何做软件架构,如何做技术选型。还有一些真实的架构案例可以参考。

这本书我要再读一两遍才能详细地和大家分享。

新的一年想做的

如果有机会,我希望可以在今年做一个小而美且有人愿意购买的产品,她会是好用的、体验好的、克制的、能带给用户更深层次的价值的。就像印刷机,他表面上看上去只是一个工具,但他对于人类知识文化的传播是功不可没的

用 Code Snippet 提高开发效率

写代码的时候总会遇到一些相同的但是反复要写的代码,比方说每次写一个 React Component, 都要这样写一遍:

function App () {
  return (
    <>
    </>
  )
}

export default App

这时候很适合把他写成 Code Snippet, 让编辑器帮我去生成。每个编辑器或 IDE 都有 Snippet 功能,我自己常用 VS Code, 所以我会去写一些 VS Code 的 Snippet 文件。

VS Code code snippet 的配置入口:

VS Code code snippet 配置是一个 JSON 文件,按照它的语法, 我可以这样去实现一个上面提到的,用于生成 React Component 代码的 snippet:

{
  "fc": {
    "scope": "javascript,typescript,typescriptreact",
    "description": "React Function Component",
    "body": [
      "function ${componentName} () {",
      "  return (",
      "    <>",
      "    </>",
      "  )",
      "}",
      "",
      "export default ${componentName}"
    ],
    "prefix": "fc"
  }
}

然后我在编辑器里只要输入 fc, 就可以选择这个 snippet:

用 Snippet 很方便,但是编写这个 Snippet 体验非常糟糕,因为需要把代码块一行一行地转换成字符串,空格也要自己加。

我想这也是我身边这么少人用 Snippet 的原因,我自己也深受其害。为了方便管理我的 Snippets 以及方便地导出为 VS Code code-snippets 文件,我写了个程序,可以让我把每个 snippet 写到单独的文件里,然后输出 VS Code code-snippets 格式的 JSON.

用 snp 管理 snippets

目前只适用于 VS Code

这个程序叫 snp, 你可以用 curl 直接安装这个程序:

$ curl -sf https://gobinaries.com/djyde/snp | sh

于是,我现在管理 snippets 非常轻松。创建一个目录,用来存放这些 snippets,

$ mkdir snippets
$ cd snippets

然后我就可以创建我的 snippets, 以触发的词作为文件名,.snp 作为后缀。例如我创建一个输入 fc 就会触发的 snippet:

$ touch fc.snp

.snp 文件里,你可以按照 snippet 语法 直接写你需要生成的代码,然后在文件的头部加上必要的信息:

---
scope: javascript,typescript,typescriptreact
description: React Function Component
---

function ${componentName} () {
  return (
    <>
    </>
  )
}

export default ${componentName}

scope 指定了这个 snippet 在哪个语言环境生效,具体可以参照 VS Code 的文档

然后执行:

$ snp -p

你会看到命令输出了一段 JSON, 你可以把这段 JSON 写入文件里:

$ snp -p > my-snippets.code-snippets

然后手动复制到 VS Code 的 snippets 文件里。

当然,如果你嫌每次更新都要手动复制很麻烦,你可以直接运行:

$ snp -u

这样 snippets 会自动写到 VS Code 的目录里,你不需要手动更新。

这样就可以像我一样,在一个文件夹里统一管理 snippets, 用 snp 同步到 VS Code,还能上传到 GitHub:




中国制造 Tesla Model 3 长续航体验

2020 年 1 月份我下订了标准续航的 Model 3, 因为工作原因一直拖着没有提车,4 月底突然出了国产长续航,于是把订单转成了长续航版本,苦等两个月,终于在 6 月份提到了这台中国制造特斯拉 Model 3.

为什么选择特斯拉

作为一个数码产品爱好者,早在 2014 年我就知道了特斯拉,当时除了觉得 Model S 非常帅之外,特斯拉这种激进、挑战传统的调性深得我心。

16 年在广州的特斯拉门店摸过、坐过真车之后,心里暗下决心将来买车有能力一定要买一台特斯拉。

今年刚好特斯拉国产化,预算还算合适,自然选择了特斯拉。

外观

Model 3 的前脸有点像保时捷,和燃油车不同的是,Model 3 因为没有发动机,他的前盖打开是一个可以置物的空间,可以足够放下一个小行李箱:

无边框车门非常好看:

因为选红色加了 8000,所以轮毂我就没有选 19 寸。拆掉原配的轮毂盖,18 寸的轮毂我觉得也不错。

买特斯拉官方合作的保险送了一把钥匙,这把钥匙单独卖竟然要 1200. 但是我拿回来基本没有用过,都是通过手机蓝牙,靠近车的时候自动解锁,离开车的时候自动上锁。

但是实体钥匙方便在于可以直接控制整个车的一些开关,它的造型就是 Model 3 本身,所以你只要按车对应的位置就会解锁。比如按后尾箱,后尾箱就会解锁。相反用手机 App 需要打开 App 然后点几下。

而手机 App 的好处是功能丰富,尤其是烈日当空的时候,可以在出发前远程开启温度控制,这样就不用感受那股滚烫的热浪了。

原配还有两张卡片钥匙,需要在 B 柱感应开锁。我平时也不用,放在钱包作备用。

驾驶体验

我喜欢电动车动力随传随到,起步不拖沓。这是电动的天然优势。Model 3 是我开过最快的车,百公里 5.3 秒的加速,虽然正常的驾驶不需要和别人去比百公里加速,但是这种加速度可以让你在变道、超车的时候非常有信心。

得益于传感器和摄像头,在行车的时候可以给我提供一些安全性的辅助,例如当我开始打方向灯准备变道,假如这个方向后面有车正在加速,车内就会响起警报声,屏幕上也会把后方的车标红,提示我不要变道。

传感器可以精确到厘米:

Autopilot

现在特斯拉标配基础版的 Autopilot,Autopilot 是特斯拉的精髓。基础版 Autopilot 是辅助驾驶,而「全自动驾驶(FSD)」需要另外购买。基础版 Autopilot 可以做到自动跟车、自动车道维持,以及一些主动安全辅助。

在规则的路上(如快速路,高速路)Autopilot 可以识别地上的线进行车道维持,一些弯道会自动转动方向盘。仅仅是基础版的 Autopilot 其实已经很大程度上减少了驾驶需要的精力。因为在高速上基本的操作也就是维持车道、保持和前车的距离。

像我从广州到中山的一个半小时车程,基本我手动开车的时间加起来大概只占半小时。其余时间我只要握好方向盘,注意是否有突发事件,随时准备把控制接管回来。在遇到前车减速、有人加塞的情况,Autopilot 会自动减速,所以整个过程就像有一位司机在帮你开车,而你又可以随时把车接管回来。

在塞车的时候开 Autopilot 会感受到它刹车不够线性,很怕后方的车追尾。而且 Autopilot 在转向的时候是不减速的,虽然这是因为他对路况判断十分有信心可以全速过弯,但是经常要替它捏一把汗。希望后续升级可以加一个过弯减速的选项吧。

做工糟不糟糕?

我在买之前已经看了很多关于特斯拉做工差的抱怨,例如门框对不齐,缝隙忽大忽小之类的。对于做工,我已经做好了充分的心理准备。但我可能比较幸运,目前为止我还没看到我的车做工糟糕的地方。

后尾箱漏水吗?

提车之后在户外经历了几场暴雨,我专门看了下后尾箱有没有漏水,答案是没有。

噪音大不大?

因为是无边框车门,隔音没有普通的车好。速度到 80km/h 以上开始会有点吵,音乐开大一点感觉问题不大。

续航焦虑吗?

目前来看不焦虑。我的长续航充满能跑 500 公里出头,我一般充到 90% 就停,也有 470 公里左右。到 100 公里以下再充。近一个月大概是一周两充,一是周五充好准备过周末,二是周末回来充好用一周。中山到广州来回完全无压力。如果第二天有比较大的用车强度,也会提前充满。

我没有自己的桩,但是广州除了特斯拉自己的超充,第三方的商业桩覆盖率非常高,所以我不担心自己没桩充电会很麻烦。

我 95% 的情况都是市内、周边城市通勤,所以目前的续航对我来说是很够用的。如果要去更远的地方自驾旅行,则需要提前做好补电的规划。

快充的速度非常快,从 20% 充到 90% 只要 40 几分钟,把每周的读书计划安排在这段时间刚刚好。

充电比加油省钱吗?

像我这样全在外面充电,可能和加油差不了多少。按照超充 2 块一度,充满 70 度需要 140 元,跑 500 公里,大概一小时充完,停车费按照每小时 6 块来算,算下来每公里电费是 0.3 元。第三方快充更低一点点,大概在 1.5 元一度左右。对比现在的油价,大概只是省一半。如果有条件在家里加桩,成本就低得可以忽略不计了。

不过我目前还在用 1500 公里的免费超充,最近还成功推了两单,又多了 3000 公里,所以我前 4500 公里基本是不用花钱的。如果你也想买特斯拉,可以通过我的邀请链接下单,成功交付后你我都能得到 1500 公里免费超充。

https://ts.la/randypriv70913

总结

Model 3 是我的第一台车,也是第一次花这么大数目的钱。翻到 2016 年去特斯拉门店的时候发的朋友圈,当时也没有想到 4 年以后会实现这个愿望。人生第一台车选择纯电是激进的,但似乎我一直以来做的决定都很激进。希望这台车可以让我走得更远,我迫不及待地想要创造更多的可能性。

离开阿里巴巴

这个月我将离开阿里巴巴,结束至今 4 年的阿里生涯。

在阿里的 4 年我体验到非常多的东西,了解到了作为一个「职业程序员」的工作状态,认识了很多很靠谱的同事。尝试了一些东西,创造了一些东西,也吸收了一些东西。

我很幸运,这几年我遇到的老板都对我非常好。在 UC, 甄子给了我很大的自由度,让我得以在技术上做很多的探索 。在花呗,完颜和我一起做了很多次难忘的乐队演出。我很喜欢 花呗前端团队 ,这里的同事很有趣,在一起工作非常快乐,他们在面对复杂且繁忙的业务时展现的专业性是我难以企及的。

Why leave?

在大公司,成为一个优秀的业务型程序员要做的更不仅仅是理解业务需求。而我更擅长的是用技术解决效率问题、通过设计降低软件复杂度(Programming complexity)。但在业务 deadline 面前,这些仿佛是次要的。很少人有精力在 deadline 和加班面前能保持追求代码质量和软件设计,但这却是保证软件稳定和快速迭代的基础。为了快速上线而忽视代码质量的代价是很沉重的,但没人会指出这些技术债的根本原因就是太着急了,整个环节都太着急了。

我也不擅长画大图,把事情说得很宏大。阿里是一个很大的公司,有很多内部系统(所谓的「中台」)已经很完善,不是每个人都有机会遇到「大」问题。但是如果不做点什么大事,在阿里的仕途不会很顺利。所以我目睹了许多因为 KPI 和职级晋升产生的内耗。它潜移默化地使人做事变得功利,它就像一把声音在你做事的时候默默地提醒你:要把事情做大。

我从 Apple 和乔布斯身上理解到了在做同一件事时,不同的目的会导致做出来的东西天差地别。钱很重要,但如果乔布斯做产品的目的是钱而不是用户体验,那么 Apple 不会是今天的这个 Apple. 我认为做技术产品也是一样的道理,「做好」应该是目的。但能遇到适合「做大」的技术产品的场景是很靠运气的,在这样的情况下,绩效和晋升的压力会让人不得不把「做大」变成了目的,这就导致了:

  • 简单的事情复杂化,增加使用者的理解成本( Cognitive Load
  • 能使用现有的技术,偏要自己再做一套。由于「做大 -> 晋升」是目的,这样的产品有很大的机率在达成了目的后被放弃。
  • 做事态度变成「能用就行」,不关心用户体验。

这样的做事方式不适合我,也违背了我做技术的理念。

What’s next

我还没有一个很确切的计划,但我希望能去一个地方,定义我价值的不仅仅是因为我用了多牛逼的技术,做了一个多大的「平台」,而是我用技术的手段,给用户创造了什么他们觉得有价值的东西,如何改进了用户体验。又或者我用技术解决了哪些效率问题。

更具体的目标是成为 Tech Lead 的角色。同时我对 Growth Hacking 有很大的兴趣,希望可以结合技术和数据找出产品增长的瓶颈、把产品做得更好,帮助产品实现增长。

我会回到广东,离开杭州。

Conclusion

离职不是什么苦大仇深的事,我对阿里没有任何的怨念,阿里有很多很好的地方,只不过不属于这篇文章要讨论的范围。无论在哪个地方,我想要的都是和一群有想法的人一起打磨一个有价值的产品,而不是不同职能的团队都只做对自己 KPI 有利的事。每个公司都有其独特的生存法则,但这个生存法则也应该让那些务实和纯粹的人能很好地生存下去。也许这就是我作为一个技术人的理想主义吧。

说说 React 和 Vue

争论框架是个无聊的事情,有一天在开车的时候我突然想到选框架和选车是一样的。

React 就像手动挡,Vue 就像自动挡。你不能因为你会开手动挡就看不起开自动挡的,我想正常的驾驶员在城市代步都会选择开自动挡。我只想代个步,为什么不把复杂的事情交给变速箱呢。

当你要下赛道,你可能要选择手动档。因为你相信你的人脑比变速箱要聪明得多。你需要由你自己的大脑控制最重要的事 —— 赛车里是速度,软件开发是性能。useMemo 或者shouldComponentUpdate 就是你车里面的那根档棍。

用手动档的没有必要看不起用自动档的,人家根本不在意这车能不能开到极速,只想安心开开车顺便摸摸副驾驶的大腿;用自动档的也没必要在路上看到别人开手动档的就骂别人傻逼,人家一个老司机开手动档多年,可能从心智上开手动档开得跟你开自动档一样轻松,自己也享受手动档的乐趣。也有可能是因为他想开的车只有手动档的 —— 比如他因为想用 Ant.Design 所以选了 React.

就像选择自动 GC 和手动 GC 的语言,作为一个成年人,应该知道什么是 trade-off.

作为专业的程序员,更重要的是知道你面对的是赛道还是公路,你的伙伴到底会不会开手动档。

最怕的是硬要让只会开自动档的人开手动档的车,轻则红绿灯起步死火被疯狂按喇叭,重则…

国内自建图床指南

我的博客很长一段时间在使用新浪微博作为图床,自从新浪微博开始防外链,我博客文章很多配图丢失了。我意识到我需要一个稳定可靠的图床,所以开始用阿里云自建一个我自己的图床,目前已经稳定使用了大半年。

我起初以为很难,而且费用不底。但是在这半年,我每个月的 CDN 费用不高(当然这也取决于访问量)。我自诩自己的博客不至于荒废或者没什么人访问,所以对于那些和我的博客规模差不多的独立博客博主,本篇应该算得上是一个十分贴切的参考。

当然,虽然我用的是阿里云,但套用到其它云服务都是一样的,读者可以读完后价比三家再作选择。

声明:本文和阿里云没有任何利益关系。

本文的目标读者

  • 有自建图床的需求,且对国内访问速度有要求的。例如独立博客、独立摄影站,甚至独立播客主于用存放音频文件。

云服务做图床的原理

云服务产品有很多,搭建图床只需要关注 OSS 和 CDN. OSS 是对象存储服务,通俗来说就是用来存文件的。OSS 都有对应的域名,文件保存在 OSS 后,可以通过 URL 下载它。

但是直接通过 OSS 下载的成本很高,价格十分昂贵,所以我们需要 CDN 来分发,节约成本。在阿里云,可以把 CDN 绑定到 OSS, 通过 CDN 去访问这个文件时,如果是首次访问,CDN 会从 OSS 取得这个文件,这个过程叫「回源」。之后再访问会直接从 CDN 读取。

步骤详解

因为我自己用的是阿里云,所以以阿里云为例(假设你已经注册好帐号)。

创建一个 OSS Bucket

一个 Bucket 相当于 OSS 中的一个存储空间,在 OSS 控制台 点击创建 Bucket:

填好 Bucket 名称和区域,其它选项按照默认即可。

创建成功后,在 Bucket 的文件管理可以上传文件:

查看上传文件的信息,你可以看到文件有 URL, 但由于在创建 Bucket 的时候,为了防止盗用,我们选的 Bucket 权限为私有,所以从 URL 其实无法访问这个文件:

创建 CDN 配置

CDN 控制台 进入域名管理,就可以开始配置 CDN 域名。

所以,在创建 CDN 前,你需要买一个域名。这个域名可以随便买个便宜的不主流的,因为没人在意一个图床的域名。

创建域名后,有一个要注意的地方,就是如果你需要国内加速,你的域名必须备案。备案其实是整个自建图床成本最高的一个环节。如果你的博客或者网站域名已经备案,那么可以直接用这个域名分配一个二级域名给 CDN 用。省去再备案的麻烦。

比如你的域名是 blabla.com, 那么你的加速域名可以是 static.blabla.com.

源站信息选「OSS域名」,选中之后会出现一个下拉选择,可以选中刚刚创建的 Bucket 源站:

如果你的网站用 https, 端口选 443.

如果你域名已经备案,就选全球或中国大陆。

设置域名的 CNAME

创建完后,你需要把你域名的 CNAME 指定为提供的值。如果你域名解析也是用阿里云,可以查看 这篇文档

开启 HTTPS

开启私有 Bucket 回源

因为前面在创建 Bucket 的时候权限设置为私有,所以需要给 CDN 开启私有 Bucket 回源的权限。

配置 Refer 防盗链

CDN 防盗是有必要的,如果你的图片被别处盗用,会增加不必要的流量。所以推荐设置 Refer 防盗白名单,只对允许指定的域名访问。例如我设置了除了我自己博客以外的一些 RSS Reader 以及 V2EX 可以访问:

使用 uPic 方便上传图片

以上的准备都做完后,你已经拥有了一个图床。现在就需要一个方便的工具把图片上传到图床。如果你用 macOS, 我推荐开源的 uPic

配置 uPic

添加阿里云 OSS 配置:

这里需要填 AccessKey 和 SecretKey:

你可以在 RAM 控制台 创建一个用户,然后创建 AccessKey.

创建后给这个 AccessKey 授 AliyunOSSFullAccess 这个权限:

我博客的 CDN 用量

从 2019 年 10 月 1 号至 2020 年 3 月 7 号总计 11.28GB. 按流量计费,每 GB 0.24 元,也才几块钱。

相关链接

如果你觉得本指南受用,可以通过 此链接 注册阿里云。

好产品帮助用户变得更好 - 读 Badass: Making Users Awesome

如何让用户愿意花钱购买你的软件 / 服务 ?

如果你也在思考这个问题,那么你一定要读一读 Kathy Sierra 写的 Badass: Making Users Awesome (中文译作《 用户思维+ 好产品让用户为自己尖叫 》)

想象一下,有一天,你看到有人在网上写了一篇如何记账和理财的文章,读罢,备受鼓舞,文章的作者在里面提到了如何用一个 App 高效做记账,于是你买下了这个 App.

背后驱使你买这个 App 的原因是什么?是因为你觉得这个 App 很不错?不,是因为你想成为那个善于理财的人。

这就是这本书的观点 —— 用户购买你的产品,他在意的是用了你的产品,他成为了什么样的人。所以,如何打造一个可以持续获得用户的产品,秘诀在于如何让用户通过你的产品成为他想成为的人。

这个观点让我耳目一新,我可以从自己或者从别人的购买行为看出这样的本质。我忽然发现,以往在思考如何打造一个用户觉得有价值的产品,我太专注在思考工具本身的功能应该怎么做,完全忽略了我的产品如何帮助我的目标用户变得更好。

例如,我总是在想着做一个博客平台,读完 Kathy 的这本书,我发现,我太过于思考从功能上,我要提供给用户什么。我想,我要提供 Podcast 托管,我要提供最快的 CDN, 我要支持自定义域名。但是,整个互联网,博客平台已经有很多,这些功能,别的平台可以提供。用户为什么选择我?

读完这本书,我会这样去思考:我如果做一个新的博客平台,用户可以因为我的平台,成为一个什么样的人?这样一来,问题就变成了:我的博客平台如何让用户成为一个优秀的独立博客主?我的博客平台如何让用户成为一个成功的 Podcast 节目主?

我们总是提供工具,但是往往忽略了如何让用户用好我们的工具,使他们变得 Awesome.

让用户用好我们的工具,是零经济成本但最有效的宣传工具。因为这一些 Badass User (通俗来说就是「玩得溜」的那些用户,我姑且称为「用家」),他们自己会帮你宣传。

当一个玩摄影的朋友告诉你,「这个镜头太牛逼了,你看我用它拍的这些照片,太好看了。」你可能会对这个镜头心动,晚上就回家搜一搜这个镜头,然后下单。

这位朋友嘴上说的是「这个 xx 太牛逼了,我用它 xxx」,事实上他想表达的是「我太牛逼了,我用它 xxx」。

只要你帮助你的用户通过你的产品变得更「牛逼」,就会有越来越多这些「用家」帮你宣传,就会有更多人购买你的产品,希望自己变得「牛逼」。也就是常说的「种草」。

然而他们宣传的方式不一定是直接跟身边的人说这个工具有多好,而是自己如何使用这个工具,做出了什么样的事。如果你有关注「少数派」这个媒体,少数派是这种案例的典型。你会发现上面几乎所有的文章,比如关于 Ulysses 这个 App 的文章,这些作者不会单纯去写这个工具怎么样,而是:

  • 《为什么我选择用 Ulysses 写了一本小说》(我如何用 Ulysses 写了一本小说)
  • 《搭建 Ulysses 学术写作之公式输入》(我如何用 Ulysses 学术写作)

越来越多的人会因为看到这些分享,希望自己可以用 Ulysses 也成为像他们这样成为一个写作者。

简单地来说,就是想一想,你的产品如何让你的用户在和朋友聊天的时候体现自己的 Awesome.

这本书也提到了在帮助用户成为「用家」的过程中遇到的问题以及解法,还从如果成为某个领域的专家作了一些科学上的分析(但我觉得这一部分的知识可以通过读 Atomic Habits 学到更多)。

我知道这本书是在翻 Egghead.io 的创始人 John Lindquist 的这条 Retweet无意中看到的,我觉得一定是本很好的书,他才会给每一个 Egghead 讲师送这本书。

我早在读 Egghead 的讲师指南的时候就发现 Egghead 会直接给每个讲师一套完整的录音设备(因为做 screencast 录音质量很重要)。我当时震惊于 Egghead 的阔绰。读完这本书,我就明白,即使需要成本,一套好的录音设备可以让这些用户(讲师)录制高质量的 screencast. 创始人就在践行这本书的理念。

注:本书的作者 Kathy Sierra 也是 Head First Java 的作者。

从「后端现在已经看不懂前端了」说起

V2EX 这两天有一篇这样的帖子:《说句实话,我后端现在已经看不懂前端了,太难了》

原文内容是:

看不懂惹,肿么办,最近在学 react,完全抓瞎。

然后又附加了内容:

一直用的 jquery,手动找位置然后对应 dom 操作,现在各种前端各种依赖,一会 route 一会 webpack 的 给爷整蒙了都。

类似这样的帖子多年来数不胜数,但这次评论区的评论比较典型,所以我决定这次不再沉默。当然,本文并非针对帖子作者。

对于那些抱怨前端「看不懂」的网友,我们应该问他们一个问题:为什么前端就应该是谁都能看懂?

这些网友从来不会抱怨别的领域的代码看不懂,比如他们不会说「我一个前端已经看不懂后端了」,或者「我一个后端已经看不懂 iOS」了。

所以在这些网友心中,前端代码就是要谁都看得懂才是正常的。因为谁都应该看得懂 HTML, 谁都看得懂 CSS.

显然,这群人对前端的认知还活在过去,他们一边享受着用 Web 技术做出来应用,一边意淫着这些包含复杂业务逻辑的 Web 应用靠 HTML 和 Vanilla JS (原生 JS) 就能写成。

换句话说,这群人最大的问题在于,他们意识不到 GUI 开发 (Graphic User Interface Programming) 是一个从图形界面被发明开始就一直在被研究的独立领域,而 Web 前端只是 GUI 开发的其中一个分支而已。

这群人没有意识到到底什么是「前端」。前端就是写 HTML / JavaScript / CSS 吗?不,前端应该泛指那些需要编写 GUI 的技术。在我看来,写 iOS / Android 应用和写 Web 前端没有本质上的区别,区别只是语言和平台,背后处理的问题是一模一样的:如何实现视觉和交互 -> 交互如何触发逻辑 -> 逻辑如何影响视觉的变化

所以,如果认为浏览器识别的是 HTML 所以认为前端应该谁都能看懂、门槛低,进而得出前端目前的工作流是问题复杂化的结果,就如同因为 Android 的 UI 是用 XML 写的就认为 Android 门槛低一样不成立得可笑

对 GUI 开发难点的不了解是导致这种误解的最主要原因,以网友「 avastms 」提出的评论为典型:

这是前端圈长期逆淘汰的结果。

模板混代码里这种 PHP 宗教行为就别提了。

个人认为因为 javascript 语言的部分原因,前端很少有人懂什么是继承,什么是类,实例到底啥意思,更别谈良好维护引用了。

什么 redux,什么 vuex 根本就是莫名其妙,不就是事件监听吗,像 Node.js 那样 EventEmitter 多么清晰,非得自己造概念。

画虎不成反类犬,凡是有这套什么状态管理逻辑的,代码完全不可读。

前端自己也是不争气,不用别人的这些狗概念,自己就不会写代码了吗?

没有什么 redux,自己就不会管理自己的属性了吗?

他说:「模板混代码里这种 PHP 宗教行为就别提了」,想必他在说 JSX. JSX 刚开始出来就充满争议,但人们最终会发现,JSX 是函数式 GUI 开发最易读写的方案。实际上,他完全可以选择不使用 JSX,这样去写代码:

const Component = h(‘div’, null, [h(‘p’, null, ‘hello world’), h(‘p’, null, ‘I am Randy’)])

但是,这位网友大概更愿意读这样的代码:

const Component = (
	<div>
		<p>hello world</p>
		<p>I am Randy</p>
	</div>
)

觉得这样的写法恶心,多数是因为不知道 JSX 这个语法糖的背后是什么。

事实上没有人阻止任何人不用 JSX, 或者看看 Flutter 就大概知道没有 JSX 的 React 会是什么样子。

作为补充,这是希望 Flutter 引入 JSX 的相关讨论:Consider JSX-like as React Native · Issue #15922 · flutter/flutter · GitHub

至于提到的 Redux 和 Vuex, 证明这位网友根本不知道 EventEmitter 和 Reactive 的区别。这里就不展开讨论了。

为什么我们宁愿用 JSX 这种折中方案也不愿意放弃函数式编程,是因为声明式(Declarative) 是编写 GUI 最好的方法。复杂的 GUI 应用包含复杂的状态,人脑在处理复杂的状态绝对不比计算机在行,我们尽可能地把这些状态交给计算机做,函数式编程就是我们实现这个目标的工具。

Elm 的作者写过一篇名叫 Concurrent FRP 的论文,讲述了函数式响应式编程 (Functional Reactive Programming) 在 GUI 开发中的历史背景和应用。

那些喜欢说「jQuery 一把梭」的网友,要么遇到的业务很简单,要么是在自虐。我见过写外挂用易语言一把梭的,但没听说过 Adobe 写 PhotoShop 用 Visual Basic 一把梭。

有些人,能接受 Maven, Gradle 的不完美,也知道什么场景应该用什么技术栈,但一旦开始写点前端代码,就认为应该有一个神一样的、完美的、还没有学习成本的构建工具,然后 Twitter, V2EX 抱怨一番,却不愿意认真学一学前端这个领域的知识。

不要妄想有一个睡醒就突然会用的构建工具。说实话,Webpack 不需要任何配置就能用,再不行试试号称 zero configuration (零配置) 的 Parcel, 用 TypeScript, 引入 CSS / LESS / SASS 通通不需要配置,跑起来就行。但那又如何呢,那些不愿意学习又想出活的网友们不懂的不是运行 webpack dev 这个命令,而是根本不懂这个命令背后解决的是什么问题。

一个人要锤子锤个钉子,结果大家都说雷神的锤子很牛逼。这个人听了,上来就要用雷神的锤子,结果拿都拿不起来。然后网上发个贴:「根本不懂为什么雷神要搞个这么重的锤子,给爷整蒙了都」。底下评论:「我工具锤一把梭!」楼下再附和:「雷神搞这个锤就是为了装逼」。

奉劝各位,把抱怨转化成求知,没有一个领域是不需要学习成本的,不管是做技术还是技术以外的领域,是分工和市场经济让各位觉得一切理所当然,然而像磨刀这样看上去简单的动作,也不是每个人都能磨好一把刀。

2019 年终总结:慢慢变成别人眼中「食古不化」的「怪人」

总结

2019 年 3 月我来到杭州,在蚂蚁金服开始了新的工作。这是我第一次在广东省外的地方生活。

新的环境,新的工作,对我来说是一个很大的挑战。挑战不在于如何适应一个新的环境,而在于在一个技术基建完备的环境里,如果业务难关不是技术可以解决的,那我的价值在哪里?在这个方面,我让很多对我抱有期望的人失望了。

「我的价值是什么」是我不断寻找答案的命题,我不满足只作为一个消费者活在世上。只有作为生产者创造自己独特的价值,才让我觉得自己是这个世界的一员。

我创造了很多,但好像只有写博客算是对别人来说稍微有价值的东西。现代的人又不怎么喜欢认真地看文字了,写好一篇文章的阅读量也不比同样内容的一个视频高。就连我一个关注了很久的独立博客博主,也转了型做视频。

「写博客」似乎已经是一件看上去「食古不化」的事,而我竟然越来越「食古不化」了。有时候到一家餐厅,被告知没有餐牌,只能扫码点餐,我会反问,「如果我没有手机呢?」。

我想,如果旁边正好有一位比我年轻十岁的客人听到我这样问,他心中也许会暗想,「x, 这个食古不化的傻x」。

今年下半年我又因为不能忍受国内社交网络的反智内容和评论,微信和微博都变成了只写的状态,除非收到消息,微信几乎不怎么打开。此举极大地改善了我的心理健康。但在别人眼里,就是一个不合群的怪人。

告别 2019,我就 25 岁了。这是一个不算年轻,也不算不年轻的岁数。如果开始「食古不化」是变老的征兆,那么我已经开始变老了。但如果不懒惰、不犬儒、不圆滑、不反智就是年轻,那么我将会永远年轻下去。

数据

博客

今年博客的 UV 比上一年多了 10, 000. PV 比上一年多了 40, 000. 《健身一年》 问率最高。

博客没有广告,所有收入来源于读者自发赞赏,今年收到的赞赏总共有约 200 元人民币。

共收到 11 封咨询信和很有心的感谢信。

音乐

今年做了 6 首翻唱,上传在 YouTubeBilibili:

播放量最高的是《奉献》和《黄昏》。

阅读

今年读的书里有以下几本想推荐给大家:

《学会提问:批判式思维指南》(豆瓣 | 购买

这本书提到了几个常见的逻辑谬误,以及如何去看待别人的观点,如何得出自己思考过的结论。这本书没有教你应该相信谁,而是不应该相信谁。

《非暴力沟通》(豆瓣 | 购买

我的书评

《颓废与沉默》(豆瓣 | 购买

这本书结合中国发生的实例解释了中国存在的「犬儒主义」,和它产生的原因和影响。

消费

  • Kindle Oasis ( 购买 ) 替换了我的 Kindle Paperwhite. 手感很好,放裤袋很轻松。是我今年买得最值的产品。
  • 黑卡 3 ( 购买 ) 替换了我的黑卡 1, 有可以旋转的屏幕,自己拍视频的时候轻松多了。
  • iPad Pro ( 我的评测 ) 替换了我的 iPad mini 2. 主要用来看微信读书,睡前浏览各个咨询网站。最后悔是没有买蜂窝版。

每天都看的内容

放空的时候看的内容

关于 2020

创造更多价值吧。

为了你爱的人,读一读《非暴力沟通》

我和父母的关系很糟糕,在小时候,我们之间的「交互」方式大部分是命令、教训,严重的时候甚至打一顿。我很讨厌被命令去做家务。

成年以后,即使脱离了原生家庭,但无论是工作伙伴之间的合作,还是处理亲密关系,都偶尔会出现矛盾,形成无效的沟通,甚至产生情绪。

很幸运我读到了《非暴力沟通》这本书,让我发现我在日常接触到的沟通中,多少隐藏着无形的暴力。

全书围绕非暴力沟通的四个要素进行讲解:观察、感受、需要、请求。如果沟通没有这些要素,那么这些沟通最终的结果很可能是情绪化的收场,又或者成为了压死骆驼前的每一根稻草,总有一天爆发。

在一段亲密关系中,我们很容易听到或自己说到过这样的话:「你怎么每次都这样」。这样的一句话,很容易使对方产生抵触情绪,导致情绪化的争吵。因为这句话也只是单纯的情绪发泄和评价,不能指望用这样情绪化的沟通去解决实际存在的问题。它缺少了非暴力沟通的四个要素。

如果读完《非暴力沟通》,你可能再也不会说出「你怎么每次都这样」这样的话。你会先说出你的观察:在这个月,你已经是第 3 次这样做了。然后说出你的感受:我觉得你这样做让我觉得你不是一个靠谱的人。再说出你的需要: 我想要一个靠谱的另一半而不是这样的人。最后说出你的请求:你可以不再这样了吗?

很多人没有区分「观察」和「评论」,像有人常说「那个产品经理真是个脑残」,这是一种评论。评论对于沟通是没有帮助的,他只会让别人厌恶,抵触。我们应该说出具体的观察:那个产品经理做的大部分需求都会改很多次,而且功能上线后用户基本都是负面反馈。

我们也常常忽略、或者故意隐藏了自己的感受和需要。在沟通中,让对方清楚自己的感受和需要是很重要的。最常见的就是在亲密关系当中,一方总会把自己的感受和需要埋在心中自己消化,另一方事实上并不知道对方的感受,潜移默化就造成了关系破裂的危机。

暴力的根源在于人们忽视彼此的感受与需要,而将冲突归咎于对方。

非暴力沟通的第四个要素:请求,也很重要。对方即使清楚你的感受和需要,但不一定知道应该怎么做。恋爱中,一方需要陪伴,那么应该直接说出具体的请求:工作日每晚至少花两个小时在一起。

我们将自己想要的回应讲得越清楚,就越有可能得到理想的回应。

在工作中「请求」同样重要。如果在会议中,发言的人在最后不明确说出想要得到与会人员怎么样的反应,只问大家「有什么想说的」,那很可能大家就没有什么想说的。但如果明确地问:「对于这个方案,大家有没有想到可能存在的缺点?」,那么就更有可能得到你想要的回应。

但如果不说出感受和需要,直接说出请求,就会像一种命令。例如我小时候母亲让我做家务的时候,对我只有一句话:「快去做清洁!」。对我来说是一种不得不服从的命令,服从只是因为她是我母亲,而不是她的感受和她的需要,会让我十分抵触。

除了非暴力沟通的四个要素,书里最后也提到了充分表达感激的重要性。对别人表达感激也有三个要素:说出对我们有益的行为;我们哪些需要得到了满足;我们是什么样的心情。

这就是为什么对于我来说,比起别人说「你真牛逼」,我更愿意看到博客读者来信告诉我,我的哪篇文章的哪一个点,让他得到了什么样的启发。

有一次我在看 React Conf, 很喜欢其中一位讲师,在 Twitter 上提到了他,他说「That means a lot to me」, 我意识到,不能吝啬表达自己的感激。

我想,如果在我的成长环境中,我的父母也掌握了非暴力沟通的技巧,我们彼次都懂得表达彼此的观察、感受和需要。也许不至于让我对原生家庭有那么多负面的情绪。

所以,为了你爱的人,应该读一读《非暴力沟通》

❌