View on GitHub

软件开发与教练

blog

[翻译] FizzBuzz 已经 FizzBuzz 岁了

翻译自 Tom Wright的博客

今年是FizzBu​​zz成为流行的开发人员面试工具15周年。作为它的忠实粉丝,我使用自己版本的FizzBuzz面试过100多名候选人。今天这篇blog里,我想花点时间来为FizzBuzz庆祝。并讨论FizzBu​​zz为何如此有用,以及我多次观察到的尝试解决这个问题时的常见模式,最后探讨一些调整让这个题目常用常新。

_ [图片由freeimages.co..uk gratuit 提供]_ .

生日快乐,FizzBu​​zz !好吧,迟来的生日快乐,我猜想2022年1月应该被视作是 FizzBu​​zz 用作编程题目满15年。

关于 FizzBuzz 的“诞生”,可以追溯到 Imran Ghory 2007 年的一篇 blog。 这一年 iPhone 刚刚公布,萨科齐当选法国总统,次贷引发“大衰退”。2007年《哈利·波特》系列的最后一部出版,LCD Soundsystem 乐队发行了专辑《Sound of Silver》,《辛普森一家》大电影上映。换句话说,15年是很久以前了!

Imran 的 blog 一经发布就触动了编程社区的神经。这篇文章在当年得到“病毒式”传播。Jeff Atwood(后来他成为了 Stack Overflow 的共同创始人)在同年晚些时候写了相关文章,汇集了许多与 Imran 中心论点一致的评论:太多开发工作的候选人难以写出代码解决简单的问题。实现 FizzBu​​zz 至少证明了候选人具备基础的能力。

这道题目的最初描述是, FizzBu​​zz 程序应输出从1到100的整数序列,但需要把3的倍数替换为“Fizz”,把5的倍数替换为“Buzz”,把3和5的倍数替换为“FizzBu​​zz”。 最后的结果应该类似于:1, 2, Fizz, 4, Buzz, Fizz, 7, 8, Fizz, Buzz, 11, Fizz, 13, 14, FizzBu​​zz…… 这道题目的有趣之处在于它很容易描述清楚,但需要注意一些细微差别才能正确实现。

15年过去了,FizzBu​​zz 已经广为人知。比如我面试的许多人以前都遇到过它(这不影响面试效果——见下文)。事实上,它已经到了几乎是陈词滥调的地步。早在2017年,也就是 FizzBu​​zz 刚刚 Buzz 岁时(其实是第二个Buzz岁,原文如此,大概作者算错了), Tom Scott 就制作了一个很棒的视频,展示了一个Javascript解决方案并谈到了题目中的一些陷阱。

不过Scott和我对于 FizzBu​​zz 是否仍然有用和有趣恐怕有不同意见。他认为 FizzBu​​zz 还被用作面试是由于“有些面试官想不出更好的办法”。而我的观点是 FizzBu​​zz 仍然非常适合用来评估开发人员的核心能力,并为探讨更复杂的想法提供了一个适应性强、领域无关的基础。

我对于面试中使用 FizzBu​​zz 的经验

自2015年以来, FizzBu​​zz 一直是我面试工具库中的关键工具。或者换句话说,我在 FizzBu​​zz 迄今为止的“后半生”中一直在使用它。在我任职的每家公司,当我招聘开发人员时我都使用它。来自英国、澳大利亚和印度的100多名候选人都曾使用 C#.NET 尝试用解决我的定制版本 FizzBu​​zz 。可以看到明显的模式和趋势,但让我着迷的是每一次尝试都是独一无二的,都有各自的特色。

我的 FizzBu​​zz 版本要求结对编程(“导游”风格),严格(甚至是刻意严格)地遵循TDD。面试官提供小的、原子的需求,候选人应该在开始实现之前先从单元测试开始每一个需求。在良好的结对编程风格中,我们鼓励候选人与面试官讨论他们的方案。同样,如果他们忘记了方法名称或某些语法,他们可以随便使用任何在线参考资料。

