Designing Qt-Style C++ APIs

Материал из Wiki.crossplatform.ru

(Различия между версиями)
Перейти к: навигация, поиск
(Указатели или ссылки?)
(Наглядный пример: QProgressBar)
 
(32 промежуточные версии не показаны)
Строка 2: Строка 2:
<b>от Матиаса Этриха</b>
<b>от Матиаса Этриха</b>
-
<b> Мы сделали существенное исследование в Trolltech в плане улучшении качества разработки Qt. В этой статье я хочу поделиться некоторыми нашими выводами и представить принципы, которые мы использовали при разработке Qt4, и показать вам, как применять их в вашем коде.</b>
+
<b>Мы закончили важное исследование в Trolltech, связанное с улучшением процесса разработки Qt. В этой статье я хочу поделиться некоторыми нашими находками и представить принципы, которые мы использовали при проектировании Qt4, а также показать вам, как использовать их в вашем коде.</b>
__TOC__
__TOC__
-
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.
+
Проектирование интерфейсов программирования - сложная задача. Это такое же сложное искусство, как проектирование языков программирования. Существует множество различных принципов построения API (Application Programming Interface, интерфейс прикладного программирования), многие из которых конфликтуют друг с другом.
-
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&nbsp;Desktop Environment, goes even further and extends Qt with many add-on libraries that implement hundreds of additional classes.
+
Перед расцветом объектно-ориентированных языков программирования код многократного использования писался главным образом производителями библиотек, а не разработчиками ПО. В мире Qt эта ситуация основательно изменилась. Программирование на Qt - это постоянная разработка новых компонентов. Типичное приложение на Qt содержит ряд собственных компонентов, которые повторно используются во всех частях программы. Часто эти же компоненты являются частью другого приложения. В KDE пошли дальше и расширили Qt множеством библиотек, которые реализуют сотни дополнительных классов.
 +
 
 +
Но что же определяет хороший и эффективный C++ API? Хороший он или плохой, зависит от многих факторов - например, задача и специфические цели. Хороший API имеет ряд характерных свойств. Наличие некоторых, как правило, является желательным, а наличие других определяется спецификой конкретной сферы.
-
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.
 
