📜  敏捷中的重构

📅  最后修改于: 2022-05-13 01:56:16.592000             🧑  作者: Mango

敏捷中的重构

重构是在不改变基本行为的情况下不断改进现有代码设计的实践。在敏捷中,团队在从 Sprint 到 Sprint 的增量基础上维护和增强他们的代码。如果在敏捷项目中不重构代码,会导致代码质量不佳,例如类或包之间不健康的依赖关系、不正确的类职责分配、每个方法或类的职责过多、重复代码以及各种其他类型的混乱和混乱。重构有助于消除这种混乱并简化不清楚和复杂的代码。以下是众多敏捷专家在重构概念中引用的定义:

最好是连续重构,而不是分阶段重构。不断地重构可以防止代码变得复杂,并有助于保持代码的清洁和易于维护。在敏捷开发中,可以有短而独立的冲刺来适应重构。本模块概述了将在敏捷项目中实施的重构的技术/工程实践。

挑战:

尽管重构为软件的代码质量带来了很多好处,但仍然存在多种挑战阻止敏捷项目中的开发人员不断重构代码。以下是敏捷项目中最常见的一些挑战

  • 时间限制:时间是在敏捷项目中进行重构的最大挑战,因为 Sprint 具有一组定义的可交付成果。
  • 不情愿:如果代码在没有进行任何重构的情况下运行良好,则会倾向于不重新访问代码。这主要是因为没有错误的心态,因此不需要进行额外的活动,即重构。
  • 与分支集成:重构后跨不同分支集成代码被认为是一项挑战
  • 恐惧因素:开发人员经常担心重构会引入错误并破坏运行良好的现有功能
  • 重新测试:如果自动化测试套件不可用,不鼓励开发人员通过额外的手动测试来检查功能进行重构
  • 向后兼容性:向后兼容性通常会阻止开发人员开始重构工作。

重构的动机:

在进行重构时,以下几点在开发人员中更为常见

  • 添加新代码变得更容易
  • 现有代码的设计得到改进。
  • 有助于更好地理解代码
  • 使编码不那么烦人

重构的优点:

小步骤的重构有助于防止引入缺陷。从实施重构的好处可以进一步看出以下几点。

  • 提高软件可扩展性
  • 减少代码维护的费用。
  • 提供标准化代码
  • 架构改进而不影响软件行为
  • 提供更具可读性和模块化的代码
  • 重构模块化组件以最大限度地提高可重用性

敏捷项目的设计指南:

在传统的软件开发项目中,需求和计划是在开发开始之前设定的,这使团队能够以合理的信心了解前进的道路,即需求或设计不会在中间发生巨大变化,而敏捷方法则是支持和拥抱变化。在敏捷项目中,需求可以在项目周期的任何时候发生变化。因此,敏捷团队必须将代码置于可以方便地接受新需求或更改的位置。

好的设计是敏捷宣言项目的一个重要原则:“持续关注技术卓越,好的设计可以提高敏捷性”。

重构注意事项:

  • 过多的模式焦点会分散编写小而简单且易于理解的代码的注意力。
  • 从重构的角度来看模式,而不仅仅是可重用的元素。

如何查看教师代码?

“代码气味”是肯特·贝克和马丁·福勒创造的短语。一些最重要的“代码气味”是:

S.No.SmellDescription
1.Duplicated codeIdentical or very similar code exists in more than one location
2.Long MethodA method/function/procedure that has grown too large
3.Long ClassA class that has grown too large
4.Too Many ParametersA long list of parameters is hard to read and makes calling and testing the function complicated
5.Feature envyA class that uses methods of another class excessively
6.Lazy ClassA class that does too little
7.Contrived complexityForced usage of overly complicated design patterns where the simpler design would suffice.
8.Complex to debugCode has become complex enough to debug

重构指南:

  • 在开始之前确保代码正常工作。
  • 确保自动化测试套件可用并提供良好的覆盖率。
  • 在每次重构之前、期间和之后频繁地运行测试。
  • 在每次重构之前,使用版本控制工具并保存一个检查点。这不仅意味着可以从灾难中快速恢复,还意味着可以尝试重构,如果对重构的代码不满意,可以退出。
  • 将每个重构分解为更小的单元。
  • 最后,如果您的环境中有可用的重构工具,请使用它。

