zoukankan      html  css  js  c++  java
  • Qt的编程风格与规范

    参考资料:

    1. https://wiki.qt.io/Qt_Contribution_Guidelines
    2. https://wiki.qt.io/Qt_Coding_Style
    3. https://wiki.qt.io/Coding_Conventions
    4. https://community.kde.org/Policies/Library_Code_Policy
    5. https://wiki.qt.io/UI_Text_Conventions
    6. https://wiki.qt.io/API_Design_Principles
    7. http://doc.qt.io/qt-5/qml-codingconventions.html
    8. https://google.github.io/styleguide/cppguide.html

    变量声明

    • 声明每一个变量都要用独立的一行
    • 避免短的或无意义的命名
    • 单个字符的变量名只适用于用来计数的临时变量,因为此时该变量的用途十分明显
    • 当一个变量被用到时再声明它
     // Wrong
     int a, b;
     char *c, *d;
    
     // Correct
     int height;
     int width;
     char *nameOfThis;
     char *nameOfThat;
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    变量一般命名法

    • 变量名和函数名以小写字母开头,开头之后的部分每个单词以大写字母开头
    • 避免使用缩写
    // Wrong
     short Cntr;
     char ITEM_DELIM = ' ';
    
     // Correct
     short counter;
     char itemDelimiter = ' ';
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    变量在Qt中的命名

    • 类名以大写字母开头,公开类以Q开头,紧跟大写字母;公用函数以q开头。(此为Qt内部规范,我们可不遵守)
    • 首字母缩写词出现在命名中,采用驼峰命名法,如QXmlStreamReader,而不是QXMLStreamReader(即只有第一个字母大写)

    空白行与空格的使用

    • 用空行在适当的地方划分代码块
    • 总是只用一个空行
    • 在关键词和花括号之间总是只用一个空格符
     // Wrong
     if(foo){
     }
    
     // Correct
     if (foo) {
     }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    指针的书写规范

    • 对于指针或引用,在类型名和或&之间用一个空格,但是在或&和变量名之间没有空格
     char *x;
     const QString &myString;
     const char * const y = "hello";
    • 1
    • 2
    • 3

    二元操作符

    • 二元操作符的左右都要有空格
    • 二元操作符对待它的两个参数是同样对待的,只是在该操作符是类外的操作符
    • 例如QLineF有它自己的==操作符
    QLineF lineF;
    QLine lineN;
    
    if (lineF == lineN) // Ok,  lineN is implicitly converted to QLineF
    if (lineN == lineF) // Error: QLineF cannot be converted implicitly to QLine, and the LHS is a member so no conversion applies
    • 1
    • 2
    • 3
    • 4
    • 5

    逗号

    • 逗号左边没有空格,逗号右边有一个空格
      #include <QApplication>
      #include <QMessageBox>
    
      int main(int argc, char *argv[])
      {
          QT_REQUIRE_VERSION(argc, argv, "4.0.2")
    
          QApplication app(argc, argv);
          ...
          return app.exec();
      }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    分号

    • 分号左边没有空格,分号作为语句的结束符,其右边一般不再有内容
      struct Point2D
      {
          int x;
          int y;
      };
    • 1
    • 2
    • 3
    • 4
    • 5

    井号

    • #号右边没有空格
      #include <QtGlobal>
    
      #if (QT_VERSION >= QT_VERSION_CHECK(5, 0, 0))
      #include <QtWidgets>
      #else 
      #include <QtGui>
      #endif
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    引号

    • 左引号的左边和右引号的右边都有一个空格,左引号的右边和右引号的左边都没有空格
    • 如果右引号右边是又括号的话,它们之间没有空格
    qDebug() << Q_FUNC_INFO << "was called with value1:" << value1 << "value2:" << value2;
    
    QT_REQUIRE_VERSION(argc, argv, "4.0.2")
    • 1
    • 2
    • 3

    cast

    • cast后无须空格
     // Wrong
     char* blockOfMemory = (char* ) malloc(data.size());
    
     // Correct
     char *blockOfMemory = reinterpret_cast<char *>(malloc(data.size()));
    • 1
    • 2
    • 3
    • 4
    • 5
    • 避免C语言的casts,尽量用C++的casts (static_cast, const_cast, reinterpret_cast)。 reinterpret_cast 和 C风格的cast用起来都是危险的,但至少 reinterpret_cast 不会把const修饰符去掉。
    • 涉及到QObjects或重构自己的代码时,不要使用dynamic_cast,而是用qobject_cast,例如在引进一个类型的方法时。
    • 用构造函数去cast简单类型,例如:用int(myFloat)代替(int)myFloat

    语句

    • 不要在一行写多条语句
    • 另起一行写控制流语句的定义
     // Wrong
     if (foo) bar();
    
     // Correct
     if (foo)
         bar();
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    花括号写法

    • 使用紧贴括号:左括号和语句的开头在同一行,如果右括号是紧跟在一个关键词之后的,则右括号和该关键词在同一行
     // Wrong
     if (codec)
     {
     }
     else
     {
     }
    
     // Correct
     if (codec) {
     } else {
     }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 例外:函数的实现和类的声明中,左括号总是在一行的开头
     static void foo(int g)
     {
         qDebug("foo: %i", g);
     }
    
     class Moo
     {
     };
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 当条件语句的执行部分多于一句的时候才使用花括号
     // Wrong
     if (address.isEmpty()) {
         return false;
     }
    
     for (int i = 0; i < 10; +''i) {
         qDebug("%i", i);
     }
    
     // Correct
     if (address.isEmpty())
         return false;
    
     for (int i = 0; i < 10;i)
         qDebug("%i", i);
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    花括号用途

    • 例外1:如果父语句占有多行,或经过多层封装,子语句要用到花括号
     // Correct
     if (address.isEmpty() || !isValid()
         || !codec) {
         return false;
     }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 例外2:对称原则:在if-else语句块中,如果if或else中的一个包含了多行,另一个为了对称性原则,也要用花括号
     // Wrong
     if (address.isEmpty())
         qDebug("empty!");
     else {
         qDebug("%s", qPrintable(address));
         it;
     }
    
     // Correct
     if (address.isEmpty()) {
         qDebug("empty!");
     } else {
         qDebug("%s", qPrintable(address));
         it;
     }
    
     // Wrong
     if (a)
         …
     else
         if (b)
             …
    
     // Correct
     if (a) {
         …
     } else {
         if (b)
             …
     }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 当条件语句的执行体是空语句的时候,用一个花括号
     // Wrong
     while (a);
    
     // Correct
     while (a) {}
    • 1
    • 2
    • 3
    • 4
    • 5

    圆括号

    • 圆括号用来给语句分组
     // Wrong
     if (a && b || c)
    
     // Correct
     if ((a && b) || c)
    
     // Wrong
     a + b & c
    
     // Correct
     (a + b) & c
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    Switch 语句

    • case标签和switch在同一列
    • 每一个case语句的末尾都要有一个break语句或return语句,除非因功能需要故意不加或另外一个case是紧跟上一个case的。
     switch (myEnum) {
     case Value1:
       doSomething();
       break;
     case Value2:
     case Value3:
       doSomethingElse();
       // fall through
     default:
       defaultHandling();
       break;
     }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    跳转语句

    • 包括:break, continue, return, and goto
    • 不要在跳转关键词后边加else
    // Wrong
     if (thisOrThat)
         return;
     else
         somethingElse();
    
     // Correct
     if (thisOrThat)
         return;
     somethingElse();
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 例外:如果这段代码是固有的对称结构,用else实现视觉上的对称也是可以的

    换行

    • 每行代码不多于100个字符;若有必要,用括号括起来
    • 逗号在行尾。操作符在新行的开头位置,这是因为编辑器过窄的话,操作符在行尾容易看不见
    • 换行时尽量避免行于行之间看起来参差不齐
     // Wrong
     if (longExpression +
         otherLongExpression +
         otherOtherLongExpression) {
     }
    
     // Correct
     if (longExpression
         + otherLongExpression
         + otherOtherLongExpression) {
     }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    一般例外与Artistic style选项

    • 一般例外:当严格遵守一条规范会让你的代码看起来很糟糕时,废弃这条规范
    • 用astyle格式化代码时的选项
    --style=kr 
    --indent=spaces=4 
    --align-pointer=name 
    --align-reference=name 
    --convert-tabs 
    --attach-namespaces
    --max-code-length=100 
    --max-instatement-indent=120 
    --pad-header
    --pad-oper
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 例如,你可以这样用以上的代码
        int foo = some_really_long_function_name(and_another_one_to_drive_the_point_home(
                first_argument, second_argument, third_arugment));
    • 1
    • 2

    C++特性

    • 不要使用异常处理
    • 不要使用运行时类型识别
    • 理智地使用模板,不要仅仅因为你会用就去用

    Qt源码中的规范

    • 所有代码都是ascii,使用者如果不确定的话,只可能是7字节
    • 每一个QObject的子类都必须有Q_OBJECT宏,即使这个类没用到信号或槽。否则qobject_cast将不能使用
    • 在connect语句中,使信号和槽的参数规范化(参看 QMetaObject::normalizedSignature),可以加快信号/槽的查找速度。可以使用qtrepotools/util/normalize规范已有代码

    包含头文件

    • 用#include
     #include <qstring.h> // Qt class
    
     #include <new> // STL stuff
    
     #include <limits.h> // system stuff
    • 1
    • 2
    • 3
    • 4
    • 5
    • 如果你想包含qplatforms.h,把它作为第一个被包含的头文件
    • 如果你想包含私有头文件,要十分小心。使用以下的语法而不用管whatever_p.h属于哪个模块在哪个文件目录下
     #include <private/whatever_p.h>
    • 1

    编译器/平台特定问题

    • 使用三目运算符 ?时要特别小心,如果每次的返回值的类型可能不一样的话,一些编译器会在运行时生成冲突的代码(此时编译器甚至不会报错)
     QString s;
     return condition ? s : "nothing"; // crash at runtime - QString vs. const char *
    • 1
    • 2
    • 要特别小心对齐问题。无论何时,当一个指针被cast后的对齐数是增加的时候,它都可能会崩溃。例如一个const char 被cast成了cons int,当cast之后的数字不得不在2或4个字节之间对齐时,指针就会在机器上崩溃。
    • 使用一个union强迫编译器正确地对齐变量,示例如下,你可以确定AlignHelper中所有的实例都和int边界对齐了
     union AlignHelper {
         char c;
         int i;
     };
    • 1
    • 2
    • 3
    • 4
    • 任何需要需要执行构造函数或相关代码进行初始化的实例,都不能用作库代码中的全局实例。因为当构造函数或代码将要运行的时候,该实例还没有被定义(在第一次调用该实例时,在加载库时,在执行main()之前)
     // global scope
     static const QString x; // Wrong - default constructor needs to be run to initialize x
     static const QString y = "Hello"; // Wrong - constructor that takes a const char * has to be run
     QString z; // super wrong
     static const int i = foo(); // wrong - call time of foo() undefined, might not be called at all
    • 1
    • 2
    • 3
    • 4
    • 5
    • 你可以按照下面的方法去做:
     // global scope
     static const char x[] = "someText"; // Works - no constructor must be run, x set at compile time
     static int y = 7; // Works - y will be set at compile time
     static MyStruct s = {1, 2, 3}; // Works - will be initialized statically, no code being run
     static QString *ptr = 0; // Pointers to objects are ok - no code needed to be run to initialize ptr
    • 1
    • 2
    • 3
    • 4
    • 5
    • 用Q_GLOBAL_STATIC定义全局实例
     Q_GLOBAL_STATIC(QString, s)
    
     void foo()
     {
         s()->append("moo");
     }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • char型变量是有符号的还是无符号的取决于它运行环境的架构。如果你明确地想使用一个signed或unsinged char,就使用signed char或unsigned char。以下代码运行在把char默认为无符号的平台上时,其条件判断恒为真。
     char c; // c can't be negative if it is unsigned
    /********/
    /*******/
     if (c > 0) { … } // WRONG - condition is always true on platforms where the default is unsigned
    • 1
    • 2
    • 3
    • 4
    • 避免64位的枚举值 
      • 嵌入式应用系统二进制接口将所有的枚举类型的值硬编码成32位int值
      • 微软的编译器不支持64位的枚举值

    编程美学

    • 偏爱用枚举值定义常量而非用const int或defines 
      • 枚举值会在编译时被编译器用实际值替换掉,因而运行时得出结果的速度更快
      • defines不是命名空间安全的(并且看起来很丑)
    • 偏爱使用冗长而详细的参数名
    • 当重新实现一个虚方法时,不要在头文件中用virtual关键字,在Qt5中,用 Q_DECL_OVERRIDE宏在函数声明之后,分号之前注解它。

    避免出现的事

    • 不要继承模版/工具类 
      • 其析构函数不是虚函数,会导致潜在的内存泄漏
      • 其符号不是导出的(not exported),会导致符号冲突
    // 例如:A库有以下代码
    class Q_EXPORT X: public QList<QVariant> {};
    • 1
    • 2
    //B库有以下代码  
    class Q_EXPORT Y: public QList<QVariant> {};
    • 1
    • 2

    这样,QList的符号就被导出了两次

    • 不要把const-iterator和none-const iterator搞混。
     for (Container::const_iterator it = c.begin(); it != c.end(); ++it) // W R O N G
     for (Container::const_iterator it = c.cbegin(); it != c.cend(); ++it) // Right
    • 1
    • 2
    • Q[Core]Application 是一个单例类。同一时间只能有一个实例在运行,但是这个实例可以被销毁,新的实例将可以被创建,如下的代码容易产生崩溃
     static QObject *obj = 0;
     if (!obj)
         obj = new QObject(QCoreApplication::instance());
    • 1
    • 2
    • 3
    • 当QCoreApplication application被销毁时,obj成为了迷途指针(野指针),可以用 Q_GLOBAL_STATIC 和qAddPostRoutine清理
    • 为了尽可能地支持静态关键词,避免使用匿名命名空间。编译单位内的一个静态名称可以保证它是一个内部连接。而一个位于匿名命名空间的名称,C++规定它是一个外部链接。

    二进制和源兼容性

    • 定义 
      • Qt 4.0.0是一个主版本,Qt 4.1.0是一个微调版本,Qt 4.1.1是一个补丁版本。
      • 在此之后的版本:代码链接到之前版本的库可以运行
      • 在此之前的版本:代码链接到一个新版本的库只对旧版本的库能工作。
      • 源码兼容性:源码在不修改的情况下进行编译
    • 微调版本保持向后的二进制兼容性
    • 补丁版本保持向后和向前的二进制兼容性 
      • 不要增加或去掉任何公共API(例如公共的函数,公有/保护/私有的方法)
      • 不要重新实现方法(甚至不要修改内连方法,也不要修改保护/私有方法)
    • 当继承一个QWidget的子类时,总是要去重写event(),即使它是空的。这将使你的widget类可以被操作而不破坏其二进制兼容性
    • 所有从Qt中导出的方法,必须以q或Q开头。用autotest符号检测是否存在违反此规则的情况。(此为Qt本身要求的规范,我们不需要严格执行。)

    命名空间

    • 请记住,Qt中,除了Tests和WibKit,全部都是处在命名空间中的代码

    float值

    • 没有float值之间的比较 
      • 用qFuzzyCompare去和delta比较其值
      • 用qIsNull去判断float值是不是二进制0,而不是和0.0比较。
    [static] bool qFuzzyCompare(double p1, double p2)
    // Instead of comparing with 0.0
    qFuzzyCompare(0.0,1.0e-200); // This will return false
    // Compare adding 1 to both values will fix the problem
    qFuzzyCompare(1 + 0.0, 1 + 1.0e-200); // This will return true
    • 1
    • 2
    • 3
    • 4
    • 5

    虚方法

    • 不要在子类中隐藏父类的虚方法:假设A类中有个virtual int val()方法,以下代码是不规范的。
     class B: public A
     {
         using A::val;
         int val(int x);
     };
    • 1
    • 2
    • 3
    • 4
    • 5

    宏定义

    • 在操作一个预处理器之前,先判断它是否被定义
     #if Foo == 0  // W R O N G
     #if defined(Foo) && (Foo == 0) // Right
     #if Foo - 0 == 0 // Clever, are we? Use the one above instead, for better readability
    • 1
    • 2
    • 3

    类的成员命名

    • 成员变量一般为名词
    • 函数成员一般为动词/动词+名词,但是当动词为get时,get常常省略。当返回值为Bool型变量时,函数名一般以前缀’is’开头
    public:
        void setColor(const QColor& c);
        QColor color() const;
        void setDirty(bool b);
        bool isDirty() const;
    
    private Q_SLOTS:
        void slotParentChanged();
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    定义私有类

    //.h文件
    class KFooPrivate;
    class KFoo
    {
    public:
        /* public members */
    private:
        const QScopedPointer<KFooPrivate> d;
    };
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    //.cpp文件
    class KFooPrivate
    {
        public:
            int someInteger;
    };
    
    KFoo::KFoo() : d(new KFooPrivate)
    {
        /* ... */
    }
    
    KFoo::~KFoo()
    { 
     // You must define a non-inline destructor in the .cpp file, even if it is empty
     // else, a default one will be built in placed where KFooPrivate is only forward
     // declare, leading to error in the destructor of QScopedPointer
    }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    标记(flags)

    • 避免使用无意义的bool型参数,以下是糟糕的例子
    static QString KApplication::makeStdCaption( const QString &caption,
                                                 bool withAppName,
                                                 bool modified);
    • 1
    • 2
    • 3
    • 解决方案是用QFlags。即使其中只有一个值,也建议这么做,这将允许你以后很方便地添加更多的值并且保持二进制兼容性。
    • 示例如下:
    class KApplication
    {
    public:
        /* [...] */
        enum StandardCaptionOption {
            /**
             * Indicates to include the application name
             */
            WithApplicationName = 0x01,
            /**
             * Note in the caption that there is unsaved data
             */
            Modified = 0x02
        };
        Q_DECLARE_FLAGS(StandardCaptionOptions, 
                        StandardCaptionOption)
    
        /**
         * Builds a caption using a standard layout.
         *
         * @param userCaption The caption string you want to display
         * @param options a set of flags from MakeStandartCaptionOption
         */
        static QString makeStandardCaption(const QString& userCaption,
           const StandardCaptionOptions& options = WithApplicationName);
        /* [...] */
    };
    Q_DECLARE_OPERATORS_FOR_FLAGS(KApplication::StandardCaptionOptions)
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    常引用

    • 每一个对象,只要它不是基础类型(int, float, bool, enum, or pointers),都应该以常量引用的形式传递。这条使得代码运行得更快。即使一个对象是隐式共享的,也应该这么做
    QString myMethod( const QString& foo,
                      const QPixmap& bar,
                      int number );
    • 1
    • 2
    • 3
    • 避免常引用的返回类型
    const QList<int> &someProperty() const;
    • 1
    • 有种情况还是可以使用常引用的返回类型的,这种情况下,此处代码的运行性能至关重要,此处的代码实现也是固定的,思考再三之后,你可以写成这样:
    QList<int> someProperty() const;
    • 1

    库代码中的信号&槽

    • 在库代码中,用Q_SIGNALS 和 Q_SLOTS 代替 signals 和 slots。它们在语法上是相等的,用以避免和boost信号的冲突。和python协同工作时使用”slots”

    属性

    • 设置属性时用Q_PROPERTY。理由是属性可以被javascript 接口访问到
    • moc中设置特定的flag用 QMetaProperty.

    构造函数

    • 为了使构造函数被错误使用的可能性降到最小,每一个构造函数(除了拷贝构函数)都应该检查自己是否需要加上explicit 符号。

    #include

    • 尽量减少在头文件中包含其他头文件的数量
    • 如下所示,可以用前置声明法
    #include <kfoobase.h>
    class KBar;
    class KFoo : public KFooBase
    {
        public:
            /* [...] */
            void myMethod(const KBar &bar);
    };
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 包含Qt自带头文件或外部库的头文件用尖括号
    #include <iostream>
    #include <QDate>
    #include <zlib.h>
    • 1
    • 2
    • 3
    • 包含自己的项目头文件用双引号
    #include "myclass.h"
    • 1
    • 包含Qt类的头文件不用包含它所在的模块名
    //正确示例
    #include <QDate> //correct
    • 1
    • 2
    //错误示例
    #include <QtCore/QDate> //incorrect, no need to specify the module QtCore
    #include <QtCore>   //incorrect, do not include the top-level Qt module
    • 1
    • 2
    • 3
    • 假如你有一个Foo类,有Foo.h文件和Foo.cpp文件,在你的Foo.cpp文件中,要先包含Foo.h文件再包含其他头文件
    • 如果你的代码写成下面这样:
    //.h文件
    class Foo
    {
    public:
        Bar getBar();
    
    };
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    .cpp文件
    #include "bar.h"
    #include "foo.h"
    • 1
    • 2
    • 3
    • 你的cpp文件能够正常编译,但当其他人用到Foo.h文件时,如果没有包含bar.h文件,编译器将不能进行编译。
    • 因此在cpp文件中首先包含其相应的.h文件,是为了.h文件能够被其他人使用
    • 头文件必须进行包含保护:避免多次包含而引起多次的编译
    #ifndef MYFOO_H
    #define MYFOO_H
    ... <stuff>...
    #endif /* MYFOO_H */
    • 1
    • 2
    • 3
    • 4

    信号&槽的标准化写法

    • 标准化的写法增加代码可读性
    • 不标准的写法可能是如下写法
    QObject::connect(this, SIGNAL( newValue(const QString&,
                                            const MyNamespace::Type&) ),
                     other, SLOT( value(const QString &) ));
    • 1
    • 2
    • 3
    • 建议采用以下写法
    QObject::connect(this, SIGNAL(newValue(QString,MyNamespace::Type)),
                     other, SLOT(value(QString)));
    • 1
    • 2

    API-最小化原则

    • 最小化的API意味着,每个API中使用尽可能少的类,每个类中使用尽可能少的公用成员(public members)。这样做的好处是使得API易于理解、记忆、调试和修改

    API-完整性原则

    • 一个完整的API意味着实现它预期的功能。这和最小化的特性可能会产生冲突。另外如果一个成员函数出现在一个错误的类里,API的使用者们可能会找不到它

    API-有明确和简单的语意

    • 正如其他的设计工作一样,你应当遵守“最小惊奇”原则。一般情况下,这是容易做到的。请不要用解决方案所解决的问题过于笼统。(例如Qt3中的QMimeSourceFactory,应该被叫做 QImageLoader从而作为一个不同的API)

    API-直观性原则

    • 不同的经历和背景让人们对什么具有“直观性”什么不具有,有着不同的感觉。以下情况我们可以认为一个API是直观的
    • 一个稍有一些经验的用户在不看帮助文档的情况下,能够正确地使用该API
    • 一个从不知道该API的用户能够看懂使用该API写成的代码

    API-便于记住

    • 选择一个一致的和准确的命名约定
    • 使用可识别的模式和概念
    • 避免使用缩写

    API-易读性原则

    • 代码是一次写成,但需要多次阅读,易读的代码可能写的时候会花费稍长的时间,但是在整个产品的生命周期之中,但节省你很多阅读和理解的时间
    • 最后,记住不同的用户会用到一个API的不同部分。虽然直接使用Qt的类生成一个实例是直观的,但我们还是有理由期待用户在派生一个Qt的类之前先阅读它的官方文档

    API-静态多态性

    • 相似的类应该有相似的API,这可以用继承的方式实现,这用到了运行时多态。
    • 但是多态也可以发生在设计类的时候,例如,你把一个对象的类型从QProgressBar 换成Qslider,或者从QString 换成QByteArray,你会发现它们之间的API是何其的相似,以至于你可以很简单地用一个去替换另一个。这就是我们所说的“静态多态性”
    • “静态多态性”使得记住这些API和使用编程模式都更为简单了。因此,为一系列相关类设计相似的API好过为每个类设计独立的、更切合自身的API
    • 一般来说,在Qt中我们偏爱使用“静态多态性”而非实际的继承,除非一些不可控制的原因要求我们不得不如此。

    常引用

    • 如果一个类型超过16个字节,用常引用传递它。
    • 如果一个类型有一个非平凡拷贝构造函数(non-trivial copy-constructor),或一个非平凡析构函数(non-trivial destructor),用常引用传递它的值而避免使用以上方法
    • 所有的其他类型都应该直接传递其值
    void setAge(int age); 
    void setCategory(QChar cat); 
    void setName(QLatin1String name); 
    void setAlarm(const QSharedPointer<Alarm> &alarm); // const-ref is much faster than running copy-constructor and destructor 
    // QDate, QTime, QPoint, QPointF, QSize, QSizeF, QRect are good examples of other classes you should pass by value.
    • 1
    • 2
    • 3
    • 4
    • 5

    枚举类型和枚举值的命名

    • 以下的示例说明了枚举值命名时给出一般的名称的危险
    namespace Qt
    {
    enum Corner { TopLeft, BottomRight, … };
    enum CaseSensitivity { Insensitive, Sensitive };
    …
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    tabWidget->setCornerWidget(widget, Qt::TopLeft);
    str.indexOf("$(QTDIR)", Qt::Insensitive);
    • 1
    • 2
    • 在最后一行,Insensitive 是什么含义呢?这是不易于理解的。因此,枚举值命名时,至少重复枚举类型名中的一个字母
    namespace Qt
    {
    enum Corner { TopLeftCorner, BottomRightCorner, … };
    enum CaseSensitivity { CaseInsensitive,
    CaseSensitive };
    …
    };
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    tabWidget->setCornerWidget(widget, Qt::TopLeftCorner);
    str.indexOf("$(QTDIR)", Qt::CaseInsensitive);
    • 1
    • 2

    企图少写代码的陷阱

    • 不要为了图方便少些一些代码。因为代码是一次书写,后期不止一次地要去理解。例如
    QSlider *slider = new QSlider(12, 18, 3, 13, Qt::Vertical, 0, "volume");
    • 1
    • 改成下面的方式会更容易理解
    QSlider *slider = new QSlider(Qt::Vertical);
    slider->setRange(12, 18);
    slider->setPageStep(3);
    slider->setValue(13);
    slider->setObjectName("volume");
    • 1
    • 2
    • 3
    • 4
    • 5

    Bool型参数陷阱

    • 这方面经典的例子是Qt中的repaint()函数,它的bool型参数用来标记窗口的背景是否被擦除。用法如下:
    widget->repaint(false);
    • 1
    • 上面的代码很容易被理解成“不重新绘制”
    • 上面代码的中用到的repaint()函数,其设计时的考虑无非是为了可以少定义一个函数,结果反而带来了误解,有多少人可以对以下三行代码所代表队含义进行准确地区分呢?
    widget->repaint();
    widget->repaint(true);
    widget->repaint(false);
    • 1
    • 2
    • 3
    • 稍微好一点的做法是这样
    widget->repaint();
    widget->repaintWithoutErasing();
    • 1
    • 2
    • 还有一个很明显的做法是尽可能地用枚举类型代替bool型参数,请对比以下的两行代码
    str.replace("USER", user, false); // Qt 3
    str.replace("USER", user, Qt::CaseInsensitive); // Qt 4

    https://blog.csdn.net/qq_35488967/article/details/70055490

  • 相关阅读:
    CSS margin的一些让你模糊的点
    VUE中CSS样式穿透
    如何给img标签里的请求添加自定义header?
    iframe的父子页面进行简单的相互传值
    Docker部署网站之后映射域名
    机器学习笔记(九)---- 集成学习(ensemble learning)【华为云技术分享】
    鲲鹏性能优化十板斧之前言 | 鲲鹏处理器NUMA简介与性能调优五步法
    【我的物联网成长记8】超速入门AT指令集【华为云技术分享】
    【直播分享】实现LOL小地图英雄头像分析案例【华为云分享】
    MongoDB一次节点宕机引发的思考(源码剖析)【华为云分享】
  • 原文地址:https://www.cnblogs.com/findumars/p/9357450.html
Copyright © 2011-2022 走看看