有效评估JS开发者软实力的十条面试题
为了顺利的再进职场,最近一个月来都在做有目的训练,训练自己的实操能力(因为这是我的一个弱项——前端项目经验薄弱,加上在特长上,编码和分析更倾向后者),而不是任意的自由的学习。然而,在具体的学习主题上,除了参考和对比常规面试题,找出一些基础主题外,对什么是“最有价值”的学习主题,我没有指引。
其实我一真很相信自己的直觉,但是难免有盲区,和价值冲突,我不清楚明天面对面的考官他希望我具备什么能力。我的担心不是没有原因的,因为软件开发技术岗位向来都是既难招亦难找,企业不知道怎么考核应聘者实力,求职者不知道学什么最重要。
这里边有一个推理,在面试和通过面试的情景里,假设把企业,和求职者分两类:
- 企业 分为懂得评估技术岗位(C1),和缺乏评估技术的企业(C2)
- 求职者分为有实力但不懂求职技巧的(P1),实力很弱但是刷题高手(P2)
那么会出现四种面试情况:C1P1 C1P2 C2P1 C2P2
如果假设成立,那么通过面试的只有 C1P1 和C2P2,但是真正算成功面试只有C1P1,因为只有这种结合才是良性的。
要创造一次良性的合作结合,关键点其实也很明显了,就是 企业掌握了评估技术岗位候选者能力的技术,包括 考核的目标(T),和考核的方法;同时,求职者通过掌握同样的学习目标(T)和学习方法,更有效提高自己的专业水平。
这里 关于 考核的目标 T的认识 是重中之重,它是对招聘者和求职者都极具意义的一点。最近在研习 函数式编程过程中,从Eric Elliott(《Programming JavaScript Applications》一书的作者)的这篇文章《10 Interview Questions Every JavaScript Developer Should Know》中,找到了一些相关有价值的观点,尝试转译出来。
10 Interview Questions Every JavaScript Developer Should Know
我写过一篇文章叫《 Why Hiring is So Hard in Tech》, 其中给出评估 技术岗位候选者 的一些 常规原则,以什么是应该和不应该 的形式罗列出来,其中有一条:
The best way to evaluate a candidate is a pair programming exercise. 评估候选者最有效的方法是「和候选者结伴的完成编程练习」。
意思是说,与候选人坐一起,让候选人敲键盘,你在旁边多看多听,少说。例如演示例如从Twitter API中提取tweet数据项,并在时间轴上显示出来。
虽然结伴练习很有价值,但是不存在一个单独的练习能决断一切,面对面交谈(的面试)也是一个非常有用的工具注1。不过, 不要浪费时间询问语法或语言怪癖。你需要看到大局,询问有关架构设计(architecture)和编程范型(paradigms) 等对整个项目有重大影响的经验知识。
语法细节和API功能特性的知识 是很容易搜索到的,但对于像 软件工程的智慧或 JavaScript开发人员 从经验中获得的 范型特性和习惯用法 这些经验知识,是很难短时间通过搜索学到的。
题外一:这里提到了重点,作为招聘方,测试 候选人 的那些 不能在半小时查资料能习得的技能,求职者同样要明白这个道理。 题外二:当然,作者提到的工程智慧,和编程经验具体指什么,有待研发
鉴于以上结论,对于Web开发和Javascript方面,我认为以下十个问题用在面试中,能比较有效评估候选人开发实力:
(译者注:我不完成同意作者的这十条,并且翻译上有所增删,观点和内容都有,根据个人的看法,实则是我自己将这些问题回答了一遍)
第一,你知道哪两种 编程范型对 JavaScript开发者来很有用?
JavaScript 是一种多范型( multi-paradigm )编程语言,支持过程式编程,面向对象编程,和函数式编程,三种编程范型。JavaScript通过 原型继承( prototypal inheritance) 支持面向对象编程,和 函数作值(所谓一等公民)支持函数式编程。
第二,什么是函数式编程?
函数式编程是使用 纯函数(或数学函数)构造 程序的一种编程范型,纯函数的优点是没有副作用(避免使用共享数据 shared state),和不使用可变数据(mutable data)注2;
Lisp(1958年)是最早支持函数式编程的语言之一,并且受到了lambda演算的极大启发。Lisp和很多Lisp家族语言至今仍在流行。
JavaScript 支持函数式编程,并且越来越流行,例如JavaScript社区流行的闭包,高阶函数,函数作参数传递都是 重要表现。
第三,传统类继承和原型继承的区别在哪里?
传统类继承是说,类(class)是「一个功能」的模板或设计样板(blueprint),它可以用来
-
派生子类(子类继承父类所有功能,并可以有所扩展),
-
和创建多个对象实例(使用new操作);
通过类继承的设计可实现程序的一种精致的分类层次结构(hierarchical class taxonomies)。
但是,由于子类和父类继承关系是一种白箱复用(父类不是完全封装,对子类可见),最终的类层次结构会高度耦合,这是 类继承 最大的问题。
与类继承不同,原型继承没有类概念(类是一个抽象的功能的“模子”),一切都是对象实例。「功能代码继承复用」通过 直接连接两个对象实例 实现,例如通过一个特殊的对象工厂函数( factory functions)生成新复用的对象,或复制(Object.create())。一个「目标新对象实例」注3可以将需要的功能小对象直接连入其中来实现复用功能,这是一种非常灵活的代码复用方法。
在 JavaScript中,原型继承有以下几种应用表现:
- 原型链(concatenative inheritance),
- 原型委托(prototype delegation),
- 功能继承/闭包(functional inheritance),
- 对象合成(object composition);
第四,函数式编程(FP)和面向对象编程(OO)各自优点和不足是什么?
FP和OOP作为完成编程——计算功能构造——这个「任务」的「工具」,有各自的适用和优点与不足。
OOP的优点
直观,对象由数据和方法组成的概念很容易理解,也容易解释方法调用的意义。OOP倾向于使用命令式风格,而不是声明式风格,命令式风格读起来像是一组供计算机遵循的直接指令,很形象。
OOP的缺点
OOP通常依赖于共享状态。对象和行为通常被绑定在同一个实体上,可以被任意数量的顺序不确定的函数随机访问,这可能会导致不希望的行为,比如竞态事件(race conditions)。
FP的优点
使用 纯函数作为 计算功能单元,程序员可以避免任何共享状态或副作用 注4,从而消除多个功能竞争相同资源所导致的bug。与OOP相比,FP的大功能的复合方式,例如所谓的无参数风格(point-free ),大大简化复杂功能的组合方式,和改善代码可重用方式。
另外,FP 倾向于 声明式和符号指代(denotational)的功能命名风格注5,FP不倾向通过详细说明功能操作的步骤,而是关注「功能要做什么」。这为重构和性能优化留下了巨大的空间,它甚至允许你用更高效的算法替换整个旧算法,而代码更改很少(例如,memoize,或者用惰性求值来代替eager 求值)。
使用纯函数实现的计算功能也很容易移植到多处理器,或分布式计算集群环境上,而不用担心线程资源冲突、竞态事件(race conditions)等。
FP的缺点
过度使用FP风格的代码(例如大量使用无参数式风格分割和组合 大功能 )可能会降低代码可读性,因为生成的代码通常很抽象,它简洁且不够具体。
与函数式编程相比,习惯OOP和命令式编程的人会更多,更深厚,因此,即使是函数式编程中的常见习惯用法也会让新团队成员感到困惑。
另外,FP的学习曲线要比OOP陡峭得多,因为OOP的广泛流行使得OOP的语言和学习材料变得更加对话化,而FP的语言则更加学术化和形式化。
总的来说,OOP使用共享状态「实现复合功能」是有害的,虽然它很直观;高度使用OOP的codebase比较“顽固”和脆弱,难改又错误百出;FP除了没有OOP的这些不足外,程序比较易读易维护,只是适应FP风格需要一些时间。
第五,在什么场景下最适合使用 类继承?
几乎没有适用的场景, 类继承能免则免,除非只有一层的继承;
第六,在什么场景下最适合使用 原型继承?
在JS中,当需要复用代码时都几乎可以使用原型继承,当然包括不适用函数式复用(FP也提供了复用机制)的时候。JS中有三类的原型继承:
- 第一,委托(Delegation),例如使用原型链;
- 第二,接合(Concatenative),例如mixins, `Object.assign()`;
- 第三,创建新(Functional),例如闭包;
每一类 原型继承都有各自适用场景,不过,它们都归结为 构成(composition)复用,是一种 has-a or uses-a or can-do 的关系,与类继承的 is-a关系相反。
第七,“对象构成优于类继承”是什么意思?
第八,什么是双向绑定和单向数据流,它们有什么不同?
第九,单体架构和微构架的优点和不足是什么?
第十,什么是异步编程,为什么说它对Web开发很重要?
- 临场小练习能测试到能力(种类)是有限的,有很多深层经验或知识只能通过 别的手段探测 到,例如交谈,主题面试;而且没有很具体的答案(检测标准),例如怎么检测候选人 功能编程 的能力?↩
- 纯函数的优点有待实证,纯函数(功能)和类对象的区别有待分析↩
- 无论何种代码复用技术(类继承,或是原型组合,或是其它),目标任务都是生成新的对象实例,实现软件功能的开发。↩
- 使用和不使用共享状态都是技术,重点是那个「功能的实现」的任务;就是为什么一定要使用中间状态?「纯函数」和「类对象」是两种编程范式最大「工具」区别。↩
- 就是更倾向使用名词, 而不是动词表达「功能」 。两种工具思想区别在于,FP是关注功能的形式和逻辑关系,OOP关于功能实现的数据的处理↩