<div id="sixcharacteristicsofgoodapis"></div>
<div id="sixcharacteristicsofgoodapis"></div>
== Шесть характеристик хорошего API ==
== Шесть характеристик хорошего API ==
-
API программисту, что GUI конечному пользователю. Буква 'P' в API означает "Программист", а не "Программа", с тем чтобы подчеркнуть тот факт, что API, используются программистами, которые являются людьми.
+
API для программиста - это тоже самое, что и пользовательский интерфейс (GUI) для конечного пользователя. Под буквой 'P' в сокращении API имеется ввиду "Программист", не "Программа", подчеркивая тот факт, что API используются программистами (которые являются людьми).
-
We believe APIs should be minimal and complete, have clear and simple semantics, be intuitive, be easy to memorize, and lead to readable code.
+
Мы считаем, что API должны быть компактным и законченным, иметь ясную и простую семантику, быть интуитивными, легко запоминаться, и приводить к читаемому коду.
-
* <b>Be minimal:</b> A minimal API is one that has as few public members per class and as few classes as possible. This makes it easier to understand, remember, debug, and change the API.
+
* <b>Быть компактным:</b> Компактный API подразумевает минимальное количество как публичных членов класса, так и самих классов. Это позволяет легче понимать, запоминать, отлаживать и изменять API.
-
* <b>Be complete:</b> A complete API means the expected functionality should be there. This can conflict with keeping it minimal. Also, if a member function is in the wrong class, many potential users of the function won't find it.
+
* <b>Быть законченным:</b> Законченный API подразумевает, что в нем заключен ожидаемый функционал. Это правило может конфликтовать с компактностью. Также, если функция-член находится не в том классе [в котором ожидалось], то многие потенциальные пользователи не найдут эту функцию.
-
* <b>Have clear and simple semantics:</b> As with other design work, you should apply the principle of least surprise. Make common tasks easy. Rare tasks should be possible but not the focus. Solve the specific problem; don't make the solution overly general when this is not needed. (For example, [[Qt:Документация_4.3.2/q3mimesourcefactory | QMimeSourceFactory]] in Qt 3 could have been called QImageLoader and have a different API.)
+
* <b>Иметь ясную и простую семантику:</b> Как и в других видах проектирования, вы должны идти по пути наименьшего удивления. Делайте выполнение стандартных задач простым. Выполнение редких задач также должно быть возможным, а не являться фокусом. Решайте конкретную задачу; не делайте решение слишком общим, если этого действительно не требуется. (Например, [[Qt:Документация_4.3.2/q3mimesourcefactory | QMimeSourceFactory]] в Qt3 мог бы называться QImageLoader и иметь другой API)
-
* <b>Be intuitive:</b> As with anything else on a computer, an API should be intuitive. Different experience and background leads to different perceptions on what is intuitive and what isn't. An API is intuitive if a semi-experienced user gets away without reading the documentation, and if a programmer who doesn't know the API can understand code written using it.
+
* <b>Быть интуитивным:</b> как и другие вещи в компьютере, API должен быть интуитивным. Опыт и различные предпосылки приводят к различному восприятию того, что является интуитивным, а что - нет. API является интуитивным, если пользователь среднего класса начинает работу без чтения документации, и если программист, не знающий API, может понять исходный код, написанный с использованием этого API.
-
* <b>Be easy to memorize:</b> To make the API easy to remember, choose a consistent and precise naming convention. Use recognizable patterns and concepts, and avoid abbreviations.
+
* <b>Быть легко запоминаемым:</b> Чтобы сделать API простым для запоминания, выберите согласованную и точную политику именования. Используйте узнаваемые шаблоны и концепции, и избегайте сокращений.
-
* <b>Lead to readable code:</b> Code is written once, but read (and debugged and changed) many times. Readable code may sometimes take longer to write, but saves time throughout the product's life cycle.
+
* <b>Способствовать лёгкой читаемости кода:</b> Код пишется однажды, но читается (и отлаживается, и изменяется) множество раз. Иногда написание легко читаемого кода может занять много времени, но это в результате сэкономит время в течении всего жизненного цикла продукта.
-
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.
+
Наконец, имейте в виду, что разные пользователи будут использовать различные части вашего API. В то время как использование экземпляров классов Qt должно быть интуитивно понятным, логично предполагать, что пользователь прочтет документацию перед использованием класса.  
Строка 32: Строка 33:
== Ловушки удобства ==
== Ловушки удобства ==
-
Общее заблуждение - чем меньшим количеством кода вы добиваетесь чего-то, тем лучше API. Помните, что код написаный уже не раз, должен быть понятен снова и снова. Например,
+
Существует распространенное заблуждение, что чем меньше кода вы пишите для решения задачи, тем лучше API. Имейте ввиду, что код пишется однажды, но должен быть понят много раз. Например
-
<pre>
+
{{code|qt|code=
QSlider *slider = new QSlider(12, 18, 3, 13, Qt::Vertical,
QSlider *slider = new QSlider(12, 18, 3, 13, Qt::Vertical,
                               0, "volume");
                               0, "volume");
-
</pre>
+
}}
-
является более трудным для чтения (и даже для написания) чем
+
намного сложнее прочитать (и даже написать), чем
-
<pre>
+
{{code|qt|code=
QSlider *slider = new QSlider(Qt::Vertical);
QSlider *slider = new QSlider(Qt::Vertical);
-
slider-&gt;setRange(12, 18);
+
slider->setRange(12, 18);
-
slider-&gt;setPageStep(3);
+
slider->setPageStep(3);
-
slider-&gt;setValue(13);
+
slider->setValue(13);
-
slider-&gt;setObjectName("volume");
+
slider->setObjectName("volume");
-
</pre>
+
}}
<div id="thebooleanparametertrap"></div>
<div id="thebooleanparametertrap"></div>
== Ловушки булевых параметров ==
== Ловушки булевых параметров ==
-
Булевые параметры часто приводят к нечитаемому коду. В частности, почти всегда является ошибкой добавлять <tt>булевый</tt> параметр к существующей функции. В Qt, традиционным примером является функция <tt>repaint()</tt>, которая принимает не обязательный <tt>булевый</tt> параметр определяющий должен ли фон быть стерт (по умолчанию) или нет. Это приводит к такому коду:
+
Булевые параметры часто приводят к не читаемому коду. В частности, практически всегда будет ошибкой добавление <tt>булевого</tt> параметра в функцию. В Qt, традиционным примером является функция <tt>repaint()</tt>, которая принимает не обязательный <tt>булевый</tt> параметр, указывающий, должен ли очищаться задний фон (по умолчанию) или нет. Это приводит к такому коду:
-
<pre>
+
{{code|qt|code=
-
widget-&gt;repaint(false);
+
widget->repaint(false);
-
</pre>
+
}}
-
который новички могут прочитать со смыслом: "Не перерисовывать!"
+
который новички могут прочитать как "Не перерисовывать!"
-
Размышление состоит, очевидно, в том, что <tt>булевый</tt> параметр сохраняет одну функцию, тем самым способствуя снижению раздувания кода. В действительности, это увеличивает раздувание кода; как много пользователей Qt знаю наизусть, что делает каждая из следующих трех строк?
+
Идея, по-видимому, состоит в том, что применение булевого параметра избавит от создания еще одной функции, что, в свою очередь, приведет к сокращению программного кода. На деле же - наоборот; сколько пользователей Qt "спиной почувствует", что делает каждая из следующих строчек?  
-
<pre>
+
{{code|qt|code=
-
widget-&gt;repaint();
+
widget->repaint();
-
widget-&gt;repaint(true);
+
widget->repaint(true);
-
widget-&gt;repaint(false);
+
widget->repaint(false);
-
</pre>
+
}}
-
Несколько лучшее API, могло бы быть таким:
+
Более качественный API:
-
<pre>
+
{{code|qt|code=
-
widget-&gt;repaint();
+
widget->repaint();
-
widget-&gt;repaintWithoutErasing();
+
widget->repaintWithoutErasing();
-
</pre>
+
}}
-
В Qt 4, мы решили проблему просто удалив возможность перерисовки без стирания виджета. Естественная поддержка двойной буферизации в Qt4 делает эту особенность устаревшей.
+
В Qt4 мы решили эту проблему просто исключив возможность перерисовки без очистки виджета. Qt4 поддерживает двойную буферизацию, что делает это свойство не актуальным.
-
Здесь даются несколько примеров:
+
Еще ряд примеров:
-
<pre>
+
{{code|qt|code=
-
widget-&gt;setSizePolicy(QSizePolicy::Fixed,
+
widget->setSizePolicy(QSizePolicy::Fixed,
                       QSizePolicy::Expanding, true);
                       QSizePolicy::Expanding, true);
-
textEdit-&gt;insert("Where's Waldo?", true, true, false);
+
textEdit->insert("Where's Waldo?", true, true, false);
QRegExp rx("moc_*.c??", false, true);
QRegExp rx("moc_*.c??", false, true);
-
</pre>
+
}}
-
Очевидное решение состоит в том, чтобы заменить <tt>булевый</tt> параметр перечислением. Это то, что мы сделали в Qt4, с регистрозависимостью в [[Qt:Документация_4.3.2/qstring | QString]]. Сравните:
+
Очевидным решением является замена булевых параметров перечислениями (enum types). Так мы поступили с регистрозависимыми строками [[Qt:Документация_4.3.2/qstring | QString]]. Сравните:
-
<pre>
+
{{code|qt|code=
str.replace("%USER%", user, false);              // Qt 3
str.replace("%USER%", user, false);              // Qt 3
str.replace("%USER%", user, Qt::CaseInsensitive); // Qt 4
str.replace("%USER%", user, Qt::CaseInsensitive); // Qt 4
-
</pre>
+
}}
Строка 88: Строка 89:
== Статический полиморфизм ==
== Статический полиморфизм ==
-
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 [[Qt:Документация_4.3.2/qcombobox | QComboBox]], or a QSlider with a [[Qt:Документация_4.3.2/qspinbox | QSpinBox]], you'll find that the similarity of APIs makes this replacement very easy. This is what we call "static polymorphism".  
+
Похожие классы должны иметь схожий API. Этого можно добиться с помощью наследования, где это имеет смысл, то есть когда используется полиморфизм времени выполнения (run-time polymorphism). Но полиморфизм также имеет место быть и при проектировании. Например, если бы вы заменили QListBox на [[Qt:Документация_4.3.2/qcombobox | QComboBox]] или QSlider на [[Qt:Документация_4.3.2/qspinbox | QSpinBox]], то обнаружили бы, что похожие API способствовали более легкой замене. Вот что мы называем "статическим полиморфизмом".
-
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.
+
Статический полиморфизм также позволяет легче запомнить API и шаблоны программирования. Как следствие, похожие API для ряда связанных классов является более предпочтительным, чем индивидуальный безупречный API для каждого класса в отдельности.
<div id="theartofnaming"></div>
<div id="theartofnaming"></div>
-
== 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?
+
== Искусство задания имен ==
 +
