2020年1月17日星期五

AlgoTogether 算法学习小组

我开了一个叫做 AlgoTogether 的算法学习小组,面向在美国寻求软件工程师工作(实习或全职)的人。我知道仅仅通过 LeetCode 准备算法面试是不够的,因为面试并不以题解的优劣来衡量你。你需要让面试官想要和你共事,这需要说服他你能跟他一起通过编程解决难题。

AlgoTogether 是一个有教练指导的学习小组,帮助你训练多方面的面试能力,让你在面试时成为面试官的最佳未来同事。在这个学习小组中,你不仅仅需要解题和编码,你还需要练习沟通你的解题思路和接受来自别人的反馈。我们的教练是 ACM/ICPC 奖牌得主,也曾带队其它学生参赛获奖,此外还是大型科技公司中富有经验的面试官。我们保证你投入到面试准备的每一滴汗水都能有充分的回报。

为了保证跟面试和工作环境一致,整个 AlgoTogether 采用全英语沟通。以下是 AlgoTogether 的详细信息及报名链接:

What is this program?

AlgoTogether is an algorithmic problem study group with a coach. The program focuses on all necessary skills for coding interviews: problem-solving, coding, debugging, articulating solutions, taking feedback. The coach leads the meeting and makes sure that students learn these skills in a way that they can reapply to new problems in real interviews.

What is the value of this program?

  1. Understand how an interviewer evaluates you. You are evaluated beyond correctness and optimality. If a company only evaluates these two it will replace human interviewers with LeetCode to save money. You should learn what’s missing beyond your LeetCode practice.
  2. Practice like you are in an interview. You will practice articulating your solution to an interviewer and taking hint or feedback from an interviewer. That’s what you don’t experience if you simply practice with LeetCode.
  3. Hold yourself accountable. Are you willing to commit to finishing certain amount of problems within a specific time frame? If you can make the commitment, we will hold you accountable and prevent you from slacking.

Who is this program for?

People who are highly committed to getting a software engineer job at one of the well-established tech companies. It’s best for people who are seeking a software engineer job for the first time, for example, newly graduated students. It’s also good for existing software engineers who haven’t done interview preparation for more than a year.

What is the structure of the program?

The program is 8-week long. Each week the coach assigns a set of problems. Students have one week to work on them. Then they present their solutions at the weekend meeting. If they need help they can join the mid-week meeting to discuss. The coach will lead both meetings.

Who is the coach?

Our coach has many years of experience working and interviewing candidates at well-established tech companies. He is also an ACM/ICPC algorithm competition medalist and has coached other students for the competition.

How much does it cost?

It’s FREE! We are going to run our free pilot program in 2020Q1. However, we will ask for a $500 deposit and we will refund it when you finish the program. If you fail to finish the program you will forfeit this $500. It’s our way of identifying committed students and hold everybody accountable.

How do I sign up?

Please fill out the information in the following form:

https://chen.cat/algotogether-pilot-program-signup

We might not be able to accommodate everybody. We will contact you through email if you are selected.

2020年1月7日星期二

Career Coaching 市场的思考

我在上一篇文章(《Career Coaching 价值的思考》)里说到了我对 coaching 价值的思考,接下来我想说说我对 coaching 服务市场的思考。我可以从我的定价策略开始聊。


基于市场订价

我设置每小时 $300 的价位,基本上就是比行业底线稍微高一点。还在 Facebook 工作的时候,我就跟公司的学习与发展中心(Learning & Development Center)聊过,了解到了为员工采购外部 coach 的价格。最高端的 executive coach 可以高达每小时 $1000 左右,最便宜但仅提供远程视频服务的 career coach 也要每小时 $200。基于这个信息,我觉得我把自己定价为每小时 $300 是合理的。

