MoreRSS

site iconCat Chen | 陈广琛修改

程序员、作者、演讲者、职业教练。曾就职于 Robinhood、Facebook、豌豆实验室、Yahoo、百度,就任工程师及经理。在美工作。
请复制 RSS 到你的阅读器,或快速订阅到 :

Inoreader Feedly Follow Feedbin Local Reader

Cat Chen | 陈广琛的 RSS 预览

Reverse Centaur

2026-05-07 08:08:22

Reverse Centaur(逆向半人马,也就是马的大脑驾驭人的躯体)是一个很少人讨论的概念,我甚至没见过有人用中文提及。

1997 年 Garry Kasparov 被深蓝击败后提出了 Centaur Chess(半人马象棋)的概念:业余选手搭配普通算法,既可以击败大师选手,又可以击败顶级算法。2000 年到 2010 年期间,这个概念被证实可行。(之后神经网络彻底超越人类,人类的介入只会让结果更差。)

Centaur 成了一个泛指「人类利用和指挥机器」的概念,随后 Cory Doctorow 提出了 Reverse Centaur 的概念,指代「人类被机器指挥和控制」。零工经济(例如外卖骑手)就是典型例子,算法决定如何调度人,被调度的人缺乏对大局的了解,只是充当机器临时的四肢(跑腿),等待被无人驾驶和机器人取代。

AI 时代,鼓吹者当然说未来是半人马模式,人类利用 AI 能做更多原本想做而不能做的事情。但现实也可能发展为更加阴暗的逆向半人马模式,AI 把人类当作外设来用,人类被迫去做自己不想做的事情。

DEI 是不应该有配额的

2025-04-19 11:24:00

我还记得在 Facebook 参加「反歧视」培训时,我举手提了一个问题:「如果我们不是歧视少数的一方,而是歧视多数的一方,这是否合法?」我得到的答案是「逆向歧视也是一种歧视」。这意味着不歧视的话,招聘招到什么样的人就是什么样的人,不可能给少数群体安排一个配额。(除非招聘总人数没有上限,在少数群体满足配额之前无论招到什么人都继续招聘。)

Facebook 在这方面是说到也做到的,不存在任何针对少数群体的配额。针对少数群体的弱势地位,有几件事情可以做:

  1. 可以放宽招聘的入口。如果招聘职位招到的都是男性,那考虑一下是不是招聘广告在女性面前的暴光率就不够,让更多的女性进入招聘的漏斗是可以的,但进入了漏斗之后的层层筛选就不能有歧视了。
  2. 可以在人远远没到招聘之前提供帮助。例如说那些针对中学生的编程课程,有女生想要加入就应该欢迎她们加入。如果有女生因为社会刻板印象而怀疑自己是否应该加入,可以鼓励她们撇开社会刻板印象从自身的兴趣出发考虑要不要深入学习编程。
  3. 可以对员工提供「无意识歧视(unconscious bias)」的培训。有时候员工并没有主动歧视某个群体,但是社会刻板印象会导致他们无意识地就在歧视某个群体,例如说有出差的机会时就默认怀孕的员工不会想要参加。这种培训可以让大家留意到自己日常工作中的一些无意识行为,从而避免因此引入的歧视。

我之前听说 Google 的招聘存在针对少数群体的配额,如果属实的话这其实也是一种歧视。


为什么有些政府或企业会设立 DEI 配额?因为这样子才能显示出自己真的有贡献,产生了影响(impact)。我上面所说的 Facebook 的做法,往往是无法在数字上体现成果的,一测量就会发现数值没产生足够可信的变化,数值浮动可能纯粹是噪声。这样很容易引起别人的质疑,「投入这么多资源去改善少数群体的处境,到底有没有效果?」

设立少数群体的招聘配额或者是晋升配额,通过逆向歧视完成配额,至少避免了上述的质疑,因为少数群体的实际招聘和晋升人数或比例每一年都在涨。这时候政府和企业就可以大谈自己在 DEI 上面的贡献有多少,成果有多出色。然而正是这种急功近利的做法,导致了最近反 DEI 的现象。

