一、什么是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 版本