2012年11月3日星期六

面试体验:Facebook 篇

GoogleMicrosoftYahoo 都是去年的事情了,接下来说说今年的吧。其实我在豌豆荚非常爽,跟身边的设计师和工程师合作都很愉快,所以唯一能够诱惑我去面试的就只有 Facebook 了。最初接受 Facebook 面试邀请的原因并不是追求它的 offer,而是我就想了解一下 Facebook 是怎么面试的,有什么是值得豌豆荚招聘借鉴的。

过去在百度做面试官,只是面试而已,公司招不招得到人我没什么感觉。我觉得公司招不到人就招不到人咯,我们没必要扩张得那么快啊,先专注于做好手头上的项目再说嘛。豌豆荚其实不是着急要招前端工程师,我们还是坚持只招一流人才,只不过长期发不出 offer 还是让人感觉招聘有问题——我们浪费了大量的资源在面试上,发不出 offer 意味着回报率低,因此我们总要想办法研究如何提高回报率。

回到正题上来,我选择参加 Facebook 的面试,就是想看看他们是如何选拔人才的,他们所使用的题目是如何设计考点的,是不是设计得比我们的要好。因此,在收到 Facebook HR 的邮件后,我回信说愿意聊一下,然后跟他约了一个时间进行电话沟通。因为 Facebook 总部在美国西岸,所以之后的电话沟通和电话面试都约在了早上 8:00。尽管这导致他们要晚一个小时下班(按朝九晚五算的话),不过 HR 也很通情达理地接受了。(我猜大多数工程师都不会采用朝九晚五的正常作息时间吧。)

HR 在电话里先简单介绍了一下 Facebook 现在的情况,然后说明这是 Menlo Park 总部的职位,让我确认如果顺利应聘的话我会愿意到美国去。接着 HR 问了我两个很基础的 CSS 问题:display block 和 inline 有什么区别?position 有哪些取值?我觉得 HR 能够问这样的问题对于工程师来说是很爽的事情,因为纯粹的小白就被过滤掉了,也不需要浪费工程师的时间来面试。(我在之前的文章中说到过,在中国大多数面试前端工程师职位的候选人无法回答这两个如此基础的问题,不知道在美国是否也如此。)随后 HR 问我还有没有什么不明白的,或者关于 Facebook 想要了解的,我说没有了。HR 的最后一个问题是「你为什么选择 Facebook?」我当时心里想的是,「是你主动联系我的,我没想过这个问题哦」。于是我跟他说,「我暂时没有答案。我现在在豌豆荚工作很开心,不过我也乐意多地了解 Facebook。」

电话沟通后,HR 给我发了两道 puzzle,选做其中一道就可以了。两道题目都是前端相关的,其中一道需要设计一个简单的算法,另一道则需要支持移动设备触击交互。这种解 puzzle 的面试方法我不是第一次遇到了,4 年前申请 Google 的 Web Developer 职位时也遇到过类似的 exercise,只不过题目只有一道,没有选择的余地而已。相比起 4 年前 Google 的 exercise 而言,这两道 puzzle 的考点更加 update。(4 年前的 exercise 还需要考你如何做圆角和背景渐变,现在都是用 CSS 3 搞掂的了。)

我花了一周的时间完成了一个 puzzle,搞掂了算法设计和界面实现,连 unit test 也都写了,然后提交给 HR。HR 在 review 的结果出来后,把我介绍给另一位 HR,说她会帮我安排接下来的面试。第一轮面试感觉有点像 Google 的,主要由 3 道题目构成。题目的考点设计得很好,基础知识能被覆盖到,常用技法也需要用到,但又绝对不需要某一方面很高深的知识。(设计得不好的题目往往是依赖于面试官很熟悉的一个难点,如果你不知道这个难点,或者你的理解跟面试官不一样,你就完蛋了。)

第一轮面试的最终通话时间为 90 分钟,我猜这意味着我做得不够好,因为如果以 Google 的标准来衡量的话,45 分钟解 3 道题才算及格。经过后面几轮面试我才发现,原来 Facebook 面试一般是要求 60 分钟解 2 道题。第一轮的面试官之所以给我加了 1 道题,估计是因为第 2 道题我做得不好,所以他相当于换了一道题给我做。面试结束,面试官又问了之前 HR 问过的问题,「你为什么选择 Facebook?」我还是那样子回答。

面试一个星期后,HR 邮件跟我说,我通过了上一轮面试,接着要安排下一轮面试。第二轮面试感觉跟第一轮差不多,包括长度和难度。只不过这次就是 60 分钟 2 道题,估计是因为我 2 道题都解出来了吧。面试结束时面试官又问那个问题了,我决定反过来问他是否喜欢 Facebook 的工作。他说 Facebook 的工作很好,周围的人都很聪明,能够从他们身上学到东西,同时公司提供一天三餐,福利好到觉得自己被宠坏了。我其实不是很在乎福利的部分(豌豆荚又不是没有一日三餐),我更在乎的是人是否聪明,合作的过程中他们是否总能教会你一些你过去不知道的事情(这是我现在在豌豆荚拥有但离开就可能失去的部分)。随后我跟他说,我在乎的是能否跟聪明人一起工作,听他这样说感觉 Facebook 不错。

又过了一个星期,HR 邮件跟我说,需要安排我到美国面试。我一开始对这件事也不特别在意,觉得那你就慢慢安排吧,有人报销机票让我到湾区旅行就是好事情。在随后的电话沟通里面 HR 跟我说,因为今年的 H–1B 签证配额已经花出去一半了,如果按照这个速度估算的话可能到 5 月底签证配额就会花完,所以希望我尽快到美国面试。(如果签证配额花完了,有没有 offer 都没意义了。明年 4 月才能申请明年的配额,申请成功也要等明年 10 月签证才生效,就算公司很想要你,也只能先安排你在海外办公室工作一年。)于是我就连忙办签证 5 月下旬飞往美国参加面试,因此也就有了我之前那篇《三藩市湾区一周游》。

4 轮面试安排在一天内完成,Facebook 委托旅行社安排好往返机票和两晚住宿,随后我就出发了。因为害怕迟到,又因为美国郊区的公交又是一小时一班的,所以面试当天我早早就起床了,结果发现酒店门口是长期有出租车的,打车到 Facebook 后等了一个多小时才到原本约定的面试时间。HR 在见到我后先把我带到 micro-kitchen 让我拿吃的喝的,并且问我那么早来是不是没有吃早餐。我说确实没有,然后她就让我拿一些食品做早餐。(其实我应该在酒店叫早餐的,因为 Facebook 允许每天报销最多 $75 的餐饮开支。)随后她把我带到用作面试的会议室,给时间我解决早餐,并且跟我简单说明了当天的安排:早上 2 轮面试,结束后她会来带我去 Facebook 餐厅吃饭,然后下午还有 2 轮面试。

总体上来说,4 轮面试的形式还是一样的,每轮都是 60 分钟解 2 道题目。所有题目都是前端相关的,HTML + CSS + JS 都会考到,不过不涉及 HTTP。最后一轮的面试官有点特别,他先问了一个很古怪的 CSS 问题,然后又跟我讨论了一个跟前端不相关的编程问题。之所以说他那个 CSS 问题古怪,是因为在现实中大家都不会那样写 CSS,但他写出来了问你会显示成怎样,不是非常熟悉 CSS 标准细节的人又很难完全答对(我也有答错了的地方)。至于第二道题,他说他是突发性想出来的,他自己也不知道最优解是什么,就是想跟我讨论一下能够如何优化,我就跟他讨论了几种可能的优化方式。所有他又说如果把常数 k 改为可以变成任意大的 n 怎么办?我就说 n 的问题能够分解为 n/2 的问题,因此能够通过二分法来优化。

通常情况下,如果由于面试而进入一家公司的话,HR 所做的只是把你从 A 地带到 B 地,保证你顺利完成面试。如果是朋友带你参观公司则会很不一样,他会带你去看有特色的东西,并且告诉你这个好玩那个有意思。Facebook 的 HR 给人的感觉更像是后者,她向我介绍 Facebook 墙上的涂鸦,带我去天桥上看 Hacker Square 全貌,并且告诉我每次 hackathon 开始时大家就会聚集到 Hacker Square 上来。除了 HR 以外,也有一些面试官会提及他们喜欢的 Facebook 特色。这让我觉得 Facebook 里面还是有不少员工挺喜欢这家公司的。

面试结束后,HR 跟我聊了一下,告诉我如果有 offer 的话接下来会需要什么。根据之前 Google 面试的经验,我猜 Facebook 会不会也要我提交一大堆的材料,HR 说只要提交申请签证所需的学位证就可以了。之后 HR 让前台帮我叫出租车,在等待的过程中前台还很好人地问我是否需要拿喝的,需要的话可以在大堂冰柜里面拿。

随后的周末是美国的亡兵纪念日,周六到周一连续放假三天,我则利用这个长周末去参加湾区的各种好友聚会。周一中午 HR 打电话来说要发 offer 了,待细节确定后下午再打电话给我告诉我具体的数字有多少。我当时就在想,难道 Facebook 的面试官和 HR 周末都工作?这个效率很高呀。只要面试官稍微拖一下,周五的面试就必须等到下周才能有结果。而且确定 offer 细节估计也要经过几个人审批吧,节假日发 offer 就意味着大家都要在节假日处理工作了。

总体上来说,Facebook 面试过程中对候选人的关怀做得很好,效率也不错。让我「大开眼界」的是面试题,原来真正好的面试题并不在于它有多难,而在于它有多简单,简单到熟悉这个领域的人一下子就明白到你在说什么以及想问什么。能够进入 Facebook 的人应该都觉得面试不难,至少跟中国的面试对比起来如此,那是因为 Facebook 把觉得面试有点难的人都过滤掉了,而中国那些很难的面试反而没什么区分度。

《面试体验》系列文章到此就结束了。接下来有时间的话或许我会写写跟应聘美国职位有关的事情,例如 H–1B 的周期和配额是怎么样的,选择什么时间面试对你比较有利,拿到 offer 之后该如何为新的生活做准备等等。我知道对于很多在中国读书或工作的人来说,直接应聘美国职位看起来门槛很高,那是因为你身边很少人这样做所以你不了解而已。只要你愿意花时间去了解清楚,你会发现这件事其实没有你想象中那么难。如果你对这个话题感兴趣的话,可以订阅我的博客,或者留言提问。


2012年8月18日星期六

孩子王?有孩子气才能为王?

以最快的速度看完了 Facebook 第 51 号员工的回忆录《The Boy Kings》。尽管是关于 Facebook 成长的故事,说的却不是跟技术或者投资相关的事情,这是因为作者 Katherine Losse 并不是一名工程师,而是一位英文硕士。她最初加入 Facebook 时是一位客服,后来负责过 Facebook 的国际化工作,最后成为 Mark Zuckerberg 的代笔写手,估计是离 Mark Zuckerberg 最近的非高管了。这种经历使得作者陈述故事的角度如此的与众不同,让读者也必须跟着反思「难道高科技公司就必须做得如此孩子气才能成功?」

作者最初加入 Facebook 时,就感觉到了工程师与非工程师地位悬殊。Zuck 的衷心欢迎是留给工程师的,客服根本不在 Zuck 的视线范围内。作者最要好的两位朋友分别是黑客 Thrax 和工程师 Sam,然而在作者任职客服期间,她的薪水和她的好友相距甚远,以至于每当工程师们自发组织出去享乐的时候,她总要想一想是否值得为了参加活动而节衣缩食几个月。

有一次 Thrax 和 Sam 去参加 Defcon(世界上最大的黑客大会),把作者也叫上了。因为 Thrax 是明星黑客,是这次行程的主角,所以他能报销各种开支。Thrax 入住酒店后的第一个想法是要在拉斯维加斯最贵的餐厅预定晚餐。Thrax 的第二个想法是,为晚餐买一身与之匹配的新衣服。作者说,「我们去 Marc Jacobs 看看吧」,Thrax 的第一反应是「那是谁?」作者忍住没笑出来,解释说「他的东西比较 cool,有点点 mod,你会喜欢的」,然后心想「估计他也不懂『mod』是什么意思」。在随后晚餐上,Thrax 穿着作者花了两个小时为他挑选的 Lacoste 衬衫,开了一瓶 $175 的红酒,三个人当中就只有他还不到合法喝酒的年龄。

Thrax 是明星黑客,同时也是个小孩子。跟大多数黑客一样,Thrax 是清晨睡觉下午起床的,于是在 Thrax 睡觉时作者就跟 Sam 找了个泳池晒太阳(他们总是耻笑其它不怎么出门的工程师「脸色苍白」)。等 Thrax 要作者帮他挑选衣服时,他就提出说「既然你们晒太阳时丢下我,这次买衣服能否反过来把 Sam 丢下?」尽管 Thrax 如同小孩子一样,希望有一段时间能够获得别人 100% 的注意力,但作者和 Sam 决定无视他这个请求。

作者对 Thrax 的看法,很多时候也是她对黑客文化的看法。一群黑客就如同一群孩子,收入不低却不懂世俗,而且还时不时耍孩子气。然而正是这样一群人,创造了 Facebook 这样的王国,于是有了标题中的「boy kings」,而 Zuck 当然就是「king of the kings」啦。

