QGIS二次开发基础 — 编码规范

这篇文档是QGIS官方的编码规范说明,对于新手来说,应该可以有一些指引作用,知道如何编码是比较规范的。对于老手来说,掌握QGIS源码规范更有助于阅读理解代码。

当一回搬运工,帮大家翻译一下。

类(Class)

名称

QGIS中的类名称以“Qgs”开头,使用CamelCase命名规范
例如:

  • QgsPoint
  • QgsMapCanvas
  • QgsRasterLayer

成员

类中的成员变量以一个小写字母‘m’开头,使用混合命名规范
例如:

  • mMapCanvas
  • mCurrentExtent

所有的成员应该为私有类型(Private),非常不推荐 公有类型(Public)的成员变量。
当成员需要通过Python子类使用的时候,保护(Protected)类型的成员应该避免,因为保护类型的成员无法被Python的插件调用。
可变静态类的成员名称应该以小写字母 ‘s’ 开头,但常量静态类成员名称应该全部大写:

  • sRefCounter
  • DEFAULT_QUEUE_SIZE

存取器方法(Accessor Functions)

类成员的值应该通过存取器方法设置,并且该方法的命名不应该包含 ‘get’ 前缀。两个私有变量的存取器方法命名举例如下:

  • mapCanvas()
  • currentExtent()

保证存取器方法标记为 ‘const’, 缓存类型的变量则标记为 ‘mutable’。

方法

方法名称以小写字母开头,使用混合命名规范。方法名应该能够传达方法目的:

  • updateMapExtent()
  • setUserOptions()

为了和Qt的API保持一致性,应当避免使用简称。例如使用 ‘setDestinationSize’,而不是 ‘setDestSize’, 使用 ‘setMaximumValue’, 而不是 ‘setMaxVal’。
缩写也应该使用CamelCase命名规范。例如使用 ‘setXml’,而不是 ‘setXML’。

方法参数

方法参数应该使用描述性的名称。一定不要使用单字符参数名(例如:使用 ‘setColor( const QColor& color )’,而不是 ‘setColor( const QColor& c )’)。
尤其注意当参数应该以引用传递的时候,除非参数对象非常小可以被频繁的复制(就像 QPoint 对象),参数对象应该以 const 引用进行传递。为了和 Qt API 保持一致,甚至隐式公用的对象也应该以 const 引用传递,例如, 使用 ‘setTitle( const QString& title )’, 而不是 ‘setTitle( QString title )’

方法返回值

将比较小可以频繁复制的对象返回为值类型,比较大的对象应该返回为 const 引用类型。这里有一个例外,那就是隐式公用对象总是返回值类型。

  • int maximumValue() const
  • const LayerSet& layers() const
  • QString title() const (QString就是隐式公用对象)
  • QList< QgsMapLayer* > layers() const (QList也是隐式公用对象)

Qt Designer

生成的类

从 Qt Designer (ui文件) 生成的 QGIS 类应该有一个 ‘Base’ 后缀,表明这是生成的基类。
例如:

  • QgsPluginManagerBase
  • QgsUserOptionBase

对话框

所有的对话框应该为涉及到的所有工具栏图标和其他相关的控件实现工具帮助说明(tooltip help)。工具帮助说明对于新手和有经验的用户同样重要。

C++文件

名称

C++的实现文件和头文件应该分别具有 ‘.cpp’ 和 ‘.h’后缀名。文件名应该全部使用小写,并且如果是类文件,文件名应该和类名称一致。
例如:QgsFeatureAttribute 的源代码文件名称应该分别是 ‘qgsfeatureattribute.cpp’ 以及 ‘qgsfeatureattribute.h’。

注意:
为了防止上面的说明不够清晰,对于一个文件名要与它的类名称匹配的说法,就是指每个类应该在它自己单独的文件中进行声明和实现。这样会让新手更容易找到类相关的代码。

标准文件头和许可

每个源代码文件应该包含一个文件头区域,以如下例子的样式填写:

/***************************************************************************
  qgsfield.cpp - Describes a field in a layer or table
  --------------------------------------
  Date : 01-Jan-2004
  Copyright: (C) 2004 by Gary E.Sherman
  Email: sherman at mrcc.com
/***************************************************************************
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 ***************************************************************************/

注意:
这里有一个 Qt Creator模板在 git 仓库里面,要使用它,只需要将它从 ‘doc/qt_creator_license_template’ 拷贝到本地,如果需要的话,修改邮件地址和名称即可。配置 QtCreator 使用模板的方法:Tools -> Options -> C++ -> File Naming。

变量名称

本地变量名称以小写字母开头,采用混合命名方式。千万不要使用像 ‘my’ 或者 ‘the’ 的前缀进行命名。
例如:

  • mapCanvas
  • currentExtent

枚举类型

枚举类型应该以 CamelCase 命名,首字母大写。例如:

enum UnitType
{
  Meters,
  Feet,
  Degrees,
  UnknownUnit
};

千万不要使用太通用的类型名称,否则会与其他类型冲突。例如:使用 ‘UnkownUnit’,而不是 ‘Unknown’。