我可以理解为什么有人觉得这个价格太高(或者是认为总体上 career coaching 就是太贵)。我经常遇到的观点有以下几种:

  • 你教的知识我可以免费在网上获得,为什么我要付费买你的服务?我在《Career Coaching 价值的思考》里面说到了,知识获取的成本确实趋近于零,但复杂的知识不容易消化,所以真正值钱的服务在于帮助别人把知识变得容易消化,保证最后知识能被吸收。
  • 如果你真的那么擅长做你教的事情,你怎么会愿意花时间来教别人?最好的教练往往不是最好的球员,最好的球员往往也不是最好的教练。球员专注于把自己发挥到极限,经验主要来源于自己踩过的坑;教练需要帮助多名球员发挥得更好,经验来自对多名球员踩坑的观察。这两个角色是不一样的。我对于我教的事情擅长到一个程度,但我更喜欢去教别人,观察别人在学习过程中会遇到什么问题,然后创造性地去解决那些问题。
  • 跟你有类似资历的人其实不少,凭什么你可以收那么贵?跟上一条说到的类似:优秀的球员不一定想要成为教练,优秀的球员也不一定有能力成为优秀的教练。因为想要做教练并且有能力做教练的人不多,所以教练市场的供应由教练数量决定而不由球员数量决定,不能用球员的数量来估算教练的价值。跟我有类似资历的人大部分要么不想做教练要么做不好教练,暂时来说市场还是供不应求地,所以价格并不便宜。

如果大家能够站在宏观的角度看待整个 coaching 市场,就会发现教练暂时还是稀缺资源,所以定价不会便宜。我自己有一个愿景,是希望 coaching 这个市场能越做越大,教练越来越多,同时也有越来越多的学生消费得起。


定价的主观因素

除去上述市场供需的客观因素,coaching 的定价也有主观因素在内,也就是消费者主观地认为服务值多少钱。

据我观察,coaching 的定价非常难跟效果挂钩。因为 coaching 是针对每个人定制的,无法形成对比,所以也就无法对效果进行定量分析。(定量分析是指:如果这位学生获得了 coaching 服务将会得到结果 X,如果他没有获得 coaching 服务将会得到结果 Y,所以 X - Y 就是 coaching 创造的价值。)我们只能对 coaching 进行定性分析,也就是调查学生本人和身边的人是否觉得 coaching 让学生变得更好了。这个「更好」值多少钱,是消费者主观进行的评估。

这使得 coaching 的价值有点像是艺术品的价值,我把这称之为「主观的为才是用(subjective meritocracy)」。一方面,这个市场具备为才是用的性质,也就是越优秀的人能够获得越高的回报。另一方面,因为「优秀」是一个主观定义,所以参与者获得的回报高低也就受它人主观评价的影响。举个例子,两个画家各自画了一幅油画,他们分别能把自己的油画卖到什么价钱要看买家的主观评价,这不是由他们客观的绘画技能高低决定。

这意味着不同的人有着不同的 career coaching 心理价位。我的定价就像是一个过滤器:心理价位比我定价高的会选择购买,心理价位比我定价低的不会购买。这样的过滤器会帮我筛选出那些理解并认同 coaching 价值的学生,这些学生会以更积极和更开放的心态拥抱 coaching,结果当然是 coaching 效果更好。

如果我纯粹只是想要优化收入的话,我应该根据我能接收的学生数量来定价,使得愿意购买的人数正好等于我愿意接收的学生数量。实现手段可能就像艺术品拍卖一样,在一个时间段内拍卖一批名额,按照拍中的最低价格对所有拍中的学生收费。当然,这只是个虚无的构想(thought experiment),我并没有真的打算这样做,因为收入还不是我的主要目标。


价格对学生的反向激励

不仅仅消费者对 career coaching 的定价存在主观因素,反过来 career coaching 的定价也会影响学生主观能动性,这是非常有意思的一件事情。因为定价是主观的,所以定价偏高的话会让学生觉得这应该是物有所值的,因而更努力的吸收知识和练习技能;定价偏低的话反而会让学生觉得就算花了钱没学到也没浪费多少钱,因为有时候会松懈,并且更容易中途放弃。

我自己有花钱参加游泳教练的小班训练,至今已经两年多了。我已经游了二十多年的自由泳,但仍然觉得教练能给我带来比我自己摸索更高效的提升。然而因为一节课的价格不到 $20,有时候天气太冷了我就会选择逃课,因为感觉浪费 $20 也就是在湾区吃一顿放的价格,而且真有需要的话我可以再去插班补课。(我实际补课的次数肯定少于逃课的次数。)我觉得这就是价格偏低的问题,让人觉得错过了损失不大。

