/
安全问题

代码过时--如何保持兼容性

29. 07. 2022

Obsah článku

在开发大型系统(如企业应用程序、共享软件包、库......)的过程中,多个层次和开发人员相互沟通,出现了如何处理新代码版本发布的问题。

让我们看一个例子,我们想为一个社区的开发者开发一个共享的Composer包。

语义版本管理

在解决后向和前向兼容性问题之前,我们需要弄清楚如何跟踪软件的变化。目前(2022年),将所有的改动都放到Git中是最好的方式。软件库可以共享,例如,通过GitHub或GitLab。每个软件变更都有一个唯一的标识符,用于识别每个提交,并描述实际发生的情况。

在开发图书馆时,以下策略对我很有效。

在开发之初,在 "master"(或 "main")分支创建一个初始提交,在那里提交底层文件结构。

对于每一个新的请求,都会从主干线上创建一个单独的分支,在其中工作。当变化准备好了,一个合并请求会以 "拉动请求 "的形式发送到主站。对该请求进行代码审查,如果一切正常,该修改就会被合并到主站。

如果该分支包含一个向后不兼容的变化(BC中断,来自 "Back Compatibility Break"),则必须相应地标记。以下各章将讨论标记BC断点的方法。

然后,使用具有以下结构的标签(基于语义版本学2.0.0)对库的生产版本进行标记。

我们以`MAJOR.MINOR.PATCH'的格式来写版本号。版本号的递增是按以下方式进行的。

  • `MAJOR'--当有一个与他人不兼容的变化时(API)。
  • 小"--在保持向后兼容的情况下增加的功能
  • PATCH"--当一个错误被修复并保持向后兼容时

通过使用预发布和添加元数据,有可能细化信息。例如:1.0.0-alpha1.0.1-beta+2

你可以在官方网站上阅读更多关于语义版本学的信息:https://semver.org。

向后和向前兼容

在设计软件时,你应该始终考虑后向兼容性(新的功能和变化必须与旧的代码兼容),在某些情况下,还要考虑前向兼容性(当前功能必须与未来的界面变化兼容)。

把这两项任务做好是非常具有挑战性的。并不总是能够在不破坏兼容性的情况下进行改变。

在进行修改时,你应该总是分步进行,并让用户有足够的时间对修改做出反应。

以下各节描述了如何思考这个问题。

第1阶段:将一个功能标记为废弃的功能

兼容性威胁的基本类型是删除或重新命名一个过去存在的功能。大多数情况下,这是因为函数接受的参数发生了变化,或者是旧的逻辑在新的方式下应该以不同的方式处理。

在第一阶段,代码的旧部分应该被标记为废弃的,但不做任何改变。

在PHP中,有一个注释@deprecated,它应该直接写在方法、函数、属性、变量、常量和一般所有被废弃的代码上面。

写出某件事被废弃的原因以及将来如何改变的理由,也是一种好的做法。例如,给出一个新功能或使用方法的名称。

一个标记代码过时的现实世界的例子。常量将被删除,最好使用内置的Enum(BC由于迁移到新版本的PHP而中断)。

class OrderNotification
{
/** @自2022-05-24起弃用,使用枚举的OrderNotificationType */**。
public const
TYPE_EMAIL = '电子邮件',
TYPE_SMS = '文本';

@deprecated注解只会对IDE(开发工具)和编译工具造成无声的警告。它不会破坏任何东西。

第二阶段:调用一个新的方法/逻辑

在第二阶段,我们用新的方法取代旧的实现,但在旧的实现中使用新的方法。这将有助于在用户不知不觉中保持界面的兼容性。

例子:该方法被废弃了,因为替代它的是一个新的静态服务。既然有人可以使用它,它只是被标记为废弃的,并在内部调用新的实现。开发者一般可以假设该方法在将来会被完全删除。

/** @deprecated since 2021-09-11 use Ip::get() instead.*/
public static function userIp(): string
{
return Ip::get();
}

第三阶段:改变静态分析的注释

如果你使用像PhpStan这样的静态分析工具(强烈推荐!),在实际改变数据类型之前,首先重写PHPDoc注释是个好主意。静态分析会通知用户有什么东西坏了,但运行时不会被触动。

第四阶段:扔掉通知

在第四阶段,一个新的方法被调用,同时抛出一个 "注释 "级错误。应用程序仍在工作,只是开始逐渐在系统日志中存储信息,即某个功能已被废弃,将被改变或删除。我们现在将对这种类型的变化进行积极提醒。开发者在开发或编译过程中会看到错误。

/** @deprecated since 2021-05-01, use UserMetaManager instead.*/
public function getMeta(int $userId, string $key): ?string
{
trigger_error(__METHOD__ . ': 此方法已被废弃,请使用UserMetaManager代替。');
return $this->userMetaManager->get($userId, $key);
}

第五阶段:抛出一个异常

我建议在完全删除该方法之前抛出一个致命的异常。这一点特别重要,因为应用程序将被完全停止,而且不能忽视这个错误。与完全删除代码不同的是,用户将被告知实际发生了什么,并可以轻松地修复错误。

第六阶段:彻底清除代码

在最后阶段,旧的代码将被完全删除。如果任何用户没有修复依赖关系,他们的应用程序将被破坏。

敏感区域的严重BC断裂应该总是在下一个 "主要 "版本中完成,并且应该至少提前一个 "主要 "版本抛出通知指出。如果你不这样做,更新图书馆将是非常困难的。

Jan Barášek   Více o autorovi

Autor článku pracuje jako seniorní vývojář a software architekt v Praze. Navrhuje a spravuje velké webové aplikace, které znáte a používáte. Od roku 2009 nabral bohaté zkušenosti, které tímto webem předává dál.

Rád vám pomůžu:

Související články

1.
11.