文章目录

  • 主调试循环
  • 验证在图层中进行
  • 优化循环时间
  • 为什么快速循环更好
  • 短循环时间是通用的吗?
  • 一些综合测试是必要的
  • 复杂性是否会导致测试验证循环?
  • 救援的暂存环境
  • 结论

仅通过测试进行验证基本上是在仪器上驾驶飞机,而不是能够向外看挡风玻璃。视觉飞行和肌肉记忆飞行与仪器相结合,既高效又安全。你不太可能误撞山。

当你已经编码了十多年时,可能很难重新捕捉初学者的思想,并向新手解释如何像程序员一样思考。我记得在大学里,当我编码的时间相对较短时,有一件事在我的脑海中结晶了编写代码背后的思维过程——你可以称之为程序员哲学。我正在帮助一个朋友完成计算机科学101任务。他们对编码完全陌生。

他们从头到尾在纸上写了一个完整的解决方案——也许是100行代码。然后,他们将其全部输入到文本编辑器中,并运行它。你认为发生了什么?他们得到了大约一千个语法错误。这时,他们来找我,感觉自己撞上了一堵砖墙。在同一堂课上,我一直坐在他们旁边——但至关重要的是,我已经编码了一段时间。我已经内化了编写代码的基本思维过程,而不必清楚地表达它。我们的老师未能传授这种思维过程。

主调试循环

然后,我必须向我的朋友解释的是我现在要称之为“主调试循环”的思维过程。我相信这是一种自然的思维方式,在所有程序员中都会发展起来——假设他们成功地学会了编码。它涉及将问题分解成非常小的部分。足够小,以便您一次编写1-3行代码。每次编写其中一个小块时,您都会运行该程序。通常它不起作用,然后您重试。慢慢地,你积累了你已经说服自己有效的代码。您可以迭代地构建整个解决方案。

关键是双重的:你正在逐步构建,并且你正在验证你前进。随着经验的积累,您将在越来越复杂的系统上工作。但这种心态可以扩展到任何复杂的问题——你只需要把它们分解得更多。

这是主调试循环。编写代码,运行代码。运行代码就是验证。

验证在图层中进行

究竟什么是验证?下面是我称之为应用程序内验证的示例。在Web开发中,您需要编写几行代码,保存文件并刷新浏览器。然后,您手动与页面/应用程序交互,以查看刚刚更改的内容是否有效。对于速度,您可能只是在测试快乐路径,或者您当前正在实现的一个边缘情况。对于任何其他类型的开发,过程是相似的,但具体细节看起来不同。

测试验证不会在应用程序中发生。相反,您可以针对刚刚编写的代码运行一小组综合断言。这是一种称为测试驱动开发的最佳实践。TDD 与应用程序内验证结合使用。在实践中(与严格的TDD相反),我的观察是开发人员在短循环中工作,验证应用程序内,然后快速快速遵循单元测试覆盖率。

另一层验证是自动化集成测试,使用Selenium等工具进行应用层验证,或使用Postman进行API层验证。您可能还需要进行详尽的手动测试,可能由专门的 QA 工程师进行。最后,您可以使用功能门控在生产中进行验证。所有这些验证层都与音乐会一起工作,以确保质量。以迭代方式编写代码时,通常使用应用程序内验证和/或测试验证,因为它们要快得多。

优化循环时间

主调试循环可以是每小时执行数百次的内容。思考和输入代码是自然的瓶颈——毕竟,你比电脑慢得多。在理想情况下,运行代码来验证您刚刚编写的内容是即时的。在我编码生涯的绝大部分时间里,运行一小块代码平均可能需要5秒。开销是由于文件系统中延迟注册文件已更新、运行时加载更改以及您自己的“人工时间”(与新更新的应用程序交互以运行更改并查看结果)造成的。

计算机的循环时间部分根据语言,框架和应用程序本身而变化。脚本语言在运行之前不必进行编译。某些类型的编码自然涉及或多或少的人际互动。例如,与刷新 Web 应用相比,运行控制台命令往往涉及更少的人为延迟。但是,由于主调试循环时间对开发人员工作流至关重要,因此语言和框架作者有很大的动机来优化它。开发人员同样受到激励,为它优化自己的应用程序。

为什么快速循环更好

在我看来,慢循环时间让人想起我职业生涯中一些最糟糕的调试噩梦,我称之为“盲目飞行”。最坏的情况有两个属性:它们不能可靠地再现,并且每次尝试复制都需要很长时间。对于调试,非再现性本身会导致不可接受的慢循环时间。如果您要部署到生产环境以查看它是否有效,或者您必须多次运行代码以查看它是否失败一次,则这是最坏情况的慢循环时间方案。

很容易看出这种极端情况如何导致开发缓慢和低质量输出。

相比之下,我对“在心流中”的一些最积极的记忆唤起了与计算机进行持续,流畅对话的感觉。就像来回对话一样,你们正在以协作方式得出结论。您正在验证代码的速度几乎与您可以想到并键入它的速度一样快。对我来说,即使能够运行和验证更改的10秒延迟也会破坏该流程。

