作为面试者或者面试官参与过不少技术面试,但发现国内的面试体验往往一言难尽,很容易出现招进来的人面试题都答对,但工作中却出现各种问题,最后浪费了大家的时间。而且对于面试官的培训并不重视,大部分厂基本没有。而面试官自己忙于平时工作,加上有时候流程出现问题,HR 事到临头才通知面试官有个面试需要去参与,根本没有太多时间来准备,只能网上找些题目丢给面试者。这样下来对双方的体验都不好,而且也达不到给团队招一个合格人选的目标。
这篇文章就聊聊我心目中什么样的技术面试才是好的技术面试,如果你对这些观点表示认同,我想我们可能合作起来会很愉快。
该招什么样的人
现代应用开发随着复杂度增加,早就不是单打独斗的年代了,而面试就是为了打造合适的团队,发挥更强的战斗力。对于多人协作的软件开发,不管前端后端移动端,还是一线开发,架构,管理等等不同的团队定位,实际上很多素质是通用的。但核心的一点是:这个人是你希望能够一同并肩工作的人吗?
这个问题可能看上去是一个废话,但往往随着面试的进行,这个问题容易被抛到了脑后。面试虽然看上去是让候选者去解决一个又一个问题,但重点应该是关注候选者如何去解决这些问题的而不是答案本身。
可以好好思考一下,当抛出一个问题时,相比于一个快速给出一个回答的候选者,你会不会更喜欢这样的人:
- 他可以清晰地指出其中的关键点或者坑
- 他可以将问题进行细化拆解,然后给出一个好的计划来逐步求解
- 他可以指出各个方案的优劣取舍和解释决定的动机
- 他可以针对问题反问其中不清晰的条件或边界
除了技术能力本身以外,沟通能力,为人友善(那些绝顶聪明但高傲自负的家伙究竟适不适合团队就需要自己掂量下了),清晰的思维能力,快速学习能力等都是非常重要的软能力,实际上如果候选者这些软能力非常棒的话我是愿意放低对技术能力的要求的。一个完美答案或者一个 bug free 的代码完全不是必须品,当着别人面写代码我觉得是一件非常难的事情,更别提还有着时间的压力下,人是非常容易犯错误的。这整个的过程才是更值得关注的。
举个例子
如果有在线的代码编辑器的话是最好的,我认为编写实际运行的代码,比编写伪代码要更好。通常我喜欢从一个简单的概念开始聊起,然后逐步的延申下去。
举个例子,如果经常写 react 应用的话,应该对 memoization 这个技巧非常熟悉,当然没听过也不要紧,但可以在我解释之后快速理解然后自己实现一个简单的方案就最好了。
比如一个纯函数,它的输出只依赖输入的参数,在内部有一些非常复杂的计算,我们需要实现一个带有缓存的版本,这样当重复的参数传入调用时,可以跳过重复的昂贵计算,直接将之前的缓存返回。典型的做法就是用一个 hash-table 来将输入和输出的映射关系保存起来,当发现 hash-table 内已经有该输入时,就可以直接把之前存储的输出返回,没有的话,就直接进行计算然后将结果保存后再返回。用 typescript 代码为例就是:
const map = new Map<number, number>();
const f = (n: number): number => {
if (map.has(n)) {
return map.get(n);
} else {
const result = expensiveCompute(n);
map.set(n, result);
return result;
}
}
接下来,可以针对这个方案继续深入和优化来进一步考察候选者。上面这个函数有一个无限大的缓存,而且这些缓存永远不会被释放,那么很明显,在生产环境中使用这个方案的话,肯定会有内存泄漏的问题,那么如何进一步优化呢?这个时候就需要根据实际情况,来制定一个缓存淘汰的策略。例如,可以使用一个 FIFO 的队列,来实现 O(1) 复杂度的淘汰策略。可以限制缓存的大小,直接像 memoize-one 一样,只有大小为一的缓存,即只会保存上一次计算的结果。也可以实现一个 LRU 缓存策略,淘汰那些最久没被使用的,或者像 LFU 那样,每次淘汰那些使用次数最少的。
如果选择了 LRU,那么也有多种方式,最容易的就是存一个时间戳,然后每次遍历 hash-table 中的数据找出最早的那个淘汰掉,这种达到了 O(n) 的复杂度,也可以使用一个最小堆来优化到 O(log n) 的复杂度,甚至更进一步,使用一个双向链表达到 O(1) 的复杂度。
哪怕候选者没有最后给出一个 bug free 的答案,但在这个过程中,可以整理好自己的思考过程,沟通清晰,代码设计简单易懂,都是非常加分的。