Google前美女面试官谈程序员面试的技巧和建议
盖尔·拉克曼(Gayle Laakmann),Google前员工,目前在宾夕法尼亚大学沃顿商学院攻读MBA。她也是在宾州大学获得计算机科学博士学位。她现在是CareerCup和Seattle Anti-Freeze两家公司的创始人兼CEO。她也是《Cracking the Coding Interview》一书的作者。
您好,盖尔。能先自我简单介绍一下呢?
您好。我先是在微软和苹果实习,后来在Google做了三年工程师。在Google,我也是招聘委员会的成员,面试过120多位本土和国际工程师,我参与了招聘会并审查过数百份简历。我很喜欢在Google工作,但我也想尝试创业。
所以,有了这些难得的经验和见识后,我在2005年成立了CareerCup。我们通过电子书和面试论坛来帮助人们准备技术类面试。
您能否简单介绍一下像微软、Google和亚马逊这类公司的面试过程?面试有几轮?哪些地方需要重点关注?
首先是有一位工程师通过电话面试你;(通过后)再参加有4-6名求职者的群面。至于需要关注的地方,大公司追求优秀的技术能力高于一切。你能否写出合格、整洁和有条理的代码?能否解决有挑战性的问题?
那学生/求职者该怎么掌握这些呢?
一定要实践!熟能生巧虽是老生常谈,但它的确有用。在纸上练习编程,然后照原样输入到电脑上。你会发现错误比你想象的要多的多!
你看到什么样的求职者才会说“哇!这就是我要找的人。”
我希望求职者是:
最后一条是最难的。作为一个面试官,我选人时主要看:当求职者拿到一个问题时,他们是仅在纸上写代码么?或者他们会定义相关的数据结构么?
求职者并不知道面试中所有问题的答案吧?他们怎么回答那些没有提示的问题呢?
一位优秀的面试官,会问那些难度非常高以致于你甚至都不会知道答案的问题。回答这类问题的最好办法是,先简化问题,做假设(比如:假设数组中只有整数);然后,解决简化后的问题;最后,归纳答案。
此外,面试官想知道你的想法,所以尽力说出你的方法,并解释你的操作过程。这将给他们留下对你技术和技能的良好印象。
在求职过程中,您认为GPA有什么样的作用?或者在面试中,纯粹看GPA么?
这个真的取决于公司。一般来说,想要得到初步面试机会,除了诸如经验和兴趣等其他东西之外,GPA也重要。
在他们面试你后,并且正决定是否用你时,GPA不应是阻碍。(否则,他们就没必要面试你了。)
我接触过一些非常聪明的人,但由于各种原因他们的GPA不好,我相信您也应该遇到过吧。您对他们有什么建议呢?你认为什么样的技能才能打动顶级公司?
这些公司真的不关心你的分数。他们看重的是:你是否聪明,你是否有强硬的技术和你是否能努力工作。
你要用其他途径来证明自己。比如:引用你做过的项目,或在开源项目中解决的问题。列举说明你在某一特别困难课程中的分数或班级排名。
在简历中列举你的奖项和参与的项目,保持更简历更新。如果你没有项目经验,竭尽全力去获取经验!这才是那些公司最最看重的东西。
在求职者去应聘Google这类公司前,请您给他们分享一些建议或注意事项。
练习诸如我们网站上的问题;先在纸上练习算法,然后按原样再输入到电脑中。
还有一件事要注意,关于你简历上的每个工作或项目,你必须能解释其中最艰难的挑战,你最喜欢它的哪部分?你最不喜欢哪部分?你学到了什么?
感谢盖尔接受采访。
增加编程经验的3种途径
1. 无偿工作(或几乎免费)
企业界通常可能并不想雇佣经验少或没有经验的人,而非盈利界通常乐意(至少愿意)雇佣这一类人。我在高中的时候,去我们当地发育性残疾人家庭当志愿者,我是这样进入程序员这行的。我用Excel表格来为他们管理财务,帮他们把这些信息整理到了一个网站上,等等。工作很吸引人吗?完全不是那么回事儿。我是无偿地在每天下午和每个周末为他们工作。唯一的福利就是在那个地方我可以随时从一个装得满满的食品柜里拿东西出来吃。除了给社区做贡献之外,它还给了我足可以写上简历的工作经历,还有一份推荐信。一些非营利组织还会支付给你少量薪水。
选择一个“废弃”的开源项目,再做二次开发。开源项目的工作经历绝对是简历上的亮点。
如果你在当地找不到慈善机构或者非营利组织,也许你也可以为家人打工。说不定你的哪个亲戚朋友的企业就需要一个程序员。主动地无偿请战吧,我敢打赌,你会发现你/你朋友的叔叔或阿姨会为你的加入而感到欣慰。
2. 拼命工作
如果你想成功,就必须赶快行动起来;我还没见过哪个开发人员的机会是轻易得来的。我猜是一些开发人员是足够幸运,亦或是他们的某个亲戚在他们刚毕业的时候就高薪聘用了他们。还有一些人找到了很好的实习机会,最终引领他们找到了其他的好工作。但对大多数目前在校或是刚走出校园的学生来说,唯一既能突显自己有能增长经验的方法就是工作、工作、再工作。就是这样!
你的老板可能更想让你在服务台工作,而不太会让你花太多时间在写代码上,。那么,如果你想在服务台工作中获得软件开发经验,就必须自己抽时间。利用午休时间来写代码吗?没错!下班后继续工作吗?没错!在家也要做计划和研发吗?没错!
我懂,我都懂……免费做事或者做那些预期之外的事,这听起来太没劲。不过,其实情况更能更糟。你是否曾经了解过医生们在住院实习期间所做的工作吗(先不提工资)?就把这个当作你自己的住院实习期吧。在今后的几个月或几年的时间中,你要额外刻苦工作,努力积累经验。尽管你的下一份工作可能并不轻松(也不会轻松),但薪水会更高。
其实还有很多增长经验和薪水的方法;诀窍就在于从工作的“后门”中一点点争取。例如,我之前的工作是网络的管理与监督。当时我已经很长时间没接触程序设计院的工作了,并且我知道我想重操旧业。但是实际情况是我的大部分工作经验都是在Perl语言领域(当时也已经基本荒废了),并且我也好多年没编程了,不过我很清楚我在完全有资格任职之前要增加我这方面的经验。那么我都做了什么呢?我开始在空闲时帮助我们部门写程序;有时候,我甚至下班后再编代码,所有的一切都是为了积累经验和一份推荐。
也许你找不到一个软件开发的工作,但是你也许可以找一个倾向那方面的工作,比方说,系统工程师或者服务台。从那起步,你便可以开始显示出你代码方面的实力从而选择或者是为自己完善一份出色的简历后辞职,还是继续晋升。实际上,服务台或是系统工程师(或者叫“计算机操作员”)的工作都是一个逐渐熟悉这个领域的最古老的方法之一。
3. 在家工作(当自由职业者)
可能你找不到接受你无偿写代码的人。也许你根本就不能把编程融入到你的非编程性质的工作中(就像一个没能得到加班授权的钟点工)。这时你家的作用就突显出来了。如果其他的选择都行不通(或者为了补充目前的能力),就在家做些工作吧。找一个你真正喜欢的程序,用自己的风格写出来。或者把你日思夜想的那个程序写出来。
在家工作时,尽量试着模仿专业环境下的软件开发流程。先写出一个项目计划,创建单元测试,晚上再执行代码等等。我保证有了它,你将成为一个更优秀的程序员,并且你也可以向雇主展示你的作品。这其实是很重要的。
我以前工作的时候,不能带上自己的作品并且展示给潜在雇主们看。因为这既违反我的雇佣合同,又违反我的雇主与客户之间的合同。但是当我在家用我自己的资源做我自己的工作的时候,我便可以自由地向潜在雇主们展示。
例如,当我想要找一份网络开发偏多,网站管理偏少的工作时,我会把简历中的亮点和推荐人的推荐语等其他东西,聚集做成一份Flash演示稿。我甚至会把演示稿压缩制作成一张可以自动运行的精美CD,所以潜在雇主只需看CD。这张CD曾帮助我在互联网泡沫时找到过工作。这真的很管用。
作为一个多次参与面试和被面试的过来人,我敢说,如果求职者有创业经历时,会给你留下一个非常深刻的印象。这能和其他工作有相同级别的薪水么?有时候能。我认为,在可靠的开源项目中做“实际工作”和正常带薪工作的效果是一样的;除非你参与的项目实在很糟糕,并且你又把它拿给面试官看了。所以,“无薪工作”是另外一个建议,这也通常能让你在和竞争同一岗位的众多入门级程序员中鹤立鸡群。
开始编程生涯的5个建议
1. 基础学习
当涉及基础学习时,很多自学编程的朋友都有一个错误的认识。促使某人自学编程的心态是一种“现在就做些事”的动力和渴望。有这种心态非常好!但不幸的是,在学习新东西时,一旦涉及基础,很多人都急于求成(还没学会走路就想跑)。这是我的个人经验。这也就是为什么自学的程序员有时候名声不大好的原因。
确信你学到了编程基础。这包括变量名、合适结构体、有时候需要查找库而不是应用程序等等。《如何学习用某某语言编程》,这类典型的书籍往往忽略了成为一名编程高手所具备的能力:解决问题的能力。在这里,我向大家推荐Abelson和Sussman的《计算机程序设计与解释》一书,此书对你快速提升能力大有帮助。
编者注:《计算机程序设计与解释》一书于1984年出版,成型于美国麻省理工学院(MIT)多年使用的一本教材,1996年修订为第2版。在过去的二十多年里,此书对于计算机科学的教育计划产生了深刻的影响。
2. 多参与项目
编程经验,多多益善。我推荐你加入一个开源项目,或者加入当地非盈利组织,帮他们编写软件。
你将有如下收获:
3. 接受减薪
如今经济的颓势已经不再是什么秘密。一直以来,我听到的说法是:虽然有很多空闲的职位,但是工资着实很低。很多雇主把目标锁定在可以接受更低工资或者重新评定工资等级的人身上。
大学应届毕业生(他们当中的很多人发现毕业后便失业)是入门级的竞争力。另外,绝大多数应届毕业生没有像已在职员工那样的经济负担,同时,和那些毕业多年的、有贷款、有家室和车贷的人相比,他们可以接受更低的薪水。
入门级的职位同样也是最容易外包出去。但不幸的是,开发行业的总体趋势是:在职业生涯中,头五年左右的日子正变得越发艰难。
4. 关注非程序研发职位
在研发团队中,有很多职位并不参与研发工作,可是它们也会让你向目标进一步迈进。比如像品质保证/检测、维护、技术支持等。必要的话,从中选取一个作为进入研发行业的敲门砖。接着,从容地寻找通向编程之门。举个例子,你现在是一名质检员,除了单纯地寻找错误并报告外,你还可以仔细检查代码,找出错误出现的位置并以用标签加以标记。研发人员会很感激你的帮助在证明自己能力之后,开发行业的大门也向你敞开了。
5. 给老板做兼职
以你现在的经验来讲,找到一份兼职工作不太靠谱,但如果是为你现在的老板多做些分外事的话,也许可以。和你的老板还有同事们沟通一下,看看有没有一些简单的程序可以替他们完成,来减轻他们的负担。接下来,便是编写程序。如果你身边有一个内部的开发团队,你可以为他们提供一些帮助。一些经理会让你在工作时间内来做,另一些则让你在自由支配时间内完成。
不管是哪种方式,你都会得到开发经验,你在目前老板面前更有价值,甚至可能在目前的公司中为自己开启一个新的职业方向。在我的职业生涯中,我一次又一次地发现,当面临事业成长、事业发展和新方向时,“多做些分外的事”可以扭转局面。
编者注:WPF(Windows Presentation Foundation)是美国微软公司推出.NET Framework 3.0的组成部分之一,它是一套基于XML、.NET Framework、向量绘图(vector graphic)技术的展示层(presentation layer)开发框架,微软视其为下一代使用者介面(user interface)技术,将广泛被用于下一代Windows平台(Windows Vista)的界面开发。
Google前工程经理王忻:如何准备软件工程师的面试
原作者简介:
王忻,出生于北京,五岁时跟随父母移居美国。中学期间跳了三级,十五岁进入了加州理工大学,加入 Google前曾在微软等公司工作。详情见其简历。
我说:“你的手上粘了很多颜料,我带你去卫生间洗洗吧,”然后我把他领到洗手间让他从镜中看到了自己的尊容
提高程序员面试代码质量的三要素
作者总结自己多年面试他人以及被他人面试的经验,发现应聘者可以从代码的规范性、完整性和健壮性三个方面提高代码的质量。
程序员在职业生涯中难免要接受编程面试。有些程序员由于平时没有养成良好的编程习惯,在面试时写出的代码质量不高,最终遗憾地与心仪的公司和职位失之交臂。因此,如何在面试时能写出高质量的代码,是很多程序员关心的问题。
代码的规范性
面试官是根据应聘者写出的代码来决定是否录用一个应聘者的。应聘者首先要把代码写得规范,才可以避免很多低级错误。如果代码写得不够规范,会影响面试官阅读代码的兴致,至少印象分会打折扣。书写、布局和命名都决定着代码的规范性。
规范的代码书写清晰。绝大部分面试都要求应聘者在白纸或者白板上书写。由于现代人已经习惯了敲键盘打字,手写变得越发不习惯,因此写出来的字潦草难辨。虽然应聘者没有必要为了面试特意去练字,但在面试过程中减慢写字速度、尽量把每个字母写清楚还是很有必要的。不用担心没有时间去写代码。通常编程面试的代码量都不会超过50行,书写不用花多少时间,关键是在写代码之前形成清晰的思路并能把思路用编程语言清楚地书写出来。
规范的代码布局清晰。平时程序员在集成开发环境如Visual Studio里面写代码,依靠专业工具调整代码的布局,加入合理的缩进并让括号对齐成对呈现。离开这些工具,应聘者就要格外注意布局问题。当循环、判断较多逻辑较复杂时,缩进的层次可能比较多。如果布局不够清晰,缩进也不能体现体现代码的逻辑,这样的代码将会让人头晕脑胀。
规范的代码命名合理。很多初学编程的人在写代码时总是习惯用最简单的名字来命名,变量名是i、j、k,函数名是f、g、h。由于这样的名字不能告诉读者对应的变量或者函数的意义,代码一长就会变得非常晦涩难懂。强烈建议应聘者在写代码时,用完整的英文单词组合命名变量和函数,比如函数需要传入一个二叉树的根结点作为参数,则可以把该参数命名为BinaryTreeNode* pRoot。不要因为这样会多写几个字母而觉得麻烦。如果一眼能看出变量、函数的用途,应聘者就能避免自己搞混淆而犯一些低级的错误。同时合理的命名也能让面试官一眼就能读懂代码的意图,而不是让他去猜变量到底是数组中的最大值还是最小值。
代码的完整性
在面试的过程中,面试官会非常关注应聘者考虑问题是否周全。面试官通过检查代码是否完整来考查应聘者的思维是否全面。通常面试官会检查应聘者的代码是否完成了基本功能、输入边界值是否能得到正确的输出、是否对各种不合规范的非法输入做出了合理的错误处理。
三种测试用例确保代码的完整性
应聘者在写代码之前,首先要把可能的输入都想清楚,从而避免在程序中出现各种各样的质量漏洞。也就是说在编码之前要考虑单元测试。如果能够设计全面的单元测试用例并在代码中体现出来,那么写出的代码自然也就是完整正确的了。通常程序员可以从功能测试、边界测试和负面测试三方面设计测试用例,以确保代码的完整性。
▲首先要考虑的普通功能测试的测试用例。应聘者首先要保证写出的代码能够完成面试官要求的基本功能。比如面试题要求完成的功能是把字符串转换成整数,应聘者就可以考虑输入字符串“123”来测试自己写的代码。这里要把零、正数(比如123)和负数(比如-123)都考虑进去。
考虑功能测试时,应聘者要尽量突破常规思维的限制,避免忽视某些隐含的功能需求。比如“打印从1到最大的n位数”,很多人觉得很简单。最大的3位数是999、最大的4位数是9999。这些数字很容易就能算出来。但最大的n位数都能用int型表示吗?如果超出int的范围可以考虑long long类型。超出long long能够表示的范围呢?面试官是不是要求考虑任意大的数字?如果面试官确认题目要求的是任意大的数字,那么这个题目就是一个大数问题。此时需要特殊的数据结构来表示数字,比如用字符串或者数组来表示大的数字,才能确保不会溢出。
▲其次需要考虑各种边界值的测试用例。很多代码都包含有循环或者递归。如果代码是基于循环,那么结束循环的边界条件是否正确?基于循环的代码要特别注意开区间和闭区间的使用(也就是区分<与<=、>与>=)。如果代码是基于递归,递归终止的边界值是否正确?这些都是边界测试时要考虑的用例。还是以字符串转换成整数的问题为例,应聘者写出的代码应该确保能够正确转换最大的正整数和最小的负整数。
▲再次还需要考虑各种可能的错误的输入,也就是负面测试的测试用例。应聘者写出的函数除了要顺利地完成要求的功能之外,当输入不符合要求时,面试官还希望他能做出合理的错误处理。在设计把字符串转换成整数的函数时,应聘者就要考虑当输入的字符串不是一个数字,比如“1a2b3c”,怎么告诉函数的调用者这个输入是非法的。
前面讨论的都是要全面考虑当前需求对应的各种可能输入。在软件开发过程中,永远不变的就是需求会一直改变。如果应聘者在面试时写出的代码能够把将来需求可能的变化都考虑进去,在需求发生变化时能够尽量减少代码改动的风险,那他就向面试官展示了自己对程序可扩展性和可维护性的理解,必定能得到面试官的青睐。如果应聘者在解答面试题“调整数组顺序使奇数位于偶数前面”时能够考虑可扩展性,他写出的代码不仅仅只是解决调整奇数和偶数的问题,还能考虑到把调整数字顺序的功能和判断一个数字是奇数还是偶数的功能解耦。这样当今后需求功能扩展要求解决类似的问题,比如调整负数和非负数的顺序、调整能被3整除的数字和不能被3整除的数字的顺序,只需要添加很少的代码都能做到,于是提高了代码的可扩展性和可维护性。
三种错误处理的方法
通常有三种方式把错误信息传递给函数调用者。
▲函数用返回值来告知调用者是否出错。比如很多Windows的API就是这个类型。Windows中很多API的返回值为0表示API调用成功,而返回值不为0表示在API调用的过程中出错了。微软为不同的非零返回值定义了不同的意义,调用者可以根据这些返回值判断出错的原因。这种方式最大的问题是使用不便,因为函数不能直接把计算结果通过返回值直接赋值给其他变量,同时也不能把这个函数计算的结果直接作为参数传递给其他函数。
▲当发生错误时设置一个全局变量。此时可以在返回值中传递计算结果了。这种方法比第一种方法使用起来更加方便,因为调用者可以直接把返回值赋值给其他变量或者作为参数传递给其他函数。Windows的很多API运行出错之后,也会设置一个全局变量。函数调用者可以通过调用函数GetLastError分析这个表示错误的全局变量从而得知出错的原因。但这个方法有个问题:调用者很容易就会忘记去检查全局变量,因此在调用出错时忘记做相应的错误处理,从而留下安全隐患。
▲异常。当函数运行出错时,程序就抛出一个异常。程序员可以根据不同的出错原因定义不同的异常类型。因此函数的调用者可以根据异常的类型就能知道出错的原因,从而可以做相应的处理。另外,由于显式划分了程序正常运行的代码块(try模块)和处理异常的代码块(catch模块),代码的逻辑比较清晰。异常在高级语言如C#中是强烈推荐的错误处理方式,但有些早期的语言比如C语言还不支持异常。另外,当抛出异常时,程序的执行会打乱正常的顺序,对程序的性能有很大的影响。
上述三种错误处理的方式各有优缺点。那么面试时应聘者该采用哪种方式呢?这要看面试官的需求。在听到面试官的题目之后,应聘者要尽快分析出可能存在哪些非法输入,并和面试官讨论该如何处理这些非法输入。和面试官进行这样的讨论对应聘者是有益的,因为面试官会觉得他对错误处理有着全面的了解,并且还会觉得他有很好的沟通能力。
代码的健壮性
健壮性是指程序能够判断输入是否合乎规范要求,并对不合要求的输入予以合理的处理。容错性是健壮性的一个重要体现。不健壮的软件在发生异常事件时,比如用户输入错误的用户名、试图打开的文件不存在或者网络不能连接,就会出现不可预见的诡异行为,或者干脆整个软件崩溃。这样的软件对于用户而言,不亚于一场灾难。
由于健壮性对软件开发非常重要,面试官在招聘时对应聘者写出的代码是否健壮也非常关注。提高代码的健壮性的有效途径是进行防御性编程。防御性编程是一种编程习惯,是指预见在什么地方可能会出现问题,并为这些可能出现的问题制定处理方式。
在面试时,最简单也最实用的防御性编程就是在函数入口添加代码以验证用户输入是否符合要求。通常面试要求的是写一两个函数,应聘者需要格外关注这些函数的输入参数。如果输入的是一个指针,那指针是空指针怎么办?如果输入的是一个字符串,那么字符串的内容为空怎么办?如果应聘者能把这些问题都提前考虑到,并作相应的处理,那么面试官就会觉得他有防御性编程的习惯,能够写出健壮的软件。
当然并不是所有与健壮性相关的问题都只是检查输入的参数这么简单。应聘者看到问题时,要多问几个“如果不……那么……”这样的问题。比如面试题“链表中倒数第k个结点”,这里隐含着一个条件就是链表中结点的个数大于k。应聘者就要问自己如果链表中的结点不是大于k个,那么代码会出什么问题?这样的思考方式,能够帮助发现潜在的问题并提前解决问题。这比事后让面试官发现问题之后应聘者再去慌忙分析代码查找问题的根源要好很多。
小结
本文从规范性、完整性和健壮性三方面介绍了应聘者如何在面试时写出高质量代码(如下图所示)。
第一,应聘者在白纸或者白板上手写代码时要注意规范性,尽量清晰地书写每个字母,通过缩进和对齐括号让代码布局合理,同时还要合理命名代码中的变量和函数。
第二,应聘者最好在编码之前全面考虑所有可能的输入,确保写出的代码在完成了基本功能之外,还考虑了边界条件,并做好了错误处理。只有全面考虑到这三方面的代码才是完整的代码。
第三,应聘者要重视代码的健壮性,确保自己写出的程序不会轻易崩溃。平时在写代码时,应聘者最好养成防御式编程的习惯,在函数入口判断输入是否有效并对各种无效输入做好相应的处理。应聘者如果能够做到这三点,自然就能写出高质量的代码,最终通过面试拿到Offer也将是水到渠成的事情。