API Design Principles/ja: Difference between revisions

From Qt Wiki
Jump to navigation Jump to search
(Simplify punctuation)
(Simplify punctuation)
Line 14: Line 14:
== 良い API の6つの特徴 ==
== 良い API の6つの特徴 ==


プログラマーにとって API とは、エンドユーザーにとっての GUI のようなものです。API が人間のプログラマーによって使われるという事実を強調する場合、API の ‘P' は "Program" ではなく "Programmer" を意味します。
プログラマーにとって API とは、エンドユーザーにとっての GUI のようなものです。API が人間のプログラマーによって使われるという事実を強調する場合、API の 'P' は "Program" ではなく "Programmer" を意味します。


[http://doc.qt.nokia.com/qq/qq13-apis.html Qt Quarterly 13 の API design に関する記事] で、Matthias は API は最小限かつ完全で、明確でシンプルなセマンティックを持ち、直感的で、簡単に覚ることができ、可読なコードを導くべきだという彼の考えを示しました。
[http://doc.qt.nokia.com/qq/qq13-apis.html Qt Quarterly 13 の API design に関する記事] で、Matthias は API は最小限かつ完全で、明確でシンプルなセマンティックを持ち、直感的で、簡単に覚ることができ、可読なコードを導くべきだという彼の考えを示しました。

Revision as of 13:15, 23 August 2015

This article may require cleanup to meet the Qt Wiki's quality standards. Reason: Auto-imported from ExpressionEngine.
Please improve this article if you can. Remove the {{cleanup}} tag and add this page to Updated pages list after it's clean.

English Русский 日本語

API 設計の原理原則

Qt が好評を得ている理由の1つが、首尾一貫した、簡単に学べる、非常に強力な API です。Qt の API を設計する上でこれまで蓄積してきたノウハウをこの記事で解説したいと思います。多くのガイドラインは普遍的なもので、中には慣例的なものもあります。我々がこのガイドラインに従う第一の理由は、既存の API の一貫性を保つためです。

基本的にこのガイドラインはパブリックな API に対するものですが、同様のテクニックをプライベートな API に対しても適用することを推奨しています。

Jasmin Blanchette による Little Manual of API Design (PDF) も合わせて読むと良いでしょう。

良い API の6つの特徴

プログラマーにとって API とは、エンドユーザーにとっての GUI のようなものです。API が人間のプログラマーによって使われるという事実を強調する場合、API の 'P' は "Program" ではなく "Programmer" を意味します。

Qt Quarterly 13 の API design に関する記事 で、Matthias は API は最小限かつ完全で、明確でシンプルなセマンティックを持ち、直感的で、簡単に覚ることができ、可読なコードを導くべきだという彼の考えを示しました。

  • 最小限であれ: 最小限の API とは、クラス数を可能な限り少なくし、クラスのパブリックなメンバも可能な限り少なくするということです。これにより、理解が容易になり、覚えやすく、デバッグがしやすく、API の変更もしやすくなります。
  • 完全であれ: 完全な API とは必要な機能を備えているということです。最小限であることとは相反することがあります。メンバ関数が誤ったクラスにある場合には、その関数にたどり着く可能性が低くなります。
  • 明確でシンプルなセマンティックであれ: 一般的な設計と同様に、「驚き最小の原則」に従うべきです。一般的なものは簡単に対応できるようにします。一般的ではないものも対応できるようにすべきですが、それにはフォーカスしません。個々の問題への対処: 解決策を必要以上に一般化しないようにしましょう (例えば、Qt 3の QMimeSourceFactory は、QImageLoader と呼ばれる異なるAPI を持つものにすることもできました)。
  • 直感的であれ: コンピューター上の全てのものと同様に、API も直感的であるべきです。何が直感的であるかは経験やバックグランドによって異なります。それなりに経験のあるユーザーがドキュメントを読まずともはじめることができ、API を知らないプログラマーでも書かれたコードを理解することができる場合は API は直感的と言えるでしょう。
  • 覚えやすくあれ: API を簡単に覚えられるようにするには、首尾一貫した正確な名前をつけることです。理解できるパターンやコンセプトを採用し、省略はさけましょう。
  • 可読なコードを導け: コードは1度しか書かれませんが、何度も読まれ(デバッグされ、変更され)ます。読みやすいコードは書くのには時間がかかることもありますが、プロダクトのライフサイクルにおいては時間の節約になります。

最後に、様々なユーザーが様々な部分の API を使用することを心に留めてください。Qt のクラスのインスタンスを単に使用するのも直感的であるべきですが、ユーザーが派生クラス化をする前にそのドキュメントを読むことを想定してください。

静的なポリモーフィズム

同じようなクラスは同じような API を持つべきです。これは、実行時のポリモーフィズムが利用できる場所では、継承により実現することができます。しかし、ポリモーフィズムは設計時にも起こります。例えば QProgressBar の QSlider への変更や QString の QByteArray への変更など、API が似ていることでこれらの変更がとても簡単にできることがわかるでしょう。我々はこれを "静的なポリモーフィズム" と呼んでいます。

静的なポリモーフィズムにより API やプログラミングのパターンを覚えるのも簡単になります。関係するクラス群が同じような API を持つということは、それぞれのクラスが完璧な API をそれぞれ持つよりも結果として良い場合があります。

Qt ではやむを得ない場合を除き、実際の継承よりもこの静的なポリモーフィズムを採用することを好んでいます。これにより、Qt のパブリックなクラス数を少なく保つことができ、Qt の初心者でもドキュメントを見て使用方法を簡単に理解することができるようになります。

良い例: QDialogButtonBox と QMessageBox は "QAbstractButtonBox" のようなクラスを継承することなくボタンに関する同じような API (addButton(), setStandardButtons() など)を持ちます。

悪い例: QAbstractSocket は QTcpSocket と QUdpSocket により継承されていますが、この2つのクラスでは動作が大きく異なります。QAbstractSocket のポインタを有効に使用した(できた)例は見たことがありません。

難しい例: QBoxLayout は QHBoxLayout と QVBoxLayout の基底クラスです。利点: QBoxLayout を使用し、ツールバーで setOrientation() を呼ぶことで Horizontal/Vertical の設定ができる。欠点: 余計なクラスで、ユーザーは ((QBoxLayout *)hbox)->setOrientation(Qt::Vertical) とする事もできるが、あまり意味がない。

プロパティに基づく API

新しい Qt のクラスは "プロパティに基づく API" を持つ傾向にあります。QTimer を例に見てみましょう。

 QTimer timer;
 timer.setInterval(1000);
 timer.setSingleShot(true);
 timer.start();

プロパティ はそのオブジェクトの状態の一部となる概念的な属性を意味します。Q_PROPERTY かどうかはここでは関係ありません。使用可能な場合、プロパティの設定は順序に依存すべきではありません。つまり、個々のプロパティは直交であるべきです。例えば、上記の例は以下のようにも書くことができます。

 QTimer timer;
 timer.setSingleShot(true);
 timer.setInterval(1000);
 timer.start();

簡単に 以下のように記述することも可能です。

timer.start(1000)<code>

同じことを QRegExp でも見てみましょう
QRegExp regExp;
regExp.setCaseSensitive(Qt::CaseInsensitive);
regExp.setPattern(".");
regExp.setPatternSyntax(Qt::WildcardSyntax);
このような API の実装では内部のオブジェクトが遅延するように生成することがポイントです例えばQRegExp の場合パターンシンタックスが何になるかがわかる前に setPattern() で指定された "'''.'''"のパターンをコンパイルしてしまうのは時期尚早です

プロパティは連続して指定されることがありますこの場合は注意深く先に進める必要があります現在のスタイルによって決まる "デフォルトのアイコンサイズ"  QToolButton  "iconSize" プロパティの例を見てみましょう
toolButton->iconSize(); // 現在のスタイルのデフォルトを返す
toolButton->setStyle(otherStyle);
toolButton->iconSize(); // otherStyle のデフォルトを返す
toolButton->setIconSize(QSize(52, 52));
toolButton->iconSize(); // (52, 52) を返す
toolButton->setStyle(yetAnotherStyle);
toolButton->iconSize(); // (52, 52) を返す
一度 iconSize を設定するとこの設定が有効になりスタイルの変更では設定が変わりませんこれは '''良いことです''' プロパティをリセットできるようにしておくと便利な場合もありこれには2つのアプローチがあります

* 特別な値 (QSize()  1Qt::Alignment(0) など)  "リセット" の意味で渡す

* resetFoo()  unsetFoo() などの関数を明示的に用意する

iconSize の場合は (QSize(1, -1) を意味する) QSize()  "リセット" の意味とすることで十分でしょう

取得関数が設定されたものと異なるものを返す場合があります例えば widget->setEnabled(true) を実行した場合でも widget->isEnabled()  false を返すことがあり得ますこれは親が無効な場合です通常はこれがチェックしたい点 (親が無効な場合は子ウィジェットもグレーアウトされるべきでそのウィジェット自身も無効として振る舞うべきであると同時にこの設定は内部では保持されており実際は "有効" であり親が再度有効になるのを待っているということを) なので問題はありませんただしこの動作はドキュメントに正確に記載されるべきですが

== C++ 固有の問題 ==

=== 値かオブジェクトか ===

=== ポインタか参照か ===

出力パラメーターにはポインタと参照ではどちらがベストでしょうか
void getHsv(int *h, int *s, int *v) const
void getHsv(int &h, int &s, int &v) const
ほとんどの C++ の本では可能な場合は参照が推奨されています一般的な観点ではポインタよりも参照の方が "より安全でより適切" とされていますこれに反してQt ではポインタを選択する傾向にありますこれはユーザーのコードがより読みやすくなるからですそれでは比較してみましょう
color.getHsv(&h, &s, &v);
color.getHsv(h, s, v);
hsv がこの関数呼び出しで変更される可能性が高いことが明らかなのは最初の行だけでしょう

=== バーチャル関数 ===

C++ ではメンバ関数をバーチャルで宣言する基本的な目的は派生クラスでその関数をオーバーロードしその振る舞いをカスタマイズできるようにすることですその関数をバーチャルにする目的はその関数の既にある呼び出しで代わりに自分のコードを実行するためですある関数を外部から呼び出すコードがどこにもない場合にはその関数をバーチャルとして宣言することに慎重になるべきです
// Qt 3 の QTextEdit: バーチャルである理由がないメンバ関数の一覧
virtual void resetFormat();
virtual void setUndoDepth( int d );
virtual void setFormat( QTextFormat f, int flags );
virtual void ensureCursorVisible();
virtual void placeCursor( const QPoint &pos;, QTextCursorc = 0 );
virtual void moveCursor( CursorAction action, bool select );
virtual void doKeyboardAction( KeyboardAction action );
virtual void removeSelectedText( int selNum = 0 );
virtual void removeSelection( int selNum = 0 );
virtual void setCurrentFont( const QFont &f );
virtual void setOverwriteMode( bool b ) { overWrite = b; }
QTextEdit  Qt 3 から Qt 4 に移植した際にほとんどのバーチャル関数は削除されました面白いことに(-期待はしていませんでしたが- 予期していなかったわけではないのですが)大きな問題はありませんでしたなぜかそれは Qt 3 では QTextEdit のためにはポリモーフィズムが使われていなかったからですQt 3 の中ではこれらの関数は呼び出していませんでした呼び出していたのはユーザーです端的に言うとQTextEdit の派生クラスを作成する理由は無くユーザーがこれらの関数を呼び出さない限りこれらの関数を再実装する理由も無かったのですQt の外のアプリケーションでポリモーフィズムが必要な場合は自分でポリモーフィズムを追加することができたのです

==== バーチャル関数を避ける ====
Qt では我々は様々な理由によりバーチャル関数の数を最小限にするように努めましたバーチャル関数の呼び出しは制御不能なノードが呼び出しグラフの中に入るため(出力が予測不可能になり)バグの修正を困難にします以下のように再実装したバーチャル関数の中でとんでもない処理をする人もいます

''' イベントの送信

* シグナルの発生

* (例えばモーダルなファイルダイアログを開くなどによる) イベントループの実行

* (例えば "delete this" となるような) オブジェクト自体の破棄

これ以外にもバーチャル関数の過度の使用を避ける理由は山ほどあります

* バイナリコンパチビリティを保ちながらのバーチャル関数の追加移動削除ができない

* バーチャル関数は簡単にはオーバーロードできない

* ほとんど全てのバーチャル関数の呼び出しはコンパイラによる最適化やインライン化ができない

* 関数呼び出しに v-table のルックアップが必要になり通常の関数に比べ2倍遅くなる

* バーチャル関数を持つクラスでは値としてのコピーが難しくなる(可能だがとても厄介で非推奨)

バーチャル関数を持たないクラスほどバグが少なく一般的にメンテナンス性が高いことが経験的に分かっています

経験則では我々がツールキットとしてそして主なユーザーとして関数を呼び出すような場合でない限りその関数はおそらく非バーチャルであるべきです

==== バーチャル vs コピー可能 ====

ポリモーフィックなオブジェクトと値型のクラスは良いお友達ではありません

バーチャル関数を持つクラスのデストラクタはバーチャルでなければなりませんこれは派生クラスのデータが破棄されることなしに基底クラスが破棄されることによって起こるメモリリークを防ぐためです

あるクラスのコピーや代入を可能にしたり値による比較をしたい場合にはコピーコンストラクタと代入演算子等価演算子が必要でしょう
class CopyClass {
public:
CopyClass();
CopyClass(const CopyClass &other;);
~CopyClass();
CopyClass &operator;=(const CopyClass &other;);
bool operator==(const CopyClass &other;) const;
bool operator!=(const CopyClass &other;) const;

virtual void setValue(int v);

};
このクラスの派生クラスを作成すると思いもよらないことがあなたのコードで起こります一般的にバーチャル関数とバーチャルなデストラクタを持たない場合はユーザーは派生クラスを作成しポリモーフィズムを使用することはできませんしかしバーチャル関数やバーチャルなデストラクタを追加した場合それがそのまま派生クラスを作成する理由となり状況は複雑になります ''バーチャル修飾子で宣言するのはとても簡単です。'' しかし混乱と破滅(可読性の低いコード)に陥るでしょう以下の例で考えてみましょう
class OtherClass {
public:
const CopyClass &instance;() const; // これは何を返すのか?何を代入すべきか?
};
(このセクションは工事中です)

=== const について ===

C++ にはあるものが変わらない/副作用が無いことを表すための "const" というキーワードがありますこれは単純な値ポインタポインタが示すものに対して有効でさらにオブジェクトの状態を変えない関数の特別な属性として使われます

const がついたもの自体にはそれほど大きな価値がありません"const" キーワードさえ持っていないプログラミング言語もたくさんありますしかしこのことがそのまま不要であるという理由にはなりません実際C++ のソースコードから関数の const のオーバーロードを消し"const" キーワードを全て検索して削除してみてもほとんどのものはコンパイル動作ともに問題ないでしょうしかし実用性を考えると"const" の使用はとても重要です

Qt  API 設計の中で"const" が適切に使用されているものをいくつか見てみましょう

==== 引き数の const ポインタ ====

ポインタを引き数として取る const 関数はほぼ全て const ポインタを引き数に取ります

ある関数が実際に const と宣言されている場合副作用やそのオブジェクトの目に見える状態を変えることはありませんそれにも関わらず const ではない引き数が必要な場合はあるのでしょうかconst 関数は他の const 関数から呼ばれることが多くそれを考えるとconst ではないポインタが渡されることはほとんどありません(const_cast を使わない場合我々はできる限り const_cast の使用を控えています)

変更前:
bool QWidget::isVisibleTo(QWidget *ancestor) const;
bool QWidget::isEnabledTo(QWidget *ancestor) const;
QPoint QWidget::mapFrom(QWidget *ancestor, const QPoint &pos;) const;
QWidget には const ではないポインタを引き数に取る const 関数がたくさんありますこれらの関数は引き数で渡された widget を変更することは可能ですが自分自身は変更できませんこのような関数は const_cast と一緒になっていますこれらの関数は const ポインタを引き数に取る方がいいでしょう

変更後:
bool QWidget::isVisibleTo(const QWidget *ancestor) const;
bool QWidget::isEnabledTo(const QWidget *ancestor) const;
QPoint QWidget::mapFrom(const QWidget *ancestor, const QPoint &pos;) const;
QGraphicsItem ではこのような変更をしましたしかしQWidget の変更は Qt 5 まで待つ必要があります
bool isVisibleTo(const QGraphicsItem *parent) const;
QPointF mapFromItem (const QGraphicsItem *item, const QPointF &point;) const;
==== 戻り値の const ====

関数呼び出しの戻り値で参照を返さないものは R-value と呼ばれます

クラスではない R-value は常に cv 非修飾型です文法的には "const" をつけることが可能であってもアクセス権に関するものは何も変更しないためあまり意味はありません

最近のほとんどのコンパイラではこのようなコードのコンパイル時に警告が表示されるでしょう

クラス型の R-value  "const" を追加した場合const ではないメンバ関数へアクセスすることやそのメンバを直接操作することは禁止されています

"const" を追加しなければそういったアクセスもできますがそれが必要となるケースはごく稀ですなぜならば加えた変更も R-value オブジェクトの有効期間の終了と共に消えてしまうからです

サンプル:
struct Foo
{
void setValue(int v) { value = v; }
int value;
};

Foo foo()

{
return Foo();
}

const Foo cfoo()

{
return Foo();
}

int main()

{
// 以下の文はコンパイルは通ります。foo() は const ではない R-value のため
// (一般的に L-value を必要とする)代入はできませんが、
// メンバへのアクセスは L-value となります。
foo().value = 1; // OK だが、一時オブジェクトは文の最後で破棄されます。

// 以下の文はコンパイルは通ります。foo() は const ではない R-value のため

// 代入はできないが、(const でなくても) メンバ関数を呼ぶことはできます。
foo().setValue(1); // OK だが、一時オブジェクトは文の最後で破棄されます。

// 以下の文はコンパイルが_通りません_。foo() は const なメンバを持つ

// const な R-value のため、メンバアクセスでの代入はできません。
cfoo().value = 1; // NG

// 以下の文はコンパイルが_通りません_。foo() は const なメンバを持つ

// const な R-value のため、const ではないメンバ関数を呼び出せません。
cfoo().setValue(1); // NG
}
==== 戻り値: ポインタ vs const ポインタ ====

const 関数がポインタを返すかconst ポインタを返すかについてですがほとんどの人が C++ での "const の正しさ" のコンセプトが破綻していると感じるところです問題は自分の状態を変更しない const 関数が const ではないメンバのポインタを返すことで起こりますthis ポインタを返す単純な例でもオブジェクトの目に見える状態は変わりませんしその影響範囲の状態も変わりませんしかしプログラマーは間接的にオブジェクトのデータを変更することができます

以下のサンプルで const ではないポインタを返す const 関数を使用した数ある const の抜け道の1つを見てみましょう
QVariant CustomWidget::inputMethodQuery(Qt::InputMethodQuery query) const
{
moveBy(10, 10); // コンパイルできない!
window()->childAt(mapTo(window(), rect().center()))->moveBy(10, 10); // コンパイルできる!
}
const ポインタを返す関数では(期待どおりかどうかは別として) this に対する副作用を少なくともある程度の範囲において防いでいますしかしconst ポインタやそのリストを返したいと思うのはどのような関数でしょうかconst の正しいアプローチを取った場合メンバの1つへのポインタ(もしくはその配列)を返す全ての const 関数はconst ポインタを返さなければいけませんしかし残念なことに現実的には API が使いづらくなります
QGraphicsScene scene;
// … シーンの構築

foreach (const QGraphicsItem item, scene.items()) {

item->setPos(qrand() % 500, qrand() % 500); // item が const ポインタなのでコンパイルできない!
}
QGraphicsScene::items()  const 関数のためconst ポインタの配列を返すべきだということになるかもしれません

Qt では我々はほとんど全てで const ではないパターンを使用していますつまり実用的なアプローチを選択しましたconst ではないポインタを返しその不正使用により起こりうる問題よりもconst ポインタを返すことによって起こりうる const_cast の過度の利用の方が深刻だと考えたのです

==== 戻り値: 値か const 参照か ====
戻り値のためのオブジェクトのコピーを保持している場合const 参照を返すのが最も速いアプローチですしかしこれは我々が後でそのクラスのリファクタリングをする際には足かせとなります (d-ポインタを使用する方法によりQt のクラスのメモリ表現をいつでも変えることができますしかしバイナリ互換を保ったまま関数のシグネチャを "const QFoo &" から "QFoo" へ変更することはできません) このため処理速度が本当に求められる場合でリファクタリングの問題が無い場合(例えば QList::at())を除いて一般的には "const QFoo &" ではなく "QFoo" を返すようにしています

==== const vs オブジェクトの状態 ====
const の正当性は C++ に置ける vi/emacs 議論でありこのトピックはいくつかの分野で破綻しています(例えばポインタベースの関数)

しかしconst 関数はクラスの目に見える状態を変えないというのが一般的なルールです状態とは "自分と自分の責任の範囲" を意味しますこれは const ではない関数がそのプライベートなメンバデータを変えることを意味するものではありませんまたconst 関数でそれをできないわけでもありませんしかしその関数はアクティブで目に見える副作用を持ちますconst 関数は通常は目に見える副作用は行いません
QSize size = widget->sizeHint(); // const
widget->move(10, 10); // const ではない
デリゲートは何か別のものの上に描画をするためのものですデリゲートの状態にはその責任が含まれるため描画対象の状態を含みます描画には副作用があり描画を行う対象の見た目(とそれに伴い状態)を変更しますこのためpaint()  const とするのは間違っています全てのビューの paint()  QIcon  paint() も同様ですある関数の const 性を明らかに破る目的ではない限りconst 関数の中で QIcon::paint() を呼ぶ人はいないでしょうまたその場合は const_cast による明示的なキャストの方がいいでしょう
// QAbstractItemDelegate::paint は const
void QAbstractItemDelegate::paint(QPainterpainter, const QStyleOptionViewItem &option;, const QModelIndex &index;) const
// QGraphicsItem::paint は const ではない
void QGraphicsItem::paint(QPainter painter, const QStyleOptionGraphicsItem option, QWidget widget = 0)
== API のセマンティックとドキュメント ==
-1 を関数に渡した場合にどうすべきかなど…。

警告致命的なエラーなど

API には品質の保証が必要です番最初のものは決して正しくありませんAPI のテストをしなければなりませんこの API を使用しているコードを見ることでユースケースを作成しそのコードが可読かどうかを確認してください

他には他の人にその API をドキュメントのありなしで使ってもらいそのクラスのドキュメント(クラスの概要と個々の関数)を書く方法があります

const キーワードはあなたのためには "なにもしてくれません" つの関数に const/const ではないバージョンのオーバーロードを持つよりは削除することを検討して下さい

== 命名の美学 ==
命名はおそらく API の設計における最重要課題ですそのクラスはなんと呼ばれるべきですかメンバ関数はなんと呼ばれるべきですか

=== 一般的な命名規則 ===
どんな種類の名前にも上手く適用できる規則がいくつかありますまずはじめに前述の通り省略はしてはいけませんこれは "previous"  "prev" とするような明らかな場合でもユーザーがどの場合は省略形なのかを覚えなければならないため長期的にはこれは良くありません

API 自体に矛盾があるのは当然よくありません例えばQt 3 には activatePreviousWindow()  fetchPrev() がありました"省略は無し" というルールにより矛盾のない API の実現が単純になります

もう1つのクラスを設計する際の重要でそれでいて繊細なルールは派生クラスのために名前空間を綺麗にするべきだということですQt 3 はこの原理に従っていないところもありました分かりやすく説明するためQToolButton を例にとりますQt 3  QToolButton  name()caption()text()textLabel() を呼んだ場合何を期待しますか Qt Designer 上で QToolButton で色々試してみてください

* name プロパティは QObject から継承したものでデバッグやテストの際に使用される内部のオブジェクト名を表します

* caption プロパティは QWidget から継承したものでウィンドウのタイトルを表しますがQToolButton は基本的には子ウィジェットとして使われるため実質的には意味がありません

* text プロパティは QButton から継承したものでuseTextLabel  true でない場合にボタン上に表示されます

* textLabel プロパティは QToolButton で定義されuseTextLabel  true の場合に表示されます

可読性の観点からname  Qt 4 では objectName となりましたcaption  windowTitle となりQToolButton  text とは別にあった textLabel プロパティは無くなっています

良い名前が思い浮かばない場合にはドキュメント化はとてもいい方法になりえますそのアイテム(クラス関数enum の値など)のドキュメントを作成してみて最初にひらめいたの文章から決めるのです正確な名前が見当たらない場合そのアイテムがあるべきではないというサインの可能性がありますもし全ての方法に失敗しそれでもそのコンセプトに意味がある場合新しい名前を発明しましょう"widget"  "event""focus""buddy" などはこの結果生まれたものです

=== クラスの命名 ===
個々のクラスに完璧な名前をつけるのではなくクラスのグループが分かるようにしましょう例えば Qt 4 に含まれるモデルを利用するビュークラスは全て View で終わる名前(QListViewQTableViewQTreeView)になっていて対応するアイテムベースのクラスは Widget で終わる名前(QListWidgetQTableWidgetQTreeWidget)になっています

=== Enum 型と値の命名 ===
enum を宣言する際C++ では(Java  C# とは異なり)型には関係なく enum 値が使われることを心に留めておかなければなりません以下の例で一般的すぎる名前を enum 値につけた場合の危険性を見てみましょう
namespace Qt
{
enum Corner { TopLeft, BottomRight, … };
enum CaseSensitivity { Insensitive, Sensitive };
…
};
tabWidget->setCornerWidget(widget, Qt::TopLeft);
str.indexOf("$(QTDIR)", Qt::Insensitive);
最後の行でInsensitive は何を意味するのでしょう我々の enum 型の命名のガイドラインではそれぞれの enum 値で enum の型名の少なくとも1つの部分を繰り返すことになっています
namespace Qt
{
enum Corner { TopLeftCorner, BottomRightCorner, … };
enum CaseSensitivity { CaseInsensitive,
CaseSensitive };
…
};
tabWidget->setCornerWidget(widget, Qt::TopLeftCorner);
str.indexOf("$(QTDIR)", Qt::CaseInsensitive);
enum の値を OR でまとめてフラグとして使用できる場合その結果は int に格納するのが伝統的な方法ですがこれは型安全ではありませんQt 4 では T  enum 型である QFlags<T> というテンプレートクラスを導入しましたQt では利便性を考えこのようなフラグ型の名前を typedef してあるためQFlags<Qt::AlignmentFlag> の代わりに Qt::Alignment と書くことができます

慣例的にenum 型の名前には(一度に1つのフラグしか持たないため)単数形を使用していて"flags" 型は複数形の名前にしてあります:
enum RectangleEdge { LeftEdge, RightEdge, … };
typedef QFlags<RectangleEdge> RectangleEdges;
"flags" 型の名前が単数形の場合もありますこの場合enum 型は Flag で終わる名前にしています
enum AlignmentFlag { AlignLeft, AlignTop, … };
typedef QFlags<AlignmentFlag> Alignment;
=== 関数と引数の命名方法 ===
関数の命名の第一のルールは名前から副作用の有無が分かるようにすることですQt 3 では const 関数 QString::simplifyWhiteSpace() がこの規則を違反していましたこの関数は名前の通り呼び出された文字列自体を変更するのではなくQString を返していたからですこの関数は Qt 4 では QString::simplified() という名前に変更されています

引数の名前はその API を使用するコードには現れませんがプログラマーにとっては重要な意味を持ちます最近の IDE ではプログラマーがコードを書いている際に表示されることが多いため引き数に適切な名前をつけヘッダファイルと同じ名前をドキュメントでも使用することはとても大事なことです

=== bool 型の取得関数設定関数プロパティの命名方法 ===
bool 型のプロパティに対する取得関数と設定関数の良い名前を探すことは常に難しい問題です取得関数は checked() とすべきでしょうかそれとも isChecked() とすべきでしょうかscrollBarsEnabled()  areScrollBarEnabled() ではどうでしょうか

Qt 4 では取得関数の命名に以下のガイドラインを使用しました

''' 形容詞は接頭語 is をつける
''' isChecked()
'''''' isDown()
'''''' isEmpty()
'''''' isMovingEnabled()

* 複数形の名詞に対する形容詞の場合は is をつけない
** scrollBarsEnabled() でありareScrollBarsEnabled() ではない

* 動詞の場合は接頭語をつけず原型にする(三単現の s はつけない)
** acceptDrops() でありacceptsDrops() ではない
** allColumnsShowFocus()

* 名詞の場合は通常は接頭語をつけない
** autoCompletion() でありisAutoCompletion() ではない
** boundaryChecking()

* 接頭語をつけないことが適切ではない場合にはis をつける
** isOpenGLAvailable() でありopenGL() ではない
** isDialog() でありdialog() ではない

(dialog() という名前の関数があった場合 何かしらの QDialog を返すと思うでしょう)

設定関数の名前は取得関数から接頭語を除いて名前の最初に set をつけています(: setDown()  setScrollBarsEnabled()) プロパティの名前は取得関数から接頭語を除いたものです

== 一般的な罠を回避する ==

=== 利便性の罠 ===

つの間違ったコンセプトは何かをするために必要なコードが短ければ短いほど良い API であるということですコードが書かれるが1度であったとしてもそのコードは何度も何度も読まれます
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");
=== bool 型の引数の罠 ===
bool 型の引数は頻繁に理解しづらいコードにつながります特に既存の関数に bool 型の引数を追加するのはほぼ例外なく間違いですこれにあてはまる Qt での例は repaint() この関数は背景を消去するかどうかの指定のための bool 型の引数をオプションでとります(デフォルトは true)これにより以下のようなコードが書かれます
widget->repaint(false);
初心者はこれを見て "再描画はされない" と思うかもしれません

無駄な拡張を防ぐために関数を1つ追加する代わりに bool 型の引数を追加したということは明らかですが逆に無駄な拡張をもたらしていますQt ユーザーの中のどのくらいの人が以下の3つの行の本当の意味での違いを知っているでしょうか
widget->repaint();
widget->repaint(true);
widget->repaint(false);
API を以下のようにすることでいくらか改善されるでしょう
widget->repaint();
widget->repaintWithoutErasing();
Qt 4 ではシンプルにウィジェットの背景を消去せずに描画するという選択自体を削除しましたQt 4 ではダブルバッファリングをフレームワークとしてサポートしたためこの機能は無くなりました

さらにいくつかの例を示します
widget->setSizePolicy(QSizePolicy::Fixed,
QSizePolicy::Expanding, true);
textEdit->insert("Where's Waldo?", true, true, false);
QRegExp rx("moc_.c??", false, true);
つの代替案は bool 型の引数を enum 型に置き換えることですQt 4 では QString の大文字小文字の区別に対してこれを適用しました以下の2行を比較してみて下さい
str.replace("USER", user, false); // Qt 3
str.replace("USER", user, Qt::CaseInsensitive); // Qt 4
=== ものまねの罠 ===

== ケーススタディ ==

=== QProgressBar ===

このコンセプトを実際の例で見てみましょうQProgressBar  API  Qt 3  Qt 4 で比較してみます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);
…
};
この API はとても複雑で一貫性に欠けています例えば名前からは reset()  setTotalSteps()setProgress() が緊密に連携していることは読み取れません

この API を改善する上でカギとなる点はQProgressBar  Qt 4  QAbstractSpinBox クラスやその派生クラスの QSpinBoxQSliderQDialog に似ているということです解決方法は progress  totalSteps  minimum  maximumvalue で置き換えvalueChanged() シグナルを追加しsetRange() 関数を利便性を考えて追加することです

次のポイントはprogressString  percentageindicator が同じものを指していることですこれは実際はプログレスバー上に表示されているテキストになります通常はこのテキストはパーセントですがsetIndicator() 関数を使用することで様々なものが設定できます新しい API は以下のようになります
virtual QString text() const;
void setTextVisible(bool visible);
bool isTextVisible() const;
デフォルトではこのテキストはパーセントの表示ですこれは text() を再実装することで変更可能です

Qt 3  API にあった setCenterIndicator()  setIndicatorFollowsStyle() 関数はアライメントに関するものですこれらは setAlignment() という1つの関数に変更しました
void setAlignment(Qt::Alignment alignment);
プログラマーが 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);
…
};
=== QAbstractPrintDialog  QAbstractPageSizeDialog ===

Qt 4.0 では QAbstractPrintDialog  QAbstractPageSizeDialog の2つのクラスが登場しそれぞれ QPrintDialog  QPageSizeDialog の基底クラスとなっていますこれはQAbstractPrint-  -PageSizeDialog のポインタを引数で取り処理を行う Qt  API がないため完全に無意味でしたqdoc をトリッキーに使用し我々はこれらを隠しましたがこれらは不必要な抽象クラスの典型的な例でした

これは ''良い'' 抽象化が間違っているということではありませんがファクトリなどの仕組みで QPrintDialog を変更する方法を用意すべきでした。#ifdef QTOPIA_PRINTDIALOG というマクロが宣言されているのがなによりの証拠です

=== QAbstractItemModel ===
Qt 4 のモデルビューの問題の詳細については様々なところで述べられていますが一般化した教訓としては"QAbstractFoo" 考えうる全ての派生クラスから単に和集合をとって作ればよいわけではないということですこのような "すべての可能性を抽象化" した基底クラスは決して良い解決法ではないということですQAbstractItemModel はこの間違いを犯しましたこのクラスは QTreeOfTablesModel であり結果的に API が複雑になりこれが ''すべての素晴らしい派生クラスに継承されています'' 

単に抽象化をしても API が自動的に良くなるわけではありません

=== QLayoutIterator  QGLayoutIterator ===
Qt 3 でカスタムレイアウトを作成するには QLayout  QGLayoutIterator("G"  generic からきています)の両方の派生クラスが必要でしたQGLayoutIterator の派生クラスのインスタンスのポインタは QLayoutIterator をラップしていたためユーザーは他のイテレータと同様に使用することができましたQLayoutIterator により以下のようなコードを書くことができました
QLayoutIterator it = layout()->iterator();
QLayoutItem **child;
while ((child = it.current()) != 0) {
if (child->widget() == myWidget) {
it.takeCurrent();
return;
}
++it;
}

Qt 4 では QGLayoutIterator クラス(と内部の Box と Grid の派生クラス)をなくし、QLayout の派生クラスで itemAt()、takeAt()、count() を実装するようにしました。

QImageSink

Qt 3 には画像を順番に読み込みアニメーションをするための一連のクラスがありました。QImageSource/Sink/QASyncIO/QASyncImageIO クラスです。しかし、これらを使用するのは QLabel でのアニメーションのみであったため、これらは結局やりすぎでした。

ここから学んだことは、なんらかの漠然とした将来の可能性のために抽象化をするのは良くないということでした。はじめはシンプルにしましょう。そのような未来が来た場合でも、変更するシステムは複雑なものよりシンプルな方が対応も簡単でしょう。

Qt 3 vs. Qt 4 その他?

QWidget::setWindowModified(bool)

Q3URL vs. QUrl

Q3TextEdit vs. QTextEdit

どのように仮想関数を無くしたのか

Qt のクリッピングの物語

クリップの矩形を設定した場合、実際は領域(setClipRect() の代わりに setClipRegion(QRect) であるべき)を設定していること。