红灯-绿灯-重构

news/2024/10/10 10:58:24/
  1. 代码在周期内的状态:处于红灯状态时,代码不管用,处于绿灯状态时,一切都想预期的那样工作,但并不一定是最佳的,到了重构阶段,我们知道测试很好的覆盖了各项功能,可以充满信息地修改他,让他变得更好

  2. 编写一个测试:

    1. 每次添加新功能时,都要首先编写一个测试,当前处于红灯状态,因为测试执行时以失败告终,即测试对代码的期望和代码实际的功能之间存在差距,更具体的说,没有代码满足最后一个测试的期望,因为我们还没有编写这样的代码,在这个阶段,可能所有的测试都通过了,但这表示存在问题
  3. 运行所有测试并确认最后一个未通过:

    1. 确认最后一个测试未通过后,就能断定他不会在没有引入新代码的情况下错误通过,如果这个测试通过了,就意味着要么相关功能早就存在,要么测试本身存在误报的问题,如果测试无论怎么实现都能通过,就意味着他毫无价值,应该删除。最后一个测试不仅必须未通过,还必须是预期原因导致的,在这个阶段,我们依然处于红灯状态,运行测试,但最后一个未通过
  4. 编写实现代码

    1. 这个阶段的目标是编写代码使最后一个测试通过。不要试图让代码完美无缺,也不要为编写花过多时间。即便编写的不好或者不是最后的,也没有关系,后面还有改进的机会。我们的真实意图是打造一个由测试构成的安全网,并确认这些测试都能通过。不要试图引入最后一个测试未描述的功能。要想引入新功能,必须回到第一步,先编写新测试。然而,仅当所有既有测试都通过后,我们才能这么做。在这个阶段,我们依然处于红灯状态。虽然已编写的代码可能让所有测试都通过,但这种假设还未得到证实。
  5. 运行所有测试

    1. 应运行所有测试,而不是只运行最后编写的那个测试,这至关重要。你刚编写的代码可能让最后一个测试得以通过,但同时破坏了其他功能。通过运行所有测试,不仅可确认最后一个测试的实现是正确的,还可确认它没有破坏整个应用程序的完整性。如果整个测试集执行速度缓慢,就昭示着测试编写得不好或者代码耦合度太高。耦合度太高将导致难以隔离外部依赖,进而增加执行测试所需的时间。在这个阶段,我们处于绿灯状态:所有测试都通过,且应用程序的行为符合预期
  6. 重构

    1. 前面所有步骤都是必不可少的,但这一步是可选的。虽然很少在每个周期结束后都进行重构,但迟早需要甚至必须这样做。并非每个测试的实现都需要重构;没有明确的规定说什么时候该重构、什么时候不用重构。一旦认为可以更佳或更优的方式重写代码,那就是重构的最佳时机。
    2. 什么样的代码需要重构呢?这个问题不好回答,因为重构的原因有很多:代码难以理解、代码位置不合理、代码重复、名称没有清晰阐述意图、方法太长、类的功能太多等——这个清单可不断列下去。不管原因是什么,最重要的规则是重构不能改变任何既有功能。
  7. 重复

    1. 所有步骤都完成后(其中重构是可选的),再重复它们。编写110行代码后就切换到下一步,因此整个周期的持续时间为几秒几分钟。如果更长,就说明测试的范围太大,应将其分成多个更小的测试。一定要快速前进,快速失败并更正,然后再重复。
  8. 案例:3*3的棋盘下棋

    1. 实现

      java">public class TicTacToeSpec { @Rule public ExpectedException exception = ExpectedException.none(); private TicTacToe ticTacToe; @Before public final void before() { ticTacToe = new TicTacToe(); } @Test public void whenXOutsideBoardThenRuntimeException() { exception.expect(RuntimeException.class); ticTacToe.play(5, 2); } 
      }
      
      1. expected属性,你可以用它来指定一个Throwble类型,如果方法调用中抛出了这个异常,这条测试用例就算通过了

      2. 这个测试中,我们指出调用方法ticTacToe.play(5,2)时,期望的结果是引发RuntimeException异常,这个测试只需创建方法play,并确保他在参数x小于1或者大于3时引发RuntimeException异常

      3. 应该测试三次,第一次运行时,他应该不通过,因为此时还没有方法play,添加这个方法后,测试也应该不通过,因为他没有引发异常RuntimeException,第三次运行时应该通过,因为他实现了与这个测试相关联的所有代码

    2. 测试

      1. 验证Y轴

      2. java">@Test public void whenYOutsideBoardThenRuntimeException() { exception.expect(RuntimeException.class); ticTacToe.play(2, 5); }
        
    3. 实现

      1. java">public void play(int x, int y) { if (x < 1 || x > 3) { throw new RuntimeException("X is outside board"); } else if (y < 1 || y > 3) { throw new RuntimeException("X is outside board"); } }
        
      2. 为让最后一个测试通过,添加一条“检查参数Y是否在棋盘内”的else子句。

    4. 测试

      1. 确定棋子在棋盘边界内后,还需确保它放在未被别的棋子占据的地方

      2. java">@Test public void whenOccupiedThenRuntimeException() { ticTacToe.play(2, 1); exception.expect(RuntimeException.class); ticTacToe.play(2, 1); }
        
    5. 实现

      1. 为实现最后一个测试,应将既有棋子的位置存储在一个数组中。每当玩家放置新棋子时,都应确认棋子放在未占用的位置,否则引发异常

      2. java">private Character[][] board = {{'\0', '\0', '\0'}, {'\0', '\0', \0'}, {'\0', '\0', '\0'}}; public void play(int x, int y) { if (x < 1 || x > 3) { throw new RuntimeException("X is outside board"); } else if (y < 1 || y > 3) { throw new RuntimeException("Y is outside board"); } if (board[x - 1][y - 1] != '\0') { throw new RuntimeException("Box is occupied"); } else { board[x - 1][y - 1] = 'X'; } }
        
    6. 重构

      1. 重构play方法

      2. java">public void play(int x, int y) { checkAxis(x); checkAxis(y); setBox(x, y); } private void checkAxis(int axis) { if (axis < 1 || axis > 3) { throw new RuntimeException("X is outside board"); } } private void setBox(int x, int y) { if (board[x - 1][y - 1] != '\0') { throw new RuntimeException("Box is occupied"); } else { board[x - 1][y - 1] = 'X'; } }
        
  9. 编写一个测试,发现他不能通过,编写实现代码,发现所有测试都通过,只要有机会就重构代码使其变得更好,并重复这个过程


