"Designing QT-style C ++ APIs" by Matthias ettrich
Http://doc.trolltech.com/qq/qq13-apis.html
The purpose of translating this article is not to let people understand QT, but to try to learn some soft skills in C ++ programming. I have learned some style experiences from the original article and hope you can gain some experience)
We have done a lot of research in trolltech to improve the QT development experience. in this article, I will share some of our achievements and present the original features we followed during the QT 4 design, and shows you how to apply them to your code.
- Six features of excellent APIs
- Convenience trap
- Boolean parameter trap
- Static Polymorphism
- Name Art
- Pointer or reference?
- Case study: qprogressbar
- How to write the correct API
Designing an application interface (APIS) is difficult. It is an art that is as difficult as designing a programming language. There are many different principles that conflict with each other.
Today's computer education focuses too much on algorithms and data structures, and seldom on design principles hidden behind programming languages and frameworks. this makes programmers have no preparation to create reusable components for increasingly important tasks.
Before the emergence of object-oriented language, most common reusable code was written by the library provider rather than the application developer. in the QT world, this situation has changed a lot. using QT programming is actually writing new components. some custom components exist in typical QT applications and are reused throughout the application. the same components are often developed as part of other programs. KDE, K desktop environment, and even using many additional libraries to further expand QT and implement many additional classes.
But what is an excellent and efficient C ++ API? Its quality depends on many factors, such as tasks at hand and specific target groups. excellent APIs have many features, some of which are expected in general, and others are for specific problem domains.
Six features of excellent APIs
For programmers, APIs are equivalent to guis for end users. in the API, 'P' stands for the programmer (programmer), rather than the program (Program). It is emphasized that the API is used by programmers, who are human rather than machine.
We believe that APIs should be simplified and complete, with clear and simple semantics, intuitive, easy to remember, and should make the code readable.
- Streamlining: A streamlined API has as few classes and public members as possible. This makes it easier to understand, remember, debug, and change the API.
- Completeness: A complete API means that you have the expected functions. this may conflict with the simplicity of the API. also, if a member function is placed in an unmatched class, many potential users who use this function cannot find it.
- Clear and simple semantics: Like other design jobs, you should stick to the principle of least surprise. make a common task simple. A rare task should be as simple as possible, but it should not be the focus. solve specific problems. do not make solutions universal when they are not needed.
- Intuition: Similar to other computer-related things, APIs should be intuitive. different experiences and backgrounds may lead to different opinions on what is intuitive and what is not intuitive. if non-professional users can use the API immediately without reading the document, or programmers who do not know this API can understand the code using the API, this API is intuitive.
- Note: To make the API easy to remember, use consistent and accurate naming rules. Use easy-to-recognize patterns and concepts to avoid abbreviations.
- Generate readable code: The code is only written once, but it needs to be read many times (debugging or modification). The readable code may sometimes need to be typed more, but it can save a lot of time in the product life cycle.
Finally, remember: different users use different parts of the API. the simple use of QT instances may be intuitive, but this may allow users to try some of the functions after reading the relevant documents.
Convenience trap
The common misunderstanding is that the fewer code you need, the more you can write better APIs. remember to write the code only once, but read it over and over again. for example:
QSlider *slider = new QSlider(12, 18, 3, 13, Qt::Vertical, 0, "volume");
It is more difficult to read (or even write) than the following code)
QSlider *slider = new QSlider(Qt::Vertical); slider->setRange(12, 18); slider->setPageStep(3); slider->setValue(13); slider->setObjectName("volume");
Boolean parameter trap
Boolean parameters often lead to unreadable code. In particular, adding a bool parameter to an existing function is generally an incorrect decision. In QT, the traditional example isRepaint ()It has an optional Boolean parameter to specify whether the background is deleted (delete by default). This causes the code to look like this:
widget->repaint(false);
Beginners may understand it as literally, "Do not redraw! "
The natural idea isThe bool parameter saves a function and reduces the code bloated. In fact, this increases the code bloated. How many QT users actually know what the following three lines of code are doing?
widget->repaint(); widget->repaint(true); widget->repaint(false);
A better API code may look like this:
widget->repaint(); widget->repaintWithoutErasing();
In QT 4, we can solve this problem by simply removing and not deletingWidgetThe possibility of re-painting. The native support of QT 4 for dual buffer will render this function obsolete.
Here are some examples:
widget->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Expanding, true); textEdit->insert("Where's Waldo?", true, true, false); QRegExp rx("moc_*.c??", false, true);
The obvious solution isBoolThe parameter is replaced by the enumeration type. This is what we have done in qstring in QT 4. Compare the following two examples:
str.replace("%USER%", user, false); // Qt 3 str.replace("%USER%", user, Qt::CaseInsensitive); // Qt 4
Static Polymorphism
Similar classes should have similar APIs. to some extent, this can be implemented by inheritance, that is, the Runtime polymorphism mechanism. however, polymorphism can also occur during design. for example, if you exchange qlistbox with qcombobox and qslider with qspinbox, you will find that the similarity of Apis makes this replacement easier. this is what we call static polymorphism ".
Static polymorphism can also make the memory API and programming mode easier. Therefore, similar APIs for a set of related classes are sometimes better than designing unique and perfect APIs for each class.
Name Art
Naming is sometimes the most important thing in API design. You need to think about the names of a class and a member function.
General naming rules
A few rules apply to all types of naming. first, as I mentioned earlier, do not use abbreviations. even obvious abbreviations like "Prev" for "previous" won't benefit long term, because users must remember which names are abbreviations.
If APIs cannot be unified, things will naturally become worse. For example, there areActivatepreviouswindow () function, also hasFetchprev () functionSticking to the rule "no abbreviation" makes it easier to create consistent APIs.
In the design class, another important but not obvious rule is to keep the name in the subclass concise and easy to understand. in QT 3, this principle is not always observed. to illustrate this, let's take the qtoolbutton example. if you call qtoolbutton in QT 3
Name (),Caption (),Text (), OrWhat do you expect when textlabel () is a member function? Try qtoolbutton In the QT designer.
- NameProperty inherited from qobject, used to represent the internal name of the object during debugging and testing.
- CaptionProperty is inherited from qwidget, which refers to the title of the form. It is meaningless for qtoolbuttons, since they are all created by the parent form.
- TextProperty is inherited from qbutton, which is usually used in buttons unless usetextlabel is true.
- TextlabelAttribute is declared in qtoolbutton. IfUsetextlabel is true and displayed on the button.
For readability, in qt4NameCalledObjectname,Caption is calledWindowtitleIn qtoolbuttonTextClear, no longer availableTextlabel attribute.
Naming class
You should not seek perfect names for different classes, but assign classes to them. For example, in QT 4, all model-related visual Class components are usedViewSuffix (qlistview, qtableview, qtreeview), corresponding component-based ClassWidgetSuffix replacement (qlistwidget, qtablewidget, qtreewidge ).
Name Enumeration type and Value Type
When designing enumeration, we should remember that in C ++ (unlike Java or C #), enumeration values do not contain type names. the following example illustrates the harm of naming enumeration values that are too general:
namespace Qt { enum Corner { TopLeft, BottomRight, ... }; enum CaseSensitivity { Insensitive, Sensitive }; ... }; tabWidget->setCornerWidget(widget, Qt::TopLeft); str.indexOf("$(QTDIR)", Qt::Insensitive);
In the above line,What does insensitive mean? It is recommended that you repeat the names of enumeration types in each enumeration value.
namespace Qt { enum Corner { TopLeftCorner, BottomRightCorner, ... }; enum CaseSensitivity { CaseInsensitive, CaseSensitive }; ... }; tabWidget->setCornerWidget(widget, Qt::TopLeftCorner); str.indexOf("$(QTDIR)", Qt::CaseInsensitive);
However, when the enumerated values are in a "or" relationship and used as a flag, the traditional solution is to save "or" resultsIntIn this way, the type is insecure. QT 4 provides a template class qflags <t>, where T is the enumeration type. QT provides convenience for the flag type name, you can useQt: AlignmentInstead of qflags <QT: alignmentflag>.
For convenience, we give names in the singular form of enumeration (only when only one flag is included) and names in the plural form of "Flags", such:
enum RectangleEdge { LeftEdge, RightEdge, ... }; typedef QFlags<RectangleEdge> RectangleEdges;
In some cases, the "Flags" type has a singular name. In this case, the enumerated type isFlagSuffix ID:
enum AlignmentFlag { AlignLeft, AlignTop, ... }; typedef QFlags<AlignmentFlag> Alignment;
Name of functions and Parameters
One rule in the function name is to clearly check whether the function has any side effects from its name. in QT 3, the regular function qstring: simplifywhitespace () violates this rule. then it returns a qstring instead of modifying the string as stated in its name. in QT 4, this function is renamed as qstring: simplified ().
Parameter names are an important source of information for programmers, even if they do not appear in the code that calls the API. since modern ide will display these parameters during programmer encoding, it is worthwhile to give these parameters an appropriate name in the header file and use the same name in the document.
Name a Boolean getter, setter, and attribute
It is always very difficult to get an appropriate name for the Boolean getter and setter attributes. getter should be calledChecked ()Or callIschecked (), takeScrollbarsenabled ()OrArescrollbarenabled ()
In QT 4, the following guiding principles are used for getter functions:
- Use adjectivesIs-Prefix. For example:
- Ischecked ()
- Isdown ()
- Isempty ()
- Ismovingenabled ()
However, nouns that apply adjectives to the plural form do not have a prefix:
- Scrollbarsenabled (), NotArescrollbarsenabled ()
- The verb has no prefix and does not use the third-person (-S ):
- Acceptdrops (), NotAcceptsdrops ()
- Allcolumnsshowfocus ()
- There is usually no prefix for nouns:
- Autocompletion (), NotIsautocompletion ()
- Boundarychecking ()
Sometimes a prefix is misleading.Is-:
- Isopenglavailable (), NotOpenGL ()
- Isdialog (), NotDialog ()
(If the function is called Dialog (), we usually think it will return the qdialog * type)
The setter name can be inferred from this, as long as the is prefix is removed, you can add the set prefix before the name. For example:Setdown ()AndSetscrollbarsenabled (). The property name is the same as that of getter, that is, there is no is prefix.
Pointers or references?
Pointer or reference?
Is it better to use a pointer or reference an External Parameter?
void getHsv(int *h, int *s, int *v) const void getHsv(int &h, int &s, int &v) const
The vast majority of C ++ books recommend that you use references whenever possible, because in most cases, references are more "safe and elegant" than pointers ". in trolltech, we tend to be pointer-oriented because it makes user code more readable. compare the following code:
color.getHsv(&h, &s, &v); color.getHsv(h, s, v);
Only the first line of code can clearly describe H, S, and V. After the function is called, its value is very likely to be modified.
Case study: qprogressbar
Case study: qprogressbar
To illustrate these concepts in actual code, we compare qprogressbar with qt3 and qt4. In QT 3:
class QProgressBar : public QWidget { ... public: int totalSteps() const; int progress() const; const QString &progressString() const; bool percentageVisible() const; void setPercentageVisible(bool); void setCenterIndicator(bool on); bool centerIndicator() const; void setIndicatorFollowsStyle(bool); bool indicatorFollowsStyle() const; public slots: void reset(); virtual void setTotalSteps(int totalSteps); virtual void setProgress(int progress); void setProgress(int progress, int totalSteps); protected: virtual bool setIndicator(QString &progressStr, int progress, int totalSteps); ... };
The key to improving this API is to observe the similarity between qprogressbar and qabstractspinbox in QT 4, as well as its subclass, qspinbox, qslider, and qdial. What are the solutions? Replace the SS and totalsteps with minimun, maximum, and value.
AddValuechanged (). IncreaseSetrange ()This convenient function.
Next, you needProgressstring,PercentageAndIndicator actually refers to the same thing: Text displayed in the progress bar. This article is usually one hundred, but it can beSetindicator ()Set to any value. Here is the new API:
virtual QString text() const; void setTextVisible(bool visible); bool isTextVisible() const;
By default, this text is a percentage indicator, which can be implemented againText.
In QT 3, setcenterindicator ()AndSetindicatorfollowsstyle () is two functions that affect alignment. They are all replaced by an advanced function,Setalignment ().
void setAlignment(Qt::Alignment alignment);
If the programmer does not callSetalignment (), Alignment is determined based on the style. For motif styles, text is displayed in the middle, and for other styles, text is right aligned.
Here is the improved qprogressbar:
class QProgressBar : public QWidget32 { ... public: void setMinimum(int minimum); int minimum() const; void setMaximum(int maximum); int maximum() const; void setRange(int minimum, int maximum); int value() const; virtual QString text() const; void setTextVisible(bool visible); bool isTextVisible() const; Qt::Alignment alignment() const; void setAlignment(Qt::Alignment alignment); public slots: void reset(); void setValue(int value); signals: void valueChanged(int value); ... };
How to write the correct APIs
APIS requires quality assurance. The earliest version is generally not very good. You must test it. The code that calls this API is used as a test case to verify the code readability.
Other tips include making people use this API without documentation or Class documentation (class overview and function description.
When you are in trouble, docization is also a good way to find a proper name: Try to mark the document for these classes, functions, and enumeration values, use the first word that appears in your mind. if you cannot find a precise name to express it, it is very likely that this thing should not exist. if any method fails and you are sure the concept is useful, create a new name. finally, the words "widget", "Event", "Focus", and "buddy" can always be used.