质量指南: 确保代码质量 [待校准@1031]

此页指导如何提高软件质量ROS 2包,聚焦更具体地区质量做法科 Developer Guide. [待校准@1032]

以下各节旨在讨论ROS 2核心、应用程序和生态系统包以及核心客户端库、c ++ 和Python。提出的解决方案是出于设计考虑提高质量属性 "Reliability" , "Security" , "Maintainability" , "Determinism" 等涉及非功能要求。 [待校准@1033]

作为ament包构建的一部分的静态代码分析 [待校准@1034]

上下文: [待校准@1035]

问题: [待校准@1038]

  • 库级静态代码分析不作为包构建过程的一部分运行。 [待校准@1039]

  • 库级静态代码分析需要手动执行。 [待校准@1040]

  • 在构建新的包版本之前忘记执行库级静态代码分析的风险。 [待校准@1041]

解决方案: [待校准@1042]

  • 使用 ament 的集成功能执行静态代码分析,作为包构建过程的一部分。 [待校准@1043]

实施: [待校准@1044]

...
if(BUILD_TESTING)
  find_package(ament_lint_auto REQUIRED)
  ament_lint_auto_find_test_dependencies()
  ...
endif()
...
...
<package format="2">
  ...
  <test_depend>ament_lint_auto</test_depend>
  <test_depend>ament_lint_common</test_depend>
  ...
</package>

示例: [待校准@1047]

结果上下文: [待校准@1054]

  • [需手动修复的语法] ament 支持的静态代码分析工具作为包构建的一部分运行。 [待校准@1055]

  • [需手动修复的语法] ament 不支持的静态代码分析工具需要单独执行。 [待校准@1056]

通过代码注释进行静态线程安全分析 [待校准@1057]

Context上下文: Context [待校准@1058]

Problem问题: Problem [待校准@1061]

Solution解决方案: ** [待校准@1063]

Implementation实施背景: ** [待校准@1065]

要启用线程安全分析,必须对代码进行注释,以使编译器更多地了解代码的语义。这些注释是特定于叮当声的属性 -- 例如 __attribute__(capability())) 。ROS 2提供预处理器宏,而不是直接使用这些属性,这些宏在使用其他编译器时会被擦除。 [待校准@1066]

这些宏可以在 rcpputils/thread_safety_annotations.h 中找到 [待校准@1067]

线程安全分析文档声明 [待校准@1068]

线程安全分析可以与任何线程库一起使用,但是它确实要求将线程API包装在具有适当注释的类和方法中 [待校准@1069]

我们已经决定,我们希望ROS 2开发人员能够直接为他们的开发版本使用 “std::” 线程原语。我们不想像上面建议的那样提供我们自己的包装类型。 [待校准@1070]

有三个c standard标准库需要注意 * GNU标准库 “libstd c ++”-Linux上的默认值,显式地通过编译器选项 “-stdlib = libstd c ++ `` * The LLVM standard library `` 库c ++” (也调用ed libcxx ) -macOS上的默认值,由编译器选项 “-stdlib = lib c ++” 显式设置 * Windows c Standard标准库-与此用例无关 [待校准@1071]

libcxx annotates its std::mutex and std::lock_guard implementations for Thread Safety Analysis. When using GNU libstdc++ , those annotations are not present, so Thread Safety Analysis cannot be used on non-wrapped std:: types. [待校准@1072]

Therefore, to use Thread Safety Analysis directly with std:: types, we must use libcxx

Implementation实施: ** [待校准@1074]

