跳转到内容

ROSE 编译器框架/编码规范

来自维基教科书,开放的世界,开放的书籍

预期内容和避免内容

[编辑 | 编辑源代码]

此页面记录了我们在 ROSE 项目中编写代码的当前推荐做法。它也作为我们 代码审查 过程的指南。

新代码应该从一开始就遵循本文件中描述的约定。

对遵循不同编码风格的现有代码的更新,只有在您是代码维护者时才应执行。

编码规范中各个章节的顺序遵循自上而下的方法:首先是大的内容,然后深入到细粒度细节。

六项原则

[编辑 | 编辑源代码]

我们使用编码规范来反映我们对所有对 ROSE 的贡献所重视的原则

  • 文档:提交的内容是什么?这是否反映在提交信息、自述文件、源代码注释或同一个提交中的 LaTex 文件中?
  • 样式:编码样式是否与要求的和推荐的格式一致?代码是否干净、令人愉悦且易于阅读?
  • 接口:代码是否具有干净且简单的接口供用户使用?
  • 算法:代码是否对所使用的算法有足够的注释?算法是否正确且高效(空间和时间复杂度)?
  • 实现:实现是否正确地实现了已记录的算法?
  • 测试:代码是否附带相应的测试转换器和输入,以确保贡献做了它们应该做的事情?
    • Jenkins 是否已配置为触发这些测试?开发者工作站上的本地测试不算。

避免编码规范之争

[编辑 | 编辑源代码]

我们直接引用来自 http://www.parashift.com/c++-faq/coding-std-wars.html 的文本,如下所示

"几乎每个软件工程师都曾在某个时候被利用过,有人将编码规范作为权力游戏。对琐碎细节的教条主义是智力薄弱的表现。不要像他们一样。这些人是那些无法以任何有意义的方式做出贡献的人,他们无法真正提高软件产品的价值,所以他们没有通过沉默暴露自己的无能,而是热心地喋喋不休关于微不足道的事情。他们无法在软件的实质内容中添加价值,所以他们争论形式。仅仅因为“他们”这样做并不意味着编码规范是坏的,然而。

另一种对编码规范的情绪化反应是由具有过时技能的个人制定的编码规范造成的。例如,有人可能根据 N 十年前的编程方式来设定今天的标准,当时标准设定者正在编写代码。这种强加会产生一种对编码规范的不信任态度。如上所述,如果您被迫忍受过这样的不幸经历,不要让它让您对编码规范的全部目的和价值感到厌恶。并不需要非常大的组织才能发现保持一致性的价值,因为不同的程序员可以编辑相同的代码,而无需不断地重新组织彼此的代码,进行关于“最佳”编码规范的拉锯战。"

必须、应该和可以

[编辑 | 编辑源代码]

术语必须、应该和可以具有特殊含义。

  • 必须的要求必须遵守,
  • 应该是一个强烈的建议,
  • 可以是一个一般的指南。

有了新想法和建议

[编辑 | 编辑源代码]

这不是一个记录将来的新想法/概念/建议的地方。如果您有建议,请放入此页面的讨论选项卡 链接

我们欢迎您提出改进和更改的建议,以便我们能够更快更好地完成工作。

Git 惯例

[编辑 | 编辑源代码]

名称和电子邮件

[编辑 | 编辑源代码]

在提交本地更改之前,您必须确保已正确配置作者和电子邮件信息(在您所有机器上)。拥有可识别且一致的姓名和电子邮件将使我们更容易评估您对我们项目的贡献。

指南

  • 姓名:您必须使用您通常用于工作/商业的正式姓名,而不是同事、经理或赞助商难以识别的昵称或别名。
  • 电子邮件:您必须使用您通常用于工作的电子邮件。如果确实经常使用该个人电子邮件进行商务目的,则可以是您的公司电子邮件或您的个人电子邮件(gmail)。

检查作者和电子邮件是否配置正确

  $ git config user.name
  <your name>

  $ git config user.email
  <your email>

或者,您也可以键入以下内容来列出您当前所有 git 配置变量和值,包括姓名和电子邮件信息。

  $ git config -l


