ごく最近出くわして嫌な気分になった、C/C++のコードパターン例を一つ紹介します(MFCを使ったのWindows用GUIプログラムです)。
BOOL CMainWindow::AskUserAboutHoge() {
CHogeDialog* dlg = new CHogeDialog( this );
if( dlg->DoModal() == FALSE ) {
delete dlg;
return FALSE;
}
delete dlg;
return TRUE;
}
このコード、オカシイですよね? 🙁
何がオカシイって、名前から分かる通りこのメンバー関数(=メソッド)を呼べばユーザに対してダイアログを表示して何かを聞くはずです。しかし、その結果をダイアログのオブジェクトから取得していません。それなのにシレっと return TRUE ですから、オカシイと思いませんか?私はオカシイと思います。
この例では、CHogeDialogクラスにて、OKボタンが押されたときにユーザ入力値をグローバル変数に設定するよう実装されています。MFCを使った経験があればピンと来るかもしれませんが、その目的でtheAppというグローバル変数が使われます。MFCの世界ではアプリケーションを表すクラスとしてCWinAppという型の派生クラスを作成することが義務(?)付けられています(普通はアプリ名を使ってCHogeAppなどといった名前で定義します)。そして当然、クラスはインスタンス化しないと無意味ですから、CHogeAppクラスを定義するソースの中で「theApp」という名前のグローバル変数が作成されてインスタンス化されます。ここまでは良いのですが、Visual C++はさらにtheAppがプログラム全体からアクセスできるようにお膳立てしてくれます。言い換えると、MFCのGUIアプリをウィザードで新規作成すると最初からtheAppというグローバル変数にどこからでもアクセス可能な状態にしてくれます。その結果、画面間で共有したいデータがあるとすぐtheAppの公開メンバー変数として定義してしまい、プログラム全体が密結合しがちでした。最近出くわしたアプリの場合、theApp(もといCHogeApp)に100近い数の公開メンバー変数(=publicなフィールド)が定義され、あらゆるGUIコードから読み書きされる代物になっていました。数を聞けば予想が付くと思いますが、一時的な処理の状態を格納する変数までこうしてグローバル化されているので、まあ処理を追い切ることは到底できません。
少し考えれば、もう少し良い方法がありますよね。たとえばグローバル変数を介さなくてもコンストラクタにパラメータ受け取り用変数を渡すとか:
BOOL CMainWindow::AskUserAboutHoge() {
// m_dataはCMainWindowが保持するメンバ変数とする
CHogeData data = m_data;
CHogeDialog* dlg = new CHogeDialog( this, &data );
if( dlg->DoModal() == FALSE ) {
delete dlg;
return FALSE;
}
m_data = data;
delete dlg;
return TRUE;
}
あるいは公開メンバー変数やgetter/setterでパラメータを渡すとか:
BOOL CMainWindow::AskUserAboutHoge() {
// m_dataはCMainWindowが保持するメンバ変数とする
CHogeDialog* dlg = new CHogeDialog( this );
dlg->SetData( m_data );
if( dlg->DoModal() == FALSE ) {
delete dlg;
return FALSE;
}
delete dlg;
dlg->GetData( m_data );
return TRUE;
}
これらはグローバルに持たれていたデータをCMainWindowに移動した上で、受け渡しをコードで明示するように改善した例です。データ変数のスコープは大差ありませんが、上記2例ではメンバー関数のソースを読むだけで「CMainWindowが管理するデータをCHogeDialogが加工し、ダイアログがOKした場合にのみそれが反映される」ことが分かります。最初の例では、CHogeDialogでOKすると何が起こるかを読み解くためにCHogeDialogのOKボタン押下時のソースを読みに行く必要があり、まったく不毛です。こういう小さな可視性は、メンテ対象のアプリが大規模になればなるほど重要です。
当時のMicrosoftには、オブジェクト指向に移行してもらうためにMFCからmain関数を排除するよりも、こうした当たり前の啓蒙もして欲しかったなぁと思ったりします。