其实很久以前就想谈一谈这个话题了,但是最近才有了足够的动机。因为从最近参加的很多 debrief 来看,我认为身边大多数的软件工程师面试中,在通过技术问题来考察候选人这方面,很多都做得不够好。比方说,我看到对于一些经验丰富的软件工程师候选人的面试,一些面试官依然是草率地扔出一道算法题让做了事,并且认为能不能够比较清晰完整地将代码写出来,是工程师级别裁定的最重要的标准。而这样的做法我认为是非常不妥的。
首先,我要明确的是,这个问题,指的是技术面试中俗称的 “主要问题”,具体来说,就是面试官会拿出一个问题和候选人讨论,并通过由此开始双方的互相沟通和问题发散来达到考察的目的,因此,这个 “问题”,从某种角度说,更像一个 “话题”。这个过程通常在每轮面试中会持续几十分钟(如果你对这种面试方式感兴趣,你可以看一下这个简单的介绍)。下面的讨论,都是建立在这种面试风格和方式之上的。
其次,作为一个 disclaimer,我想说,以下内容来自于我的认识,并且是针对于技术面试这一个狭窄范围内的认识,自然带有主观的倾向性和认知的局限性,它并不是来自任何公司或组织的标准。
好,下面我就来尝试把这个问题讲清楚、讲透彻。我认为这并不是一件容易的事情,因此如果你对其有不同的看法,欢迎和我一起讨论。
典型案例
我先来举这样一个典型的例子,这里面包含了若干个值得商榷的方面,你可以看看是不是似曾相识:
在和候选人谈论完项目和经历以后,面试还有 40 分钟,于是面试官问:你能否实现一个 LRU 队列?
于是候选人想了一下,就开始做题了,也就是在白板上大写特写,于是面试官也就开始忙自己的事儿了。等到 40 分钟后,候选人写了一白板,但是显然,他在这过程中遇到了一些困难,最后虽然实现了,但是代码写得有些复杂,也遗漏了两、三个重要的 corner case。
于是面试之后,面试官在评语中写道,“候选人能力一般,算法题实现起来磕磕绊绊,最后的代码偏臃肿,而且有明显的 bug”。
在往下阅读以前,请你想一想,这样的面试形式有哪些值得商榷的地方?
技术面试的目的
好,我先卖个关子,先不回答上面的问题,而是先谈一谈,对于软件工程师候选人来说,我们为什么要进行这样的技术面试。事实上,有很多考察项,完全不需要技术面试这样麻烦的途径,就可以很容易、很高效地实现;而下面我说的这些方面,这些对于软件工程师来说至关重要的方面,技术面试却是合理考察的唯一可行途径。
技术能力方面
通常不同轮次的面试,会考察不同的技术点,这个对于不同的团队和职位,是不一样的。
举例来说,对于某业务平台团队的一个高级工程师的职位,五轮面试中,一轮考察项目和经验,一轮考察系统设计,两轮考察具体问题的解决,特别包括算法和数据结构,还有一轮考察面向对象设计等其它内容,这其中,后三轮都包括白板编码的考察。
对于一个有经验的工程师候选人来说,我认为这就是一个比较立体、综合,也是一种比较合理的技术考察点的划分方式。并且,这五轮中有四轮都会花费大量的时间,通过我今天将谈到的技术问题,来对候选人进行技术能力方面的评估。这里,有这样几个非常常见的技术方面的考察项:
- 分析问题,整理需求的能力。问题在一开始可能很模糊,但是优秀的且有经验的工程师可以识别出核心的诉求来,这个 “识别” 的能力,下文我还会详述。这里的诉求可能有多个,但是考虑到时间的关系,面试过程中往往只会从某个角度覆盖其中一两个。
- 根据需求来设计系统的能力。这里既包括功能性需求,又包括非功能性需求,前者是必须要涉及到的,但是后者也经常也放在一起考量。其实这一点可大可小,它未必一定指系统设计中,功能是否得到实现,并且这个过程中,可能会涉及到系统的扩展性、可用性、一致性等等方面。
- 将核心逻辑实现的能力。如今,考察比重容易被高估的算法和数据结构就大体上属于这一部分。它也有功能和非功能的两个角度——功能上算法能否实现需求,非功能上算法是否具备足够的性能,编码是否遵循最佳实践,代码是否具备良好的可扩展性等等。而编码能力,指的是在思路达成一致以后,核心逻辑能不能落到纸面(代码)上。毕竟,“空谈误国,实战兴邦”。
- 经验和其它工程能力。这部分相对更为灵活。比如对测试能力的考察,即可以做怎样的测试来实现对于功能需求和非功能需求正确性的保证。对于特定的团队和项目来说,有时候会特别专注于特定的技术能力,比如前端的团队,是需要考察前端的基础技能的。
考虑到时间和覆盖面、覆盖深度的权衡,上面这四点有时候不能在一轮面试中完全覆盖,往往也会包括三点。比如,系统设计的面试可以着重覆盖 1、2、4 点,而编码为主的面试可以着重覆盖 1、3、4 点。
非技术能力方面
和技术能力考察所不同的是,非技术能力考察在不同面试官中的特异性更大,换言之,每个人的角度和标准都可能不相同。但我觉得下面这几条是特别重要,因而必须要覆盖到的:
- 沟通合作的能力。如果不计时间成本,最理想的面试是什么?其实就是一起工作。工作中才会有足够多必要的沟通,无论是正面的品质还是负面的问题都会无所遁形。可是面试的时间有限,我们没有办法实现真正的工作氛围,但依然可以模拟工作中一起考察、分析和解决问题的过程,而这个过程,就是要通过 “沟通” 串起来的。沟通是一个大的话题,具体的考察项有很多,比如,能不能接受建议?能不能讨论想法?有些候选人一旦进入自己的思考模式就听不进别的话了,于是一个点要反复强调几遍;而有的则是缺少 backbone,稍微追问一下,也不思考,就立马改变主意。
- 热情和兴趣。热情和兴趣的影响是巨大的,都说兴趣是最好的老师,这部分是很难 “教出来” 的。对于初级工程师来说更是如此。热情和兴趣不但会影响到他/她自己的未来发展,也会影响到整个团队的氛围。
- 学习能力。学习能力很大程度决定了候选人在新的岗位上进步的潜力。同样的基础和基本的问题解决能力,有的候选人能够 “一点就透”,触类旁通,这在一定程度上就反映了学习的能力。毫无疑问,软件工程师每天都在面对新问题,入职以后就会发现,不止问题是新的,代码是新的,类库是新的,工具是新的,在成熟到能够有一定产出之前,这一步一定是学习。
技术能力和非技术能力,哪个更占主导?事实上,这二者都很重要,并且二者各自又具备不同的特点。当然相对来说,非技术能力更加难以提高,因而这方面的问题要更加引起重视。比方说,要让一个对于软件领域缺乏热情和兴趣的候选人,在入职以后改头换面,是几乎不可能的一件事情。
其它方面
上面说的是技术面试中对于 “能力” 的考察。其实,还有一些考察项,严格来说并不能算是 “能力”,因此我就没有归类在上面,也不是本文的重点,但这并不是说它们不重要。
比如,候选人是否具备正直的品格。在 OCI,debrief 的结果,通常有 hire 和 no hire 两种,但是有一种情况,可以归结到一个特殊的 “never hire” 里面去,这样的候选人没有面试冷冻期,不会有职位和级别的讨论,就是一个 “永不考虑” 聘用——这就是品格问题。品格问题会导致 never hire 的出现,比如候选人对当前的所在职位说谎了。
再比如,和团队的契合程度。不同的团队,接纳候选人的程度和要求都是不一样的。一个典型的例子是,有时候我们发现,有的候选人在投票的边界线上,即本身是具备相当的潜力的,但是由于相对缺乏领域经验,且某些方面显示出方法明显不得要领。如果团队中有成熟、有经验的工程师可以带着,且团队有一定的空间允许他花更长的时间学习和成长,那么最后的结论就是 hire,否则就是 no hire。你也可以看出,很多时候这样的决定都不是非黑即白的,影响的因素是多方面的。
再再比如,性格不兼容导致的风险。我遇到过一例,候选人在面试过程中,在多轮面试中都表现出高傲和自满的个性来。于是这成为了一个担心招聘进来以后,风险过高的重要方面。于是最后我们放弃了这个候选人,尽管这个候选人的技术方面是没有问题的。有人可能会说,聪明人都是有个性的。但其实 “有个性” 和 “难相处” 却有着微妙的差别,而且再包容的团队,也有自己的底线。我们当然不希望错过优秀的人才,但是这并不是不计代价的。
从这几个方面也能看出,这些 “非能力” 的考察项,往往具备着或 0 或 1 的 “red flag” 的特点。面试官一般不会花心思在这部分的考察上,但如果发现这方面的问题,且经过了明确。那结果往往就会是一个明确的否决票,而这个否决票是和级别、职位无关的。
回看那个案例
讲完了技术面试的目的,再来回看那个案例。那个案例中,所记叙的面试过程,对于技术面试的技术能力和非技术能力的考察,是否有覆盖呢?我们不妨一条一条看吧:
技术能力方面:
- 分析问题,整理需求的能力。这一条考察的程度明显是不够的,给出的问题,是一个明确的、具体的算法题,也就是要实现一个 LRU 队列。也许这其中存在着问题分析和需求整理的空间,但对于具体算法题来说,这个空间显然并不大,而且候选人闷头就写了,这方面无从考察。对于一个初级工程师的面试来说可能还好一些,我通常没有微词;可对于一个要去面试一个经验丰富的工程师,我是很不赞成纯算法题面试的,而这就是其中的一个重要原因。
- 根据需求来设计系统的能力。这一点的覆盖基本为 0。上来就写代码了,不清楚思考的过程,也更谈不上什么系统设计了。
- 将核心逻辑实现的能力。这条确实是这种面试方式能够覆盖的部分,因为整个过程,就是候选人思考并编码实现的过程,只不过,面试官能得到的只有一个 “结果”,而非整个 “过程”。这样的数据,能反映出来在核心逻辑实现方面的价值,就要大打折扣了。
- 经验和其它工程能力。也许能够从代码的实现上获知一部分,但这一条考察的程度也显然是很不够的。
再来看非技术能力方面:
- 沟通合作的能力。这是最大的问题,因为这方面是远远不足的,整个过程没有沟通,没有合作,只有默默地做题。
- 热情和兴趣。这个过程很难从这个角度获取足够的候选人在热情和兴趣方面展示出来的信息。
- 学习能力。同上。
也就是说,除了 “将核心逻辑实现的能力” 可能还勉强过得去,这样的面试方式,并无法全面、合理地考察候选人作为软件工程师的综合素质。
事实上,如果你联想实际工作。如果你的团队中有这样一个工程师,拿到一句简单的需求,不确认问题,不沟通设计,不讨论方案,直接就开始埋头苦干,就算能写出可以工作的代码来,这是不是依然是一件无比恐怖的事情?显然,我们的面试要尽可能避免这样的事情在真实世界中发生。我们要找的是软件工程师,不是只会刷题编码者。
这也是我把这个案例,放在开头,作为反面案例的原因之一。
等等,“之一”!难道还有别的原因?
是的,这还没完,这个案例还有着其它弊端,我想再卖个关子——而现在,你可以想一想,再往下看。
怎样的问题才是 “好” 问题?
终于要正面回答标题中的问题了,到底怎样的问题才能真正称得上 “好” 问题呢?下面是我认为最重要的几条衡量标准。
从模糊到清晰
首先,这个问题在一开始要足够模糊,以便让候选人可以逐层递进,逐步细化,寻根究底。这个过程,其实就是将 “具体问题” 经过分析、归纳、思考、抽象并将其映射成为一个 “软件问题” 的过程。在问题变得清晰的过程中,理想的情况是,候选人可以表现出主动性,即候选人可以在多数情况下引领讨论的思路,而不是面试官。面试官需要顺着候选人的思路,逐步框定下问题的讨论范畴,并明确到其核心实现是确实可以用软件的办法实现的。
在这样的状态下,候选人可以以自然的状态,具备相当自由度地发挥自己的能力。从这个过程中,可以观察得到太多候选人的不同角度的特质了。通过这种方式,也可以很大程度避免了已经知道 “标准答案” 的面试官,由于思路的局限性,而给面试施加的源自于主观偏好的影响。
这就好像是开放世界的 RPG 游戏,有多个不同的路径都可以完成任务,玩家可以决策并决定主角的走向,但是这一切始终还要在游戏设计者的掌控之内。这当然是说的理想状态,有时候会有偏差,但我们朝着这个方向努力。这也对面试官驾驭不同的状况有着很高的要求,毕竟,面试官要对这个问题前前后后足够的熟悉,以便应对各种不同的细化场景。有一个常见的方式,是可以从一个自己已经足够熟悉的问题开始,比如自己曾经多年工作涉及的某类系统。
我来举一个具体例子。比如,有这样一个问题:
怎样设计一个流量控制系统?
这就是一个模糊到没法再模糊的问题了。不知你会不会产生下面这样的问题:
- 什么系统需要流量控制?
- 现在的流量是多少?
- 需要支持到什么时间精度?
- 流量控制的规则怎么定义?
- 超过流量的请求怎么处理?
- ……
其实,这些都或多或少是需要面试官和候选人一起逐步思考、分析和明确的。在这个过程中,可以考察的内容太多太多了。
事实上,针对不同程度的候选人,上述这个问题给出的最原始的模糊程度是不一样的,问题越是模糊,这部分对于候选人的要求也就越高。对于一个工作十多年的,有着多年系统设计经验的工程师来说,上面这些问题大致都应该是他/她能够主动提问,或是主动引领明确的。
值得一提的是,理想的问题最好还有一些隐藏的 “坑”,能否把这些坑识别出来,也是对于工程能力方面,一个很好的小的考察点。比方说,优秀的候选人应该想到,流量控制可以基于绝对时间窗口,或是相对时间窗口来进行的,但是要真正保护系统,相对时间窗口才是最理想的。当然在实现难度上,相对时间窗口,往往会更难一些。
而对于一个没有工作经验,并将要研究生毕业的候选人来说,问这样一个模糊的问题,往往带来了过大的难度,不但不容易推进面试的进程,还可能给候选人带来沮丧的心理。我们不希望看到,候选人拿到问题以后就懵了,如果发现候选人推进有困难,面试官需要介入并帮助。
因此根据候选人的程度,这需要面试官主动回答这些问题,或是直接缩小或明确问题的范畴,当这个问题的范畴缩到最小时,这可以是一个直接存在多种解法的算法题。极端地说,这个问题可以一直缩小到这样的程度:
假定说有这样一个 API,名字叫做 isAllowed,这个 API 在系统每次收到请求的时候就调用,传入的是请求对象,传出 boolean 值表示是否允许这次调用——如果最近一分钟内调用次数小于 10000 次就允许,反之则不允许。你能否将这个 API 实现出来?
如果候选人还一脸迷茫,可以提供这样的参考 API:
1
2
3
4
5
|
class RateLimiter { public boolean isAllowed(Request req) { // TODO } } |
你看,这只是一个将问题明确、细化和分解的过程,并没有涉及到实际实现代码该用的算法。但是,上面提的那些问题,要么都通过这个例子明确了,要么都给出具体数字了,这本身,就将一个模糊的问题,降低难度明确为一个具体的算法问题了。
不止一个解
前面一步已经谈到了有不同的方式可将模糊的 “实际问题” 映射到了一个可解的 “软件问题”,那么现在,这个 “软件问题” 依然没有标准答案。可能有几个参考答案,它们互相比较起来各有优劣。大多数情况下,候选人的思路,都在这几个参考答案的思路之中,但有时也能看到特立独行的新奇思路。
如同前一步所说的那样,对于不同级别的软件工程师职位来说,需求分析、系统设计等等这些方面的要求可能有着很大的差别;但是在这一步,对于数据结构和算法这样的基础能力,却是接近的。
对于这里谈到的流量控的算法来说,实现方式是有很多种的,代码复杂程度,控制精度,时间复杂度和空间复杂度等等都有着非常大的区别。当然此时涉及到的,已经基本只是算法层面的话题了,就算法本身而言,我在极客时间专栏中对其中的几个典型方法做了一些介绍,感兴趣的话可以阅读。
我可以再举一个我曾经经常在面试中拿来使用的例子:
某社交网站有两百万的注册用户,每个用户都有积分属性,且积分根据用户在社交网站上的行为而不断有小幅度的频繁变更(比如登陆一次就+1 分,评论一次就+2 分等等),怎样设计一种算法,能够高效、准确地实时获取指定用户在所有用户中基于积分的排名?
上面的问题从模糊逐步落实到实现上的时候,异步、定时地排序,是最容易想到的方案,而题目表述中的 “高效” 和 “实时” 这两个修饰词让这个问题变得困难。这个过程中,我见到的不错的办法就至少有七、八种,比方说,下面这个推进问题解决的例子:
- 候选人:在需要的时候进行排序,方案是……
- 面试官:好,这样的方式下,时间、空间复杂度是多少?
- 候选人:……(说着说着自己意识到时间消耗可能巨大)
- 面试官:对,你能否优化?
- 候选人:……(提出了一些优化思路,但是他自己对它们的实时性也不满意)
- 面试官:好,有换个角度更进一步优化的方式吗?
- 候选人:对了,可以让数据一直是排好序的!
- 面试官:好主意,那你怎么设计数据结构呢?
- 候选人:我可以使用一个 map 来保存用户 id 到积分的映射,再把积分从小到大按序放在数组中,这样二分查找就可以找到对应积分所处的排名。
- 面试官:听起来不错,那么这时候获取排名的复杂度是多少?
- 候选人:……
- 面试官:对,每当用户的积分小幅变化的时候,你怎么维持这个数组依然有序?
- 候选人:从数组中拿掉一个老的积分,再放入一个新的积分……
- 面试官:这个变更影响的数据量有多少,时间复杂度又是如何?
- 候选人:……
- 面试官:不错,可这个方法有什么问题吗?
- 候选人:(恍然大悟)如果新添加一个用户,新的积分会出现在数组头部,数组内的所有数据都要向后移动一个单位!
- 面试官:没错,那你打算怎么优化?
- 候选人:可以把数组内的积分从大到小排序,这样新添加的用户所对应的积分总在尾部。
- 面试官:很好,这个方法还有什么问题吗?
- 候选人:……(意识到在某些情况下,有很多用户拥有相同的积分,这时时间复杂度会退化)
- 面试官:那样的话,你怎么优化?
- 候选人:数组的元素除了记录当前积分,还记录有多少个用户具有这个积分,从而消除相同积分的重复元素……
- 面试官:很好,可这个方法会带来一个问题,你能想到吗?
- 候选人:对了,如果积分变化以后,新的积分是没有出现过的,那么添加到数组里,就是一个新元素,于是所有比它小的积分全部都要向后移动一个单位。
- 面试官:非常好,那么你怎么优化?
- 候选人:如果使用链表来代替数组就可以避免这个问题,(突然意识到)可是链表我就没法二分查找了……
- 面试官:没错,那什么样的数据结构和链表在这方面具有一定相似性,又能够具备二分查找相似的时间复杂度?
- 候选人:……(这一步能回答出来答案就很多了,很多都是很不错的思路,比如有用跳表的,有用二叉搜索树的等等)
这只是一个简化了的片段,实际的沟通的内容远比这部分内容多,但是从中也依然可以管中窥豹,看出问题解决的过程是怎样逐步推进的。如果你对这个问题本身感兴趣的话,可以参阅一下 《排行榜算法设计实现比较》和 《海量用户积分排名算法》这两篇文章,它们讨论了其中的几个思路;另外,我以前在这篇文章里也简单讨论了其中的一个思路。
从这里也可以看出,无论是从实际问题细化到软件问题,还是求解这个软件问题,都存在着多条通往罗马的道路,看起来很美好,但这样的问题设计和面试把控并不容易。但既然大家都是软件工程师,是未来有可能一起工作的工程师,面试官的能力和可能就和候选人接近,于是,为了保证面试的效果,就一定要精心准备这样的问题,而不能指望随机和临场想出来一个 “好” 的问题提问。
围绕问题的解决要完整
这个问题的分析、讨论和解答过程要完整。对,其实这一点说的已经不是问题本身了,而是攻克这个问题的过程了。
这指的是整个过程要努力让候选人能够抵达 “踮踮脚能够到” 的难度,并且能够完成从确认、分析、讨论、编码、验证和改进等一个过程。这让整个面试显得完整,同时带来了这样两大好处:
- 对于面试官来讲,这样一个完整的过程,可以更全面地考察候选人,避免陷入视角过窄和一叶障目的情境。同时,“踮踮脚能够到” 的难度,又可以给整个考察的进程具备较为合理的预期。
- 对于候选人来讲,心态可以得到一定程度的平复,不沮丧,能够 “完整地” 面试完一轮,能够收获信心。别忘了,面试是双向的,给候选人一个良好的印象是很重要的。
前文我举的这个将问题从模糊到逐步清晰化的这个例子,就是一个需要面试官根据候选人情况动态调整的例子。在候选人能够经过思考而快速推进问题解决进展的时候,要让出主动权,以被动回答和鼓励为主;但在候选人卡壳的时候,要夺回主动权,及时给出提示和引导。
在落到数据结构和算法上面的时候,极少有候选人能够在叙述思路的时候直接给出最优解的。这时候,如果时间充裕,特别是在候选人进展非常顺利的时候,可以不断提示、追问以要求 “代码前优化”,一步一步优化到他/她的问题解决的能力边界,这就是其中的一个探寻其 “踮踮脚能够到” 的这个问题解决能力之边界的一个办法。但这个过程的前提,是一定要给编码留足时间。当然,如果候选人不能在限定时间内给出清晰的优化后思路,那不妨就退一步到原先那个算法角度不那么 “好”,但是思路清晰的解法上,并落实到代码。
比方说,对于前文所述的那个流量控制的问题,候选人在还有半个小时的时候就想到了使用一个时间复杂度为 O(N) 的解,而面试官认为时间还比较充裕,那就可以尝试挑战一下候选人 “能否再优化一下复杂度?”。
有些时候,由于前面的过程磕磕绊绊,时间剩下不多了,依然只有时间复杂度较高的 brute force 的解法,那么将这个解法实现了,其实也是一个不错的选择。有时候时间实在比较紧张,可以要求实现一部分核心代码,这些都比由于时间太短而代码写了一半匆匆收尾要好得多。我的经验是,在讨论充分,思路清晰的情况下,代码完成的时间,一般只需要 5 到 15 分钟,这样的代码量对于面试来说是比较合理的。
当然,相较而言,一种更为糟糕的结果是,一直到最后,讨论的深度依然离编码尚远,甚至依然停留在一个很高的泛泛而谈的层面。
如果在编码完成之后,尚有时间,优秀的候选人会拿实际的例子去验证代码的正确性。而面试官也可以和候选人讨论 “代码后优化”,比如以下的问题:
- 你能否进一步优化算法以提高时间/空间复杂度?
- 如果是工业级别的代码,你觉得代码还有哪些问题?
- 你该怎样去设计测试,来保证这段代码的正确性 ?
对考察项的覆盖兼有深度和广度
这个问题要能够考察前文所述的技术能力和非技术能力。
这里说的覆盖,不一定要全部覆盖,但是要覆盖其中的大部分,并且对于每一个考察项要具备一定的考察深度。我见到过不少其它的面试风格,但我认为这样就着一个模糊的主要问题(话题)逐步展开的方式是最好的。因为它可以兼具广度和深度的平衡。具体来说:
面试很容易走向的一个极端就是考虑广度,但缺乏深度。比如一种风格是绝大部分的面试时间用来询问候选人的项目和经验,让候选人自己介绍,而面试官跟进追问。这原本是一种很好的方式,但是由于候选人对自己的项目通常远比面试官熟悉得多,除非明确的同一领域,否则面试官较难对于其中的内容挖掘到足够的深度,从而识别出候选人是真正做事的人,还是夸夸其谈的人。这也是这种方式理论可行,但实际开展难度较大的原因之一。
另一个极端,自然就是考虑深度,但缺乏广度。比如给出一个过于具体的问题,缺乏发挥和迂回的空间,对这个问题所涉及的很小一部分深度挖掘,甚至纠缠于某个特殊而单一的 case 很长时间,但是却只能覆盖很少的考察角度。
由于深度和广度都是可控的,那么这样的可以拿来问不同经验和不同技术背景的工程师候选人。这样对于面试官来说,可以获得足够的数据,便于在遇到新的候选人的时候,能够进行横向比较,做出更准确的评估。比方说,过去某级别的候选人能够在面对这个问题的时候,能够达到什么样的级别,而如今这个候选人有着类似表现,这就可以以过去的那个例子来作为参考比较了。
再次回看那个案例
现在,让我们再次回到文章开头那个例子问题,除了已经提到的考察项的覆盖不够,它还有哪些问题呢?参考上文已经提到了 “好” 问题的标准,我觉得其中的这样两条是违背的:
从模糊到清晰。显然,问题给出的时候就已经相对比较清晰了,这样的方式并不能模拟软件工程师日常面对的许多模糊而困难的实际问题。我已经提到,对于经验丰富的候选人来说,这样的问题无法重现将实际问题映射到软件问题这样的一个重要思考过程。
另一个是,围绕问题的解决要完整。示例问题的考察过程中,只有缺乏互动的编码环节,没有其它过程,没有编码前的分析、思考和优化,也没有编码后的测试、改进和优化。考虑到 LRU 完整算法是一个实现代码量偏大的问题,拿来放到面试中做一个完整实现,由于会消耗过多的时间,挤压其它时间,因此显得有些过了。
其它 “不好” 的问题
前面说了过于清晰的纯算法题的 “不好” 之处,也说了 “好” 问题的例子,最后我想再来说几类其它的且典型的 “不好” 的例子。
被使用过太多遍的问题
很多公司(如 Google 和 Facebook)都有参考题库,题库会不断更新,其中一个重要原因,就是要尽量避免候选人做过被问到的题目。而且,问题越明确和具体,一旦候选人做过,考察的效果就越不客观。比方说,下面这个问题本来是个很不错的问题,但由于是被过于广泛地使用了,因此我还是不建议拿它来用作面试题的:
怎样设计一个短网址系统?
依赖于特定语言或框架
而这里说的依赖不好,是因为考虑到候选人不同的技术背景,如果没有特殊的需要,避免这样的依赖,以避免产生不应有的错误的衡量数据。
比方说,问一个关于 JVM 的问题,这个问题可以问,特别是在候选人强调其具备一定 Java 背景的前提下。但是这样的问题不能成为 “主菜”,尤其是候选人不一定具备很强的 Java 背景,这样的方式会导致考察的偏颇。
过于复杂的规则或背景知识
这主要是基于面试的有限时间考虑的,过于复杂的规则或背景知识,容易把时间消耗到澄清它们上面;另外,有些背景知识并非是所有人都熟知的,这就会引起考察标准的非预期。我给一个经典的反面例子:
设计一个算法,把一个小于一百万的正整数,使用罗马数字来表示。
这个问题描述简洁,但是拿来做技术面试中的主要问题,其中一个不妥之处就是,不是所有人都很清楚一百万内的罗马数字表示法的。对于不清楚的人来说,要搞清楚这个表示法的规则,就已经是一件有些复杂和耗时的事情了。显然,这个罗马数字表示法的知识点,不应该成为考察和衡量的标准。
当然,有时候有些问题的背景知识是冷门的,但是表述简洁,那么这样的话只要 面试官能够主动、迅速地说清楚,拿来使用是没问题的。
知识性问题
我想特别谈一谈,对于知识性问题的提问。在讨论问题的过程中,如果涉及到关联的具体知识,那么提问一些基础知识是一个不错的选择,这是考察候选人 CS 基础是否牢靠的方式。比如候选人提到使用 HashMap,而他/她最熟悉的编程语言是 C++,那么询问在 C++中 HashMap 是怎样实现的就是一个可行的问题。而且,由于这样的问题是嵌套在前述的这个大流量控制问题的解决过程中的,更显得自然而不突兀。
反之,如果这样的知识性问题较为冷门或浅表,和当前的讨论中的问题无关,或是不在候选人的知识体系内,这样的问题就值得商榷了。比如,仅仅因为自己的项目组使用 Spring,面试官就询问候选人 Spring 的 bean 的单例和多例分别是怎样配置的,而恰巧候选人又是 C++背景,对于这部分并不熟悉,这样的问题除了给候选人造成困扰以外,意义就不太大了。
如果把知识性问题作为主要问题来提问,我认为是不适合的。因为一方面它不具备普适性,另一方面它又具备太强的随机性。
这里不具备普适性这一条,指的是不同技术背景的软件工程师候选人都要能够适合参与这个问题的解答。举例来说,如果问 “Tomcat 的线程池的配置策略?”,这就是一个不具备普适性的问题,这是一个偏重 “知识性” 的问题,如果候选人没有使用过 Tomcat,或是只是略有了解,很可能就栽了。
而具备太强的随机性这一条,指的是一旦待考察的问题具体以后,这个问题就容易从对 “能力” 的考察变成了对 “知识” 的考察,而这个知识,又恰恰是比较容易随机的。也就是说,我们不希望候选人 “恰巧” 知道或不知道某一个细小的知识点,来决定他/她是否通过这项考察。
无论如何,知识性的问题作为考察的辅助方式可以,但不应成为主角。我还是觉得我们应当能把更多的时间,留给主要问题的解决过程本身。
关于 “技术面试中,什么样的问题才是好问题?”,我就说这么多吧。也欢迎你分享你的看法,我们一起讨论。
文章未经特殊标明皆为本人原创,未经许可不得用于任何商业用途,转载请保持完整性并注明来源链接 《四火的唠叨》
国内的面试官真的参差不齐,比如说他只懂考察某个语言特性,某个候选人这方面可能没那么熟悉,然后就直接拒了,但其他方面都没有考察,大部分面试官没有构建模糊问题的能力,只能通过知识性的问题去考察。
可能和教育体系有关,直接给标准答案,而不注重思考,反思,学习过程。
总结的不错
说点自己的想法,首先面试官要有自己的定位,面试其实也是一种学习交流的过程,因为程序的答案不会只有一种,思路会有很多,不要把自己的答案当做最优解
我觉得这种面试对面试官的要求也挺高的,感觉自己目前还不够火候
讲的很系统了,`主要问题`这个点在我理解中就是构建专业性对话基础的过程,如果构建不出这样一个基础(第一个思路以及第一版可运行的实现),那么这次面试几乎可以说是一次无用面试了
总结得比较全面