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

第4回 コードの分割―その2 2つの方向からの分割― トップダウンとボトムアップ

この記事を読むのに必要な時間:およそ 2 分

2つの方向からの分割

それではコードを分割していきましょう! ……でもちょっとだけ待ってください。あと1点だけ。コードの分割のプロセスには,大きく分けて2種類あります。トップダウン方式とボトムアップ方式です。

トップダウン方式

トップダウン方式では,まず必要なクラスやメソッドを洗い出して分割し,そのあと実装を行います。洗い出しはUMLやホワイトボードを使ってクラス設計したり,テスト駆動開発のように「クラスの使われ方」から洗い出したりします注4)。乱暴にいうと「俺にとって都合のよい,こんなクラスがあったら便利だな」といった具合に,「俺視点」でクラスを創造する方式です。筆者は「神の気分でプログラミング」と呼んでいます。あるプログラマの頭の中での「神々たちのつぶやき」を聞いてみましょう。

私は神だ。これからメール送信を行うクラスを創造するぞ。必要なのはメールを送信するMailSenderクラスだ。必要なメソッドは,そうだな,メール本文のテンプレートデータとメールアドレスとパラメータを受け取ってメールを送信するsendメソッドがあればよいだろう。

MailSender sender = new MailSender();
Map<String,String> params = new HashMap<String,String>();
params.put("name", "太郎");
sender.send("${name}さん,お買い得な情報です。",
"user@example.com", params);

私も神だ。まただまされたな,SMTPサーバのホスト名やポートの指定は必要ないのか?

だまされてはおらぬ,実はもう一つコンストラクタが用意されておるのだ。これでどうだ?

MailSender sender = new MailSender("localhost", 22);

いや,まただまされたな。CCやBCCが追加されるとどうなる?

だまされてはおらぬ,実はMailクラスを用意して引数オブジェクトにするつもりだったのだ(汗)。これでどうだ?

Mail mail = new Mail();
mail.addTo("user1@example.com");
mail.addCc("user2@example.com");
mail.addBcc("user3@example.com");
mail.setBody(""${name}さん,お買い得な情報です。"");
mail.addParam("name", "太郎");
sender.send(mail);

「暇を持て余した神々たちの設計」注5の結果,MailSenderクラス,Mailクラスの外部APIが決定しました。このように使い方=APIから考えて,クラス分割された設計図を作成します。

トップダウン方式は設計の書籍などでもよく取り上げられますし,いわゆるクラス設計という作業はこちらの方式そのものです。

問題は「トップダウン方式は思ったより難しい」という点です。トップダウン方式を行うためには,ある程度のプログラミング経験やクラス設計の力が必要になります。「コードを何手か先まで読める」状態でないと「神の気分」になることは難しく,「何を書いてよいかわからない」ということになりかねません。また事前の予測が外れると大きなダメージを受けることがあります注6)。

なお,トップダウン方式は行き当たりばったりではないので,APIや分割の単位は比較的きれいに整理されることが多いです。

注4)
正確にはテスト駆動開発はリファクタリングやインクリメンタルな手法からボトムアップ的な側面も強いです。ここでは「使い方から考える」という面に着目してトップダウン方式に分類しています。
注5)
ここは笑うところです。意味がわからない人は,「あらびき団神々の遊び」で検索してください。
注6)
いわゆるYAGNI(You Aren't Going to Need It)の精神からすると「事前の大げさな設計は悪である」という考え方もあります。

ボトムアップ方式

ボトムアップ方式はあとから分割する方式です。「とりあえずベタに書いてみて」,そのあとリファクタリングで分割していきます。ボトムアップ方式はシンプルで簡単ですので,初心者の人にはこちらがまずはお勧めです。先ほどのメール送信の例を,今度は「神々」ではなく「普通のプログラマ」にやってもらいましょう。

まずはメール送信を行う処理を書いてみよう。「Java メール送信」でググって,コピって,貼り付けて,……おっとさすがに変数名とかは読みやすく変えておかないと怒られるなー。

String template = "${name}さん,お買い得な情報です。";
String to = "user@example.com";
Map<String,String> params = new HashMap<String,String>();
params.put("name", "太郎")
...
...(テンプレートの置換処理)
...
// メール送信処理
MimeMessage msg = new MimeMessage(session);
...
// メール送信
Transport.send(msg);

できたー。


アドレスとか変わっても共通で使えるように,メール送信処理をメソッドに抽出するのじゃ。

分割してみたー。

String template = "${name}さん,お買い得な情報です。";
String to = "user@example.com";
Map<String,String> params = new HashMap<String,String>();
params.put("name", "太郎")
send(template, to, params);
...
// 抽出されたメール送信メソッド
private void send(String template, String to,
    Map<String,Stirng params) {
    ...
    ...(テンプレートの置換処理)
    ...
    // メール送信処理
    MimeMessage msg = new MimeMessage(session);
    ...
    // メール送信
    Transport.send(message);
    ...
}

ボトムアップ方式は,先に実装コードを書いて,動くようになった時点で分割していきます。この方式は分割を簡単に実現できるので,初心者の人にはまずはこちらがお勧めです注7)。また,トップダウン方式と比べて最低限のシンプルなAPIになる傾向があります注8)。


実際には,トップダウン方式とボトムアップ方式を組み合わせてコードの分割・整理を行うことが多いです。決して対立する概念ではありませんので,うまく使い分けて相互補完するのがポイントです。

注7)
最近はEclipseなどのIDEで,簡単にリファクタリングができますしね。
注8)
最低限のAPIができあがるのはテスト駆動開発も同じですね。

著者プロフィール

縣俊貴(あがたとしたか)

学生時代にMSXで制限された環境でのプログラミングの楽しさを学ぶ。以来,オープンソースのWiki実装「MobWiki」の開発や受託開発などを経て,現在はプロジェクト管理ツール「Backlog」,ドローツール「Cacoo」など,コラボレーション型のWebサービスの企画と製品開発を行う。また,Webアプリケーションフレームワーク「Cubby」のコミッタを務める。福岡在住。株式会社ヌーラボ所属。

ブログ :http://d.hatena.ne.jp/agt

Twitter:@agata

コメント

コメントの記入