HomeBuildTroubleshoot your code

排查代码问题

⚠️ Update Notice:

Please read Substrate to Polkadot SDK page first.


由于 Substrate 和 FRAME 提供了一个灵活且模块化的框架来构建区块链应用程序,因此遵循常见的最佳实践和基本编码原则对于避免引入错误或使代码难以调试非常重要。

常规编码实践

以下一般原则并非 Substrate 或使用 FRAME 独有的,但在构建具有严格安全要求和受限资源(如区块链应用程序)的复杂软件时,它们尤其重要:

  • 格式和可读性。使用一致的格式并遵循编写可读代码的最佳实践,使你的程序易于你和其他人理解和维护。
  • 注释。向你的代码添加清晰简洁的注释,以解释代码的作用,并在适用的情况下解释代码为何以这种方式编写。
  • 样式和命名约定。遵循 Rust 样式指南和命名约定,使你的代码与其他 Rust 程序保持一致,并使其他 Rust 程序员更容易阅读和调试你的代码。
  • 许可。确保你的存储库包含适当的开源许可证以及你使用的未编写代码所需的任何许可证、版权声明和归属声明。在大多数情况下,如果你使用的是你未编写的代码,则应保留原始许可证并提及作者。
  • 重构。改进代码的设计、结构或实现,以创建更简单、更清晰、更高效或更具表现力的程序。通常,重构会简化代码逻辑而不会更改代码功能,并会产生更易于阅读、维护和扩展的代码。
  • 不要重复自己 (DRY)。遵循软件开发的 DRY 原则,并使用数据抽象或数据规范化来避免冗余。
  • 测试。编写和执行单元测试,以确保所有单个软件组件都能按预期工作。有关在运行时单元测试和测试模块的更多信息,请参阅 测试
  • 错误和警告。解决编译器报告的所有错误和警告,以确保你了解错误或警告的原因以及如何解决它。
  • 依赖项。定期更新依赖项,以确保你的代码不会落后于新版本太多而过时。你应该定期更新 Rust 编译器和工具链,并检查 Polkadot 新版本是否有更改。
  • 硬编码。避免将数据直接嵌入源代码中。

常见的 Substrate 问题

Substrate 的一些常见方面如果处理不当可能会导致错误或性能问题。 在编写链的逻辑时,你应该特别注意以下潜在的麻烦点:

基准测试

Substrate 基准测试系统旨在帮助你确定要为模块中的函数分配的适当权重。 设置适当的权重是确保区块链可靠和安全的重要步骤。 虽然你可以在开发的早期阶段跳过基准测试和设置事务权重,但你应该知道,使用权重为零会使你的代码容易受到攻击。 如果没有与函数执行相关的交易费用,恶意行为者可以重复调用该函数(基本上是用事务向网络发送垃圾邮件),从而在拒绝服务 (DoS) 攻击中阻止链。

通常,你应该确保运行时中可以执行的所有函数都已定义权重,并从调用帐户中减去相应的费用。 交易费用通常是防止拒绝服务 (DoS) 攻击并为链创建可持续经济模型的重要经济激励。

有关基准测试系统的更多信息,请参阅 基准测试。 有关如何编写和运行基准测试的简单示例,请参阅 添加基准测试

模块耦合

在 Substrate 中,一个模块可以调用另一个模块中的函数有两种方式模块耦合是指模块如何调用另一个模块中的函数。

  • 紧密模块耦合更严格,通常在某个模块依赖于另一个模块中的所有或大量类型和方法时使用。
  • 松散模块耦合更灵活,通常在某个模块依赖于另一个模块公开的特定特性或函数接口时使用。

紧密模块耦合要求在运行时中安装这两个模块,并且这两个模块不能独立使用。 此外,紧密耦合的模块可能更难维护,因为一个模块中的更改通常需要在另一个模块中进行更改。 在大多数情况下,松散耦合是更灵活的解决方案,因为你可以重用另一个模块中的类型和接口,而无需在运行时中包含该模块。

有关紧密和松散模块耦合的更多信息,请参阅 模块耦合 和此 代码示例。 有关模块耦合的简单示例,请参阅 使用紧密模块耦合使用松散模块耦合

离链工作程序

你可以使用离链操作来查询离链来源的数据或在离链上处理数据。 例如,离链工作程序使你能够卸载执行时间可能超过最大块执行时间允许的任务。 但是,离链操作的某些特性可能会产生意想不到的后果。 如果你计划使用离链工作程序,则应考虑以下事项:

  • 默认情况下,当验证者节点执行其块创作时,离链工作程序会在验证者节点上运行。
  • 如果你想在不是验证者的节点上运行离链工作程序,则必须使用 --offchain-worker always 命令行选项。
  • 要阻止任何节点(验证者或非验证者)运行离链工作,则可以使用 --offchain-worker never 命令行选项。
  • 如果你在网络上将离链工作程序作为并行进程运行,则可能需要实现并发编程技术以避免竞争条件。
  • 默认情况下,即使块未完成,也会为每个块导入触发离链工作程序。
  • 由于离链工作程序可以完全访问状态,因此你可以创建条件,使其仅在某些特定情况下运行。

有关离链操作的更多信息,请参阅 离链操作。 有关如何使用离链组件的示例,请参阅 离链工作程序

存储

运行时存储中所述,区块链存储的基本原则是最大限度地减少存储的数据项的数量和大小。 不必要地存储数据会导致网络性能缓慢和资源耗尽。

在规划和检查代码中是否存在潜在问题时,请记住以下准则:

  • 只存储关键信息。
  • 不要存储中间或瞬态信息。
  • 不要存储如果操作失败则不需要的数据。
  • 如果可能,不要存储已存储在另一个结构中的信息
  • 尽可能存储长度有限的哈希数据。

通常,最好使用一个较大的数据结构而不是许多较小的数据结构,以减少复杂性和读写操作的数量。 但是,情况并非总是如此,你应该使用基准测试来根据具体情况衡量和优化存储数据的方式。

列表和存储映射都会产生存储成本,因此你应该注意如何使用它们。列表或映射中的项目越多,迭代项目对运行时性能的影响就越大。 存储映射通常存储无界的数据集,并且由于访问映射的元素比访问列表的元素需要更多的数据库读取,因此使用存储映射进行迭代的成本可能要高得多。

如果你项目的平行链,则注意迭代存储映射中项目所需的时间尤其重要。 如果迭代存储所需的时间超过块生成允许的最大时间,则区块链将停止生成块,从而停止工作。

通常,你应该避免在存储映射中包含无界数据,并避免迭代存储大量数据集的存储映射。 你应该使用基准测试来在不同条件下测试运行时中所有函数的性能,包括迭代列表或存储映射中的大量项目。 通过测试特定条件(例如,触发函数以在具有许多迭代的大型数据集上执行),基准测试可以帮助你确定何时最好通过限制列表中的元素数量或循环中的迭代次数来强制执行边界。

有关存储和存储结构的更多准则,请参阅 状态转换和存储运行时存储。 有关迭代存储的更多信息,请参阅 迭代存储映射

事件

模块通常会发出事件,以将有关运行时中数据或条件更改的通知发送给接收实体(如用户或应用程序),这些实体位于运行时外部。

在自定义模块中,你可以定义以下与事件相关的信