后来由于客服职位的发展空间有限,作者想要离开 Facebook。正好在这个时候 Facebook 发布了自己的开发平台,平台的市场推广需要人手,于是作者就加入了这个新组建的团队。Thrax 在知道这件事后跟作者说,「所以你现在能搬到工程师所在的楼层来咯?真无耻。」「你才无耻。」「反正没有人喜欢你,所以……」互相挑衅显然也是作者眼中孩子气的一部分。在平台市场推广短暂停留后,作者又被邀请去做 Facebook 的国际化工作。作为英文硕士,她喜欢语言和旅行,自然她选择了接受新的工作。

作者的新头衔是「国际化产品经理」,这意味着她终于成为工程部门的一员,也有了对产品的话事权。这时候作者才亲身体验到了工程师的生活方式:你可以工作到很晚,有时候也必须工作到很晚,因为这就是黑客特立独行的特质。你无需听命于权威,可以连夜编写突破性的天才产品(或者是在 Facebook 上挑衅别人)。同时作者也见识到了某些工程师对机械过程的迷信:Facebook 众包给用户翻译的词条偶尔会遇到多个翻译版本难以取舍的情况,某些工程师就坚信用户投票经算法计算得出来的结果一定是正确的。这时候作者就会站在这些工程师的对立面,坚持翻译结果必须经过人手审阅。

这种技术和人性之间的竞争一直没有停止过。工程师总想要把这个世界变得更加技术化,因为这才符合他们的理想。而作者作为一名女性,同时又是英文硕士,则希望更人性化一些。

作者的下一份工作是 Zuck 的代笔写手,专门负责用 Zuck 的口吻写博客。越是接近 Zuck,作者越觉得自己在公司里是个异教徒。为了工作需要,她可以用 Zuck 的思维方法进行思考和写作,但这也使得她更在意自己独立思考的能力,最终的结果就是她发现自己的想法跟 Facebook 的理想不一致。这是一家相信通过技术能够解决所有问题并且以以此为荣的公司,但作者却认为技术和人性只是解决问题的不同手段而已,没有优劣之分。公司想要通过技术手段解决一切问题,但这不是她想要的未来。

最后,作者选择了离开 Facebook,并且移居到一个无线网络慢到可以用「与世隔绝」来形容的地方来写这本书。在这本书里面,除了可以看到 Facebook 早期的一些趣事,还能看到技术质疑者眼中的技术革命是怎么样的。利用技术革命改变世界的人自然无条件地信仰技术,然而这一切并非如此无懈可击,换一个视角还是又很多地方值得质疑的。此外,尽管黑客拥有改变世界的能力,但他们那种希望永远年轻、不负责任、不受监管、无可阻挡的文化,在外人看来也有很可笑的一面。如果你正在做技术,并且你也坚信自己做的事情是无比正确的,那么你可以看一下这本书,怀疑一下自己的世界观。

2012年8月6日星期一

面试体验:Yahoo 篇

前面两篇文章提到了 GoogleMicrosoft 的面试体验,可惜都没有 offer,接下来说说有 offer 的。

考虑到我已经在 Google 和 Microsoft 的招聘流程当中了,于是我也让 Yahoo 的同学帮我内部推荐一下,试试 Yahoo 的面试如何。本来没想着很正经地面,不过最后拿到了 offer,所以才有了我之前那篇文章所说的「越是放松越能成功」。

Yahoo 一开始并没有什么 HR 沟通和预约,某一天我从百度下班回家正准备做饭就接到面试官电话。我开头以为他想要跟我约时间,结果他问我是否方便进行面试。我当时毫无准备不是很想面试,不过既然室友可以做饭那面试一下也没什么所谓。Yahoo 的面试不像 Google 那样有很明确的规范,所以每一轮的面试官喜欢怎么面试就怎么面试。第一轮的面试官问了很多很基础的问题,每一道题考一个基本的知识点,例如某个 CSS 属性的取值,或者是 HTTP 的状态码。这些问题基本上不需要任何的解题能力,知道就知道,不知道也没办法。前端的基础知识我都知道,所以这对我来说没什么难度,只是感觉自己被人当 wiki 来查而已。

Yahoo 的面试让我感到舒服的一个原因是,它有前端工程师职位,所以不需要强行用后端工程师的标准来衡量我。在通过第一轮电话面试后,HR 终于出现了,跟我约了一个下午的面试时间。我问她要具体的时间安排,跟 Google 和 Microsoft 不一样的是,她说没有具体的面试安排,预计我的面试需要占用整个下午,所以请我预留整个下午的时间。这是让我感觉安排不够严谨的地方,后来才知道因为有多个不同的团队想要面试我,所以从一开始就给我安排了更多轮的面试,让不同团队的人都有机会来面试我。

我在 Yahoo 办公室一个下午的面试见了 4 位工程师,其中包括 1 位经理。因为 Yahoo 的经理也跟大家一起写代码,所以我也把他算作工程师。每一位面试官面试的风格都不一样,不过都涉及写代码解题。最后经理进来的时候给我带来了一罐 Diet Coke,除了让我写代码外,他还让我打开现有 Yahoo 产品的页面查看源代码代码,然后问我有哪些地方做得不够好以及如何能够改进。

在面试的过程中,我很明确地向经理表示我希望能加入一个多元化的团队,跟来自不同国家不同背景的人合作,最好有机会到美国出差工作一段时间。经理表示,既然我想要跟美国团队合作,他可以额外安排美国的同事跟我面试一下。我的理解是,到这里我就相当于已经有了口头 offer,不过有机会跟美国同事聊一下那就聊一下咯。

因为对方在 Miami,中国的上班时间正好是他的下班时间,所以面试只能约在中国上班的前一个小时。我早上 8:50 到 Yahoo 办公室后,不仅仅 HR 还没到,连前台都还没上班。9:00 前台上班,过了一会儿 HR 才来把我带到视频会议室。在 IT 帮忙调试半个小时后,确认视频用不了,只好降级为电话会议。Miami 那边的同事很认真地把问题分作 HTML、CSS 和 JavaScript 三部分来问,半个小时自然聊不完,但他的下班时间到了,只好跟 HR 说明天继续。结果第二天还是同样时间去 Yahoo 办公室通过电话会议聊了一个小时。

由于 Yahoo 知道我在等 Google 的结果,所以 HR 在电话口述 offer 给我听后,告诉我 offer 的邮件先不会发出来,因为发出来我就必须在指定的天数内接受,否则系统就会自动取消 offer。我觉得这还是挺人性化的。Yahoo 的面试安排规范化程度看起来没有 Google 和 Microsoft 那么高,随意性比较大。当然,这样做的好处是灵活性也大一些,经理和 HR 可以按照自己的需要做一些特殊安排。

《面试体验》系列文章就只剩下 Facebook 那一篇了,我承诺我一定会把它完成,但可能要经过一段时间后我才会把它发表出来,所以建议大家订阅我的博客,或者是在 Twitter 上 follow @CatChen。此外,我的博客也接受捐赠,通过信用卡支付,金额随意($1 起)。

最后是广告一则:如果有任何曾经参加过豌豆荚(豌豆实验室)面试的人愿意写类似的面试体验文章,欢迎联系我并把文章地址发给我,我愿意在我的博客上以链接的形式进行分享。

更新:Facebook 篇》已发布。


2012年8月4日星期六

面试体验:Microsoft 篇

在上一篇《面试体验:Google 篇》中说到,我对猎头的标准回复是「有美国或者香港的职位吗?」在进入 Google 招聘流程后,Microsoft 有一位 HR 打电话来跟我说有一个北京的职位跟美国总部会有密切的合作,问我有没有兴趣。我当时想的是,如果加入美国公司的中国分公司,或许将来有机会 relocate 到美国去,至少会有去总部出差的机会吧,所以就决定去试试。

HR 在联系我之后,招聘经理 Alex 直接联系我跟我约了晚餐时间。晚餐其实不是什么面试,只是互相了解一下。Alex 原本在 Microsoft 总部工作,只是碰巧他来北京轮岗 3 个月,有候选人申请职位他自然也乐意见见面。至于这个跟美国密切合作的项目,总监和一半的成员在美国,中国这边已经有几个人但还要多招几个。

Alex 在晚餐中教会我一件最重要的事情是:什么叫做 「commodify 工程师」。所谓的「commodity」是指无差别的一般等价物,例如按桶算的原油就是这样子,无论中东产的还是中国产的都一样。在说原油价格一桶多少钱时,我们并不会关注到底是哪里产的,因为价格差不多,使用起来也没有区别。因此「comodify 工程师」就是把工程师当做一般等价物看,无视其人性和个性,把工程师看做无差别的人力资源单位,哪个项目缺多少人力资源单位,就为它补充多少人力资源单位。他帮助我意识到我不满的百度现状是什么,同时也支持我要换一家公司并找机会到美国工作的想法。

之后 Alex 帮我约了一轮电话面试,面试官是印度人。他问了我两道问题,一道比较简单写代码就能解决的,另一道则是分布式系统设计相关的,我全无经验只能说说我知道的概念。我感觉后面这一道题回答得不是很好,因为总是没办法说到点上,同时也不像算法题面试官给些提示就能向前推进。事后证明这一轮面试的反馈确实不是很好。

随后 Alex 又约了我到 Microsoft 办公室进行一天的面试。早上到了之后他先让我参加了当天的 daily scrum,让我知道他们是如何工作的。接着是跟美国的总监通过电话会议进行面试,没有讨论技术问题,更多的是互相了解对方的工作方式,看看双方是否合适。接下来是跟北京这边的经理面试,因为团队在北京没有专门的经理,所以人事方面的事情就交由北京的经理负责。看到经理 Norman 的姓后,我就知道他是说粤语的,同时因为他一开始就说自己普通话不是很好,所以我就提议说不如我们说粤语吧,于是我就在 Microsoft 一天面试当中用到了英语、粤语和国语。Norman 问了基础的算法题和逻辑题,也聊了一下分布式存储的设计,同样我对后者回答不上什么点来。感觉 Norman 的题目很重视逻辑思维,在我说某一道基础算法题不能用贪心算法后,他问我使用贪心算法的充要条件是什么,同时另外一道逻辑题考的也是是否清楚充要条件是什么。

在 Norman 面试之后,他叫上 Alex 跟我一起去午餐,然后下午我跟中国这边的团队成员聊聊天了解一下他们的工作内容就完事了。整个过程并不是很难,也不会像 Google 那样专门考算法。感觉 Microsoft 更重视逻辑多一些,同时跟美国团队进行面试也确实比跟中国团队进行的面试要让人更舒服一些。(Alex 和 Norman 分别是在 Microsoft 总部和湾区工作多年的人,所以面试风格应该都还是很美国的。)我下午离开的时候,口头 offer 算是出来了,HR 在电话中说很高兴招到了人,并让我提交当前的薪酬信息。

由于快到过年了,提交薪酬信息的事情我一拖就拖了两个星期,过年回来后才提交。事后 HR 跟我说,进行第一轮面试的印度人对我不熟悉后端这一点表示有顾虑,其它人尝试说服他但是没有成功,所以 offer 出不来了。于是这个到手的口头 offer 就飞走了。

事实上,我在 Microsoft 的面试经历到此只不过是进行了 1/3,不过后面的我就不想详细说了。在我过年回广州放假的时候,有一位 Microsoft 的 HR 打电话给我问我面试时间安排的事情,我说面试不是结束了吗,然后才发现这是另外一个团队的 HR,并且她不知道我之前的面试,于是我又面试了一个不同的团队。因为前面所说的口头 offer 最终发不出来,Norman 把我推荐了给另外一位经理,那位经理又约了我进行了几轮面试。

总的来说,我在 Microsoft 的面试经历就是不停地被加试。加试意味着还不能确定,但又还不想放弃。不确定的原因自然是我没有后端经验,不放弃的原因估计是算法题和逻辑题我回答得还可以。最终我在 Microsoft 的 3 个团队中面试了 15 位工程师和 2 位 HR,还是拿不到 offer。Microsoft 的面试过程尽管没有 Google 那么体贴,但是安排还是挺专业的。因为 Microsoft 是各个招聘经理自己去招人,不像 Google 那样公司统一招聘,所以推动招聘进度的更多是招聘经理而非 HR,候选人也能直接感受到招聘经理到底有多在乎自己。

在整个 Microsoft 面试过程中,我觉得最有收获的是认识了 Alex 并且跟他聊了一些事情。我说如果有机会的话我想要体验一下在美国工作生活是什么样子的,然后才能知道我想要到哪里去。他帮我分析说去美国有 3 种途径:读书然后在美国找工作、加入美国公司的中国分公司后再找机会 relocate、直接加入美国公司,其中第一种方法最容易,第三种方法最难。他的首选建议是第一种方法,不过因为我短期内没有去美国读书的计划,所以第二种方法也不错。我觉得作为 mentor 最重要的是他要真的在乎你的个人发展,同时能够利用他的经验帮助你,因此我觉得 Alex 会是个很好的 mentor。在我加入豌豆荚后,他还联系过我,说 Microsoft 总部他所在的团队有职位开放,问我是否感兴趣。因为我当时刚刚加入豌豆荚,所以就拒绝了。

如果你对《面试体验》系列文章感兴趣,欢迎订阅我的博客,或者是在 Twitter 上 follow @CatChen。我承诺我接下来一定会完成 Yahoo 和 Facebook 相关的两篇文章。此外,我的博客也接受捐赠,通过信用卡支付,金额随意($1 起)。

最后是广告一则:如果有任何曾经参加过豌豆荚(豌豆实验室)面试的人愿意写类似的面试体验文章,欢迎联系我并把文章地址发给我,我愿意在我的博客上以链接的形式进行分享。