设置您的姓名和电子邮件

  $ git config --global user.name "<Your Name>"
  $ git config --global user.email "<[email protected]>"

提交信息

[编辑 | 编辑源代码]

拥有简洁准确的提交信息对于帮助代码审查人员完成他们的工作非常重要。

最新要求

示例提交信息,摘自 链接

(Binary Analysis) SMT solver statistics; documentation

* Replaced the SMT class-wide number-of-calls statistic with a
  more flexible and extensible design that also tracks the amount
  of I/O between ROSE and the SMT solver.  The new method tracks
  statistics on a per-solver basis as well as a class-wide basis, and
  allows the statistics to be reset at arbitrary points by the user.

* More documentation for the new memory cell, memory state, and X86
  register state classes.
  • (必需) 摘要:提交信息的第 1 行是提交的一个简短的单行摘要(<50 个字)。以一个主题开头,用括号括起来,以指示此提交代表的项目、功能、错误修复等。
  • (可选)使用一个项目符号列表(使用星号,*)对于每个项目来详细说明提交

另请参阅 http://spheredev.org/wiki/Git_for_the_lazy#Writing_good_commit_messages.

设计文档

[编辑 | 编辑源代码]

“软件设计文档是您、您的团队、您的项目经理和您的客户之间的书面协议。当您记录您的假设、决定和风险时,它会让团队成员和利益相关者有机会达成一致意见或要求澄清和修改。一旦软件设计文档得到相关各方的批准,它就成为限制项目范围变更的基线。” - 如何编写软件设计文档 | eHow.com

我们仍在定义设计文档的要求,但初步而言,以下是在为 ROSE 模块(分析、转换、优化等)编写设计文档时应遵循的初始规则。

(我们感谢 维韦克·萨卡尔教授莱斯大学 对一些初始设计文档要求提出的富有洞察力的意见。)

  • 所有新的 ROSE 分析、转换和优化必须有相应的同行评审设计文档,然后才能开始实际实现。
  • 要足够具体,以便具有 ROSE 技能但不是原始设计人员的人员(原则上)只需查看文档即可实现设计。
  • 预计不同的开发人员将在数据结构等方面做出不同的底层选择

需求与设计文档

[编辑 | 编辑源代码]

如果需求文档是软件的“为什么”,那么技术设计文档就是“如何”。为简单起见,我们目前将需求和设计都放在一个文档中。如果需要,我们可以提供单独的需求分析文档。

编写技术设计文档的目的是指导开发人员实现(并满足)软件的需求,它是软件的蓝图。

文档必须

  • 用 LaTex 编写,以便在出版物和提案中重复使用。
  • 存储在版本控制下,以支持协作编写。

您的文档至少应包含以下正式部分

  • 标题页
  • 作者信息:参与主要写作的人员
  • 审阅者信息:审阅和批准文档的人员
  • 目录
  • 页码格式
  • 节号
  • 修订历史

主要部分

  • 概述
    • 解释模块的动机和目标:该模块做什么、目标、要解决的问题等。
  • 需求分析:该模块需要什么
    • 定义接口:命名空间、函数名称、参数、返回值。其他人如何调用此模块并获取返回值
    • 性能要求:时间和空间复杂度
    • 输入/测试代码范围:要支持哪些类型的语言、要支持的语言结构、要使用的基准
  • 设计注意事项
    • 假设
    • 约束
    • 权衡和局限性:为什么要使用该算法、优先级是什么等。
    • 非标准元素:文档中任何非标准符号、形状、缩略词和独特术语的定义
    • 计划:如何实现每个要求
  • 内部软件工作流程
    • 图表:逻辑结构和逻辑处理步骤:必须有 UML 图或 PowerPoint 图
    • 伪代码:必须有伪代码来描述关键数据结构和高级算法步骤
    • 示例:必须使用至少一个示例输入代码来说明设计的算法,以贯穿算法的重要中间结果。
    • 错误、警报和警告消息(可选)
  • 性能:必须进行复杂度分析。估计该模块的时间和空间复杂度,以便用户可以了解预期结果
  • 可靠性(可选)
  • 相关工作:引用教科书和论文中的相关工作