环境设置和一般的观察

候选人会拿到一个 .NET Core 的解决方案,其中包含三个项目:一个类库、一个 XUnit 项目和一个控制台应用程序。其中有一些样例模版代码为候选人展示三个项目如何交互。有一个示例单元测试调用示例方法,该方法也被控制台应用程序调用。我甚至提供了期望候选人实现的方法的接口。 真正的“开箱即用”体验

设置所有这些导轨的目的是……

因为归根结底,我想看的是他们编写一些代码,而不是看着他们挣扎和恐慌。

无论怎样,候选人在过程中的反应会告诉你很多关于他们的职业经验。比如,一下就能看出他们以前是否编写过单元测试。而且更重要的是,那有足够的模板,就算从未写过单元测试的人也能搞清楚怎么做,这意味着可以继续剩下的任务来发现候选人其它方面的能力。

我寻找的其它通用能力指标包括:

需要明确的是,我不会仅仅因为候选人表现出任何这些行为而淘汰他。毕竟,我见过许多才华横溢的开发人员和我的偏好不尽相同。而是说,这些观察有助于整体评估候选人。

需求的交付

一开始提供给候选人的功能要求非常少,随着练习的进行会越变越更大。这给了一些热身机会,对于经验不足的候选人很有帮助。为了迎合经验丰富的候选人,我们准备了两个进阶目标,面试官可以根据候选人的进展情况选择一个。通过这些方式,我发现我可以为所有候选人使用同一题目,在面试期间根据候选人展现的能力动态调整。

注意:如果你想看我给候选人的任务,我已经上传到 Github:github.com/tdwright/FizzBu​​zzInterviewTask

如果你想尝试完成任务,请继续关注了解即将到来的比赛的详细信息!

在我工作的最近两家公司中,每次 FizzBuzz 挑战结束时我会要求开发人员将他们的更改提交到 git 的新分支。这不仅让我能够评估他们的 git 舒适度,还意味着我保有了一个相当大的候选人尝试的语料库。这样我就能够比较它们,并找到模式和主题。那么在这篇文章的下一部分中,让我们看一下任务的组成部分,以及每个部分可以告诉面试官什么。

核心任务

我的 FizzBu​​zz 版本中,我总是从字符串替换的方面开始问题。前四个需求处理经典 FizzBu​​zz 中单个数字的四种可能结果。我们首先实现 IFizzBu​​zz 接口,向事先提供的类中添加一个简单的方法。这时,该方法只需要返回一个字符串,其中包含作为参数传入的整数。例如,当传入1时,返回“1”。对于大多数开发人员来说,这是个柔和开始,使他们能够熟悉解决方案结构、 XUnit 和 TDD 方法。然而对于一些开发人员来说,我们发现必须向他解释如何使用Int32.ToString(),这本身就对面试评估很有指导意义。对于其他一些开发人员,这一步常常显示出他们在使用接口方面经验不足。

接下来的两个需求是关于扩展相同的方法来处理传入3结果为 Fizz 和传入5结果为 Buzz 的情况。这一步通常很快,并且能建立信心。取决于候选人是否对 FizzBu​​zz 之前有过了解(以及他们遵循TDD的严格程度),我们常常在这里看到两种主要方案:比较天真的方案会在简单地在 if 语句中插入 return 语句,而那些知道(或猜到)接下来的需求变化的人通常会把结果加入到一个初始为空的字符串中。

第四个需求是候选人通常会遇到困难的地方。这也不是我的版本的特别设计—— Tom Scott 在他的视频中也提到了这一点,在古早的 C2 Wiki FizzBu​​zz 页面上也提到了这一点。这个需求是处理3和5的倍数(例如,15)的情况,输出应该是 FizzBu​​zz 。最简单的实现是增加额外的 if 语句和额外的 return ,但很多人仍然会失败,如果 if 语句的顺序没有被正确处理。比如在判断 FizzBu​​zz 之前就返回了 Fizz ,因为它也是3的倍数。即使候选人正确地实现了这一需求,这里也是讨论重构的好地方——我们可以减少一些 if 嵌套吗?我们可以减少逻辑检查的重复吗?如果候选人使用字符串拼接实现,我们可以讨论字符串的不变性、引用类型和值类型之间的区别以及StringBuilder等。