更新:Yahoo 篇》和《Facebook 篇》已发布。


2012年8月3日星期五

面试体验:Google 篇

尝试在自己的博客上搜索点东西,结果发现 4 年多以前还在博客上写过一系列的 recruiting events,把大四时候参加过的各种笔试面试都记录下来了。我从去年准备离开百度开始,到现在总过面试过 4 家公司:Google、Microsoft、Yahoo、Facebook,原本去年也想把面试经验写一写的,结果一拖就拖到现在。我不想写面试经验,因为我个人不喜欢漏题和背题的做法。我自己作为面试官,知道要设计出来一道好用的题目有多难,所以我希望面试者都是如实表现自己解题能力的。我更喜欢写面试体验,就是在整个面试过程中一家公司给人的印象是怎样的,HR 和面试官是否专业,能否让人信服这是一家值得长期工作的公司。

我想写的第一家公司是 Google,因为它是我在想要离开百度时第一家联系到我的公司。2010 年 12 月底的某一天早上,我突然感觉到我应该离开百度,因为如果这个时候已经没有勇气离开这家公司了,很可能就不会再想要离开了。当天中午在百度大厦西餐厅吃午饭,接到一个 Google 上海 HR 的电话,问我有没有兴趣去面试,我想既然你打电话来的时机那么好,我就答应你去面试吧。(在那一天之前,我对猎头的标准回复是「有美国或者香港的职位吗?」)她问我将来希望在北京还是上海工作,当时我对北京的厌恶程度还没有现在那么高,同时觉得搬家到上海又比较麻烦,于是就说在北京,接着我就变成跟北京 HR 沟通了。

Google 的 HR 会负责做两件简单得不需要面试官做的事情,这能够很好的提高招聘流程的效率。第一件是确认你能够适应工作环境中的英语,为此 HR 要我用英语跟她对话两三分钟,主要就是让我说说工作经验和其中的亮点。习惯在私企工作的人不要以为外企对英语的要求很高,其实大多数长期在中国工作的人说话或者发邮件都会很 Chinglish 啦,所以关键是要敢于用英语进行沟通。

然后 HR 发了一个 Codility 的地址给我,让我有空抽时间去做题。一个小时 3 道难度相当于 OI 基础题的题目,平均 20 分钟一道。最简单的题目一看就知道是 O(n) 能解决的,最复杂的题目看上去是 O(n^2) 但想一下就能优化为 O(n log n)。对于有算法训练背景的人来说,这样的题目会让人感觉到很有把握。对于没有经受过算法训练的人来说,掉进陷阱里是很容易的。很可能没有把 O(n^2) 优化为 O(n log n),结果超时;可能没仔细看题目说明的数值取值范围,某些变量选错了数值类型,结果溢出。考虑到 Google 重视算法的程度,再加上 Google 中国面试的额外难度,算法训练还是很必要的。

在我通过 Codility 测试后,HR 问我了对题目难度的反馈,然后约了一轮电话面试,并且告知面试主要围绕算法、数据结构、系统设计、编码来进行。Google 面试的格式都很固定,45 分钟内期望你能做出 3 道题来。这 3 道题最起码要能把人人都能想出来的「笨办法」用代码写出来,否则会让面试官感到不满意。如果有些题目能够比较快地做出来,面试官就会让你优化。就算你第一次给出的答案已经是业界已知最优解,面试官都还是会让你优化,因为谁也不知道有没有人能在面试过程中突然爆发,想出一些过去没人想到过的解法。如果面试官心中已有优化的方案,在你想不出优化方案时他可能会给你提供一些提示。

一轮电话面试后,HR 就开始约到 Google 办公室的面试了。第一次约了下午 3 轮面试,还是那个很固定的格式:每轮面试 45 分钟,两轮间隔 15 分钟。整个面试流程让人感觉到很人性化:在 Google 签到后,HR 会先带你去 kitchen 拿点吃的喝的,然后把你带到面试所用的会议室。多轮面试的话,HR 中间还会来问一下你要不要去洗手间,或者多拿两瓶水。面试完毕后 HR 会来问你感觉如何,同时也会让你知道面试官的初步反馈是否跟你的感觉一致。我在 3 轮面试中有一轮感觉不太好,因为面试官只给了 2 道题,并且我最终都没办法解出来,HR 也确认了就是这一轮的反馈不好。

此外,Google 的招聘流程还让人感觉到很有效率。作为面试官,我也知道自己写面试反馈有多喜欢拖延,而且公司填写面试反馈的系统越不人性化我就越想要拖延,然而公司内部系统做得人性化的又实在罕见。Google 的面试基本上隔天就有结果,然后 HR 就会约下一轮的面试。因为我在百度的时候每周哪个时间没有会议是很确定的,所以我总是选择下周同一个时间段来面试。在经过总共 4 轮面试后,HR 说因为前面有一轮的面试官反馈不好,所以希望再加一轮面试。因为前面反馈不好的面试官比较 senior,所以这次找了一位同样 senior 的面试官来面试,于是我又去了一次 Google 办公室。

完成 5 轮面试后,HR 把材料提交给 Google 的北京招聘委员会,结果没有通过。HR 说,因为 Google 都是按照后端工程师的标准来招聘,看重算法和数据结构,前端工程师要通过不容易。因为 Google 没有专门的前端工程师,只有一个软件工程师职位,所以所有人还是必须按照一个标准来衡量。她问我如果找到专门需要前端工程师的团队,并且需要额外再面试的话,我是否感兴趣。当时 Google 是我的第一选择,我当然说感兴趣啦。

后来 HR 跟我说,她帮忙问过 Google Maps,可惜对方说不要专才只要通才。又过了几个星期,HR 发现 IME 需要专门做前端的人,于是帮我再约了一轮面试。这轮面试是在 Google 办公室做的,但实际上是视频会议,因为面试官在美国。(不确定面试官是在美国出差,还是美籍华人。)面试过程跟电话面试类似,用 Google Docs 写代码,比电话面试要好的是说话时能够见到人。

这一轮面试结束后,我的材料再次进入 Google 的北京招聘委员会。HR 说这次专门找了对前端有经验的人来审阅我的材料,结果顺利通过了。接着 HR 问我要了一大堆的补充材料,包括高考成绩和 GPA(连同成绩单),还包括当前薪酬和竞争对手的 offer(我当时有 Yahoo 的 offer),甚至包括过去的获奖和晋升经历。所有这些材料都会发往 Google 美国总部审阅,具体流程 HR 没有细说,但看 Don Dodge 的文章可以了解一些。最后我被 Google 美国总部给拒绝了,然后 HR 还是一如既往地及时沟通,并且安慰了我几句。

整个 Google 招聘流程下来,可以感觉到人性化和高效率,同时也能感觉到 HR 确实在很努力地为候选人争取机会。可以说,无论是否通过,Google 招聘流程至少能给候选人一个很好的印象。据我所知,尽管 Google 声称全球招聘标准一致,但因为中国聪明且懂算法的人实在太多,所以难度更高是很正常的。能够在 Google 中国以外的地区应聘的话,应该会容易一些。

如果你对《面试体验》系列文章感兴趣,欢迎订阅我的博客,或者是在 Twitter 上 follow @CatChen。我承诺我接下来一定会完成 Microsoft、Yahoo 和 Facebook 相关的三篇文章。此外,我的博客也接受捐赠,通过信用卡支付,金额随意($1 起)。

最后是广告一则:如果有任何曾经参加过豌豆荚(豌豆实验室)面试的人愿意写类似的面试体验文章,欢迎联系我并把文章地址发给我,我愿意在我的博客上以链接的形式进行分享。

更新:Microsoft 篇》、《Yahoo 篇》和《Facebook 篇》已发布。


2012年7月28日星期六

华附高中奥班 2012 级经验交流

Benny 在知道我回广州后,跟我预约了 2012 级高中奥班的入学培训时间,让我去分享经验。然后我就开始想,有什么事情是我已经习以为常但高中入学时又特别想要知道的。我把我想到的点都先以大纲的方式写了下来,然后调整了一下顺序,最后使用 iPad 上的 Paper 把我觉得通过图像来表达更有效的信息画出来。

Untitled

开篇第一个问题自然是「我是谁」。

Untitled

我 1998 年进入华附奥班,2004 年毕业离开,随后经历了中大、百度、Yahoo 全球研发中心和豌豆实验室。在华附的时候,高一黑过华附论坛,从此开始做华附论坛管理员,并且创立了华附学生网。高二高三拿过 NOIP 省一等奖,然后保送中大。进入中大后负责成立 Maxcell 东校区分会,到大四的时候由于写博客而获得微软 MVP,随后又多拿了两次。

Untitled

不过,对于华附高三的老师来说,可能我让他们印象深刻的是另外一件事。2004 年华附高考重点上线率为 89%,但升重率为 91%,那就是说有 2% 的人高考没有过终点线,然而又进入了重点大学,我就是其中那 2%。当然,我一个人产生不了 2% 那么大的影响力,只是其中之一而已。

Untitled

接下来进入正题,我想讲的第一个点是「vision」。如果站在 2012 年往前看,你能看到 2018 年吗?有什么是你现在开始努力能在 2018 年获得的?你所在的领域 2018 年会需要什么样的你?

Untitled

我在华附初中时就看到校内的 3 大主流媒体:负责黑板报的华通社,还有广播站和电视台。尽管当时还没有接触到互联网,但我相信计算机能够带来下一个主流媒体,所以没有加入 3 大社团的我一直在寻找建立自己社团的机会。

Untitled

我中学的时候就知道有 MCSE,同期中国也开始评选 MVP。哪一样长远来说更有价值?当然是 MVP,因为 MVP 的数量是有限的。尽管国内有很多机构能够让你不怎么学习也能获得 MCSE,而 MVP 却需要 Microsoft 员工或 MVP 提名,但只要有实力总是有机会的。实力不是短时间能够获得的,但长远来说有实力才是最重要的。

Untitled

讲完「vision」下一个点自然是「opportunities」。你看得到你的目标,但你不知道合适的机会在哪里以及什么时候发生。你需要坚持下去,不断尝试,直到找到合适的机会为止。

Untitled

在我直觉认为第 4 大主流媒体一定会出现的时候,我尝试过用计算机做不同的事情。初中的时候试过用 Word 来做出版物,也是过在图书馆的多媒体阅览室(俗称「网吧」)做过管理员,直到高一黑了华附论坛接过管理员账号,才开始为学校做网站。高二做了华附学生网,实现了建立第 4 大主流媒体的梦想,尽管最终被证明网络在华附里不那么主流,因为那个时候大家不可能有那么多时间使用电脑,智能手机也还没有普及。

我一开始在 Blogger 上写技术博客,但一直没什么人来看。后来我把内容导入到博客园,迅速来了不少读者和高质量的评论,接着 dudu 问我有没有兴趣申请评选 MVP。这也说明了,坚持做同一件事情,也要遇到合适的机会才会成功。

Untitled

我想说的下一个点是「right people」。有正确的人将能够帮助你事半功倍。

Untitled

「知识就是力量」,别人的知识也是你的力量。在奥班读书最大的优势就是你周围的人都足够聪明,他们的知识都能为你所用。这显然包括你的专业知识,例如说一道题一个人要想出多种解法来不容易,但在奥班很容易就能发现不同人有不同的解法。这也包括不显眼的非专业知识,例如奥班因为保送的人多而在这方面积累了足够多的知识,从而帮助你更好地挑选学校,更有策略性地申请学校。

Untitled

从学习效率来说,最高的当然是有老师指导的训练,其次是小组学习,最次是自学。如果能找到正确的老师,他使用恰当的方法来为你提供训练,你的学习效率能够比别人高很多倍。在奥校学习就是一个很实在的例子。

Untitled

500 年前,中等数学还是只有数学家才会花毕生精力去研究的东西,一般人都不会去学,现代教育却确保了人人都能在 20 年之内学会。至于竞赛所需要使用到的高等数学知识,500 年前甚至还没出现,是 17 世纪后期才有的。可见正确的人给你正确的训练有多重要。

Untitled

要选择相信你能成功并且愿意帮助你成功的人在一起。这也是一流公司招聘时常说的话。一流的公司不会说「我们这里缺一个人力资源单位,正好你能胜任,所以你来填充这个位置吧」。他们会说「我们觉得你在我们公司能够获得成功,面试过你的人也愿意跟你合作帮助你成功」。我觉得我在百度时最幸运的就是遇到愿意帮助我成功的人。我想要做什么样的项目,经理会给机会我去了解。我觉得写博客是我的长处,经理给机会我去成立官方博客。

Untitled

最后,有两点是需要特别注意的。一方面,你不能被你的不安全感驱使。精英教育会使得你害怕平庸。你能够把事情做得很好,但是你拒绝把事情做得很一般。最终这也会成为一种无能,就是你不能把事情以一个很低的成本随便处理掉。

Untitled

另一方面,你也不能被你的贪婪驱使。接受精英教育的结果是,你确实能够为自己赚取越来越多的东西,但你总会低估你长远目标所需要的成本,结果一旦精力分散了就可能无法完成长远目标,这真的是得不偿失。

Untitled

最后附上这次演讲所用的幻灯片 PDF 下载地址。这是我第一次完全使用 Paper 来绘制一份幻灯片,所以我希望分享出来让大家看看。其实纯手绘的幻灯片能够做出非超好的效果来,关键还是你要能把想法转变为易于传达信息的画面。

2012年6月16日星期六

HTTP 状态码详解