开发指南

[编辑 | 编辑源代码]
  • 编码指南:标准和约定。
  • 标准语言和工具
  • 变量定义和使用位置说明

参考资料

[编辑 | 编辑源代码]

待办事项

[编辑 | 编辑源代码]
  • 一个示例设计文档

规则

  • 所有贡献必须附带相应的测试翻译器和输入文件,以证明贡献按预期工作。
  • 所有测试必须由“make check”规则触发
  • 所有测试应具有自我验证功能,以确保生成正确的结果
  • 所有测试必须由 Jenkins 的至少一个集成测试激活(用于检查是否可以将某些内容合并到我们中心存储库的主分支中的测试作业)
    • 这将确保将来没有提交可以破坏您的贡献。

编程语言

[编辑 | 编辑源代码]

核心语言

[编辑 | 编辑源代码]

只允许使用 C++。任何其他编程语言都是个案 basis 上的例外。

问题:但是编程语言 XYZ 比 C++ 好多了,而且我真的擅长 XYZ!!!

答案:我们只允许 XYZ,如果

  • 您可以教我们团队中至少一只老狗(工作人员)新的技巧,以有效地使用 XYZ
  • 您将在未来 5 到 10 年内留在我们的团队中,以维护所有用 XYZ 编写的代码,如果老狗都没有时间/兴趣切换到 XYZ
  • 您可以证明 XYZ 可以与 ROSE 中现有的 C++ 代码良好交互

脚本语言

[编辑 | 编辑源代码]

只允许使用两种脚本语言

  • bash shell 脚本
  • perl

再次强调,这只是工作人员的偏好,以及我们目前的情况。在一个项目中允许不受控制的脚本语言数量,将使项目无法维护,难以学习。

命名规范

[编辑 | 编辑源代码]

子部分的顺序反映了开发周期中添加内容的从上到下的方法:从目录 --> 文件 --> 命名空间 --> 等。

  • 语言:所有名称都应该用英语编写,因为英语是国际上开发的首选语言。
  • fileName; // NOT: filNavn

缩写和首字母缩略词

[编辑 | 编辑源代码]

避免使用含糊不清的缩写:在用户清晰度和生产力之间取得良好的平衡。

缩写和首字母缩略词用作名称时不应大写。

  • exportHtmlSource(); // NOT: exportHTMLSource();
  • openDvdPlayer(); // NOT: openDVDPlayer();

同样,常见的全小写缩写和首字母缩略词在 CamelCase 名称中不应以小写字母开头。

  • SgAsmX86Instruction // NOT: SgAsmx86Instruction
  • myIpod // NOT: myiPod

文件/目录

[编辑 | 编辑源代码]

大小写:

  • camelCasefileName.hpp: 这与 ROSE 中使用的现有名称一致

文件扩展名:

  • 头文件.h或者.hpp
  • 源文件.cpp或者.cxx
    • .C应避免使用,以与不区分大小写的文件系统协同工作。

命名空间

[编辑 | 编辑源代码]
  • 命名空间应该表示一个逻辑单元,通常封装在特定目录中的单个头文件中。
  • 命名空间使用CamelCase,例如 SageInterface、SageBuilder 等。
    • 避免使用小写名称,错误名称:sage_interface
  • 在命名空间名称中使用名词单数,避免使用复数。
  • 使用完整词语,避免使用缩写。
  • 使用至少两个词语以减少名称冲突。

原因:命名空间的命名规范旨在与现有代码兼容,并与命名空间内的函数名称一致。

  • CamelCase 命名空间可以很好地与 doSomething() 结合使用,例如:NameSpace::doSomething()
  • 小写命名空间名称可能看起来不一致,例如 name_space_1::doSomething()
  • ROSE 中许多现有的命名空间已经遵循 CamelCase,如 链接 所示。

[注意] Leo:我相信这应该与 ROSE 编译器框架/ROSE API 进一步讨论。

