来自因子团队的TDD

2021-06-18 19:39:35

我们显然有很多谈论,我们最近谈到了游戏更改,或者计划做,但我们不想分享任何一个。

然而,目前对我们来说非常相关的话题,我们可以没有透露任何具体修改游戏分享。今天' S Post将是非常技术性的,与编程相关,所以如果您刚刚来到游戏新闻,您可以安全地跳过这一点。

现在,只有开发人员在这里,我可以分享我对鲍勃叔叔的新发现以及他对一些与编程项目管理相关的一些基本原则的非常好的解释,以及更多。如果你手上有8.5个免费的时间,我就会提出你看它,因为会有一些引用他在以后的提到。

我的一般思想是,我们保持代码的质量非常高,我们有一个合理的工作方法。但我们实际上是许多地方的选择性盲目的受害者。有趣的是,一些碎片代码在始于开始,在整个年份都非常好,即使它扩大了很多......而一些代码才会变得巨大恶化。

什么是蜡基金会,与您可能会问的编程有关如何?我的祖父是一个非常热情的蜜蜂守护者。我的童年在我们的花园里度过,在那里你必须小心你在哪里,你坐下的地方,你永远不会留下任何甜蜜的东西,因为你会很快找到一大块蜜蜂。我不得不帮助他并不时了解蜜蜂,我真诚地讨厌,因为我知道我永远不会拥有自己的蜜蜂。但他对了一件事,你学到的一切都会以某种方式对你有用。

一个你身边的蜜蜂,作业的是,蜂蜜是由蜜蜂带走的时候,你把蜂巢蜡的基础上,它看起来像这样:

它的主要功能是蜜蜂均匀地建立瓷砖,并且遵循已经存在的优化结构是自然的。这正是从一开始就具有良好和可扩展设计的代码发生的事情。

另一方面,有一个代码有一个懒惰的原创设计,或者从未预料到复杂性中的复杂性,并且每个变化都只是乱七八糟的一个少量补充。最终我们已经习惯了这一部分代码的想法只是地狱,而且制作小的变化很烦人。这意味着我们只是' t就像代码的这一部分,我们希望尽可能少的时间与它一起工作。结果是,问题正在缓慢循环失控。

当我拿走叔叔鲍勃眼镜并开始环顾四周时,我很快就确定了这样的几个有问题的地方。这不是一件巧合,这些地方正在消除一个不成比例的大量开发时间,不仅是因为改变很难,而且因为它们充满了回归错误,并且通常是永无止境的问题来源。

这是拥有一家公司的美丽事物,该公司在股票市场上and#39; t。想象一下,你有一家公司每季度慢慢慢,然后你对股东面对陈述,即解决它的方式,是为四分之一或两个,重构代码做出绝对没有新功能,学习新方法等等。怀疑股东将允许这一点。幸运的是,我们没有任何股东,我们了解这项投资的重要意义。不仅在项目中,而且还在我们的技能和知识中,所以我们下次做得更好。

如果有相同数量的人从开始完成工作,那么它看起来很合理,但它不是。这只是我的开始,现在有9个程序员。它可以通过游戏来解释越来越多的互联机制,这更难以维持。或者它也可能意味着代码的密度提高了如此大。这两个都没有足够的解释为什么有更多程序员并导致更快的开发。

这表明鲍勃叔叔描述了与我们相关的问题,解决方案实际上是改善我们发展的方式,而不是仅缩放人数。一旦我们有一个漂亮的干净基础,那么招聘新程序员并让他们加快代码即可更快。

现在让我解释我们所拥有的问题的一些典型例子,以及我们如何修复它们:

我们对GUI进行了很多关于GUI(例如FFF-216)以及我们如何从用户和程序员的角度来看,我们如何迭代地提出了我们发现的栏。来自FFF和编码的共同外带是,我们总是低估了复杂的GUI逻辑/样式/布局等。这意味着改善GUI写的方式具有大的潜在收益。

我们对GUI对象的结构和布局自0.17更新以来的方式感到满意。但是,码头,它仍然感觉比它应该更加愉快。主要问题是您需要触摸以添加一些交互元素所需的地方。让我向您展示一个例子,一个简单的按钮,用于在地图生成窗口中重置预设。