最近看《REST in Practice》,发现 HTTP 如此之多的状态码都有各自的含义,要准确使用并不难,但现实当中很少人能够做得到。大多数人熟悉的状态码就那几个,平时也不会去阅读 RFC 2616,结果反复使用的也就是那几个状态码。其实很多 REST 中可能遇到的情况,在 HTTP 状态码中都已经有考虑到,不需要自己去发明新的状态码,也不需要在 header 或者 body 自定义错误信息。

在说状态码之前,首先建议大家还是先阅读一下 RFC 2616 中的相关章节,看看已有的状态码描述都是什么。我相信有部分状态码是你看了描述也不知道用来干什么的,这时候就需要有更具体的例子来告诉你怎么用了。(我准备详细说的是那些比较少人知道但又实际上应该被更广泛使用的状态码。)

2xx

200 OK

所有人都知道 200 OK 是什么。这估计是最经常被滥用的状态码。很多人在应该使用其它 2xx 状态码时都选用了 200 OK 来表示。

201 Created

如果你在设计一个 REST API,或者一个 CRUD API,当你使用 POST(或者 PUT)成功创建一个新的资源后,服务器应该返回 201 Created 同时在 header 的 Location 字段给出刚刚创建好的这个资源的 URI。

例如说,如果你使用 POST 请求通过 \comments URI 创建了一个新的评论,那么服务器应该返回 201 Created,同时带上形如 Location: \comments\1234 的字段表明新创建的评论的 URI。

202 Accepted

如果服务器在接受请求后,需要进行一个异步处理才能有结果,并且觉得不需要让 TCP 连接保持到结果出来再返回,它可以返回 202 Accepted,意思是请求已接受,但没有立即可返回的结果。

204 No Content

在一个 REST API 或者 CRUD API 里面,当你使用 PUT 成功更新一个资源后,如果服务器完整接受了客户端的更新,没有拒绝也没有额外更新,那实际上是不需要返回任何东西的,因为现在客户端和服务器端已经拥有完全一致的状态。在这种情况下,服务器可以返回 204 No Content,同时 body 为空,客户端就知道它的状态已经跟服务器端同步了。

206 Partial Content

断点续传和多线程下载都是通过 206 Partial Content 实现的。

请求的 header 必须包含一个 Range 字段,表明自己请求第几个字节到第几个字节的内容。如果服务器支持的话,就返回 206 Partial Content,然后使用 header 的 Content-Range 指明范围,并在 body 内提供这个范围内的数据。

3xx

301 Moved Permanently

永久性重定向。目标由 header 的 Location 字段给出,同时 body 中也应该有指向目标的链接。新请求使用的方法应该和原请求的一致。如果用户使用 HEADGET 以外的方式发起原请求,客户端在遇到 301 Moved Permanently 后应当询问用户是否对新的 URI 发起新请求。

302 Found

临时性重定向。

这应该是浏览器实现最不符合标准的一个状态码了。理论上,除了临时性这一点,302 Found301 Moved Permanently 应该是完全一样的。然而实质上,很多浏览器在遇到 302 Found 后就会使用 GET 去请求新的 URI,而无论原请求使用的是何种方法。由于这种现象的普遍存在,使得这成为了一个与书面标准相违背的事实标准,新的客户端在实现时很难选择应该遵守哪一个标准,所以 RFC 2616 专门新增了 303 See Other307 Temporary Redirect 两个状态码来消除二义性。

303 See Other

临时性重定向,且总是使用 GET 请求新的 URI。

304 Not Modified

如果客户端发起了一个「条件 GET」,同时资源确实没被修改过,那么服务器端就应该返回 304 Not Modified,同时 body 不包含任何内容。

所谓的「条件 GET」,是指 GET 的 header 带上了 If-Modified-SinceIf-None-Match 字段。这两个 header 就是「条件」,如果条件符合了 GET 就应该正常执行,否则就应该返回 304 Not Modified,以便告诉客户端它想要请求的资源在上一次请求之后没有被更新过,客户端可以继续使用之前的版本。

307 Temporary Redirect

临时性重定向,且总是使用原请求的方法来进行新请求。

4xx

400 Bad Request

服务器无法理解请求的格式,客户端不应当尝试再次使用相同的内容发起请求。

401 Unauthorized

请求未授权。如果请求 header 没有 Authorization 字段,服务器端应该在返回 401 Unauthorized 的同时在 header 中用 WWW-Authorization 字段指出授权方式,以便客户端带上登录信息重新发起请求。如果 Authorization 字段已经存在,则表明登录信息不正确。

402 Payment Required

需要支付。这是一个在任何浏览器中都没有被实现的状态码,仅预留将来使用。

百度曾经有一个部门印过一批背上写着 402 Payment Require 的衣服,并且开玩笑说这批衣服最适合在互联网企业员工讨薪时穿。

403 Forbidden

禁止访问。即使使用 Authorization 字段提供登录信息也会得到相同的结果。

如果客户端使用 HEAD 以外的方法请求,403 Forbidden 必须同时在 body 中返回禁止访问的原因。如果原因不能够公开,则应该使用 404 Not Found

404 Not Found

找不到如何与 URI 相匹配的资源。服务器无需指出资源是临时性不存在还是永久性不存在,但如果服务器端知道该资源已经被永久性删除则应该返回 410 Gone

404 Not Found 是服务器端在不愿意提供理由的情况下拒绝提供资源的最佳借口。

405 Method Not Allowed

请求的方法被拒绝。

如果你有一个 REST API 或 CRUD API 被设计为只读,那么在遇到 PUTPOST 或者 DELETE 方法时服务器端都应该返回 405 Method Not Allowed,同时在 header 的 Allow 字段说明允许的方法(如 GETHEAD)。

409 Conflict

冲突,且需要用户手工解决。

如果你使用 git(或者其他源代码管理软件),你已经知道「冲突」是什么了。409 Conflict 通常发生在 PUT 请求时,如果要更新的资源已经被其他人更新过了,你再更新就可能产生冲突。

410 Gone

如果服务器端将此资源标记为已被永久性删除,则应该使用 410 Gone 而非 404 Not Found,其用意在于告诉客户端资源是被有意删除的,而且删除是永久性的,客户端不应该再保留这个 URI 的链接。

举例来说,你有一个 REST API 或 CRUD API 用于向用户提供优惠信息。有一则优惠的 URI 是 /promotions/1234,但由于优惠活动已经结束了,所以这一则优惠信息不再有效且应当被永久性删除,那么这时候服务器端就应该让该 URI 永远返回 410 Gone 了。

412 Precondition Failed

条件判断失败,操作不会被执行。

在解释 304 Not Modified 时提到了「条件 GET」的概念,但「条件」本身也可以应用于非 GET 请求,这时候如果条件判断失败服务器端就应该返回 412 Precondition Failed,同时拒绝执行客户端请求的方法。

条件请求可以被看作是一种乐观锁。它不需要服务器端有任何逻辑判断操作是否存在冲突,服务器端只要记录资源的时间戳(或其它版本信息)即可。

5xx

500 Internal Server Error

最常见的服务器端错误。

503 Service Unavailable

服务器端暂时无法处理请求(可能是过载或维护)。

返回 503 Service Unavailable 的意思是当前的状况是临时性的,客户端可以稍后重试。服务器端可以在返回时通过 header 的 Retry-After 字段告诉客户端多久后可以重试。如果不提供这个字段的话,客户端应当把 503 Service Unavailable 等同于 500 Internal Server Error 处理。

总结

在看完这篇文章后,你有发现经常用错的状态码吗?如果有的话,将来在设计 REST API 或 CRUD API 时就应该改过来。由于状态码和 header 字段数目众多,所以我建议一般用户尽可能复用主流的 REST 框架或 CRUD 框架,而不要自己重新实现一遍。

如果你喜欢这篇文章,欢迎订阅或者捐赠。如果我有时间的话,我会再写写常见的 header 字段详解。

2012年6月9日星期六

向我的博客捐赠

阮一峰一年前说他的 AdSense 账号被封了,所以尝试接受捐赠,并且说一年后会公布收入详情的,不过至今仍未公布。考虑到现在支持小额支付的平台越来越多,所以我决定也试试接受捐赠,看看是否能带来收入。

看了几个不同的平台后,发现最简单易用的还是 Gumroad。有些专门针对内容收费的平台(如 ReadabilityFlattr),需要读者先去注册,然后在阅读过程中支付。考虑到我的读者里面没有多少比例可能注册过这些网站,这种平台首先被否决掉。至于专业做小额支付的平台,我觉得 Gumroad 是界面最好看流程最简单的了——你只要填个邮箱和卡号就可以支付了。当然,Gumroad 也有不如意的地方,那就是它不支持 PayPal 支付,估计是因为再加上 PayPal 手续费它就没有利润空间了吧,但这会使得不愿意提供信用卡信息给 Gumroad 的人拒绝支付。

如果你觉得我博客提供的信息对你来说有价值,你可以使用我的 Gumroad 链接进行捐赠。Gumroad 还有一个优势是可以设置价格为 n+,最终买家可以输入任何大于 n 的价格进行购买。我的捐赠链接设置为 $1+,大家觉得给多少合适就填多少。

2012年6月8日星期五

关于「如何追男生」的回答摘要

之前写了《关于「如何追女生」的回答摘要》,然后就有人问我有没有反过来的版本,所以我决定把 Why Men Love Bitches 的读书笔记整理出来写一篇文章。

先说说女人跟男人看待这个问题的本质区别。看 PUA 的资料,你会发现一切方法论皆以扑倒为终结点,随后的事情大家都觉得是不需要教的。但是给女人看的书就不一样,如果吸引男人的兴趣只是内容的一小半,另外一大半则是关于如何保持男人的兴趣。(我之所以感兴趣看 Why Men Love Bitches,也是因为好奇后面这一部分内容,因为 PUA 资料都不会覆盖到这些话题。)需要注意的是,这本书是假设女人采取 submissive 姿态的,所以如果你习惯了以很 dominant 的姿态来对待男人,这本书里面的部分观点对你来说并不适用。

自信

自信这一点是性别无关的,所以无论站在男人的观点来看还是站在女人的观点来看,自信都是放在第一位的。我觉得英文里面描述「自信」最好的一句短语是「comfortable in one’s own skin」,展开的解释比中文抽象的「自信」二字要具体多了:

表现出对自我清晰了解而带来的自信,没什么能让你感到不适。

自信跟盲目自大的区别就在于「对自我清晰了解」,因此要做到自信首先要增加对自我的了解——什么是我的强项,什么是我的弱项。对于强项,你应该尽可能多的发挥出来,尤其是在能够表现你特质时;对于弱项,你不应该表现出有任何的安全感缺失,就算别人指出了你也能坦然承认不会反应过度。

对于女人来说,缺乏安全感是很常见的,但为了表现出真实的自信,这又是一个很有必要解决的问题。通常的安全感缺失来源来自竞争对手(例如对方的前任),这时候如果你表现出受到威胁了,你的吸引力就会消失,同时你还帮助了你的竞争对手。

书中说,为什么大多数女人要到 30 岁后才能达到 sexual peak,那是因为到她们要到那个阶段才开始有足够的安全感,或者是意识到自己其实是不需要跟任何女人竞争,然后才能轻松地表达自己喜欢什么和不喜欢什么。但因为能做到这一点对男人来说是个 turn-on,所以显然你不会希望自己等到过了 30 岁然后克服安全感缺失。

满足男人成就感

这在书中也是一个很重要的话题,所以说这本书适合 submissive 的女人看,然后驱动男人在 dominant 的同时获得成就感。由于征服是人类本能,所以并不是说你应当可以刁难谁,而是你需要让他通过征服的过程获得满足感,因为这才符合人性。只要他觉得他对你来说还是 sexually desirable 的,同时他觉得自己还有机会,他就会继续接受新的挑战。

不要侵犯男人领地

男人的「自由」也就是他的「领地」。他想什么时间去哪里做什么,这是他的自由,你不应该八卦。一旦问了,就是侵犯对方领地了。就算你安全感缺失,有些事情你很想知道,你也应该控制住自己,因为在他爱你爱到一定程度之后他会很乐意主动告诉你这些事情。如果你想抛弃一个男人,你可以反过来使用这条规则。在他感觉到窒息的时候,他自然会远离你。

赞美而非唠叨

赞美是最好的调教方法,而唠叨则是毫无疑义的。不要说「你以前会送花给我的」,而应该在他送花之后说「这是我见过最美的」。如果男人请你吃饭,你要赞美食物或者餐馆。(如果真的不好吃,就别作任何评价。)无论如何都不要尝试通过唠叨来解决问题,因为一旦某一件事情你重复说三次,你就变成他母亲了,而他就会表现出反叛精神。

保持吸引力

PUA 的目标很单一,甚至可以说大多数男人的目标都很单一,那就是扑倒。所以作为女人总是会很在意如何保持吸引力。其中一个方法是,男人想要的东西不要一次性满足他,而必须一点一点提供给他。在这个过程中,你可以让他更好地了解你,慢慢地发现你的特质,他会发现你还有更多他想要的东西,然后他就会想要继续在你身边。

如果你发现他开始对你丧失兴趣了,那意味着你需要去寻找一件比他更能让你充满激情的事情。只要有一件事情对你来说显得比他还重要,他就会想要回到你身边。

总结