http://www.ppmy.cn/news/1536973.html

相关文章

Mysql 索引底层数据结构和算法

目录 索引数据结构 Hash表 二叉树 红黑树 B树 B树 索引数据结构 索引&#xff08;index&#xff09;是帮助MySQL高效获取数据的一种有序数据结构。索引是存储到表空间中&#xff0c;当我们的 sql 中的where条件用到索引的时候&#xff0c;会在存储引擎层就过滤出数据来…

Redis:cpp.redis++通用接口

Redis&#xff1a;cpp.redis通用接口 redis对象通用接口set & getexistsdelflushallkeysttlexpiretype 本博客讲解redis的C客户端redis-plus-plus&#xff0c;这个版本的客户端&#xff0c;接口和redis原生命令几乎完全一致&#xff0c;博客内部不会详细讲解每个接口的具体…

如何用深度神经网络预测潜在消费者

1. 模型架构 本项目采用的是DeepFM模型&#xff0c;其结构结合了FM&#xff08;因子分解机&#xff09;与深度神经网络&#xff08;DNN&#xff09;&#xff0c;实现了低阶与高阶特征交互的有效建模。模型分为以下几层&#xff1a; 1.1 FM部分&#xff08;因子分解机层&#…

C#系统学习路线

分享一个C#程序员的成长学习路线规划&#xff0c;希望能够帮助到想从事C#开发的你。 我一直在想&#xff0c;初学者刚开始学习编程时应该学些什么&#xff1f;学习到什么程度才能找到工作&#xff1f;才能在项目中发现和解决Bug&#xff1f; 我不知道每位初学者在学习编程时是…

SpringBoot 整合 阿里云 OSS图片上传

一、OOS 简介 ‌阿里云OSS&#xff08;Object Storage Service&#xff09;是一种基于云存储的产品&#xff0c;适用于存储和管理各种类型的文件&#xff0c;包括图片、视频、文档等。‌ 阿里云OSS具有高可靠性、高可用性和低成本等优点&#xff0c;因此被广泛应用于各种场景&…

快速区分 GPT-3.5 与 GPT-4

问&#xff1a;鲁迅为什么暴打周树人&#xff1f; GPT3.5回答 各种稀奇古怪的理由 GPT4回答 正确区分鲁迅和周树人是同一个人 国内GPT入口 https://ai-to.cn/url/?ulihaimao

vue3实现登录获取token并自动刷新token进行JWT认证

在《django应用JWT(JSON Web Token)实战》介绍了如何通过django实现JWT&#xff0c;并以一个具体API接口实例的调用来说明JWT如何使用。本文介绍如何通过vue3的前端应用来使用JWT认证调用后端的API接口&#xff0c;实现一下的登录认证获取JWT进行接口认证。 一、账号密码登录获…

【ROS】机器人系统仿真-URDF集成Rviz基本流程

机器人系统仿真&#xff1a;是通过计算机对实体机器人系统进行模拟的技术&#xff0c;在 ROS 中&#xff0c;仿真实现涉及的内容主要有三:对机器人建模(URDF)、创建仿真环境(Gazebo)以及感知环境(Rviz)等系统性实现。 1.URDF URDF可以以一种 XML 的方式描述机器人的部分结构&…