全局常量 & 宏

全局常量和宏应该以大写字母命名,以下划线分割,例如:

const long GEOCRS_ID = 3344;
  • 1

Qt信号和槽

所有信号/槽连接应该使用 Qt5 中的新样式。关于这个更详细的信息在 QEP #77 中可以查到。

避免使用 Qt 自动连接的槽(例如,命名为 ‘on_mSpinBox_valueChanged’ 的槽)。自动连接的槽在窗口重构的时候会更容易出错,而且没有任何警告信息。

编辑

所有文字编辑器或者 IDE 都编辑 QGIS 的代码,只要满足如下要求即可。

制表符

设置编辑器的制表符为2个空格字符代替。

注意:
在 Vim 中,设置 ‘expandtab ts = 2’ 即可实现。

缩进

源代码应该具有缩进,这样更便于阅读。这里有一个脚本文件在 ‘scripts/prepare-commit.sh’路径下,这个脚本会查找变化的文件,同时使用 Astyle 将它们进行重新缩进。这个脚本文件应该在提交代码之前执行一次。你也可以使用 ‘scripts/astyle.sh’脚本来缩进单独的文件。

因为新的 Astyle 版本中缩进与源码缩进使用的版本不同,这个脚本文件仍然采用较老的 Astyle 版本,已经包含在源码仓库中了(在 CMake 中使用 WITH_ASTYLE 选项包含它)。

括号

括号应该另起一行,例如:

if(foo == 1)
{
  // do stuff
  ...
}
else
{
  // do something else
  ...
}

API兼容

QGIS 提供了 C++ 的 API 文档。

开发者努力让 API 稳定并且兼容老版本。就像 Qt 的源码文件一样,API 说明应该清晰、详尽,例如:

class Foo
{
  public:
    /** This method will be deprecated, you are encouraged to use
     *  doSomethingBetter() rather.
     *  @deprecated doSomethingBetter()
     */
    Q_DECL_DEPRECATED bool doSomething();

    /** Does something a better way.
     *  @note added in 1.1
     */
    bool doSomethingBetter();

  signals:
    /** This signal will be deprecated, you are encouraged to
     *  connect to somethingHappenedBetter() rather.
     * @deprecated use somethingHappenedBetter()
     */
#ifndef Q_MOC_RUN
    Q_DECL_DEPRECATED
#endif
    bool somethingHappened();

    /** Something happened
     *  @note added in 1.1
     */
    bool somethingHappenedBetter();
}

编码风格

这里是一些编程的提示和技巧,希望能够减少编码错误、编程时间和维护成本。

只要可能,生成代码

如果你经常使用复制/剪切粘贴代码,或者经常写同样的代码,考虑一下将这段代码封装进一个函数里面。
这可以带来如下的好处:

  • 如果要修改,只需要修改一个地方就可以了,其他调用的地方会跟着改变
  • 有助于防止代码冗余
  • 避免多个代码拷贝,减轻阅读和维护的难度

将常量放在比较判断语句的前面

例如,使用

0 == value
  •  

而不是

value == 0
  •  

这样有助于减少程序员不小心使用 ‘=’ 而不是 ‘==’ 来做比较的概率,避免一些潜在的逻辑漏洞。如果将常量放在前面,并且使用了 ‘=’ 来比较的话,编译器会生成一个错误,因为常量是不允许赋值的。

空格字符是你的朋友

在运算符、语句和方法之间添加空格会让你的代码更容易被阅读和理解。

例如,相比之下,下面的代码

if (!a&&b)
  •  

当然比

if ( ! a && b )
  •  

更加容易阅读。

注意:
‘scripts/prepare-commit.sh’ 脚本会自动完成这个操作。

将命令放在单独的行中

在阅读代码的时候,如果命令不在行首开头的话,很容易被忽略掉,例如:

if (foo) bar();

baz(); bar();

这段代码很容易看漏 if 后面的 bar() 函数调用,而

if (foo)
  bar();

baz();
bar();

就会好很多。

将访问修饰符缩进

访问修饰符将一个类结构化为不同的 API 区域,包括公有(public) API,保护(protected) API,私有(private) API等。访问修饰符自身就起到了代码分组的作用,缩进访问修饰符和声明。

class QgsStructure
{
  public:
    /**
     * Constructor
     */
     explicit QgsStructure();
}

推荐书单

  • 《Effective Modern C++》, Scott Meyers
  • 《More Effective C++》, Scott Meyers
  • 《Effective STL》, Scott Meyers
  • 《Design Patterns》, GoF

同时,强烈建议阅读 Qt Quarterly 关于 designing Qt style (APIs) 的文章。

代码贡献信用

建议新方法的贡献者通过如下的方法,让别人知道自己的贡献所在:

  • 在 ChangeLog 中添加一个注释,说明这段代码的第一个版本出处,使用这种方式:
This feature was funded by: Olmiomland http://olmiomland.ol
This feature was developed by: Chuck Norris http://chucknorris.kr

© 版权声明
THE END
喜欢就支持一下吧
点赞785 分享
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

取消
昵称表情代码图片

    暂无评论内容