这里的代码迁移建议绝不是完整的 -- 在编写 (或注释现有) 线程代码时,我们鼓励您尽可能多地使用适合您用例的注释。然而,这是一个很好的开始! [待校准@1075]

  • 为包/目标启用分析 [待校准@1076]

    当ccompiler编译器发出响声时,启用 -Wthread-safety 标志。以下基于CMake的项目示例 [待校准@1077]

    if(CMAKE_CXX_COMPILER_ID MATCHES "Clang")
      add_compile_options(-Wthread-safety)   # for your whole package
      target_compile_options(${MY_TARGET} PUBLIC -Wthread-safety)  # for a single library or executable
    endif()
    
  • 注释代码 [待校准@1078]

    • 步骤1-注释数据成员 [待校准@1079]

      • 查找 “std::mutex” 用于保护某些成员数据的任何地方 [待校准@1080]

      • RCPPUTILS_TSA_GUARDED_BY(mutex_name) 注释添加到受互斥体保护的数据中 [待校准@1081]

      class Foo {
      public:
        void incr(int amount) {
          std::lock_guard<std::mutex> lock(mutex_);
          bar += amount;
        }
      
        void get() const {
          return bar;
        }
      
      private:
        mutable std::mutex mutex_;
        int bar RCPPUTILS_TSA_GUARDED_BY(mutex_) = 0;
      };
      
    • 步骤2-修复警告 [待校准@1082]

      • 在上面的示例中-“foo::get” 将产生编译器警告!要修复它,请在返回杆前锁定 [待校准@1083]

      void get() const {
        std::lock_guard<std::mutex> lock(mutex_);
        return bar;
      }
      
    • 步骤3-(可选但推荐) 将现有代码重构为私有互斥模式 [待校准@1084]

      线程化ccode代码中推荐的模式是始终将您的 mutex 作为数据结构的 “私有:” 成员。这使得数据安全成为包含结构的关注点,卸载结构用户的责任,并最小化受影响代码的表面积。 [待校准@1085]

      将锁设为私有可能需要重新考虑数据接口。这是一个很棒的练习 -- 这里有几件事需要考虑 [待校准@1086]

      • 您可能希望提供专门的接口来执行需要复杂锁定逻辑的分析,例如对互斥保护的地图结构的过滤集合中的成员进行计数,而不是实际将底层结构返回给消费者 [待校准@1087]

      • 在数据量很小的地方,考虑复制以避免阻塞。这可以让其他线程继续访问共享数据,这可能导致更好的整体性能。 [待校准@1088]

    • 步骤4-(可选) 启用负能力分析 [待校准@1089]

      https://clang.llvm.org/docs/ThreadSafetyAnalysis.html# 消极能力 [待校准@1090]

      负能力分析允许您指定 “调用此函数时不得持有此锁”。它可以揭示其他注释无法揭示的潜在死锁情况。 [待校准@1091]

      • 在指定 -Wthread-safety 的地方,添加附加标志 -Wthread-safety-negative [待校准@1092]

      • 在任何获取锁的函数上,使用 “rcpputils_tsa_need (!mutex)” 模式 [待校准@1093]

  • 如何运行分析 [待校准@1094]

Resulting结果上下文: ** [待校准@1104]

  • 当使用Clang和 libcxx 时,潜在的死锁和竞争条件将在编译时浮出水面 [待校准@1105]

动态分析 (数据循环和死锁) [待校准@1106]

Context上下文: Context [待校准@1058]

  • 您正在开发/调试多线程C++ 生产代码。 [待校准@1107]

  • 您可以使用plines或c11 11线程 + llvm lib c ++ (在线程消毒器的情况下)。 [待校准@1108]

  • 您不使用Libc/libstdc + + 静态链接 (在线程消毒器的情况下)。 [待校准@1109]

  • 您不能构建与职位无关的可执行文件 (在线程清理程序的情况下)。 [待校准@1110]

Problem问题: Problem [待校准@1061]

  • 数据竞争和死锁会导致严重的错误。 [待校准@1062]

  • 使用静态分析无法检测到数据竞争和死锁 (原因: 静态分析的局限性)。 [待校准@1111]

  • 在开发版本调试/测试期间,数据竞争和死锁不得出现 (原因: 通常不是所有可能的控制路径都通过生产代码执行)。 [待校准@1112]

Solution解决方案: ** [待校准@1063]

  • 使用动态分析工具,该工具专注于查找数据种族和死锁 (此处为clang线程消毒器)。 [待校准@1113]

Implementation实施: ** [待校准@1074]

Resulting结果上下文: ** [待校准@1118]

  • 在部署生产代码之前,发现数据竞争和死锁的机会更高。 [待校准@1119]

  • 分析结果可能缺乏可靠性,工具处于 β 阶段阶段 (在螺纹消毒剂的情况下)。 [待校准@1120]

  • 由于生产代码仪表产生的开销 (为仪表化/非仪表化生产代码维护单独的分支,等等)。 [待校准@1121]

  • 检测代码每个线程需要更多内存 (如果是线程消毒器)。 [待校准@1122]

  • 仪表化代码映射大量虚拟地址空间 (如果是线程消毒器)。 [待校准@1123]