顺便说一句,正是在第四个需求很多人会理解到单元测试的作用。在试图解决15的情况时破坏了其它情况是很常见的。重新运行单元测试集可以提醒候选人这一点并提供有价值的反馈。

变体0 —— 循环到 100

我将其称为第零个变体,因为大多数 FizzBu​​zz 挑战都将其作为核心要求的一部分。尽管我在主要任务的需求5和6中包括了它,但对于非常初级和非常资深的开发人员我经常会跳过它们。我这样做的动机是,当我们完成需求1-4后,我已经能够衡量开发人员的水平。时间压力意味着我经常想为更有能力的候选人切换到更具挑战性的事情。相反地,对于那些在主要任务中挣扎的候选人,我们可能已经没有时间了。

当我使用这部分需求考核时,我会关注一些事情。首先,我会仔细观察“差一”错误。这通常是由错误的有界循环引起的,并不是什么大问题,但是看看开发人员是否能发现原因总是很有趣的。我要注意的另一件事(并将询问候选人)是他们是否会使用比简单循环更有逼格的东西——可能是 Enumerable.Range 提供的 LINQ 语句,或者可能使用 yield return 。循环显然足以完成工作,但这可能是候选人露一手的好机会。

_ 1到100 [图片由Flickr 的Leo Reynolds 提供]_

变体 1 – FlexiFizzBu​​zz

对于轻松完成第一组需求的候选人,我喜欢用这个变体来挑战他们。在这个变体中需求没有以主需求相同的细粒度详细列出,而是指定需要实现的新接口,笼统地描述所需的行为,并提供一些示例。简单来说,我们希望候选人实现的是泛化版 FizzBu​​zz ,使得任何一组 int/string 都可以作为替换规则。比如,也许我们不想用 Fizz 替换3的倍数,用 Buzz 替换5的倍数,而是想用单词“Even”替换2的倍数。

接口指定了数据结构 —— 一个整型-字符串的 Dictionary 。我们并未指定是通过构造函数、设置属性或调用某些方法来操作字典来填充它。这些设计决定对于探索候选人如何看待API设计,以及API调用者对 FizzBu​​zz 类生命周期的期待非常有用。

这个变体真正有趣的部分是候选人调整他们以前的代码来处理更一般情况的方式。首先,观察他们是否试图保持与他们已经编写的单元测试(可能还有控制台应用程序)的向后兼容性。具体来说,大约一半的候选人最初并没有打算保留 3:Fizz; 5:Buzz 作为默认设置,这显然会导致它们之前的所有单元测试都失败。

通常,开发人员的解决方案会对字典进行迭代,检查key是否是输入数字的一个因子,并将value值添加到一个作为收集器的字符串。

我最近看到的一个有趣的例外是完全忽略字典,而是可以将多个 Func 添加到类中,每个 Func 完全封装返回什么字符串的逻辑(如果有返回值)。这意味着经典的 FizzBu​​zz 可以实现为一个或两个 Func ,取决于调用开发人员的喜好。这也意味着替换逻辑可以比简单地检查倍数更加多样。例如,你可以提供一个 Func 根据数字是素数,或是数位的个数,或是数字是否是回文来返回某个单词——没有什么限制你的想象力。这种方法可能会有些争议(它还算是FizzBu​​zz吗?),但这提供了与候选人讨论的有趣线索。

变体 2 – CloudFizzBu​​zz

这个不是我的功劳,我是从别处学来这个变体的…… 在2018年,当我搬到澳大利亚申请工作时,HeadUp Labs 用这个 FizzBu​​zz 的变体作为技术挑战。让我倍感愉悦。向优秀的James Devlin致敬!