必须使用以大写字母开头的混合大小写,如SavingsAccount

  • 长度:范围较大的变量应具有较长的名称,范围较小的变量可以具有较短的名称。
  • 临时变量用于临时存储(例如循环索引)最好保持简短。阅读此类变量的程序员应该能够假设它的值在几行代码之外不会被使用。整数的常见临时变量是i, j, k, m, n. 或者,可以使用 ii、jj、kk、mm 和 nn,这些在查找索引错误时更容易突出显示。
  • 大小写: camelCase--以小写字母开头的混合大小写,如functionDecl
    • 变量故意以小写字母开头,而类型则以大写字母开头。因此,通过查看第一个字母可以清楚地知道名称是变量还是类型。

布尔值

[编辑 | 编辑源代码]

必须避免使用否定布尔变量名。当这样的名称与逻辑否定运算符一起使用时,就会出现问题,因为这会导致双重否定。!isNotFound 的含义并不立即明了。

bool isError; // NOT: isNoError
bool isFound; // NOT: isNotFound

表示对象集合的名称应使用复数形式。这增强了可读性,因为名称为用户提供了关于变量类型和可以在其元素上执行的操作的直接线索。

例如,

vector<Point> points;
int values[];

命名常量(包括枚举值):必须全部大写,使用下划线分隔单词。

例如

int MAX_ITERATIONS, COLOR_RED;
double PI;

一般来说,应尽量减少使用此类常量。在许多情况下,将值实现为方法是更好的选择。

int getMaxIterations() // NOT: MAX_ITERATIONS = 25
{
    return 25;
}

泛型变量应与其类型具有相同的名称。这通过减少使用术语和名称的数量来降低复杂性。此外,仅给定变量名称,也可以轻松推断出类型。如果出于某种原因,此约定似乎不适用,则强烈表明类型名称选择不当。

void setTopic(Topic* topic) // NOT: void setTopic(Topic* value)
                            // NOT: void setTopic(Topic* aTopic)
                            // NOT: void setTopic(Topic* t) 

void connect(Database* database) // NOT: void connect(Database* db)
                                 // NOT: void connect (Database* oracleDB)

非泛型变量具有作用。这些变量通常可以通过结合作用和类型来命名。

Point  startingPoint, centerPoint;
Name   loginName;

全局变量

[编辑 | 编辑源代码]

必须始终使用限定名,使用范围解析运算符::.

例如,::mainWindow.open()::applicationContext.getName()

一般来说,应避免使用全局变量。相反,

  • 将变量放入命名空间
  • 使用单例对象

私有类变量

[编辑 | 编辑源代码]

私有类变量应具有下划线后缀。除了名称和类型之外,变量最重要的特征是范围。通过使用下划线来指示类范围,可以轻松地区分类变量和局部临时变量。

例如,

class SomeClass {
  private:
    int length_;
}

一个问题是下划线应该添加为前缀还是后缀。两种做法都很常见,但建议使用后者,因为它似乎最能保留名称的可读性。下划线命名约定的一个副作用是,它很好地解决了为 setter 方法和构造函数找到合理的变量名称的问题。

  void setDepth (int depth)
  {
    depth_ = depth;
  }

方法和函数

[编辑 | 编辑源代码]

表示方法或函数的名称:必须是动词,并以混合大小写编写,以小写字母开头,以指示它们返回的内容和过程(空方法)之后它们所做的事情。

  • 例如 getName()、computeTotalWidth()、isEmpty()

方法名应避免重复对象名

  • 例如 line.getLength(); // NOT: line.getLineLength();

后者在类声明中看起来很自然,但在使用中却显得多余,如示例所示。

术语getset必须用于直接访问属性的地方。

  • 例如:employee.getName(); employee.setName(name); matrix.getElement(2, 4); matrix.setElement(2, 4, value);

术语compute可以在计算某个东西的方法中使用。

  • 例如:valueSet->computeAverage(); matrix->computeInverse()

为读者提供立即的线索,表明这是一个可能非常耗时的操作,如果反复使用,他可能会考虑缓存结果。一致地使用该术语可以增强可读性。