以上是我觉得 Why Men Love Bitches 里面比较有意思的论点。因为我是从看过那么多 PUA 资料的角度出发来看这本书的,我的目标主要是看女人观点和男人观点的异同,所以可能我看到的跟对你有帮助的不一定重合。我看过很多的 PUA 资料,但我看过写给女人看的这方面的书就几本,这本是我觉得写得最好的了,所以我会建议你去看原书。

此外,如果你觉得我写的文章对你有价值,欢迎订阅或者捐赠

2012年6月6日星期三

关于「如何追女生」的回答摘要

因为时不时就有人会问我「如何追女生」或者是「如何追某某特定女生」的问题,我又不像某些 dating coach 一样靠教这个赚钱,所以就决定把我知道的写下来。如果以后有谁还要问我相似的问题,可以先看这篇文章的内容再说。

尽管我几年前看过很多 PUA 相关的书籍和资料,但大多都不记得了,所以我不准备写很复杂的内容。由于原本我是想要基于 The Natural 的读书笔记写一篇书评的,所以这篇文章主要还是基于 The Natural 的读书笔记来写。

自信

无论你想要的是工作还是女人,第一条建议总是「自信」。(关于工作你可以参考另一篇文章,所以这里只说女人。)自信与否起到了关键作用,这可以从两个不同的角度来解释。

一方面,你可以认为自信会影响到你的发挥。如果你相信自己能拿 90 分的话考试成绩可以有 95 分,但如果你觉得自己拿不到 90 分的话考试成绩可能只有 85 分。这种现象也叫做 self-fulfilling prophecy

另一方面,自信也是一种说服力。The Natural 里面说到,当几个拿不准主意的人在尝试说服别人时,谁越坚信自己是正确的谁越能说服别人。这看起来很 tricky,但其实很合理——如果所有人都是理性的,并且所有人都如实表现自己拥有的信息的置信度,自然置信度最高的胜出。因此,跟其他人在一起时,包括跟女人在一起时,如果你确信你的经验能做出正确的判断(那家西餐厅很好吃所以我们去那里午餐),你就应当自信地宣布这个判断。

首领气质

在 PUA 的社区里,你会经常听到「alpha male」这个词,意思是动物群族中的雄性首领。PUA 还会强调要成为雄性首领,那是因为在很多哺乳类动物中,雄性首领拥有在群族中优先获得食物和配偶的权利。尽管人类社会已经进化到没有明确群族首领的阶段,但能够展示首领气质的男人还是在吸引女人方面有优势的。

在 The Natural 一书当中,作者认为以下 5 种特征属于首领气质:

  • 强烈的自我信念
  • 强壮的体格
  • 领导和抉择的能力和意愿
  • 在高压环境下保持泰然自若
  • 社交智商(与他人结识和沟通的能力)

至于如何培养这些气质,因为这是篇很概览性的文章,所以我不准备细说,想知道的人还是去看原书吧。

3 个阶段

The Natural 把追女分作 3 个阶段:

一开始你应该是 Mr. Sociable。你很善于交际,无论对方对什么感兴趣你都能说半天;就算对方不对任何事情感兴趣,你还是能分享很多有趣的事情。同时你还能传递正面能量,让人想要跟你聊天,跟你进行更多互动。

然后你变成 Mr. Comfort。能让人感觉到你像一位认识多年的老朋友,可以很放松地跟你聊任何事情,发生肢体接触也不会有任何不适。

最后你要成为 Mr. Seducer。逐步展现你的欲望,让对方感觉到你逐步被她吸引住了。

这 3 个阶段基本上跟 The Mystery Method 里面提到的模型是一致的,只是名字不一样:Attraction、Comfort、Seduction。

这 3 个阶段发生的顺序不对,或者漏了其中某一阶段,或者某一阶段做得不够好,都会导致结果不如人意。例如说「让我们继续做朋友吧」通常就是因为身陷第 2 阶段,不敢展现自己的欲望,结果也无法进入第 3 阶段。

训练勇气

要做对上面所说的,必然需要一些勇气。例如对于内向的人来说,如何慢慢地展现自己真实的欲望是很有挑战的事情,但如果你跨不过去你就永远停留在「让我们继续做朋友吧」这一阶段。The Natural 说了一件其它 PUA 材料没有提及的事情,那就是勇气是可以被训练出来的。

有些事情你因为感觉不舒服所以总是回避,但如果你能在确认安全的情况下多练习几次,你就会发现结果其实也不会有多糟糕,然后你不舒服的感觉就会开始消失。继续练习,你就习惯做这件事情了。这个道理很简单,但它能帮助你克服很多存在已久的毛病。

总结

上面是 The Natural 里面几个我觉得比较重要的点,如果大家觉得有价值的话,建议还是去读原书。对于入门的人来说,可能书中一些我认为很平常的点对你更有帮助。

P.S. 我知道肯定有人要问有没有「如何追男生」版本的了,所以我把 Why Men Love Bitches 的读书笔记整理成一篇《关于「如何追男生」的回答摘要》。

2012年6月4日星期一

三藩市湾区一周游

上个星期四一早从广州飞往三藩市,到这周三中午返回,大约一周时间,在三藩市湾区充满拜访了多位好友,也参观了几家互联网公司。还记得两年前去三藩市湾区,遇上暴风雨,不仅景色没看到,还让人担心自驾安全。这次旅行途中南湾阳光明媚,就算是三藩市也只是有雾而已,没有遇到下雨的情况,感觉好多了。

5/24

因为 Delta 取消了广州到东京的航线,所以尽管这次还是经东京转机,但飞的是 ANA 和 United。到达三藩市机场后,我做的第一件事情是把手机掏出来搜索机场免费 Wi-Fi。两年前的三藩市机场好像是没有免费 Wi-Fi 的,但我很确信现在是有的。找到 Wi-Fi 后先用 Foursquare check-in 一下,再打开 Twitter 回复了几条 tweet,然后才尝试连接手机漫游网络。很奇怪的是,我的联通 3G 卡开了漫游后到美国竟然连不上任何一个网络,AT&T 和 T-Mobile 都不行。无奈之下只好先打车去酒店。

头两天住的是 Sheraton Palo Alto,从机场打车过去要半个小时,花费 $100。酒店环境很不错,有泳池有喷泉,靠近 Caltrain 站同时 Stanford Shopping Center 也在步行范围内。住进酒店后,第一件事情当然是使用酒店 Wi-Fi 连接 MacBook Air,结果才发现连接 iPhone 时要再次收费。打电话给酒店的技术支持,对方说跟前台说一声就会撤销重复扣费,我想了想决定将来还是用有线再共享好了。

既然联通漫游失败,接下来肯定要买一张电话卡,否则离开 Wi-Fi 后连打电话联系朋友的能力也丧失了。根据几位之前来过美国出差的朋友推荐,购买 T-Mobile 每天 $3 的预付费卡是最合适的了,拥有无限量的电话、短信和流量,而且每天的前 200MB 流量还可以有 4G 网络的速度。幸好 University Avenue 上就有一家 T-Mobile Store,散步过去花 $30 买张卡够用一星期了,然后再散步回来顺便看看周围的环境。

晚上三藩市的朋友开车过来接我去晚餐,吃的是 San Mateo 的 Hokkaido Seafood Buffet北海道布斐)。在美国吃这类自助餐最好的地方在于分量充足的同时价格也不贵,$10 到 $20 能吃到的东西在中国至少要吃 ¥200 一位的自助餐才可能提供。

5/25

第二天去 Facebook,进门后见到室内的涂鸦就觉得很有创意。据说是请了知名涂鸦画家 David Choe 来画的,当然也有 Facebook 员工的作品。Facebook 现在用的园区以前是 Sun 的,Facebook 重新装修后只启用了其中的几栋楼,例如说工程师都在 Building 16,其余的楼房都还是空的。园区内只有一条主干道,因此不会迷路也不需要地图来导航。

Facebook 内部有趣的东西很多,例如 Building 16 里面的开放区域有几台街机,甚至还有一台带笔记本电脑支架的跑步机——你可以随时把电脑放上去接好线,然后就可以一边跑步一边工作。墙壁上的大屏幕是室内导航图,而且还带有会议室查询功能,点两下就能知道哪些会议室在用,或者你要进行会议的会议室在哪里。此外,为了降低 IT 服务成本,如果员工想要领取外设硬件,连 IT 也不需要去麻烦,在贩卖机上刷一下卡就能拿到。

Facebook 园区内最显眼的地标是 Hacker Square,据说在广场上写一个大大的「HACK」字,就是为了确保 Google Maps 的卫星图(或航拍图)能够拍到。每次 Hackathon 举行前人们都会聚集到这个广场上,然后 Zuck 以敲钟的形式宣布这场 Hackathon 的开始。

从 Facebook 回来后,我约了在 Stanford 读书的中学师弟出来吃饭,然后我先步行去 Stanford Shopping CenterApple Store 买 Gift Card。晚饭是在 The Counter 吃的,汉堡的牛肉和炸洋葱圈都很美味。晚饭后去了一趟师弟的宿舍,帮他解答作业上的一些 JavaScript 和 Rails 问题,结果一直折腾到凌晨两点才结束(他很悲剧地又要消耗 late day 了)。

5/26

离开 Sheraton Palo Alto 后,我搬到了三藩市朋友家住,在 Balboa Park 附近,搭乘 BART(地铁)和 MUNI(轻轨)都很方便。感谢当天 @liuxi 开车过来帮忙搬东西,在南湾这样乡下的地方没有车真的很不方便。那天还去了 Hong Kong Saigon Seafood Harbor Restaurant(西贡渔港)喝下午茶,见到了 @iveney。西贡渔港的出品很不错,据说中大有些同学很喜欢来这里吃,没时间在这里吃也打包带走。

喝完下午茶,去参观了一下 Google。在做 Android 的 Building 44 门外能看到一堆代表不同 Android 版本的大公仔,里面有大屏幕的 Android 设备(类似 Apple Store 的大 iPhone)可以用来试玩。我跟 @liuxi 想要试试如果对着展示设备的 Gmail 发邮件会怎么样,结果发现发不进去,而在展示设备上的 Gmail 中写邮件也发布出来。难道是机器没有联网?但网页和 YouTube 都能正常打开。估计早就有人想到我们会这样子搞,所以提前封锁了 Gmail 的访问,使得展示设备的 Gmail 处于离线状态。

Google 的访客中心还有一些有趣的展示品,例如一台由 8 块大屏幕组成的全景 Google Earth。由于 Google 的园区比较大,所以不像 Facebook 那样是封闭的,建筑物之间会有公路,跟其他公司的建筑物之间也没有围墙分隔。

5/27

住朋友家的第二天,跟着他们一家去了 South Sea Seafood Village(南海渔村)喝茶,虽然感觉没有西贡渔港那么正,不过至少做得还算正中。随后跟着他们去购物,买了几条廉价的 Levi’s(相对国内廉价),然后在 GameStop 买了 Forze 4,在 BestBuy 买了 Xbox 360 用的 Wireless Speed Wheel。虽说这样买比在 Amazon 贵大概 10% 到 20%,而且还要多交近 10% 的税,但既然出来购物了那就买吧。见识过这样的定价,才明白为什么实体连锁店都快要倒闭了。

下午去了 Half Moon Bay,可惜有阳光的时间比较短,大多数时候都是阴天。看了一下港口的渔船,据说 11 月份的时候蟹只卖 $2 一磅,好像很便宜的样子。往返的路上还路过了 Crystal Springs Reservoir。据说水库里的水部分来自 Yosemite(优山美地),现在专门提供给三藩市作自来水用。

晚饭是去 Mountain View 跟 Changhao 吃的,回三藩市时有了人生第一次搭 Caltrain(火车)和 BART 的经历。感觉 Caltrain 比 BART 要干净,而且因为搭乘时间比较长所以列车上还有洗手间(整列车只有一个)。BART 作为地铁,有闸机验票,而 Caltrain 则只有随机检票。一般情况下,Caltrain 只要在站台买票然后上车就是了,我搭的这一次 Caltrain 没遇上检票的。这一次从 Mountain View 搭 Caltrain 到 Millbrae 用了近一个小时,而 Millbrea 搭 BART 到 Balboa Park 又用了半个小时,加上等待和步行时间差不多两个小时。这再次说明了在南湾没有车是很不方便的。

5/28

Memorial Day 长假期的最后一天,经过 @diamondtin 的介绍,受 @lordhong 邀请去他们家作客。去之前的先去了一趟 Gilroy 的 Premium Outlets,逛了一圈最终只买了两条牛仔裤和两瓶鱼油。其实我还看中了 Sony 的 Blu-ray 3D Player,只是考虑到电压不兼容,还可能有分区锁,想想还是算了。打完折的价钱(好像是 $90)换算成人民币也很划算,在中国支持 3D 的蓝光播放器都不止这个价钱。

去到 @lordhong 家,第一次见到美国的 house 里面是什么样子的,有花园有车库的感觉还是很爽的。我们整个下午就在花园里晒太阳聊天,感觉美国人家的午后生活真的很写意啊。随后是海鲜大餐,感谢 @lordhong 夫妇提供的生蚝和螃蟹,这一餐吃得真爽了。这次的聚会还见到了 @delphij 夫妇、@jinghuaz 夫妇以及 @jasonlai,聊了各种有趣的事情。