据我所知,有一些高端的健身房和健身教练收取较高的费用就是为了让消费者狠下决心,既然花了钱就必须把应有的价值给赚回来,所以健身才会更努力,结果自然也会更好。从这个角度来看,越贵的 coaching 越有效果在一定程度上是自证预言(self-fulfilling prophecy)——因为贵,所以学生更投入,所以效果更好。我在跟我的学生讨论价格上调时说到这个观点,有一个学生开玩笑地跟我说「那你应该尽早的涨价啊,那样子我会更加投入」。


总结一下,career coaching 并不是一个完全竞争的、由边际成本决定价格的市场,因为供应还是远小于需求。(在我看来,人人都可以从 coaching 获益,因为效率比自学更高,所以需求理论上是非常之大的。)价格跟成本的关联性很弱,更多的是看消费者主观的心理价位。现在 career coaching 的价格都不低,但高价在一定程度上更能激发学生的主观能动性。

我的愿景是让更多人获得 coaching。我暂时没有一个清晰的思路如何能达成这个目标,但我会去尝试。我此时此刻的想法是学习 Tesla 的模式——先做只有少数人买得起的电动跑车 Roadster,只需要有小规模的成功就能赚到足够钱做高端豪华车 Model S,在 Model S 能够获得大规模成功并验证市场后再用赚到的钱做低端豪华车 Model 3,最终通过 Model 3 把电车普及给大众。只做 Roadster 无法改变世界,但一开始就做 Model 3 也不切实际,所以这必须是一个分多阶段来实施的计划。

最后,如果你喜欢我的文章,欢迎通过邮件RSS/Atom 订阅我的博客。

2020年1月2日星期四

把我的个人网站推倒重来(Part 9 - 精简 Bootstrap 编译)

我在很久之前一篇文章里讲述了我为何选择 Bootstrap 作为样式框架,在那篇文章的结尾我提到了一个我当时没做的优化:去掉我不使用的 Bootstrap 模块。

我现在终于有时间把这项优化做了,我可以先说说做好的效果:Bootstrap 全部的 CSS 共 152kb,在优化后变为 72kb,节省了一半的体积。(压缩后的网络传输体积由 23kb 优化到了 11kb。)这个优化的效果还是很明显的,接下来我说说我具体做了什么吧。(具体代码可以参考我的 pull request。)

引用 Bootstrap 源代码

我们要去掉了不需要的 Bootstrap 模块,就要手工选择 Bootstrap 的 SCSS 文件,然后只编译和引用我们需要的。我们当然可以下载 Bootstrap 的 SCSS 文件,不过更好的做法是直接用 Git 的 submodule 概念引用 Bootstrap 源代码。在我的源代码目录下执行以下的命令就可以添加指向 Bootstrap 官方 GitHub 的 submodule:

git submodule add https://github.com/twbs/bootstrap.git

不过官方 GitHub 的 master 分支并不总能通过编译,这是我后来才发现的。修复的办法也很简单,就是强迫 submodule 指向特定 Bootstrap 版本的 commit。在 Bootstrap 源代码我们可以找到 v4.4.1 tag,然后定位到它的 commit 是 dca1ab7

接下来进入 bootstrap 子目录,里面就如同另外一个 Git 仓库一样,我可以用 git reset v4.4.1 --hard 把它的 HEAD 从 master 上最新的 commit 指向到 v4.4.1 的 dca1ab7。因为这个子目录是个 submodule,改变它的当前 commit 会修改我项目源代码的状态,这个改变在本地 commit 和 push 后就能在别处 pull 出来。这保证了 Netlify 在编译我的项目时使用的是同样的 Bootstrap v.4.4.1。

导入 Bootstrap 模块

Harp 内置了对 SCSS 的支持,所以我们不需要自己安装任何工具来编译 SCSS。现在 Bootstrap 已经以子目录的形式存在了,如何在我的项目中引用呢?我删除了原来直接引用的 bootstrap.min.css,然后添加了一个 bootstrap-custom.scss,并在页面里引用 bootstrap-custom.css。Harp 会发现这个 CSS 文件不存在但存在同名的 SCSS 文件,然后就编译同名 SCSS 文件并把输出结果保存到同名 CSS 文件。

bootstrap-custom.scss 件里,我直接复制粘贴了 Bootstrap 自己的 bootstrap.scss。这个文件本身不包含任何 CSS 规则,它只通过一大堆 @import 指令引用 Bootstrap 的所有模块。在我更新所有 @import 指令的路径后,引用 bootstrap-custom.css 的效果就跟引用完整的 Bootstrap 一样,包含了所有的模块。只不过这次我们不是用 Bootstrap 编译好的文件,而是让 Harp 从源代码开始进行编译。