重构技术的实现:

有多种可用的重构技术可以使现有代码在性能和可维护性方面更好。在敏捷或任何其他方法中重构的基本目的是“让模块处于比你发现它更好的状态”

Martin Fowler 将重构技术分类如下:

  • 作曲方法
  • 在对象内移动特征
  • 组织数据
  • 简化条件表达式
  • 使方法调用更简单

每个类别都有一定的重构技术。下面解释了一些技术。

1.组合方法:它处理方法或函数代码的正确包装。具有非常长的复杂逻辑实现的大型方法/函数会降低代码的可维护性和可读性。应用提取方法等重构技术将有助于使代码更易于维护和可读。编写方法/函数的一些重构技术详述如下:

  • 提取方法
  • 内联方法
  • 用方法对象替换方法

提取方法:这是一种常用的重构技术。这种技术背后的实现非常简单。它包括通过将复杂的代码块转换为具有非常描述性标识符的新方法来分解长方法。此方法用于以下情况:

  • 该代码由涵盖多个逻辑流程的长方法组成,这些逻辑流程可以分解为更小的方法
  • 如果相同的代码在多个流中重复,请将其移至单个方法并从所有其他流中调用

例子:

Before Refactoring: 
void printStudentRecord() {

   printSchoolName();

   // print details  
   System.out.println("Id: " + _id);

   System.out.println("name " + _name);

}
After Refactoring:
void printStudentRecord() {
   printSchoolName();
   printStudentDetails();
}
void printStudentDetails() {
   // print details  
   System.out.println("Id: " + _id);
   System.out.println("name " + _name);
}

这种方法的优点是:

  • 通过将一组语句分组到单个较小的方法中来进行适当的代码重组
  • 减少代码重复
  • 增加代码的可读性

内联方法:与重构的提取方法技术相比,内联方法技术建议用方法/函数的主体替换方法/函数调用。当方法/函数的源代码非常小时,建议使用。此方法用于以下情况:

  • 当函数调用导致性能瓶颈时
  • 当内联代码增加代码的可读性时,因为函数函数相同
  • 当代码中的委托过多时

例子:

Before Refactoring 
int getRating() {

   return (moreThanFiveLateDeliveries()) ? 2 : 1;

}

boolean moreThanFiveLateDeliveries() {

   return _numberOfLateDeliveries > 5;

}

After Refactoring 
int getRating() {

   return (_numberOfLateDeliveries > 5) ? 2 : 1;

}

这种方法的优点是:

  • 减少不必要的复杂性,减少方法调用开销
  • 增加代码的可读性

用方法对象替换方法:好的代码的拇指规则是,它应该是可读且易于管理的。有时我们可能会遇到一个非常长的方法,其中包含许多局部变量,其中包含复杂的逻辑实现。由于使用了太多局部变量,因此无法通过应用提取方法技术来简化这种方法,因为传递这么多变量将与 long 方法本身一样混乱。在这种情况下,可以为这种方法创建一个类。此类将具有该 long 方法的所有局部变量作为其数据成员。其中一种方法是长方法。通过应用提取方法技术可以轻松简化该方法的复杂逻辑。

例子:

Before Refactoring
class Employee {

   private:

       // some declarations  
       double CalSalary(float fVCPI, float fCPI, float fPPF) {

           double dIncomeTax;

           double dHRA;

           double dLTA;

           // complicated salary calculations  

       }

};
After Refactoring 
class SalaryCalculator {
   private: float m_fVCPI;

   float m_fCPI;

   float m_fPPF;

   double m_dIncomeTax;

   double m_dHRA;

   double m_dLTA;

   public:

       SalaryCalculator(float fVCPIpercent, float fCPIpercent, float 
       fPF, double dIncomeTaxPercent, double dHRA, double dLTA) {

           // initialize private data members  
       }

   double CalculateSal() {

       // complicated salary calculations  
   } };

class Employee {

   private:

       // some declarations  
       public:

       double CalSalary() {

           SalaryCalculator obj(14, 13, 200.43, dITPercent, dHRA, dLTA);

           return obj.CalculateSal();

       }};

2. 对象内的移动特征:

对象设计中最重要和最关键的部分之一是决定在哪里分配职责。有很多选择。重构技术可以帮助我们做出相同的决定。以下是一些重构技术,可用于确定适当的职责分配:

  • 移动方法:考虑执行功能的场景。与编写它的方法(即 Class-1)相比,方法多次使用另一个类(即 Class-2)的成员(数据或函数)。然后可以使用移动方法技术重构这样的代码。在这种情况下,根据该技术,可以将该方法移至 Class-2。 Class-1 可以调用此方法来完成所需的功能,并且可以从 Class-1 中完全删除该方法。当一个方法被声明和定义它的类之一使用最多次数时,也可以应用移动方法技术。
移动方法重构技术

  • 移动字段:如果某个类的成员被其他类的访问方法使用的次数达到最大值,则可以将该字段移动到另一个类本身。这需要在具有该数据成员的早期类中进行必要的修改。在开发阶段确定新类时,也将应用移动方法技术。
移动域重构技术

  • 隐藏委托:面向对象设计的重要特征之一是,如果在其中一个类中进行了更改,那么其他类或极少数类不应因为此更改而对其代码产生影响。如果一个类将其请求发送到另一个类,那么这样的类可以称为客户端类。为发送的请求提供服务的类可以称为服务器类。考虑一个函数,它必须通过调用 A 类的方法之一来获取 B 类的对象。该函数还必须调用 B 类的方法来获取所需的数据。这意味着函数必须先与类 A 交互,然后再与类 B 交互。在这种情况下,类 B 的内部结构部分地暴露给该函数。如果 A 类有一个方法,该方法本身将从 B 类获取必要的东西并将其发送到客户端函数,则可以避免相同的情况。这个委托部分可以对客户端函数隐藏。下图显示了一个图形演示,其中“部门”类对“客户”隐藏,因为对“部门”类的调用被移至“个人”类方法。
隐藏委托重构技术

3. 组织数据:

以下部分说明了一些可以使数据组织更好的技术。这也使处理数据更容易。用对象替换数组:用于数据组织的最常见的数据结构之一是数组。该数组是同名的内存连续位置中同质数据的集合。尽管是同质的,但同一个数组可以包含具有不同含义的数据,从而在访问此数据结构时造成混乱。在这种情况下,最好用对象替换数组。

4. 简化条件表达式:

理解程序中实现的条件逻辑可能是一项具有挑战性的任务。使用重构技术,可以简化条件逻辑,使代码易于理解。本节有一些重构技术,使用这些技术可以轻松简化条件逻辑。

5. 使方法调用更简单:

对象可以通过公共方法与彼此以及外部世界进行交互。这些方法应该很容易理解,那么整个面向对象的设计就会变得容易理解。在本节中,我们将讨论一些使方法调用更容易的重构技术。

测试驱动开发中的重构:

测试驱动开发 (TDD) 是一种软件工程方法,包括编写失败的测试用例首先覆盖功能。然后实现必要的代码以通过测试,最后在不改变外部行为的情况下重构代码。

TDD 处理两种类型的测试,即

  • 单元测试:用于验证与其环境分离的单个类的功能。
  • 验收测试:用于检查单个功能

同样,重构也可以分为不同的类型。

  • 在本地类级别重构:这个类的单元测试没有改变,测试仍然是绿色的。如果单元测试旨在检查完整的场景而不是单个成员的调用,那么重构就属于这一类。
  • 重构影响多个类:在这种情况下,验收测试是重构后检查完整功能的唯一方法。如果提供了一种新的功能方式,并且通过一个接一个地更改类和相关的单元测试来实现,这将很有用。在 TDD 中重构时要记住的一个重要方面是,当重构影响接口/API 的代码时,该接口/API 的所有调用者以及为接口/API 编写的测试都需要作为重构的一部分进行更改。与定义一致,重构是一种“保持行为”的变化。