Задание имен, возможно, является единственным наиболее важным вопросом при проектировании API. Каким образом задавать имена классов? Как назвать методы класса?
 +
 
=== Общие правила именования ===
=== Общие правила именования ===
-
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.
+
Ряд правил применим в равной степени ко всем типам имен. Во-первых, как я упоминал ранее, не используйте сокращения. Даже явные сокращения, например, "prev" для "previous" не окажут должного эффекта, так как пользователь должен запомнить, что слова сокращены.
-
Things naturally get worse if the API itself is inconsistent; for example, Qt 3 has <tt>activatePreviousWindow()</tt> and <tt>fetchPrev()</tt>. Sticking to the "no abbreviation" rule makes it simpler to create consistent APIs.
+
Дела пойдут плохо, если API не логичный; например, в Qt3 есть <tt>activatePreviousWindow()</tt> и <tt>fetchPrev()</tt>. Правило "без сокращений" поможет написать логичный API.
-
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 [[Qt:Документация_4.3.2/qtoolbutton | QToolButton]]. If you call <tt>name()</tt>, <tt>caption()</tt>, <tt>text()</tt>, or <tt>textLabel()</tt> on a [[Qt:Документация_4.3.2/qtoolbutton | QToolButton]] in Qt 3, what do you expect? Just try playing around with a [[Qt:Документация_4.3.2/qtoolbutton | QToolButton]] in <i>Qt Designer</i>:
+
Другое важное, но не очевидное правило, при проектировании классов - вы должны постараться сохранить ясное пространство имен подклассов. В Qt3 не всегда следовали этому правилу. Чтобы продемонстрировать это, мы приведем в пример класс [[Qt:Документация_4.3.2/qtoolbutton | QToolButton]]. Что вы ожидаете при вызове методов <tt>name()</tt>, <tt>caption()</tt>, <tt>text()</tt> или <tt>textLabel()</tt> класса [[Qt:Документация_4.3.2/qtoolbutton | QToolButton]]? Просто попробуйте поиграть с [[Qt:Документация_4.3.2/qtoolbutton | QToolButton]] в Дизайнере форм:
-
* The <tt>name</tt> property is inherited from [[Qt:Документация_4.3.2/qobject | QObject]] and refers to an internal object name that can be used for debugging and testing.
+
-
* The <tt>caption</tt> property is inherited from [[Qt:Документация_4.3.2/qwidget | QWidget]] and refers to the window title, which has virtually no meaning for [[Qt:Документация_4.3.2/qtoolbutton | QToolButton]]s, since they usually are created with a parent.
+
-
* The <tt>text</tt> property is inherited from QButton and is normally used on the button, unless <tt>useTextLabel</tt> is <tt>true</tt>.
+
-
* The <tt>textLabel</tt> property is declared in [[Qt:Документация_4.3.2/qtoolbutton | QToolButton]] and is shown on the button if <tt>useTextLabel</tt> is <tt>true</tt>.
+
-
In the interest of readability, <tt>name</tt> is called <tt>objectName</tt> in Qt 4, <tt>caption</tt> has become <tt>windowTitle</tt>, and there is no longer any <tt>textLabel</tt> property distinct from <tt>text</tt> in [[Qt:Документация_4.3.2/qtoolbutton | QToolButton]].
+
* Свойство <tt>name</tt> наследуется от [[Qt:Документация_4.3.2/qobject | QObject]] и указывает на имя внутреннего объекта, которое может быть использовано для отладки и тестирования.
 +
* Свойство <tt>caption</tt> наследуется от [[Qt:Документация_4.3.2/qwidget | QWidget]] и указывает на заголовок окна, который не имеет значения для [[Qt:Документация_4.3.2/qtoolbutton | QToolButton]], так как обычно он создается внутри родительского виджета.
 +
* Свойство <tt>text</tt> наследуется от QButton и обычно используется на кнопке, если <tt>useTextLabel</tt> ложно.
 +
* Свойство <tt>textLabel</tt> объявленно в [[Qt:Документация_4.3.2/qtoolbutton | QToolButton]] и отображается на кнопке, если <tt>useTextLabel</tt> истинно.
 +
В Qt4 в интересах удобочитаемости свойство <tt>name</tt> у [[Qt:Документация_4.3.2/qtoolbutton | QToolButton]] названо <tt>objectName</tt>, свойство <tt>caption</tt> стало <tt>windowTitle</tt> и <tt>textLabel</tt> изменено на <tt>text</tt>.
-
=== Именование классов ===
 
-
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 <tt>View</tt> ([[Qt:Документация_4.3.2/qlistview | QListView]], [[Qt:Документация_4.3.2/qtableview | QTableView]], and [[Qt:Документация_4.3.2/qtreeview | QTreeView]]), and the corresponding item-based classes are suffixed with <tt>Widget</tt> instead ([[Qt:Документация_4.3.2/qlistwidget | QListWidget]], [[Qt:Документация_4.3.2/qtablewidget | QTableWidget]], and [[Qt:Документация_4.3.2/qtreewidget | QTreeWidget]]).
 
 +
=== Имена классов ===
 +
