WEB+DB PRESS Vol.51 特集2「“巧い”メソッド設計」連動企画

第3回 構造化技法でプログラムの品質を上げる 可読性の高いメソッドを書くための実践テクニック

短いメソッドを書こう

みなさんは、小説のような長いメソッドを見たことがあるでしょうか。いつ終わるかわからないif-else文。エディタの画面をスクロールしているはずなのに「スクロールしてないんじゃないか?」という錯覚に陥るくらい同じ光景が延々と続くソースコード。

こういったプログラムを保守するのはとてもたいへんです。そして、保守性が低いということは、すなわち「品質が低い」ということにほかなりません。

本稿では、どうすれば保守性が高い(品質が高い)プログラムを書くことができるか、筆者の経験に基づく独断と偏見から記述していきたいと思います。

変数のスコープを最小限に抑える

変数のスコープ(寿命・可視性)を最小限に抑えると、プログラムの解析性を高めることができます。

ローカル変数は、変数のスコープとしては最も狭い部類に入りますが、メソッドが長いときに問題になるのがローカル変数です。ローカル変数が遥か彼方で大量に宣言されていることを想像してみてください。今読んでいるソースコードの該当個所に関係する変数がいったいどういう状態なのかをすべて把握するのは非常に困難です。こうなるとローカル変数もグローバル変数となんら変わりがなく、グローバル変数のデメリットがローカル変数にもそのまま当てはまることになります。

影響のある変数の数を最小限に抑えるために「変数のスコープ=メソッドの粒度」となるように、一つ一つのメソッドは小さく実装し、小さなメソッドの集まりとして大きな機能を構築しましょう。

変数はfinal宣言しよう

変数があちこちでコロコロ書き換えられる可能性を排除すると、プログラムの解析性が高くなります。

本質的には「変数」にしなければならないものは「繰り返し」処理のループカウンタやアキュムレータ[1]以外にはありません[2]⁠。もしfinalにできない変数があるとしたら、その変数をfinal宣言できるようにメソッドの構造を分割しましょう。

処理の構造とメソッドの構造を一致させよう

プログラムの「理解しやすさ」

  1. 論理的な処理の構造
  2. 物理的なメソッドの構造

の2つが一致していることによってもたらされます。⁠論理的な処理の構造」とは、ある仕様が与えられたとき、その仕様を実現するためには「こういった処理が必要だろう」という推論をトップダウンで進めたときに得られるロジックツリーのことです。 このような論理的な処理の構造を図示する技法として、少し古い技法になりますが「構造化チャート」というダイアグラムが利用できます。

図1 構造化チャートの記述方法
図1 構造化チャートの記述方法

一方、⁠物理的なメソッドの構造」とは、まさにメソッドの「呼び出し階層」のことです。実際に実装したメソッドの呼び出し階層はEclipseで簡単に確認できます図2⁠。

図2 Eclipseでの呼び出し階層の表示
図2 Eclipseでの呼び出し階層の表示

「論理的な処理の構造」「物理的なメソッドの構造」が一致しているということは、⁠考えていること」「プログラムのテキスト構造」がシンクロしている状態といえます。このようなプログラムが理解しやすいのは間違いありません。

メソッドの抽象化レベルを合わせる

プログラムと文書作成にはアナロジー(類比)があります。文書は通常「章/節/項」に構造化して記述します図3⁠。章は節の集合、節は項の集合から構成されます[3]⁠。

図3 文章の構成
図3 文章の構成

プログラムも同様に「章/節/項」に構造化して記述しましょう。

同じ階層にあるものは同じレベルの抽象度に合わせるとプログラムは理解しやすくなります[4]⁠。

呼び出し階層を浅く保つ

抽象化の階層は深いほうがよいのでしょうか? それとも浅いほうがよいのでしょうか? 具体例でみてみましょう。

処理A・処理B・処理Cを順番に実行するリスト1リスト2の2つの例を見てください。

リスト1 呼び出し階層が深い
public SomeObject doSomething() {
    [処理A...]
    return doB(a);
}

