良いコ-ドへの道―普通のプログラマのためのステップアップガイド

第4回コードの分割―その6 Step4:クラスに分割する

Step4:クラスに分割する

それではいよいよクラスの分割です。

まず、ここでは一時変数のrowIndexに注目します。先のリスト5 のbuildDocument メソッドを見ると、rowIndexはで宣言されて、②③のループの中でインクリメントされながら使われています。変数が使用されている範囲はbuildDocumentメソッドの全体に渡っていて、その間XMLを生成するときに必要な現在行の状態を保持しています。このように、長い範囲で使用されるローカル変数があると、リファクタリングが行いにくくなるという問題があります。

たとえば、組織要素、ユーザ要素の追加を別メソッドにする場合、それぞれのメソッドでの行の増分をrowIndexに足したり、現在のrowIndexを渡したりする必要があります。つまり、メソッド呼び出し、戻しの際に「状態を引き継ぐ」必要が出てくるのです。

このような「状態を表すローカル変数」は、まずその処理自体をクラスに抽出します。次に「状態を表すローカル変数」をフィールド変数に移動することで、メソッド間の状態の引き継ぎを一掃でき、後のリファクタリングが行いやすくなります。

クラスを抽出した結果はリスト6図4です。XmlBuilderという名前のクラスを作成してnewして使用しています⁠。rowIndexはXmlBuilderクラスのフィールド変数に移動しています⁠。createDivisionsNodeメソッド⁠、createUsersNodeメソッドで組織、ユーザの要素を作成していますが、rowIndexがフィールド変数となっているのでこのリファクタリングが可能になりました。引数でrowIndexを渡したり、増分を戻したりする必要はありません。そのほかのXmlBuilder が使用するメソッドは、XmlBuilderクラスに移動しています。

リスト6 Step4:クラスの抽出による分割
public class XmlApiAction extends Action {
    ...
    public ActionResult index() throws Exception {
        List<Division> divs = diviisonService.getDivisions();
        List<User> users = userService.getUsers();
        // XmlBuilderをnewして使用する
        XmlBuilder builder = new XmlBuilder(divs, users);     ―①
        Document doc = builder.buildDocument();
        writeDocument(doc);
    }
    private void writeDocument(Document doc) {
        ...
    }
    // インナークラスで宣言!
    private static class XmlBuilder {
        private final List<Division> divs;
        private final List<User> users;
        private int index = 1;     ―②
        public XmlBuilder(List<Division> divs, List<User> users) {  ┓
            this.divs = divs;                                       |
            this.users = users;                                     |
        }                                                           ┛③
        private Document buildDocument() throws Exception {
            Document doc = newDocument();
            Element rootNode = doc.createElement("data");
            doc.appendChild(rootNode);
            rootNode.appendChild(createDivisionsNode(doc));
            rootNode.appendChild(createUsersNode(doc));
            return doc;
        }
        private Element createDivisionsNode(Document doc) {              ┓
            Element divisionsNode = doc.createElement("divisions");      |
            for (Division div : divs) {                                  |
                Element node = doc.createElement("division");            |
                node.setAttribute("index", String.valueOf(rowIndex++));  |
                ...                                                      |
            }                                                            |
            return divisionsNode;                                        |
        }                                                                ┛④
        private Element createUsersNode(Document doc) {                  ┓
            Element usersNode = doc.createElement("users");              |
            for (User user : users) {                                    |
                Element node = doc.createElement("user");                |
                node.setAttribute("index", String.valueOf(rowIndex++));  |
                ...                                                      |
            }                                                            |
            return usersNode;                                            |
        }                                                                ┛⑤
        private Element createElement(Document doc, String nodeName,String textContent) {
            ...
        }
    }
...
}
図4 Step4のクラス図
図4 Step4のクラス図

最終的にすべての変数のスコープとメソッドが「本当にその変数が必要な範囲」と一致するようにリファクタリングされて整理されました。変数やメソッドが必要な範囲は明確になり、メンテナンスが行いやすい状態になっています。

考察1:XmlBuilderクラスをインナークラスとして実装しているのはなぜ?

クラス内にクラスを宣言するインナークラスは使い出すとかなり便利です。今回のように特定の場所だけで使用される処理のインナークラスによる実装には、次のメリットがあります。

実際に使用される個所のすぐ近くにクラスがあるのでわかりやすい

アクセスレベルをprivateにすることで、ほかのクラスから利用させないようにできます。ただしprivateなクラスだとテストが書きにくいので、package privateにするのがよいでしょう。

元のクラスとの依存関係をstaticで制御できる

staticなインナークラスにすると元のクラスのフィールド変数にはアクセスできませんが、その分依存が少ないクラスになります。staticを外すともとのクラスのフィールド変数にアクセスできます[13]⁠。ケースバイケースで使い分けるとよいでしょう。

新しいファイルを増やさなくてよい

初心者のころは新しいクラスを作成するのは勇気のいることです。特に新しいファイルを増やすとなんだかリーダーに怒られるんじゃないか?と心配になる人もいるんじゃないかと思います。インナークラスなら新しいファイルを増やす必要がないので、気軽にクラスを作れるようになります。本来インナークラスか否か?というのは、技術的な判断で決定されるべきです。しかし、現実的に人がプログラミングするので、このような心理的なメリットは十分大きいと筆者は考えます。


まとめると、外部向けにクラスを作るほどおおげさではないけど、状態を保持する自分都合のクラスを作りたくなったら、インナークラスがお勧めです。

考察2:divs、usersをコンストラクタで渡しているのはなぜ?

リスト6のXmlBuilderのコンストラクタは組織とユーザ情報のリストが引数になっています。これは、XmlBuilderからdivisionService、userServiceへの依存をなくし、テスタビリティを高めるのが目的です。このようにしておけば、テスト用の組織とユーザのリストをコンストラクタで渡すことで、簡単にXmlBuilderをテストできます。staticなクラスとして宣言しているのは、XmlBulilderからXmlApiActionのフィールド変数であるdivisionService、userServiceへのアクセスを防ぐためです。

考察3:フィールド変数になりそうなもの

クラスを抽出した際にフィールド変数として保持したほうがよいものとしては、フラグや状態を表す変数、いくつかのインスタンスメソッドで使用するデータ、いくつかの値をセットで扱う必要がある変数などがあります。

考察4:これからどうする?

ここからの発展系としては、ローカル変数docをフィールド変数に移動して、引数からdocをなくすこともできます。こちらは本誌Webサイトからの配布用のサンプルコードに「Step5Action」として含まれていますので、興味がある人はそちらもご覧ください。

今回のまとめ

今回は処理の分割について考えていきました。メソッド分割やクラス分割に答えはありませんが、試行錯誤することで、勘所は必ずつかめてきます。本稿が「メソッド分割やクラス分割って気軽にやってもいいんだ!」という考えのきっかけになれば幸いです。

今まで、連載では「名前付け」⁠スコープ」⁠コードの分割」という順番で「良いコード」の基本的な要素について扱ってきました。次回は少し趣向を変えて、⁠プログラムを作成するプログラム⁠⁠、メタプログラミングについての話です。お楽しみに。

おすすめ記事

記事・ニュース一覧