他们的变形是将 FizzBu​​zz 实现为一对 Azure Functions(一个用于生成序列,另一个用于处理替换),两者以消息队列连接。我仍记得我在 FizzBu​​zz(这个我用在自己面试别人的题目)和云技术的融合中获得了很多乐趣。

在这个挑战中探索有趣的事情可能包括:

换句话说,实际的实现(只要它能工作)可能不如它可能引发的讨论那么有趣。我在这里寻找的不仅是候选人的知识范围,还包括他们在我们反复思考时的协作和开放程度。

这种变体比上一个要复杂得多,以至于我不会要求候选人将其作为现场编码的一部分。相反,这个变体可以作为一个带回家的后续任务,特别是对于更高级或面向云的角色。我个人尽量不把“家庭作业”作为面试流程的一部分,但我知道很多地方都这样做。

在这种情况下, FizzBu​​zz 仍是个美妙的题目,由于它易于理解,无需与任何领域或任何特定的文化知识相关联。

如果有候选人看到这个……

如果您计划与我进行面试并阅读了这篇文章,请不要羞于告诉我。您已阅读本文这一事实表明您已融入开发者社区,并且即使在我们开始编码之前,这也是一个强烈的积极信号。

以现场编程来交付这项任务的一大好处是它不会被以前的知识所破坏。这道题目并不依赖于候选人对潜在的陷阱一无所知——关键点不是恍然大悟地喊出“我明白了!” 即使有人因为记住了这篇文章而顺滑无比地完成了编程,我仍然可以通过后续问题来评估他们的能力。例如,询问为什么某事以某种方式完成,或者询问候选人是否曾在其它情况下使用过某种技术。

期待 FizzBu​​zz 的下一个 FizzBu​​zz 年!

正如您可能已经猜到的,我是 FizzBu​​zz 的忠实粉丝。我认为它对于面试任务具有完美的复杂性——足够丰富,可以深入了解候选人的技能,同时又足够简单,可以在合理的时间内从零到有意义的结果。像所有好的面试题目一样, FizzBu​​zz 可以很容易地解释清楚,即使是对以前从未听说过的人也是如此。它不依赖于任何文化或领域知识。

我希望我已经证明 FizzBu​​zz 可以轻松扩展以适应各种能力的考察。我上面描述的灵活适应意味着你不需要根据对候选人能力的预判来选择题目,而是可以根据他们的表现动态地调整任务。

我发现这使 FizzBu​​zz 有别于技术面试中其它的常见题目。例如,反转字符串或生成斐波那契数列都非常容易解释,但扩展范围有限。毕竟从一个成功的反转字符串继续下去扩展什么呢?另一极端,有些公司要求候选人在真实的代码库上做一些工作。这不是我喜欢的东西(尽管我承认它可能会在代码理解能力方面提供信息)。除了关于无偿占有劳动成果的棘手问题外,这类任务的进入门槛很高,通常不能保证它会揭示任何有趣的事情。 FizzBu​​zz 的灵活性(如前所述)允许它覆盖所有基础技能,这是非常有价值的。

另一方面是对题目的熟悉。我已经看了很多次解题的尝试,我知道会发生什么以及应该注意什么。这可以使面试过程比其他方式更有效率。另一方面,我们必须承认,候选人对问题的熟悉程度可能增加他们感觉自己“圈内人”的身份。为了提高职业的多样性,我们需要排除干扰,在这种情况下意味着关注每个候选人的能力,无论他们以前是否遇到过这道题目。

总之,我绝对可以看到自己在可预见的未来继续在面试中使用 FizzBu​​zz 。不是如有人暗示的因为我懒得想更好的题目,而是在考虑了替代方案之后,我得出的结论是 FizzBu​​zz 仍然是优选。它不是我唯一的题目,但它是默认准备,适用于大多数情况。

那么,期待 FizzBu​​zz 的下一个 FizzBu​​zz 年!

既然已经读到这里,你可能对我正在计划的FizzBu​​zz比赛感兴趣。订阅我的博客,以确保不会错过……