吃完一餐,又要赶场下一餐。@essej 开车过来接我,去 Chef Yu(岳阳楼)吃饭,顺便见见在 Sunnyvale 忙于准备面试的另一位中学同学。(其实还有第三位中学同学,据说因为在女生当中太过 popular 而总是约不到,当天晚上他也没时间出来。)吃完饭后去 @essej 在 Palo Alto 的住处看了一下他租的 house 是怎样的。$1400 能够有独立的卧室和浴室洗手间,共享的客厅和厨房,我觉得不错啦。类似的空间在北京没有 ¥4000 你都不用想了,但你在北京的收入数字部分真的能做到 Palo Alto 的 3 倍吗?

5/29

在三藩市最后完整的一天,我选择了去参观 GREE,顺便体验三藩市室内公共交通。由于出门就有 MUNI 的 KT 线,所以上了 K 车后我也没怎么考虑 K 车和 KT 线到底是什么关系。话说 MUNI 的这些有轨电车很可爱,在路面上看就是有轨电车,但是进入隧道后感觉又好像地铁。中午跟 @jasonlai 到 Polo Grounds 吃汉堡(我又是冲着大块牛肉来的),下午到 GREE 看了一下,然后就准备搭 MUNI 往回走了。结果发现在我下车的站台上,反方向来的只有 T 车,那我到底要上不要上呢?一犹豫就错过了一辆车,接着下一辆车要 20 分钟后。好吧……无论下一辆是 K 车还是 T 车我都上,搭错了再说,反正我有 Google Maps,不信我找不到路回去。结果下一辆来的还是 T 车,走的路线则跟来时正好相反。(到现在我还没明白 KT 线和 K 车、T 车是什么关系。)

到倒数第二个站停车后,司机就不关门也不往前走了。看到前面轨道上停着一辆有轨电车,司机说估计 5 分钟内不可能解决问题,想要下车的人可以在这里下车。想想我只需要步行一个站,迅速选择下车往前走,发现前面堵了好几辆有轨电车,最后发现某个路口出事故了,警察在处理,两个方向的有轨电车都停下来等待。看来三藩市的公共交通系统还是挺脆弱的。

5/30

最后一天自己搭 BART 去机场,再经东京飞回广州,一路都很顺利。比较搞笑的是,到东京后被告知我们的飞机早到了,分配给我们的廊桥还被上一个航班占用着,机场正在跟他们沟通,希望他们能够准点离开。此外,在三藩市机场的时候我又忍不住花钱了,买了一本 PC Gamer 和一本叫做 The Zombie Survival Guide 的书。买前者是因为它的封面(有 Company of Heroes 2 的内容哦),买后者是为了在飞机上打发时间(结果在飞机上只看了一章,然后看了 3 部电影)。

总结一下,尽管这次湾区的路行安排有点匆忙,很多朋友也是临时约的,但抵达后能够见到那么多的朋友也实在是幸运。尽管没有车确实有点不方便,但朋友们提供了不少帮助,结果还是去了不少地方长了不少见识。这次唯一的小遗憾是没去看金门桥 75 周年庆典,不过根据去看了烟花的 @diamondtin 所说,看完烟花后人流散去公共交通资源实在稀缺,或许不去确实是正确的选择。

P.S. 在写这篇文章时,一开始地点我都是用 Google Maps 链接的,后来想想还是用 Foursquare 链接比较方便,因为地点我都 check-in 过,去 History 页把链接复制过来就可以了。此外,大家也可以通过 Foursquare 链接看到更多的照片,不仅仅是我发的,也有其他人发的。

2012年6月1日星期五

理想的技术面试过程

作为面试官

从在大学里面试社团大一新生,到加入百度后帮公司面试候选人,我觉得我对面试这件事一直不得要领。百度提供面试培训,也允许参考或使用题库,但我还是觉得不知道如何判断给不给一名候选人通过我这关。偶尔我会遇到非常优秀的实习生候选人,我能十分确定我要给他过,甚至想方设法确保他能来。其它时候,我觉得我的判断随机性太大,或许还不如一枚硬币做得好。

在百度做二面的时候,我往往会问一些组合问题,就是候选人需要有扎实的基础加上一定的解题能力才能做出来的。我假设一面的面试官已经问过基础问题,所以我不会再问基础问题。结果通常是,候选人的基础不够扎实,会作出一些错误的假设,甚至面对组合问题就无从下手,不知道如何分解为更小的问题然后再一步一步来解决。我不知道是否应该期望候选人全部答对,但答对小部分的状况让我无从判断。

为此我开始跟其它人讨论面试经验。Acumon 说应该针对候选人说他擅长的领域来提问,而且使用开放性问题以便了解候选人的思考方式,但我发现我遇见的大多数候选人都不清楚自己擅长什么,或者是他们自认为的强项无法达到我的预期。后来在上海跟 Winter 和 Hax 聊天时发现一个可怕的现实:大多数前来应聘的前端工程师都无法回答「position 属性取值都有哪些」以及「display 属性取值都有哪些」。随后我尝试在我的面试中先问这两个问题,发现确实有些人回答不出来取值,更多人则是无法准备描述常见取值的作用。(我甚至不把 inline-block 和 fixed 归类到常见取值里面。)遇到如此基础的问题都回答不出来的候选人,我通常会跟他告诉他正确答案,再问一些基础问题然后给他一些学习建议,最后很礼貌地送他走。

状况在我到达豌豆荚后有所改善。不是来应聘的人都非常优秀,而是我开始有感觉了。关键原因我觉得是我们不着急招人。现在我们还能应付手头上的工作,也没有快速扩张的需求,所以我们只有在遇到最合适的人选时才邀请他加入。我相信我前面的两位面试官做得足够好,然后我可以放胆地去考量「懂不懂」之外的其它方面。准确来说,这甚至不是一种考量,我只想知道我跟这个人一起工作是否会很愉快。我会以工作上遇到问题时同事跟同事间讨论的方式去跟他进行讨论,有可能是工作上实际遇到的问题,也有可能是刻意设计出来的有趣问题。(我觉得在一个充满活力的工作环境中,同事之间互相找些稀奇古怪的问题来讨论是很正常的,大家也会享受这类挑战。)如果我感觉跟他讨论问题是很愉快的过程,他能够提出有趣的想法,甚至能告诉我一些原本我不知道的事情,我肯定会给他很正面的面试评价。

作为面试者

换个角度来说,如果你作为面试者发现自己在面试的过程中能够进入这种状态,感觉如同跟同事讨论问题一样放松,那么你应该对面试结果充满信心。至少根据我个人的经验来说,感觉如同轻松愉快讨论的面试我都能得到面试官不错的评价。我明白要做到这一点很不容易,很多人在面试时都会很紧张,甚至会假设面试官一定会用各种难题来考自己,这种心态其实会把自己放在不利的位置上。我过去的经验是,我越不在乎的面试,往往我越能放松地跟面试官随便聊,结果反而越好。我很在乎的面试,反而有时候会高估面试官问题的难度,结果简单的问题没给出简单的答案,最后得到的评价反而不好。

在百度大厦的时候,我喜欢穿越二层平台,因为那里有很多小圆桌,也有很多人会选择在那里进行讨论或者面试。如果你对肢体语言有点最基础的了解,或者你就是在这方面有感觉,你会发现在那里很容易看得出哪些桌是同事间的讨论,哪些桌是在面试,以及谁是候选人。同事之间的讨论,往往大家都会很放松;面试的话,面试官也会很放松,但面试者通常处于比较紧张的状态,身体会略为前倾,就算不需要写字也会把手放在桌子上,用于支撑上半身的重量。对于有经验或者有感觉的面试官来说,这种肢体语言会传递一种不好的信号,那就是你太想要这份工作了,就算更深入的沟通后可能你会发现这份工作不适合你,但你还是会追求这个机会。

类似的场景也会出现在异性之间,下次去餐厅吃饭时你可以观察一下隔壁桌的异性交互。就算你完全不知道谈话内容,你也可以尝试从面部表情和肢体语言去判断谁在追谁。为什么有时候你追的人各种虐待你,还有充足的信心你不会放弃?因为他们看得出你把自己摆在什么位置上。在统计学的角度来说,我觉得女人在这方面比男人有优势,因为女人看的电视剧都不是白看的,她们在逛街吃饭时还无时无刻地在观察身边发生的各种剧情。就如同资深前端工程师可以只看 CSS 片段就猜出遇到了什么浏览器 bug 以及作者尝试如何解决一样,有经验的女人(又或者是面试官)要看明白你的立场太容易了。

因此,首先你要把面试看作一个平等的双向选择过程,不仅仅公司在选择你,你也在选择公司。如果面试官问的问题显得他很没有品味,或者是 HR 的某些安排让你觉得这家公司的文化很有问题,你随时可以提出说你没兴趣聊下去。这时候你反而可以比较好的发挥出来你真实的实力。对于你真的不知道的问题,你就直接说出哪部分你不知道或者不确定。同事间讨论也会遇到你不懂的问题,你平时是怎么处理的在面试时就怎么处理,面试官不会因为你直率地表达不懂某个点而鄙视你的,他应该帮你把这个点绕过去然后继续。

我觉得最好的面试建议就是 Changhao 跟我说的那句,「Be yourself」。(其实这也是很好的追女建议。)

改善面试流程

既然技术面试应该平等地讨论问题,通过感觉这个人是否能成为一名你喜欢的新同事来做出判断,那又应该如何衡量他的技术能力呢?这其实应该放在面试流程尽可能靠前的部分来做。很多外企的标准做法是,通过首轮(或前两轮)电话面试来判断一个人的技术,随后通过多轮的面对面讨论来判断这个人是否适合这家公司这个团队。我相信这套方法在美国执行起来没什么问题,因为空缺和候选人的数量差不多。

当然,上述流程来到中国后就必然会发生变化。中国有能力来做这份工作的人太多,同时还有更多的人想要来浑水摸鱼,这就导致了招聘成本大大提升。如果在中国还是用首轮电话面试来根据候选人的技术能力做一个初步的筛选,我想面试官就要疯掉了,所以大多数大公司都会在此之前加上一轮笔试。(对应届生可能是多轮笔试,否则面试官就阅卷都足以疯掉。)

有些公司会选择通过 ACM/ICPC 竞赛经历做出筛选,尽管无法保证获得最优结果,但至少能获得次优结果。招聘本来就是个搜索问题,一个在美国能有效求解的算法到中国来发现搜索空间大小增长了几个数量级,自然要想办法调整算法提高性能。这是非常理性的做法。当然它会带来一些外部性,例如说使得更多中国学生在可能不适合或不喜欢参加竞赛的前提下选择参加竞赛。

这些方法确实能把想要浑水摸鱼的人筛掉,不过能通过的人有时候还是太多了,所以接纳候选人过多的大公司往往会无端增加面试难度,问一些纯粹刁难人的题目。我觉得这没什么意思,也没在应届生面试以外的场合遇到过。既然应届生及格的太多,我觉得就直接随机抽签好了,这是最公平的做法。抽签可以通过公证过的算法来做,这比问一些候选人可能随机地知道或不知道的问题可靠多了。

最后说一下,为什么我觉得平等讨论的面试方式比问一堆问题要好。不同的面试方式,取决于你把候选人看做一个人力资源单位,还是一个有个性的人。如果你把候选人看做一个人力资源单位,他只要能完成你给出的任务就可以了,那确实是能力测试更重要。如果你把候选人看做一个有个性的人,你就需要知道他的个性能否很好地融入团队当中,这使得你在面试过程中必须把他当做一名未来的同事来看待。

2012年5月9日星期三

怎么样才算是 RESTful?读 REST in Practice

最近 O’Reilly 搞活动,我就半价买了一本《REST in Practice》(Kindle 版链接)。对于 O’Reilly 的书,我通常会对比 O’Reilly 打折后的价钱和 Kindle 版的价格,通常是那家更便宜就在那家买,但图表或代码比较多的我就会坚持买 O’Reilly 的版本,因为 PDF 能够最好地保存这些格式。

回到 REST 的话题上。尽管这个概念 2000 年就被提出来了,2007 年成为了一个热词,随后越来越多的服务都宣称自己是 RESTful 的,但是到底真么做才是真正的 REST 我从来没有自习学习过。由于 2007 年的时候 Ruby on Rails 也十分热门,所以我以为 Rails 风格的 CRUD API 就是 REST 了,同时对于外界关于「什么算是 REST 什么不算是 REST」的争论没怎么关心过。

记得之前老赵提到过《REST in Practice》是本好书,所以我就收藏到 wish list 里面了,在 O’Reilly 打折时就下狠心买下来,然后看看争论得如此多的 REST 到底是什么东东。书中提到 Richardson 的 REST 成熟度模型,通过这个模型你可以理解什么是 REST 什么不是 REST。这个模型是这样子的:

第 0 级服务:只使用一个 URI 作为一个服务端口,也只使用一个 HTTP 方法传输数据。大多数 WS-* 服务都是这个级别的,XML-RPC 和 POX 也是。这种做法相当于把 HTTP 这个应用层协议降级为传输层协议用。HTTP 头和有效载荷是完全隔离的,HTTP 头只用于保证传输,不涉及业务逻辑;有效载荷包含全部业务逻辑,因此 API 可以无视 HTTP 头中的任何信息。

第 1 级服务:使用多个 URI,不同的 URI 代表不同的调用入口,但只使用同一个 HTTP 方法传输数据。

第 2 级服务:使用多个 URI,不同的 URI 代表不同的资源,同时使用多个 HTTP 方法操作这些资源,例如使用 POST/GET/PUT/DELET 分别进行 CRUD 操作。这时候 HTTP 头和有效载荷都包含业务逻辑,例如 HTTP 方法对应 CRUD 操作,HTTP 状态码对应操作结果的状态。我们现在看到的大多数所谓 RESTful API 做到的也就是这个级别。

