tag:blogger.com,1999:blog-70050362024-03-08T13:19:30.027-08:00Cat in ChineseCat Chenhttp://www.blogger.com/profile/17757778716179768922noreply@blogger.comBlogger547125tag:blogger.com,1999:blog-7005036.post-7790139940234275512024-03-01T11:17:00.000-08:002024-03-01T11:17:00.126-08:00如何扩大工作的 scope?<p>在美国工作的程序员都会遇到一个问题:想要晋升但经理说自己工作的 scope 不够大,又或者是影响力不够大。那要如何才能扩大自己工作的 scope 呢?</p>
<p>职业前期直系经理会负责为你找 scope,一步一步地给你更大的 scope,到了后面经理就会说「自己找 scope,这是晋升到下一个级别的要求,我给你 scope 就满足不了这个要求了。」既然不能依赖别人给你 scope,那要去哪里找扩大 scope 的机会呢?</p>
<p>这是很多人感觉到困扰的问题。假设自己在做的 scope 大小算作 1,把上下左右的外延做了也就是 1.1、1.2、1.3……的增长,这扩张速度太慢了。找一个跟自己已有 scope 相似但需要花同样功夫的 scope,那可以从 1 变成 2,但工作量跟着翻倍,拼命卷可以升一级,但 scope 大小从 2 到 3 是不可能的。况且职级线性上升时需要的 scope 是指数上升的,卷到三倍的工作量也没用,公司期望 scope 按照 1、2、4、8……的速度来增长。</p>
<hr />
<p>在这篇文章里面,我们的关注点是如何找到更大的 scope,不是有了更大的 scope 后怎么做出来。怎么做出来当然是个难题,但很多人被卡在了找不到 scope,所以必须先解决这个问题。找不到 scope,不意味着找到了就有能力做出来,但找不到的时候人就会觉得自己有能力无处施展,这会导致很强的挫败感。</p>
<p>如何能找到更大的 scope?关键是要走出去跟更多人接触。技术再厉害的人,把自己关在小黑屋里面对着代码库发呆,是几乎不可能找到更大的 scope 的。你只能看到你已经知道的问题和机会在哪里,最多再看到一点外延,但指数级扩大的问题和机会很难闭关冥想出来。</p>
<p>我们可以在白纸上画三个从小到大的同心圆,分别代表:我能控制的事情、我能影响的事情、我能感知的事情。如果只考虑工作上的事情,这三个集合应该是两两子集的关系,「我能控制的事情」是「我能影响的事情」的子集,「我能影响的事情」是「我能感知的事情」的子集。</p>
<div class="separator" style="clear: both;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjCpzgX2dKdDqEe64cLTlBeh_ntd9r3gP80mxp2CSA2rzdx4kTx67zOSBvybys1lKrJbBGe7HmzChHXSLj-_sUE-ANC8YV-PuhaIrwAd-GWftGUmLmFuMeQaCnoOiA_FAWOCibcJvU8yQ4bfOQA-SF7Qn4vqz4vi0yWLf8DWy1CAeNGMoxCF0fs5w/s1000/Paper.Drawings.jpeg" style="display: block; padding: 1em 0; text-align: center; "><img alt="" border="0" width="320" data-original-height="1000" data-original-width="1000" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjCpzgX2dKdDqEe64cLTlBeh_ntd9r3gP80mxp2CSA2rzdx4kTx67zOSBvybys1lKrJbBGe7HmzChHXSLj-_sUE-ANC8YV-PuhaIrwAd-GWftGUmLmFuMeQaCnoOiA_FAWOCibcJvU8yQ4bfOQA-SF7Qn4vqz4vi0yWLf8DWy1CAeNGMoxCF0fs5w/s320/Paper.Drawings.jpeg"/></a></div>
<p>找不到更大的 scope,问题往往来自于「我能控制的事情」扩张到逼近「我能感知的事情」的边界了,然后就找不到空间继续扩张了。想要对着「我能控制的事情」大力出奇迹是很难有效果的,把工作量翻倍后 scope 依然上不去。真正能扩大 scope 的,是先把「我能感知的事情」扩张出去,有空间了再把「我能影响的事情」扩张出去,最后才能把「我能控制的事情」扩张出去。</p>
<p>如何能够把「我能感知的事情」扩张出去?我们必须走出去,接触更宽广的世界,接触更多各式各样的人。在一家大厂内部,这往往就是走出去跟别人聊天。我们对自己每天在做的项目、在解决的问题有太深的了解了,我们需要了解别人尝试解决什么问题、有什么问题解决不了,更大的 scope 就埋藏在那里。</p>
<hr />
<p>如何走出去跟别人聊天?可以先从已经跟你有交集的人开始。肯定存在一些人,项目上跟你有合作,但有限的合作导致你们的交互很少,你不知道在合作之外他的主要工作是什么。你可以约他们喝咖啡,不要聊你已经知道的事情,问一下他在合作以外的主要工作是什么,以及他的工作有什么有意思的地方、有什么难题或者是不爽的地方。</p>
<p>关键是你要会聆听,他说的事情你要么理解了,要么通过更精准的问题追问。想象一下你跟他聊天后需要回来汇报跟他相关的所有信息,你自然会仔细听,甚至会做笔记,不会瞎扯一番大家爽了但谁也不记得聊了什么。这个对话应该像个自然的社交聊天,你不能如同审问对方一样获取信息。对方说的事情,你有共鸣的可以回应,你有不同观点的可以分享,这才是一个对话。</p>
<p>职级比较低的人,对于找职级比较高的人聊天可能存在心理上的障碍,觉得不应该浪费对方宝贵时间,错误假设自己懂的对方都懂了,所以对方不会从对话中获得任何价值。其实职级比你高的人也是人,你跟他们聊天可以提供情绪价值,有时候还能帮他们理清思路。如果长远来看你们会有更多合作,他们肯定乐意花时间跟你建立良好的信任关系,这包括花时间互相了解对方,尤其是对方的思维方式、思考问题的出发点。</p>
<p>刚刚开始跟几个人聊天时,你会获得大量碎片化的信息。聊的人多了之后,慢慢这些信息就会连接起来,呈现出公司内部更大的图谱。你需要在这个更大的图谱当中寻找当前运作得不够好的地方,例如说可靠性低或者是效率低的地方,然后想想你是否有想法和有能力去解决,这些都是你潜在的 scope。可能你没有信心直接玩起袖子开干,但你至少有素材跟你经理进行对话,说说你看到的机遇在哪里,让他就什么机遇更适合你提出他的看法。</p>Cat Chenhttp://www.blogger.com/profile/17757778716179768922noreply@blogger.com0tag:blogger.com,1999:blog-7005036.post-67540085832177950902024-02-18T21:26:00.000-08:002024-02-18T21:26:23.488-08:00Vision Pro 使用体验(Part 2)<p>Vision Pro 的「截屏」功能非常符合 iOS 和 watchOS 用户的习惯,把仅有的两个物理按键同时按下去就可以了。跟用 Vision Pro 拍摄 3D 视频没有取景框一样,截屏时你没办法知道截屏边界在哪里。Vision Pro 给你一个相当宽的可视角度,但实际截屏时它会截取一个 16:9 的区域然后保存为 1920x1080 的图片。</p>
<p>考虑到 Vision Pro 两块屏幕加起来像素超过 4K 屏幕,截屏 1920x1080 的分辨率实在是有点低。你可以截屏分享给别人,让别人看看你佩戴 Vision Pro 的体验是怎样的,但千万不要指望别人能够看清楚你第一人称视觉能看清楚的细节,更别期望别人能看清截屏上的小字。经过 3D 到 2D 的投射之后,截屏上偏小的文字是很难看清楚的,需要很用力地看才能看明白。</p>
<p>此外,我不知道 Vision Pro 截屏时使用的是左眼还是右眼的视觉,这值得研究一下。</p>
<hr />
<p>把 Vision Pro 变成 Mac 外置屏幕的功能还不错。就算是 16“ MacBook Pro 的屏幕也只有 16”,但用 Vision Pro 打开瞬间可以变成 75" 的大屏幕,而且依旧看不到像素。这个屏幕可以用来玩游戏,游戏的计算应该是在 Mac 上进行的,Vision Pro 只是投屏而已。对于游戏来说,有一个巨大的投屏可比 MacBook Pro 的屏幕爽多了!而且这个投屏还不受物理空间限制,即使你的房间没那么大,放下 MacBook Pro 后就没多少空间了,你依然可以在 Vision Pro 里面放置一个突破房间墙壁限制的大屏幕。</p>
<div class="separator" style="clear: both;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiXIImLnB02xvH4wFswwDDmEIRb33yFooZb0PEKWtetNqF8gESGxjvBKrXnjSd1ULvvefi9yrV9Xm_bj9893e134WEX0mIdbRAkS1x6UBlHi2aF38uOKO3yFuHL27LTpTaWnrddY9Wz3TcBNtnkre5VKKumEFLW04iLmLljl8F9dwNCK_qAIp0bqA/s692/Screen%20Mirroring%20from%20Mac.png" style="display: block; padding: 1em 0; text-align: center; "><img alt="" border="0" width="320" data-original-height="594" data-original-width="692" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiXIImLnB02xvH4wFswwDDmEIRb33yFooZb0PEKWtetNqF8gESGxjvBKrXnjSd1ULvvefi9yrV9Xm_bj9893e134WEX0mIdbRAkS1x6UBlHi2aF38uOKO3yFuHL27LTpTaWnrddY9Wz3TcBNtnkre5VKKumEFLW04iLmLljl8F9dwNCK_qAIp0bqA/s320/Screen%20Mirroring%20from%20Mac.png"/></a></div>
<p>比较遗憾的是缺乏一台 Mac 多屏幕投屏的支持。Vision Pro 只能显示 Mac 的主屏幕,不能选择增加屏幕。如果在投屏到 Vision Pro 之前 Mac 就已经连接了外置屏幕,投屏后所有外置屏幕都会熄灭。Mac 自身的主屏幕也会熄灭,既然投屏了就没必要在 Vision Pro 外面显示一模一样的内容了。这对于注重隐私的人来说会很有用,例如说在咖啡店或在飞机上用 Mac 加 Vision Pro 投屏,别人就看不到你的屏幕了。</p>
<div class="separator" style="clear: both;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiXfPjvJ7cy-2GGFsBf5cKnyGTRG1CKH08o4vp1xmaX4SCbJwHJkQrlJF2_2hZXXXH4iGbqhRE8N8RTlP1kHoNpcdvgmTbw4FSNK-GGO-LyjGaU9HLQQ3Yf4JZjGPdxt_uTjWcs6zkbHLW_YdSs3vWr1_WYejUJzLPd_tyVvIVJlCcNVh_BWlFefQ/s1920/Screen%20Mirroring%20to%20Vision%20Pro.PNG" style="display: block; padding: 1em 0; text-align: center; "><img alt="" border="0" width="320" data-original-height="1080" data-original-width="1920" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiXfPjvJ7cy-2GGFsBf5cKnyGTRG1CKH08o4vp1xmaX4SCbJwHJkQrlJF2_2hZXXXH4iGbqhRE8N8RTlP1kHoNpcdvgmTbw4FSNK-GGO-LyjGaU9HLQQ3Yf4JZjGPdxt_uTjWcs6zkbHLW_YdSs3vWr1_WYejUJzLPd_tyVvIVJlCcNVh_BWlFefQ/s320/Screen%20Mirroring%20to%20Vision%20Pro.PNG"/></a></div>
<p>想把 Mac 投屏到 Vision Pro 时,可以在 Vision Pro 里面抬头调出头顶上的 Control Center 然后选择连接附近的 Mac。更加神奇的连接方式是,在 Vision Pro 里面以穿透方式看着一台 Mac 的屏幕,Vision Pro 自动能够识别出这是哪台 Mac 然后在 Mac 的屏幕上方放置一个投屏按钮,点击按钮就会开始投屏。</p>
<hr />
<p>反过来,你在 Vision Pro 里面看到的画面也可以投屏到 Mac 或 iPad 上。这很适合用来做演示,把自己在 Vision Pro 里面看到的内容分享给身边的人看。操作起来跟 iPhone 投屏到 Mac 上一模一样,在 Vision Pro 的 Control Center 选择 Screen Mirroring 就可以了。</p>
<div class="separator" style="clear: both;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgkiF9-7dxgLfMYCKQWtbbRyuEXrycN0luNf-2IBJvwWbTyzWW_GLAgH67wFRwrFvyDfMfjYezx-qAJWKR5WoveZYn3YVr64fxGoAWIt-EghtRH76bHsgiCnvdPqUjdqtbWc6ikkGeJ6hVJkrIHWK9TBlhfnJOgcjF66Ftg_-Ja6kGTNRNxNXWHAg/s1920/Screen%20Mirroring%20from%20Vision%20Pro.PNG" style="display: block; padding: 1em 0; text-align: center; "><img alt="" border="0" width="320" data-original-height="1080" data-original-width="1920" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgkiF9-7dxgLfMYCKQWtbbRyuEXrycN0luNf-2IBJvwWbTyzWW_GLAgH67wFRwrFvyDfMfjYezx-qAJWKR5WoveZYn3YVr64fxGoAWIt-EghtRH76bHsgiCnvdPqUjdqtbWc6ikkGeJ6hVJkrIHWK9TBlhfnJOgcjF66Ftg_-Ja6kGTNRNxNXWHAg/s320/Screen%20Mirroring%20from%20Vision%20Pro.PNG"/></a></div>
<p>访客模式是使用投屏的另外一个常见原因。visionOS 不像 macOS 那样存在多用户登录,更像是 iOS 那样只允许单一用户,只能够绑定单一 iCloud 帐号。如果想要把 Vision Pro 借给别人使用,就要开启访客模式,之后可以限制访客能够打开的应用(他会看到能打开的应用中你的所有数据)。为了更好的指导访客使用你的 Vision Pro,或者是为了更好地监控他在你的 Vision Pro 里面干什么,你可以在开启访客模式的同时投屏,那你就能看到它看到的画面了。</p>
<hr />
<p>前面说到 Mac 投屏突破房间墙壁限制,我可以解释一下 visionOS 的 2D 窗口是如何跟现实世界的 3D 物品重叠的:应用的 2D 窗口永远会优先于现实世界的物品。举个例子,我可以在我和应用窗口之间放一个小盒子,理论上这个小盒子应该会遮挡窗口的一部分,而且 visionOS 确实能扫描到这个小盒子的存在。然而 visionOS 并不会让这个小盒子穿透显示到我的 Vision Pro 屏幕上,应用窗口即使在小盒子背后也会被优先显示出来,导致小盒子被隐藏起来。唯一例外的是我的双手,把手举起来放在 Vision Pro 和窗口之间,手是会被显示出来的,我可以看清楚手和窗口的互动操作。</p>
<p>Vision Pro 使用拇指和食指触碰一下表示单击,这是大家都在官方视频中看到的,但其实还有另外一种点击方式。只要把窗口拉到自己面前,手指可以直接点击窗口上的按钮,手指穿越 3D 空间中的 2D 窗口会被视为点击,手指穿越窗口后上下左右移动会被视为拖拽。这种设计使得 visionOS 里面显示的所有 2D 窗口名义上都是 iOS 一样的「触摸屏」,习惯触摸屏的用户会发现这非常符合直觉。</p>
<hr />
<p>这次就写到这里吧,接下来想到有新内容再更新。这个系列的《Vision Pro 使用体验》,我准备想到哪里就写到哪里。不想错过接下来的内容的话,敬请关注和订阅。这篇文章首发于<a href="https://www.patreon.com/catchen">我的 Patreon</a>,大家可以到 Patreon 上付费支持我写作。</p>Cat Chenhttp://www.blogger.com/profile/17757778716179768922noreply@blogger.com0tag:blogger.com,1999:blog-7005036.post-12028378743689345132024-02-09T19:41:00.000-08:002024-02-09T19:41:55.863-08:00Vision Pro 使用体验(Part 1)<p>在 Vision Pro 之前,我有 Oculus Rift 和 Quest 2,这两个都是纯 VR 设备(Quest 2 在接近障碍物时会触发黑白穿透视频)。Vision Pro 跟它们比,最大的优势是 AR 不会晕,我连续使用几个小时都没问题。之前 Quest 2 玩 Light Saber 一天最多玩 20 分钟,然后就开始感觉到头晕了。
虽然 Vision Pro 是有点重,但习惯后并不会觉得难受,当然脱下来之后脸上还是会有一圈的痕迹。我选择的是到 Apple Store 店里提货,顺便做 fit test,保证扫脸得到的尺寸合身。我觉得 fit test 还是挺重要的,扫脸归扫脸,但最终你需要找到一个几乎不漏光且重量均匀分布在脸上的尺寸。如果重量主要落在额头、脸颊或左右两侧,都会感觉到不舒服。</p>
<p>Apple Store 的店员有一套专门的「合身调试流程」。他们会先按照扫脸的结果给你对应的尺寸,然后问你哪里有漏光、哪里重量不均匀、哪里压得你不舒服,根据你的回答来找不同的尺寸给你试。当然,这暂时只能在美国体验到。在其它地方购买别人选好的尺寸,不太合身也只能将就了。</p>
<hr />
<p>Vision Pro 的视力矫正镜片本质上跟眼镜是一样的,分为处方眼镜和老花眼镜两种。老花眼镜 $100 一对,只有固定的几个度数。处方眼镜就是要验光才能生产的眼镜,支持近视(据说能到 1100 度)、远视(据说能到 500 度)、散光等,$150 一对。之所以叫做处方眼镜,是因为在美国必须要有最近 6 个月的眼光处方才能配。镜片由蔡司生产,必须上传美国处方并由蔡司验证后才会进行生产和寄送。</p>
<div class="separator" style="clear: both;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhJw7exQ6viF1AkyxbLt5Y4PtuQ-uEgeK9oNcESCdM_7ggPgwxy_hJB9FAv2nMzaTcSyoxyY6tYZAi0hg1PTjoItyfO_IqAtEw1_QwdfYdjoXvWKd5rudP2Lzx97G3PYZuRw69M83nLZtFCG545RTWGGaXAb0HL01NkEM4Gt49G0CpdRczP_CNdag/s4032/ZEISS_optical_inserts.jpeg" style="display: block; padding: 1em 0; text-align: center; "><img alt="" border="0" width="320" data-original-height="3024" data-original-width="4032" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhJw7exQ6viF1AkyxbLt5Y4PtuQ-uEgeK9oNcESCdM_7ggPgwxy_hJB9FAv2nMzaTcSyoxyY6tYZAi0hg1PTjoItyfO_IqAtEw1_QwdfYdjoXvWKd5rudP2Lzx97G3PYZuRw69M83nLZtFCG545RTWGGaXAb0HL01NkEM4Gt49G0CpdRczP_CNdag/s320/ZEISS_optical_inserts.jpeg"/></a></div>
<p>镜片的使用体验很好,轻轻放在 Vision Pro 屏幕上面就会通过磁吸固定。跟我之前佩戴眼镜使用 Rift 和 Quest 2 对比,不需要配搭眼镜的体验好多了!不需要顾及眼镜如何塞进设备有限的空间里面,不需要小心穿戴设备,戴上不需要调整眼镜立即能用。购买时 Apple 会强烈建议你在镜片上刻字(只能使用大写字母进行拼写),保证你的镜片就算不慎跟别人的镜片放在一起了也能迅速区分开来。(左右两片镜片有「L」和「R」的标记进行区分。)</p>
<p>两个人共用一台 Vision Pro 的话,切换物理镜片很方便,唯一问题是镜片取下来后没有专门用来存放的盒子,只能放回原包装盒里。visionOS 能够知道镜片更换了,需要重新校准目光,否则 Vision Pro 没办法精确地根据你看着哪里来点亮哪里。这个过程就比较麻烦了,要连续三次地点亮六边形六个顶点。就算两个人都有 Vision Pro 的锁屏密码,不需要开启 Guest Mode,切换镜片依然麻烦。</p>
<hr />
<p>Vision Pro 的操作系统叫做 visionOS。对于习惯了 iOS 和 tvOS 的用户来说,visionOS 会显得完成度不够高。这不会影响使用,但会在很多细节上体现出来:</p>
<ol>
<li>iMessage 尚不支持 Contact Key Verification。这在最新版 macOS 和 iOS 都已经支持的功能,在最新的 visionOS 1.0.2 竟然还不支持。(尚处于 beta 的 visionOS 1.1 我没安装。)如果你的 iCloud 帐号已经启用了这一功能,在 visionOS 激活 iMessage 时就会出错。你必须在另一个设备上关闭这一功能后,才能在 visionOS 上再次登录 iMessage。</li>
<li>在 iOS 上 FaceTime 和 iMessage 同时出现在系统设置里面,如果遇到上述 iMessage 激活问题你会想要在 visionOS 系统设置里面找 iMessage 重新激活,然后你会发现 visionOS 系统设置只有 FaceTime 没有 iMessage。visionOS 会在桌面 iMessage 图表旁边显示一个叹号,让你在打开它时重新登录。visionOS 把 iMessage 当作一个普通的应用把它的系统设置中藏起来了。</li>
<li>除了能够显示多语言以外,国际化设置基本上不存在。输入法只有英文和 Emoji,Siri 只有英语,日期和时间格式的选项是不存在的,公制单位还是英制单位的选项也是不存在的。(多语言的字典倒是存在的,所有 macOS 上有的字典 visionOS 都有。)</li>
<li>Game Center 必须使用 iCloud 帐号,不能如同 iOS 一样注销后再登录另一个 Apple ID。</li>
</ol>
<hr />
<p>Vision Pro 天然支持 3D 视频,因为左右两眼是两个独立的屏幕,可以显示不同的内容。Disney+ 的应用在 Vision Pro 里面会专门显示一个 3D 影片的分类,很多 Marvel 电影和 Pixar 动画片都有 3D 的版本。我打开了《Avengers: End Game》看了个开头,发现 3D 电影的效果很好。前景的人物和物件显然是立体的,背景的投射会随着你头部移动而进行轻微的调节,使得背景看起来有深度。这是 Vision Pro 跟 3D 电视不一样的地方,后者并不会跟踪你的头部移动。</p>
<p>Vision Pro 支持播放 iPhone 15 Pro 和 iPhone 15 Pro Max 拍摄的 3D 视频,同时也能进行 3D 视频拍摄。用 iPhone 拍摄的话,视频必须用横屏模式拍摄,因为这样 iPhone 才能使用两个摄像头从左右两个角度进行拍摄。拍摄好 3D 视频后,在 Vision Pro 里面打开照片应用就能看到(假设 iCloud 已经设置好,图片视频已经自动同步),其中专门有一个 3D 视频的分类帮你把 3D 视频从所有视频中筛选出来。</p>
<p>在 Vision Pro 里面播放自己录制的 3D 视频,感觉就像在眼前打开了一个传送门,看到传送门另一端 3D 的景象。平面视频会让你觉得原本 3D 的世界已经被强行投射到了一个平面上,只是这个平面上的画面在动。但 3D 视频看起来就像一个传送门,能够看到立体的人物和物件在动。3D 视频的播放器还有一个「全屏模式」,或者说是「沉浸模式」,把视频播放器的边框虚化为云雾一般,然后嵌入到你身处的场景当中去。</p>
<p>同样是拍摄 3D 视频,用 Vision Pro 的话没有取景框,而且拍出来的视频长宽比是 1:1,也就是正方形的。没有取景框的体验有点奇怪,你没办法确定什么被拍进去了、什么被剪裁掉了。此外 Vision Pro 拍摄视频的稳定性没有 iPhone 好,人带着 Vision Pro 拍摄视频时难免会随着目光转移而轻微移动头部,拍出来的视频就会有对应的轻微抖动,观看时就更有可能觉得不适。iPhone 拍视频时已经智能对视频做了稳定化,即使有一点点手抖拍出来的视频依然是非常稳的。</p>
<p>在可以选择的情况下,我会建议使用 iPhone 拍摄 3D 视频。iPhone 的相机默认是没有开启 3D 视频拍摄的按钮的,第一次使用之前必须先去系统设置打开,然后在拍摄视频的界面就会出现一个 Vision Pro 外圈形状的图标,用以开启 3D 视频拍摄。具体怎么操作可以看 <a href="https://support.apple.com/guide/iphone/record-spatial-videos-for-apple-vision-pro-iph6e3a6d4fe/ios">Apple 官方的帮助文档</a>。</p>
<hr />
<p>这个系列的《Vision Pro 使用体验》,我准备想到哪里就写到哪里。这次先写这么多,接下来想到有新内容再更新。不想错过接下来的内容的话,敬请关注和订阅。这篇文章首发于<a href="https://www.patreon.com/catchen">我的 Patreon</a>,大家可以到 Patreon 上付费支持我写作。</p>Cat Chenhttp://www.blogger.com/profile/17757778716179768922noreply@blogger.com0tag:blogger.com,1999:blog-7005036.post-16482016702372077762023-10-22T16:02:00.003-07:002023-10-22T16:02:26.615-07:002023 年邀请链接(referral links)<p>我在 2018 年和 2019 年夏天都分享里一批我觉得值得推荐的服务和产品。疫情开始之后我就忘记了这回事,好多年都没再分享过。(不过我们《<a href="https://avocadotoast.live/">牛油果烤面包</a>》还是每年年底做一期《好物推荐》的。)现在想起来,就写一篇 2023 年的吧。</p>
<p>我今年推荐的服务和产品包括:</p>
<ol>
<li>Neon</li>
<li>Interviewing.io</li>
<li>One Medical</li>
</ol>
<p>以下是它们的详细介绍。</p>
<h2>Neon</h2>
<p>Neon 是一个三藩市湾区的亚洲菜外卖服务。跟其他随时点随时送的外卖不一样,Neon 是需要预订的,饭店会提前做好交给 Neon,Neon 每天下午派送。派送时食物是冰着的,重新加热一下就行。这些都是有门店的饭店,不是中央厨房,出品也是在门店能点到的菜色。</p>
<p>他们有 Koi Palace(鲤鱼门)的焗葡挞,一份 8 只或 24 只,我买回来用空气炸锅三分钟加热一下就很好吃。饭后作为甜品吃一个,一份可以吃一星期。(没有空气炸锅的话,烤箱也可以但就是慢。)两磅一份的咖喱牛筋腩也不错,完全冰冻的,重新蒸热了就能吃。牛筋这种在家里要煮很久才能煮烂的食材就很适合买别人煮好的。</p>
<p>他们有 Shanghai Dumpling King(包饺店)的小笼包,一份 10 只或 30 只。我买了之后冰起来作为备用的主食,什么时候需要了就拿 10 个出来,蒸 10 分钟就能吃了。他们还有 Harborview Restaurant & Bar(凯悦汇)的贵妃黄毛鸡和明炉烧鸭,这两个我也经常点。除此之外,他们还有其他东南亚菜系,都是由三藩市不同的饭店做的。</p>
<p>住在湾区,有时候不想做饭也不想出去吃的话,就会叫外卖。但是等饿了再叫的话,外卖又不知道要等多久才来。如果能够提前计划一下这周吃是什么的话,订 Neon 是很可靠的,通常下午 2:00 到 4:00 送到。因为 Neon 的食物需要重新加热,所以是当作食材来卖的,没有消费税。因为司机一天下午把整个湾区都送了,所以也没有转门的送餐费用,消费就随意了。</p>
<p>如果你使用我的邀请链接的话:</p>
<ul>
<li>你得到的好处:$15 的 Neon credit。</li>
<li>我得到的好处:$15 的 Neon credit。</li>
</ul>
<p>邀请链接:<a href="https://chen.cat/neon-referral">https://chen.cat/neon-referral</a></p>
<h2>Interviewing.io</h2>
<p>这是一个针对特定大厂(Facebook、Microsoft、Google、Amazon、Apple)匿名模拟面试(mock interview)服务。匿名是它最大的特点,你和面试官之间只有语音和代码,没有视频。面试官都是来自大厂的面试官,他们会用公司的面试流程和标准来面试你,如果你对某一家大厂的面试方式一无所知的话可以通过模拟面试来了解。面试结束后,你可以问匿名的面试官要反馈,也可以问他更多关于他公司的面试信息。</p>
<p>这些匿名的面试官会评价你的面试,如果他们觉得你通过了面试就会在 Interviewing.io 的系统里提交相关信息。如果系统发现你能稳定通过某一家大厂的模拟面试,它就会把你推荐给那家大厂进行 on-site 面试。你能跳过 technical phone screen,因为你已经通过了的 Interviewing.io 匿名面试就当作是 technical phone screen 了。</p>
<p>跟真实但匿名的大厂面试官进行模拟面试是要花钱的,具体价格由面试类型(coding、system design、behavior 等等)决定。除此之外,Interviewing.io 还提供免费的 peer-to-peer 面试预约,但面试官不是后台认证的大厂面试官所以质量就很难说了。如果只是想要多找人互相模拟面试,这是一个不错的选择。</p>
<p>如果你使用我的邀请链接的话:</p>
<ul>
<li>你得到的好处:首次消费时 $100 的折扣。</li>
<li>我得到的好处:一次免费的算法模拟面试(必须在你消费之后)。</li>
</ul>
<p>邀请链接:<a href="https://chen.cat/iio-referral">https://chen.cat/iio-referral</a></p>
<h2>One Medical</h2>
<p>今年 2 月份被 Amazon 收购的 One Medical 是一个连锁的诊所服务。有一些互联网大厂提供免费或打折的 One Medical 订阅服务,但就算没有公司打折的话也值得考虑全额自费购买。</p>
<p>One Medical 在<a href="https://www.onemedical.com/locations/">美国各大城市(都市圈)</a>都有诊所。在手机上预约医生面对面的门诊很方便,通常能约到一个星期内的。预约医生(可能包括 Nurse Practitioner)视频门诊的话,通常能约到一个小时之内的。疫情期间我每次出行回来都会用 One Medical 来约一次核算,流感等疫苗有时候我也会选择预约 One Medical 的(虽然 Walgreens 和 CVS 通常也有)。</p>
<p>总的来说 One Medical 提供了便利,不再需要自己去使用难用的医疗保险网站搜索医生,只要医疗保险能覆盖到 One Medical,就能直接在上面搜索附近的地点然后选一个最快能约到的医生。对于我这种以前在 Facebook 时习惯了园区内就有医生和牙医的懒人来说,非常方便。</p>
<p>如果你使用我的邀请链接的话:</p>
<ul>
<li>你得到的好处:第一年年费省 $50。</li>
<li>我得到的好处:没有。</li>
</ul>
<p>邀请链接:<a href="https://chen.cat/one-medical-referral">https://chen.cat/one-medical-referral</a></p>
<h2>实体商品</h2>
<p>我推荐的实体商品都会放在<a href="https://chen.cat/amazon">我的 Amazon Storefront</a> 上面,今年我觉得值得推荐的是 <a href="https://www.amazon.com/dp/B0B826KS4B?asc_item-id=amzn1.ideas.2OBAZIYDUWLB6&th=1&linkCode=ll1&tag=catchen-20&linkId=9689c79e47506211ce2a9583f190542a&language=en_US&ref_=as_li_ss_tl">Insta360 Link 摄像头</a>和<a href="https://www.amazon.com/dp/B0BDHWDR12?asc_item-id=amzn1.ideas.2OBAZIYDUWLB6&linkCode=ll1&tag=catchen-20&linkId=a3fe51f50da09a7f71b9d7cffda9ddae&language=en_US&ref_=as_li_ss_tl">第二代的 AirPods Pro(USB-C 版本)</a>。</p>
<p>Insta360 Link 是 Insta360 把便携智能云台用电脑摄像头上的结果,它能够通过人脸识别找到我,然后自动跟随我,还能根据我离摄像头的距离来调整变焦。作为 4K 摄像头,它的视频质量是没问题的。更重要的是,我不需要在视频会议前刻意调整我的摄像头角度了,Insta360 Link 一启动就会找我的脸。</p>
<p>AirPods Pro 应该不需要介绍了,大家都看过 Apple 官方的宣传片,降噪效果确实非常好。值得一提的是,此前有一个 Lightning 版本的我不小心连盒子扔进洗衣机洗了之后,拿出来干了竟然还能用。(偶尔有点小问题,但能用。)</p>Cat Chenhttp://www.blogger.com/profile/17757778716179768922noreply@blogger.com0tag:blogger.com,1999:blog-7005036.post-75232469383700748202023-03-30T12:49:00.002-07:002023-03-30T12:49:00.182-07:00熟练使用 Google 或 Facebook 内部工具毫无用处?<p>有一部分 Google 和 Facebook 员工总是在担心一件事情:<strong>公司内部使用的所有工具都不是来源的,离开了公司就不会再用到,花那么多年把这些工具用到熟手了,一旦离职就会变成毫无用处的沉没成本。</strong>这种忧虑显得非常围城:在大厂外面的人觉得自己使用的工具不够好,想要进大厂看看到底人家有什么黑科技;在大厂里面的人觉得自己只用闭源工具,担心因为自己不懂其它公司都在用的来源工具而失去竞争力,想要花时间学习开源工具但又没有机会。</p>
<p>担心这种事情并非毫无道理,但我觉得这不是一个真正值得解决的问题,而且不是一个解决后就一了百了的问题。如果此时此刻在大厂,还不如专心做好眼前的工作,等到真正换工作的时候再根据工作需要现学。真正重要的能力,不是你现在懂什么,而是需要在未知环境中摸索时你能够学得有多快。(学校里建立激励机制是错的。根据你此时此刻懂多少来决定给予多少正向或反向激励,一旦你把这内化了就会造成职业上长远的负面影响。)</p>
<hr />
<p>为什么说「不懂外部开源工具」这个问题不可能被彻底解决?这本质上还是因为技术发展的速度太快,如果一门技术此时此刻用不上,你学了之后就会逐渐贬值。</p>
<p>很多人只看到了现在成功的独角兽在用什么技术,但现在已经成功了的独角兽都是 10 后那一代创业公司了,它们创业时赶上了 AWS 的浪潮,但那时候大家用的 AWS 跟今时今日 AWS 提供的服务根本不是同一回事。那时候大家还是用 EC2 这样赤裸裸的机器,运维还是工作的一大部分,大家尝试做运维自动化但成熟度跟今时今日比简直就是玩具。</p>
<p>随着这一批 10 后创业公司的成长,它们对 AWS 的用量越来越大,遇到的运维问题越来越复杂,然后才出现了现在常见的这一些解决方案:用 AWS 管理界面太手工了,所以用 Terraform 来统一管理需要用到的 AWS 资源。自己在 EC2 上安装和维护软件太麻烦,关键的是缺乏一致性,公司内部做一套统一的容器镜像吧,把所有常用的东西打包进去。很多这些解决方案都是为大规模 AWS 运维设计出来的。</p>
<p>然而如果你看一下 20 后新生代的创业公司在用什么,你会发现你学会前面的一切都没用。20 后创业公司一上来就用 Firebase。EC2 是什么?虚拟机是什么?容器是什么?统统不知道,也不需要知道,赶紧把东西做出来,获得用户和拿到融资最重要。Firebase 当然有它的弱点和限制,大多是 20 后创业公司在扩大规模时都会遇到 Firestore 无索引筛选慢的问题,然后要把数据迁移到 GCP 或干脆把服务迁移走。</p>
<p>现实情况就是这样子,如果你离开大厂加入一家 10 后创业成功的独角兽,你就必须接受过去 10 年大家在 AWS 上建立起来的复杂生态环境,但即使你学会了你依然摆脱不了原来的忧虑:这些技术仅适用于这一批公司。你跳回去大厂,这些技术就没用了。你跳到 20 后早期创业公司,这些技术也没用。等它们 10 年后成长为独角兽时,你需要为它们解决新一代技术带来的问题,而不是去纠结上一代技术已经把上一代的问题解决得有多好。</p>
<hr />
<p>你的职业发展如何,受你控制且最重要的部分是你能够为别人创造多大的价值。(这也是学校激励机制从来没有帮助你内化的东西。)从懂什么技术出发思考如何优化职业发展,这本身就是错误的方向。你必须从如何创造价值出发进行思考,如果没有充足的信息进行思考那就先尽心观察收集数据。</p>
<p>每一家公司要解决的问题都不一样,具体的某一种技术可能为解决特定的某一个问题提供了所需的手段,但越是通用的技术越需要针对特定问题通过定制化来落地。我见过很多人把「这在我上一家公司非常成功地解决了这个问题」带到新公司,尝试重复一边解决同样的问题,最终发现没有两个问题是一样的。在新公司不接地气,过去成功过的解决方案在新公司不能落地,最终都会失败。</p>
<p>我观察到一些人的成功路线,是借助在上一家公司打开的眼界,在下一家公司结合实际地解决问题,然后迅速地成长。例如说,在大厂当个 L5 接手维护一个曾经非常有开创性的系统,保证它的服务质量同时在上面添加功能。没错,这开创性的工作是轮不到你做了,几年前做这件事情的人升了 L6,但这样的机会只有一次,你不是第一个把路从无到有地踩出来的人,你就没机会升 L6。但你可以把这个系统和它解决的问题搞明白,接着去一家尚未解决过这个问题的独角兽,帮助他们解决这个问题。</p>
<p>对你来说真正有价值的是你见过这件事情能做成的视野。一件探索性的事情知道它能做成,你就已经成功了一半。(有很多探索性的事情,最终是做不成的,至少是在你有生之年人类无法达成。)但你不能把大厂的方案直接抄一遍,你需要理解在这个相似的领域这家公司独有的问题是怎样的,然后定制一个能在这家公司落地的方案。在一家独角兽迅速成长的那几年里,很多问题都是由于规模迅速扩张而造成的,曾经帮助这家公司成功的那批人对这些问题一无所知,而你在大厂见过规模成功扩张后的方案,所以他们会寄望于你来帮助他们解决问题。因为这件事情在这家公司还没有发生过,这个问题也没有被解决过,你在这家公司做出来就算是开创性的了,于是你可以在这里升 L6。</p>
<p>等你升完 L6,环视四周,发现升 L7 的好机会也都被别人挖掘完了,接下来在这家公司升 L7 会变得越来越难。没关系,你需要做的是把前面这个过程再重复一遍,这家公司已经被人利用过的 L7 机会,总有下一家还在路上的公司还没有遇到过相关的问题,等着你去解决。闭源的大厂,尤其是 Google 和 Facebook 这两家,里面有很多黑科技是外面大家根本不知道能做得这么好的,知道能做得这么好就是你最大的优势。(说得直白点,小厂是「贫穷限制了想象力」,而你见识过拥有几乎无限资源的大厂的想象力能去到的地方。)</p>
<hr />
<p>回到文章的主题上来,熟练使用内部工具本身没有什么特别的价值,但知道这样的工具能做出来(而别人不知道这样的工具竟然能做出来)就很有价值,深入理解这些工具是如何被设计出来的以及它们被设计出来时的历史背景也很有价值。</p>Cat Chenhttp://www.blogger.com/profile/17757778716179768922noreply@blogger.com0tag:blogger.com,1999:blog-7005036.post-67451488396408718512023-03-26T20:01:00.001-07:002023-03-26T20:01:00.173-07:00从人工编辑到算法排序<p>为什么作为企业内部沟通工具,Slack 在公司还小的时候那么好用,但在公司变得越来越大之后 Slack 会变得如此低效?这个问题我思考有一段时间里,我觉得这是一个数据规模的问题,在规模还小的时候 Slack 是一个很好的解决方案,但一旦规模大到一定程度 Slack 就不再有效率。</p>
<p>如果要跟 Slack 做对比,在 Facebook 内部使用 Facebook Workplace 的效率就很高。Workplace 有它自己的问题,例如说导致员工不停地在刷 newsfeed,因为总是害怕自己错过了什么重要的信息,所以一有空停下来就要刷。除此之外,在 Facebook 内部使用 Workplace 的体验很好,重要的信息总是会在 newsfeed 上出现。因为 Facebook 知道你是谁、你跟哪些同事的交互比较多、他们又跟哪些内容交互比较多,所以 Workplace 的排序算法能够把对你重要的内容有限显示出来。</p>
<p>为什么 Slack 和 Facebook Workplace 存在这样的区别?我思考的结果是,Slack跟 Workplace 的区别本质上很像 Yahoo 作为一个门户网站跟 Google 的区别。前者依赖于人工编辑,而后者使用算法排序。在数据规模有限时,人工编辑能够应付得过来而且效果可以非常好,毕竟这是让人亲自审阅这些内容然后决定什么重要、什么不重要、如何归类等等。随着数据规模变得越来越大,人工编辑最终会应付不过来,这就是 Yahoo 作为门户网站最终会输给 Google 的原因,同时这也是 Slack 不好用的原因。</p>
<hr />
<p>面对大量没经过整理的信息,我们有两个非常不一样的选择:</p>
<ol>
<li>尝试控制这些信息的产生和流动。</li>
<li>放弃对信息产生和流动进行控制。</li>
</ol>
<p>这两个不同的方向,导致了截然不同的产品设置。</p>
<p>门户网站和搜索引擎的区别能够很好地解释这两个方向的差异有多大。门户网站相信自己可以人工整理和索引世界上所有的网站,或者至少是重要的网站。因此如果你要找某个网站,在门户网站上你要么能找到这个网站(有索引)要么不能找到(没索引)。这就好比图书馆,你不知道是否存在一本某个题材的书,但你可以根据图书索引找到这个题材的书所在的书架,浏览完书架上所有的书你就知道你想要的这本书是否存在了。</p>
<p>搜索引擎做了一个本质上截然不同的假设。沿用图书馆的例子,因为数据规模如此之大,你就算根据图书索引找到了你想要的书所存在的书架,你也会发现符合这一分类的书架如此之多你根本浏览不过来。在一个小图书馆里,可能计算机科学只是一个书架。在人类知识的图书馆里,计算机科学的书架区域可能有好几平方公里大。使用搜索引擎的前提,就是你选择了主动放弃对信息的控制,你让图书馆管理员帮你挑 10 本跟你想要题材最相关的书,他选好后拿过来给你浏览,你从中挑一本你觉得最合适的拿去读。</p>
<p>使用 Google 进行搜索时,虽然 Google 会告诉你总共有多少条结果、分开多少页显示,但大多数人根本就不会在乎,因为前几条有你想要的信息就是有,没有的话再翻几页也不太可能有。这就是我所定义的放弃控制:选择相信算法把对你重要的信息放在前面,放弃排在后面的信息。无论你从哪一个点开始选择不再看排在后面的信息,你都可以相信你已经浏览了相关性高的信息,而你不看的信息的相关性肯定不如你已经看过的高。</p>
<hr />
<p>Slack、邮件以及公司内部人工编辑的门户网站,其实都属于第一种选择,相信人能够控制信息。Slack 可以通过 channel 控制信息分类,可以通过加入和退出 channel 来控制你具体接收的信息。邮件无论使用 Gmail 还是 Outlook 接收,都可以设置复杂的规则对邮件进行控制,例如说什么邮件不看,什么邮件不那么频繁地看。公司的内部门户网站也一样,编辑往往站在公司的立场来思考公司想要让什么员工获取什么信息,然后编写文章并发布到对应的频道。</p>
<p>Facebook Workplace 属于第二种选择。使用 Workplace 意味着你放弃了手工(包括通过设置规则)判断什么信息重要、什么信息不重要,你选择相信 Facebook 的排序算法帮你把重要的信息排在前面,因此无论你刷 newsfeed 刷到哪里停下来你都可以相信你已经阅读了比较重要的信息,排在后面你不去阅读的信息一定没有前面的那么重要。正是因为 Workplace 的这种特性,在 Facebook 这种规模已经非常大的公司内部 Workplace 会显得很好用。</p>
<p>今时今日已经没有人使用门户网站了,大家都使用搜索引擎,这是因为全球公开的信息的规模已经非常之大。然而企业面对的情况不一样,每一家企业都是从小开始做大的,在它们还小的时候 Slack 就会显得非常好用。(我自己经营一家几个人的公司,一个 Facebook Messenger 的群聊就够了,甚至不需要分多个 channel。所有人知道公司内部所有正在进行的讨论,因为根本就没有多少讨论。)然而当企业做大之后,切换到 Workplace 就很难。因为信息架构的本质区别,Slack 里面的信息不可能导入到 Workplace 里面,这就使得 Slack 拥有巨大的粘性。</p>
<p>Workplace 有些很成功的跨国公司客户,例如说 Walmart 和 Starbucks。对于这种规模的公司来说,使用 Workplace 非常合适。但创业公司上了 Slack 的船后就很难下来了,这就导致了今时今日很多创业公司有一定规模后需要想办法应对 Slack 带来的各种负面影响。我当然希望能够直接从 Slack 切换到 Workplace,但实际上这对于任何一家这种规模的公司来说这都是一个痛苦的迁移。</p>Cat Chenhttp://www.blogger.com/profile/17757778716179768922noreply@blogger.com0tag:blogger.com,1999:blog-7005036.post-63902919712555479682023-03-19T23:13:00.002-07:002023-03-19T23:13:00.187-07:00在 React Native WebView 中使用自定义字体<p>在 React Native 中使用自定义字体(自己提供 woff2 或其他格式的字体文件)很常见,官方文档也说得很清楚。在 WebView 中使用自定义字体也是有文档可以参考的。但在 React Native 里面使用 WebView 并且要在 <a href="https://github.com/react-native-webview/react-native-webview">React Native WebView</a> 里面使用自定义字体,我搜索了一下没找到现成的文档,只好自己根据 iOS 和 Android 原生 WebView 的文档研究对应的 React Native 方法。</p>
<p>这里说的是在 React Native WebView 中嵌入构建时已经打包进去的本地自定义字体文件。如果使用网络上的自定义字体文件的话,标准的 CSS <code>@font-face</code> 就能解决,唯一需要注意的是网络上的自定义字体文件是否跨源(cross origin)。跨源在 WebView 里也不是解决不了的问题,可以通过改变页面的 <code>baseUrl</code> 把本地生成的 HTML 页面变成同源。也可以把字体部署到自己控制的服务器上,把 <code>Access-Control-Allow-Origin</code> header 设置好。</p>
<h2>iOS</h2>
<p>React Native WebView 在 iOS 上使用的是 <code>WkWebView</code>,React Native 中的组件 API 跟原生 <code>WkWebView</code> 的很相似。原生有一个这样的 API 用来加载一个 HTML 页面:</p>
<pre><code>func loadHTMLString(
_ string: String,
baseURL: URL?
) -> WKNavigation?
</code></pre>
<p>原生应用可以通过 <code>Bundle.main.bundleURL</code> 来获得应用目录的绝对路径 URL,也就是以 <code>file://</code> 开头的一个 URL。在 <code>loadHTMLString</code> 时把 <code>baseURL</code> 指向这个 URL 那打开页面的绝对路径也就是应用目录的绝对路径了。假设打包时已经把 <code>custom-font.woff2</code> 文件当作资源打包进去放在应用根目录了,那在 CSS 中就可以以相对路径的方式指向这个文件了:</p>
<pre><code>@font-face {
font-family: "Custom Font";
src: url("custom-font.woff2") format("woff2");
}
</code></pre>
<p>在这里 <code>custom-font.woff2</code> 等同于 <code>./custom-font.woff2</code>,也就是在应用根目录的同名文件。这在原生很容易解决的问题,在 React Native 中的主要障碍是缺乏一个 JavaScript 可以直接读取的 <code>Bundle.main.bundleURL</code>。为此我们要自己写一个简单调用 <code>Bundle.main.bundleURL</code> 的 <a href="https://reactnative.dev/docs/native-modules-ios">Native Module</a>。</p>
<pre><code>// WebViewBaseUrl.m
#import <Foundation/Foundation.h>
#import <React/RCTBridgeModule.h>
@interface RCT_EXTERN_MODULE(WebViewBaseUrlModule, NSObject)
RCT_EXTERN_METHOD(getBaseUrl:(RCTPromiseResolveBlock)resolve
rejecter:(RCTPromiseRejectBlock)reject)
+ (BOOL)requiresMainQueueSetup
{
return YES;
}
@end
</code></pre>
<pre><code>// WebViewBaseUrl.swift
import Foundation
@objc(WebViewBaseUrlModule)
class WebViewBaseUrlModule: NSObject {
@objc
func getBaseUrl(_ resolve: @escaping RCTPromiseResolveBlock,
rejecter reject: @escaping RCTPromiseRejectBlock) {
resolve(Bundle.main.bundleURL.absoluteString)
}
}
</code></pre>
<p>获取到应用路径后,把它用于 React Native WebView 的 <code>baseUrl</code>,本质上跟 iOS 原生的 <code>baseURL</code> 没什么区别,只不过前者是 <code>string</code> 后者是 <code>NSURL</code>。</p>
<pre><code>const baseUrl = await NativeModules.WebViewBaseURL.getBaseUrl();
return (
<WebView
source={{
html,
baseUrl,
}}
/>
);
</code></pre>
<p>(考虑到 macOS 的相似性,这个方法估计在 macOS 上也有效,但我没有测试过。)</p>
<h2>Android</h2>
<p>Android 的应用根目录路径跟 iOS 不一样,此外 Android 给 WebView 提供了一个神奇的但仅限于 WebView 的应用根目录路径,那就是 <code>file:///android_asset/</code>。在 Android 的原生代码里面是不能使用 <code>file:///android_asset/</code> 访问应用内的文件的,但在 WebView 里面却可以使用这个神奇的绝对路径。</p>
<p>有了这个神奇的路径后,Android 上需要做的事情就很简单了。我们不需要从原生代码获取应用路径,只需要使用准确的相对路径就可以了。假设 <code>custom-font.woff2</code> 文件正确地放置到了 <code>assets/fonts/custom-font.woff2</code> 目录,便宜打包后这个文件就可以在 WebView 里面通过 <code>file:///android_asset/fonts/custom-font.woff2</code> 访问。为了跟 iOS 使用相似的 CSS 相对路径,我们可以把 <code>baseUrl</code> 指向 <code>file:///android_asset/fonts/</code>(假设 WebView 不需要用到其他本地编译时打包好的资源的话)。</p>
<pre><code>let baseUrl;
if (Platform.os === 'ios') {
baseUrl = await NativeModules.WebViewBaseURL.getBaseUrl();
} else {
baseUrl = 'file:///android_asset/fonts/';
}
return (
<WebView
source={{
html,
baseUrl,
}}
/>
);
</code></pre>
<p>这样就可以同时覆盖 iOS 和 Android 的场景了。React Native WebView 其实还支持 Windows,但我不需要支持 Windows 所以也没去研究。</p>Cat Chenhttp://www.blogger.com/profile/17757778716179768922noreply@blogger.com0tag:blogger.com,1999:blog-7005036.post-8663503248645760442023-03-06T16:22:00.001-08:002023-03-06T16:22:00.187-08:00工程师成长到最后最重要的是什么?<p>有一个问题我最近在 career coaching 时连续被不同的人问了几次,第一次被问到时我想到什么就说什么,但被连续问了几次之后我觉得我需要好好思考一下。这个问题问的是,「工程师成长到最后,最重要的是什么?」</p>
<p>刚刚进入互联网行业的新人,往往觉得自己懂得还不够多,别人比自己厉害、比自己资深,是因为别人比自己懂得更多。按照这个逻辑进行推导,正确的目标自然是让自己懂得越来越多。再往前推导,既然每一个人的时间是有限的,那必须优先弄懂那些重要的事情,因此有了上述这个问题。大家都不想走弯路,希望找到直达终点的直线。</p>
<hr />
<p>我觉得这个问题的答案并不是清晰可知的,因为清晰可知的事情迟早会被大家优化到极点。能够通过面试进入大厂的人,都聪明和勤奋到一定程度。如果存在这样一条直线,大家都或多或少地都沿着这条直线全力往前跑,最终跑出来的结果会存在一定的差别但不会存在巨大的差别,尤其是不会存在那种走直线比走曲线所应该获得的巨大优势。</p>
<p>那真正能够产生差异的是什么?那必然是一些别人没办法直接告诉你的事情。这是可以通过反证法证明的。如果有人能够教会你如何能够获得别人不能获得的巨大优势,他会选择教你吗?为什么?大家很容易产生一种错觉,「比我厉害的人肯定懂得比我多,这跟在学校里一样,但是他们不愿意教我所以我不如他们厉害。他们联合起来保守秘密,阻止我们获得他们拥有的知识」。</p>
<p>按照这样的假设,比你厉害的人当中只要有一个人守不住秘密,这种知识早就传播开来了。而且教授这种知识肯定是能赚钱的,所以比你厉害的人其实都有动机通过教授这种知识来赚钱,他们不可能真的联合起来保守秘密。这就可以反证,不是别人拥有你没有的知识而且不愿意教你,现实更有可能是他们没办法教你,或者说教你的成本太高所以不知道花时间去教。</p>
<hr />
<p>有一个概念叫做「<a href="https://zh.wikipedia.org/wiki/%E9%9A%90%E6%80%A7%E7%9F%A5%E8%AF%86">隐性知识</a>」,英文叫做「<a href="https://en.wikipedia.org/wiki/Tacit_knowledge">tacit knowledge</a>」,指代的是那些难以言述的知识。隐性知识类似于通过深度机器学习训练出来的一个模型:它可以用,它大多数时候是正确的,少数时候会出错。你没办法通过人和人之间很容易就能沟通的若干条规则把它描述清楚,因为能描述清楚的话就不需要用到机器学习了,用规则系统就可以了。你没办法很好地预测它什么时候对什么时候错,你也不太能解释它错的时候为什么出错了。</p>
<p>面对这样的模型,你可以选择把它整个拷贝走。但如果你不能拷贝呢?(因为人类还没有进行人脑到人脑拷贝的技术,也不确定这样的技术是否真能被发明出来。)如果你能使用这个模型但不能拷贝这个模型,你可以用这个模型作为基准和反馈从零开始训练一个相似的模型。这也是现在已知的传播隐性知识的方法:找一个把某件「只能意会不能言传」的事情做得非常好的人,你在进行训练时请他提供反馈,最终你训练出来的结果不可能跟他完全一样,但你的目标也只是获得相似效果而已。至于能否训练出来,以及要训练多久才能达到你想要的效果,这都是要尝试过才知道的。</p>
<hr />
<p>现实就是这样子的:有能力进入大厂的人,通过过书本(或其他媒体)学习显性知识的效率肯定不会差。瓶颈往往出现在显性知识能学的都学了,学更多也不能变得更厉害了,但又没有意识到需要甚么样的隐性知识,或者是不知道从哪里能够获得需要的隐性知识。</p>
<p>所以工程师成长到了一定的阶段以后,搞明白自己需要什么以及找到跟谁能获得有效的训练是最重要的。具体怎么做就不是通过写一篇文章就能说清楚的,否则这就不叫做隐性知识了。我能给的建议是:找你相信真心关注你成长的人,请他们花时间观察你工作,问他们觉得你的成长需要什么、谁能够帮助你训练你所需要的。</p>Cat Chenhttp://www.blogger.com/profile/17757778716179768922noreply@blogger.com0tag:blogger.com,1999:blog-7005036.post-26871211621622006812022-09-25T15:01:00.005-07:002022-09-25T15:01:00.177-07:00帮助别人的正确姿势<p><div class="separator" style="clear: both;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjj7kKVuAwoshF0PK3G83NwOJ9DIwNFR3pqrgN1y-IDHrf-bHx2QvEaJNZkqf4cYBdfUscFBUZQQRjzFpUcXs-fHfxG_foyjwGNASlZj_BXZboIXXSySn4R7NWY_NxsX7lsSIXB7UmGLhevAoKZJS2g3J8oSO1uavT5RiEMWtCLTcWH7bonnEQ/s5599/toa-heftiba-_UIVmIBB3JU-unsplash.jpg" style="display: block; padding: 1em 0; text-align: center; "><img alt="" border="0" width="320" data-original-height="3733" data-original-width="5599" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjj7kKVuAwoshF0PK3G83NwOJ9DIwNFR3pqrgN1y-IDHrf-bHx2QvEaJNZkqf4cYBdfUscFBUZQQRjzFpUcXs-fHfxG_foyjwGNASlZj_BXZboIXXSySn4R7NWY_NxsX7lsSIXB7UmGLhevAoKZJS2g3J8oSO1uavT5RiEMWtCLTcWH7bonnEQ/s320/toa-heftiba-_UIVmIBB3JU-unsplash.jpg"/></a></div></p>
<p>大家晋升到一定级别后就会被要求带新人,怎样带新人才算是成功?绩效评价时是只看有还是没有带人,还是要看带人具体的效果?如果要看效果的话那如何衡量效果?这是我在 career coaching 的过程中时不时会遇到的问题,我觉得可以把我的框架写下来分享一下。</p>
<p>首先,带新人以及更广泛的 mentoring 和 coaching 肯定是可以根据结果来衡量做得好还是不好的,但刚刚开始上手这项工作时更应该看重的是效率而不是效果。这不是说完全不看结果,而是说在合理的难度上能及格地完成工作但不追求更好的效果。</p>
<p>这有点像程序员刚刚开始学习编程时写代码的目标:要能够通过编译,执行结果要符合预期,但不考虑执行性能,也不考虑代码是否优雅。首先要会写代码,接着要多写,写得越快就能在同样的时间里写得越多。练习多了以后,再考虑如何提升围绕着编程的其它指标。如果一开始写不出正确的代码,或者练习的数量不够,其它指标均无法优化。</p>
<hr />
<p>在有机会带人之后,首先要保证别人确实能够从你这里得到帮助。简单地问一句「你觉得这对你有帮助吗?」就够了,绝大多数时候别人都会说「有帮助」。如果你发现对方迟疑一下才告诉你「有帮助」,你可以再追问一下「有哪些方面可以改进吗?」可能你分享了很多信息,但没有回答到点上;可能你提供了很多理论,但对方还是不知道接下来该做什么。多尝试直接或间接地获取反馈,做到对别人提供有效帮助并不难。</p>
<p>在确认别人能够获得帮助后,接着就需要提高效率。我们可以用一个数值来衡量效率:杠杆比例——如果你花 1 倍时间来帮助别人就能让别人省 n 倍时间的话,这个 n 是多少。效率最低时 n=1,这意味着对方需要获取帮助的事情你帮他做了,而且你做得还不比他快多少。(如果 n<1 你要认真考虑一下你是否应该带人。)带新人的话往往一开始就能做到 n=2 甚至 n=3,毕竟你比新人要更熟悉整个技术栈,就算你帮他把他的工作做了,你也应该比他亲自做要快不少。</p>
<p>我通常建议的目标是 n=8,也就是说如果一天工作 8 小时,那你花 1 个小时就能帮助别人省 1 天的时间。从 n=2 到 n=8 有一段距离,没有人可以瞬间做到 n=8,但要把这做为一个长远的目标,不停地想办法优化以提高 n 的数值。这时候有一种意识很重要:只帮对方必须要获得外界帮助的东西,他自己能够缓慢学习提高的东西不需要帮。</p>
<p>打一个比喻:想象一个倾斜的平面上有一个因为摩擦力而没有向低处滑动的物体。因为静摩擦比动摩擦要大很多,重力无法抵消静摩擦所以物体不会运动,但有可能重力比动摩擦要大,只要运动起来就可以加速。你需要做的就是提供一开始克服静摩擦的那个力。如果没有这个力,这个物体可以相对平面永久地静止,但一旦它运动起来了,无论初速度有多低都能逐渐加速。</p>
<p>这跟「授人以渔」是不冲突的。你教会一个人基本的捕鱼技巧之后你就可以让他自己通过训练来提高捕鱼的效率,他应该自己想要捕更多鱼,这样子他才能从饿肚子变成能吃饱,再到能够卖鱼赚钱。他开头的阶段体验可能不太好,自己捕的鱼自己都吃不饱,饿着肚子又要去捕鱼了。但你效率也很重要,你效率越高就可以越大规模地帮助其他还完全不会捕鱼的人,这些人因为完全不会捕鱼都快要饿死了。(前提是确实有很多人能从你的帮助获得价值,都等着从你那里获得帮助。)</p>
<p>为了实现这个目标,在帮助别人之后最好自己复盘一下,想想自己有哪里的效率做得不够高,如果再遇到类似情况的话怎样能够提高性价比。</p>
<hr />
<p>效率上去之后,你就可以开始关注难题了。就好像你在能够熟练编程以后,你就会想要找难题来解决一样。难题之所以难,是因为你不能通过提供信息(包括知识、数据等)来解决问题,真正的障碍往往来自于你想要帮助的人的意识。可能在他价值观深处藏着一些他自己也不知道的东西,阻止了他达成他的目标。可能你能看出来他的目标在南面,但他的价值观会引导他向北走。</p>
<p>有时候你提供信息(一张地图)给他就行了,他能够从逻辑上分析出来:要么调整价值观,使得价值观能够允许自己往南走;要么放弃这个目标。有时候你会遇到很顽固的人,他觉得只要往北走足够远就能绕地球一圈抵达目标。你需要挖掘他此前怎样的人生经历塑造了他这样的价值观,然后才能确定你是否能做点什么。这些难题的共同点只存在于这个层面:你不可能用你的知识和逻辑去帮助他,你需要搞明白这个人是经历了什么才成为了今天这样子。</p>
<hr />
<p>这篇文章首发于<a href="https://www.patreon.com/catchen">我的 Patreon</a>,对同类文章感兴趣的话可以考虑在 Patreon 上支持我,付费订户可以提前至少一周阅读到我的最新文章。同时也欢迎大家通过<a href="https://chen.cat/subscribe-to-blogs">邮件</a>或 <a href="http://feedproxy.google.com/CatChen/Chinese">RSS/Atom</a> 免费订阅我的博客。</p>Cat Chenhttp://www.blogger.com/profile/17757778716179768922noreply@blogger.com0tag:blogger.com,1999:blog-7005036.post-88699065746303442092022-08-24T14:07:00.002-07:002022-08-24T14:07:00.180-07:00求助于别人的正确姿势<div class="separator" style="clear: both; text-align: center;"><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/a/AVvXsEh68MChQ96xpYZXGysv6QMDZD8-8REqjOndiipDHX9-IOy63Z0sDRsPbONyvHOceBIvRvUgcsDKx69oKCQNOUWowtXh4N2cZAHNJwUUb5yGxGHrqCcWtQUO9TKatkcmBkyPh7pMyxPG4WIx158K9MAgQIw6jxuqox6XTouvD1vki1KceQn8p8o" style="margin-left: 1em; margin-right: 1em;"><img alt="" data-original-height="4573" data-original-width="6860" height="213" src="https://blogger.googleusercontent.com/img/a/AVvXsEh68MChQ96xpYZXGysv6QMDZD8-8REqjOndiipDHX9-IOy63Z0sDRsPbONyvHOceBIvRvUgcsDKx69oKCQNOUWowtXh4N2cZAHNJwUUb5yGxGHrqCcWtQUO9TKatkcmBkyPh7pMyxPG4WIx158K9MAgQIw6jxuqox6XTouvD1vki1KceQn8p8o" width="320" /></a></div></div>
<p>什么情况下应该求助于别人?求助于别人是否就是伸手党,就会被人鄙视?我在做 career coaching 的过程中发现这是个常见问题,决定拿出来专门写一篇文章。Facebook 的 Bootcamp 在这方面对新人做了很多意识方面的训练,非常值得参考。</p>
<p>在 Bootcamp,专门有一个区域是给 mentor 提供 office hour 的,目标类似于学校里的 office hour(课后问答),如果作为新人的 bootcamper 在工作过程中遇到了问题,可以去这个区域进行提问。新人当然会问:那我该什么时候去提问?标准答案是:如果你自己捣腾了一个小时还是毫无进展,你就该去提问了。</p>
<p>跟学校作业不太一样的是,Facebook 布置给 bootcamper 的任务并不是标准答案已知的练习题,而是实际项目中不紧急也不重要的任务,每一个任务都不一样,mentor 不会提前知道问题,也不一定总能找到正确答案。新人在 office hour 提出问题后,mentor 都会问「那你都尝试了哪些办法?」如果有人说不出来,那 mentor 都会说「你先回去自己捣腾一下,如果一个小时了都毫无进展再回来提问」。</p>
<p>在新人解释完自己尝试过的办法后,如果 mentor 知道答案,可能会直接告诉他答案,把相关文档的链接发给他;如果 mentor 也不知道答案,他会亲自动手,把新人写了一半的代码拿过来 debug,试着解决问题;如果 mentor 解决不了,但知道谁可能提供有效帮助,他会把新人介绍过去。</p>
<p>就这样简单地一个过程,它强调了几种在 Facebook 成功必要的意识:</p>
<ol>
<li><strong>mentor 必须授人以渔,bootcamper 必须求学捕鱼,不可以伸手要鱼。</strong>之所以问你尝试过什么办法但不成功,是因为接下来 mentor 必须要告诉你或者示范给你看什么办法能成功。你不能说「我捕不到鱼,请给我一条鱼」,正确的提问方式是「我用这样的方式捕鱼,我试过了但什么鱼也捕不到,请教我正确的捕鱼方式」。</li>
<li><strong>即使面对你完全不懂毫无经验的问题,动手开干是最好的学习方式。</strong>动手开干不一定是写代码,动手开干也可以是搜索和研究。这跟在学校课程遇到问题存在一个本质的区别:学校课程能遇到的问题,都有已知答案,已知答案都属于一个清晰明了的知识体系,教材总能站在一个对该领域无所不知的角度帮你讲清楚一切。实际工作上的问题,往往你只能获取到碎片化的相关信息,然后再加上你自己的捣腾,产出一个可用的结果。你必须拥抱「真相在一定程度上不可知」然后冲上去捣腾。</li>
<li><strong>你能直接接触到的人不一定有你需要的信息,你需要学会找到正确的人来获取正确的信息。</strong>这也是跟学校成体系的知识和课程结构很不一样的。在学校,你可以假设教这一课程的人对课程知识范围无所不知,你去 office hour 提问肯定能得到答案。在工作上需要获取的信息,有可能需要经过多个节点才能获取到,这个人建议你问那个人,那个人建议你问另一个人。你虽然不确定要经过多少个节点才能获取到你需要的信息,但你最好有一套方法能够收敛你和目标的距离。</li>
</ol>
<p>修炼好这几种意识,即使不在 Facebook 工作也能让你更好地在需要时求助别人。即使你做不好后面两点,你至少要把第一点做好。</p>
<hr />
<p>我在 career coaching 时有时候会遇到客户这样的顾虑:我向比我资深这么多的人求助,会不会是很浪费他们的时间?面对这种自我怀疑,你必须调整心态,把自己放在一个能够双赢的角度来思考你们的处境。</p>
<p>首先,你要确定你想要求助的事情确实重要。如果是工作上遇到的问题,你要确定解决这类问题是符合公司利益的,站在公司的立场来看这类问题确实值得解决。这时候无论是你解决还是别人解决,这类问题都是要解决的,这奠定了双赢的基础。</p>
<p>接着,你求助的态度必须是「请教会我捕鱼。我也不想总是找你要鱼,所以教会我捕鱼是符合双方利益的双赢选择。」对于公司来说,这是个三赢的选择,公司解决了符合公司利益的问题,你学会了可以重复使用的技能,帮助你的人将来不需要反复地帮助你。</p>
<p>最后你当然要把这实践执行到底,不能在别人教你捕鱼时捕到一条鱼就走人。尽管这一条鱼满足你此时此刻的需求,但很可能你还没有扎实的学会捕鱼,下次又要劳烦别人。你表明了你想学,你就必须比教你的人更投入地学。</p>
<hr />
<p>「求助于别人的正确姿势」应该有一个镜像话题「帮助别人的正确姿势」。这是新手 mentor 时常会遇到的问题:我觉得帮助我的 bootcamper 非常消耗时间,但我不帮他又不行,怎样把握这个度呢?我可以下次写一写这个话题。这个话题不仅仅对 mentor 有用,对 mentee 也有用,因为在知道 mentor 如何才能更高效地帮助你之后,你也能更高效地利用 mentor 的时间。</p>
<p>这篇文章首发于<a href="https://www.patreon.com/catchen">我的 Patreon</a>,对同类文章感兴趣的话可以考虑在 Patreon 上支持我,付费订户可以提前至少一周阅读到我的最新文章。同时也欢迎大家通过<a href="https://chen.cat/subscribe-to-blogs">邮件</a>或 <a href="http://feedproxy.google.com/CatChen/Chinese">RSS/Atom</a> 免费订阅我的博客。</p>Cat Chenhttp://www.blogger.com/profile/17757778716179768922noreply@blogger.com0tag:blogger.com,1999:blog-7005036.post-15104132623569335982022-08-13T17:00:00.004-07:002022-08-15T05:51:09.509-07:00面试题:开发新功能和重构老代码之间怎么选?<p></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/a/AVvXsEi_8263ix9YKjofzZLBR-J9BlT9xKd9khsU7QtrVo1-110FDID950OlANkU3ovWb7GSx-X5k0v4PR6A1KjK0XUH1Xq6u4aAOOmN0X4twcg8B2JnEe8WwNbI6u3Us4pCZIxVO1ninjtgTmbdVMs-iWh1MDa0KDY5EA-fFZToSy8hohoQn0nRRfY" style="margin-left: 1em; margin-right: 1em;"><img alt="" data-original-height="3648" data-original-width="5472" height="213" src="https://blogger.googleusercontent.com/img/a/AVvXsEi_8263ix9YKjofzZLBR-J9BlT9xKd9khsU7QtrVo1-110FDID950OlANkU3ovWb7GSx-X5k0v4PR6A1KjK0XUH1Xq6u4aAOOmN0X4twcg8B2JnEe8WwNbI6u3Us4pCZIxVO1ninjtgTmbdVMs-iWh1MDa0KDY5EA-fFZToSy8hohoQn0nRRfY" width="320" /></a></div><br />最近面试的多家公司当中,有至少一家尝试在行为(Behavior Questions,或 BQ)面试中问我这样的问题。尽管可以去猜这家公司到底更重视迭代速度还是代码质量然后从对应的角度去回答,但正确的答案只有一个,那就是不要回答这个问题。<p></p>
<hr />
<blockquote>
<p>第一原理(英语:First principle),哲学与逻辑名词,是一个最基本的命题或假设,不能被省略或删除,也不能被违反。第一原理相当于是在数学中的公理。最早由亚里士多德提出。——<a href="https://zh.wikipedia.org/wiki/%E7%AC%AC%E4%B8%80%E5%8E%9F%E7%90%86">Wikipedia</a></p>
</blockquote>
<p>对于所有商业公司来说,第一原理永远是商业,不是产品也不是技术。(「商业」和「业务」在英文里都是 business,在这里不针对中文进行区分。)产品的存在是为了服务于商业,技术的运用是为了服务于产品和商业。脱离商业探讨技术上的取舍毫无意义,因此如果被问到先开发新功能还是先重构老代码,必须要从商业的角度进行回答。</p>
<p>开发新功能对产品和商业有什么影响?重构老代码又有什么影响?这是你必须和面试官探讨的问题。面试官可以会提供更多具体的场景信息,也可能让你陈述不同场景下不同的取舍。我可以提供一些不同场景的例子。</p>
<hr />
<p>如果我们是一个创业公司,或者是大公司的内部创业,正在做一个探索新市场的新产品。大家对这个新市场不是特别有信心,希望尽快通过产品验证市场是否存在,不存在的话就换方向或者撤销团队。这时候重构代码的优先级就很低了。或许几个月后发现假象中的新市场并不存在,现在的代码写得再烂,几个月后都随着产品下线而全面删除,谁在乎它烂呢?</p>
<p>这背后是有真实的故事的。Facebook 曾经做过一个针对拉美的应用,叫做 Flash,跟 SnapChat 很类似但这是在 Facebook 主应用之外的一个独立应用。Flash 花了 3 个月就发布了第一个版本,我们团队跟 Flash 团队合作,基于他们的代码进行二次开发,有时候会因为代码质量踩坑,我们非常希望 Flash 团队维持很高的代码质量。但他们的目标就是快速验证拉美是否存在一个低端用户的 SnapChat 市场,所以他们就一直往前冲。又过去了三个月,他们团队进行了一次内部讨论,到底接下来应该继续往前冲,还是先重构一下代码以便将来跑得更快,最后他们选择了继续往前冲。</p>
<p>我们团队虽然不喜欢他们这个决定,但这是一个正确的决定。做决定之后的三个月,他们做了最后的冲锋,把值得做的事情都尝试了一遍,最后的结论就是这个市场不存在或者说不值得做。Flash 应用下线,代码全部删除,团队解散。重构并不会改变这个结果,之后稍微推迟这个结果。从商业的角度来说,重构导致了近期成本,但不会带来长期收益,所以不应该进行重构。</p>
<hr />
<p>如果我们是一个做券商的金融创业公司,那取舍的方向就可能很不一样。我们的首要目标是评估代码出现逻辑错误导致的财务风险。如果我们做的只是现金业务,财务风险是有上限的,也就是所有用户存放在我们这里的现金总量。在最坏的情况下,我们可以把所有用户存放在我们这里的现金都归还给用户,然后可能再承担对应的监管机构罚款和诉讼赔偿,损失的总额是可以进行估算的。如果我们涉及到证券业务,财务风险可能就是不封顶的,因为证券交易可能涉及杠杆和做空。</p>
<p>在进行这种讨论时,需要注重两方面的思维能力:</p>
<p>第一,对商业运作涉及的风险有基本的认识。可以不是很懂面试这家公司的具体商业模式,但要有商业常识和词汇,能够跟面试官进行这方面的沟通,然后才能进行商业上的趋势。</p>
<p>第二,坚持第一原理思维。不要为了控制商业风险就进行重构,而要思考控制商业风险的最佳手段是什么,用这来判断重构是不是合适的技术手段。商业风险有很多非技术的控制手段,例如说买保险。就算是技术手段,也不一定需要是重构,可能提高测试覆盖比例的性价比更高。总之不要为了合理化重构而找一个商业上的理由,那如同拿着锤子找钉子,本末倒置。</p>
<hr />
<p>从这一道面试题可以推导出来一个更加广泛适用的思维模式:不要别人问什么你就答什么。考试(包括 LeetCode 形式的上机考试)往往把人训练为问什么就答什么,因为问题是没有协商空间的。但在面试的时候,一个大活人随时准备好跟你对话,你必须先对问题进行协商。</p>
<p>这是我在做 career coaching 中时常会提醒客户的事情。尤其是面试 senior 或以上职位时,搞清楚面试题的第一原理很重要,你找到了第一原理然后才能围绕第一原理进行解题。至于引出问题的说辞,例如「开发新功能和重构代码之间怎么选」,这其实不是重点。有时候这个说辞可能是个「<a href="https://coolshell.cn/articles/10804.html">X-Y 问题</a>」,但我们期望资深的程序员有侦测和解决 X-Y 问题的意识。</p>Cat Chenhttp://www.blogger.com/profile/17757778716179768922noreply@blogger.com0tag:blogger.com,1999:blog-7005036.post-58037666722637676372022-07-05T16:08:00.001-07:002022-07-05T16:08:29.159-07:00美国互联网公司大多数都是要撤出中国的<div style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEizUVJ3jzTg2VlHJ62En-H14UmPwJJ1oL11huycZhLmDddUSyyy_s5EWesRy0hHhUI_5Zd7aU_3kdWsNmdR8_r4B0vUlei5Tm5DUOABZkobG9FzzVCN6AE3b19iUxHmmdxu7chcfKyCIkG7EM09hpaxQCRswJKaQc_8LE0NI9KdRfCxE8YRnpA/s3556/diego-jimenez-VDOHQLuYwy8-unsplash.jpeg" style="display: block; padding: 1em 0px; text-align: center;"><img alt="" border="0" data-original-height="2371" data-original-width="3556" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEizUVJ3jzTg2VlHJ62En-H14UmPwJJ1oL11huycZhLmDddUSyyy_s5EWesRy0hHhUI_5Zd7aU_3kdWsNmdR8_r4B0vUlei5Tm5DUOABZkobG9FzzVCN6AE3b19iUxHmmdxu7chcfKyCIkG7EM09hpaxQCRswJKaQc_8LE0NI9KdRfCxE8YRnpA/s320/diego-jimenez-VDOHQLuYwy8-unsplash.jpeg" width="320" /></a></div>
<p>很多美国互联网公司在中国的业务做不起来,最终选择从中国撤出,在我看来都能用一句话来解释,「就缺一个李开复」。</p>
<hr />
<p>我曾经非常近距离地观察过某美国互联网公司尝试进入中国的过程,后来也了解了一下其它公司的故事,基本上中国甚至是亚太地区做不下来都是同一个原因,那就是亚太部门得不到充足的自主权(autonomy)。</p>
<p>同一个故事,在不同的公司、不同的部门反复重演。这可能发生在一个经理和一个总监之间,或者是一个总监和一个副总裁之间,反正都是一对直接汇报的上下级。在这对关系当中,上级之所以成为上级,就是因为过去的成功经历。他参与了公司夺取美国甚至是美洲市场的过程,然后带领公司夺取欧洲和非洲(EMEA)市场。他在这个过程中爬到了公司职级的顶端,现在被委以重任,要把久攻不下的亚太市场打下来。</p>
<p>这个级别的人很清楚这件事不是自己亲历亲为就行的,必须搭建一个专业的本地团队。他从本地公司把人才挖过来,或者从本地其它外企挖。这个人是本地业务的专家,他很清楚怎样做才能成功,他能够搭建自己完全本地的团队。一开始这一切都是欣欣向荣的样子,公司有充足的预算来冷启动一个本地团队,公司上下看着这个团队成长的速度都都觉得进度非常满意。</p>
<p>本地团队搭建好了,真正要开始从本地竞争对手手上抢流量了,问题就会慢慢暴露出来。这一对上下级当中,下级是本地业务的专家,对于如何抢占本地市场他有他的方法。但是亚太市场跟欧美相差得实在是太远了,他做的事情他的上级无法理解,于是他要在两者当中选一个:</p>
<p>一个选择是做上级能够理解并且认可的事情,只有上级认可才能获得更多资源,自己才能爬职级。即使这些事情在本地市场完全没效果也没关系,等大家发现自己实际上打不下来这个市场时,自己已经爬上去了,可以跳槽了。</p>
<p>另一个选择是做正确的事情,然后不断被上级质疑。这时候上级在欧美市场的成功经验会成为最大的障碍。上级从自己的经验来看,下级做的事情都是徒劳的,都不会有结果。别说结果了,他甚至不知道如何衡量进度。他不知道如何向上汇报这个本地市场当前的进度,但不能展现良好的进度就很难获取更多的资源,最终影响自己爬职级。这时候他也只有两个选择:</p>
<p>一个选择是信任下级,让他做正确的事情,即使自己不理解。抛弃自己在欧美市场的成功经验,选择相信自己挑人的眼光,豪赌自己选对了人。这其实是非常难做的一个决定。作为一个投资人,你会投资给一个你完全看不懂甚至进度也测量不了的业务吗?这不是一个理性的选择,大多数爬到这个职级的人都不会做这样的选择。</p>
<p>另外一个选择是亲自下场。既然下级做不出自己满意和能够向上汇报的进度,想想自己在欧美市场的成功经验,相信自己能做得不差,干脆自己做好了。这时候这对上下级之间的矛盾就出现了。(理论上他还有第三个选择,那就是换下级,希望下一个下级是正确的选择,能够做出令人满意的进度来。但这本质上就是回到第一步。)</p>
<p>很多美国公司最后就死在这一步。自己所有的成功经验都来自于欧美,把成功经验投入到亚太地区导致失败,但请本地专业人才来做又难以衡量进度和结果。这时候唯一可能能赢的选择是盲目地提供给本地团队相当高的信任程度,让他们有充足的自主权做他们认为正确的事情。有可能本地团队所招到的其实是人渣而不是人才,他们都在浪费公司资源追逐私利,但不愿意赌的话就不可能成功。</p>
<hr />
<p>跟亚太地区比的话,欧美地区是如此地同质化国家之间的区别可以忽略不计。我在美国互联网公司观察过亚太地区不同国家的数据,不同国家之间的文化差别如此之大,一个产品、一种策略在某个国家的数据完全不能用来预测另外一个国家的趋势。</p>
<p>把亚太区拆开,我们只看东亚的数据,中日韩这三个国家的数据没有什么相关性。再把国家拆开,例如说看港澳台地区和内地的数据,依然没什么相关性。亚太市场实在是太难了,每一个国家和地区都不同,都需要进行针对性的产品和运营优化。尝试把整个亚太区看作一体的公司,最终会发现在一个国家适用的产品在另外一个国家不使用。</p>
<p>举一个具体的例子:Facebook 的定位是把现实生活中的好友关系映射到社交媒体上,Facebook 不希望用户把它作为网上匿名交友平台,所以要求用户使用真实姓名。这在 Facebook 拿不下来的日本市场遭受巨大的阻力,为此只能给日本用户开特例,允许他们使用网名。Facebook 对日本用户做调研时发现他们的态度是「我上网时用的就是我网上的名字和身份。就算是现实生活中的朋友,在网上也叫我网名而非真名。我不愿意在 Facebook 上用真名。」</p>
<p>再举一个例子:十多年前百度曾经有一个目标叫做「划洋而治」,意思是占领太平洋这一边的市场但不参与太平洋另一边的竞争。百度进军的第一个国际市场是日本,为日本做了一个全新的贴吧,比当时的中国贴吧要先进,编辑器支持富文本,发贴可以有彩色文字、可以嵌入图片等等。这个产品当然没能占领市场,最后对日本用户进行调研,他们说「我们不需要富文本编辑器,我们习惯使用纯文本编辑器。但是我们必须要有 Emoji,没有 Emoji 我就不用了。」那可是 Emoji 还没标准化为 Unicode 的年代,日本不同运营商使用不同编码表示 Emoji。</p>
<hr />
<p>回到我最初所说的「就缺一个李开复」,我真正想表达的意思是这些美国公司都缺一个同时能够在公司高管圈内混得开且在深入了解中国的人。如果这两项技能分别来自于两个人,那就会出现我前面所说的上下级矛盾。能够在公司高管圈内混得开的上级跟深入了解中国的下级难以达到心灵默契的状态,前者觉得后者没有做出来合理的进度,后者认为前者完全不懂中国市场。最终中国市场做不起来,撤出中国就是理性选择了。</p>Cat Chenhttp://www.blogger.com/profile/17757778716179768922noreply@blogger.com1tag:blogger.com,1999:blog-7005036.post-20402667907960890462022-02-16T23:07:00.004-08:002022-02-16T23:07:51.821-08:00Facebook 工程师文化独特之处<p>我在 Facebook 工作了 7 年,结合 Facebook 之前和之后的其它公司的经验,我觉得 Facebook 的文化有些独特的地方值得分享一下。尽管我说了「独特」,这不代表其它公司绝对不会这样做,有些公司有相似的文化,有时候相似的文化用力程度不一样得到的结果也不一样。</p>
<h2>工程师对产品结果负责任</h2>
<p>我见过很多互联网公司只看工程师产出的技术成果,实际产品结果或商业结果在考评中占的比例很大。Facebook 从高级工程师开始,考评主要看对产品结果的产出,而且有时候非常数据驱动。</p>
<p>考评只看技术的公司,或者是 Facebook 没到高级工程师的级别,只要把技术做极致了就行,产品得不到提醒,公司的商业没有变得更成功,工程师都不用负责任,锅可以甩给产品经理。一款新产品一个新功能做出来,代码可靠从不崩溃,性能优化做足从来不卡顿,界面跟设计师出的图完美吻合,等等等等,工程师就算是优秀的工程师了。如果团队的目标是提升用户留存率,这个技术上完美的新功能发布后留存率不仅仅没有上升还下降了,工程师不需要负责任,考评受罚的只有产品经理。</p>
<p>这种模式的问题是工程师和产品经理目标不一致,更容易产生利益冲突。工程师说「我就是要做这个技术上这么复杂的项目,否则晋升委员会觉得我技术不够不让我晋升」,产品经理说「这个项目消耗你很多时间还不太可能提升产品留存率,同样的时间你可以做好几个有可能提升留存率的功能了。」在极端情况下,这会让双方对立。我在百度时就见过一个例子,只是把工程师换成设计师:设计师说这样设计好,产品经理说那样设计好,双方都只是在利用主观判断说服对方,最后产品经理说「如果做你的设计,KPI 掉了你负责任;如果做我的设计,KPI 掉了我负责任。你想要对 KPI 负责任吗?」设计师立即闭嘴。</p>
<p>考评主要看产品结果的公司,把技术做到极致没用,产品完成指定目标才有用。如果团队的目标还是提升用户留存率,留存率提升了,达到预订目标了,工程师和产品经理(以及其它角色)都能考评过关,远超过预订目标的话还能获得奖励;留存率没有提升,或者是提升不够,工程师和产品经理的考评都会受罚。</p>
<p>工程师不能甩锅给产品经理,两者的目标高度重合。工程师知道,「产品经理指错路」不是借口,产品达不到预订的目标就要受罚。因此 Facebook 的工程师往往非常了解自己在做的产品的指标和数据,他们会花时间去看分析产品数据、阅读用户调研报告,而不是等待产品经理搞明白要做什么了再分配任务。如果工程师坚信产品经理指错路,那就必须自己找到正确的方向然后把结果做出来。没有结果就要受罚,可能是不及格的绩效,可能被炒掉,Facebook 不接受任何解释只看结果。</p>
<p>这种模式可以驱动一个产品团队坐下来好好合作,想方设法利用每一个成员的能力,因为只有这样才能达成目标。如果工程师觉得自己擅长做技术且只喜欢做技术,在这种环境下迟早被炒。所有人都在一条船上,要死一起死。「喜欢做什么」有时候就要给「什么必须要做成」让步,因为做不成船就沉了,大家一起死。</p>
<p>这种模式在自顶而下划分任务的公司不可能成功,只有鼓励自底而上解决问题的公司才能成功。自顶而下的公司,就如同只有船长有权向下发布命令,而且很多时候不需要解释命令的意图,所有船员都觉得自己只要执行命令就好,不需要关注其它人在做什么,不需要理解整艘船是如何运作的。如果船快要撞上冰山了,没有船员会觉得自己有能力和责任阻止船真的撞上去,因为只有船长知道船是怎么运作的,其它人只能无奈的等船沉没。</p>
<p>Facebook 的各级经理都鼓励下属自行定义「什么叫做成功」,如果经理觉得成功定义得合理,就放手让下属自己想办法把事情做成,不需要告诉下属「做什么才能成功」、「如何做才能成功」。如果产品团队主动提出「留存率增长 10% 叫做成功」,只要经理觉得这是对公司合理的贡献,产品团队自己想办法把留存里提升 10%。这样做才能要求产品经理和工程师一起对产品结果负责任,因为「当初是谁定义成功就是留存率增长 10% 的?」</p>
<h2>基础架构被视为内部产品</h2>
<p>有一些公司会强行在内部推广自己的基础架构。基础架构要进行不向下兼容的升级时,所有内部用户必须派人负责升级。据我所知 Google 就是这样做的,例如说 Google 的某个基础架构服务要从 1.0 升级到 2.0,但它们的 API 是互补兼容的,那用到这个服务的所有团队都必须安排工程师负责更新自己的代码,改为调用 2.0 的 API,否则 1.0 一旦下线这个团队就无法使用这个服务。</p>
<p>Facebook 正好是相反的,基础架构在一定程度是也被看作是产品,只是用户是内部用户而已。那作为产品,当然自己要想办法找到自己的用户,自己要想办法把用户留住,自己想办法扩大用户基数,等等等等。一个新的基础架构出来,没有名气,没有成功案例,必须自己想办法推广和招揽用户。潜在的客户可能说,「你这东西看起来不错,但我不是很确定用了是不是真的对完成我的目标有帮助,我也没空学习和使用你的东西」。遇到这种情况,Facebook 的基础架构团队就跟外面的 SaaS 服务提供商一样,要想办法讨好用户,例如说「没关系,我们团队派专人来帮助你们学习和使用我们的东西,觉得好用的话你们接着自己维护,不好用的话也不浪费你们资源」。</p>
<p>当初 React、React Native 等技术出来时也经历同样的过程,在没有名气之前作者就要想办法推广、说服别人来用自己的东西。外面只看到它们发布时的光环,没看到早期推广的各种困难。2016 年我曾经负责调研 React Native 是否适合用户增长部门使用,因为用户增长主要靠做实验,实验周期受客户端升级周期限制,React Native OTA 升级可以突破客户端升级周期限制。在我做完调研后,我给了大大的一个「NO」,因为 Facebook 主要的用户增长来源自 Android 而当时 React Native 的 Android 版性能(加载速度和内存消耗)实在是不行,和内部其它服务的整合也满地是坑。</p>
<p>基础架构不仅仅新品需要推广,升级也需要推广。「你们出个 2.0 跟 1.0 不兼容?我们一个高级工程师需要花三个月时间改写我们的代码才能兼容 2.0?那我们不升级了。你们准备准备暂停 1.0 的服务?没关系,我们自己出机器,我们自己跑一个 1.0 的实例,反正公司内所有组可以访问所有代码。你们准备把 1.0 代码改得面目全非?我们现在立即 fork 你们的代码!之后我们自行维护一个 1.0 的 fork。」</p>
<p>不向下兼容的基础架构升级往往还是要想方设法讨好已有用户。「你们可以继续用我们的 1.0 服务,但 2.0 性能更好哦。你们组的目标不是转化率吗?你们之前也做过实验,证明了转化率和性能高度相关,提升性能就能提升转化率。我们来算一下,提升这么多性能就能提升这么多的转化率,这绝对值得你们一个高级工程师花三个月来做啦。」工程师既要做销售又要做客服,实在不容易。</p>
<p>既然基础架构被视为内部产品,那产品有的压力基础架构也有。新的基础架构如果在一定时间内无法获得充足的用户,那就很可能被要求终止。已经垄断内部市场的基础架构也不能松懈,如果服务不稳定,内部用户不开心,他们会用脚投票。这些用户团队可能会自己做一个仅适用于他们的服务来取代你的基础架构,也可能有其它人做一个跟你竞争的基础架构然后趁机挖你的用户。</p>
<h2>救火比防火更容易获得回报</h2>
<p>Facebook 的文化有优点也有缺点。因为公司非常数据驱动,考评倾向于用数据说话,没有数据等于没有干活,完全不看你有没有苦劳,这导致防御性措施很难吸引人去做,因为成功阻止了坏事发生时你没办法收集数据说你成功阻止了多少件坏事、它们可能有多坏。</p>
<p>喜欢写测试的工程师进入 Facebook 往往会因为多写测试而受到惩罚。「你能证明这些测试实际带来的好处吗?你能量化这些好处吗?不能的话你还不如去做新功能,至少新功能能帮助提升产品指标。」不能量化的事情在 Facebook 是很难吸引人去做的。很多工程师技术非常好,但用数据讲故事的能力不行,这种事情就没办法做。</p>
<p>懂得用数据讲故事的话,好像测试这种不能直接量化的东西,可以找参考数据间接量化。「我们分析了去年的每一次事故并且确定其中 n 次是可以被测试防范的,考虑到今年我们团队多了 50% 的人,如果有测试的话我们今年可以防范了 1.5n 次事故。」如果经理认同这种推理的话,你就可以安心写测试了。(如果真的有人明年回来分析今年的事故的话,你最好能证明其中没有一次事故能被测试防范。」)</p>
<p>这种间接量化还是必须等坏事发生过了才能使用,如果你在坏事尚未发生之前就成功阻止了这一类坏事的发生,你没办法证明你工作的价值。这就导致了一个很不好的现象,我往往用以下的比喻来解释:</p>
<blockquote>
<p>假想你是一个小镇的消防队队长,你四处去检查镇上有没有违反消防规范的地方,有你就叫负责人整改。这个小镇上所有人都狠你,见到你来检查消防就叫你滚,因为你给大家制造麻烦而没人觉得这些麻烦带来了任何好处。小镇的商户联合起来说每年这百分之几的成本都是你造成的,没有你的话就能有更高的利润。</p>
<p>在消防队队长这个职位上,你只能默默地等待火灾发生。一个房子烧着了你还不要着急去救,救了大家就会说「火灾的损失也很有限嘛,不知道花那么高的代价去防火」。你利用这个时间着手准备灭一场大火。等房子烧了一片了,小镇居民在路上奔跑呼喊了,你就去救火吧。(当然这一切的前提都是你真的有能力救火。)之后你要颁布什么新的消防规范,所有居民都会主动配合。</p>
</blockquote>
<p>这就是为什么 Facebook 内部那么多问题处于起火状态,因为不起火就没有救火英雄。</p>Cat Chenhttp://www.blogger.com/profile/17757778716179768922noreply@blogger.com1tag:blogger.com,1999:blog-7005036.post-16284144295452558902021-12-25T22:20:00.000-08:002021-12-25T22:20:10.370-08:00互联网选大厂还是小厂:读了本书还是写了本书<p>这个系列上次写到《<a href="https://chinese.catchen.me/2021/12/big-or-small-company-there-is-a-cloud-in-the-data-center.html">互联网选大厂还是小厂:数据中心里有朵云</a>》,这次简短地写一个小一点的区别。在小公司,你更容易遇到同事说「我读了本书,书上说的这个事情能解决我们遇到的问题。我把书上说的事情投入实际应用后,得到了这么好的效果。」在大公司,你更有可能遇到同事说「我是这本书的作者,有问题欢迎来问我,我也可以定期组织相关的研讨。」</p>
<p>有资格写书的大神门往往聚集在大厂中,小厂很厉害的话或许能抢到一个这样的大神。这些人已经工作很多年了,积累也很深了,有心情就把知识整理出来写本书。科技理论的前沿往往发生在学术界,但科技落地的前沿是在大厂。大厂把学术界早就搞明白的事情拿过来,然后进行从来没人实践过的落地。在这方面,大厂提供了更多的机会去做探索。因为前人没做过,就算理论摆在那里,也不可能有书把这件事情描述清楚。</p>
<p>小厂正好是反面,走的路往往是无数前人已经走过的路,成长过程中遇到的问题很多其它公司早就遇到过,已经有人把解决问题的方法抽象出来写成书。这即是优点又是缺点,要看你站在什么角度去看。从创造影响力的角度来看,这是优点,一个应届生新人,不需要很强的原创性,不需要很好的研究能力,读本书就有机会为公司解决大问题,然后得到升职加薪之类的回报。在大厂,这是不可能的,写书的作者都在这里了,如果有利用这本书上的知识就能创造影响力的机会,早就已经做了,怎么可能轮到你呢?但在大厂里,你可以直接接触到作者哦,你可以跟他一起工作,直接从他身上学习,这是小厂没办法提供的。</p>Cat Chenhttp://www.blogger.com/profile/17757778716179768922noreply@blogger.com0tag:blogger.com,1999:blog-7005036.post-19792459182997227662021-12-04T22:10:00.000-08:002021-12-04T22:10:19.524-08:00互联网选大厂还是小厂:数据中心里有朵云<p>时常会有人来问我,应该加入大厂还是小厂。提问的方式有时候是很具体的,例如说「我有这几家公司的 offer,他们规模不一样,这会对我的职业发展产生什么影响呢?」有时候是很抽象的,就是讨论一下以后找工作到底应该专注于大概是什么规模的公司。我曾经想要写一篇长长的文章,从不同的角度来对比大厂和小厂的区别,最后发现很难完成。现在我决定换一个方法,想到哪里就写到哪里,写完一个具体能进行对比的角度,我就发一篇文章。</p>
<p>在这篇文章里,我想要对比的是大厂和小厂在规模上的本质区别。如果你希望接触到一些特大规模的技术问题,你就只能选择进大厂,因为小厂不会遇到这类问题,也就没有机会让你去接触这类问题。这里有一个很有意思的故事,叫做「数据中心里有朵云」,是个很好的例子。</p>
<p>Facebook 早期跟那个年代的其它公司一样,也是在别人的机房租机位开始的。后来做大了,就有了自建机房的需求。机房需要廉价的土地、电力和制冷,荒漠能够很好的满足这三个需求。Facebook 为了节能,专门设计了利用荒漠低温空气对服务器进行降温的通风系统,一旦发现室外温度比室内空调温度还要低,巨大的风扇就会启动,吸入室外温度更低的空气进来降温。</p>
<p>荒漠通常是零降水的,但有一次傍晚真的下雨了,导致室外气温迅速下降,机房开始从室外吸入冷空气降温。因为刚刚下过雨,室外空气虽然温度低但湿度非常高,进入室内后迅速被服务器温度加热升温。数据中心天花非常高,高温度高湿度的空气自然会往上跑,最终空气中的水分冷却后变成云。据说当时在数据中心的人听着服务器发出霹雳啪啦的火花声音,但没有任何办法去抢救服务器。他们把这次事故描述为「There is a cloud in the data center」。</p>
<p>Facebook 后来学聪明了,不能简单根据室外空气温度来判断是否应该吸入室外空气来降温,还要加上室外空气湿度作为限制条件。这种类型的问题,现在世界上也就只有几家公司能够遇到,因为现在绝大多数互联网公司只需要用别人的云服务,不需要自己拥有物理机器,更不需要自建数据中心。如果你想要接触到这类其它公司都没解决过的前沿问题,那只有大厂能够有足够的规模去触发这类问题,小厂根本触发不了。</p>
<p>类似的问题在 Facebook 其实不少,但也就只有这种规模的公司能遇上。举个例子,Facebook 曾经用 BitTorrent 来部署 web 服务器的更新,因为 web 服务器数量过多(百万级别),向每一台 web 服务器单独上传几个 GB 的编译文件效率太差。更新不是就覆盖几个 PHP 文件吗?为什么是几个 GB 的编译文件?因为 Facebook 那时候使用 HPHP 把 PHP 转译为 C++ 再编译为二进制文件。那为什么 Facebook 要发明 HPHP?因为 PHP 执行效率太差,当时有人根据历史数据进行拟合,发现 18 个月后 Facebook 的用户服务器比(用户总数除以服务器总数)将会下降到 1.0,就算 Facebook 有那么多钱买服务器,世界上也没有那么多机位放服务器,所以必须想办法提升执行效率。</p>
<p>所有这些规模导致的问题都是一环扣一环的,一个发明创造解决了上一个规模导致的问题,同时也带来了机遇给下一个发明创造。这种类型的发明创造只能发生在大厂里,因为小厂触发不了这个级别的规模问题,也没有充足的资源去进行发明创造。</p>Cat Chenhttp://www.blogger.com/profile/17757778716179768922noreply@blogger.com0tag:blogger.com,1999:blog-7005036.post-60485746737748902902021-09-18T23:39:00.000-07:002021-09-18T23:39:27.638-07:00面小厂就比面大厂更容易吗?<p>在我做 career coaching 的过程中,我发现不少人觉得「如果进不了大厂的话,那就降低一点目标,总能进小厂吧」,然后他们发现小厂 offer 也拿不到,于是怀疑自己能力是不是如此之差。大家从升学考试的经验出发,「进不了这一档次的学校就尝试下一档次的学校吧」,把这种逻辑推广到「进不了大厂就尝试小厂吧」看起来很合理,但很不幸人才和公司的匹配并不适用这种模式。</p>
<hr />
<p>我时常用这样一个比喻来描述大厂和小厂招聘的本质区别:</p>
<p>大厂招聘,就如同 Walmart(沃尔玛)去采购两吨苹果用来上架。作为一家超市,Walmart 肯定有一套经过长期优化的采购标准,一个苹果只要满足采购标准 Walmart 就愿意要。Walmart 不在乎这些满足标准的苹果之间有什么个体差异,因为绝大部分都会被顾客买走,一小部分会卖不掉然后烂掉,但这早已在营收当中考虑到,Walmart 并不会亏钱。</p>
<p>小厂招聘,就如同个人消费者去买两个苹果。无论你个人对挑选苹果有什么准则,你肯定会按照你的准则挑选两个最好的。每个人的挑选准则都不太一样,如果有足够多的消费者,绝大部分的苹果都会被买走。</p>
<p>应聘时你就如同是一个苹果。如果你想被 Walmart 采购,你就要满足 Walmart 的采购标准。但因为 Walmart 是一个非常成熟的超市,所以采购标准相对稳定和可预测,反而更容易有针对性地做优化。如果你想被个人消费者挑选,反而很难抓住具体的某一个消费者让他挑你。每一个个人消费者的挑选准则都不太一样,你猜不到当前在挑苹果的这一位消费者是怎么想的,如果他不挑你你也没有办法,再等下一位消费者吧。这个过程比较看缘分。</p>
<hr />
<p>为什么大厂和小厂的招聘模式会截然不同?</p>
<p>小厂招聘往往是针对一个或几个很具体的职位做的。举个例子,公司创业一开始还没找到产品方向,想要招一两个什么都懂但不用太精通的全栈工程师,创始人想要尝试做什么产品,熬个夜第二天就把原型做出来了。尝试了多个产品方向后,公司终于找到了一个靠谱的方向,开始缓慢地有用户积累,顺便招个前端工程师来打磨用户体验。用户增长持续加速,当初熬夜写出来那个单体后端已经支撑不住了,于是要招个有 micro-service 经验的后端工程师来重新设计后端架构,把后端拆成多个服务,并且为将来 sharding 做准备。服务拆了之后,单体数据库通过 replica 还撑了一段时间,终于也要不行了,需要赶紧招一个精通数据库优化的程序员。小厂每时每刻面临的问题都不一样,用固定的标准招通才并不能有效解决问题。</p>
<p>大厂可以用固定的标准招通才,尤其是 Facebook 和 Google 这种全公司统一招聘的。虽然大厂内的每一个团队都好像小厂一样,拥有此时此刻特定的招聘需求,但因为团队数量足够多,所以在统计学上大厂可以无视这些差异按照一个标准来招人。由于大厂的招聘标准如此的稳定和可预测,针对大厂的招聘标准做准备反而更容易。你可以尝试搞清楚大厂的招聘标准,搞清楚后这套标准不会变来变去。一旦你满足了这套标准,你不需要太过关心个体差异。</p>
<p>如果你达不到大厂的标准,要参与小厂的招聘,那马上就会变成跟上述苹果比喻一样看缘分。你不知道具体哪个小厂正好需要你,你要明白到大部分的小厂当前的需求都跟你不匹配,从统计学的角度来说你必须通过大量的小厂去找一个匹配的。这时候你的体验会变得完全不一样,从只需要针对几个大厂做准备变成需要跟很多小厂打交道。</p>
<hr />
<p>从升学考试经验推导而来的期望在这时候完全不成立。如果你高考差几分上不了北大清华,你很可能还是能去非常好的学校。但在找工作时,差一点进不了大厂并不意味着马上有小厂意识到你的价值把你招走,你需要在茫茫大海中搜索跟你匹配的小厂。这时候设置正确的期望很重要,大量小厂跟你擦肩而过,但这不代表你能力有问题,你需要坚持下去。</p>
<p>小厂的招聘流程并不像大厂那样标准化和可预测,那意味着他们扔掉你简历时并不一定是你简历有问题;他们面试时不像大厂一样按套路出题,他们会针对特定的招聘需求来出题,所以你答不上来不一定是你能力有问题;经验有限导致他们的面试并不一定能有效挑选人才,你觉得自己面得很好但他们不要你,同样不一定是你能力有问题。这个过程的负反馈可能比应聘大厂难受很多,你唯一能选择的就是坚持住继续尝试。</p>Cat Chenhttp://www.blogger.com/profile/17757778716179768922noreply@blogger.com0tag:blogger.com,1999:blog-7005036.post-36420300636098343242021-06-27T20:37:00.002-07:002021-06-27T20:37:33.691-07:00如何有理有据地说服其他程序员<p><strong>TL;DR</strong> 当两个相对聪明和胜任的程序员对同一个问题持有截然不同的观点时,往往问题出现在大家对问题的定义不一样。争辩观点对错的往往并不能达成一致,这时候必须先确认大家在讨论的是同一个问题。如果没办法对齐(align)问题,尤其是当双方的优先级(priority)不一致时,争辩问题的解决方案是不可能有结果的。</p>
<h2>人际矛盾</h2>
<p>人际矛盾是行为面试(behavior interview)中常见的考点,作为面试官我自己经常问相关的问题。例如说,我在 Facebook 时就经常问以下两个问题:</p>
<ol>
<li>有没有试过你和你的同事各持不同的观点,而且双方都坚持认为自己是对的,最后你成功说服了对方?</li>
<li>把上面的问题反过来,有没有试过最后你被对方说服了?</li>
</ol>
<p>对于不同级别的程序员,我们对人际矛盾有不同的期望值。初级程序员,就算不能把矛盾解决,至少不能把矛盾激化。高级程序员,需要有能力把矛盾化解掉,从而推动自己的项目前进。更资深的程序员,需要有能力梳理组织当中错中复杂的利益和矛盾关系,优化组织提高效率。</p>
<p>在这篇文章里,我们的关注点是如何化解人际矛盾,这是很多初级程序员需要学会的技能,也是很多高级程序员需要持续提高的技能。在 career coaching 过程中,我时不时也会遇到客户提出这方面的问题,我给出的方案都是类似的,因此我觉得用一篇文章来解释会更好。</p>
<h2>两个聪明人解同一问题……</h2>
<p>我还在 Facebook 时,一个非常资深(总监级别)的华人程序员在一次吃饭时分享了一个观点:如果两个聪明人尝试解决同一个问题,但他们各自得出了不一样的答案,往往并不是其中一个人错了,而是他们拥有的信息不一样,各自根据自己拥有的信息进行推理就会得到不一样的答案。这时候双方必须先互通有无,把自己拥有的信息都摆到台面上来,然后再一起利用眼前的信息解决问题。</p>
<p>这一个观点是我的理论基础。我见过很多人因为意识不到这一点而陷入无效的争辩,甚至激化双方情绪。尤其是强调逻辑思维能力的程序员,很容易推理出这样一个结论:因为一个问题必然有且仅有一个正确的答案,又因为我和你的答案不一样,所以我们两个当中至少有一个人是错的。因为我很确信我自己是正确的,所以你必然是错的。因为你是错的,所以你比我笨。</p>
<p>这个推理过程是正确的,但前提是两个人在解决的是同一个问题。否则就如同考试后对答案发现两个人的答案不一样,争辩半天谁对谁错后才发现这场考试分 A 卷和 B 卷,大家做的根本不是同一道题。很多聪明人都犯过这样的错误,也就是跟别人争辩为什么自己的解法是对的,而忘记了检查大家是不是在做同一道题。</p>
<h2>先定义问题再讨论解法</h2>
<p>对于很多名校毕业或者大厂工作的程序员来说,其实自己和身边的人都已经足够聪明了。如果发现自己在跟别人争辩观点争辩不出来结果时,最好就先审视一下双方是不是在讨论同一个问题,如果不是很确定的话就先把问题定义清楚。当然,复杂的问题不可能方方面面都定义得很清楚,还是会涉及很多假设。如果双方对约束和优先级做了不同的假设,得出不同的结论是很正常的。所以我会建议大家先明确约束和优先级都有哪些。</p>
<h2>对约束进行不同的假设</h2>
<p>两个人从广州出发去北京,A 说往左走,B 说往右走。这时候看起来「从广州去北京」是同一个问题定义,但可能 A 想着的是去机场搭飞机,B 想着的是去火车站坐火车,所以走了不同的方向。如果两个人对于交通工具没什么看法,那澄清一下自己假设使用的交通工具矛盾就化解了。「我以为我们去搭飞机呢,原来你想着的是坐火车。我其实没什么所谓,你想要搭什么我们就一起走吧。」</p>
<p>但如果有更深层次的原因导致他们做出这样的假设呢?例如说,B 没有钱搭飞机,他只能坐火车。这就是一个约束了,加上了这个约束问题定义就不再是「从广州去北京」,而是「用有限的钱从广州去北京」。如果 A 不知道 B 有这个约束,那 A 和 B 在解决的就不是同一个问题。接着 A 和 B 可以就飞机好还是火车好大战五百回合,但矛盾仍然是解决不了。要化解这个矛盾,就必须要让 A 知道 B 有一个钱的约束,之后问题可能多种解法。A 可以说「那我陪你一起坐火车吧」,也可以说「我多出点钱请你搭飞机吧」。但如果 B 觉得没钱搭飞机是一条必须隐藏起来的信息,那这个矛盾就非常难解决了。(你需要一个情商非常高的 A,在获得这一条信息后按照这个约束来解题,同时又不暴露自己掌握了这一条信息。)</p>
<hr />
<p>我在管理某个 iOS 的团队时就遇到过由于对约束的假设不一致带来的矛盾。我们在做一个新的 app,一边是两位很资深的程序员,他们拥有丰富的创业经验,他们用创业的风格迅速的写了一版原型;另一边是几个大厂培养出来的高级程序员,他们接过这个原型,把它打磨成最终能发布的产品。这种一边是特种兵另外一边是正规军的配合,很快就出现了矛盾。其中有一个矛盾我印象深刻,因为这个矛盾明显就是由约束不一致造成的。</p>
<p>正规军觉得特种兵写的原型代码是渣渣,接手后就开始大规模重构,按照 iOS 的思路把东西都封装进不同的 <code>ViewController</code> 里面。特种兵觉得正规军在浪费时间重构,正规军觉得这样做才符合大厂规范。这个矛盾我深挖到底,才发现源头来自于哪里。在特种兵打头阵写原型时,他们负责解决的是最难的技术问题,因为这些问题交下去给别人写可能就解决得不够好。其中一个技术挑战是,这个 app 在云端有三万张照片时,客户端要能够流畅地滚动和浏览照片。这个挑战在原型中是解决了的,但重构的过程中为了进行封装,一个原本 <code>O(n)</code> 的算法就被迫重写为 <code>O(n^2)</code>,于是客户端在有三万张照片时滚动就不再流畅。</p>
<p>这里的隐藏约束就是「有三万张照片时要能流畅滚动」,这是当初特种兵带头立项时定下来的需求之一。但这没有很好地沟通给所有人,所以当正规军接手后就不知道有这个需求。在没有这个约束时,重构一下能提升接下来的工作效率那当然应该重构,于是就重构到不再符合这条约束了。这个矛盾的本质跟前面的例子一样,「显示云端照片」和「显示三万张云端照片且能流畅滚动」不是同一个问题。</p>
<h2>对优先级进行不同的假设</h2>
<p>A 和 B 又要从广州去北京了,这时候 B 已经有钱搭飞机了,但 A 反而想要坐火车了。A 尽管有钱搭飞机,但他最近想要存钱投资,觉得坐火车省钱更好。B 则想要快点到北京,多一点时间在北京玩。这时候看起来问题都还是「从广州去北京」,但两个人的优先级是不一样的。A 要解决的问题是「用尽量少的钱去北京」,B 要解决的问题是「用尽量少的时间去北京」,这自然会导致不一样的解法。</p>
<p>不同优先级带来的矛盾更难解决,因为有时候就算双方都把自己的优先级摆上台面了,但因为双方都只在乎自己的优先级不在乎对方的优先级,问题定义无法达成一致,最终问题不可能被好好解决。如果 A 坚持要省钱,B 坚持要省时间,那就算双方把问题定义清楚了,还是不能一起解决。最终的方案可能是,A 提前出发坐火车,最后跟 B 同一时间达到,这样 A 省了钱同时 B 省了时间,但两个人就不能同行了。</p>
<hr />
<p>这种矛盾在那些前后端明确分工的公司还挺常见的:后端的优先级是自己少干活,最好就是 API 数据结构和数据库直接存在映射关系,复杂的场景就让前端调用多个不同的 API 然后自行组织数据维护状态吧。前端的优先级也是自己少干活,最好前端要做的每一件事情都只需要一个对应 API 调用就能解决,如果多个 API 调用之间需要维持状态那就应该由后端来维持。这种优先级的矛盾,如果双方都隐藏起来不说,再怎么争辩什么是「更好的系统设计」都没有用。如果双方愿意把自己的优先级说清楚,明白到对方的苦处,找个折衷方案,各自承担一部分的负担,那才能把矛盾化解掉。</p>
<h2>总结</h2>
<p>最后总结一下,当你发现你跟别人争辩不出结果时,尤其是你觉得自己是对的但对方好像也有道理时,你最好检查一下你们是不是在解同一道题。往往你会发现你们在解的不是同一道题,这时候你们就应该先讨论问题的定义,把问题定义清楚往往很快能找到一个双方都同意的结论。当然有时候因为双方优先级不一致,无法接受同一个问题的定义,那就是另外一类问题了。有空我可以再写写如何应对优先级不一致造成的矛盾,大家可以订阅/关注我,这次就先写到这里。</p>Cat Chenhttp://www.blogger.com/profile/17757778716179768922noreply@blogger.com0tag:blogger.com,1999:blog-7005036.post-16839659589591177712021-03-07T14:17:00.000-08:002021-03-07T14:17:08.935-08:00SystemsExpert 测评<p><a href="https://chen.cat/algoexpert-referral">SystemsExpert</a> 是 AlgoExpert 新出的一系列针对系统设计面试的内容,包括系统设计常见概念的科普型视频,以及若干模拟面试题和答案。它的内容非常适合从来没有面过系统设计的人,用来了解系统设计是面什么的,以及需要用到哪些知识点。</p>
<p>我接触到不少学生,毕业时通过了算法面试、成为了软件工程师,工作一段时间后跳槽时发现需要面系统设计,但因为从来没接触过所以觉得心虚。其实大家工作一段时间后,都或多或少地在实践中接触到了系统设计,要么是看过已有系统被设计成什么样子,要么是自己设计如何把新功能添加到已有系统上。但是可能因为没有非常刻意地做设计、覆盘、沟通,所以知识不成体系。SystemsExpert 能够帮助大家解决这方面的问题,通过二十多个视频系统地讲述每一个常见的系统设计概念。可能你看完后会发现,很多概念是你已经在工作中用到了的,只是现在变成了一个很具体你能拿来跟面试官交流的东西。</p>
<p>现阶段 SystemExperts 包括以下这些概念的视频:</p>
<ul>
<li>客户端-服务器端模型</li>
<li>网络协议</li>
<li>存储</li>
<li>延时和吞吐量</li>
<li>可用性</li>
<li>缓存</li>
<li>代理</li>
<li>负载均衡</li>
<li>散列化(Hashing)</li>
<li>关系型数据库</li>
<li>键值存储</li>
<li>特种存储(Specialized Storage Paradigms)</li>
<li>复制和分片(Replication And Sharding)</li>
<li>领导选举(Leader Election)</li>
<li>P2P 网络</li>
<li>轮询和流式传输(Polling And Streaming)</li>
<li>配置</li>
<li>限速(Rate Limiting)</li>
<li>日志和监控</li>
<li>发布/订阅模式</li>
<li>MapReduce</li>
<li>安全与 HTTPS</li>
<li>API 设计</li>
</ul>
<p>除此之外,SystemsExpert 还有一个视频提供系统设计面试的概述,简单介绍系统设计面试是考察什么的。每一个视频十分钟上下,不需要花很多时间看,内容很容易理解,但不会讲得很深入。</p>
<p>以上是 SystemsExpert 的优点,但它也不是没有缺点的。缺点是模拟题不可能好像 AlgoExpert 或 LeetCode 那样给你任何的反馈,唯一模拟了的地方是面试官回答你的 clarifying questions。除此之外,模拟题就提供一个白板,让你自己书写你要怎样回答这道题目。至于模拟题的答案,SystemExpert 跟 AlgoExpert 一样提供了高质量的官方答案,包括一个长视频讲解,和对应的图文解释。</p>
<p>如果此前没有系统设计的面试经验,或者觉得自己没有系统学习过,花 $79 买一年的 SystemExpert 是值得的。如果你决定要买了,可以考虑用我的折扣码。点击以下链接打开 AlgoExpert 的网站,然后选择购买 SystemExpert,或是 AlgoExpert + SystemExpert 的套餐,记得在付费前在「promo code」一栏输入「catchen」,然后就可以享受九折。请记得一定要输入「catchen」这个折扣码,单纯使用链接打开是不会自动打折的。</p>
<p><a href="https://chen.cat/algoexpert-referral">https://chen.cat/algoexpert-referral</a></p>
<p>P.S. 如果你不知道 AlgoExpert 是否值得一起买的话,可以去看我之前的《<a href="https://chinese.catchen.me/2020/01/algoexpert-review.html">AlgoExpert 测评</a>》。</p>Cat Chenhttp://www.blogger.com/profile/17757778716179768922noreply@blogger.com0tag:blogger.com,1999:blog-7005036.post-50723830962638894422020-10-29T17:17:00.006-07:002020-10-29T17:17:51.321-07:00AlgoTogether 算法面试小班(信息更新)<p>我们三个月前发布的<a href="https://chinese.catchen.me/2020/08/algotogether-2020q4.html">招生信息</a>仍然有效,现在再补充一些新信息。</p>
<h2>免费学习材料</h2>
<p>我们不提供试听课,但我们免费提供一部分的学习材料,方便大家来了解我们的教学风格。这里面包括两段教学视频,和两道 LeetCode 中等难度题目的解题思路文档。大家可以打开以下链接填写你的基本信息后获得上述免费学习材料:</p>
<p><a href="https://techcareer.typeform.com/to/IKKYnObZ">https://techcareer.typeform.com/to/IKKYnObZ</a></p>
<p>如果看完免费学习材料后觉得我们的教学风格对你胃口的话,请尽快来<a href="https://techcareer.io/programs/algotogether">付费报名</a>哦,因为我们的第四期马上就要开始了。</p>
<h2>教练及助教信息</h2>
<p>我们的教练团队越来越大,并且新增了助教角色,所以必须向大家公布一下我们最新的团队信息。以下是我们详细的教练信息:</p>
<ul>
<li><strong>Wilson</strong>: ACM/ICPC 金牌选手,毕业后回校训练学生并带队参赛。在 FANG 有多年的面试官经验。</li>
<li><strong>Cat</strong>: 从小学开始参加编程竞赛,一直参赛到高中获取保送。在 FANG 面试 ~200 名候选人并培养了 ~50 名新面试官。</li>
<li><strong>Michael</strong>: 在 FANG 拥有丰富经验的面试官。</li>
</ul>
<p>除了一流的教练外,我们还有非常优秀的助教:</p>
<ul>
<li><strong>Leaf</strong>: AlgoTogether 优秀毕业生,成功从非科技行业转入互联网行业。</li>
<li><strong>Tiger</strong>: 在读计算机博士生,曾在 FANG 实习。</li>
</ul>
<h2>学生评价</h2>
<p>以下是我们收到的学生评价,大家可以参考一下,看看我们这个班的强项是否对你有帮助。</p>
<p>jhs:</p>
<blockquote>
<p>第二期学员反馈:1. 题目分类清晰内容丰富,一轮项目结束以后对不同类型的算法题会建立起比较直接的大脑反应。对于准备刚开始刷题或者刷了一段时间题摸不清套路的同学很有帮助,对我个人的提升是解题速度更快了。 2. 比较喜欢 mock interview 的环节,能够最大限度的锻炼你 think loud 的习惯,这对于在北美求职的同学来说,这种习惯可能会让你在面试中更有优势。 3. 教练经验丰富,回答很多公司招聘时的实际问题。而这些问题很多时候只有一定经验的面试官才会有,对于求职者来说非常有帮助。</p>
</blockquote>
<p>Tianqiang:</p>
<blockquote>
<p>第二期学员。之前在知乎上有看到这个项目的介绍,比较感兴趣就报了一期。当时 Leetcode 刷了差不多 200+。项目每周总结的 topic 的题目做下来之后归纳总结,后面碰到类似的很快就有思路。我个人觉得帮助最大的是 Mock Interview,现实生活中很难找到有大厂招人经验的人来 mock,全英文交流以及白板或 codeshare 环境和 leetcode 刷题的感觉完全不同,重点在于与面试官的沟通与交流,习惯后真正面试时也不会过于紧张。推荐刷了很多题但是还不够自信面试的同学尝试这个项目。</p>
</blockquote>
<p>Yuqing:</p>
<blockquote>
<p>我是第二期的学员,算法知识方面的帮助很多同学都有提到,我也不再多说了,除此之外我觉得这个项目最大的亮点是每周的 presentation 和 mock 都可以得到大厂面试官的点评。面试的时候可能题会做但是怎么能清晰的表达出来自己的想法,把自己的算法和面试官讲明白同样也很重要。在这个项目中能知道专业的面试官希望听到什么样的回答,怎么能更有效的和面试官沟通,这一点是很多其他算法班没办法做到的,对我的帮助也非常大。</p>
</blockquote>
<p>FlynnGao:</p>
<blockquote>
<p>作为第二期的学员,说一下感受。首先一个给课程结构一个好评,本身预期以为有算法学习讲课,但实质通过模拟面试的讲解和更注重整个面试流程的各种问题,也可以从另外一个方面更加深入理解算法题目。面试过程中可能遇到的各种问题都有解答,有更加全面理解面试流程的效果。算法题的选择上个人觉得整体中等偏简单一些,当然也是为了更加方便整合整个模拟面试流程。对于学员的各种问题回答和表现的评估,AlgoTogether 的几位老师也表现出色。顶着时差基本全部的课都上完了,我给到 85 分。</p>
</blockquote>
<p>罗凯:</p>
<blockquote>
<p>第一期学员发表一下感想。1.题目归纳的很有条理,基本上跟着项目走下来 leetcode 上大部分题目都有思路了。 2.教练业界经验很丰富,回答了很多关于实际工作的问题,也给出了很多代码风格如何优化的建议。3.同学都很认真,每周一次 mock 的时候听同学讲也收获挺多的。很多题目的想法是 leetcode discussion 里面也没有的。教练也非常有耐心,有一次周二晚上讲一个比较难的题目,一直讨论了快半小时,我这边东边都 11 点多了。4.项目每周大概十个题,要都弄懂还是要花点时间的。做 mock 对真实面试帮助挺大,需要自己思路清晰,代码能跑到把代码讲清楚这之间也是有距离的,需要练习一下。</p>
</blockquote>
<p>SKY:</p>
<blockquote>
<p>作为第一期的学员,还是非常推荐这个项目的。教练很上心水平也很高,有问必答。每周的资料都很有针对性,涵盖面很广。最后一周还有教练自己收集总结的的最新大厂 OA 以及面试题,受益匪浅,在这里说一声谢谢。</p>
</blockquote>
<h2>邮件咨询</h2>
<p>如果你看到这里了都还犹豫不决,那你<a href="mailto:algotogether@techcareer.io?subject=AlgoTogether%20Inquiry">发一封邮件</a>给我进行咨询吧,在邮件里说一下你找工作的计划、你对找工作的决心有多大、你觉得现在的瓶颈在哪里,然后附件发送你的简历(或粘贴你的 LinkedIn 地址)。我会回信跟你分析一下你的现状,给你一些建议。</p>
<p><a href="mailto:algotogether@techcareer.io?subject=AlgoTogether%20Inquiry">algotogether@techcareer.io</a></p>Cat Chenhttp://www.blogger.com/profile/17757778716179768922noreply@blogger.com1tag:blogger.com,1999:blog-7005036.post-69428542497936486732020-08-23T15:49:00.005-07:002020-08-23T15:49:27.564-07:00AlgoTogether 算法面试小班(第四期招生)<p><a href="https://techcareer.io/programs/algotogether?utm_source=blogger&utm_medium=post&utm_campaign=algotogether2020q4">AlgoTogether</a> 上周日宣布<a href="https://chinese.catchen.me/2020/08/algotogether-2020q3.html">第三期招生</a>,不到一周时间就全部报满了!一开始我们开放了 20 个名额,结果到周五晚上就全没了。周五一天之内有 8 个人报了名,把剩下的名额都抢光了。因为还有学生特别想要报名,周五没抢到名额就跟我联系,我周六悄悄加了 5 个名额上去,当天就消耗完了。</p>
<p>考虑到 AlgoTogether 如此热门,仍然有学生想要报名,我决定立即开放第四期的报名和支付。第四期预计 10 月 31 日开始,到明年 1 月 2 日结束。课程格式跟第三期相似,但会在第三期快要结束时根据第三期收到的反馈做轻微调整。如果你原本想要报名第三期但错过了,可以点击以下链接报名参加第四期:</p>
<p><a href="https://techcareer.io/programs/algotogether?utm_source=blogger&utm_medium=post&utm_campaign=algotogether2020q4">https://techcareer.io/programs/algotogether</a></p>
<p>如果你还不是很了解 AlgoTogether,以下是第四期的详细信息如下:</p>
<h2>这门课程适合什么人?</h2>
<p>这门课程适合下定决心要在美国一流科技公司找一份软件工程是(Software Engineer)工作的人。这门课程对于首次找软件工程师工作的人来说价值最大,例如说<strong>应届生</strong>或从其它行业<strong>转专业</strong>过来的人。找<strong>全职</strong>和<strong>实习</strong>都适用。对于<strong>有工作经验的软件工程师</strong>来说,如果最近一年没有做过面试的准备,通过这门课程来准备面试也是一个很好的选择。</p>
<h2>这门课程的价值在哪里?</h2>
<p>我们的核心价值是「授人以渔」,因此课程的价值如下:</p>
<ol>
<li><strong>学会以面试官的角度进行思考。</strong>在面试过程中,公司对你的评价不仅仅在于代码的正确性和性能优化。我们会向你解释面试官是如何评价面试者的,这是你不可能通过 LeetCode 学习和训练的技能,你学会这些技能后就可以用面试官的视角审视自己,找到自己应该提高的地方。</li>
<li><strong>基于面试场景的训练模式。</strong>LeetCode 是训练对着机器做题,面试需要的是对着人解题。我们假设你对前者已经有一定的经验,着重训练后者。后者涉及的技能包括:如果通过提问理清面试官的需求、如何清晰地陈述自己的思路、如何接受面试官的反馈和提示。</li>
<li><strong>给自己下一个破釜沉舟的决心。</strong>如果你想要下定决心,在限定时间内做完指定数量的题目,同时完成指定数量题目讲解和模拟面试,我们会保证你对你自己定下的目标负责任。</li>
<li><strong>理解解题思路而非背诵面经和答案。</strong>一知半解就去大量做题的话会导致背诵面经和答案的现象。我们强调学习和理解解题思路,然后通过适量的练习来学会如何灵活运用。以不变的解题思路去应付不断变化的面试题目。</li>
</ol>
<h2>课程结构是怎么样的?</h2>
<p><strong>整个课程总共 9 个星期的时间。每个星期我们有 3 个天会进行 Zoom 视频会议,另外 4 天进行线下各自的练习和 Slack 上的交流。</strong></p>
<p>每周教练挑一个主题,例如说动态规划(Dynamic Programming),然后围绕这个主题布置 15 道作业题。你有一周的时间来完成这些题目并且提交结果,你至少需要提交 10 道题目的代码和 1 道题目的详细解题思路。所有学生都完成提交后,助教会组织大家投票选出最优秀的解题思路,并且安排题目讲解(Presentation)和模拟面试(Mock Interview)的时间和人选。教练会在整个过程中针对学生表现提供反馈。</p>
<p>我们每周三、周六和周日晚上 7 点(Pacific Time)通过 Zoom 视频会议进行教学、答疑 、题目讲解和模拟面试。不进行 Zoom 视频会议的日子每天同样时间在 Slack 上进行专题讨论。</p>
<h2>课程教练是什么人?</h2>
<p>我们的教练都在一流科技公司拥有多年的工作经验和面试官经验。我们的首席教练曾多次获得 ACM/ICPC 竞赛奖牌,此外还培训其它学生参赛获奖。我们的助教团队都有一流科技公司的工作经验,且近期经历了面试找工作的流程,非常能理解正在经历这个过程的学生需要什么。</p>
<h2>课程使用什么语言教学?</h2>
<p><strong>为了保证跟面试过程和工作环境一致,整个 AlgoTogether 采用全英语沟通。</strong>学生毕业后,可以加入微信校友群,使用中文沟通。</p>
<h2>如何报名和支付?</h2>
<p>请打开以下链接然后选择第四期(10 月 31 日到 1 月 2 日)进行报名和支付:</p>
<p><a href="https://techcareer.io/programs/algotogether?utm_source=blogger&utm_medium=post&utm_campaign=algotogether2020q4">https://techcareer.io/programs/algotogether</a></p>
<p>如果你看到这篇文章时已经错过了第四期,可以打开链接然后选择你需要的那一期进行报名和支付。</p>Cat Chenhttp://www.blogger.com/profile/17757778716179768922noreply@blogger.com0tag:blogger.com,1999:blog-7005036.post-76171487966583193302020-08-16T21:20:00.010-07:002020-09-03T09:23:48.577-07:00AlgoTogether 算法学习小组(第三期招生)<p>我开了一个叫做 AlgoTogether 的算法学习小组,面向在美国寻求软件工程师工作(实习或全职)的人。头两期学习小组的效果不错,第二期学生对项目打分 4.4/5.0 分,对老师打分 4.8/5.0 分(<a href="https://techcareer.typeform.com/report/FeLAv1/41Lya8CYwQMsCQb1">数据</a>),所以我决定继续做第三期学习小组。针对头两期学生的反馈,我对第三期学习小组进行了调整,增加了提供标准化反馈的模拟面试环节,设立了学生共同维护的解题思路库。</p>
<p>我知道仅仅通过 LeetCode 准备算法面试是不够的,因为面试并不以题解的优劣来衡量你。你需要让面试官想要和你共事,这需要说服他你能跟他一起通过编程解决难题。AlgoTogether 是一个有教练指导的学习小组,帮助你训练多方面的面试能力,让你在面试时成为面试官的最佳未来同事。在这个学习小组中,你不仅仅需要解题和编码,你还需要练习沟通你的解题思路、接受模拟面试和倾听来自别人的反馈。我们的教练是 ACM/ICPC 奖牌得主,也曾带队其它学生参赛获奖,此外还是大型科技公司中富有经验的面试官。我们保证你投入到面试准备的每一滴汗水都能有充分的回报。</p>
<p>以下是往期学生对 AlgoTogether 的评价:</p>
<blockquote>
<p><a href="https://www.zhihu.com/people/sky-71-4">SKY</a>:作为第一期的学员,还是非常推荐这个项目的。教练很上心水平也很高,有问必答。每周的资料都很有针对性,涵盖面很广。最后一周还有教练自己收集总结的的最新大厂 OA 以及面试题,受益匪浅,在这里说一声谢谢。</p>
<p> <a href="https://www.zhihu.com/people/luo-kai-37">罗凯</a>:第一期学员发表一下感想。1.题目归纳的很有条理,基本上跟着项目走下来 leetcode 上大部分题目都有思路了。 2.教练业界经验很丰富,回答了很多关于实际工作的问题,也给出了很多代码风格如何优化的建议。3.同学都很认真,每周一次 mock 的时候听同学讲也收获挺多的。很多题目的想法是 leetcode discussion 里面也没有的。教练也非常有耐心,有一次周二晚上讲一个比较难的题目,一直讨论了快半小时,我这边东边都11点多了。4.项目每周大概十个题,要都弄懂还是要花点时间的。做 mock 对真实面试帮助挺大,需要自己思路清晰,代码能跑到把代码讲清楚这之间也是有距离的,需要练习一下。</p>
</blockquote>
<p>为了保证跟面试和工作环境一致,整个 AlgoTogether 采用全英语沟通。以下是 AlgoTogether 的详细信息及报名链接。$800 的 early bird 价格到 8 月 29 日周六结束,之后将变为 $1,000 的正常价格。</p>
<h2><strong>What is this program?</strong></h2>
<p>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 meetings and mock interviews and makes sure that students learn these skills in a way that they can reapply to new problems in real interviews.</p>
<h2>What is the value of this program?</h2>
<ol>
<li><strong>Understand how an interviewer evaluates you.</strong> 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.</li>
<li><strong>Practice like you are in an interview.</strong> 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.</li>
<li><strong>Hold yourself accountable.</strong> Are you willing to commit to finishing a certain amount of problems within a specific time frame? If you can commit, we will hold you accountable and prevent you from slacking.</li>
</ol>
<h2>Who is this program for?</h2>
<p>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, <strong>newly graduated students</strong>. It’s also good for <strong>experienced software engineers</strong> who haven’t done interview preparation for more than a year.</p>
<h2>What is the structure of the program?</h2>
<p><strong>The program is 9-week long. We meet in Zoom 3 times a week and practice together offline through other days.</strong></p>
<p>Each week the coach picks a theme (e.g. dynamic programming) and assigns 10+ problems within this theme. You have one week to work on them. Then all students submit their solutions, share their thinking processes, vote on other students’ sharing, practice presenting solutions, and get mock interviews. The coach will provide support and feedback throughout the whole process.</p>
<p>We meet on Wednesday, Saturday, and Sunday 7 pm (Pacific Time) in Zoom to have lectures, office hours, solution presentations, and mock interviews. We convene on Slack on the other days at 7 pm (Pacific Time) to discuss a focus topic.</p>
<h2>Who is the coach?</h2>
<p>Our coach has many years of experience working and interviewing candidates at well-established tech companies. He is also an <strong>ACM/ICPC algorithm competition medalist</strong> and has coached other students for the competition.</p>
<h2>How much does it cost?</h2>
<p><strong>Early Bird: $800. (Before 8/22 23:59 Pacific Time.)</strong></p>
<p><strong>Regular: $1,000. (After 8/23 00:00 Pacific Time.)</strong></p>
<p>We think it’s the right price to identify committed students and hold everybody accountable. You will be able to pay with a credit card. It’s non-refundable once the payment goes through.</p>
<h2>When does the program start?</h2>
<p><strong>We start on 8/29 Saturday and end on 11/1 Sunday.</strong> We plan to stop accepting signup and payment on 8/28 23:59 Pacific Time.</p>
<h2>How do I sign up and pay?</h2>
<p>Please open the following page and click the “purchase” button to start. We will contact you through email after successful payment:</p>
<p><a href="https://chen.cat/algotogether">https://chen.cat/algotogether</a></p>
<p><strong>Update</strong>: Please open the link from above and check out the latest program’s date and price, and then signup.</p>Cat Chenhttp://www.blogger.com/profile/17757778716179768922noreply@blogger.com2tag:blogger.com,1999:blog-7005036.post-61371053906170965512020-06-29T19:14:00.002-07:002020-06-29T19:14:56.334-07:00《牛油果烤面包》回顾(Part 5 - 中国)<p>我在<a href="https://chinese.catchen.me/2019/10/podcast-one-month-review-part-1.html">这个系列的第一篇文章</a>里面就说到过,当初为了用最低的成本和最短的时间把节目推上线,我们选择了使用 Anchor 这个平台做发布。它如同 YouTube 一样免费和易用,当然也如同 YouTube 一样无法在中国访问。在节目上线后我们很快就意识到这个问题,于是费了很大功夫进行调整。</p>
<hr />
<p><strong>喜马拉雅</strong>是我们的第一个尝试,因为它是国内的平台所以它有义务保证内容可以在国内访问到。在得知 Anchor 在中国无法访问时,我们第一时间去注册了喜马拉雅,并且把我们的节目<a href="https://www.ximalaya.com/keji/29161862/">同步发布到喜马拉雅</a>。之后斯图亚特还去注册了企鹅 FM 和荔枝 FM,但因为手工多平台发布的成本高,但又收获不了多少收听次数,最终放弃了。现在国内的平台就只做喜马拉雅。</p>
<p>提到喜马拉雅就不得不提一下 <strong>Apple Podcasts</strong> 在中国那一套心照不宣的特殊做法。如果你正常地使用 <a href="https://podcastsconnect.apple.com/">Podcasts Connect</a> 提交一个播客的 RSS,默认这个节目不会出现在中国区。Apple 通知你节目通过审核时,这仅仅是意味着节目通过了 RSS 格式审核,但这跟中国一点关系都没有。之后 Apple 会默默地审核你的内容,决定你是否能发布到中国区,整个过程不会有任何的沟通,审核不通过也无法上诉。因为我们一开始使用的是 Anchor,它的 RSS 在中国根本无法访问,自然 Apple 不会把我们的节目发布到中国区。</p>
<p>想要把节目发布到 Apple Podcasts 的中国区,民间有一个办法,那就是用喜马拉雅导出的 RSS。因为喜马拉雅本身在中国,所以 Apple 乐意发布任何来自喜马拉雅的 RSS。为此我们专门在喜马拉雅后台申请了导出 RSS,然后把 RSS 添加到 Podcasts Connect。为了避免听众看到我们的 Apple Podcasts 上有两个节目分不清哪个是哪个,我们把喜马拉雅导出的那个叫做<a href="https://podcasts.apple.com/podcast/id1520154407">牛油果烤面包中国版</a>。</p>
<hr />
<p>考虑到喜马拉雅不是一个泛用型播客平台,为了方便中国的听众使用 Overcast、Pocket Casts、Castro 等泛用型播客应用收听,我们又花钱购买了 <strong>Typlog</strong> 的服务并且把每一集的内容<a href="https://avocadotoast.typlog.io/">同步发布到 Typlog</a>。作为付费平台,Typlog 的灵活性比 Anchor 和喜马拉雅都要好很多,而且大多数时候能在中国访问到,不过也有极少数时候用户会报告不能访问。</p>
<p>Typlog 值得一提的是节目文本内容的编辑灵活性。Anchor 和喜马拉雅对内容都是有限制的:Anchor 不允许多层套叠的 bullet points,如果我们写了多层的就必须手工改为一层的。喜马拉雅连 bullet points 都不支持,只能人手在每一行文本前加个星号表示这几行是个 bullet points。Typlog 完全没有这些限制,而且可以用 Markdown 语法来写,非常符合我的编辑习惯。</p>
<p>在建立好 Typlog 发布后,我们就逐步把各个平台收录的 RSS 地址从 Anchor 改为 Typlog 了。这样做是为了保证中国听众就算是通过 Overcast、Pocket Casts、Castro 等平台搜索到我们节目后能正常收听。可惜最近发生了一系列事情,使得很多这些泛用型播客应用被 Apple 中国区下架了,估计这些应用的中国用户将来只会变得越来越少。</p>
<hr />
<p>最后,为了保证我们的品牌、域名和链接掌握在我们手上,不受任何一个发布平台的干预和限制,我选择了建立我们自己的网站。我们的域名 <a href="https://avocadotoast.live/">avocadotoast.live</a> 最初只是简单地指向到我们的 Anchor 页面或喜马拉雅页面(根据访问来源国家智能指向),但这样子我们得不到任何网站分析数据,不知道有多少来自中国的用户因为指向错误而打不开 Anchor。为此我决定利用静态网站生成器编写我们自己的网站,每一集节目都要能在我们的网站上打开和播放。</p>
<p>网站的主体是我一个周末连夜赶工做出来的,之后花了不少时间打磨和提升自动化程度。现在只要我们发布到 Typlog、喜马拉雅或 Anchor,我们的网站就会自动更新,保证最新一集总能在我们自己的网站上看到。为了兼容中国和非中国用户的体验,我们的网站还设计为从多个音频来源进行播放,如果其中一个音频来源播放失败就自动切换到下一个音频来源。这样就算中国听众无法访问某些音频来源,最终还是会切换到喜马拉雅的音频来源从而保证播放。</p>
<p>制作这个网站的详细技术抉择我觉得可以单独写一个系列,就不在这里详细展开。有兴趣了解这个项目背后细节的话,请通过<a href="https://chen.cat/subscribe-to-blogs">邮件</a>或 <a href="http://feedproxy.google.com/CatChen/Chinese">RSS/Atom</a> 进行订阅。如果我接下来还有时间继续写这个系列的话,我会写一写我们进行远程录音的经验。尽管我之前已经写过<a href="https://chinese.catchen.me/2019/10/podcast-one-month-review-part-2.html">录音剪辑</a>经验,但现在大家不能面对面录音了所以我们又摸索了一套新方法来做远程录音。这个话题我们下次再聊。</p>Cat Chenhttp://www.blogger.com/profile/17757778716179768922noreply@blogger.com0tag:blogger.com,1999:blog-7005036.post-1039068452070237832020-06-28T23:39:00.000-07:002020-06-28T23:39:06.614-07:00《牛油果烤面包》回顾(Part 4 - 选题)<p>之前写了 3 篇《牛油果烤面包》的满月回顾(<a href="https://chinese.catchen.me/2019/10/podcast-one-month-review-part-1.html">1</a> <a href="https://chinese.catchen.me/2019/11/podcast-one-month-review-part-2.html">2</a> <a href="https://chinese.catchen.me/2019/10/podcast-one-month-review-part-3.html">3</a>),现在准备继续写下去。当然满月早就过了,甚至连半年都不止了,然而又没到一周年,所以文章标题就不再提及时间啦。上次我们说到了我们从个大平台都能获取到什么样的收听数据,那这次我们就说一说我们是怎么做选题的吧。</p>
<p>要说选题,那必须先说一下我们的受众定位。这在我们几位主播之间其实没有非常统一的观点,我们有人更在乎在美国的中文听众,也有人更在乎来自中国的听众。来自中国的听众又可以细分为更多的类型:有些是已经接触到比较多英文信息和海外信息的,那就比较类似在美国的中文听众;有些技术研究得很深入但只熟悉中文的术语,遇到英文的就比较难听进去;有些技术了解不深,希望多听听浅显易懂的科普内容,对深入话题不感兴趣。因为我们在这方面没有统一,所以选题时也没有刻意针对哪一种中文听众类型来做,每一期的受众会略微不一样。</p>
<hr />
<p><strong>我们选题一般会有两种风格:一种是侧重专业性的,一种是侧重娱乐性的。前者的目标是让听众爽,后者更多是主播和嘉宾自己爽。</strong></p>
<p>我们先说说前者吧,我们在做这类选题时一般优先从我们身边的朋友里找专业人士,想想他们熟悉的专业内容如何能够塑造成相对科普一点的话题,保证讲述过程中的趣味性和故事性。然后我们就会跟这些朋友说,希望找他们来录一期节目,问一下他们是否愿意,然后再协商什么能说什么不能说。因为他们工作领域和公司的限制,可能有些事情他们能多说,有些事情他们不能说,这些都是要提前说好的。</p>
<p>因为我们强调自己是一个科技性的节目,所以我们找的很多专业人士都是自己的同行,也就是科技行业的从业人员。<a href="https://avocadotoast.live/episodes/23">推荐系统</a>、<a href="https://avocadotoast.live/episodes/33">人工智能硬件加速</a>、<a href="https://avocadotoast.live/episodes/34">人工智能在传统行业的落地</a>、<a href="https://avocadotoast.live/episodes/38">搜索引擎</a>、<a href="https://avocadotoast.live/episodes/39">计算机视觉</a>、<a href="https://avocadotoast.live/episodes/7">激光雷达</a>都属于这种类型的节目。当然我们身边也有不少朋友在非科技领域有非常深入的见解,我们也会请他们来介绍他们的专业。<a href="https://avocadotoast.live/episodes/13">私人飞行执照</a>、<a href="https://avocadotoast.live/episodes/19">美国公务员</a>、<a href="https://avocadotoast.live/episodes/28">美国个税</a>、<a href="https://avocadotoast.live/episodes/32">疾控中心</a>、<a href="https://avocadotoast.live/episodes/42">Airbnb 短租房东</a>、<a href="https://avocadotoast.live/episodes/36">桌游</a>、<a href="https://avocadotoast.live/episodes/40">电子游戏</a>都属于这种类型的节目,因为作为主播我们自己也不是很懂,所以录制节目的过程也是我们自己学习的过程。</p>
<p>至于娱乐性节目,主要就是我们自己想要说,说完了还想要分享给大家听,所以我们就录了。这方面的选题是非常随性的,总之有主播想要录,我们有档期就录。这类型的节目包括<a href="https://avocadotoast.live/episodes/41">互联网信息获取</a>、<a href="https://avocadotoast.live/episodes/37">在家办公</a>、<a href="https://avocadotoast.live/episodes/18">台北旅游</a>、<a href="https://avocadotoast.live/episodes/29">托斯卡纳旅游</a>、<a href="https://avocadotoast.live/episodes/27">伊朗旅游</a>。此外还有一些选题是介于专业性和娱乐性之间的,我们做不到非常有深度的专业内容,但我们觉得嘉宾有独到的经历和见解非常值得分享,于是我们就会去录:<a href="https://avocadotoast.live/episodes/31">中美互联网公司异同</a>、<a href="https://avocadotoast.live/episodes/26">美国裁员</a>、<a href="https://avocadotoast.live/episodes/25">冻卵</a>、<a href="https://avocadotoast.live/episodes/21">硅谷春晚</a>。</p>
<p>最后还有一类特殊的选题,叫做蹭热点。我们还是希望我们的节目能够获得快速增长的,而蹭热点有时候是个很有效的办法,虽然不一定能成功。(这是我在知乎回答问题的经验啦,回顾了一下得票最多的答案,有一部分是蹭热点而来的。)这部分选题的时效性非常强,某个事件发生后必须尽快完成录音、制作和发布。我们的第一期节目<a href="https://avocadotoast.live/episodes/1">iPhone 11 发布会</a>就是这种类型,后面还有<a href="https://avocadotoast.live/episodes/9">BlizzCon</a>、CES(<a href="https://avocadotoast.live/episodes/20">1</a> <a href="https://avocadotoast.live/episodes/22">2</a>)以及下周马上要发布的 WWDC。</p>
<hr />
<p>回顾我们的选题,从播放量来看最可靠的是专业性题材的节目。这些节目我们可以持续做,只要我们能够一直找到新的专业话题,在一定程度上来说我们可以「量产」这种类型的节目,而且每一集都会有比较高的播放次数,但并不是最高的播放次数。那最高的播放次数来自什么呢?这需要的不是专业见解,而是情绪上的刺激。要让听众一看到题目就能产生情绪上的反应,以下是一些例子:</p>
<ul>
<li><a href="https://avocadotoast.live/episodes/26">美国裁员</a>这一集刺激的是大家恐惧的情绪。在美国拿着工作签证的人都知道,一旦被裁员那就有可能被迫离开美国,之前那么多年投资在留学和工作上,就差那么一点拿不到绿卡是一个巨大的损失。这一集的播放次数非常高。</li>
<li><a href="https://avocadotoast.live/episodes/31">中美互联网公司差异</a>成功的刺激到了中国程序员对行业内卷的负面情绪,大家都想了解一下其它国家的互联网行业从业体验是怎样的,所以播放次数也很好。</li>
<li><a href="https://avocadotoast.live/episodes/38">搜索引擎</a>通过在标题中提及百度的没落而刺激到了中国网民对百度的厌恶情绪,很多人不仅仅收听了这一集的节目,还在评论种表达了对百度的不满。</li>
</ul>
<p>有意思的是,人类确实更容易受负面情绪的驱动,在收听播客时也如此。这是现代市场推广所才去的一个常见手段,当然最终会导致民众两极分化,这又是一个新的问题。我们在选题时不会刻意的去选择挑起负面情绪的话题,但从结果上来看确实这样的话题效果更好。</p>Cat Chenhttp://www.blogger.com/profile/17757778716179768922noreply@blogger.com0tag:blogger.com,1999:blog-7005036.post-30016979565107356772020-06-22T00:22:00.004-07:002020-06-22T00:23:13.658-07:00为什么美国网银转账那么慢:ACH 详解<p>我刚刚来到美国时就发现美国网银的转账速度很慢,往往中国网银一天甚至实时能完成的转账,在美国需要三天甚至更久,不过后来习惯了也就没再仔细思考这件事情。我最近加入了 <a href="https://robinhood.com/">Robinhood</a> 的 Funding 团队,我们团队负责用户资金转进、转出 Robinhood 账户,为此我好好学习了一下美国网银转账背后所使用的 ACH Transfer 机制。现在终于可以来向大家解释一下为什么美国银行的转账速度那么慢了。</p>
<p>我这篇文章主要参考了 Gusto 关于 ACH 的系列博客(<a href="https://engineering.gusto.com/how-ach-works-a-developer-perspective-part-1/">1</a> <a href="https://engineering.gusto.com/how-ach-works-a-developer-perspective-part-2/">2</a> <a href="https://engineering.gusto.com/how-ach-works-a-developer-perspective-part-3/">3</a> <a href="https://engineering.gusto.com/how-ach-works-a-developer-perspective-part-4/">4</a> <a href="https://engineering.gusto.com/how-ach-works-a-developer-perspective-part-5/">5</a>),大家喜欢看原文的话可以去看看。在开始之前,我们来解释一下下文要用到的概念:</p>
<ul>
<li><strong>ACH:Automatic Clearing House。</strong>这是一套自动清算系统,是金融机构之间使用计算机进行自动清算的协议。</li>
<li><strong>NACHA:National Automated Clearing House Association。</strong>这是一个制订 ACH 标准协议的机构,并且负责管理 ACH 网络。</li>
<li><strong>Federal Reserve。</strong>美联储,实际运营 ACH 网络的机构。</li>
<li><strong>ODFI:Originating Depository Financial Institution。</strong>这是代表发起 ACH 操作一方的发起方银行。</li>
<li><strong>RDFI:Receiving Depository Financial Institution。</strong>这是代表 ACH 操作接受一方的接受方银行。</li>
</ul>
<hr />
<p>在美国的金融机构之间进行金额不是很大的转账,往往使用的都是 ACH。一个 ACH 操作中通常会涉及到 5 个参与者,他们分别是:发起方、发起方银行(ODFI)、美联储(Federal Reserve)、接受方银行(RDFI)、接受方。举个例子,Alice 是 Bob 的雇主,Alice 要发工资给 Bob,那么 Alice 就是发起方,Alice 的银行就是发起方银行,Bob 的银行就是接受方银行,Bob 就是接受方。</p>
<p>假设 Alice 发工资给 Bob 的那天算作第 1 天,Alice 的银行会要求 Alice 在特定时间(假设是 7:00 PM)之前发起这个操作,超过这个时间就算是下一个工作日的操作了。为此 Alice 必须在第一天的 7:00 PM 前发起一笔 ACH 操作转出 Bob 的工资,与此同时 Alice 的银行会在 Alic 的账户余额上减去 Bob 的工资。这是第一步。</p>
<p>Alice 的银行(也就是发起方银行)会在午夜把记录了这一笔 ACH 操作的 ACH 文件发给美联储,然后美联储把这个 ACH 文件转发给 Bob 的银行(也就是接受方银行)。这是第二步。</p>
<p>Bob 的银行会在第 2 天清早(假设是 5:00 AM)处理这一笔 ACH 操作,把工资加到 Bob 的账户余额上。这是第三步。如果一切顺利的话,事情到这里也就结束了,工资成功从 Alice 的账户转到 Bob 的账户上。但是如果发生异常的话,那就还有两步要走。</p>
<p>什么情况可能导致异常呢?Bob 可能填错账户号码了,Bob 可能把 checking account 错误标记为 savings account 了,Bob 还可能注销账户了,有各种原因可能导致异常。这些都叫做 ACH Return,会导致一笔 ACH 操作失败。值得注意的是,跟中国的网银不一样,Alice 作为发起方不仅仅可以转钱给 Bob,只要 Bob 签署了正确的 ACH 授权,Alice 还可以要求从 Bob 那里收钱回来。这时候 Bob 余额不足或者是 Bob 撤销授权等等的情况,都会导致异常。</p>
<p>在第三步里,Bob 的银行在第 2 天清早会处理这一笔 ACH 操作,如果它发现有异常的话它可以把 ACH Return 发给美联储,但这个 ACH Return 最晚可以在第 3 天结束之前发出。按照最坏的情况考虑,ACH Return 会在第 3 天和第 4 天之间的午夜由 Bob 的银行经过美联储发送给 Alice 的银行。这是第四步。</p>
<p>Alice 的银行会在第 4 天清早(再次假设是 5:00 AM)处理这一笔 ACH Return,然后把 Bob 的工资归还到 Alice 的账户余额上。这是第五步。整个过程耗费了 3 天的时间,准确来说是 3 个银行工作日(bank day)的时间。</p>
<p>一般来说,Alice 发工资给 Bob,如果 Bob 的银行没发生异常,Bob 是不会提出异议的,毕竟钱是多了而不是少了。但如果 Alice 发起的 ACH 操作是从 Bob 那里收钱,Bob 的银行没有遇到异常不代表 Bob 不会提出异议。跟 Bob 的银行不一样,Bob 本人有 60 天的时间提出异议(dispute),然后就会导致 ACH Return。Alice 的银行收到 ACH Return 之后,就要把已经给了 Alice 的钱拿回去。如果 Alice 的账户已经没有那么多钱了,Alice 的银行就赔钱了。</p>
<p>因此 Alice 的银行进行 ACH 操作向 Bob 收钱后,收回来的钱该不该给 Alice,是全额个 Alice 还是部分给 Alice,具体什么时候给,这些都是开放性问题,不同的银行会做不同的策略。有些银行会等到第 4 天才把钱给 Alice,因为第 3 天结束后 Bob 的银行就不再可能因为异常(如 Bob 账户余额不足)而返回 ACH Return 了。</p>
<p>有些银行会因为 Alice 长期的良好表现而信任 Alice,在第 2 天就提前把全额或部分给 Alice。如果金额比较大,Alice 的银行担心 Bob 在第 3 天结束后提出异议,那 Alice 的银行还可能要进行更多的沟通协调,向 Bob 的银行确认这真的是 Bob 授权了的,那就会拖更长的时间。</p>
<hr />
<p>希望上述信息能够解释清楚为什么美国的网银转账这么慢。</p>
<p>如果大家对 ACH 文件的格式感兴趣的话,可以读一读<a href="https://engineering.gusto.com/how-ach-works-a-developer-perspective-part-4/">这篇文章</a>,里面做了简单介绍。实际的文件格式,请以 NACHA 官方手册为准,那是一本几厘米厚的砖头书籍,我们 Funding 团队就有一本今年最新版本的。</p>Cat Chenhttp://www.blogger.com/profile/17757778716179768922noreply@blogger.com0tag:blogger.com,1999:blog-7005036.post-91078154774357728312020-04-30T11:39:00.002-07:002020-04-30T11:39:52.978-07:00Real Dev 测评<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="463" height="381" viewBox="0 0 463 381">
<defs>
<path id="a" d="M32.407 16.344c0 8.708-7.08 15.77-15.818 15.77C7.854 32.114.77 25.052.77 16.344.77 7.64 7.854.578 16.589.578"/>
<path id="b" d="M13.658.673c-.868 0-1.689.4-2.826 1.453-.882.825-5.642 2.542-8.356 2.542-2.707 0-2.35 2.145-2.35 2.145h19.752V3.188c0-1.787-1.303-2.515-1.303-2.515h-4.917z"/>
<linearGradient id="c" x1="-.003%" x2="100.004%" y1="50.017%" y2="50.017%">
<stop offset="0%" stop-color="#3D342A"/>
<stop offset="100%" stop-color="#281C17"/>
</linearGradient>
<path id="e" d="M11.18 2.367c-.847.32-1.473 1.108-2.13 2.797-.513 1.31-1.443 4.332-6.963 4.165C-.722 9.25.613 12.733.613 12.733l5.415-.035 13.804-5.265-1.534-4.36C17.545.928 15.966.546 15.966.546s-3.938 1.505-4.785 1.821"/>
<linearGradient id="f" x1="-1.814%" x2="93.375%" y1="63.407%" y2="40.757%">
<stop offset="0%" stop-color="#3D342A"/>
<stop offset="100%" stop-color="#281C17"/>
</linearGradient>
<path id="h" d="M1.64.449S.57 1.174.57 2.929v3.604h16.2s.3-2.127-1.93-2.127c-2.225 0-6.127-1.706-6.853-2.514C7.055.843 6.387.449 5.674.449H1.64z"/>
<linearGradient id="i" x1="100%" x2=".017%" y1="50.003%" y2="50.003%">
<stop offset="0%" stop-color="#3D342A"/>
<stop offset="100%" stop-color="#281C17"/>
</linearGradient>
<path id="k" d="M1.676.65S.632 1.376.632 3.14v3.593H16.47s.289-2.127-1.887-2.127c-2.17 0-5.991-1.695-6.698-2.514C6.97 1.047 6.32.649 5.624.649H1.676z"/>
<linearGradient id="l" x1="99.995%" x2="-.009%" y1="50.03%" y2="50.03%">
<stop offset="0%" stop-color="#3D342A"/>
<stop offset="100%" stop-color="#281C17"/>
</linearGradient>
<path id="n" d="M.635.212h58.659V4.77H.635z"/>
<path id="p" d="M3.826.578C2.036.765.171.955.171.955l1.292 15.66 4.7-.013s.439-1.914-2.142-2.36c-2-.343.991-3.896 1.747-4.885.967-1.289 1.192-1.741 1.116-2.628l-.413-5.077S5.734.561 4.172.561c-.112 0-.224.003-.346.017"/>
<linearGradient id="q" x1="52.774%" x2="49.301%" y1="100.922%" y2=".463%">
<stop offset="0%" stop-color="#3D342A"/>
<stop offset="100%" stop-color="#281C17"/>
</linearGradient>
<path id="s" d="M1.58.27S.226 1.046.226 2.953V6.84h20.457s.368-2.298-2.436-2.298-7.733-1.837-8.653-2.72C8.42.69 7.575.27 6.677.27H1.58z"/>
<linearGradient id="t" x1="100.001%" x2=".003%" y1="49.979%" y2="49.979%">
<stop offset="0%" stop-color="#3D342A"/>
<stop offset="100%" stop-color="#281C17"/>
</linearGradient>
<path id="v" d="M7.994.582s-2.56 3.31-3.102 4.02c-.554.719-.719 1.665-.506 3.34.165 1.299 1.272 4.38-2.732 6.172-1.675.751-.88 2.195-.88 2.195l3.85-.026L14.21 3.847 11.023 1.29C10.187.616 9.38.46 8.812.46c-.503 0-.818.123-.818.123"/>
<linearGradient id="w" x1="12.281%" x2="72.32%" y1="98.302%" y2="7.714%">
<stop offset="0%" stop-color="#3D342A"/>
<stop offset="100%" stop-color="#281C17"/>
</linearGradient>
<path id="y" d="M13.614.055c-.856 0-1.67.404-2.797 1.48-.878.835-5.594 2.584-8.276 2.584C-.15 4.12.204 6.3.204 6.3h19.577V2.61c0-1.814-1.289-2.555-1.289-2.555h-4.878z"/>
<linearGradient id="z" x1=".004%" x2="99.996%" y1="50.016%" y2="50.016%">
<stop offset="0%" stop-color="#3D342A"/>
<stop offset="100%" stop-color="#281C17"/>
</linearGradient>
</defs>
<g fill="none" fill-rule="evenodd">
<path fill="#004DFF" d="M216.978 66.227s-2.732.247-9.164 5.873c-4.044 3.535-9.072 8.55-14.147 12.039-13.147 9.033-28.432 15.348-28.432 15.348s-.44 2.756 1.24 4.845c5.59-.871 40.096-13.635 49.465-21.253 9.372-7.623 8.423-18.533 1.038-16.852"/>
<path fill="#FFAD97" d="M174.398 219.368v5.89h-4.892v-6.212z"/>
<g transform="translate(157 224)">
<mask id="d" fill="#fff">
<use xlink:href="#b"/>
</mask>
<path fill="url(#c)" d="M13.658.673c-.868 0-1.689.4-2.826 1.453-.882.825-5.642 2.542-8.356 2.542-2.707 0-2.35 2.145-2.35 2.145h19.752V3.188c0-1.787-1.303-2.515-1.303-2.515h-4.917z" mask="url(#d)"/>
</g>
<path fill="#FFAD97" d="M224.187 215l1.813 5.374-4.09 1.626-1.91-5.66z"/>
<g transform="translate(210 219)">
<mask id="g" fill="#fff">
<use xlink:href="#e"/>
</mask>
<path fill="url(#f)" d="M11.18 2.367c-.847.32-1.473 1.108-2.13 2.797-.513 1.31-1.443 4.332-6.963 4.165C-.722 9.25.613 12.733.613 12.733l5.415-.035 13.804-5.265-1.534-4.36C17.545.928 15.966.546 15.966.546s-3.938 1.505-4.785 1.821" mask="url(#g)"/>
</g>
<path fill="#FFAD97" d="M202.566 58.248c-2.772-8.386-.651-17.097-.651-17.097 1.391.317 4.581.046 6.874.139 2.3.096 2.436 3.352 2.643 4.894.208 1.536 1.524 4.899 2.35 5.116.325-1.763.096-7.35 2.732-5.48 2.63 1.867 1.828 2.788 1.81 6.066-.018 3.27 3.82 2.538 3.82 2.538-.626 7.708-.562 7.84-.558 10.246-.004 3.849-11.377 14.037-11.109 3.545.079-3.216 1.631-6.933 1.631-6.933-5.193 3.538-7.99 1.535-9.542-3.034"/>
<path fill="#444199" d="M201.916 41.152s-4.403-.639-5.05-3.084c-.637-2.45 5.444-2.667 10.3-4.877 4.858-2.217 12.132-5.12 15.913 1.27 0 0 4.016 1.997 3.677 6.356-.337 4.366-3.387 7.76-4.614 13.609 0 0-3.834.731-3.816-2.539.018-3.28.815-4.198-1.81-6.069-2.636-1.867-2.41 3.72-2.736 5.48-.823-.214-2.139-3.577-2.35-5.112-.204-1.542-.343-4.802-2.64-4.898-2.296-.093-5.482.178-6.874-.136"/>
<path fill="#444199" d="M212.828 49.622s.24 7.643-2.951 8.368c-3.194.732-7.371-1.007-7.404 1.585-.028 2.585 3.638 5.066 6.617 4.02 2.98-1.05 4.675-2.72 4.85-4.802.186-2.095-.075-9.896-.075-9.896l-1.037.725z"/>
<path fill="#3B3687" d="M180.394 131.106s-9.22 30.88-11.127 45.029c-1.914 14.14-2.182 45.05-2.182 45.05s4.088 2.313 8.724 0c0 0 3.129-41.787 14.985-58.317 11.857-16.518 8.38-24.683 8.38-24.683l-18.78-7.08z"/>
<path fill="#FFAD97" d="M163.702 102.66c.533 1.364-.733 3.135-2.837 3.956-2.103.825-4.241.382-4.774-.982-.533-1.364.733-3.134 2.836-3.955 2.103-.822 4.245-.382 4.775.981"/>
<path fill="#444199" d="M212.007 132.06s-8.949 24.759.862 50.874c9.81 26.11 13.08 34.818 13.08 34.818s-2.962 3.063-7.461 1.939c-4.085-6.12-29.057-46.821-33.685-66.681-3.272-14.145-4.066-21.356-4.066-21.356l31.27.407z"/>
<path fill="#7DADFF" d="M199.38 76.706s6.81-10.61 15.67-11.695c8.855-1.09 12.124 6.526 9.81 17.818-2.314 11.288-12.808 35.975-11.173 49.648 0 0-16.964 9.586-34.75 0 0 0 11.652-41.148 20.443-55.77"/>
<path fill="#002A8A" d="M.257 120.694h187.957V.669H.257z"/>
<path fill="#26CBDD" d="M.257 15.52h187.957V.673H.257z"/>
<path fill="#FFF" d="M3.784 12.757h8.934V3.832H3.784zM30.311 12.757h114.204V3.832H30.311z"/>
<path fill="#000" d="M9.314 10.584L6.424 8.26l2.89-2.346z"/>
<path fill="#FFF" d="M16.563 12.757h8.941V3.832h-8.941z"/>
<path fill="#000" d="M19.96 10.584l2.901-2.324-2.9-2.346z"/>
<path fill="#004DFF" d="M159.385 8.183a3.847 3.847 0 0 1-3.849 3.845 3.84 3.84 0 0 1-3.837-3.845 3.833 3.833 0 0 1 3.837-3.845 3.84 3.84 0 0 1 3.849 3.845"/>
<path fill="#AA27D9" d="M171.369 8.183a3.844 3.844 0 0 1-7.686 0 3.837 3.837 0 0 1 3.844-3.845 3.838 3.838 0 0 1 3.842 3.845"/>
<path fill="#F2F5FE" d="M183.36 8.183a3.844 3.844 0 0 1-3.835 3.845 3.85 3.85 0 0 1-3.852-3.845 3.843 3.843 0 0 1 3.852-3.845 3.837 3.837 0 0 1 3.834 3.845"/>
<path fill="#E6E6E6" d="M20.232 113.546h2.125v-91.05h-2.125zM11.72 28.833h3.52v-3.52h-3.52zM11.72 36.537h3.52v-3.516h-3.52zM11.72 44.248h3.52v-3.523h-3.52zM11.72 51.963h3.52v-3.527h-3.52zM11.72 59.657h3.52v-3.503h-3.52zM11.72 67.368h3.52v-3.517h-3.52zM11.72 75.086h3.52v-3.523h-3.52zM11.72 82.79h3.52v-3.516h-3.52zM11.72 90.498h3.52v-3.516h-3.52zM11.72 98.213h3.52v-3.53h-3.52zM11.72 105.91h3.52v-3.51h-3.52z"/>
<path fill="#FFF" d="M30.924 24.365l-2.56 2.72a.507.507 0 0 0 0 .679l2.482 2.556a.5.5 0 0 0 .69.01.49.49 0 0 0 .154-.356.491.491 0 0 0-.14-.347l-2.142-2.213c.49-.514 2.239-2.381 2.239-2.381a.499.499 0 0 0 .143-.336.516.516 0 0 0-.172-.364.49.49 0 0 0-.694.032M43.02 24.334a.542.542 0 0 0-.154.364c0 .118.05.243.129.332 0 0 1.741 1.87 2.235 2.381-.494.507-2.135 2.217-2.135 2.217a.484.484 0 0 0-.133.346c0 .125.04.254.15.357a.51.51 0 0 0 .698-.01l2.475-2.557a.503.503 0 0 0 0-.678l-2.564-2.72c-.176-.207-.498-.21-.701-.032M39.045 24.613l-4.757 4.844c-.2.19-.2.504.01.69.19.192.501.192.691-.015l4.76-4.83a.476.476 0 0 0-.007-.69.486.486 0 0 0-.697 0M30.924 107.78l-2.56 2.724a.512.512 0 0 0 0 .682l2.482 2.552c.19.19.493.2.69.011a.453.453 0 0 0 .154-.35.491.491 0 0 0-.14-.346s-1.652-1.703-2.142-2.21c.49-.517 2.239-2.385 2.239-2.385.09-.1.143-.217.143-.339a.508.508 0 0 0-.172-.36.496.496 0 0 0-.694.021M43.02 107.757a.533.533 0 0 0-.154.36c0 .126.05.24.129.34 0 0 1.741 1.87 2.235 2.385-.494.507-2.135 2.21-2.135 2.21a.49.49 0 0 0-.133.346c0 .128.04.257.15.353a.503.503 0 0 0 .698-.014l2.475-2.553c.175-.178.175-.481 0-.681l-2.564-2.724c-.176-.197-.498-.197-.701-.022M39.045 108.038l-4.757 4.834c-.2.189-.2.517.01.703.19.19.501.19.691-.01l4.76-4.831a.493.493 0 0 0-.007-.703.487.487 0 0 0-.697.007"/>
<path fill="#E29616" d="M24.585 35.605h16.052v-3.17H24.585z"/>
<path fill="#F19B0F" d="M24.585 77.732H42.63v-3.174H24.585zM24.585 105.896h12.286v-3.185H24.585z"/>
<path fill="#17BB99" d="M28.552 42.645h25.647V39.48H28.552z"/>
<path fill="#FBDD82" d="M28.555 84.893H52.04v-3.177H28.555z"/>
<path fill="#89C0C8" d="M28.555 91.659h23.037v-3.16H28.555z"/>
<path fill="#17BB99" d="M28.552 98.702h12.342v-3.166H28.552z"/>
<path fill="#FBDD82" d="M43.834 98.702h52.54v-3.166h-52.54zM54.289 91.659h38.205v-3.16H54.289z"/>
<path fill="#E29616" d="M95.445 91.659h12.335v-3.16H95.445z"/>
<path fill="#89C0C8" d="M113.313 84.893h24.532v-3.177h-24.532z"/>
<path fill="#17BB99" d="M54.525 84.893h55.694v-3.177H54.525z"/>
<path fill="#FBDD82" d="M56.857 42.645h14.717V39.48H56.857zM28.555 56.9h16.957v-3.166H28.555z"/>
<path fill="#17BB99" d="M75.81 56.89l-27.54.01v-3.16l27.536-.028z"/>
<path fill="#89C0C8" d="M74.225 42.645h26.36V39.48h-26.36zM28.552 49.864h29.231V46.69H28.552z"/>
<path fill="#E29616" d="M60.805 49.864h28.588V46.69H60.805z"/>
<path fill="#17BB99" d="M92.233 49.864h19.982V46.69H92.233z"/>
<path fill="#89C0C8" d="M28.552 63.934h34.646V60.78H28.552z"/>
<path fill="#FBDD82" d="M65.848 63.934h18.488V60.78H65.848z"/>
<path fill="#17BB99" d="M28.552 70.638h34.646v-3.177H28.552z"/>
<path fill="#89C0C8" d="M65.848 70.638H97.98v-3.177H65.848z"/>
<path fill="#17BB99" d="M87.412 63.934h35.855V60.78H87.412zM43.72 35.605H52v-3.17h-8.28z"/>
<path fill="#FFAD97" d="M165.476 114.087c.533 1.36-.737 3.134-2.84 3.952-2.1.825-4.238.386-4.774-.978-.533-1.364.736-3.135 2.836-3.956 2.106-.825 4.245-.385 4.778.982"/>
<path fill="#004DFF" d="M217.242 70.314s-2.85.532-9.052 7.104c-3.899 4.123-8.684 9.914-13.673 14.09-12.93 10.836-28.374 19.015-28.374 19.015s-.19 2.945 1.792 4.984c5.794-1.482 40.813-18.372 49.908-27.336 9.092-8.964 7-20.367-.6-17.857"/>
<path fill="#473F66" d="M237.867 231.954c0 2.713-43.72 4.909-97.666 4.909-53.924 0-97.641-2.196-97.641-4.91 0-2.698 43.717-4.894 97.641-4.894 53.946 0 97.666 2.196 97.666 4.895"/>
<path fill="#473F66" d="M396.429 231.954c0 2.713-36.382 4.909-81.271 4.909-44.872 0-81.25-2.196-81.25-4.91 0-2.698 36.378-4.894 81.25-4.894 44.89 0 81.27 2.196 81.27 4.895"/>
<path fill="#0043FF" d="M284.911 59.6l16.385-7.054s2.707 2.253 2.707 4.052c0 1.8-14.581 15.605-14.581 15.605l-4.51-12.603z"/>
<path fill="#FFAD97" d="M304.663 49.113c-1.31 1.457-1.502 3.417-.433 4.37 1.07.95 2.994.536 4.3-.921 1.301-1.46 1.498-3.417.428-4.37-1.065-.95-2.993-.539-4.295.921"/>
<path fill="#6B63BF" d="M418.321 38.105l-6.416-6.83-11.32 5.206h-80.627c-3.934 0-7.153 3.213-7.153 7.14v33.212l-5.297 2.435 7.1 7.407c.167.193.339.379.521.55l.083.086h.021c1.263 1.12 2.908 1.828 4.725 1.828h93.885c3.935 0 7.154-3.213 7.154-7.14V43.62c0-2.231-1.06-4.206-2.676-5.516"/>
<path fill="#8686FE" d="M413.885 74.56c0 3.927-3.219 7.14-7.153 7.14h-93.886c-3.934 0-7.153-3.213-7.153-7.14V36.182c0-3.927 3.22-7.14 7.153-7.14h93.886c3.934 0 7.153 3.213 7.153 7.14V74.56z"/>
<path fill="#FFF" d="M316.444 36.182a3.59 3.59 0 0 0-3.58 3.57l-.014 31.238c-.004 1.964 1.606 3.57 3.573 3.57h86.732a3.584 3.584 0 0 0 3.577-3.57V39.752c0-1.963-1.61-3.57-3.577-3.57h-86.71z"/>
<path fill="#8686FE" d="M329.786 57.676v9.46h-4.614V43.92h4.614v9.45h4.37v-9.45h4.633v23.216h-4.632v-9.46zM346.189 48.212h-4.518V43.92h13.616v4.29h-4.495v18.926h-4.603zM378.821 43.922v23.216h-4.631V51.84l-3.212 15.298h-5.619l-3.24-15.298v15.298h-4.614V43.922h6.41l4.252 18.868 4.245-18.868zM383.38 67.138V43.922h4.617v18.925h6.413v4.29z"/>
<path fill="#FFAD97" d="M282.334 224.41v5.258h4.149v-5.537z"/>
<g transform="translate(281 227)">
<mask id="j" fill="#fff">
<use xlink:href="#h"/>
</mask>
<path fill="url(#i)" d="M1.64.449S.57 1.174.57 2.929v3.604h16.2s.3-2.127-1.93-2.127c-2.225 0-6.127-1.706-6.853-2.514C7.055.843 6.387.449 5.674.449H1.64z" mask="url(#j)"/>
</g>
<path fill="#FFAD97" d="M245 224.251V229h5v-5z"/>
<g transform="translate(245 226)">
<mask id="m" fill="#fff">
<use xlink:href="#k"/>
</mask>
<path fill="url(#l)" d="M1.676.65S.632 1.376.632 3.14v3.593H16.47s.289-2.127-1.887-2.127c-2.17 0-5.991-1.695-6.698-2.514C6.97 1.047 6.32.649 5.624.649H1.676z" mask="url(#m)"/>
</g>
<g>
<path fill="#666" d="M252.182 123.296s4.045 32.062-.358 50.67c-4.395 18.61-7.574 48.603-7.574 48.603s1.467 1.285 5.409.347c0 0 21.735-54.726 21.735-72.642 0-17.916-4.603-22.753-4.603-22.753s-11.652-7.587-14.609-4.225"/>
<path fill="#8C8C8C" d="M258.737 123.296s13.928 32.553 15.145 54.217c1.22 21.662 6.633 47.312 6.633 47.312h6.35s6.597-56.101 3.179-101.096c-.755-9.914-26.471-5.929-31.307-.433"/>
<path fill="#FFAD97" d="M287.349 27.487s.132 5.389.132 6.116c0 .734 1.396 3.197 2.058 3.791.666.602-.132 1.801-.995 1.862-.862.068-.733 1.196-.733 2.26 0 2.262.136 9.373-7.178 4.922 0 0-1.335 4.759-.937 6.288l-11.49.218s1.263-5.84 1.062-8.503c-.197-2.657 9.007-19.975 18.08-16.954"/>
<path fill="#444199" d="M288.81 22.17c-5.183.533-17.282-5.318-20.27 2.527 0 0-2.428.068-1.83 4.153.598 4.089-.494 16.255 5.083 16.255 5.584 0 7.829-2.241 7.829-2.241s-4.038-5.435-1.379-7.53c.824-.648 1.68-.1 1.726 1.346.053 1.447-.136 8.103.723 9.975 1.303 2.832 3.787 2.42 4.786 2.141 3.089-.862 3-7.747 2.305-5.689-.694 2.048-3.225 1.998-4.489.738-1.263-1.264-2.352-8.64-2.355-8.643.046-3.53 1.049-8.446 6.41-7.716 0 0 1.393.465 3.187.598 1.797.136 3.458-6.441-1.726-5.915"/>
<path fill="#002A8A" d="M251.05 122.778s1.81-50.62 6.336-60.637c4.517-10.025 9.95-12.335 13.34-12.335 3.39 0 15.293 1.028 24.632 15.933 9.346 14.898 13.563 17.465 22.906 18.668 9.346 1.196 14.168 1.54 14.168 1.54s1.245 3.176 0 6.509c0 0-16.502.902-24.413-1.278-7.911-2.191-15.597-7.58-15.597-7.58l-.225 40.727s-5.992 5.524-21.932 5.524c-15.936 0-19.215-7.07-19.215-7.07"/>
<path fill="#FFAD97" d="M334.845 88.779c0 1.56 1.926 2.824 4.295 2.824 2.37 0 4.292-1.263 4.292-2.824 0-1.562-1.922-2.825-4.292-2.825s-4.295 1.263-4.295 2.825"/>
<path fill="#473F66" d="M350.051 321.42c0 2.72-30.734 4.922-68.66 4.922-37.908 0-68.642-2.202-68.642-4.923 0-2.707 30.734-4.909 68.642-4.909 37.926 0 68.66 2.202 68.66 4.91M290.687 362.443c0 2.721-30.999 4.923-69.257 4.923-38.237 0-69.237-2.202-69.237-4.923 0-2.707 31-4.909 69.237-4.909 38.258 0 69.257 2.202 69.257 4.91"/>
<g transform="translate(347.324 375.73)">
<mask id="o" fill="#fff">
<use xlink:href="#n"/>
</mask>
<path fill="#473F66" d="M59.294 2.496c0 1.26-13.13 2.278-29.331 2.274C13.762 4.766.635 3.739.635 2.482S13.762.21 29.963.212c16.201.004 29.331 1.031 29.331 2.284" mask="url(#o)"/>
</g>
<path fill="#FFAD97" d="M330.329 221.256c.94 1.224.304 3.301-1.429 4.622-1.732 1.329-3.898 1.4-4.847.161-.934-1.231-.297-3.3 1.432-4.622 1.733-1.328 3.898-1.393 4.844-.161"/>
<path fill="#E6426B" d="M310.008 232.08c2.592 1.197 6.6 1.623 14.279-2.61-.483-1.012-1.228-2.054-2.384-2.835-7.86 2.238-12.697 2.395-15.843 1.647 1.757 1.973 3.139 3.427 3.948 3.799"/>
<path fill="#002A8A" d="M315.208 238.032h147.19v-94.185h-147.19z"/>
<path fill="#474E5E" d="M315.208 155.505h147.19v-11.658h-147.19z"/>
<path fill="#FB392E" d="M326.49 149.743a3.011 3.011 0 0 1-3.01 3.015 3.02 3.02 0 0 1-3.018-3.015 3.016 3.016 0 0 1 3.018-3.008 3.008 3.008 0 0 1 3.01 3.008"/>
<path fill="#FECB15" d="M335.89 149.743a3.014 3.014 0 0 1-3.014 3.015 3.017 3.017 0 0 1-3.025-3.015 3.013 3.013 0 0 1 3.025-3.008 3.01 3.01 0 0 1 3.014 3.008"/>
<path fill="#25B2C9" d="M345.273 149.743a3.01 3.01 0 0 1-3.007 3.015 3.01 3.01 0 0 1-3.018-3.015 3.007 3.007 0 0 1 3.018-3.008 3.007 3.007 0 0 1 3.007 3.008"/>
<path fill="#E29616" d="M324.723 171.265h12.564v-2.484h-12.564z"/>
<path fill="#F19B0F" d="M324.726 204.32h14.13v-2.482h-14.13zM324.726 220.109h9.626v-2.496h-9.626z"/>
<path fill="#0298AE" d="M327.834 176.786h20.092v-2.488h-20.092z"/>
<path fill="#DFB815" d="M327.83 209.926h18.381v-2.48H327.83z"/>
<path fill="#FFF" d="M327.834 215.25h18.037v-2.477h-18.037z"/>
<path fill="#DFB815" d="M347.987 215.25H377.9v-2.477h-29.914z"/>
<path fill="#F9392A" d="M380.21 215.25h9.661v-2.477h-9.661z"/>
<path fill="#FFF" d="M394.195 209.926h19.216v-2.48h-19.216z"/>
<path fill="#0298AE" d="M348.18 209.926h43.603v-2.48H348.18z"/>
<path fill="#DFB815" d="M349.991 176.786h11.534v-2.488H349.99zM327.834 187.975h13.28v-2.481h-13.28z"/>
<path fill="#0298AE" d="M364.838 187.963l-21.56.028v-2.495l21.56-.022z"/>
<path fill="#FFF" d="M363.604 176.786h20.626v-2.488h-20.626z"/>
<path fill="#F83A2A" d="M327.83 182.44h22.888v-2.474H327.83z"/>
<path fill="#E29616" d="M353.088 182.44h22.38v-2.474h-22.38z"/>
<path fill="#0298AE" d="M377.7 182.44h15.647v-2.474h-15.646z"/>
<path fill="#F83A2A" d="M327.834 193.492h27.126v-2.477h-27.126z"/>
<path fill="#DFB815" d="M357.036 193.492h14.483v-2.477h-14.483z"/>
<path fill="#0298AE" d="M327.834 198.738h27.126v-2.478h-27.126z"/>
<path fill="#FFF" d="M357.036 198.738h25.15v-2.478h-25.15z"/>
<path fill="#0298AE" d="M373.92 193.492h28.083v-2.477H373.92z"/>
<path fill="#DFB815" d="M339.714 171.265h6.494v-2.484h-6.494z"/>
<path fill="#FFAD97" d="M238.867 303.115l-5.96.552.494 5.33 6.279-.583z"/>
</g>
<g transform="translate(230 301)">
<mask id="r" fill="#fff">
<use xlink:href="#p"/>
</mask>
<path fill="url(#q)" d="M3.826.578C2.036.765.171.955.171.955l1.292 15.66 4.7-.013s.439-1.914-2.142-2.36c-2-.343.991-3.896 1.747-4.885.967-1.289 1.192-1.741 1.116-2.628l-.413-5.077S5.734.561 4.172.561c-.112 0-.224.003-.346.017" mask="url(#r)"/>
</g>
<g>
<path fill="#522038" d="M260.334 259.474c-2.7 15.442 13.488 33.054 12.048 36.143-1.433 3.084-32.372 3.632-34.358 4.36-1.253 3.255-.18 9.077-.18 9.077h42.635c8.457 0 10.974-5.093 10.442-10.53-.549-5.455-7.02-35.048-7.02-35.048l-23.567-4.002z"/>
<path fill="#FFAD97" d="M310.726 312.663l-1.795 5.778h5.117l1.888-6.075z"/>
</g>
<g transform="translate(308 316)">
<mask id="u" fill="#fff">
<use xlink:href="#s"/>
</mask>
<path fill="url(#t)" d="M1.58.27S.226 1.046.226 2.953V6.84h20.457s.368-2.298-2.436-2.298-7.733-1.837-8.653-2.72C8.42.69 7.575.27 6.677.27H1.58z" mask="url(#u)"/>
</g>
<g>
<path fill="#FFAD97" d="M305.064 190.12s-.46 5.26-.535 5.978c-.083.704 1.016 3.25 1.601 3.904.582.654-.323 1.73-1.177 1.702-.855-.029-.848 1.083-.955 2.116-.248 2.206-.89 9.139-7.554 4.019 0 0-1.615 2.628-1.393 4.154l-11.1-2.395s1.504-2.32 1.601-4.93c.094-2.614 10.971-18.474 19.512-14.548"/>
<path fill="#6F2051" d="M307.066 185.107c-5.123-.043-16.313-7.047-20.086.264 0 0-2.377-.193-2.237 3.85.14 4.04-2.247 15.767 3.206 16.368 5.457.604 7.894-1.34 7.894-1.34s-3.36-5.728-.531-7.473c.876-.543 1.648.082 1.54 1.502-.108 1.408-1.009 7.865-.377 9.781.962 2.896 3.44 2.764 4.445 2.596 3.116-.5 3.78-7.211 2.868-5.288-.901 1.924-3.364 1.602-4.466.24-1.095-1.366-1.357-8.67-1.357-8.67.427-3.429 1.939-8.105 7.097-6.818 0 0 1.31.608 3.055.93 1.738.328 4.079-5.9-1.051-5.942M296.362 254.963s19.422-.358 30.034 3.053c10.181 3.26 5.04 17.722 2.7 27.035-2.338 9.314-10.609 29.013-10.609 29.013s-3.956.719-8.454-.354c0 0 5.934-31.522 4.498-36.36-1.447-4.84-51.98 6.443-53.595-11.104-1.619-17.562 35.426-11.283 35.426-11.283"/>
<path fill="#FFAD97" d="M342.327 222.185c.97 1.209.363 3.29-1.35 4.645-1.712 1.347-3.887 1.458-4.85.25-.965-1.216-.358-3.296 1.354-4.651 1.712-1.348 3.88-1.46 4.846-.244"/>
<path fill="#F27A3B" d="M276.158 214.546s4.796-7.172 13.071-4.419c8.275 2.746 20.021 20.054 23.141 21.49 3.12 1.435 8.275 1.788 19.422-5.723 3.838 1.062 5.155 5.123 5.155 5.123s-12.464 12.907-24.092 14.687c-11.635 1.802-13.796-9.07-15.35-7.283-1.559 1.795-6.477 8.238-.482 16.368 0 0-3.36 18.09-36.991 11.816 0 0 .06-32.66 16.126-52.059"/>
<path fill="#E6426B" d="M171.548 271.735l-37.246-15.209s-.507-.382-.507-2.788c0-2.41.507-2.642.507-2.642s26.648 3.26 50.963 13.189c24.308 9.931-13.717 7.45-13.717 7.45"/>
<path fill="#FFAD97" d="M125.477 252.959c0 1.458 1.58 2.638 3.53 2.638 1.949 0 3.532-1.18 3.532-2.638 0-1.455-1.583-2.639-3.533-2.639-1.949 0-3.529 1.184-3.529 2.639"/>
<path fill="#0FAD91" d="M149.829 241.849l-6.44-6.84-11.363 5.213H79.817c-3.949 0-7.18 3.214-7.18 7.15v33.26l-5.316 2.44 7.126 7.418c.168.193.34.379.524.55l.082.083h.022c1.267 1.126 2.919 1.834 4.742 1.834h65.517c3.95 0 7.18-3.214 7.18-7.15v-38.434c0-2.231-1.062-4.212-2.685-5.524"/>
<path fill="#25E0C0" d="M145.376 278.359c0 3.932-3.23 7.15-7.18 7.15H72.68c-3.949 0-7.18-3.218-7.18-7.15v-38.434c0-3.936 3.231-7.15 7.18-7.15h65.517c3.95 0 7.18 3.214 7.18 7.15v38.434z"/>
<path fill="#FFF" d="M76.29 239.925c-1.974 0-3.59 1.605-3.593 3.575l-.014 31.284c-.004 1.962 1.611 3.575 3.586 3.575h58.337c1.975 0 3.59-1.613 3.59-3.575V243.5c0-1.97-1.615-3.575-3.59-3.575H76.291z"/>
<path fill="#25E0C0" d="M87.39 251.67v6.11h2.104c1.403 0 2.103-.482 2.103-1.444v-4.666H87.39zm-4.444-4.118h9.825c.89 0 1.655.29 2.298.872.66.6.99 1.326.99 2.163v5.749c0 1.702-.66 3.07-1.967 4.108-1.227.972-2.757 1.455-4.598 1.455H87.39v7.955h-4.444v-22.302zM103.583 260.762v9.092H99.14v-22.302h4.444v9.077h4.211v-9.077h4.46v22.302h-4.46v-9.092zM121.116 251.67v6.11h2.104c1.407 0 2.107-.482 2.107-1.444v-4.666h-4.211zm-4.444-4.118h9.829c.89 0 1.655.29 2.298.872.657.6.987 1.326.987 2.163v5.749c0 1.702-.657 3.07-1.968 4.108-1.224.972-2.757 1.455-4.598 1.455h-2.104v7.955h-4.444v-22.302z"/>
<path fill="#FFAD97" d="M278.22 346.77l4.311 3.378-3.034 3.847-4.544-3.56z"/>
</g>
<g transform="translate(273 349)">
<mask id="x" fill="#fff">
<use xlink:href="#v"/>
</mask>
<path fill="url(#w)" d="M7.994.582s-2.56 3.31-3.102 4.02c-.554.719-.719 1.665-.506 3.34.165 1.299 1.272 4.38-2.732 6.172-1.675.751-.88 2.195-.88 2.195l3.85-.026L14.21 3.847 11.023 1.29C10.187.616 9.38.46 8.812.46c-.503 0-.818.123-.818.123" mask="url(#x)"/>
</g>
<path fill="#FFAD97" d="M182 353.254l-.92 4.746H177l.92-5z"/>
<g transform="translate(164 357)">
<mask id="A" fill="#fff">
<use xlink:href="#y"/>
</mask>
<path fill="url(#z)" d="M13.614.055c-.856 0-1.67.404-2.797 1.48-.878.835-5.594 2.584-8.276 2.584C-.15 4.12.204 6.3.204 6.3h19.577V2.61c0-1.814-1.289-2.555-1.289-2.555h-4.878z" mask="url(#A)"/>
</g>
<g>
<path fill="#FFAD97" d="M163.104 264.223c-.948-6.088 1.505-11.844 1.505-11.844.908.374 3.131.55 4.697.878 1.559.328 1.282 2.588 1.253 3.676-.04 1.072.482 3.538 1.024 3.787.424-1.177.905-5.047 2.503-3.463 1.584 1.587.93 2.127.55 4.38-.385 2.254 2.323 2.186 2.323 2.186-4.227 4.604-7.657 3.578-7.657 3.578-3.968 1.844-5.656.144-6.198-3.178"/>
<path fill="#444199" d="M164.609 252.378s-2.952-.936-3.118-2.693c-.15-1.756 4.04-1.22 7.621-2.185 3.58-.972 8.896-2.138 10.76 2.686 0 0 2.528 1.829 1.795 4.799-.722 2.962-3.203 4.953-4.708 8.838 0 0-2.708.068-2.323-2.186.38-2.253 1.034-2.793-.55-4.38-1.598-1.585-2.076 2.285-2.503 3.462-.542-.248-1.063-2.714-1.024-3.787.03-1.087.306-3.348-1.253-3.675-1.566-.328-3.789-.504-4.697-.879"/>
<path fill="#444199" d="M171.121 259.455s-.707 5.289-2.977 5.426c-2.273.136-4.935-1.534-5.254.244-.313 1.779 1.918 3.903 4.076 3.525 2.162-.385 3.512-1.336 3.875-2.758.36-1.415 1.07-6.818 1.07-6.818l-.79.381z"/>
<path fill="#3B3687" d="M237.406 285.422s8.616 9.846 10.857 24.7c2.237 14.853.775 15.804 3.447 18.478 2.672 2.679 25.858 17.64 25.858 17.64s-.349 4.148-2.934 6.74c-9.305-3.114-26.454-8.921-32.746-15.221-6.288-6.307-12.753-25.045-19.468-29.88-6.72-4.839-12.58-12.438-12.58-12.438l27.566-10.02z"/>
<path fill="#444199" d="M203.702 298.285s-8.367 5.577-14.806 14.116c-4.522 5.994-14.244 39.694-14.244 39.694s3.434 3.12 8.268 2.188c0 0 14.236-30.214 22.341-34.905 8.102-4.68 46.608-6.145 35.594-29.063-3.2-6.188-8.548-9.997-8.94-10.469-.387-.468-26.808 14.116-28.213 18.44"/>
<path fill="#FFAD97" d="M124.252 287.763c0 1.469 1.58 2.657 3.53 2.657 1.95 0 3.534-1.188 3.534-2.657 0-1.465-1.584-2.653-3.534-2.653-1.95 0-3.53 1.188-3.53 2.653"/>
<path fill="#F2577E" d="M133.08 285.162s10.913 2.03 22.607-3.125c11.697-5.159 13.715-15.16 23.857-19.383 10.127-4.215 35.381-2.808 58.452 22.662-5.143 2.812-26.03 11.722-34.294 12.971-6.234-3.128-21.511-12.661-24.158-15.156-3.125.306-20.894 14.847-46.303 7.654 0 0-1.408-2.97-.162-5.623"/>
<path fill="#473F66" d="M439.76 324.8c0 .59-8.873 1.07-19.827 1.066-10.943-.004-19.82-.486-19.82-1.073 0-.594 8.877-1.07 19.82-1.066 10.954.004 19.828.486 19.828 1.073"/>
<path fill="#C78A30" d="M451.72 297.326c0 15.289-12.365 27.673-27.61 27.673-15.252 0-27.603-12.384-27.603-27.673 0-15.286 12.35-27.674 27.603-27.674 15.245 0 27.61 12.388 27.61 27.674"/>
<path fill="#EFA330" d="M445.369 297.326c0 15.289-12.362 27.673-27.61 27.673-15.253 0-27.603-12.384-27.603-27.673 0-15.286 12.35-27.674 27.603-27.674 15.248 0 27.61 12.388 27.61 27.674"/>
<path fill="#F2FBFD" d="M411.714 302.493v3.302l-13.19-6.718v-2.945l13.19-6.714v3.316l-9.406 4.881zM412.801 307.314l7.668-19.988h2.553l-7.664 19.988zM423.81 292.734v-3.319l13.191 6.718v2.944l-13.19 6.722v-3.305l9.412-4.882z"/>
<path fill="#5973C2" d="M415.926 341.76c0 19.93-16.125 36.079-36.003 36.079-19.888 0-35.999-16.15-35.999-36.08 0-19.925 16.11-36.093 36-36.093 19.877 0 36.002 16.168 36.002 36.094"/>
<path fill="#8686FE" d="M407.839 341.76c0 19.93-16.129 36.079-36.007 36.079-19.885 0-35.999-16.15-35.999-36.08 0-19.925 16.114-36.093 36-36.093 19.877 0 36.006 16.168 36.006 36.094"/>
<path fill="#FFF" d="M352.237 344.182v-4.86c1.203-.05 2.09-.277 2.654-.702.57-.421 1.012-1.073 1.314-1.97.309-.885.463-2.375.463-4.507 0-2.656.119-4.489.367-5.472.237-.979.628-1.764 1.149-2.34.535-.583 1.318-1.072 2.349-1.45 1.041-.382 2.524-.558 4.467-.558h1.078v4.845c-1.451 0-2.392.076-2.838.23-.452.163-.779.407-.987.771-.205.342-.305.986-.305 1.933 0 1.703-.08 3.604-.241 5.692-.115 1.357-.496 2.577-1.12 3.657-.475.792-1.293 1.574-2.446 2.33.987.565 1.763 1.274 2.345 2.142.578.87.962 1.976 1.15 3.312.085.576.182 2.451.283 5.648.04 1.177.115 1.9.215 2.182.19.417.51.734.995.957.478.205 1.455.324 2.949.324v4.842H365c-1.95 0-3.408-.151-4.356-.479-.941-.313-1.706-.766-2.306-1.393-.592-.608-1.016-1.379-1.275-2.315-.262-.943-.395-2.556-.395-4.86 0-2.548-.136-4.284-.423-5.184-.273-.885-.704-1.573-1.29-2.001-.582-.443-1.49-.699-2.718-.774M391.422 339.323v4.86c-1.189.076-2.08.303-2.665.713-.575.421-1.013 1.062-1.307 1.97-.323.892-.478 2.39-.478 4.524 0 2.672-.115 4.486-.359 5.447-.22.98-.603 1.76-1.138 2.344-.532.59-1.326 1.065-2.35 1.454-1.03.375-2.527.551-4.46.551h-1.077v-4.838c1.44 0 2.385-.087 2.82-.242.452-.165.775-.403.983-.763.212-.345.323-.986.323-1.937 0-1.702.083-3.61.241-5.695.093-1.353.463-2.567 1.095-3.632.496-.803 1.308-1.58 2.464-2.319-.988-.576-1.76-1.288-2.33-2.152-.586-.875-.97-1.98-1.168-3.338-.072-.558-.172-2.448-.28-5.637-.04-1.188-.111-1.923-.24-2.185-.177-.436-.5-.756-.977-.962-.489-.205-1.451-.316-2.931-.316v-4.85h1.077c1.947 0 3.398.162 4.335.486.934.317 1.706.782 2.317 1.404.603.612 1.012 1.386 1.278 2.319.273.947.402 2.556.402 4.86 0 2.549.137 4.284.413 5.17.27.896.711 1.562 1.286 2.012.582.435 1.49.695 2.726.752"/>
</g>
<path d="M-52-44h604v448H-52z"/>
</g>
</svg>
<p><a href="https://chen.cat/real-dev">Real Dev</a> 是一个我称之为「实战项目的 LeetCode 平台」。跟 LeetCode 相似的地方是,Real Dev 也是一个 OJ (Online Judge);跟 LeetCode 不同的是,Real Dev 不需要大家解决算法难题,但要大家解决工业界常见的实战问题。举个例子,Real Dev 的 Hello World 问题要求大家做一个最简单的 web 服务器,打开 <code>/hello</code> 页面的话服务器要返回 <code>"REAL WORLD"</code>。</p>
<p>以下一些是 Real Dev 的问题:自动完成组件(Autocomplete/Typeahead)、短地址生成服务、数据分页 API、响应式配色、JavaScript 到 TypeScript 迁移、克隆 Yelp。这些项目比 LeetCode 的算法题更贴近工业界,更像是软件工程师日常工作需要做的事情。做这些项目时需要用到的技术框架也是工业界所用的:Node.js + Express 或者是 Python + Django。</p>
<p><strong>优点</strong></p>
<ul>
<li>解题过程更像是软件工程师日常工作。</li>
<li>能够练习使用常见框架,例如 Express 和 Django。</li>
<li>有官方的<a href="https://www.reddit.com/r/RealDev/">问题讨论板块</a>。</li>
</ul>
<p><strong>缺点</strong></p>
<ul>
<li>题目数量不够多。</li>
<li>每道题都要从头开始搭架子。</li>
</ul>
<p>这次的测评不附送任何折扣链接。Real Dev 是我以前在 Facebook 的同事离开 Facebook 之后做的,他们现在正在做全站打折,由每个月 $20 打折到 $10,这已经是非常便宜的价钱了。大家想要试用的话,直接打开 <a href="https://chen.cat/real-dev">https://real.dev/</a> 就可以了,有一部分题目是免费也能访问的。</p>Cat Chenhttp://www.blogger.com/profile/17757778716179768922noreply@blogger.com0