精简 Bootstrap 模块

接下来要精简 Bootstrap 模块就很简单了,我可以一个一个地剔除我不需要 @import 的模块,只留下我需要的。精简后的 bootstrap-custom.scss 只用到不到原来一半的模块,体积自然下来了。

很可惜的是,这些模块不能进一步地分拆和精简了。例如说 _variables.scss 这个文件,其实它为大量我用不到的模块提供了常量定义,但就算我用不到这些常量我也无法把它们剔除。只要我用到的模块依赖于其中某些常量,我就必须把所有的常量带上。因此,尽管精简后 CSS 体积减半,但仍然有大量不需要用到的 CSS 被打包进去了。

配置 Autoprefixer

尽管 Harp 能够编译 Bootstrap 的 SCSS 文件,但还有一件事情是 Bootstrap 官方编译做了但 Harp 不会做的,那就是 Autoprefixer,这个工具会为 CSS 规则加上浏览器厂商前缀,保证 CSS 的兼容性。例如说,如果我们手写了 user-select: none,Autoprefixer 会自动添加 -webkit-user-select: none-moz-user-select: none 等等。

Autoprefixer 自己不能直接在命令行里使用,必须通过 PostCSS 调用。也就是说,调用 PostCSS 处理 CSS 文件,然后指定使用 Autoprefixer 插件。为此我们需要安装 PostCSS 和 Autoprefixer,这强迫我把我的项目变为一个 NPM 模块,这样我才有 package.json 文件,然后才能安装我所需的 NPM 模块。

需要注意的是,Autoprefixer 必须在 Harp 编译之后调用。Harp 编译先把 SCSS 编译为 CSS,然后再把输出的 CSS 交给 Autoprefixer 处理一遍。Harp 把项目目录里面的 public/ 编译后输出到 www/,然后我让 Autoprefixer 编译 www/css/ 并原地覆盖就好了。最后我写出来的编译命令如下:

harp compile .
npx postcss www/css/*.css --use autoprefixer -d www/css/

配置 Netlify 执行上述命令进行编译,我们的工作也就完成了。如果你对这类技术文章感兴趣,欢迎通过邮件RSS/Atom 订阅我的博客。

2019年12月19日星期四

Career Coaching 价值的思考

我是从 2018 年开始做 career coaching 服务的,从深度打折的每小时 $100 开始做起到现在每小时 $300 的行业价位。我在做这件事情的过程中学习到了不少东西,也深入思考过一些问题,可以拿来分享一下。


首先,大多数人最想知道的估计是我的 career coaching 适合哪类人群。从我现在的学生群体来看,主要有两个大的类别,但也有少数学生是在这两大类别之外的。

第一大类别是大公司里面的 junior 到 senior 的群体,也就是 Facebook 或 Google 的 IC3 到 IC5 级别。他们已经有很好的工作和很好的收入,技术往往不是他们晋升的瓶颈,他们也有很强大的意志力来完成高强度的工作。他们需要的更多是软技能方面的培训:

  • Communication:如何保证对方确实理解了自己的意思。如果保证对方对自己承诺的事情有一个正确的期望值,不会因为期望过高而最终失望。中国人比较常见的一种问题是在无法理解对方意思时一路 yes 到底,就希望尽快结束这个对话,结果连自己答应了对方什么承诺都不知道。
  • Alignment:如果一件事情需要多方合作才能完成,如何保证大家能达成共识。如果大家已经存在明显分歧,各自想要的事情各不不同,甚至是有你就就没我的零和博弈,那该如何解决。
  • Planning & Execution:如何做规划和如何执行。我经常说:IC3 和 IC4 做的项目是可以用贪心算法的,但从 IC5 开始就要以动态规划的方式去做,有时候往前走是为了获得新的信息,获得新的信息后有可能最佳的选择是倒回来再往另外一个方向走。
  • Product & Business:技术是服务于产品和商业的,解决错误的问题就算问题很难也无法产生价值,所以大家必须理解背后的产品和商业价值是什么。
  • Mentorship:如何掌握辅导别人的度。一方面,你不希望帮得太多,导致你帮别人把事情都做了。另一方面,你不希望帮得太少,别人自己折腾很久都没有多少进展,拖累团队进度。

这方面软技能的培训跟我之前在 Facebook 时为其它人提供的培训很类似。Facebook 内部有一个面向工程师的 coaching program,叫做 Good to Great。在这个 program 里,学生提出的问题 80% 是软技能相关的,只有 20% 是技术相关的。这个状况很容易理解,因为 Facebook 招来的人已经非常优秀,技术上的事情绝大部分都可以自学,更需要的是在自己原来的盲点进行一些点拨。

第二大类是一流高校里在读的学生,例如 Stanford 和 University of California 系列的学生,他们希望优化他们找工作的战略。

假设目标就是大公司软件工程师职位,学生找工作为什么不直接刷题解决?因为刷题只触及了这个问题最浅层的一面,如果我们再深入一些我们可以把这个问题分解为多个阶段:

  1. 熟练地编写代码:只要想得到的逻辑,都能转化为正确和高质量的代码。
  2. 熟练地运用常见算法:见到难度相当于 LeetCode Medium 的问题,都能够想出来正确的算法,并且条例清晰地描述该算法。(然后再利用上一个阶段的能力编写代码。)
  3. 参与团队合作:能够跟一两个人合作完成一个编程项目,互相协调分工,交叉 code review,解决一些意见上的分歧。这是很多学校都不教也不提供实践机会的技能,就算课程项目要求多人合作学校也不强调合作过程的质量只考察最终结果。如果拥有同等的算法和编码能力,简历上能展现出团队合作能力甚至是团队领导能力的人在求职上更有优势。
  4. 展现自己:在简历上和面试过程中展现自己的上述能力,表现出自己最优秀的一面。有些人并不是能力不行,但因为展现出来时打了折扣,别人就会低估他的能力。拥有实力是根本,没有实力就没有东西可以展现的,但展现自己本身也是一项重要的能力。
  5. 向上管理:在实习阶段向上管理自己的经理(包括 intern manager 和 team manager),确保他们理解实习项目的进度和最终结果的价值。如果有幸拿到实习,并且所在公司通过实习表现决定全职 offer 的话,实习期间展现自己的能力就很重要,但这跟简历和面试时的表现又不完全一样,因为这是跨度几个月的汇报关系。
  6. 职业战略规划:明白自己毕业两三年后想要成为怎么样的人,让毕业后的第一份工作服务于这个目标。有很多人没有想过这个问题,有些人想了也暂时没有答案,但有些人其实是有明确的答案的。对于有明确答案的人来说,就应该做战略规划,避免走弯路。就算是没有答案的人,也应该避免用贪心算法往前走掉坑里,至少要大致知道坑在哪里。

刷题本身非常重要,但我们应该意识到刷题只覆盖了头两个阶段应该掌握的技能,后面的几个阶段需要用到完全不一样的能力,仅仅靠刷题是不够的。我做 career coaching 往往会从学生最终想要的长远目标出发,也就是毕业几年后想要成为怎么样的人,然后再反向规划路线,最终决定接下来立即需要优化的能力是什么。


上面说了我做 career coaching 的主要两大类学生,接下来我可以说说我对 career coaching 价值的思考。这里包括我之前做 manager 时深入思考过的一些问题,也包括现在跟 coaching 和 training 同行交流时获得的心得。

根据我过去做 manager 的经验,我觉得 coaching 的第一步是教练和学生一起找到学生的职业愿景(career vision),也就是学生几年之后那个「更好的自己」是长什么样子的。学生可以说说这个世界上什么事情是自己最在乎的、什么事情是自己擅长做的、什么事情是自己喜欢反复做的、手上有多少资源可以动用来追求自己的目标。教练可以根据自己过去对其它人的观察提供参考信息,告诉学生什么样的目标是前人已经抵达过的并有已知的可靠路线,什么样的目标从来没有人做成过所以风险较高,如果学生给自己订的目标太低教练还可以提出一个更高但切实可行的目标。

双方把各自的信息摆到一起来,然后找到一个学生能下决心执行的长远目标,这其实是 coaching 的核心价值。这跟传统的传道受业解惑不完全一样,不是简单地把知识和技能从一个人身上转移到另外一个人身上,而是帮助一个人变成他想要变成的更好的样子。在这个过程中,知识和技能的转移只是其中的一部分,但不是全部。教练和学生都必须真心相信这个愿景可以达成而且值得达成,然后共同发力来帮助学生达成这个愿景。如果中途遇到了障碍,两个人应该共同想办法解决,学生不能被动地等待教练传授知识解决难题,教练也不能假设学生获得知识后就能自行解决所有难题。

既然说到了解决难题,我就想说一下教练对学生在情绪上提供支持(emotional support)的价值。其实做 coach(或者是 manager)一段时间后你就会发现,有时候学生做不成一件事情并不是能力问题,而是心理上有一道坎没办法迈过去。心理上这道坎,往往是对未知的恐惧,害怕自己失败,而不敢踏出自己的舒适感区域(comfort zone)。这时候教练的角色非常重要,既需要有同理心(empathy)理解学生为什么没办法迈出这一步并且关怀学生的情绪,又要在恰当的时机下得了狠心强迫学生走出这一步。学生走出这一步的过程可能会相当地痛苦,但抵达彼岸时他会想明白为什么这是必须的,同时也会感谢教练。

如果人类好像机器一样没有情绪永不倦怠,其实学习能够变得高效很多。机器学习的算法你想跑就能跑,机器永远不会说「我今天学累了,明天继续吧」。但正因为我们不是机器,一个好的教练要能帮助学生避免倦怠的情绪。通过对同行的观察,我发现保持教学内容简短和有趣非常重要,这也是现在短视频(10 分钟左右)教学的趋势。我之前在《牛油果烤面包》播客节目上采访过飞行员教练 Simon,他告诉我 FAA(美国联邦航空管理局)沉甸甸的手册上有丰富的知识,但大家很难看得进去,于是纷纷上网购买其它飞行员教练录制的视频课程,因为视频里的各种冷笑话让学生保持兴趣。在获取知识的成本无限趋近于零的今天,所谓「知识付费」并不是付费给知识本身,真正的附加价值存在于对知识的加工,使得知识变得容易消化。

总结一下,career coaching 的价值往往不在于知识本身,而在于帮助学生变成更好的自己。在这个过程里,知识很重要,但往往并不是价钱的主要部分,因为现在网上无数成本接近于零的知识。真正值钱的是如何保证学生能够消化和吸收正确的知识,最终达成自己的目标。


最后,我对于 career coaching 在经济学和社会学的大环境下还有一些思考吧。接下来我会再写一篇《Career Coaching 消费的思考》来跟大家聊聊。如果你不想错过下一篇文章,欢迎通过邮件RSS/Atom 订阅我的博客。

2019年11月15日星期五

Early Reader 2020 调整

我在 Patreon 上开放 Early Reader 这一档有一年多了,感谢大家长期的支持,让我来聊聊这一年的通过经营 Patreon 学到的东西吧。

Early Reader 这一档每个月收 $1 的费用,对应的好处是提前一周阅读我最新发布的文章。(事实上我推迟了文章在其它平台发布的时间,同时在 Patreon 上设置了解锁时间。)在这一年内我获得了 9 位朋友的支持,现在仍然活跃的有 5 位。

我觉得 Early Reader 这一档并没有达到我原来的预期。我原本设想这一档能够通过足够低的价格获得尽可能多的读者支持,但实际上没有足够多的人愿意为「提前一周」这个福利而付费。我在跟 Joma 聊天时他提醒了我一件事情,「你不应该根据『你需要付出多少』来定价,而要根据『别人能获得多少』来定价,提前一周阅读对读者来说其实不值钱」。我觉得这个视角是对的,所以我准备对 Early Reader 这一档作出调整。

我从读者的角度来思考什么有价值,我觉得主要是两方面吧:

  1. 获得我不对外公开的独家内容。
  2. 能够真正的激励我创作更多的内容。

为此我新开了两档,分别叫做 Coffee Supporter ($5) 和 Sushi Supporter ($30)。这两档的价格比原来的更高,但请我吃饭(尤其是吃寿司)这件事情确确实实能够激励我干活。我有一个猜想,很透明地分享一下吧:如果我的读者觉得他们给我的是看得着摸得到的实物(食物),那会比原来虚无地付 $1 但不知道这 $1 有什么具体含义更乐意付费。(你们可以在评论区告诉我这个猜想对不对。)

此外我会开始做不对外公开的独家内容,专门给付费读者看。一开始主要还是文章,之后我会探索音频和视频。这些内容如果是长篇的话,我可能会免费发布其中一部分在公开的博客上,但不会发布全部。

最后说一下现在 Patreon 上的 Early Reader 这一档会如何迁移。我现在已经对新读者关闭了这一档,并且计划在 2020 年 1 月完全终止这一档,那意味着如果你到时候仍然使用这一档的话 Patreon 就会把你的付费订阅取消掉。如果你愿意升级到 Coffee Supporter 或 Sushi Supporter 的话,你可以在那之前进行升级。升级后会保留原有的福利,同时获得上述新增福利(不对外公开的独家内容)。

如果你是在我 Patreon 以外的博客上看到这篇文章,欢迎来 Patreon 付费支持我。如果你觉得美元付费对你来说并不方便,请在评论区留言告诉我,我正在努力开通支持人民币支付的对应版本。

Career Coaching 2020 新价格

我的 Career Coaching 将在 2020 年初开放更多名额,同时进行年度价格调整。新学生的价格为每小时 $300。2019 年内或更早加入的学生将能继续获得优惠,优惠价将在 2020 年 1 月统一调整到每小时 $200。

对于已经在 Patreon 上进行付费的现有学生,我会在 12 月开放新的一档 $200 的服务,请你们把自己的帐号从现在的 $100 或 $150 一档调整到 $200 一档,然后我会在 1 月删除 $100 和 $150 这两档。(我到时候会发消息逐一通知你们。)

对于等待新名额的学生,我将会在 12 月放出 $300 这一档。如果你 12 月就在 Patreon 上加入的话,系统会在 1 月头扣钱,我会在 12 月跟你约好 1 月份开始的 bi-weekly 30 minutes 1:1。

2019年11月12日星期二

《牛油果烤面包》满月回顾(Part 3 - 统计数据)

Apple Podcasts Connect

上一篇文章里我讲述了《牛油果烤面包》的录音和剪辑过程,接下来我们看看听众订阅和收听的数据如何。


我在第一篇文章里面提到我们用 Anchor 把播客自动发布到多个平台,同时我们手动发布到喜马拉雅和企鹅 FM。如果我们要看数据,我们就必须一个一个平台地看。

  • Anchor:Anchor 提供「播放数」和估算的「听众数」,同时提供听众的国家、播放平台和设备分布。Anchor 会统计每一集每一天的播放次数。(参考截图
  • Apple Podcasts:Apple Podcasts Connect 不提供「订阅数」只提供「设备数」,所以无法知道有多少人订阅了。Apple 强调的是总收听时间,我们在过去的 60 天内有 96 个小时的收听时间,这样就可以推算出来每台设备 85 分钟的收听时间。此外这 96 个小时里 84% 来自于订阅听众,但这还是无法推算出订阅数。具体到每一集,Apple 同样提供设备数和总收听时间,同时提供节目内的留存率,也就是用户听到第几分钟第几秒钟就流失了。(参考截图:1 2
  • Spotify:Spotify 提供「播放次数」、「播放超过 60 秒次数」、「听众数」和「订阅数」。后面两个都是根据 Spotify 注册用户进行计算的,所以不存在多次在不同设备播放导致的重复计算。我们现在有 153 个订阅用户,但每期节目只有不到 20 次播放,意味着 Spotify 的用户经常订阅但不收听。Spotify 为每一集提供留存率,以及听众的性别和年龄分布。(参考截图)
  • Google Podcasts:Google Podcasts 本身并不提供任何的数据,只要 RSS 被 Google 抓取了就会显示在 Google Podcasts 里。有意思的是,Google Play Music Podcast Portal 可以认领 podcasts,但我认领后还没能看到任何数据,不知道这个产品现在到底是否还活着,还是因为不会有人在 Google Play Music 里面听 podcasts。暂时来说,我们无法从 Google 获得任何数据。
  • Castbox:Castbox Creator Studio 提供节目的订阅数、播放数和每一集的播放次数。(此外,因为 Castbox 提供了评论系统,所以作为管理员可以管理用户评论。)
  • Breaker:Breaker 自动收录第三方的 RSS,对于不是在 Breaker 上创建的 podcasts 无法认领也无法获取数据。
  • Overcast:Overcast 自动收录 Apple Podcasts 已经收录的 RSS,并且不提供认领。有意思的是,Overcast 在抓取 RSS 时通过 user agent 来上报订阅数,但由于我们使用 Anchor 提供 RSS 所以无法截获 user agent 获取订阅数。
  • Pocket Casts:Pocket Casts 接受 podcasts 提交,但不提供认领和数据。
  • RadioPublic:RadioPublic 自身不提供任何的数据分析,但可以使用 Google Analytics 收集数据。不过只有付费用户才能使用这项功能,所以我并不知道 RadioPublic 会向 Google Analytics 汇报什么数据。
  • Stitcher:Stitcher Partners 提供播放数、播放时间和听完率。
  • 喜马拉雅:喜马拉雅提供「订阅数」和「播放数」,以及每周新增听众的留存率。喜马拉雅不提供节目内的「留存率」但提供「跳出率」,虽然本质上是一样的数据但呈现出来的曲线就完全不一样的。(「跳出率」是「留存率」的倒数。)此外喜马拉雅提供听众的省份、城市和性别分布。

总结一下:这些平台提供的数据各不一样,有些平台基本上不提供什么数据。这就导致我们无法轻易地整合数据,分析听众喜好,然后优化将来的节目。现阶段我们能做的是,尽量把用户带往几个提供数据分析的平台,然后依靠这几个大平台的数据做决策。

长远来说,我觉得我们可以做自己的平台入口,让用户可以直接在我们自己的网站上播放甚至是订阅,这样我们就可以自己收集一部分的统计数据了。当然,如果用户喜欢用其它平台的 app 进行订阅和播放我们依然无法收集到那部分的数据,但至少我们能收集多少是多少吧,这对我们优化选题和节目内容很重要。


说完数据之后,我们接下来可以说说选题。如果你想知道我们是如何进行选题的,欢迎通过邮件RSS/Atom 订阅我的博客,保证你不会错过下一次关于选题的文章。

2019年11月9日星期六

追踪短地址的小工具:Trace URL

我写了一个追踪短地址的小工具,输入任何短地址(或普通带重定向的地址),它就能找到原地址显示给你看。我把它叫做 Trace URL,欢迎大家来使用。这是一个 PWA (Progressive Web App),所以 Android 用户可以把它添加到首屏,添加后就可以把短地址「分享」给 Trace URL 进行追踪。

这个项目是完全开源的,不过源代码分布在多个 GitHub 项目当中。traceurl-web 包含了 PWA 客户端代码。这个项目使用 Create React App 创建,然后使用 Material UI 做样式模板,最后手工编辑了一堆 metadata 使它成为一个好用的 PWA。在这里我分享一个比较小众的 PWA 知识吧,也就是如何让 PWA 接受来自 Android 分享的数据,这需要在 manifest.json 里面添加一个叫做 share_target 的信息,例如说:

{
  "share_target": {
    "action": "/",
    "method": "GET",
    "enctype": "application/x-www-form-urlencoded",
    "params": {
      "title": "title",
      "text": "text",
      "url": "url"
    }
  }
}

添加 share_target 信息后,只要 PWA 被添加到首屏,它就能成为 Android 系统的分享目标。需要注意的是,Chrome for Android 有个已知的 bug,分享地址时会把地址传输给 text 参数而非 url 参数。所以在处理传入参数时我们需要同时看 texturl,哪个有值就处理哪个。

这个 PWA 背后实际调用的 API 在 traceurl-api 项目里。这个 API 其实只是对 traceurl 库做了个简单的封装,这个库才是真正负责跟踪短地址的。这样层层封装只是为了好玩,让我能够尝试一些新技术,学习新知识。例如说,traceurl 其实是一个 package,我原本把它发布到 NPM Registry 了,后来 GitHub 发布测试版的 GitHub Package Registry 时我就试用了一下,把 traceurl 发布到那里去然后在 traceurl-api 里面引用。这就让我理解到了到底为什么 GitHub 要做自己的 Registry。(到底是为什么?我另外写一篇文章解释。)

这次就先说这么多吧,大家记得来试用一下 Trace URL 哦,好用的话记得添加到 Android 首屏哦!下次我再写文章讲述 GitHub Package Registry,感兴趣的话请记得通过邮件RSS/Atom 订阅本博客。