第 3 级服务:使用超媒体(hypermedia)作为应用状态引擎。要解释这个概念先要解释什么是超媒体:

我们已经知道什么是多媒体(multimedia),以及什么是超文本(hypertext)。其中超文本特有的优势是拥有超链接(hyperlink)。如果我们把超链接引入到多媒体当中去,那就得到了超媒体,因此关键角色还是超链接。使用超媒体作为应用引擎状态,意思是应用引擎的状态变更由客户端访问不同的超媒体资源驱动。

举个例子来说,用户在论坛的帖子列表点击超链接进入某个帖子,这时候浏览器作为一个客户端就会去 GET 这个帖子的链接 URI,应用状态就切换为显示某个帖子了。接着用户输入回复提交表单,浏览器就会根据 form 上面的 action 属性 POST 内容给目标 URI,应用状态就再一次发生切换,完成了回复的存储。

使用超媒体与前面第 1 级、第 2 级的显著区别是,客户端不再和 URI 紧耦合。在第 1 级或者第 2 级的应用里面,客户端都需要知道资源使用的 URI 模版(如 /orders/{id}),然后要操作什么样的资源就生成什么样的 URI。超媒体客户端只知道入口 URI,之后的每一个 URI 都是通过超链接获得的。

还是用上述论坛例子来解释,假若这个论坛通过 Atom 协议支持非浏览器的客户端访问。客户端是不需要知道论坛帖子的 URI 模版的,因为客户端可以通过帖子列表的 Atom 获得帖子的超链接,然后在用户选择浏览帖子时获取对应 URI 的内容。获取回来的结果不会带有 form,但会带有 <link rel="reply" />,通过这个 link 客户端又知道了用户提交的回复应该发往哪个 URI。

说完 Richardson 的成熟度模型,说说 REST 这篇论文作者 Roy Fielding 的回应。Roy Fielding 说「只有使用了超媒体的才能算是 REST」。简单来说,他认为第 3 级成熟度以外的都不算 REST。我个人的看法是,我支持 Roy Fielding 对 REST 的严格定义,我也认为一个真正使用了超媒体实现应用状态引擎的服务非常了不起,但是在普通 CRUD API 能满足需求的情况下我觉得使用 CRUD API 也可以。

这本书后面还讲到了如何使用缓存来增加系统健壮性,如何使用 Atom 和 Atom 发布协议,如何使用 OpenID 和 OAuth 等认证授权技术。因为这本书我还没看完,所以这些内容我也不好做评价。

这本书总体来说写得不错,但就是废话稍微多了一点点。如果你跳着章节地去看,可能就不会觉得那是废话了,因为你挑选出来阅读的部分都包含所有的信息。但如果你像我这样从头到尾地阅读,就会觉得作者怎么前面提到过的事情后面还要再提及。

此外这本书提供的大量 .NET 和 Java 代码对我来说没什么意义。我觉得这本书更多是写给做企业服务的人看的,希望他们的思维模式能够从企业环境里常见的 WS-* 跳出来,同时希望证明给他们看实现 REST 是多么简单的事情。这对于天天研究互联网服务的我来说没什么必要,况且我也很久没写过 .NET 代码了,所以各式各样的示例代码给我无视掉了。我觉得我知道这件事情 WCF 能做也就足够了。

2012年4月24日星期二

把 Blogger 服务的博客都迁移到 Disqus 评论了

一直使用 Blogger 的服务,因为我觉得使用起来比较放心,相信 Blogger(实际上是 Google)能够保证数据不会丢失。我的第一需求是要能专心写文章,第二需求是前端在有需要的情况下能够定制,这两点 Blogger 都能很好地满足到,所以在大家都忙于搭建 WordPress 时我还是坚守在 Blogger 上。因此,我不需要面对因为自己的失误,或者宿主环境的问题,而导致 WordPress 数据丢失的风险。

尽管对 Blogger 没什么好抱怨的,不过看到 Disqus 提供的评论服务还是觉得挺有吸引力的。看到越来越多的大网站使用 Disqus,相信越来越多的用户会拥有 Disqus 账号或能登录到 Disqus 的第三方账号,我就开始考虑迁移到 Disqus。背后的主要原因是,Blogger 评论支持的账号系统不多,不是很 geek 的人通常不会拥有它所支持的账号,而不登录的话就难以形成对话。(谁会去看自己匿名发表的评论是否有回复?)我希望我的评论区域能够形成对话,就好像在博客园那样子,大家都使用博客园账号互相回复讨论问题,因此我决定换成 Disqus 试试。

将 Disqus 部署到 Blogger 比我想象中的要容易。尽管 Blogger 不像 WordPress 那样允许你随意改后端代码,但 Disqus 早就准备好了前端解决方案——只要是有评论的页面,Disqus 就会将 Blogger 原有的评论隐藏掉,然后把自己显示出来。轻松跟着 Disqus 的 Blogger 迁移向导做,点几下就能把 Blogger 的评论替换为 Disqus。

完成功能迁移后,接着就可以进行内容迁移了。Disqus 也知道 Blogger 用户原本可能已经拥有评论了,所以它可以帮你把内容都迁移过来,只不过账号是不可能对应上了(就算该评论作者在 Disqus 有对应账号)。尽管 Disqus 写的是 24 小时内能够完成内容迁移,但实际上我等了几天也没见它有动静,后来不再在意这件事了,却发现迁移完成了。因此,如果你要做类似的迁移,我建议你先迁移内容,然后再迁移功能,这样能够做到无缝切换。

2012年2月8日星期三

如何帮助别人改变坏习惯

最近为了写一个 slides 而重新看了一遍 The Mystery Method,然后又重新学习了一遍我已经学习过并且忘记掉的知识。其中让我觉得比较有意义的一条是关于如何训练别人的行为模式的,尽管背后的道理源自对动物行为模式的训练。

如果你的狗总喜欢跳到你身上来,你该怎么办?你可以打它骂它,但它无法理解你的情绪,所以它只会把这理解为关注,也就是你在乎它,因此它会继续跳到你身上来,以便获取更多的关注。你可以把硬币放在一个罐子里,然后摇晃那个罐子让狗因为恐惧而走开,但久而久之它会理解到你和罐子存在关系,因此把你和厌恶联系在一起,这显然也不是你想要的结果。正确的做法是,先通过摇晃罐子让它从你身上下来,然后让它做一个简单容易做到的动作,例如跟它说「sit」,等它坐下来后在拍拍它给它一些奖励。这样子它就会把厌恶和错误的行为联系起来,但不会把厌恶跟你联系起来,因为你还是会奖励它的。

尽管 The Mystery Method 把这个方法用于训练别人的服从性,不过如果你想帮助身边的朋友改掉坏习惯这也是适用的。可能你觉得,既然大家都是朋友那么平等理性地沟通就可以了,只要对方知道这是个缺点他就可以改掉。然而事实不是这样的,可能沟通并不一定能够很好地传达你的意思,让对方有足够的决心去改变;可能对方一早就知道这个缺点的存在,但是他的意识无法扭转无意识的行为习惯,久而久之他就会合理化自己的行为,为自己无意识的行为辩论。

关于意识和无意识,我喜欢用 The Happiness Hypothesis 里面的那个比喻——无意识是大象,有力量,能够做重复性劳动,但是智慧不是很高;意识是骑象人,力量比不过大象,而且还懒惰,但智商足够高。这个比喻的一个要点就是,人永远不能跟象较劲。象看到了香蕉就要冲过去,而人看到了香蕉就在悬崖边上,这时候人想拉住象是不可能的。人应该使用自己的聪明才智防止这种情况的发生,例如训练象看到香蕉不要那么冲动。

显然你不希望在帮助对方改掉坏习惯的过程中让对方讨厌你,尤其是无意识讨厌你同时意识到应该感激你。前面已经说了,人是不能跟象较劲的,所以如果你有幸让一个人无意识讨厌你,那么无论他多么明白事理,他也无法让自己喜欢上你。为此,当你需要帮助别人改变习惯的时候,不一定需要跟对方的象骑者沟通。如果对方足够聪明,也足够信任你,他会允许你直接训练他的象,并且他也能理解你在做什么。

2012年1月31日星期二

Google Search Plus 把用户选择放到了内容质量的前面

Google 发布了 Search, plus Your World,简称 Google Search+,带来最大的改变包括 3 点:

  1. 跟个人相关的搜索结果:你跟 Google+ 上好友发表过的链接和照片都会出现在搜索结果内,包括通过 Circle 限制为非公开的。
  2. Google+ Profile:如果搜索人名,自动完成和搜索结果都会出现对应的 Google+ Profile。
  3. 相关 Google+ Profile:如果 Google+ 上有 Profile 跟你搜索的关键字相关,会出现在搜索结果页右侧。

其中最直接影响搜索结果排名的是第 1 点,后面两点都只是往搜索结果里插入少数 Google+ Profile 条目而已。这第 1 点可以说是把用户选择放到了内容质量前面来,使得过去完全针对内容的搜索引擎优化手段变得需要同时考虑针对用户做优化,也就是引入营销手段。

在过去,搜索引擎优化考虑的是内容质量。理论上来说,如果内容质量足够高,别人就会频繁引用你的页面,这样你的页面自然应当排到前面去。但是有了 Google Search+ 之后游戏规则彻底改变了,就算你的页面内容十分小众,只要它被某人发布到 Google+ 上去了,它就会出现在此人好友的搜索结果上。这时候,被多少人 +1 就显得比内容如何更重要。

这个变化可以说是由作者投票变成了读者投票。过去 PageRank 是以对待论文的方式来对待页面,被引用的次数越多,引用来源越重要,这篇论文也就越重要。然而,能够影响论文引用的只有论文作者,有多少读者读过一篇论文后受益是不知道的,除非他为此再写一篇论文。现在 Google Search+ 的做法可以说是把页面对读者的影响力也计算在内了。

跟作者投票不一样的是,读者投票的影响是跟个人相关的。只有离你近的读者投票才会影响到你的搜索结果,离你远的读者投票对你影响不明显(除非你指定搜索他的信息)。因此,如果要让你的内容通过搜索引擎展示给特定的受众看,你不一定再需要把内容优化为其他作者会认同的样子,只要你有营销手段让受众中的一部分先认同你的内容,搜索引擎就会让这种认同扩散出去。

尽管已经有很多社会化营销手段涉及到 Facebook 和 Twitter,Google Search+ 暂时还不支持这两者,所有社会化信息仅来自 Google+。无论这是 Google 有意的恶性竞争行为,还是技术上和商务上暂时没把对 Facebook 和 Twitter 的支持做好,现在你要影响 Google 搜索结果中的社会化成分就必须通过 Google+。估计很快网站就会重视页面上嵌入的 +1 按钮,并且使用各种方式去鼓励用户点击那个按钮。

2012年1月18日星期三

如何设计大规模 JavaScript 应用 (Part 1 - 概览)

背景

我一直很关注如何设计大规模的 JavaScript 应用,因为我一直在做的都是大规模的 JavaScript 应用。从百度 Hi 网页版到百度地图,从 Yahoo Search Direct 到豌豆荚客户端。好吧……Yahoo Search Direct 本身的规模不大,但作为一个网页插件它要能跑在任何宿主页 面上,其复杂度也不低。在大规模 JavaScript 应用开发和维护的过程中,有两个问题尤其值得关注:设计和性能。前者是必须在开发阶段之前做好的,开发开始后就来不及改了,只能事后重构;后者则更多发生在开发的中后期,等 profiling 结果出来了,再针对瓶颈做优化。在这个文章系列中,我们的关注点是设计。

尽管我一直在写跟设计相关的文章,不过现在看来我过去写下的文章都有点山寨了,也不够 update。在我看到 Large-scale JavaScript Application Architecture 这个幻灯片后,我决定重新写一个系列来说说我在大规模 JavaScript 应用方面的经验,也包括一些设想。

如果说大规模 JavaScript 应用设计方面有什么核心原则的话,我觉得核心原则就是这一条:永远不要尝试构建大规模应用。构建小应用,保证它们的可测性,然后把它们组装成大应用。因此,我觉得在讨论任何设计模式之前,我们先要讨论一下如何把大应用拆分成小应用。

“The secret to building large apps is never build large apps. Break your applications into small pieces. Then, assemble those testable, bite-sized pieces into your big application”

- Justin Meyer

模块化

CommonJS Modules

先说最理想的模块化方式,那就是 CommonJS Modules。一个模块系统至少要能解决两个问题:依赖项的加载、私有作用域和公有导出成员的区分。CommonJS Modules 通过 requireexports 很好地实现了上述两个功能。下面是一个 CommonJS Modules 模块定义和使用的例子:

util.js

var util = {
    extend: function(target, source) {
        /* implementation */
    }
};
util.extend(exports, util);

feature.js

var util = require('util');
var features = {
    core: {
        start: function() {
            /* implementation */
        }
    }
};
util.extend(exports,features);

app.js

var features = require('features');
features.core.start();
AMD

考虑到浏览器里面没有 CommonJS 的 Modules 接口,也不可能完整实现这样的 Modules 接口,所以就有了 AMD (Asynchronous Module Definition) 这样的解决方案。AMD 使用 define 函数定义模块,要求模块提前声明依赖项,然后通过回调加载模块,解决了浏览器无法同步加载模块的问题。下面是一个 AMD 模块定义和使用的例子:

util.js