Donald Trump 反对 DEI,并且说要创造一个「colorblind and merit-based」的社会,然而真正符合 DEI 精神的做法本身就应该是「colorblind and merit-based」的。他和他的支持者实际反对的是逆向歧视,这是一个合理的诉求,毕竟逆向歧视也是歧视,只不过受到伤害的是多数群体而已。但是这种做法把 DEI 的名字搞臭了,现在不再有一个能代表真正「colorblind and merit-based」的词汇。


真正做 DEI 必须要沉得住气,在数据长期不动的前提下坚持做自己认为正确的事情。我在 Facebook 内部接触到各种帮助员工提升的群体,大多数帮助的结果都无法量化,但如果你相信你帮到了别人你就应该继续做下去。

我曾经跟负责 Facebook 女工程师成长的资深工程师聊过,她说她们组织资深的女工程师对其他女工程师提供帮助,结果就是无法量化。无论你如何分析晋升和绩效数据,都没有可量化的结果。有时候做好事和面子工程是不可兼得的。她最后的结论是,「如果你认定事情是对的,你就应该坚持去做,无论最好是否有可量化的结果」。

做给 GitHub Actions 开发者用的 Actions(Part 2)

2024-07-29 12:37:00

在开发 GitHub Actions 时,有时候会遇到这样的问题:如果这个 Action 接受来自用户的 GitHub token,那该如何以这个 token 背后的身份完成所需的 git 操作?

我在上一篇文章里介绍了我做的 config-git-with-token-action,专门用来解决这个问题的,但这个 Action 在配置 git 的时候又是怎么获取对应的用户名和邮箱的呢?这是通过另外一个叫做 token-who-am-i-action 来解决的。

这个 Action 可以接受用户生成的 PAT (Personal Access Token),也可以接受 GitHub App 的 token。(默认的 GitHub Actions token 当然是接受的。)它会使用 Action 调用 GitHub API 查询关于 token 自己身份相关的信息,因为类似于 Linux 的 whoami 命令所以我把它叫做 token-who-am-i-action