术语find可以在查找某个东西的方法中使用。

  • 例如:vertex.findNearestVertex(); matrix.findMinElement();

为读者提供立即的线索,表明这是一个简单的查找方法,涉及最少的计算。一致地使用该术语可以增强可读性。

术语initialize可以在建立对象或概念的地方使用。

  • 例如:printer.initializeFontSet();

应优先使用美式英语的 initialize,而不是英式英语的 initialise。应避免使用缩写 init。

对于布尔变量和方法,应该使用前缀is

  • 例如:isSet、isVisible、isFinished、isFound、isOpen

在某些情况下,有一些替代前缀比is更适合。这些前缀是hascanshould

  • bool hasLicense();
  • bool canEvaluate();
  • bool shouldSort();

参数之间应该用单个空格隔开,参数列表中不应包含前导空格或尾随空格。

  • YESvoid foo(int x, int y)
  • NOvoid foo ( int x,int y )

目录

[edit | edit source]

命名约定

[edit | edit source]

常用名称列表

  • src: 用于放置源文件、头文件
  • include: 如果头文件很多,不想将它们都放在 ./src 中,可以使用此目录来放置头文件。
  • tests: 用于放置测试输入
  • docs: 放置 README 中未涵盖的详细文档

请使用 camelCase 命名目录。

  • 应避免以大写字母开头。

首选名称示例

  • roseExtensions
  • roseSupport
  • roseAPI

要避免的名称

  • rose_api
  • rose_support

布局

[edit | edit source]

TODO:有关在 ROSE git 存储库中放置内容的总体情况。


对于 ./projects 下的每个项目目录,我们的约定是为不同的文件创建子目录。

  • README:必须要有此文件
  • ./src:用于放置所有源文件
  • ./include:如果不想将所有头文件都放在 ./src 中,可以使用此目录来放置头文件。
  • ./tests:用于放置测试输入文件
  • ./doc:如果 README 不够,可以使用此目录来放置更详细的文档

文件

[edit | edit source]

单个文件应该包含一个逻辑单元或功能。保持模块化!

命名规范

[edit | edit source]

文件名应该具体且描述性地说明其内容。

应该使用 camelCase(开头使用小写字母)。

  • 好的示例:fileName.h

要避免的名称

  • 以大写字母开头,
  • 使用下划线的错误示例:file_name.h

错误的文件名

  • functions.h
  • file_name.h

参考资料

行长

[edit | edit source]
  • 文件内容应该保持在 80 列以内。

80 列是编辑器、终端模拟器、打印机和调试器常用的尺寸,在多人之间共享的文件应该保持在这些限制内。避免无意间换行,在程序员之间传递文件时,这可以提高可读性。如果编写了一个超过 80 列的教程,它很可能无法在一页上显示。如果没有查看代码库本身,这实际上会使教程变得毫无用处。

缩进

[edit | edit source]

除了需要使用制表符(\t)的情况(例如,Makefile),应避免使用制表符进行代码缩进。

建议使用 2 或 4 个空格进行代码缩进。

for (i = 0; i < nElements; i++) 
  a[i] = 0;

1 个空格的缩进过小,无法强调代码的逻辑布局。超过 4 个空格的缩进会使嵌套过深的代码难以阅读,并增加代码行需要拆分的可能性。

字符

[edit | edit source]
  • 必须避免使用 TAB 和分页符等特殊字符。

在多人、多平台的环境中使用这些字符会导致编辑器、打印机、终端模拟器或调试器出现问题。

我们已经内置了一个 perl 脚本来执行此策略。

头文件

[edit | edit source]

文件名

  • 必须使用 camelCase:例如 fileName.h 或 fileName.hpp
  • 避免使用 file_name.h

后缀

  • 对于 C 头文件:使用.h
  • 对于 C++ 头文件:使用.h或者.hpp

必须要有

  • 受保护的预处理指令以防止头文件被包含多次,例如
#ifndef _HEADER_FILE_X_H_
#define _HEADER_FILE_X_H_

