招聘需要把初衷搞清楚,把对于软件工程师候选人考察的策略大体订立清楚,做到心中有谱,以“先胜后战”。
公司和团队,到底需要怎样的技术人才?这就是我们建立整个面试考察评估体系的时候,最最基础的那个问号。
1.能干活的,能加班的
这种初衷可以理解,但实施起来却非常困难。也许在某些行业这的确是可行的,但是在软件工程的世界里却恰恰相反。
持有这个观点的人,把软件工程这个复杂的行为,使用简单的劳动分工来划分了。在历史上,这样的做法并非没有人尝试过,只不过大多数的尝试都失败了,或是最终令它的其它环节成本,高到失去了这样做的意义。
比方说,我们可以把软件工程的流程简单地分为需求、设计、实现、测试、维护等几个步骤。当我们将“实现”环节的人员要求降低,也相应地将质量要求降低,那么我们就将迫不得已在其它环节投入更多的人力去平衡,最终导致研发成本并没有得到本质上的改善,反而在实现环节埋下隐患,比如糟糕的代码设计为未来的维护和扩展挖坑。
这样的平衡,可能是花更多时间来完成更细致的设计文档,可能是添加更多的测试人手和执行更全面的测试,也可能是花更长时间试运行版本和 bug 修复,还可能是招更多的、专门的管理人员来盯着这些程序员严格遵守流程。
虽然我未抱着这个思想进行招聘,但是由于一些客观原因,招聘更多的是大量的新手。而老手又不具备教的实力,强行教,反而让新手沾染了老手的不良习惯。团队也陷入了这样的困境。培养一个人与培养一个团队难度指数天差地别。于是,我想把更多的精力,也就是投入更多的管理成本、培训成本,在新手身上。
说白了,就是在软件工程的其它环节做更多的工作,然后给糟糕的实现环节擦屁股和买单。
2.“算法好的”
好端端一个软件工程师的面试,最后变成了刷题成果检验会。
原因很简单,用四个字来概括,就是“以偏概全”。
软件工程是一个多维度的、复杂的行为,代码实现只是其中的一个部分;而算法,更只是代码实现的其中一个部分。单纯的算法牛人离真正的软件工程师,其实还差了十万八千里。
3. “牛人”
如果我们把“牛人”理解成“技术杰出的人”,团队真的需要各种大牛吗?我看未必,相较于“牛人”,团队更需要的是“合适的人”。
先从技术角度来说。理想的团队,技术层面上是需要在一定程度上互补的。比方说,在一个全栈的团队中,有的成员擅长系统底层,有的擅长 Web 应用;有的系统思维严谨,有的产品直觉敏锐,那么无论在系统设计还是在问题定位的时候,团队的决策就可以因为取长补短而达到一定的平衡。
再从非技术角度来说。即便在技术层面候选人已经无可挑剔,我们都还有很多非技术因素需要考虑;即便在个人的非技术层面已经无可指摘,我们依然需要考虑候选人和团队风格的契合程度。举例来说,某些技术特别出色的人,个性更为鲜明,或者说不易相处。我们应该对这样的候选人说 yes 还是 no?
牵涉到一个公司的文化,以及团队的接纳程度了。显而易见的是,一个团队里面,如果全都是“刺儿头”,到处是激烈的观点碰撞,这肯定是一个糟糕的状况,很可能经理每天要忙于摆平冲突;而一个团队里面,如果全是“老好人”,只有赞美和观点认可,这其实也是一个令人担忧的状况,因为缺乏观点往往就意味着决策力的缺乏。
4. 聪明、有潜力的”
一家公司也好,一个团队也好,有经验的工程师和有潜力的工程师,需要在构成上达成一定的平衡。
有的团队相对技术成熟,架构稳定,流程确凿,而有经验的工程师可以有一定的时间来指导和帮助新人成长,团队可以培养自己的骨干,那么这样的候选人可以说是再好不过了。
而有的团队则相反,特别是某些创业团队,或是产品初创团队,每个人都要挑大梁,甚至大家都忙于救火,那么这种情况就特别需要自我管理能力强、综合素质和经验皆备的老兵,这时这样的候选人就不适合。
我们公司开发团队,属于第二类情况,亟需改变这样的现状。
公司和团队到底需要怎样的技术人才?或者说,反映在面试中,我们应该从怎样的角度去考察和甄选人才?从宏观上怎样去把握软件工程师候选人甄选的维度?
常用的角度,就是对于一名优秀的软件工程师普遍的、客观的理解。
(1)技术层面
技术层面上,我们要考察软件工程师的基础技能。包括:实际问题解决能力,代码设计和实现能力,系统分析设计能力,测试和验证的能力。
a.实际问题解决能力
我不赞成面试中,较多地使用单纯的算法和数据结构问题。不是因为它不具备考察性,而是它的覆盖面太窄,具体说,一个典型的实际问题的解决,需要包含两个部分:
而单纯的算法和数据结构问题,不但只能解决上述两点中的第二点,而且这第二点还必须得是一个落得到编码层面、甚至数学层面上的问题,足见其覆盖面之窄。至于怎样的问题才是一个好的面试问题。
b.代码设计和实现能力
这是软件工程师的基础技能.它涵盖的范围也比单纯的算法和数据结构广泛得多了,像是有些公司特别喜欢考察的面向对象就属于这个范畴。
c.系统分析和设计能力
包括是否能够针对问题给出合理的系统估算、架构设计等等。这一部分是和经验密切挂钩的。
一般来说,丰富的工作经验,理应意味着一定的系统分析和设计能力,我们可以简单这样认为:
这里说的“系统”是泛指的“软件系统”,而不是“分布式系统.
如今的互联网的浪潮,给技术人也带来了一些不太好的影响,比如过度追捧“分布式”这样的热门词汇。仿佛谈及系统,就是大数据、海量、高吞吐,一些简单的系统都不太瞧得上了,我觉得这是很不妥的。
对于系统的理解,就如同数学的基础是初等算术一样,还是要把根基打扎实。
比如测试与验证的能力等等。不是说它们不重要,也不是说不考察,而是说,在面试中,对于软件工程师的技术考察,我们要有所侧重。比方说,我们可以简单地约定只把 20% 的技术考察时间,放在这部分。
计算机的基础知识考察在哪里啊?其实,它就蕴藏在上述的考察项之中。
比方说,要设计一个网站系统,要有对于网络协议,特别是 HTTP 协议的基础认识吧,要有对于中间件、容器的基础知识吧,还要有对于数据库的基础理解吧。
(2)非技术层面
我们关注工程师的基础品格、团队合作能力、学习能力、沟通能力,以及任务管理能力等等。
举例来说,面试中,如果你发现与候选人的沟通存在很大问题,那么你不可能不留意到这一点,而根本不需要特别地去问一个“专门”考察沟通能力的问题。
综上,对于软件工程师的候选人来说,面试的轮次要保证大部分都以技术面试的形式覆盖,而允许少部分以非技术面试的形式覆盖(比如 5 轮中有 4 轮技术面试,1 轮非技术面试)。但是每一轮技术面试拿到的数据,却应当同时覆盖二者。
对于踏入职场不久的工程师,要更关注潜力相关的素质,比如是否能够听从建议并加以思考,以及是否具备兴趣与热情,因为这是学不到的东西。
对于有丰富经验的工程师,要更关注视野,技术理解的深度,以及职业素养。
指的是不同的团队和细分职位,对于软件工程师候选人,有着不一样的要求。
技术层面上,比方说有一些职位要求具备前端技能,有一些职位要求具备 AI 背景,而有的团队则要求云计算方面的工作经验。
在非技术层面,很多大厂都会设置几条特定的指导原则,这些原则就像纲领一样,指导对于候选人非技术部分评估的过程。比如亚马逊的领导力准则,这几年来讨论的人很多,在亚马逊每一轮面试都会被要求覆盖其中的一到两条。
即便某一特定的细分职位,对于技能有着较为细致和明确的需求,我们还是可以发现,对于大厂的面试,往往常见角度部分所占比例就越大。
显而易见的是,越细分的职位,越特定的要求,就越能够提高候选人的匹配度,但是这也降低了人才筛选的普适性,让招聘变得无比困难。因此,这些大厂,给出了模糊的招聘标准,而选择在招进来以后,做进一步地培养。这既是刻意为之,也是无奈之举。
一个面试中在“常见角度”部分表现优秀的软件工程师,更可能就是一个通常意义上的优秀的软件工程师,那么就算他并不立即具备特定职位的某项技能,但是经过较短时间的学习和融入,他也很可能可以很快地胜任这个职位。
对于任何一个公司和团队来说,都不是要招“牛人”,而是要招“合适的人”,我们大的目标是和候选人一起共赢,而不是单纯的劳动性价比。
首先,清楚团队的需求和限制,设立合理预期。我们当然不希望招错人,但是我们也不希望错过优秀和合适的人。
我们需要明确的是,时间、人力成本的足够投入,和考察角度的合理覆盖,是两件完全不同且都必不可少的事情。我们希望达到的效果是,面试团队通过足够量的投入,得到了可以帮助评估候选人的较为全面的数据,但却没有过度地重复覆盖某一个角度。
流程正确了,数据充分了,结果就会是合理的。
再次,考察角度、考察重点、考察内容,三者也要遵从一致性原则。
最后,考察轮次需要保持渐进性。
当我们理解了团队需要和考察角度,把人选和计划排出来,虽然真正的面试轮次还没正式开始,可是已经成功了一半。
电话面试最大的作用,就是用相对较小的代价,“过滤”掉那些明显不靠谱的人。
在计划中,电话面试轮次通常会比较少(1~2 轮),因而,电话面试要能覆盖到我们最关心的那部分内容。而电话面试的决策,通常也非常迅速,电话面试的面试官互相碰个头,如果通过,招聘经理也觉得没什么大问题,那就可以继续,进入现场面试。
电话面试是否通过,如果在你在犹豫,那么:你觉得候选人能否妥妥地通过至少一次现场面试?如果能,那就通过电话面试。
而从非技术角度来看,要重点关心对方是否有一些明确的要求,是否和团队能提供的所匹配。比如说,如果这个职位要求外派出差办公,而候选人不能接受这一点,那就必须要在电话面试或者之前就沟通清楚,以节约大家的时间。
应届生与老兵,这是两类截然相反的候选人,我们考察的重点是不同的。
对于没什么经验的工程师来说,我们更看重的是潜力;
而对于经验丰富的工程师来说,我们更看重的是能力,这从对于系统设计能力的要求上就能看出来。
1.知识性的问题,考察的知识,记忆力,而不是任何一项工程师的核心能力。不适合花费大量的时间去考察。因为一方面它不具备普适性,另一方面它又具备太强的随机性。
不具备普适性,指的是候选人必须要了解特定的框架或者库,考虑到优秀的候选人的知识背景有所区别,所以这一要求过于武断;
而太强的随机性,指的是候选人是否知道这一个知识点,随机性太强,而并不能反映候选人的技能水准。
有一些问题特定编程语言、框架和库限定下的问题就是其中之一。
“请实现 C++ 的 atoi 算法。”
这个问题,如果团队就是要求候选人必须具备 C++ 技能,那没什么问题;但是对于大多数团队来说,情况并非如此,那么这个问题就对平时以 Java 为主要编程语言的候选人不太公平了。这也是为什么,很多大厂对于面试流程的培训中,有一条指导原则:不限制候选人使用的编程语言。
2.过于常见的问题
被使用太多遍的问题,很有可能就是候选人准备好了的,这就让考察的真实性大打折扣了。诚实的候选人会告诉你,这题我做过了,但你不能奢望每个候选人都如是操作。
相反,我们恰恰就是要通过“新”问题考察候选人的“旧”能力。这就是为什么我们说,问题是多变的,但套路是永恒的。
3.规则过于复杂的问题
复杂问题的简化途径,和考察项是密切相关的。
以及远离了我们要解决的实际问题,远离了我们的工作场景,既没有业务覆盖,也没有技术覆盖,它在技术面试中的价值实际就很低了。比如一些脑筋急转弯题目。
围绕面试计划和面试目标,获得有效考察数据,从而便于我们在面试后做出决策。
举例,某面试官要对候选人进行面试考察,重点是“实际问题的解决以及对系统的理解”。于是,他决定使用一个“网约车系统的设计”问题来作为主要问题,在面试中和候选人一起讨论。那么,从这里我们大致可以看出,面试官需要把握这两个要点:
从模糊到清晰可以理解,可是什么是从实际到抽象呢?难道不应该是从抽象到实际吗?只有落到实际才能够实现啊!
软件工程师,就是要做工程的,而做工程,从本质上说,就是要解决实际问题的。
从模糊到清晰,以及从实际到抽象,看似是两个过程,但其实它们恰恰是统一的:
实际问题,往往就是模糊的,它可能来源于顾客的一句抱怨,用户的一个建议,或是一个不明不白的痛点;
而软件途径可以解的问题,必须是清晰的、简单的,如果连软件的设计人员自己都说不清楚需求和逻辑,实现就无从谈起了,既然能够说清楚,这个描述肯定是经过了简化和抽象,去掉了细枝末节,只保留问题的核心。
因此,一个具体问题只会有某些核心的部分可以通过软件来解决,而余下的大部分只能是作为陪衬。这一条原则,其实谈的是“深度”。
面试官向候选人提出了这样的技术问题:
“请你设计一个网约车系统。”
有些候选人拿到问题以后,会不知所措,“天呐,我该从何下手?”没错,这个问题看起来就非常大,但这也是一个实际问题,工程师要去解决的,经常就是模糊的实际问题。
我们希望和候选人一起,做出这样一个将问题从模糊到清晰,从实际到抽象的转化过程,以系统设计的考察路径为例:
从中你可以看出,整个思路是这样的:
问题 > 需求 > 系统设计 > 代码设计 > 代码实现
为了可操作性,每前进一步,都会缩小范围,纵深地考察候选人不同层次的技能。不止一个考察角度,不止一个解.
“不止一个考察角度”,指的是一开始同样模糊的描述,可以根据面试计划中的安排,逐步引导到某一个特定的考察角度上去。这一条原则,其实谈的是“广度”。
如果考察重点要求我们走一条数据结构和算法考察的路径,那也是可以的,比如从起点到终点的简单的寻路算法,落地到代码;我们还可以走面向对象设计考察的路径,设计关键用例中涉及到的类,包括它们的结构和关联关系,等等。
“不止一个解”,指的是在更低一级的层面,在细化到上述的具体的问题之后,解答不应该是唯一的,而应该存在多种不同的方法。比方说,寻路类的算法问题,我们有时可以用暴力回溯法来解,有时候可以用经过优化的动态规划方法来解,等等。
越是软件经验丰富的候选人,我们就越可以给一个较为模糊的问题,尽量让他来主导这个分解和细化的过程;
而对于校招生,我们则可以给一个相对清晰具体的需求。当然,在这个过程中,
如果发现难度过高,我们可以积极地给出提示,或者帮助他完成部分过程,这都是在合理范围内的。
什么样的问题,哪怕在发散性探讨时,你依然觉得尽在掌握?那一定是自己熟悉的。
“请你设计一个网约车系统。”
在问题抛出以后,有的候选人能够淡定地做进一步沟通,明确问题,而有的候选人则需要面试官逐步引导——“这个问题具体包括什么?”没错,这正是一个分析和细化需求的过程,需要候选人和面试官一起讨论。
我们可以从核心用例的角度来思考,想想都有什么角色,可以通过该系统完成什么功能,比方说:
于是我们可以选择其中最必不可少的一项或几项功能,来跟候选人继续进行探讨,毕竟面试时间有限,贪多嚼不烂。
在功能需求明确以后,进入设计步骤前,我们还需要讨论确定几项重要的非功能需求。比如用户量有多少、每日的请求数有多少等等。这些数值不需要多么精准,但是数值的数量级会影响到系统的设计。
候选人和面试官一起对问题层层梳理,把一个模糊问题转化为一个简化了的业务问题,只剩下有限的几个需求点要实现。
下一步,才是根据前面选定的需求点,从技术角度去考虑怎么实现;而技术实现,依然要秉承一样的原则,从模糊到清晰,逐步细化:
在有了一个大体可行的系统设计后,从中选出一层、一个组件,或是一个机制,进一步细化展开。
比如说,对于存储方面,如果候选人提出使用关系型数据来存储乘客和司机的位置信息,那么我们可以通过这样的思路来展开:
不同的候选人,能力是不同的,对于同一个问题能够完成的进展也是不同的。问题的深入要有层次,逐步细化问题、抽丝剥茧,最后落实到一个短时间内就能完成的局部实现上。
如果留意到问题对于候选人较为困难,面试官需要动态调控,目的是降低难度,方法可以是给出问题提示、主动减小问题范围、弱化问题要求,甚至直接推进问题分析。
而如果问题对于候选人来说过于简单,那就要压缩流程中时间的分配,更多地把时间放到问题解决之后,对于进一步改进等等的“follow-up”的讨论上面。
如果一个算法问题是,使用非递归算法实现二叉树的前序和后序遍历,而候选人看起来缺乏头绪,那么我们可以:
通过暗示也好,明推也好,不断深入并击破难度递增的每一层问题挑战,并且最理想的效果,是最终这个迷你项目的最高难度,恰恰是候选人“踮踮脚能够到”的难度,既不会过度简单,又不会难不可达。
太简单了没意思,候选人也会觉得这家公司和团队不怎么样;
太难了则又可能对候选人打击太大,或者导致他无法完成整个问题的解决流程。
我们可以通过上面的四种办法,引导候选人理清思路,直到可以开始动手编码的程度。
这个动态调整的过程是需要通过经验的积累,才能驾驭自如。但是我们从一开始,就可以树立这样的目标,我们希望不同程度的候选人,都可以“来时开开心心,走时不住回味”。
“持续收集数据,调整你的问题”。就好像要把任何一件复杂的事情做好,都要经过千锤百炼一样,一个问题设计好了以后,我们要反复地在实践中使用它。
一方面,可以不断地获取数据,这样的数据可以帮助你建立起属于自己的“坐标参考系”,在评估一个新的候选人的时候,我们知道他大概处于一个什么位置;另一方面,又可以根据结果,反向地调整、改进,甚至替换问题,包括可以根据情况适当地增大或者减小问题的范围等等。
使用一个你自己设计的问题,还会给候选人带来一定的“新鲜感”,做这个迷你项目,解决一个新颖、有趣的问题,而不是互联网上一搜一大把那些。感觉这是一个信手拈来的事情,可他所不知道的是,这个问题是面试官精心准备的,也是在实践中锤炼过的。
- “如果你所在的团队拥有一个重要的 service,它的平均请求响应时间为 1 秒,有一天你部署
- 了软件新版本后,留意到它的平均请求响应时间异常地增大到了 10 秒,你该怎么做?”
延伸一——考察对用户体验、对工程的理解:第一时间我们该怎样做,才能把对用户的影响降到最低?我们可能会谈到回滚,那么我们就可以讨论 Ops。怎样避免未来再出现这样的问题?这就可以讨论软件工程的流程,讨论质量保证,讨论 CI/CD,讨论灰度发布等等。这个延伸可以考察软件工程流程的方面,考察 Engineering Excellence 的方面。
延伸二——考察监控和告警系统设计:我们该怎样争取在第一时间知道我们的系统出了问题?这就可以谈论软件监控和告警,我们需要对怎样的数据进行监控和告警?怎样采集数据,怎样发送数据,即怎样设计告警系统?这个延伸可以继续细化到一个监控系统设计的问题,或是做代码层面的考察。
延伸三——考察问题排查思路、系统的理解:我们该怎样去定位问题,问题可能出现在哪一个层面?比如,可能 service 根本没事,只不过客户端出了问题,也可能网络有了问题,还可能就是 service 出的问题。而 service 出的问题,又可能有哪些原因造成,可以采用怎样的策略,去一层一层从高到低去定位问题所在?这一个延伸可以继续考察候选人对于系统的理解,尤其是通过一个请求响应的过程,考察他对于系统全栈的理解。
延伸四——聚焦系统某一层:通过细化到某一层来进一步展开,这部分的思路就很多了,根据需要我们可以选择几个常用的切入点。比方说,如果问题是关系数据库的语句过慢造成的,这里我们就可以进一步聚焦,讨论一些常见的关系数据库性能问题出现的原因,以及相应的解决思路。如果是应用层面的问题,比如应用的内存不足,那么可以从这个角度进一步展开。
延伸五——聚焦系统限流问题:如果问题是恰巧由于该时间点某一客户端在短时间内发送过量的请求,系统无法及时处理而导致的拖慢,那么我们就又可以从流量控制的角度展开。流量控制的问题也是属于经典的系统问题之一,既可以从系统设计的角度考察,也可以从代码设计的角度考察。
你看,一个模糊的实际问题,可以拿过来做多种方向的延伸,而这些延伸之间又具备密切的联系。我们可以根据需要,在面试中选取其中一个延伸的角度来进行考察。
如果我们总是能把技术考察,从一个简略、模糊的实际问题开始,逐步深入,到最后以一个完整的问题解决过程收尾,那么几乎就可以说,我们就做到了深度和广度的兼顾。
为什么我不推荐面试中使用简单粗暴的纯算法题?说到底就是一个考察面的问题,单纯地考察算法题,就是一个可以具备足够的考察深度,但是很容易丢失考察广度的例子。
有一种面试风格,是让候选人谈论一个他自己最熟悉,或是最自豪的一个项目,然后从中抓住某些点不断深挖。这本身听起来似乎也是一个挺不错的考察思路,和我们前面谈到的面试官去准备问题的思路,恰好相反——由候选人来主导这个过程。
但是现实中,对于经验尚浅的面试官,我一般不太推荐上面的方式,主要原因是,在实践中这个方式对于面试官的素质要求非常高:如果候选人谈到的项目或者系统,面试官比较熟悉,那一切都好说,但是如果不熟悉呢,面试官就很难把握住这里的关键,容易被候选人牵着鼻子走,可能这一轮面试候选人一直在泛泛地说,谈到了许多方面,但是面试官也提不出什么一针见血的问题。