这个 Action 能返回的信息当中,首先要看 typeUser 还是 Bot

  • 如果是 User 的话,它所返回的其他信息包括 nameemail。这两项都可能是 undefined,因为用户可以选择隐藏自己的名称和邮箱。
  • 如果是 Bot 的话,它必须返回 nameemailappSlug,全部都是字符串,不可能是 undefined。每一个 GitHub App 必须有一个用于显示的名字,然后由此生成对外公开的地址(格式为 https://github.com/apps/${appSlug})。GitHub App 其实并不存在邮箱,但使用特定格式(${id}+${login}@users.noreply.github.com)生成的邮箱会被正确识别并显示正确的头像,例如说 GitHub Actions 默认 token 的身份使用的邮箱是 41898282+github-actions[bot]@users.noreply.github.com

除此之外,无论是哪种类型的身份,这个 Action 还能返回 loginidglobalIdlogin 对于 User 来说,就是他的个人页面地址(https://github.com/${login})中的路径,有些文档也把这个叫做 username;对于 Bot 来说,这可以由 appSlug 通过特定格式(${appSlug}[bot])生成,例如 GitHub Actions 的就是 github-actions[bot]

至于 idglobalId 分别用于 REST API 和 GraphQL。这是 GitHub 数据存储很有意思的一个地方。对于每一种 REST API 的类型(如 user),它背后都是一张独立的关系型数据表,都有一个这种类型内部唯一的 id,但这个 id 可以跟其他类型冲突。在 GraphQL 里面,因为 id 可以用来查询任何类型,所以引入了 globalId 的概念,保证即使跨类型依然唯一。GraphQL 的某些 query/mutation 的 id 默认就是 globalId;但某些必须加上 X-GitHub-Next-Global-ID: 1 的 header 进行请求才是 globalId,否则就是跨类型不唯一的 id


那我们如何在编写自己的 Action 时调用 token-who-am-i-action 呢?如果你在编写的是 composite action,可以这样写:

runs:
  using: 'composite'
  steps:
    - uses: CatChen/token-who-am-i-action@v1
      id: token-who-am-i
      with:
        github-token: ${{ inputs.github-token }}

    - shell: bash
      env:
        LOGIN: ${{ steps.token-who-am-i.outputs.login }}
        GLOBAL_ID: ${{ steps.token-who-am-i.outputs.global-id }}
        ID: ${{ steps.token-who-am-i.outputs.id }}
        NAME: ${{ steps.token-who-am-i.outputs.name }}
        EMAIL: ${{ steps.token-who-am-i.outputs.email }}
        TYPE: ${{ steps.token-who-am-i.outputs.type }}
        APP_SLUG: ${{ steps.token-who-am-i.outputs.app-slug }}
      run: |
        echo "Login is $LOGIN"
        echo "Global id is $GLOBAL_ID"
        echo "Id is $ID"
        echo "Name is $NAME"
        echo "Email is $EMAIL"
        echo "Type is $TYPE"
        echo "App slug is $APP_SLUG"

如果你编写的是 JavaScript action 可以先从 NPM 安装同名的 token-who-am-i-action 包,然后再进行调用:

import { tokenWhoAmI } from 'token-who-am-i-action';

const me = await tokenWhoAmI(githubToken);

const {
  login,
  globalId,
  type,
} = me;

if (me.type === 'User') {
  const {
    id,
    name,
    email,
  } = me;
} else if (me.type === 'Bot') {
  const {
    appSlug,
    id,
    name,
    email,
  } = me;
}

希望这个 Action 对各位 GitHub Actions 开发者有用。非 Actions 开发者也可以直接在 Workflow 里面使用这个 Action,如果你的 Workflow 使用非默认的 GitHub Actions(机器人)身份进行 git 操作的话。大家在使用过程中遇到什么问题,或者是希望增加什么新功能,欢迎到项目的 GitHub 开 issue。

做给 GitHub Actions 开发者用的 Actions(Part 1)

2024-07-01 13:30:00

在开发 GitHub Actions 时,有时候会遇到这样的问题:如果这个 Action 接受来自用户的 GitHub token,那该如何以这个 token 背后的身份完成所需的 git 操作?

使用 token 操作 GitHub API 是很容易的,通过 @actions/github(或 @octokit/core)创建一个 Octokit 实例时把 token 传进去就可以了,之后通过这个实例进行的所有 API 调用(包括 REST 和 GraphQL)都会以这个 token 的身份进行。但命令行的 git 操作怎么办呢?如何让 git commit 的作者变成 token 背后的身份?如何让 git push 以 token 背后的身份进行提交?(这两者并不一定要用同一个身份。)我做了 config-git-with-token-action 就是用来解决这个问题的。

这个 Action 会对 ghgit 进行配置,让它们的身份信息变成 GitHub token 背后的身份信息。gh 的配置相对简单一些,把 GH_TOKEN 这一环境变量配置好就行了,然后执行 go auth status 就能打印出 gh 认为自己在使用的身份信息。对 git 进行配置稍微麻烦一些,gh auth setup-git 只能保证 git 在跟 GitHub 交互时从 gh 获得身份信息,但并不指明具体是哪一个身份。为了保证 git commit 使用正确的身份,需要通过 git config 来设置正确的用户名和邮箱。此外,为了保证 git push 使用正确的身份,需要通过 git remote set-url origin 来更新上游地址,在 https://github.com/… 的地址中注入用户名和 token,让它变成 https://username:[email protected]/…

这个 Action 假设用户在执行它之前已经执行过了 @actions/checkout,所以它不会尝试自行建立项目目录。大家最好使用同一个 token 进行 @actions/checkout,这样项目目录从一开始就是以 token 背后的身份创建的。以下是一个完整的用例:

runs:
  using: ‘composite’
  steps:
    - uses: actions/checkout@v4

    - uses: CatChen/config-git-with-token-action@v1
      with:
        github-token: ${{ inputs.github-token }}

    - shell: bash
      run: |
        echo “Set up git user name: $(git config —get user.name)”
        echo “Set up git user email: $(git config —get user.email)”
        echo “Set up git remote origin with login and token: $(git remote get-url origin)”

    - shell: bash
      run: |
        touch test_file
        git commit test_file -m ‘Created test file’
        git push

做为 GitHub Actions 开发者,如果你利用 JavaScript 而非 bash 进行开发,那上述 composite action 的用例并不适用,我们需要一个针对 JavaScript action 的用例。我自己有同样的需求,所以 config-git-with-token-action 同时还是一个 NPM 包,可以在 JavaScript 中进行调用获得同样的功能。(这个包具备完整的 TypeScript 类型信息。)安装好之后,通过 JavaScript 调用的用例如下:

import { configGitWithToken } from ‘config-git-with-token-action’;

await configGitWithToken(githubToken);

希望这个 Action 对各位 GitHub Actions 开发者有用。非 Actions 开发者也可以直接在 Workflow 里面使用这个 Action,如果你的 Workflow 使用非默认的 GitHub Actions(机器人)身份进行 git 操作的话。大家在使用过程中遇到什么问题,或者是希望增加什么新功能,欢迎到项目的 GitHub 开 issue。

至于这个 Action 是如何获取到 token 背后的身份信息的,那是这个系列的下一篇文章要介绍的下一个 Action 负责的。

如何扩大工作的 scope?

2024-03-02 03:17:00

在美国工作的程序员都会遇到一个问题:想要晋升但经理说自己工作的 scope 不够大,又或者是影响力不够大。那要如何才能扩大自己工作的 scope 呢?

职业前期直系经理会负责为你找 scope,一步一步地给你更大的 scope,到了后面经理就会说「自己找 scope,这是晋升到下一个级别的要求,我给你 scope 就满足不了这个要求了。」既然不能依赖别人给你 scope,那要去哪里找扩大 scope 的机会呢?

这是很多人感觉到困扰的问题。假设自己在做的 scope 大小算作 1,把上下左右的外延做了也就是 1.1、1.2、1.3……的增长,这扩张速度太慢了。找一个跟自己已有 scope 相似但需要花同样功夫的 scope,那可以从 1 变成 2,但工作量跟着翻倍,拼命卷可以升一级,但 scope 大小从 2 到 3 是不可能的。况且职级线性上升时需要的 scope 是指数上升的,卷到三倍的工作量也没用,公司期望 scope 按照 1、2、4、8……的速度来增长。


在这篇文章里面,我们的关注点是如何找到更大的 scope,不是有了更大的 scope 后怎么做出来。怎么做出来当然是个难题,但很多人被卡在了找不到 scope,所以必须先解决这个问题。找不到 scope,不意味着找到了就有能力做出来,但找不到的时候人就会觉得自己有能力无处施展,这会导致很强的挫败感。

如何能找到更大的 scope?关键是要走出去跟更多人接触。技术再厉害的人,把自己关在小黑屋里面对着代码库发呆,是几乎不可能找到更大的 scope 的。你只能看到你已经知道的问题和机会在哪里,最多再看到一点外延,但指数级扩大的问题和机会很难闭关冥想出来。

我们可以在白纸上画三个从小到大的同心圆,分别代表:我能控制的事情、我能影响的事情、我能感知的事情。如果只考虑工作上的事情,这三个集合应该是两两子集的关系,「我能控制的事情」是「我能影响的事情」的子集,「我能影响的事情」是「我能感知的事情」的子集。

找不到更大的 scope,问题往往来自于「我能控制的事情」扩张到逼近「我能感知的事情」的边界了,然后就找不到空间继续扩张了。想要对着「我能控制的事情」大力出奇迹是很难有效果的,把工作量翻倍后 scope 依然上不去。真正能扩大 scope 的,是先把「我能感知的事情」扩张出去,有空间了再把「我能影响的事情」扩张出去,最后才能把「我能控制的事情」扩张出去。

如何能够把「我能感知的事情」扩张出去?我们必须走出去,接触更宽广的世界,接触更多各式各样的人。在一家大厂内部,这往往就是走出去跟别人聊天。我们对自己每天在做的项目、在解决的问题有太深的了解了,我们需要了解别人尝试解决什么问题、有什么问题解决不了,更大的 scope 就埋藏在那里。


如何走出去跟别人聊天?可以先从已经跟你有交集的人开始。肯定存在一些人,项目上跟你有合作,但有限的合作导致你们的交互很少,你不知道在合作之外他的主要工作是什么。你可以约他们喝咖啡,不要聊你已经知道的事情,问一下他在合作以外的主要工作是什么,以及他的工作有什么有意思的地方、有什么难题或者是不爽的地方。

关键是你要会聆听,他说的事情你要么理解了,要么通过更精准的问题追问。想象一下你跟他聊天后需要回来汇报跟他相关的所有信息,你自然会仔细听,甚至会做笔记,不会瞎扯一番大家爽了但谁也不记得聊了什么。这个对话应该像个自然的社交聊天,你不能如同审问对方一样获取信息。对方说的事情,你有共鸣的可以回应,你有不同观点的可以分享,这才是一个对话。

职级比较低的人,对于找职级比较高的人聊天可能存在心理上的障碍,觉得不应该浪费对方宝贵时间,错误假设自己懂的对方都懂了,所以对方不会从对话中获得任何价值。其实职级比你高的人也是人,你跟他们聊天可以提供情绪价值,有时候还能帮他们理清思路。如果长远来看你们会有更多合作,他们肯定乐意花时间跟你建立良好的信任关系,这包括花时间互相了解对方,尤其是对方的思维方式、思考问题的出发点。

刚刚开始跟几个人聊天时,你会获得大量碎片化的信息。聊的人多了之后,慢慢这些信息就会连接起来,呈现出公司内部更大的图谱。你需要在这个更大的图谱当中寻找当前运作得不够好的地方,例如说可靠性低或者是效率低的地方,然后想想你是否有想法和有能力去解决,这些都是你潜在的 scope。可能你没有信心直接玩起袖子开干,但你至少有素材跟你经理进行对话,说说你看到的机遇在哪里,让他就什么机遇更适合你提出他的看法。

Vision Pro 使用体验(Part 2)

2024-02-19 13:26:00

Vision Pro 的「截屏」功能非常符合 iOS 和 watchOS 用户的习惯,把仅有的两个物理按键同时按下去就可以了。跟用 Vision Pro 拍摄 3D 视频没有取景框一样,截屏时你没办法知道截屏边界在哪里。Vision Pro 给你一个相当宽的可视角度,但实际截屏时它会截取一个 16:9 的区域然后保存为 1920x1080 的图片。

考虑到 Vision Pro 两块屏幕加起来像素超过 4K 屏幕,截屏 1920x1080 的分辨率实在是有点低。你可以截屏分享给别人,让别人看看你佩戴 Vision Pro 的体验是怎样的,但千万不要指望别人能够看清楚你第一人称视觉能看清楚的细节,更别期望别人能看清截屏上的小字。经过 3D 到 2D 的投射之后,截屏上偏小的文字是很难看清楚的,需要很用力地看才能看明白。

此外,我不知道 Vision Pro 截屏时使用的是左眼还是右眼的视觉,这值得研究一下。


把 Vision Pro 变成 Mac 外置屏幕的功能还不错。就算是 16“ MacBook Pro 的屏幕也只有 16”,但用 Vision Pro 打开瞬间可以变成 75" 的大屏幕,而且依旧看不到像素。这个屏幕可以用来玩游戏,游戏的计算应该是在 Mac 上进行的,Vision Pro 只是投屏而已。对于游戏来说,有一个巨大的投屏可比 MacBook Pro 的屏幕爽多了!而且这个投屏还不受物理空间限制,即使你的房间没那么大,放下 MacBook Pro 后就没多少空间了,你依然可以在 Vision Pro 里面放置一个突破房间墙壁限制的大屏幕。

比较遗憾的是缺乏一台 Mac 多屏幕投屏的支持。Vision Pro 只能显示 Mac 的主屏幕,不能选择增加屏幕。如果在投屏到 Vision Pro 之前 Mac 就已经连接了外置屏幕,投屏后所有外置屏幕都会熄灭。Mac 自身的主屏幕也会熄灭,既然投屏了就没必要在 Vision Pro 外面显示一模一样的内容了。这对于注重隐私的人来说会很有用,例如说在咖啡店或在飞机上用 Mac 加 Vision Pro 投屏,别人就看不到你的屏幕了。

想把 Mac 投屏到 Vision Pro 时,可以在 Vision Pro 里面抬头调出头顶上的 Control Center 然后选择连接附近的 Mac。更加神奇的连接方式是,在 Vision Pro 里面以穿透方式看着一台 Mac 的屏幕,Vision Pro 自动能够识别出这是哪台 Mac 然后在 Mac 的屏幕上方放置一个投屏按钮,点击按钮就会开始投屏。


反过来,你在 Vision Pro 里面看到的画面也可以投屏到 Mac 或 iPad 上。这很适合用来做演示,把自己在 Vision Pro 里面看到的内容分享给身边的人看。操作起来跟 iPhone 投屏到 Mac 上一模一样,在 Vision Pro 的 Control Center 选择 Screen Mirroring 就可以了。

访客模式是使用投屏的另外一个常见原因。visionOS 不像 macOS 那样存在多用户登录,更像是 iOS 那样只允许单一用户,只能够绑定单一 iCloud 帐号。如果想要把 Vision Pro 借给别人使用,就要开启访客模式,之后可以限制访客能够打开的应用(他会看到能打开的应用中你的所有数据)。为了更好的指导访客使用你的 Vision Pro,或者是为了更好地监控他在你的 Vision Pro 里面干什么,你可以在开启访客模式的同时投屏,那你就能看到它看到的画面了。


前面说到 Mac 投屏突破房间墙壁限制,我可以解释一下 visionOS 的 2D 窗口是如何跟现实世界的 3D 物品重叠的:应用的 2D 窗口永远会优先于现实世界的物品。举个例子,我可以在我和应用窗口之间放一个小盒子,理论上这个小盒子应该会遮挡窗口的一部分,而且 visionOS 确实能扫描到这个小盒子的存在。然而 visionOS 并不会让这个小盒子穿透显示到我的 Vision Pro 屏幕上,应用窗口即使在小盒子背后也会被优先显示出来,导致小盒子被隐藏起来。唯一例外的是我的双手,把手举起来放在 Vision Pro 和窗口之间,手是会被显示出来的,我可以看清楚手和窗口的互动操作。

Vision Pro 使用拇指和食指触碰一下表示单击,这是大家都在官方视频中看到的,但其实还有另外一种点击方式。只要把窗口拉到自己面前,手指可以直接点击窗口上的按钮,手指穿越 3D 空间中的 2D 窗口会被视为点击,手指穿越窗口后上下左右移动会被视为拖拽。这种设计使得 visionOS 里面显示的所有 2D 窗口名义上都是 iOS 一样的「触摸屏」,习惯触摸屏的用户会发现这非常符合直觉。


这次就写到这里吧,接下来想到有新内容再更新。这个系列的《Vision Pro 使用体验》,我准备想到哪里就写到哪里。不想错过接下来的内容的话,敬请关注和订阅。这篇文章首发于我的 Patreon,大家可以到 Patreon 上付费支持我写作。