BreakingChange:在软件开发中的多个方面详细介绍

发布时间:2023-05-20

一、什么是BreakingChange

BreakingChange,意为“破坏性改变”,在软件开发中指不兼容的 API 或库版本更新,可能会破坏原有版本中已经在使用的功能或者造成意料之外的行为。 例如,一个接口的参数名称被重命名或者接口返回值发生类型变化,这些修改都会使得依赖该接口的其他代码出现问题。 BreakingChange 的发生不可避免,软件开发者需要在面对 BreakingChange 时,选择如何处理和应对,以最小化对软件系统的影响。

二、BreakingChange 的分类

BreakingChange 可以分为 API BreakingChange 和库版本 BreakingChange 两类。

1、API BreakingChange

API BreakingChange 指 API 接口的变化,包括但不限于以下方面: 1)新增或移除方法、属性、事件等
2)修改方法、属性、事件的输入参数或返回值
3)重命名方法、属性、事件等
4)修改 API 的行为或语义
5)调整 API 异常的定义
6)调整 API 的权限控制
API BreakingChange 的影响范围通常比较局限,对于开发者而言,最明显的表现就是编译时出现错误或警告。

2、库版本 BreakingChange

库版本 BreakingChange 则是指库或框架的版本变化,包括但不限于以下方面: 1)新增或删除公共类型或接口
2)修改公共类型或接口的成员
3)重命名公共类型或接口
4)调整库或框架的内部实现方式
5)修复已知 Bug
库版本 BreakingChange 的影响范围更广,可能导致依赖库或框架的代码出现难以排查的运行时异常。

三、应对 BreakingChange 的策略

以下是针对 BreakingChange 发生时,开发者可以采取的应对策略:

1、忽略 BreakingChange

如果 BreakingChange 的影响比较局限,或者开发者当前无法处理,可以通过忽略 BreakingChange 来解决。 例如,某个库版本发生破坏性改变,但功能并不是很关键,我们可以选择暂时不升级版本。 不过需要注意,即使是小规模的 BreakingChange,也可能在后续版本中进一步发展,最终导致无法控制的问题。

2、适配 BreakingChange

适配 BreakingChange 是最常见的应对策略之一。开发者可以修复自己代码中由于 BreakingChange 引起的问题,以达到新旧版本间兼容的目的。 例如,库版本发生 BreakingChange,导致某个方法需要新增一个参数,开发者需要修改自己代码中该方法的调用逻辑,添加新的参数后方可兼容新版本。

3、向增量升级

向增量升级是一种将旧版本逐渐转化为新版本的方法,从而减小破坏性的影响。 例如,库版本从 1.0 升级到 2.0,开发者可以先升级到 1.1,再到 1.2,逐渐靠近 2.0,以便适应各个版本的修改。

4、择时升级

择时升级是指针对 BreakingChange,选择一个合适的时间点进行升级。比如等待下一个迭代周期、项目进入稳定阶段等情况。 择时升级可以让升级过程更加平缓,并在变化较大时减小风险。

四、代码示例

1、API BreakingChange 示例

// V1 版本接口声明
public interface IService
{
    void Foo(string name);
}
// V2 版本接口声明
public interface IService
{
    void Bar(string name, int age);
}
// 使用 V1 版本调用代码
IService service = new Service();
service.Foo("Tom"); // 编译通过
// 升级到 V2 版本后的错误示例
IService service = new Service();
service.Foo("Tom"); // 编译错误,没有此方法

2、库版本 BreakingChange 示例

// V1 版本库的代码
public class Calculator
{
    public int Add(int a, int b)
    {
        return a + b;
    }
}
// V2 版本库的代码
public class Calculator
{
    public int Add(int a, int b, int c)
    {
        return a + b + c;
    }
    public int Sub(int a, int b)
    {
        return a - b;
    }
}
// V1 版本调用代码
Calculator calc = new Calculator();
int result = calc.Add(1, 2); // 运行正常
// 升级到 V2 版本后的错误示例
Calculator calc = new Calculator();
int result = calc.Add(1, 2); // 编译通过,但运行时会抛出异常

3、忽略 BreakingChange 示例

// V1 版本接口声明
public interface IService
{
    void Foo(string name);
}
// V2 版本接口声明
public interface IService
{
    void Bar(string name, int age);
}
// V1 版本调用代码
IService service = new Service();
service.Foo("Tom"); // 正常运行
// 针对升级到 V2 版本,可以选择不升级的方式

4、适配 BreakingChange 示例

// V1 版本库的代码
public class Calculator
{
    public int Add(int a, int b)
    {
        return a + b;
    }
}
// V2 版本库的代码
public class Calculator
{
    public int Add(int a, int b, int c)
    {
        return a + b + c;
    }
    public int Sub(int a, int b)
    {
        return a - b;
    }
}
// V1 版本调用代码
Calculator calc = new Calculator();
int result = calc.Add(1, 2); // 运行正常
// 升级到 V2 版本后,需要修改调用方式
Calculator calc = new Calculator();
int result = calc.Add(1, 2, 3); // 运行正常

5、向增量升级示例

// V1 版本库的代码
public class Calculator
{
    public int Add(int a, int b)
    {
        return a + b;
    }
}
// V2 版本库的代码
public class Calculator
{
    public int Add(int a, int b, int c)
    {
        return a + b + c;
    }
    public int Sub(int a, int b)
    {
        return a - b;
    }
}
// 增量升级,先升级到 V1.1 版本
public class Calculator_v1_1 : Calculator
{
    public new int Add(int a, int b)
    {
        return Add(a, b, 0);
    }
}
// V1.1 版本调用代码
Calculator calc = new Calculator_v1_1();
int result = calc.Add(1, 2); // 运行正常
// 继续升级到 V2 版本
Calculator calc = new Calculator();
int result = calc.Add(1, 2, 3); // 运行正常

6、择时升级示例

// V1 版本库的代码
public class Calculator
{
    public int Add(int a, int b)
    {
        return a + b;
    }
}
// V2 版本库的代码
public class Calculator
{
    public int Add(int a, int b, int c)
    {
        return a + b + c;
    }
    public int Sub(int a, int b)
    {
        return a - b;
    }
}
// 在开发人员认为适合的时间点升级到 V2 版本