#endif //_HEADER_FILE_X_H_
  • 尝试将变量、函数、类放在描述性的命名空间中。
  • 包含语句必须仅位于文件顶部。
    • 通过源文件深处的“隐藏”包含语句来避免不必要的编译副作用。

在头文件中要避免的

  • 全局变量、函数或类 ; // 它们会污染全局范围
  • using namespace std;
    • 这将污染包含此头文件的每个 .cpp 文件的全局范围。using namespace 仅应由 .cpp 文件使用。有关更多说明,请访问 linklink2
  • 函数定义
    • 头文件用于公开类型和函数接口。它们将被多个 cpp 文件包含。头文件中的函数定义将在编译包含它的多个 cpp 文件时导致重复定义错误。


参考资料

源文件

[edit | edit source]

同样,文件名应遵循命名约定

  • camelCase 文件名:例如 sageInterface.cpp
  • 避免使用大写字母、空格和特殊字符

首选后缀

  • 使用.c用于 C 源文件
  • 使用.cpp或者.cxx用于 C++ 源文件

要避免的名称

  • 大写.C用于源文件。这将在将 ROSE 移植到不区分大小写的文件系统时导致一些问题。

参考资料

README

[edit | edit source]

ROSE git 存储库中的所有主要目录都应该有一个 README 文件

  • projects/projectXYZ 必须有一个 README 文件。

文件名应为 README

要避免的

  • README.txt
  • readme

必需内容

[edit | edit source]

对于 ROSE 中的所有主要目录,应该有一个 README 文件,解释

  • 此目录中包含什么
  • 此目录实现了什么功能
  • 谁添加了它以及添加时间

每个项目目录都必须有一个 README 文件,解释

  • 此项目是关于什么的
    • 项目名称
    • 动机:为什么我们要创建这个项目
    • 目标:我们想要实现什么
  • 设计/实现:以便下一个人能够快速了解并为这个项目做出贡献
    • 我们如何设计/实现它。
    • 主要算法是什么
  • 关于如何使用该项目的简要说明
    • 安装
    • 测试
    • 或指出在哪里可以找到完整的文档
  • 状态
    • 什么有效
    • 什么无效
  • 已知限制
  • 参考文献和引用:用于底层算法
  • 作者和日期

格式

[edit | edit source]

README 格式

  • 文本格式,包含清晰的章节和项目符号
  • 可选地,可以使用由 w:Markdown 定义的样式

示例

[edit | edit source]

可以在以下位置找到一个 README 示例

源代码文档

[edit | edit source]

ROSE 的 源代码 使用 Doxygen 文档系统 进行 文档化

一般准则

[编辑 | 编辑源代码]
  • 仅限英文
  • 使用有效的 Doxygen 语法(参见下面的“示例”)
  • 使代码对第一次阅读代码的人来说易于理解
    • 记录关键概念、算法和功能
    • 涵盖您的项目、文件、类/命名空间、函数和变量。
    • 明确说明您的输入和输出,特别是输入或输出的含义
      • 如果用户不必考虑输出的含义或输入应该是什么,他们更有可能使用您的代码
    • 聪明通常等同于混淆,在编码中避免这种形式的聪明。

TODO,尚未准备好

  • 测试您的文档,方法是在您的机器上生成它,然后手动检查它以确认它的正确性

TODO:生成本地文档

这有时不起作用,因为我们有一个配置文件来指示要扫描哪些目录以生成 Web 参考 HTML 文件

  $ make doxygen_docs -C ${ROSE_BUILD}/docs/Rose/

使用 //TODO

[编辑 | 编辑源代码]

这是改进代码注释的推荐方法。

在进行增量开发时,通常会有一些您决定在下次迭代中做的事情,或者您知道当前的实现/功能有一些限制,需要在将来修复。