Определите группы классов вместо того, чтобы придумывать имя для каждого отдельного класса. Например, все классы, основанные на поэлементном представлении данных, в архитектуре Модель/Представление (Model/View) имеют суффикс View ([[Qt:Документация_4.3.2/qlistview | QListView]], [[Qt:Документация_4.3.2/qtableview | QTableView]] и [[Qt:Документация_4.3.2/qtreeview | QTreeView]]), а соответствующие им виджеты имеют суффикс Widget ([[Qt:Документация_4.3.2/qlistwidget | QListWidget]], [[Qt:Документация_4.3.2/qtablewidget | QTableWidget]] и [[Qt:Документация_4.3.2/qtreewidget | QTreeWidget]]).
-
=== Именование перечисление и значений ===
+
=== Имена типов перечислений и их значений ===
-
When declaring enums, we must keep in mind that in C++ (unlike in Java or C#), the enum values are used <i>without</i> the type. The following example shows illustrates the dangers of giving too general names to the enum values:
+
При объявлении перечисления нужно помнить, что в C++ (в отличии от Java или C#), их значения не имеют типа. Следующий пример показывает опасность задания слишком общих имен значений перечисления:
-
<pre>
+
{{code|qt|code=
namespace Qt
namespace Qt
{
{
Строка 126: Строка 129:
};
};
-
tabWidget-&gt;setCornerWidget(widget, Qt::TopLeft);
+
tabWidget->setCornerWidget(widget, Qt::TopLeft);
str.indexOf("$(QTDIR)", Qt::Insensitive);
str.indexOf("$(QTDIR)", Qt::Insensitive);
-
</pre>
+
}}
-
In the last line, what does <tt>Insensitive</tt> 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:
+
-
<pre>
+
Что подразумевается под <tt>Insensitive</tt> в последней строке? Одно из правил задания имени значению перечисления состоит в том, чтобы в имени значения содержался хотя бы один элемент имени самого перечисления:
 +
 
 +
{{code|qt|code=
namespace Qt
namespace Qt
{
{
Строка 140: Строка 144:
};
};
-
tabWidget-&gt;setCornerWidget(widget, Qt::TopLeftCorner);
+
tabWidget->setCornerWidget(widget, Qt::TopLeftCorner);
str.indexOf("$(QTDIR)", Qt::CaseInsensitive);
str.indexOf("$(QTDIR)", Qt::CaseInsensitive);
-
</pre>
+
}}
-
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 <tt>int</tt>, which isn't type-safe. Qt 4 offers a template class QFlags&lt;T&gt;, where <tt>T</tt> is the enum type. For convenience, Qt provides typedefs for the flag type names, so you can type <tt>Qt::Alignment</tt> instead of QFlags&lt;Qt::AlignmentFlag&gt;.
+
-
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:
+
Когда значения перечисления могут быть использованы с оператором 'или' и как флаги, традиционным решением является сохранение результата операции 'или' в переменной типа <tt>int</tt>, который не является типобезопасным. Вместо этого Qt4 предлагает шаблон <tt>QFlags<T></tt>, где <tt>T</tt> - тип перечисления. А для удобства в Qt есть ряд typedef'ов, так что вы можете писать <tt>Qt::Alignment</tt> вместо <tt>QFlags<Qt::AlignmentFlag></tt>.
-
<pre>
+
По соглашению мы даём имена типам в единственном числе (так как он может содержать один флаг в один момент времени), а имена флагов - во множественном числе. Например:
 +
 
 +
{{code|qt|code=
enum RectangleEdge { LeftEdge, RightEdge, ... };
enum RectangleEdge { LeftEdge, RightEdge, ... };
-
typedef QFlags&lt;RectangleEdge&gt; RectangleEdges;
+
typedef QFlags<RectangleEdge> RectangleEdges;
-
</pre>
+
}}
-
In some cases, the "flags" type has a singular name. In that case, the enum type is suffixed with <tt>Flag</tt>:
+
-
<pre>
+
В некоторых случаях типы флагов имеют имена в единственном числе, а имена типов имеют приставку <tt>Flag</tt>:
-
enum AlignmentFlag { AlignLeft, AlignTop, ... };
+
-
typedef QFlags&lt;AlignmentFlag&gt; Alignment;
+
-
</pre>
+
-
=== Именование функций и параметров ===
+
{{code|qt|code=
-
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&nbsp;3, the const function [[Qt:Документация_4.3.2/qstring | QString]]::simplifyWhiteSpace() violated this rule, since it returned a [[Qt:Документация_4.3.2/qstring | QString]] instead of modifying the string on which it is called, as the name suggests. In Qt 4, the function has been renamed [[Qt:Документация_4.3.2/qstring | QString]]::simplified().
+
enum AlignmentFlag { AlignLeft, AlignTop, ... };
 +
typedef QFlags<AlignmentFlag> Alignment;
 +
}}
-
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.
+
=== Имена функций и параметров ===
 +
Первое правило в при задании имени функции: из имени функции должно быть ясно, имеет ли она побочные эффекты. В Qt3, константная функция <tt>QString::simplifyWhiteSpace()</tt> нарушает это правило, так как она возвращает [[Qt:Документация_4.3.2/qstring | QString]] вместо того, чтобы модифицировать строку, для которой она вызывается (как предполагается, исходя из имени). В Qt4 эта функция переименована в <tt>QString::simplified()</tt>.
 +
Имена параметров являются важным источником информации для программиста, даже если он не видит кода, который использует API. Так как современные IDE показывают названия параметров функций, хорошей мыслью будет задать внятные имена параметрам в заголовочных файлах и использовать эти имена в документации.
-
=== Именование булевых геттеров, сеттеров, и свойств ===
+
=== Имена булевых геттеров, сеттеров, и свойств ===
-
Поиск хороших имен для геттера и сеттера <tt>булевого</tt> параметра - всегда головная боль. Должен ли геттер называться <tt>checked()</tt> или <tt>isChecked()</tt>? <tt>scrollBarsEnabled()</tt> или <tt>areScrollBarEnabled()</tt>?
+
Поиск хороших имен для геттера и сеттера <tt>булевого</tt> параметра особо тяжелое занятие. Должен ли геттер называться <tt>checked()</tt> или <tt>isChecked()</tt>? <tt>scrollBarsEnabled()</tt> или <tt>areScrollBarEnabled()</tt>?
-
В Qt4, мы используем следующие руководящие принципы для именования геттерных функций:
+
В Qt4 мы следуем следующим правилам, присваивая имя геттерам:
-
* Прилагательные имеют приставку <tt>is</tt>-. Например:
+
* К прилагательным добавляется приставка <tt>is</tt>-. Например:
** <tt>isChecked()</tt>  
** <tt>isChecked()</tt>  
** <tt>isDown()</tt>  
** <tt>isDown()</tt>  
Строка 174: Строка 179:
** <tt>isMovingEnabled()</tt>  
** <tt>isMovingEnabled()</tt>  
-
*Однако, у прилагательных, относящихся к существительному во множественном числе, нет никакой приставки:
+
*Хотя прилагательные, относящиеся к существительному во множественном числе, остаются неизменны:
** <tt>scrollBarsEnabled()</tt>, вместо <tt>'''are'''ScrollBarsEnabled()</tt>  
** <tt>scrollBarsEnabled()</tt>, вместо <tt>'''are'''ScrollBarsEnabled()</tt>  
-
* Глаголы не имеют никакой приставки и не используют третье лицо (-<tt>s</tt>):
+
*Глаголы не имеют приставок и не используется третье лицо (-<tt>s</tt>):
** <tt>acceptDrops()</tt>, вместо <tt>accept'''s'''Drops()</tt>  
** <tt>acceptDrops()</tt>, вместо <tt>accept'''s'''Drops()</tt>  
** <tt>allColumnsShowFocus()</tt>  
** <tt>allColumnsShowFocus()</tt>  
-
* У существительных вообще нет никакой приставки:
+
*Существительные в основном не имеют приставок:
** <tt>autoCompletion()</tt>, вместо <tt>'''is'''AutoCompletion()</tt>  
** <tt>autoCompletion()</tt>, вместо <tt>'''is'''AutoCompletion()</tt>  
** <tt>boundaryChecking()</tt>  
** <tt>boundaryChecking()</tt>  
-
* Иногда, отсутствие приставки вводит в заблуждение, в этом случае мы добавляем приставку <tt>is</tt>-:
+
* Иногда, отсутствие приставки вводит в заблуждение, тогда мы используем приставку <tt>is</tt>-:
** <tt>isOpenGLAvailable()</tt>, вместо <tt>openGL()</tt>  
** <tt>isOpenGLAvailable()</tt>, вместо <tt>openGL()</tt>  
-
** <tt>isDialog()</tt>, вместо <tt>dialog()</tt>  
+
** <tt>isDialog()</tt>, вместо <tt>dialog()</tt><br />(От функции <tt>dialog()</tt>, мы обычно ожидаем, что она вернет [[Qt:Документация_4.3.2/qdialog | QDialog]]<tt>*</tt>.)
-
*(От функции называемой <tt>dialog()</tt>, мы обычно ожидаем, что она вернет [[Qt:Документация_4.3.2/qdialog | QDialog]] <tt>*</tt>.)
+
Название сеттера получается из имени геттера, путем замены приставки <tt>is</tt> на <tt>set</tt>, например, <tt>'''set'''Down()</tt> и <tt>'''set'''ScrollBarsEnabled()</tt>. Имена свойств определяются подобно именам геттеров, но без приставки <tt>is</tt>.  
-
 
+
-
Название сеттера получается из имени геттера, удаляя все приставки, и помещая <tt>set</tt> перед именем, например, <tt>'''set'''Down()</tt> и <tt>'''set'''ScrollBarsEnabled()</tt>. Название свойства - такое же как и у геттера, но без приставки <tt>is</tt>.
+
Строка 199: Строка 202:
Что лучше для выходных параметров, указатели или ссылки?
Что лучше для выходных параметров, указатели или ссылки?
-
<pre>
+
{{code|qt|code=
void getHsv(int *h, int *s, int *v) const
void getHsv(int *h, int *s, int *v) const
-
void getHsv(int &amp;h, int &amp;s, int &amp;v) const
+
void getHsv(int &h, int &s, int &v) const
-
</pre>
+
}}
-
Большинство книг по C++ рекомендуют ссылки, когда это возможно, в соответствии с общим мнением о том, что ссылки являются "безопасными и лучше", чем указатели. В противоположность этому, в Trolltech, мы, как правило, предпочитаем указатели, поскольку они делают пользовательский код более читаемым. Сравните:
+
 
-
<pre>
+
Большинство книг по C++ рекомендуют использовать ссылки, где это возможно, исходя из того, что ссылки "безопаснее и красивее" указателей. В противоположность этому, в Trolltech, мы, как правило, предпочитаем указатели, поскольку они делают код более наглядным. Сравните:
-
color.getHsv(&amp;h, &amp;s, &amp;v);
+
 
 +
{{code|qt|code=
 +
color.getHsv(&h, &s, &v);
color.getHsv(h, s, v);
color.getHsv(h, s, v);
-
</pre>
+
}}
-
Только первая строка дает понять, что существует высокая вероятность того, что <tt>h</tt>, <tt>s</tt> и <tt>v</tt> будут изменены вызовающей функцией.
+
 
 +
Только по первой строке можно понять, что с большой вероятностью <tt>h</tt>, <tt>s</tt> и <tt>v</tt> будут модифицированы в методе <tt>getHsv()</tt>.
<div id="casestudyqprogressbar"></div>
<div id="casestudyqprogressbar"></div>
-
== Case Study: QProgressBar ==
+
== Наглядный пример: QProgressBar ==
-
To show some of these concepts in practice, we'll study the [[Qt:Документация_4.3.2/qprogressbar | QProgressBar]] API of Qt 3 and compare it to the Qt 4 API. In Qt 3:
+
Чтобы продемонстрировать некоторые принципы на практике, мы рассмотрим API класса [[Qt:Документация_4.3.2/qprogressbar | QProgressBar]] в Qt3 и сравним с API Qt4. В Qt3:
-
<pre>
+
{{code|qt|code=
class QProgressBar : public QWidget
class QProgressBar : public QWidget
{
{
Строка 223: Строка 229:
     int progress() const;
     int progress() const;
-
     const QString &amp;progressString() const;
+
     const QString &progressString() const;
     bool percentageVisible() const;
     bool percentageVisible() const;
     void setPercentageVisible(bool);
     void setPercentageVisible(bool);
Строка 240: Строка 246:
protected:
protected:
-
     virtual bool setIndicator(QString &amp;progressStr,
+
     virtual bool setIndicator(QString &progressStr,
                               int progress,
                               int progress,
                               int totalSteps);
                               int totalSteps);
     ...
     ...
};
};
-
</pre>
+
}}
-
The API is quite complex and inconsistent; for example, it's not clear from the naming that <tt>reset()</tt>, <tt>setTotalSteps()</tt>, and <tt>setProgress()</tt> are tightly related.
+
API достаточно сложен и противоречив; например, из имен <tt>reset()</tt>, <tt>setTotalSteps()</tt>, и <tt>setProgress()</tt> не ясно, что они тесно связаны.
-
The key to improve the API is to notice that [[Qt:Документация_4.3.2/qprogressbar | QProgressBar]] is similar to Qt 4's [[Qt:Документация_4.3.2/qabstractspinbox | QAbstractSpinBox]] class and its subclasses, [[Qt:Документация_4.3.2/qspinbox | QSpinBox]], QSlider and [[Qt:Документация_4.3.2/qdial | QDial]]. The solution? Replace <tt>progress</tt> and <tt>totalSteps</tt> with <tt>minimum</tt>, <tt>maximum</tt> and <tt>value</tt>. Add a <tt>valueChanged()</tt> signal. Add a <tt>setRange()</tt> convenience function.
+
Для улучшения данного API следует сделать акцент на том, что [[Qt:Документация_4.3.2/qprogressbar | QProgressBar]] похож на [[Qt:Документация_4.3.2/qabstractspinbox | QAbstractSpinBox]] в Qt4 и его подклассы: [[Qt:Документация_4.3.2/qspinbox | QSpinBox]], QSlider и [[Qt:Документация_4.3.2/qdial | QDial]]. Решение? Заменить <tt>progress</tt> и <tt>totalSteps</tt> на <tt>minimum</tt>, <tt>maximum</tt> и <tt>value</tt>. Добавить сигнал <tt>valueChanged()</tt>. Добавить вспомогательную функцию <tt>setRange()</tt>.
-
The next observation is that <tt>progressString</tt>, <tt>percentage</tt> and <tt>indicator</tt> 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 <tt>setIndicator()</tt> function. Here's the new API:
+
Следующее, что обращает на себя внимание: <tt>progressString</tt>, <tt>percentage</tt> и <tt>indicator</tt> в действительности указывают на одно и то же - текст, который показывается на прогрессбаре. Обычно текстом являются проценты, но его можно заменить на что угодно при помощи <tt>setIndicator()</tt>. Вот новый API:
-
<pre>
+
{{code|qt|code=
virtual QString text() const;
virtual QString text() const;
void setTextVisible(bool visible);
void setTextVisible(bool visible);
bool isTextVisible() const;
bool isTextVisible() const;
-
</pre>
+
}}
-
By default, the text is a percentage indicator. This can be changed by reimplementing <tt>text()</tt>.
+
-
The <tt>setCenterIndicator()</tt> and <tt>setIndicatorFollowsStyle()</tt> functions in the Qt 3 API are two functions that influence alignment. They can advantageously be replaced by one function, <tt>setAlignment()</tt>:
+
По умолчанию текстом является индикатор процентов, что можно изменить реализовав виртуальный метод <tt>text()</tt>.  
-
<pre>
+
Методы <tt>setCenterIndicator()</tt> и <tt>setIndicatorFollowsStyle()</tt> в API Qt3 - это функции, которые влияют на расположение. Они удачно заменяются методом <tt>setAlignment()</tt>:
 +
 
 +
{{code|qt|code=
void setAlignment(Qt::Alignment alignment);
void setAlignment(Qt::Alignment alignment);
-
</pre>
+
}}
-
If the programmer doesn't call <tt>setAlignment()</tt>, 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.
+
 
 +
Если программист не вызовет setAlignment(), то положение определится текущим стилем. Для стилей, основанных на Motif, текст будет отцентрирован; для других - с правой стороны.
-
Here's the improved [[Qt:Документация_4.3.2/qprogressbar | QProgressBar]] API:
+
Вот улучшенный API класса [[Qt:Документация_4.3.2/qprogressbar | QProgressBar]]:
-
<pre>
+
{{code|qt|code=
class QProgressBar : public QWidget
class QProgressBar : public QWidget
{
{
Строка 294: Строка 302:
     ...
     ...
};
};
-
</pre>
+
}}
<div id="howtogetapisright"></div>
<div id="howtogetapisright"></div>
-
== 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).
+
== Как получить правильный API ==
 +
API требует тщательного продумывания. Первая версия никогда не будет правильной; вы должны тестировать его. Убедитесь, что код, использующий ваш API, читабельный.
 +
 
 +
Другой фокус заключается в том, чтобы дать кому-нибудь попробовать свой API с документацией и без нее (обзор класса и документация на методы).
-
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 <i>and</i> 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.
+
Документирование также является хорошим способом для поиска имен, если вы застряли на месте: попробуйте описать элемент (класс, функцию, перечисление и т.д.) и используйте свое первое предложение как отправную точку. Если вы не можете подобрать подходящее имя, то это является сигналом того, что этот элемент не должен присутствовать в API. Если ничего не получается, но вы убеждены, что ваша концепция [API] имеет смысл, то <i>изобретайте</i> новое имя. Вот как были придуманы "widget", "event", "focus", и "buddy".

Текущая версия на 08:16, 26 марта 2010

Image:qt-logo_new.png Image:qq-title-article.png
Qt Quarterly | Выпуск 13 | Документация

от Матиаса Этриха

Мы закончили важное исследование в Trolltech, связанное с улучшением процесса разработки Qt. В этой статье я хочу поделиться некоторыми нашими находками и представить принципы, которые мы использовали при проектировании Qt4, а также показать вам, как использовать их в вашем коде.

Содержание

Проектирование интерфейсов программирования - сложная задача. Это такое же сложное искусство, как проектирование языков программирования. Существует множество различных принципов построения API (Application Programming Interface, интерфейс прикладного программирования), многие из которых конфликтуют друг с другом.

В настоящее время компьютерные науки уделяют много внимания алгоритмам и структурам данных, упуская из вида принципы проектирования языков программирования и фреймворков. Это упущение является причиной неподготовленности программистов к задаче, которая встает перед ними все чаще и чаще: создание компонентов повторного использования.

Перед расцветом объектно-ориентированных языков программирования код многократного использования писался главным образом производителями библиотек, а не разработчиками ПО. В мире Qt эта ситуация основательно изменилась. Программирование на Qt - это постоянная разработка новых компонентов. Типичное приложение на Qt содержит ряд собственных компонентов, которые повторно используются во всех частях программы. Часто эти же компоненты являются частью другого приложения. В KDE пошли дальше и расширили Qt множеством библиотек, которые реализуют сотни дополнительных классов.

Но что же определяет хороший и эффективный C++ API? Хороший он или плохой, зависит от многих факторов - например, задача и специфические цели. Хороший API имеет ряд характерных свойств. Наличие некоторых, как правило, является желательным, а наличие других определяется спецификой конкретной сферы.


[править] Шесть характеристик хорошего API

API для программиста - это тоже самое, что и пользовательский интерфейс (GUI) для конечного пользователя. Под буквой 'P' в сокращении API имеется ввиду "Программист", не "Программа", подчеркивая тот факт, что API используются программистами (которые являются людьми).

Мы считаем, что API должны быть компактным и законченным, иметь ясную и простую семантику, быть интуитивными, легко запоминаться, и приводить к читаемому коду.

  • Быть компактным: Компактный API подразумевает минимальное количество как публичных членов класса, так и самих классов. Это позволяет легче понимать, запоминать, отлаживать и изменять API.
  • Быть законченным: Законченный API подразумевает, что в нем заключен ожидаемый функционал. Это правило может конфликтовать с компактностью. Также, если функция-член находится не в том классе [в котором ожидалось], то многие потенциальные пользователи не найдут эту функцию.
  • Иметь ясную и простую семантику: Как и в других видах проектирования, вы должны идти по пути наименьшего удивления. Делайте выполнение стандартных задач простым. Выполнение редких задач также должно быть возможным, а не являться фокусом. Решайте конкретную задачу; не делайте решение слишком общим, если этого действительно не требуется. (Например, QMimeSourceFactory в Qt3 мог бы называться QImageLoader и иметь другой API)
  • Быть интуитивным: как и другие вещи в компьютере, API должен быть интуитивным. Опыт и различные предпосылки приводят к различному восприятию того, что является интуитивным, а что - нет. API является интуитивным, если пользователь среднего класса начинает работу без чтения документации, и если программист, не знающий API, может понять исходный код, написанный с использованием этого API.
  • Быть легко запоминаемым: Чтобы сделать API простым для запоминания, выберите согласованную и точную политику именования. Используйте узнаваемые шаблоны и концепции, и избегайте сокращений.
  • Способствовать лёгкой читаемости кода: Код пишется однажды, но читается (и отлаживается, и изменяется) множество раз. Иногда написание легко читаемого кода может занять много времени, но это в результате сэкономит время в течении всего жизненного цикла продукта.

Наконец, имейте в виду, что разные пользователи будут использовать различные части вашего 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");

[править] Ловушки булевых параметров

Булевые параметры часто приводят к не читаемому коду. В частности, практически всегда будет ошибкой добавление булевого параметра в функцию. В Qt, традиционным примером является функция repaint(), которая принимает не обязательный булевый параметр, указывающий, должен ли очищаться задний фон (по умолчанию) или нет. Это приводит к такому коду:

widget->repaint(false);

который новички могут прочитать как "Не перерисовывать!"

Идея, по-видимому, состоит в том, что применение булевого параметра избавит от создания еще одной функции, что, в свою очередь, приведет к сокращению программного кода. На деле же - наоборот; сколько пользователей Qt "спиной почувствует", что делает каждая из следующих строчек?

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

Более качественный API:

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

В Qt4 мы решили эту проблему просто исключив возможность перерисовки без очистки виджета. Qt4 поддерживает двойную буферизацию, что делает это свойство не актуальным.

Еще ряд примеров:

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

Очевидным решением является замена булевых параметров перечислениями (enum types). Так мы поступили с регистрозависимыми строками QString. Сравните:

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


[править] Статический полиморфизм

Похожие классы должны иметь схожий API. Этого можно добиться с помощью наследования, где это имеет смысл, то есть когда используется полиморфизм времени выполнения (run-time polymorphism). Но полиморфизм также имеет место быть и при проектировании. Например, если бы вы заменили QListBox на QComboBox или QSlider на QSpinBox, то обнаружили бы, что похожие API способствовали более легкой замене. Вот что мы называем "статическим полиморфизмом".

Статический полиморфизм также позволяет легче запомнить API и шаблоны программирования. Как следствие, похожие API для ряда связанных классов является более предпочтительным, чем индивидуальный безупречный API для каждого класса в отдельности.

[править] Искусство задания имен

Задание имен, возможно, является единственным наиболее важным вопросом при проектировании API. Каким образом задавать имена классов? Как назвать методы класса?


[править] Общие правила именования

Ряд правил применим в равной степени ко всем типам имен. Во-первых, как я упоминал ранее, не используйте сокращения. Даже явные сокращения, например, "prev" для "previous" не окажут должного эффекта, так как пользователь должен запомнить, что слова сокращены.

Дела пойдут плохо, если API не логичный; например, в Qt3 есть activatePreviousWindow() и fetchPrev(). Правило "без сокращений" поможет написать логичный API.

Другое важное, но не очевидное правило, при проектировании классов - вы должны постараться сохранить ясное пространство имен подклассов. В Qt3 не всегда следовали этому правилу. Чтобы продемонстрировать это, мы приведем в пример класс QToolButton. Что вы ожидаете при вызове методов name(), caption(), text() или textLabel() класса QToolButton? Просто попробуйте поиграть с QToolButton в Дизайнере форм:

  • Свойство name наследуется от QObject и указывает на имя внутреннего объекта, которое может быть использовано для отладки и тестирования.
  • Свойство caption наследуется от QWidget и указывает на заголовок окна, который не имеет значения для QToolButton, так как обычно он создается внутри родительского виджета.
  • Свойство text наследуется от QButton и обычно используется на кнопке, если useTextLabel ложно.
  • Свойство textLabel объявленно в QToolButton и отображается на кнопке, если useTextLabel истинно.

В Qt4 в интересах удобочитаемости свойство name у QToolButton названо objectName, свойство caption стало windowTitle и textLabel изменено на text.


[править] Имена классов

Определите группы классов вместо того, чтобы придумывать имя для каждого отдельного класса. Например, все классы, основанные на поэлементном представлении данных, в архитектуре Модель/Представление (Model/View) имеют суффикс View ( QListView, QTableView и QTreeView), а соответствующие им виджеты имеют суффикс Widget ( QListWidget, QTableWidget и QTreeWidget).

[править] Имена типов перечислений и их значений

При объявлении перечисления нужно помнить, что в C++ (в отличии от Java или C#), их значения не имеют типа. Следующий пример показывает опасность задания слишком общих имен значений перечисления:

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

Что подразумевается под Insensitive в последней строке? Одно из правил задания имени значению перечисления состоит в том, чтобы в имени значения содержался хотя бы один элемент имени самого перечисления:

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

Когда значения перечисления могут быть использованы с оператором 'или' и как флаги, традиционным решением является сохранение результата операции 'или' в переменной типа int, который не является типобезопасным. Вместо этого Qt4 предлагает шаблон QFlags<T>, где T - тип перечисления. А для удобства в Qt есть ряд typedef'ов, так что вы можете писать Qt::Alignment вместо QFlags<Qt::AlignmentFlag>.

По соглашению мы даём имена типам в единственном числе (так как он может содержать один флаг в один момент времени), а имена флагов - во множественном числе. Например:

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

В некоторых случаях типы флагов имеют имена в единственном числе, а имена типов имеют приставку Flag:

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

[править] Имена функций и параметров

Первое правило в при задании имени функции: из имени функции должно быть ясно, имеет ли она побочные эффекты. В Qt3, константная функция QString::simplifyWhiteSpace() нарушает это правило, так как она возвращает QString вместо того, чтобы модифицировать строку, для которой она вызывается (как предполагается, исходя из имени). В Qt4 эта функция переименована в QString::simplified().

Имена параметров являются важным источником информации для программиста, даже если он не видит кода, который использует API. Так как современные IDE показывают названия параметров функций, хорошей мыслью будет задать внятные имена параметрам в заголовочных файлах и использовать эти имена в документации.

[править] Имена булевых геттеров, сеттеров, и свойств

Поиск хороших имен для геттера и сеттера булевого параметра особо тяжелое занятие. Должен ли геттер называться checked() или isChecked()? scrollBarsEnabled() или areScrollBarEnabled()?

В Qt4 мы следуем следующим правилам, присваивая имя геттерам:

  • К прилагательным добавляется приставка is-. Например:
    • isChecked()
    • isDown()
    • isEmpty()
    • isMovingEnabled()
  • Хотя прилагательные, относящиеся к существительному во множественном числе, остаются неизменны:
    • scrollBarsEnabled(), вместо areScrollBarsEnabled()
  • Глаголы не имеют приставок и не используется третье лицо (-s):
    • acceptDrops(), вместо acceptsDrops()
    • allColumnsShowFocus()
  • Существительные в основном не имеют приставок:
    • autoCompletion(), вместо isAutoCompletion()
    • boundaryChecking()
  • Иногда, отсутствие приставки вводит в заблуждение, тогда мы используем приставку is-:
    • isOpenGLAvailable(), вместо openGL()
    • isDialog(), вместо dialog()
      (От функции dialog(), мы обычно ожидаем, что она вернет QDialog*.)

Название сеттера получается из имени геттера, путем замены приставки is на set, например, setDown() и setScrollBarsEnabled(). Имена свойств определяются подобно именам геттеров, но без приставки is.


[править] Указатели или ссылки?

Что лучше для выходных параметров, указатели или ссылки?

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

Только по первой строке можно понять, что с большой вероятностью h, s и v будут модифицированы в методе getHsv().

[править] Наглядный пример: QProgressBar

Чтобы продемонстрировать некоторые принципы на практике, мы рассмотрим API класса QProgressBar в Qt3 и сравним с API Qt4. В 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 достаточно сложен и противоречив; например, из имен reset(), setTotalSteps(), и setProgress() не ясно, что они тесно связаны.

Для улучшения данного API следует сделать акцент на том, что QProgressBar похож на QAbstractSpinBox в Qt4 и его подклассы: QSpinBox, QSlider и QDial. Решение? Заменить progress и totalSteps на minimum, maximum и value. Добавить сигнал valueChanged(). Добавить вспомогательную функцию setRange().

Следующее, что обращает на себя внимание: progressString, percentage и indicator в действительности указывают на одно и то же - текст, который показывается на прогрессбаре. Обычно текстом являются проценты, но его можно заменить на что угодно при помощи setIndicator(). Вот новый API:

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

По умолчанию текстом является индикатор процентов, что можно изменить реализовав виртуальный метод text().

Методы setCenterIndicator() и setIndicatorFollowsStyle() в API Qt3 - это функции, которые влияют на расположение. Они удачно заменяются методом setAlignment():

void setAlignment(Qt::Alignment alignment);

Если программист не вызовет setAlignment(), то положение определится текущим стилем. Для стилей, основанных на Motif, текст будет отцентрирован; для других - с правой стороны.

Вот улучшенный API класса QProgressBar:

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

API требует тщательного продумывания. Первая версия никогда не будет правильной; вы должны тестировать его. Убедитесь, что код, использующий ваш API, читабельный.

Другой фокус заключается в том, чтобы дать кому-нибудь попробовать свой API с документацией и без нее (обзор класса и документация на методы).

Документирование также является хорошим способом для поиска имен, если вы застряли на месте: попробуйте описать элемент (класс, функцию, перечисление и т.д.) и используйте свое первое предложение как отправную точку. Если вы не можете подобрать подходящее имя, то это является сигналом того, что этот элемент не должен присутствовать в API. Если ничего не получается, но вы убеждены, что ваша концепция [API] имеет смысл, то изобретайте новое имя. Вот как были придуманы "widget", "event", "focus", и "buddy".