重构,代码复用 重复代码_如何重构代码以使其更具可测试性

重构,代码复用 重复代码

您如何编写难以测试的错误代码?

对于不可测试的代码 ,您应该避免编写很少的反模式。 这些是:

  • 具有很多条件行为的代码 ,这取决于另一个不可读的代码。
  • 根据代码相对于其他代码的执行顺序为您提供不同结果的代码。
  • 负责设置相同( 全局 )变量的不同代码
  • 该代码取决于一长串的独立评估和分配。

我曾经对尝试证明包含所有这些反模式的代码的正确性感到不满(或高兴,因为它教会了我糟糕的代码有多糟糕 )。 它是用C ++编写的,用于我不打算讨论的特定嵌入式系统应用程序。 但是我重新创建了代码在JavaScript中的作用的要点,如下所示:

错误,笨拙的代码

首先让我们了解一下这段代码正在尝试做什么。

valIncrementer函数采用名为val的参数并返回val+1 ,但有一些警告:

  • 如果val是0到10之间的数字,则该函数返回val+1
  • 如果val为0和10的范围之外,则该函数将返回0,如果val小于0或10,如果val大于10。
  • 如果设置了disabled标志,则该函数仅返回val而不更改它。

以上代码不仅难以阅读,而且难以测试。 为了证明代码的正确性,您必须运行一组测试以查看valIncrementer函数为不同的输入提供的内容。 至少,我们需要测试val是何时在有效范围(即0和10)之间的数字,以及边缘情况,当它小于0,小于等于0,等于10和小于等于10时大于10

res等于:

糟糕,笨拙的代码为我们提供了正确的答案,但是不可能对测试整个逻辑一个方面的代码的不同部分进行单元测试。 例如,当您希望增加val时,如何测试val是否增加? 好吧,在开始时将nextVal分配为val+1 ,但不能保证保持这种状态。 您必须逐步遍历firstStageSetter1firstStageSetter2secondStageSetter ,以确保nextVal不会被重新分配给其他对象。

更好,更可测试的代码

这就是我重写不良,笨拙的代码以提高代码的可维护性和可测试性的方式。

请注意,我的重写代码与不好的笨拙代码相比要短多少。 我的重构有两个指导原则:

  • 纯函数:纯函数时,它不会使用未作为计算参数提供的任何内容(无全局状态),也不会修改给定的任何内容。 纯函数总是只返回一个新副本(无突变)。 考虑纯函数的一种好方法是: 如果将此输入赋给一个函数,无论如何我总会得到相同的输出吗? 如果答案为否,那么它可能不是纯函数。
  • 解耦设计:开始编码之前,请考虑如何将要做的事情分解为一组简单的构建块,每个构建块都做一件非常简单的事情。 我更好的valIncrementer将其职责的各个方面委托给助手功能。 它分开了我应该做的代码在做它的代码,它可以是独立单元测试。

每个助手功能负责一个简单的任务,并且可能很短。 它只关心自己的论点,并且不受外部任何事情的影响。 valIncrementer的唯一责任是将所有这些功能与一个if语句绑定在一起。

为什么我要使incrementedVal成为一个单独的函数? 我不需要像这样的简单功能。 但是,假设这不只是增加一个数字那么简单。 假设此操作需要访问数据库,并且可能会有延迟和其他副作用。 我们想将不确定的代码与确定性的代码分开。

人们为什么要编写笨拙,无法测试的代码?

我看到两个原因:

  • 技术债务:大多数人不打算编写错误的代码。 随着时间的推移,随着更多功能的添加,它会发生。 更改后重构代码通常是不可行的,尤其是当您的代码是API并且代码重写可能会破坏用户代码时。
  • 不良文化:有时还有管理压力和计划/财务约束,以最大程度地减少对“工作”代码的其他部分的更改(除了许多其他常见的借口之外请不要解决未中断的论点)。 本文指出:
事实证明,更高的灵活性导致开发人员编写了其他人实际上难以理解的代码。 很难决定是因为不足够聪明地掌握逻辑,还是因为不必要的复杂性而感到羞愧。 另一方面,在某些情况下,人们会觉得“特殊”于理解和应用对他人来说很难的概念。 在开发人员之间保持如此聪明的差异确实不利于团队动力,而复杂性总是导致这种情况。

如果您已经处在这种情况下,那么当您继续忽略代码重构和代码重写时,您会认识到您正在增加技术负担,并且有一个观点认为,对其进行更改并确保其仍然有效超过了成本/时间不重构的好处。 有很多资源可以帮助您识别代码不可测试的症状以及有关如何重构的建议。 可能还有许多其他人也在为您遇到的同一问题而苦苦挣扎,因此有机会在开放源代码解决方案上与同一个生态系统中的其他开发人员进行创意和协作。

如果您只是开始一个项目,请遵循以下准则:

  1. 确保设计好代码以考虑将来的修订。 这意味着将代码的可维护性和可测试性作为设计目标,并在设计代码时期望您的代码将来必须进行大量更改才能添加更多功能。
  2. 注意诸如面向对象编程之类的设计模式。 虽然面向对象编程的力量设计师要经过广泛的规划阶段,使得基线设计,用更少的缺陷对象,就已经下了很大的火近来对其“高度结构化的物理环境中后-其实变化是过于昂贵,如果不是不可能”。 面向对象编程的问题由《 编码员在工作》中的 Joe Armstrong很好地总结了 彼得·西贝尔(Peter Siebel)表示:“面向对象语言的问题在于,它们具有随身携带的所有这种隐式环境。 您想要香蕉,但是得到的是一只大猩猩,里面盛着香蕉和整个丛林。”
  3. 尝试避免复杂的依赖项注入。 依赖注入是通过允许客户端在编译时配置其对服务的使用来将服务的客户端与服务的实现分离技术 。 实际上,复杂的依赖项注入意味着在可以为客户端完全配置服务之前,需要在构造函数中指定许多内容。 依赖项注入越复杂,行为与环境的耦合就越紧密,从而使对服务实现的独立测试变得更加困难。 如果您的依赖项注入变得太复杂,则始终可以使用自己的依赖项注入将大型,复杂的服务拆分为较小,较简单的服务。

复杂的依赖项注入的问题在于,它可能会导致“它对我的机器现象起作用”,如下面的这个可爱的卡通插图所示。

Derick Bailey的博客
谢谢阅读!

如果您觉得这个故事有趣,请随时鼓掌👏

对于那些有兴趣学习更多有关此主题的人,以下是一些后续阅读:

翻译自: https://hackernoon.com/how-to-refactor-unwieldy-untestable-code-4a73d75cb80a

重构,代码复用 重复代码

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值