define('util', [], function() {
    return {
        extend: function(target, source) {
            /* implementation */
        }
    };
};

feature.js

define('features', ['util'], function(util) {
    return {
        core: {
            start: function() {
                /* implementation */
            }
        }
    };
});

app.js

define('app', ['feature'], function(feature) {
    features.core.start();
});
CommonJS Modules/Wrappings

考虑到 AMD 的写法跟 CommonJS Modules 的写法区别十分之大,要把已有的 CommonJS Module 写法模块改为兼容 AMD 不容易,所以又有人设计一些改动不那么大的写法,如 AMD 工厂函数的 function(require, exports, ...) {...} 签名,或 CommonJS Modules/Wrappings (意思是 Modules 的 Wrappings)。由于浏览器必须异步加载依赖项,所以这些写法只能通过对工厂函数源代码做静态分析提前找出依赖项,在异步加载好之后再执行工厂函数。这样做的坏处是工厂函数内部对 require 的调用缺乏灵活性。下面是一个 CommonJS Modules/Wrappings 模块定义和使用的例子:

util.js

define(function(require, exports, module) {
    var util = {
        extend: function(target, source) {
            /* implementation */
        }
    };
    util.extend(exports, util);
});

feature.js

define(function(require, exports, module) {
    var util = require('util');
    var features = {
        core: {
            start: function() {
                /* implementation */
            }
        }
    };
    util.extend(exports,features);
});

app.js

define(function(require, exports, module) {
    var features = require('features');
    features.core.start();
});
UMD

尽管 Node.js 写好的 CommonJS Modules 模块可以通过 CommonJS Modules/Wrapping 包装一下使得它能用在浏览器内,尽管包装过的模块通过 r.js 也能用于 Node.js 环境下,不过这不是完美的解决方案。因此,又有人提出 UMD (Universal Module Definition),希望提供跨平台的模块定义方案。

UMD 现在还没有定稿,不同的人提出了不同的解决方案。最全面的方案同时支持 AMD 和 Node.js,顺便还把传统的浏览器脚本顺序加载模式也兼容了。具体的做法就是判断环境中是否存在 AMD 所依赖的 define,如果存在的话就使用 AMD 加载,不存在的话就使用别的方式模拟 AMD 加载。

util.js

(function (root, factory) {
    if (typeof exports === 'object') {
        module.exports = factory();
    } else if (typeof define === 'function' && define.amd) {
        define('util', [], factory);
    } else {
        root.util = factory();
    }
})(this, function() {
    return {
        extend: function(target, source) {
            /* implementation */
        }
    };
});

feature.js

(function (root, factory) {
    if (typeof exports === 'object') {
        module.exports = factory(require('util'));
    } else if (typeof define === 'function' && define.amd) {
        define('feature', ['util'], factory);
    } else {
        root.feature = factory(root.util);
    }
})(this, function(util) {
    return features = {
        core: {
            start: function() {
                /* implementation */
            }
        }
    };
    util.extend(exports,features);
});

app.js

(function (root, factory) {
    if (typeof exports === 'object') {
        module.exports = factory(require('feature'));
    } else if (typeof define === 'function' && define.amd) {
        define('app', ['feature'], factory);
    } else {
        root.app = factory(root.feature);
    }
})(this, function(feature) {
    var features = require('features');
    features.core.start();
});

小结

在这篇文章里面,我们了解了大规模 JavaScript 应用设计的核心原则:使用模块化的方式把大应用分解为小应用来编写和维护。同时我们也看了不同的模块定义方式,我们可以针对平台来选择一种合适的方式,也可以选择通用的方式但需要维护更多的代码。

在有了模块的概念之后,我们就可以讨论具体的设计模式了。这个系列接下来将会深入讨论每一个对大规模 JavaScript 应用设计有用的设计模式。如果你对这个系列的文章感兴趣,可以选择订阅本博客。

2012年1月12日星期四

Covariant(协变)与 Contravariant(逆变)

今天为了解释某个问题而提到协变和逆变,发现每次解释这两个概念都会忘掉它们的本质,然后要重新看看定义,重新消化一下才能说明白。所以我决定把自己对协变和逆变的理解写下来,以免将来再次忘掉。

我知道 .NET 的用户喜欢用 delegate TResult Func<in T, out TResult>(T arg); 来解释协变逆变,我则喜欢把 Func 的签名简写为 Haskell 签名形式。也就是说,把 Func<T, TResult> 写成 f :: a -> b 的形式;把 Func<T1, T2, Result> 写成 f :: a -> b -> c 的形式。

其实无论是协变还是逆变,本质都是一样的:对于签名为 f :: A -> B 的函数,实际可接受的参数范围为 ASub,实际可返回的参数范围为 BSub。这个很容易理解吧?任何时候子类的实例都可以当做超类实例来使用,无论是接受还是返回。

协变和逆变用于描述高阶函数签名,如 f :: (X -> Y) -> Z。那上面的 f :: A -> B 做模版,我们可以把 (X -> Y) 看做 A,把 Z 看做 B。应用同样的逻辑,函数实际可接受的参数范围是 (X -> Y) 的子类,实际可返回的参数范围是 Z 的子类。对于后者我们没什么疑问,但 (X -> Y) 的子类到底是什么呢?它的所谓「子类」应该是 (XSuper -> YSub)

为什么说 (X -> Y) 的「子类」应该是 (XSuper -> YSub) 呢?因为子类在能力上应该完整覆盖超类的能力,因此如果对方要求你提供一个函数,这个函数接受 X 类型返回 Y 类型,你提供的函数至少要能接受 X 的超类而返回必须是 Y 的子类。这时候 X 是逆变参数(类型可以更宽松),而 Y 是协变参数(类型可以更严格)。

一般来说,如果把「类型可以更严格」看做协变的话,函数的返回类型一定可以协变,非高阶函数的参数也可以协变,高阶函数的非函数参数同样可以协变。把「类型可以更宽松」看做逆变的话,只有高阶函数中的函数参数中会出现逆变,也就是作为参数的参数出现。那么参数的参数的参数呢?也就是说高阶函数的参数仍然是高阶函数,那会怎么样呢?这个大家可以尝试自行分析,盯住 f :: ((X -> Y) -> Z) -> W 看一会儿,再不停类比上文的 f :: A -> B,或许你就明白了。

2012年1月6日星期五

世界顶级黑客自传:Ghost in the Wires

上个星期终于把 Ghost in the Wires 看完了,现在就抽时间来写写书评吧。尽管我一向懒得写书评,不过这本书真的是超级推荐!

最初我是在 Audible 上面买了这本书的有声读物,然后发现朗读得超级好,尤其是 Kevin 知道危险逼近时心中的独白——「Fuuuuuuuck!」朗读的语气掌握得非常好。后来我决定买 Kindle 版来仔细读一下,因为有声读物不会专心听,只能把我到大意。随后我边听边读把整本书看完了。

这本书整体上分为 4 大部分。第 1 部分讲述 Kevin Mitnick 是如何入行的——他父亲和叔叔都是商人,这使得他很小的时候就意识到通过言语可以驱使他人做一些对自己有利的事情。同时他又是个超级 geek,从小喜欢在书店里面看一些稀奇古怪的书,例如说如何伪造身份让自己凭空消失,或者是如何获取他人的驾驶记录、资产记录、信用报告、银行信息、非公开电话号码。

由于这些爱好的驱使,他接触到了电话玩家(phone phreak),也就是一群喜欢研究电话系统和拿电话系统做实验的人。然后他开始学习如何打免费电话(Steve Jobs 和 Steve Wozniak 也做个类似的事情),并且如同研究黑箱电路一样通过不停地打电话探索电话公司内部的组织形式。

后来由于同伴的出卖(这又是因为 Kevin 爱在同伴面前耍小聪明),Kevin 被捕并被起诉。在这个过程中,公诉人和媒体想尽办法抹黑 Kevin,给他安上各种奇怪的罪名,例如黑进 NSA、切断假释官电话、篡改法官信用报表等等,其中最莫名其妙的是说他由于喜欢明星 Kristy McNichol 而切断她的电话服务。(Kevin 独白:有见过因为喜欢一个人而切断她电话的吗?)正是由于这段经历,导致 Kevin 后来被 FBI 追捕时选择了逃亡,因为他认定政府是邪恶的,只会想往他头上加更多的罪名。

第 2 部分讲 Kevin 假释后的生活。尽管他的假释条款规定了他不能再进行违背道德的黑客行为,也不能再跟别的黑客有所接触,但为了调查弟弟死亡的真相,他还是忍不住黑进电话公司并且监听被怀疑对象的电话。随后他又回到了黑客道路上来。这时候他碰到了一位神秘黑客 Eric,看样子跟娱乐圈混得很近,不用工作也能过得很滋润,而且跟 Kevin 一样对电话系统有着深入的了解。最最重要的是,Eric 知道 Kevin 不知道的一种设备,叫做 SAS,可以用来全自动地监听任意线路。

Kevin 一方面对 Eric 有所怀疑,觉得他可能是卧底或者是线人;另一方面又受不住 SAS 的诱惑,希望搞清楚这套系统是怎么用的。最终他假装电话公司员工,向 SAS 制造商要到了完整的 SAS 设计手册,里面的资料比电话公司拥有的 SAS 用户手册还要丰富,包括用户用不到的调试指令。随后 Kevin 要想办法绕过 SAS 的安全策略来监听别人。

SAS 如此强大的设备自然有相对严谨的安全策略——你可以拨号给 SAS 让它监听一条线路,SAS 也能帮你监听这条线路,但监听的结果不会输出到你拨入 SAS 的线路,只会输出到你指定的回拨线路,问题就在于每一台 SAS 都限制了回拨线路,你不能让它回拨到任意一个号码上。Kevin 想了一个很聪明的方法来绕过这个限制:他先用线路 A 拨入到 SAS 的回拨线路上,然后用线路 B 向 SAS 发送指令。SAS 要回拨时它自然会接上回拨线路,实际上就接到了线路 A 上,这时候 Kevin 就对着话筒哼一个线路就绪音(就是我们平时拿起听筒听到的声音),SAS 以为线路就绪了就开始拨号,实际上线路早就被接通了。

电话公司发现有人入侵了,安全小组就联通 FBI 一起侦察,而 Kevin 发现自己被监听以后,就想办法监听电话公司安全小组以及 FBI 的通话,以便搞清楚到底对方都收集到多少证据了,自己是否还有机会脱身。在如此几个回合后,Kevin 决定是时候人间蒸发了,于是就开始了他的逃亡之旅。

第 3 部分讲 Kevin 逃亡的过程。他先换了一个临时假身份来到了丹佛,然后再换一个长期假身份。每一次他都先要把出生证明和驾驶证骗到手,然后再去办社会保障号码卡。由于他用的都是真是存在的同龄人身份,简历上印的学历也是真实的,所以他能够找到工作赚钱生活下去。在这个过程中,他还弄到了一叠盖好章的出生证明!这意味着将来他想换身份就更容易了,直接把想要克隆的身份填写到出生证明上面就可以了,然后去考驾照和申请补发社会保障号码卡。

第 4 部分讲 Kevin 最终被捕的过程。Kevin 一直做足安全措施,包括使用克隆的手机号码,拨号时利用先前已经黑下来的电话公司交换机设置多重呼叫转移使得拨号源头难以跟踪,为此 FBI 一直都抓不到他。最后他黑了安全专家 Tsutomu Shimomura。对方是个高傲的黑客,因为被黑而决定帮助 FBI 抓住 Kevin,FBI 为此也给了他很多平民在合法程序上不可能获得的特权,例如监听电话和网络。最终 Kevin 被捕,同时公诉人再次想往他头上套上最严重的罪名,结果反而导致了 Free Kevin 活动的活跃。

媒体最初一直把 Kevin Mitnick 渲染为大黑客,说他只要对着电话吹口哨就能发射核导弹。现在由于政府玩大了,风向就发生了变化,人们开始支持 Free Kevin——不是说他无罪,而是说他应该在合理的范围内承担他的罪名。他盗窃了大量的源代码和信用卡信息,但是他没有利用这些信息赚钱,所以造成的损失也就是各大公司少赚了一份源代码授权费用而已,因此应该根据这部分损失来做出判决。

公诉人和法官当然不这样想,但 Kevin 竟然找到了一份极具参考价值的判例——一位税局官员曾经处于好奇心而获取了众多议员的税务信息,最终因为他没有因此获利而被轻判,理由是他只是好奇心驱使而已。这个判例使得 Kevin 获得了合理的对待,他只需要赔偿源代码授权费的损失就可以了,而且赔偿金额还要根据他未来几年的赚钱能力调整。考虑到假释条款上规定他不能使用电脑、不能接触电话、不能做技术咨询……基本上他就只能去麦当劳煎牛肉了,法官判他 $4000 多一些的赔偿。

假释期满后,Steve Wozniak 亲自给他送了一台最新款的 PowerBook G4,然后他又回到了他的黑客世界。只不过这次他是开咨询公司,在道德的范围内,在客户的要求下,去黑客户的公司,帮助客户找出他们的漏洞。

总的来说,如果你对社会工程学有点好奇心,或者想知道美国政府是如何不小心把 Kevin Mitnick 搞成头号黑客的,Ghost in the Wires 能够很好地满足你的好奇心。里面有技术细节,但非常少,就算你完全看不懂也不影响对情节的理解。

P.S. 这本书里面的每一章开头都有一段密文,有兴趣的人可以自己尝试去破解,懒得破解的可以搜索相关的文章。