private SomeObject doB(Object b) {
    [処理B...]
    return doC(b);
}

private SomeObject doC(Object c) {
    [処理C...]
    return obj;
}
リスト2 呼び出し階層が浅い
public SomeObject doSomething() {
    Object a = doA();
    Object b = doB(a);
    return doC(b);
}

private SomeObjectA doA() {
    [処理A...]
    return a;
}

private SomeObjectB doB(SomeObjectA a) {
    [処理B...]
    return b;
}

private SomeObject doC(SomeObjectB b) {
    [処理C...]
    return obj;
}

どちらの構造が保守性が高いでしょうか。たとえば、処理A・処理B・処理Cがそれぞれ、データベースにアクセスする処理だったとします。パフォーマンスや同時実行性の観点から処理の順番を入れ替えることになった場合、呼び出し階層が浅いリスト2のほうが簡単に順番を入れ替えられます。 もし、リスト2のように処理の順番が呼び出し階層で表現されているとの順番の入れ替えが難しくなります。

次に、処理A・処理Bには副作用がなく、処理Cだけ副作用がある場合を考えてみます。具体的には、処理Cはファイルの読み込み処理だったり、ダイアログボックスを表示してユーザ入力を待ったり、といったケースです。

リスト1の形だと、結局doSomething・doB・doCのすべてのメソッドで副作用が発生することになり、たとえばJUnitでのテストが困難、あるいは不可能、というデメリットが発生します。

リスト2の形になっていれば、doA doBメソッドについてはピンポイントでJUnitテストケースを作成できるでしょう。

リファクタリングで「メソッドの抽出」を進めていくと、どうしてもメソッドの呼び出し階層が深くなりがちです。

しかし、上記の例から、メソッドの呼び出し階層を浅く保ち、階層の上下ではなく同じ階層の横の関係で処理の順序を表現したほうが保守性が高くなることがわかります。

メソッドの記述順序について

C言語などの古い言語と違い、Javaではメソッドの記述順序について煩わしい制約がなく、自由な順序で記述できます。

高度なソースコードブラウジング機能を備えたIDEIntegrated Development Environment; 統合開発環境)が簡単に利用できるようになった昨今、メソッドの記述順序が話題に上ることはめったにありません。

どのコーディング規約を見ても「可視性の高い順」「わかりやすい順序」といった大雑把な定義しかなかったり、そもそもメソッドの記述順序にはまったく触れていない、といったものが大多数です。

それでも、これまでに説明した内容を踏まえ、一歩踏み込んで、図4のような一連のメソッドをリスト3のような順序できれいに構造化して記述することを提案してみたいと思います。

図4 メソッドの構造化された配置
図4 メソッドの構造化された配置
リスト3 メソッドの構造化された配置
public class Foo {

    public void method1 {
        [処理1]
    }

    private void method1_1 {
        [処理1.1]
    }

    private void method1_1_1 {
        [処理1.1.1]
    }

    private void method1_1_2 {
        [処理1.1.2]
    }

    private void method1_2 {
        [処理1.2]
    }

    private void method1_2_1 {
        [処理1.2.1]
    }

}

publicメソッドが複数ある場合は、publicメソッドをどう配置するかは2パターンに分かれます。

1つはpublicメソッドを先頭にまとめて配置する方法です。これはpublicメソッドが目次の役割を果たします。

もう1つは、文書と同じく各章の先頭にpublicメソッドを配置する方法です。

これは、プロジェクトのコーディング規約に従ってどちらのパターンを採用するか決めれば良いでしょう。たとえば「メソッドは可視性の高い順に並べる」というコーディング規約があれば、前者しか選択の余地はありません。

おわりに

保守性の高い、すなわち品質の高いプログラムを書くということは、読み手に優しく、読み手の理解を助けるような、きれいに構造化されたプログラムを書くことにほかなりません。

しかし、これが唯一の正しい方法だとは思いません。ここに記載した方法がみなさんにとっての「巧いプログラム」の書き方についての議論の出発点になれば幸いです。

おすすめ記事

記事・ニュース一覧