一个好方法是在您做出这种决定时立即将 TODO 源注释(// TODO blar blar ..)放入相关代码中,这样您就不会忘记下次要做什么。

TODO 还可以作为代码中的一些便捷标志,供其他人使用,如果他们想在您离开后改进您的工作。

通常一个简短的单行注释就足够了

//! Brief description.

Doxygen 支持具有多行的注释。

/**
 
   ... text..
 
 */

/**
 *
 *  ... text..
 *
 */


/*******************************//**
 *         text
*********************************/

/////////////////////////////////////
///  ... text <= 80 columns in length
//////////////////////////////////////

组合单行和多行

[编辑 | 编辑源代码]

Doxygen 可以为函数生成简短的注释,并且可以选择在用户单击函数时显示详细注释。

以下是一些支持组合单行和多行源注释的选项。

选项 1:

/**
 * \brief Brief description.
 *        Brief description continued.
 *
 * [Optional detailed description starts here.]
 */

选项 2:

/**
 \brief Brief description.
        Brief description continued.
 
 [Optional detailed description starts here.]
 */

---

单行注释后跟多行注释':

您可以使用多行注释(选项 1 或 2)扩展现有的单行注释。例如

//! Brief description.
/**
 * Detailed description starts here.
 */


TODO:提供完整的组合示例。

规则

  • 除了简单的函数(如 getXX() 和 setXX())之外,所有其他函数都应该至少有一个行注释来解释它做了什么
  • 避免使用全局函数和全局变量。尝试将它们放入命名空间中。
  • 一个函数不应该超过 100 行代码。请将大型函数重构为更小的独立函数。
  • 限制无条件的 printf(),这样您的翻译器在处理多个输入文件时就不会打印数百行不必要的文本输出
    • 使用 if 条件控制 printf(),用于调试目的,例如“ if ( SgProject::get_verbose() > 0 ) ”
  • 函数的开头部分应该尝试对函数参数进行健全性检查。

规则

  • 请遵循 Doxygen 风格注释
  • 请详细解释您的函数如何工作以及算法中的步骤。
    • 审阅者将阅读您注释的信息以了解您的算法,然后阅读您的代码以查看代码是否正确且高效地实现了算法。

正确实现设计/记录的算法。未来的用户将没有时间直接阅读您的代码来辨别它的功能。

代码在时间和空间(内存)复杂度方面都应该是有效的。

请注意,您的翻译器可能处理数千条语句,甚至更多 AST 节点。

请注意,除了您之外的人员也可能使用您的代码或进一步开发它。请尽可能地做到这一点。

尽可能使用命名空间,避免使用全局变量或类。

名称等于功能

[编辑 | 编辑源代码]

以它是什么来命名类。如果您想不出它是什么,这是一个线索,说明您对设计思考得还不够深入。

  • 类名应该是一个名词。

超过三个词的复合名词是一个线索,说明您的设计可能在系统中混淆了各种实体。重新审视您的设计。尝试使用 CRC 卡会议来查看您的对象是否承担了比应有的更多职责。

显式访问

[编辑 | 编辑源代码]

所有部分(public、protected、private)都应该被明确地识别。不适用的部分应该被省略。

公共成员优先

[编辑 | 编辑源代码]

类的各个部分应该按 public、protected 和 private 的顺序排序。

排序方式是“最公共的成员优先”,因此只需要使用该类的人员在到达 protected/private 部分时就可以停止阅读。

类变量

[编辑 | 编辑源代码]

类变量不应声明为 public。

公共变量违反了 C++ 信息隐藏和封装的概念。请改用私有变量和访问函数。此规则的一个例外是,当该类本质上是一个数据结构时,没有行为(等效于 C 结构)。在这种情况下,将类的实例变量设为 public 是合适的。

避免使用结构体

[编辑 | 编辑源代码]

结构体在 C++ 中保留是为了与 C 保持兼容,避免使用它们可以减少使用的结构数量,从而提高代码的可读性。请改用类。

for() 结构中只能包含循环控制语句,其他内容不允许。

//Correct
sum = 0; 
for (i = 0; i < 100; i++) 
  sum += value[i]; sum += value[i];

//Incorrect
 for (i = 0, sum = 0; i < 100; i++) 

这提高了可维护性和可读性。它还允许未来的开发人员清楚地区分控制内容和循环中的内容。

循环变量应该在循环之前立即初始化。

类型转换

[编辑 | 编辑源代码]

类型转换必须始终显式完成。永远不要依赖隐式类型转换。

  //Correct
  floatValue = static_cast<float>(intValue); 
  //Incorrect 
  floatValue = intValue;

通过这种方式,程序员表明他了解所涉及的不同类型,并且混合是故意的。

条件语句

[编辑 | 编辑源代码]

条件语句的语句体必须放在单独的一行。

 if (isDone) 
 // NOT: if (isDone) doCleanup(); doCleanup();

这是为了调试目的。当在一行中写代码时,无法直观地判断测试结果是否真的为真。

关键字if与条件语句之间必须有一个空格(isDone).

if (isDone)
  ^ space

避免使用复杂的条件表达式。必须引入临时布尔变量

//recommended way
bool isFinished = (elementNo < 0) || (elementNo > maxElement); 
bool isRepeatedEntry = elementNo == lastElement; 
if (isFinished || isRepeatedEntry) { : } 

// NOT: if ((elementNo < 0) || (elementNo > maxElement)|| elementNo == lastElement) { : }

通过将布尔变量赋值给表达式,程序获得了自动文档。构造将更容易阅读、调试和维护。当变量命名得当时,它还有助于未来的开发人员理解构造的每个部分所完成的功能。

printf 和 cout

[编辑 | 编辑源代码]

所有屏幕输出必须放在 if 语句中,以条件方式执行,无论是通过详细级别还是其他调试选项。

它们默认情况下不得打印信息。

TODO:这可以在将来通过一个简单的 Compass 检查器来强制执行。

仔细区分

  • 已知允许忽略的内容和
  • 当前实现尚未处理的内容。
  switch(type->variantT())
 {
    case V_SgTypeDouble:
      {
        ...
      }
      break;
    case V_SgTypeInt:
      {
        ...
      }
      break;
   case V_SgTypeFloat: // things which are known to be allowed to be ignored.
      break;
   default:
    {
     //Things which are not yet explicitly handled
      cerr<<"warning, unhandled node type: "<< type->class_name()<<endl;
    }

鼓励经常使用 assert 来明确表达和保证代码中使用的假设。

请使用 ROSE_ASSERT() 或 assert()。

对于每个断言的出现,必须添加一个 printf 或 cerr 消息以指示代码中的位置以及出错的地方,以便用户可以立即了解断言失败的原因,而无需通过调试器来找出错误原因。

应避免的语句

[编辑 | 编辑源代码]

以下语句通常应该避免

  • 不应使用 goto 语句。goto 语句违反了结构化代码的理念。只有极少数情况下(例如从深层嵌套结构中跳出)应该考虑使用 goto,而且只有当等效的结构化对应项可读性较差时。
  • 应避免条件语句中的可执行语句。带有可执行语句的条件语句非常难以阅读。
  File* fileHandle = open(fileName, "w"); 
  if (!fileHandle) { : } 
  // NOT: if (!(fileHandle = open(fileName, "w"))) { : }

表达式

[编辑 | 编辑源代码]

可读性、简单性和可调试性的指南。

  • 三元运算符 (?:) 应替换为 if/else。
  • 长表达式应分解为几个更简单的语句。为沿过程获得的每个指针值添加断言以帮助以后调试。
  • 应将运算符优先级、短路求值、赋值表达式等的巧妙使用重写为易于理解的替代形式。
  • 始终记住,未来的程序员会欣赏清晰简单的代码,而不是模糊的聪明技巧。

AST 转换器

[编辑 | 编辑源代码]

所有基于 ROSE 的转换器都应在完成所有转换后调用 AstTests::runAllTests(project) 以确保转换后的 AST 是正确的。

这比仅仅正确地反解析为可编译代码具有更高的标准。AST 经常会正确地通过反解析但无法通过健全性检查。

更多信息请参见 健全性检查

参考资料

[编辑 | 编辑源代码]

我们列出了一些外部资源,这些资源对我们定义 ROSE 的编码标准具有影响

华夏公益教科书