然后,我们需要覆盖MapGeneratorClass中ActionListener的方法,因此我们可以侦听单击操作。

最后,我们可以实现这种方法,我们如果我们考虑的元素/否则我们会进行实际逻辑 void mapgeneratorgui :: onmouseclick(const agui :: mousevent&活动) { if(event.getsourceWidget()==& this-> resetPreesetButton) this-和gt; onresetsettings(); 否则if(event.getSourceWidget()==& this-> randomizeseedbutton) this-> randomizeeed(); ......

这是一个带有一个简单动作的一个按钮的样板太多。我们在代码中注册了ActionListeners的500多个地方,所以想象膨胀的数量。

我们有点注意到,当我们使用lambdas来信号回调和类似的东西时,它往往更令人愉快。那么如果我们做到了这样的主要方式,那么做什么?

我们决定完全重写它的工作方式,所以而不是从事件捕获功能添加侦听器和过滤,我们可以确定:

这已经是一个很大的改进,因为添加和维护新的逻辑只需要您查看一个地方而不是几个地方,并且它通常更可读,更容易易于错误。

由于我们不需要保持指针对象进行比较,我们可以完全从课堂上删除它的定义,并在此时尚的许多地方匿名使它成为:

重写所有GUI内部人员(再次)是一项重要任务,但最终它真的很值得,现在我可以和#39; T想象我们如何站得能够努力做旧的方式。它也导致了数千行代码被删除。

当您尝试制作代码清洁剂时,有几个主要目标可以追求。删除代码复制是第一个也是最大的优先级。当代码isn' t结构良好时,函数太长,函数太长,或者名字是奇怪的,但如果你有5个版本的同一堆代码,那么这里有略有变化,它是最糟糕的兽。这只是一个时间的问题,直到错误修正/更改仅适用于某些变体,并且它变得越来越不那么明显,是否有变体之间的差异是意图或间接的。

手动构建逻辑是一个怪物,因为它已经支持的所有东西:

然后,所有这些逻辑都需要乘以2(当您懒惰和复制粘贴时),您可以拥有正常的建筑和幽灵大楼。

然后,您将整个代码乘以2倍再次憎恶。为什么?因为我们还需要在延迟隐藏模式下执行所有这些逻辑。听起来很糟糕,但它是' t所有的,因为这个逻辑在整个历史上不断地修补并触及了不同的人,代码的核心是一种疯狂的长方法,代码看起来像叔叔提到的地平线鲍勃。

现在想象一下,你需要改变关于这个代码的事情,特别是当你考虑时,代码自然有许多角落的错误,或仅在代码的某些变体中固定。这是懒惰的长期设计如何导致生产力差的一个很好的例子。

长话短说,这是一个像我的业余爱好者的侧面项目才会完成,但最终,所有重复都是合并的,代码结构很好并进行了完全测试。管理代码需要与以前的状态相比的一小部分,因为读者不需要读取一堆代码只是为了获得大图片并能够改变任何东西。

这让我想起了类似种类的重构:"一旦我们完成了这一点,它就实际上是一种乐于将东西添加到这个代码。" isn'它美丽吗?它不仅更有效和更少的越野车,与之工作也是更有乐趣的,并且在不管其他方面,往往更快地努力工作。

不,我们显然没有自动测试到达这一点,我们已经提到了几次(FFF-29,FFF-288等)。我们试图连续提高测试所涵盖的代码区域,这导致我们涵盖另一个地区GUI。这与重复低估了GUI需求的低估了低估了。它根本没有测试是这种低估的一部分,它发生了多少次,我们发布了一个释放,它只是因为我们没有测试了一个测试,它只是因为我们没有测试了愚蠢的简单而崩溃了。按钮。最后,它证明并不难以自动化GUI测试。 我们只需使用GUI创建测试环境(即使在没有图形的测试时,即使测试)。我们声明了一些帮助方法,允许一个非常简单的定义,我们希望光标移动,或者我们想要点击的内容:

然后,点击方法调用输入的低级别事件,因此测试所有事件处理和GUI逻辑的所有层。这是端到端测试的一个例子,这是一个有争议的主题,因为一些"学校"考试方法说,所有应该单独测试一切,所以在这种情况下,我们应该理论上只能测试,首先单击按钮,从而创建要处理的输入,然后,对输入工作的独立测试工作。在某些情况下,我喜欢这种方法,但我真的很喜欢,我可以只用几行代码渗透所有的逻辑层。 (在测试依赖性部分中更多)

我必须承认,我没有知道TDD真的是什么直到最近。我认为这是一些废话,因为它对某些功能的所有测试(没有尝试它或甚至编译它)来说,它听起来非常不切实无于并且不切实际,然后尝试实现满足它的东西。

但这不是TDD,它必须在A&#34中向我展示;对于假人"让我意识到我有多错了。

TDD实际上是延长测试之间的恒定快速旋转,并使它们持续通过。因此,在编写测试时,您将编写代码以同时基本满足它们。这允许您立即测试您所写的内容,主要使用测试作为代码应确认的指令,这指出了思想过程,使您考虑您所在位置,并编写更具结构化和可测试的代码从一开始就。

所以之后" aha"实现TDD的时刻真的,我开始是即时粉丝。我现在正在努力尽量努力,尽可能地遵循TDD方法,并强迫它在团队中的其他人。它感觉速度慢,即使是恰到好处的简单逻辑也是正确的,但是测试已经证明了我已经多次证明了我,并阻止了在不久的将来讨厌低级调试会话。

如果测试应该真正独立,测试C,应该有一些A和B的模型,因此C的测试与系统A + B独立工作。共识似乎是,这导致更独立的设计等。

这可能适用于很多情况,但我相信随时随地试图实现这种方法是接近不可能的,它会导致很多杂乱和我' m不是唯一一个有问题的杂乱。

例如,让我们说我们对地图上正确连接的电极测试。但是当我不知道地图上搜索地图上的实体正常工作时,我很难测试它。

我的结论是,只要依赖于依赖性也在测试中,就具有这样的依赖性,但是当你打破某些东西并且很多测试开始失败时,问题就会出现问题。当你做小的改变时,是/否的测试是足够的,但它始终是一个选择,特别是当你重构一些内部结构时,在这种情况下,你有点期待打破很多东西,而你一旦它再次编译,需要有一种方法来逐步修复它们。

如果测试没有任何特殊的结构,那么局势在100测试时都是非常不幸的,你剩下的就是尝试半随机选择一些测试,然后开始调试它。但是,当一些复杂的测试案件在中间失败时,它真的误导了,你花了很长时间调试它,只是意识到它是导致失败的一些非常简单的低级错误。

目标很容易,我希望得到我改变的最简单的失败情况。

为此,我实现了一个简单的测试依赖系统。测试以某种方式执行并列出,即当您要调试并检查测试时,您就知道其所有依赖项已经正常工作。我试图搜索其他人也使用依赖关系,以及他们如何做到,而且我惊奇地没有找到任何东西。

在重构鬼/真杆连接逻辑的重复时,我建立并使用了这种结构,它肯定会加速使它正常工作的过程,我自信地确信这是对我们的结构测试的方式可预见的未来。不仅它使测试结果更有用,但它迫使我们将测试套件分成更小的专业单位,这也很有帮助。

当Boskid加入团队作为QA Guy时,他的主要角色之一是确保在实际修复之前首先通过测试首次被发现的错误,并且通常改善我们的代码测试覆盖。这使得释放更有信心,我们的回归错误较少,直接转化为长期效率。我强烈认为,这清楚地表明并支持鲍勃叔叔所说的话。使用测试感觉较慢,但实际上是更快的。

测试覆盖范围是当应用程序运行时(通常意味着在此上下文中运行测试)时执行代码的部分的指示符。我从未使用过一个工具以衡量测试覆盖,但由于它是鲍勃叔叔的一个谈话之一,我试图第一次使用它。我找到了一个仅适用于Windows的工具,但需要最少的设置量,它是OpenCPPCoverage,它提供了如此:

它立即可见,即在测试中都不会触发两个条件命令。它基本上意味着代码只是测试,所以它应该被覆盖,或它' s死代码,所以它应该被删除。我非常自信(再次),使用这可以帮助我们编写清洁高质量代码。

如果你被这一点移动,如果你读到这一点的情绪是,"我希望我的老板有这些优先事项"考虑在武器申请工作!