2.5 Qt风格的编程(www.cppentry.com)规范
笔者的一位朋友曾在北京一家电信软件公司担任技术总监。当我问他每天都在忙碌什么时,他半开玩笑的回答"我们公司的软件系统经过这么多年的开发,体系架构、性能等都已经比较成熟,并不需要大幅度的重构。我每天做的工作也就是修改函数、参数的名字,使程序更加易读"。的确,在传统的大学课程中,算法、系统架构、设计等会被强调,而诸如函数命名这样的编程(www.cppentry.com)规范常被忽略。良好的编程(www.cppentry.com)规范可以大幅提高一个程序的可读性、可维护性。
软件开发领域并没有一个公认的、统一的编程(www.cppentry.com)规范,不同的软件开发项目或者组织会采用不同的编程(www.cppentry.com)规范,如Google公司采用参考文献[4]作为该公司所有开源项目的编程(www.cppentry.com)规范。Qt的开发团队采用参考文献[5]作为编程(www.cppentry.com)规范。该规范使得Qt的API更容易被程序员理解和使用,缩短了开发人员的学习周期,是Qt得以广泛流行的原因之一。学习并遵循该规范,可以使我们开发的Qt应用程序也更易读、更易维护。下面我们介绍该规范的主要内容。
1.API的设计原则。(1)精炼。软件系统应该尽量少定义类,每个类中应该尽量少定义成员。(2)清晰而简单的语义。用户应该能够轻易地使用一个API来解决那些常见的任务。对于不常见、复杂的任务,用户也能通过该API来实现,但是这不应该成为API设计的重点。另外,API不能过度追求通用性,它应该首先被用来解决具体的、常见的问题。(3)直观。使得那些没有阅读过一个API文档的程序员也能够读懂使用该API开发出来的程序。(4)容易记忆。应该使用精确、统一的命名规则来对类、函数、参数等进行命名。
2.可读性比精炼更重要。例如,语句:
- QSlider *slider = new QSlider(12, 18, 3, 13, Qt::Vertical, 0, "volume");
虽然精炼但是可读性差。类QSlider的API应该被改为:- QSlider *slider = new QSlider(Qt::Vertical);
- slider->setRange(12, 18);
- slider->setPageStep(3);
- slider->setValue(13);
- slider->setObjectName("volume");
又比如,当我们调用一个控件的成员函数repaint()重新绘制该控件时,有时我们希望先擦除其背景再绘制,有时却又希望直接绘制以提供速度。一种做法是令该函数包含一个布尔类型的参数,表示是否需要擦除背景。这种做法虽然只用了一个函数来处理两种情形,看起来比较简洁,但是可能导致下面的代码:- widget->repaint(false);
API的初级用户可能将其理解为"不重新绘制控件"。因此,为了提高代码的可读性,更好的一个做法是用两个函数来实现上述两种情形:成员函数repaint()擦除背景再绘制控件,而成员函数repaintWithoutErasing()只绘制控件、不擦除背景。
3.相似的类应该具有相似的接口。例如,Qt中的QTextStream负责以文本形式输入或者输出程序中的数据,而QDataStream负责以二进制方式输入或者输出。由于二者具有相似的功能,它们都重载了运算符"<<"以及">>",使得熟悉其中一个类的用户能够迅速学会另外一个类的使用方法,缩短了学习周期。
4.命名原则。Qt推荐使用以下命名原则。
不要使用缩写。即使对于"previous'prev"这样广为接受的缩写形式,Qt也不予采纳,因为这要求程序员记忆哪些单词被缩写、哪些没有被缩写。
如果一个标识符含有多个单词,则第2个单词之后所有单词的首字母应该大写,比如"installSceneEventFilter"。有的编程(www.cppentry.com)规范建议使用下画线分割各个单词,但这会增加标识符的长度。
类的名字以大写字母开始。其中绝大多数以字母Q开始,表示该类是Qt软件包中定义的类,此时,第二个字母也应该是大写的。而且,类的名字应该是一个名词,比如QFileSystemWatcher。
函数名字以小写字母开始,应该是一个动词或者含有动词的短语,比如collidesWithItem()。
全局常量名字中的字母都应该大写,比如Q_BIG_ENDIAN。枚举常量的名字应该含有枚举类型的信息。这是由于枚举常量在C++(www.cppentry.com)程序中可被直接使用,如果其名字过于简单,可能会导致歧义。例如,设有:
- enum CaseSensitivity { Insensitive, Sensitive };
由于有上下文,枚举常量Insensitve及Sensitive在被定义时并没有任何歧义。然而,当它们在程序其他地方被引用时,程序的读者难以理解它们的含义。因此,应该在它们的名字中嵌入枚举类型的信息,比如 - enum CaseSensitivity { CaseInsensitive, CaseSensitive };
如果某个数据成员具有布尔类型,读取该数据的成员函数应该以"is"打头,比如isEmpty()、isMovingEnabled()。然而,如果该数据成员的名字是复数形式的,则不需要加任何前缀,比如scrollBarsEnabled()。