尽管如此,断言较短的循环时间更好是基于一个无法证明的假设 – 为给定的工作范围运行更多的循环将导致每个时间段的更高输出和/或更高的质量。我相信这是真的,但我承认(无论个人多么违反直觉),总体吞吐量和质量可能会随着循环时间的慢而达到或更高。

短循环时间是通用的吗?

循环时间不能保证很短 – 事实上,技术熵将施加恒定的压力以增加循环时间。需要花费大量的开发人员时间来确保测试套件继续快速运行,应用程序快速重新加载代码,并且UX本身(在面向用户的应用程序上)为开发人员提供了快速重新加载和验证的能力。

鉴于全新的项目往往从其母语言和框架中继承了小型代码库上的短循环时间,因此在我迄今为止在小型初创公司的职业生涯中,我们往往循环时间较短也就不足为奇了。我们不得不花费精力来保持它们简短,但我们通常能够做到这一点。

但是,我已经看到,在较大的代码库中,对于较大的团队,较短的循环时间并不是给定的。也许随着复杂性的扩大,保持较短的循环时间成本太高?无论原因是什么,一旦循环时间达到中断点,开发人员自然会寻求更短的循环,例如切换到测试验证。在极端情况下,您可能根本不在应用程序中验证,并相信您的测试正在验证正确性。

对我来说,撤退到测试验证似乎非常危险 – 但开发人员会做他们需要做的事情来保持循环时间短,即使他们没有完全验证。

一些综合测试是必要的

如果在发布代码之前的某个时候,如果在完全集成的环境中运行代码,则运送错误的风险会大大提高。这可能看起来像在编写代码时验证应用程序内,详尽的手动测试或生产中的门控验证。仅在综合方案中进行验证根本无法提供与同时运行所有内容相同的信心。有多少次单独开发的组件在最终集成后无法正常工作?即使接口完全匹配,也会发生这种情况 – 组合行为通常仍然是错误的。

这个星球上没有一个QA人员会容忍将某些东西运送到生产环境,而不以用户体验它的方式运行它 – 集成在一起。

仅通过测试进行验证基本上是在仪器上驾驶飞机,而不是能够向外看挡风玻璃。视觉飞行和肌肉记忆飞行与仪器相结合,既高效又安全。你不太可能误撞山。

复杂性是否会导致测试验证循环?

当您拥有复杂的系统和大型代码库时,保持较短的循环时间是很困难的。热重新加载本身可能需要超过10秒的时间,以便加载和重新编译大型代码库。脚本语言在这方面有优势,但有自己的非正交成本。如果框架需要转译,甚至脚本语言也可能具有不可接受的延迟。

面向服务的体系结构为主调试循环带来了独特的优势和挑战。一方面,您大部分时间都在处理单独的,较小的代码库。热重载时间更短。另一方面,运行由服务和外部数据存储组成的应用程序既非常复杂,又需要大量的计算资源。不久之后,在本地运行它甚至是不可能的。

在实践中,我注意到大型代码库、服务体系结构和将测试验证作为主要调试循环的撤退之间存在相关性。

救援的暂存环境

过渡环境就像生产的微型版本。它应该设置所有相同的服务,以及相同的基本网络架构。它只是被大大缩小了。通常,它具有完全相同的数据存储和架构,但数据集完全不同。暂存与生产完全隔离;您无法与任何服务的生产版本通信,也无法与生产数据存储通信。

根据产品域的敏感度,您可以完整或清理地将生产数据同步到暂存。在许多领域中,从安全角度来看,这是不可能的,因此您需要创建虚假的测试数据,整个工程团队使用相同的测试数据集和数据存储。您开始拥有“最喜欢的”测试用户和记录,并且可以在应用程序中快速启动它们。

过渡环境有很多用途 – 但它们如何帮助开发人员保持在应用程序内验证流程中?智能业务路由可以帮助解决本地机器资源问题,并减轻维护本地数据集的负担。缺点是它要求开发人员具有与分期的有效互联网连接。

前提是将开发服务与暂存挂钩,并通过正常的暂存服务图路由各个应用程序内验证请求— 当前正在开发的一个或两个服务除外。暂存网络拓扑会将服务调用图的各个部分从云中的暂存环境返回到开发盒,可能通过 VPN。这听起来真的很复杂,但这种动态路由是服务感知路由网格框架(如linkerd)的标准功能。

结论

在主调试循环中从应用程序内验证切换到测试验证会导致质量降低、速度变慢以及上下文切换。

系统到测试验证需要做很多工作来抵消。维护较短的调试循环很快成为某人或某人团队的全职工作。但是,即使只有50名工程师,我所在的组织也选择支付这笔费用。有可能(尽管我还不相信)随着人员和代码库大小的扩展,成本呈指数级增长。在这种情况下,我希望大规模的公司几乎普遍生活在缓慢的调试周期和测试验证的首要地位。