• <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>

            子彈 の VISIONS

            NEVER back down ~~

            C++博客 首頁 新隨筆 聯(lián)系 聚合 管理
              112 Posts :: 34 Stories :: 99 Comments :: 0 Trackbacks
            Designing Qt-Style C++ APIs
            by Matthias Ettrich
            We have done substantial research at Trolltech into improving the Qt development experience. In this article, I want to share some of our findings and present the principles we've been using when designing Qt 4, and show you how to apply them to your code.

             

            Designing application programmer interfaces, APIs, is hard. It is an art as difficult as designing programming languages. There are many different principles to choose from, many of which tend to contradict each other.

            Computer science education today puts a lot of emphasis on algorithms and data structures, with less focus on the principles behind designing programming languages and frameworks. This leaves application programmers unprepared for an increasingly important task: the creation of reusable components.

            Before the rise of object-oriented languages, reusable generic code was mostly written by library vendors rather than by application developers. In the Qt world, this situation has changed significantly. Programming with Qt is writing new components all the time. A typical Qt application has at least some customized components that are reused throughout the application. Often the same components are deployed as part of other applications. KDE, the K Desktop Environment, goes even further and extends Qt with many add-on libraries that implement hundreds of additional classes.

            But what constitutes a good, efficient C++ API? What is good or bad depends on many factors -- for example, the task at hand and the specific target group. A good API has a number of features, some of which are generally desirable, and some of which are more specific to certain problem domains.

            Six Characteristics of Good APIs

            An API is to the programmer what a GUI is to the end-user. The 'P' in API stands for "Programmer", not "Program", to highlight the fact that APIs are used by programmers, who are humans.

            We believe APIs should be minimal and complete, have clear and simple semantics, be intuitive, be easy to memorize, and lead to readable code.

            Finally, keep in mind that different kinds of users will use different parts of the API. While simply using an instance of a Qt class should be intuitive, it's reasonable to expect the user to read the documentation before attempting to subclass it.

             

            The Convenience Trap

            It is a common misconception that the less code you need to achieve something, the better the API. Keep in mind that code is written more than once but has to be understood over and over again. For example,

                QSlider *slider = new QSlider(12, 18, 3, 13, Qt::Vertical,
            0, "volume");
            

            is much harder to read (and even to write) than

                QSlider *slider = new QSlider(Qt::Vertical);
            slider->setRange(12, 18);
            slider->setPageStep(3);
            slider->setValue(13);
            slider->setObjectName("volume");
            

             

            The Boolean Parameter Trap

            Boolean parameters often lead to unreadable code. In particular, it's almost invariably a mistake to add a bool parameter to an existing function. In Qt, the traditional example is repaint(), which takes an optional bool parameter specifying whether the background should be erased (the default) or not. This leads to code such as

                widget->repaint(false);
            

            which beginners might read as meaning, "Don't repaint!"

            The thinking is apparently that the bool parameter saves one function, thus helping reducing the bloat. In truth, it adds bloat; how many Qt users know by heart what each of the next three lines does?

                widget->repaint();
            widget->repaint(true);
            widget->repaint(false);
            

            A somewhat better API might have been

                widget->repaint();
            widget->repaintWithoutErasing();
            

            In Qt 4, we solved the problem by simply removing the possibility of repainting without erasing the widget. Qt 4's native support for double buffering made this feature obsolete.

            Here come a few more examples:

                widget->setSizePolicy(QSizePolicy::Fixed,
            QSizePolicy::Expanding, true);
            textEdit->insert("Where's Waldo?", true, true, false);
            QRegExp rx("moc_*.c??", false, true);
            

            An obvious solution is to replace the bool parameters with enum types. This is what we've done in Qt 4 with case sensitivity in QString. Compare:

                str.replace("%USER%", user, false);               // Qt 3
            str.replace("%USER%", user, Qt::CaseInsensitive); // Qt 4
            

             

            Static Polymorphism

            Similar classes should have a similar API. This can be done using inheritance where it makes sense -- that is, when run-time polymorphism is used. But polymorphism also happens at design time. For example, if you exchange a QListBox with a QComboBox, or a QSlider with a QSpinBox, you'll find that the similarity of APIs makes this replacement very easy. This is what we call "static polymorphism".

            Static polymorphism also makes it easier to memorize APIs and programming patterns. As a consequence, a similar API for a set of related classes is sometimes better than perfect individual APIs for each class.

            The Art of Naming

            Naming is probably the single most important issue when designing an API. What should the classes be called? What should the member functions be called?

            General Naming Rules

            A few rules apply equally well to all kinds of names. First, as I mentioned earlier, do not abbreviate. Even obvious abbreviations such as "prev" for "previous" don't pay off in the long run, because the user must remember which words are abbreviated.

            Things naturally get worse if the API itself is inconsistent; for example, Qt 3 has activatePreviousWindow() and fetchPrev(). Sticking to the "no abbreviation" rule makes it simpler to create consistent APIs.

            Another important but more subtle rule when designing classes is that you should try to keep the namespace for subclasses clean. In Qt 3, this principle wasn't always followed. To illustrate this, we will take the example of a QToolButton. If you call name(), caption(), text(), or textLabel() on a QToolButton in Qt 3, what do you expect? Just try playing around with a QToolButton in Qt Designer:

            • The name property is inherited from QObject and refers to an internal object name that can be used for debugging and testing.
            • The caption property is inherited from QWidget and refers to the window title, which has virtually no meaning for QToolButtons, since they usually are created with a parent.
            • The text property is inherited from QButton and is normally used on the button, unless useTextLabel is true.
            • The textLabel property is declared in QToolButton and is shown on the button if useTextLabel is true.
            In the interest of readability, name is called objectName in Qt 4, caption has become windowTitle, and there is no longer any textLabel property distinct from text in QToolButton.

             

            Naming Classes

            Identify groups of classes instead of finding the perfect name for each individual class. For example, All the Qt 4 model-aware item view classes are suffixed with View (QListView, QTableView, and QTreeView), and the corresponding item-based classes are suffixed with Widget instead (QListWidget, QTableWidget, and QTreeWidget).

            Naming Enum Types and Values

            When declaring enums, we must keep in mind that in C++ (unlike in Java or C#), the enum values are used without the type. The following example shows illustrates the dangers of giving too general names to the enum values:

                namespace Qt
            {
            enum Corner { TopLeft, BottomRight, ... };
            enum CaseSensitivity { Insensitive, Sensitive };
            ...
            };
            tabWidget->setCornerWidget(widget, Qt::TopLeft);
            str.indexOf("$(QTDIR)", Qt::Insensitive);
            

            In the last line, what does Insensitive mean? One guideline for naming enum types is to repeat at least one element of the enum type name in each of the enum values:

                namespace Qt
            {
            enum Corner { TopLeftCorner, BottomRightCorner, ... };
            enum CaseSensitivity { CaseInsensitive,
            CaseSensitive };
            ...
            };
            tabWidget->setCornerWidget(widget, Qt::TopLeftCorner);
            str.indexOf("$(QTDIR)", Qt::CaseInsensitive);
            

            When enumerator values can be OR'd together and be used as flags, the traditional solution is to store the result of the OR in an int, which isn't type-safe. Qt 4 offers a template class QFlags<T>, where T is the enum type. For convenience, Qt provides typedefs for the flag type names, so you can type Qt::Alignment instead of QFlags<Qt::AlignmentFlag>.

            By convention, we give the enum type a singular name (since it can only hold one flag at a time) and the "flags" type a plural name. For example:

                enum RectangleEdge { LeftEdge, RightEdge, ... };
            typedef QFlags<RectangleEdge> RectangleEdges;
            

            In some cases, the "flags" type has a singular name. In that case, the enum type is suffixed with Flag:

                enum AlignmentFlag { AlignLeft, AlignTop, ... };
            typedef QFlags<AlignmentFlag> Alignment;
            

            Naming Functions and Parameters

            The number one rule of function naming is that it should be clear from the name whether the function has side-effects or not. In Qt 3, the const function QString::simplifyWhiteSpace() violated this rule, since it returned a QString instead of modifying the string on which it is called, as the name suggests. In Qt 4, the function has been renamed QString::simplified().

            Parameter names are an important source of information to the programmer, even though they don't show up in the code that uses the API. Since modern IDEs show them while the programmer is writing code, it's worthwhile to give decent names to parameters in the header files and to use the same names in the documentation.

            Naming Boolean Getters, Setters, and Properties

            Finding good names for the getter and setter of a bool property is always a special pain. Should the getter be called checked() or isChecked()? scrollBarsEnabled() or areScrollBarEnabled()?

            In Qt 4, we used the following guidelines for naming the getter function:

            • Adjectives are prefixed with is-. Examples:
              • isChecked()
              • isDown()
              • isEmpty()
              • isMovingEnabled()
              However, adjectives applying to a plural noun have no prefix:
              • scrollBarsEnabled(), not areScrollBarsEnabled()
            • Verbs have no prefix and don't use the third person (-s):
              • acceptDrops(), not acceptsDrops()
              • allColumnsShowFocus()
            • Nouns generally have no prefix:
              • autoCompletion(), not isAutoCompletion()
              • boundaryChecking()
              Sometimes, having no prefix is misleading, in which case we prefix with is-:
              • isOpenGLAvailable(), not openGL()
              • isDialog(), not dialog()
              (From a function called dialog(), we would normally expect that it returns a QDialog *.)
            The name of the setter is derived from that of the getter by removing any is prefix and putting a set at the front of the name; for example, setDown() and setScrollBarsEnabled(). The name of the property is the same as the getter, but without the is prefix.

             

            Pointers or References?

            Which is best for out-parameters, pointers or references?

                void getHsv(int *h, int *s, int *v) const
            void getHsv(int &h, int &s, int &v) const
            

            Most C++ books recommend references whenever possible, according to the general perception that references are "safer and nicer" than pointers. In contrast, at Trolltech, we tend to prefer pointers because they make the user code more readable. Compare:

                color.getHsv(&h, &s, &v);
            color.getHsv(h, s, v);
            

            Only the first line makes it clear that there's a high probability that h, s, and v will be modified by the function call.

            Case Study: QProgressBar

            To show some of these concepts in practice, we'll study the QProgressBar API of Qt 3 and compare it to the Qt 4 API. 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 API is quite complex and inconsistent; for example, it's not clear from the naming that reset(), setTotalSteps(), and setProgress() are tightly related.

            The key to improve the API is to notice that QProgressBar is similar to Qt 4's QAbstractSpinBox class and its subclasses, QSpinBox, QSlider and QDial. The solution? Replace progress and totalSteps with minimum, maximum and value. Add a valueChanged() signal. Add a setRange() convenience function.

            The next observation is that progressString, percentage and indicator really refer to one thing: the text that is shown on the progress bar. Usually the text is a percentage, but it can be set to anything using the setIndicator() function. Here's the new API:

                virtual QString text() const;
            void setTextVisible(bool visible);
            bool isTextVisible() const;
            

            By default, the text is a percentage indicator. This can be changed by reimplementing text().

            The setCenterIndicator() and setIndicatorFollowsStyle() functions in the Qt 3 API are two functions that influence alignment. They can advantageously be replaced by one function, setAlignment():

                void setAlignment(Qt::Alignment alignment);
            

            If the programmer doesn't call setAlignment(), the alignment is chosen based on the style. For Motif-based styles, the text is shown centered; for other styles, it is shown on the right hand side.

            Here's the improved QProgressBar API:

                class QProgressBar : public QWidget
            {
            ...
            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 Get APIs Right

            APIs need quality assurance. The first revision is never right; you must test it. Make use cases by looking at code which uses this API and verify that the code is readable.

            Other tricks include having somebody else use the API with or without documentation and documenting the class (both the class overview and the individual functions).

            Documenting is also a good way of finding good names when you get stuck: just try to document the item (class, function, enum value, etc.) and use your first sentence as inspiration. If you cannot find a precise name, this is often a sign that the item shouldn't exist. If everything else fails and you are convinced that the concept makes sense, invent a new name. This is, after all, how "widget", "event", "focus", and "buddy" came to be.

            posted on 2008-05-30 16:51 子彈のVISIONS 閱讀(796) 評論(1)  編輯 收藏 引用 所屬分類: 2.0 工作參考

            Feedback

            # re: Designing Qt-Style C++ APIs 2008-05-30 16:52 子彈

            設(shè)計Qt風格的C++API

            作者Matthias Ettrich,譯者Googol Lee,原文地址在這里

            在奇趣(Trolltech),為了改進Qt的開發(fā)體驗,我們做了大量的研究。這篇文章里,我打算分享一些我們的發(fā)現(xiàn),以及一些我們在設(shè)計Qt4時用到的原則,并且展示如何把這些原則應(yīng)用到你的代碼里。

            設(shè)計應(yīng)用程序接口,API,是很難的。這是一門和設(shè)計語言同樣難的藝術(shù)。這里可以選擇太多的原則,甚至有很多原則和其他原則有矛盾。

            現(xiàn)在,計算機科學教育把很大的力氣放在算法和數(shù)據(jù)結(jié)構(gòu)上,而很少關(guān)注設(shè)計語言和框架背后的原則。這讓應(yīng)用程序員完全沒有準備去面對越來越重要的任務(wù):創(chuàng)造可重用的組件。

            在面向?qū)ο笳Z言普及之前,可重用的通用代碼大部分是由庫提供者寫的,而不是應(yīng)用程序員。在Qt的世界里,這種狀況有了明顯的改善。在任何時候,用Qt編程就是寫新的組件。一個典型的Qt應(yīng)用程序至少都會有幾個在程序中反復(fù)使用的自定義組件。一般來說,同樣的組件會成為其他應(yīng)用程序的一部分。KDE,K桌面環(huán)境,走得更遠,用許多追加的庫來擴展Qt,實現(xiàn)了數(shù)百個附加類。(一般來說,一個類就是一個可重用組件,原文這里沒有寫清楚。)

            但是,一個好的,高效的C++ API是由什么組成的呢?是好還是壞,取決于很多因素——比如,手頭的工作和特定的目標群體。好的API有很多特性,一些特性是大家都想要的,而另一些則是針對特定問題域的。

            好的API的六個特性

            API是面向程序員的,用來描述提供給最終用戶的GUI是什么樣子。API中的P帶表程序員(Programmer),而不是程序(Program),用來強調(diào)API是給程序員用的,給人類的程序員用的。

            我們堅信API應(yīng)該是最小化且完整的,擁有清晰且簡單的語義,直覺化,容易記憶,并且引導(dǎo)人寫出易讀的代碼。

            • 最小化:最小化的API是指一個類盡可能只擁有最少的公開成員且盡可能只擁有最少的類。這個原則可以讓API更簡單易懂,更好記,更容易除錯,且更容易改變。
            • 完整的:完整的API是指要提供所有期望的功能。這個可能與最小化原則相沖突。另外,如果一個成員函數(shù)屬于一個不應(yīng)該屬于的類,很多潛在的使用者都會找不到這個函數(shù)。
            • 擁有清晰且簡單的語義:就像其他設(shè)計工作一樣,你必須遵守最小驚奇原則(the principle of least surprise)。讓常見的任務(wù)簡單易行。不常見的工作可行,但不會讓用戶過分關(guān)注。解決特殊問題時,不要讓解決方案沒有必要的過度通用。(比如,Qt3中的QMimeSourceFactory可以通過調(diào)用QImageLoader來實現(xiàn)不同的API。)
            • 直覺化:就像電腦上的其他東西一樣,API必須是直覺化的。不同的經(jīng)驗和背景會導(dǎo)致在判斷什么是直覺而什么不是時不同的感覺。如果一個中級用戶不讀文檔就可以使用(a semi-experienced user gets away without reading the documentation,沒懂這里的get away該怎么翻譯),并且一個程序員不懂API就可以理解縮寫的代碼,這種API就是直覺化的。
            • 易于記憶:讓API易于記憶,使用統(tǒng)一且精確的命名方法。使用可識別的模式和概念,并且避免縮寫。
            • 引導(dǎo)易讀的代碼(Lead to readable code):代碼一經(jīng)寫就,會讀(并且除錯和修改)多次。易讀的代碼可能會花點時間來寫,但是可以節(jié)省產(chǎn)品周期中的其他時間。

            最后,記住,不同類型的用戶會用到API的不同部分。雖然簡單的實例化一個Qt類是非常直覺化的,讓資深專家在試圖子類化之前讀一遍文檔,是很合理的。

            便利陷阱

            這是個常見的誤解:更好的API,用更少的代碼完成一件事。永遠記住代碼一次寫就,之后需要不斷的閱讀并理解。比如:

                QSlider *slider = new QSlider(12, 18, 3, 13, Qt::Vertical,
            0, "volume");
            

            遠比下面那樣難讀(甚至難寫):

                QSlider *slider = new QSlider(Qt::Vertical);
            slider->setRange(12, 18);
            slider->setPageStep(3);
            slider->setValue(13);
            slider->setObjectName("volume");
            

            布爾參數(shù)陷阱

            布爾參數(shù)通常會導(dǎo)致不易讀的代碼。更進一步,給一個已經(jīng)存在的函數(shù)加入一個布爾參數(shù),這常常是個錯誤。在Qt里,一個傳統(tǒng)的例子是repaint(),這個函數(shù)帶有一個布爾參數(shù),來標識是否擦除背景(默認擦除)。這讓代碼通常寫成:

                widget->repaint(false);
            

            初學者很容易把這句話理解成“別重畫”!

            這樣做是考慮到布爾參數(shù)可以減少一個函數(shù),避免代碼膨脹。事實上,這反而增加了代碼量。有多少Q(mào)t用戶真的記住了下面三行程序都是做什么的?

                widget->repaint();
            widget->repaint(true);
            widget->repaint(false);
            

            一個好一些的API可能看起來是這樣:

                widget->repaint();
            widget->repaintWithoutErasing();
            

            在Qt4里,我們重新設(shè)計了widget,使得用戶不再需要不重畫背景的重畫widget,來解決這個問題。Qt4原生支持雙緩存,廢掉了這個特性。

            這里還有一些例子:

                widget->setSizePolicy(QSizePolicy::Fixed,
            QSizePolicy::Expanding, true);
            textEdit->insert("Where's Waldo?", true, true, false);
            QRegExp rx("moc_*.c??", false, true);
            

            一個顯而易見的解決方法是,使用枚舉類型代替布爾參數(shù)。這正是我們在Qt4中QString大小寫敏感時的處理方法。比較:

                str.replace("%USER%", user, false);               // Qt 3
            str.replace("%USER%", user, Qt::CaseInsensitive); // Qt 4
            

            靜態(tài)多態(tài)

            相似的類應(yīng)該含有相似的API。在必要的時候——就是說,需要使用運行時多態(tài)的時候——這可以通過繼承實現(xiàn)。但是多態(tài)依舊會發(fā)生在設(shè)計時期。比如,如果你用QListBox代替QComboBox,或者用QSlider代替QSpinBox,你會發(fā)現(xiàn)相似的API使這種替換非常容易。這就是我們所說的“靜態(tài)多態(tài)”。

            靜態(tài)多態(tài)也使API和程序模式更容易記憶。作為結(jié)論,一組相關(guān)類使用相似的API,有時要比給每個類提供完美的單獨API,要好。

            (譯注:C++ 0x將要引入的concept,就是靜態(tài)多態(tài)的語法層實現(xiàn)。這個要比單獨的函數(shù)名相似更強大且易用。)

            命名的藝術(shù)

            命名,大概是設(shè)計API時唯一最重要的問題了。該怎么稱呼這個類?成員函數(shù)該叫什么?

            通用的命名規(guī)則

            一些規(guī)則通常對所有名字都是有用的。首先,就像我之前提到的,別用縮寫。甚至很明顯的縮寫,比如“prev”表示“previous”從長遠看也是不劃算的,因為用戶必須記住哪些詞是縮寫。

            如果API本身不一致,事情自然會變得很糟糕,比如, Qt3有activatePreviousWindow()和fetchPrev()。堅持“沒有縮寫”的規(guī)則更容易創(chuàng)建一致的API。

            另一個重要但更加微妙的規(guī)則是,在設(shè)計類的時候,必須盡力保證子類命名空間的干凈。在Qt3里,沒有很好的遵守這個規(guī)則。比如,拿QToolButton來舉例。如果你在Qt3里,對一個QToolButton調(diào)用name()、caption()、text()或者textLabel(),你希望做什么呢?你可以在Qt Designer里拿QToolButton試試:

            • name屬性繼承自QObject,表示一個對象用于除錯和測試的內(nèi)部名字。
            • caption屬性繼承自QWidget,表示窗口的標題,這個標題在視覺上對QToolButton沒有任何意義,因為他們總是跟隨父窗口而創(chuàng)建。
            • text屬性繼承自QButton,一般情況下是按鈕上現(xiàn)實的文字,除非useTextLabel為真。
            • textLabel在QToolButton里聲明,并且在useTextLabel為真時顯示在按鈕上。

            由于對可讀性的關(guān)注,name在Qt4里被稱作objectName,caption變成了windowsTitle,而在QToolButton里不再有單獨的textLabel屬性。

            給類命名

            標識一組類而不是單獨給每個類找個恰當?shù)拿帧1热纾琎t4里所有模式感知項目的視圖類(model-aware item view classes)都擁有-View的后綴(QListViewQTableViewQTreeView),并且對應(yīng)基于項目的類都用后綴-Widget代替(QListWidgetQTableWidgetQTreeWidget)。

            給枚舉類型及其值命名

            當聲明枚舉時,時刻記住,在C++(不像Java和C#)中,使用枚舉值不需要類型信息。下面的例子演示了給枚舉值起個太過常用的名字所引起的危害:

                namespace Qt
            {
            enum Corner { TopLeft, BottomRight, ... };
            enum CaseSensitivity { Insensitive, Sensitive };
            ...
            };
            tabWidget->setCornerWidget(widget, Qt::TopLeft);
            str.indexOf("$(QTDIR)", Qt::Insensitive);
            

            在最后一行,Insensitive是什么意思?一個用于命名枚舉值的指導(dǎo)思想是,在每個枚舉值里,至少重復(fù)一個枚舉類型名中的元素:

                namespace Qt
            {
            enum Corner { TopLeftCorner, BottomRightCorner, ... };
            enum CaseSensitivity { CaseInsensitive,
            CaseSensitive };
            ...
            };
            tabWidget->setCornerWidget(widget, Qt::TopLeftCorner);
            str.indexOf("$(QTDIR)", Qt::CaseInsensitive);
            

            當枚舉值可以用“或”連接起來當作一個標志時,傳統(tǒng)的做法是將“或”的結(jié)果作為一個int保存,這不是類型安全的。Qt4提供了一個模板類 QFlags<T>來實現(xiàn)類型安全,其中T是個枚舉類型。為了方便使用,Qt為很多標志類名提供了typedef,所以你可以使用類型 Qt::Alignment代替QFlags<Qt::AlignmentFlag>。

            為了方便,我們給枚舉類型單數(shù)的名字(這樣表示枚舉值一次只能有一個標志),而“標志”則使用復(fù)數(shù)名字。比如:

                enum RectangleEdge { LeftEdge, RightEdge, ... };
            typedef QFlags<RectangleEdge> RectangleEdges;
            

            有些情況下,“標志“類使用了單數(shù)的名字。這時,枚舉類使用-Flag做后綴:

                enum AlignmentFlag { AlignLeft, AlignTop, ... };
            typedef QFlags<AlignmentFlag> Alignment;
            

            (這里為啥不是把”標志“類用-Flag做后綴,而是把枚舉值做后綴呢?感覺有點混淆……)

            給函數(shù)和參數(shù)命名

            給函數(shù)命名的一個規(guī)則是,名字要明確體現(xiàn)出這個函數(shù)是否有副作用。在Qt3,常數(shù)函數(shù)QString::simplifyWhiteSpace()違反了這個原則,因為它返回類一個QString實例,而不是像名字所提示的那樣,更改了調(diào)用這個函數(shù)的實例本身。在Qt4,這個函數(shù)被重命名為QString::simplified()。

            參數(shù)名是程序員的重要信息來源,雖然在使用API時,并不直接展示在代碼里。由于現(xiàn)代IDE在程序員寫代碼時可以自動顯示參數(shù)名(就是自動感知或者自動補全之類的功能),值得花時間給頭文件里聲明的參數(shù)一個合適的名字,并且在文檔中也使用相同的名字。

            給布爾值設(shè)置函數(shù)(Setter)、提取函數(shù)(Getter)和屬性命名

            給布爾屬性的設(shè)置函數(shù)和提取函數(shù)一個合適的名字,總是非常痛苦的。提取函數(shù)應(yīng)該叫做checked()還是isChecked()?scrollBarsEnabled()還是areScrollBarEnabled()?

            在Qt4里,我們使用下列規(guī)則命名提取函數(shù):

            • 形容類的屬性使用is-前綴。比如:
              • isChecked()
              • isDown()
              • isEmpty()
              • isMovingEnable()
              另外,應(yīng)用到復(fù)數(shù)名詞的形容類屬性沒有前綴:
              • scrollBarsEnabled(),而不是areScrollBarsEnabled()
            • 動詞類的屬性不使用前綴,且不使用第三人稱(-s):
              • acceptDrops(),而不是acceptsDrops()
              • allColumnsShowFocus()
            • 名詞類的屬性,通常沒有前綴:
              • autoCompletion(),而不是isAutoCompletion()
              • boundaryChecking()
              有時,沒有前綴就會引起誤解,這種情況使用前綴is-:
              • isOpenGLAvailable(),而不是openGL()
              • isDialog(),而不是dialog()
              (通過調(diào)用dialogue()方法,正常情況下會期望返回一個QDialog*的實例。)

            設(shè)置函數(shù)名字繼承自提取函數(shù)名,只是移掉了所有前綴,并使用set-做前綴,比如:setDown()還有setScrollBarsEnabled()。屬性的名字與提取函數(shù)相同,只是去掉了前綴。

            指針還是引用?

            傳出參數(shù)的最佳選擇是什么,指針還是引用?

                void getHsv(int *h, int *s, int *v) const
            void getHsv(int &h, int &s, int &v) const
            

            大部分C++書推薦在能用引用的地方就用引用,這是因為一般認為引用比指針更“安全且好用”。然而,在奇趣(Trolltech),我們傾向使用指針,因為這讓代碼更易讀。比較:

                color.getHsv(&h, &s, &v);
            color.getHsv(h, s, v);
            

            只有第一行能清楚的說明,在函數(shù)調(diào)用后,h、s和v將有很大幾率被改動。

            例子:QProgressBar

            為了展示如何實際應(yīng)用這些概念,我們將學習Qt3中的API QProgressBar并和Qt4里相通的API做比較。在Qt3里:

                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);
            ...
            };
            

            API相當復(fù)雜,且不統(tǒng)一。比如,僅從名字reset()并不能理解其作用,setTotalSteps()和setProgress()是緊耦合的。

            改進API的關(guān)鍵,是注意到QProgressBar和Qt4的QAbstractSpinBox類及其子類QSpinBoxQSliderQDial很相似。解決方法?用minimum、maximum和value代替progress和totalSteps。加入alueChanged()信號。加入setRange()函數(shù)。

            之后觀察progressString、percentage和indicator實際都指一個東西:在進度條上顯示的文字。一般來說文字是百分比信息,但是也可以使用setIndicator()設(shè)為任意字符。下面是新的API:

                virtual QString text() const;
            void setTextVisible(bool visible);
            bool isTextVisible() const;
            

            默認的文字信息是百分比信息。文字信息可以藉由重新實現(xiàn)text()而改變。

            在Qt3 API中,setCenterIndicator()和setIndicatorFollowStyle()是兩個影響對齊的函數(shù)。他們可以方便的由一個函數(shù)實現(xiàn),setAlignment():

                void setAlignment(Qt::Alignment alignment);
            

            如果程序員不調(diào)用setAlignment(),對齊方式基于當前的風格。對于基于Motif的風格,文字將居中顯示;對其他風格,文字將靠在右邊。

            這是改進后的QProgressBar API:

                class QProgressBar : public QWidget
            {
            ...
            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);
            ...
            };
            

            如何把API設(shè)計好(原文是How to Get APIs Right,我總想成We do APIs right……)

            API需要質(zhì)量保證。第一個修訂版不可能是正確的;你必須做測試。寫些用例:看看那些使用了這些API的代碼,并驗證代碼是否易讀。

            其他的技巧包括讓別的人分別在有文檔和沒有文檔的情況下,使用這些API;或者為API類寫文檔(包括類的概述和獨立的函數(shù))。

            當你卡住時,寫文檔也是一種獲得好名字的方法:僅僅是嘗試把條目(類,函數(shù),枚舉值,等等呢個)寫下來并且使用你寫的第一句話作為靈感。如果你不能找到一個精確的名字,這常常說明這個條目不應(yīng)該存在。如果所有前面的事情都失敗了并且你確認這個概念的存在,發(fā)明一個新名字。畢竟,“widget”、 “event”、“focus”和“buddy”這些名字就是這么來的。

              回復(fù)  更多評論
              

            精品久久无码中文字幕| 精品综合久久久久久88小说| 国产精品VIDEOSSEX久久发布| 国产精品一久久香蕉国产线看| 欧美激情精品久久久久久久九九九 | 国内精品欧美久久精品| 国产精品久久自在自线观看| 人妻无码久久一区二区三区免费 | 亚洲欧洲中文日韩久久AV乱码| 偷偷做久久久久网站| 无码人妻久久一区二区三区免费 | 亚洲va久久久噜噜噜久久 | 久久国产午夜精品一区二区三区| 亚洲精品NV久久久久久久久久 | 久久精品国产第一区二区三区| 久久久人妻精品无码一区 | 伊人久久亚洲综合影院| 久久国产成人午夜AV影院| 久久久久久极精品久久久| 亚洲精品乱码久久久久久不卡| 女人高潮久久久叫人喷水| 久久精品国产福利国产秒| 久久国产精品久久精品国产| 91精品国产综合久久四虎久久无码一级| 一本大道久久香蕉成人网 | 国产精品久久久久免费a∨| 伊人久久五月天| 日韩精品久久无码中文字幕| 久久精品www| 777午夜精品久久av蜜臀| 夜夜亚洲天天久久| 中文国产成人精品久久亚洲精品AⅤ无码精品 | 国产精品一久久香蕉产线看| 精品水蜜桃久久久久久久| 亚洲国产成人精品无码久久久久久综合| 性高湖久久久久久久久| 国产一区二区精品久久| 一日本道伊人久久综合影| 国产成人久久精品激情 | 一级女性全黄久久生活片免费 | 国产美女久久精品香蕉69|