我确实有一些代码,如果需要的话,将较长的字符串与两个较短的段混淆在一起。恶心。以下是我在保持一些数据兼容 12 年后的经验:
定义你的目标- 那里有两个:
- 新版本应该能够读取旧版本写入的内容
- 旧版本应该能够读取新版本写入的内容(更难)
添加对版本 0 的版本支持- 至少写一个版本头。再加上保留(可能很多)旧的阅读器代码,可以简单地解决第一种情况。如果您不想实施情况 2,请开始拒绝新数据现在!
如果您只需要情况 1,并且随着时间的推移预期的变化相当小,那么您就已经准备好了。不管怎样,在第一次发布之前做好这两件事可以让你以后免去很多麻烦。
序列化期间转换- 在运行时,仅将数据以“新格式”保存在内存中。在持久性限制下进行必要的转换和测试(读取时转换为最新版本,写入时实现向后兼容性)。这将版本问题隔离在一处,有助于避免难以追踪的错误。
保留一组来自所有版本的测试数据。
存储可用类型的子集- 将实际序列化的数据限制为几种数据类型,例如 int、string、double。在most在这种情况下,额外的存储大小是通过支持这些类型的更改的减少的代码大小来弥补的。 (不过,这并不总是您可以在嵌入式系统上做出的权衡)。
例如不要存储比本机宽度短的整数。 (你might当您需要存储长整型数组时需要这样做)。
添加断路器- 存储一些密钥,允许您故意使旧代码显示一条错误消息,表明此新数据不兼容。您可以使用作为错误消息一部分的字符串 - 那么您的旧版本可能会显示一条它不知道的错误消息 - “您可以使用我们网站上的 ConvertX 工具导入此数据”在本地化版本中并不好应用程序,但仍然比“Ungültiges 格式”.
不要直接序列化结构- 这就是逻辑/物理分离。我们与两种人一起工作,两者都有其优点和缺点。如果没有一些运行时开销,这些都无法实现,这几乎会限制您在嵌入式环境中的选择。无论如何,在持久化期间不要使用固定的数组/字符串长度,这应该已经解决了一半的麻烦。
(A) 适当的序列化机制- 我们使用二进制序列化器,允许在存储时启动一个“块”,它有自己的长度标头。读取时,会跳过额外的数据,并且默认初始化缺失的数据(这大大简化了序列化代码中“读取旧数据”的实现。)块可以嵌套。这就是您在物理方面所需的全部内容,但需要一些糖衣来完成常见任务。
(B) 使用不同的内存表示- 内存中的表示基本上是map<id, record>
其中 is 可能是一个整数,并且record
可能
- 空(未存储)
- 原始类型(字符串、整数、双精度型 - 使用越少就越容易)
- 原始类型数组
- 和记录数组
我最初这么写是为了让大家不会问我每个格式兼容性问题,虽然实现有很多缺点(我希望我今天能清楚地认识到这个问题......)它可以解决
查询不存在的值默认情况下将返回默认/零初始化值。当您在访问数据和添加新数据时牢记这一点时,这会很有帮助:想象一下版本 1 会自动计算“foo length”,而在版本 2 中用户可以覆盖该设置。 “计算类型”或“长度”中的零值应表示“自动计算”,并且您已设置完毕。
以下是您可以预期的“变化”场景:
- 标志(是/否)扩展为枚举(“是/否/自动”)
- 一个设置分为两个设置(例如,“添加边框”可以分为“在偶数天添加边框”/“在奇数天添加边框”。)
- 添加设置,覆盖(或更糟糕的是,扩展)现有设置。
为了实现案例 2,您还需要考虑:
- 任何价值都不得被删除或被另一价值取代。 (但在新格式中,它可能会说“不支持”,并且添加了一个新项目)
- 枚举可能包含未知值、有效范围的其他变化
唷。那是很多。但它并不像看起来那么复杂。