Step4:配列/コレクション化して抽象化する
抽象化完了! まずメインの処理の中で,共通的な部分と違う部分に着目してみました。違う部分は「画像ディレクトリのパス」だったので,パスの一覧を配列として持って(リスト6①),ループでパスを切り替えながらImageFilesの生成を行うようにしたっす(③)。
データと処理を分けた感じですね。取得したデータはFilesListというList型の変数に保持しています(②)。
リスト6 Step4Action
@Path("/")
public class Step4Action extends Action {
public static final String[] PATHS = { ┓
"/images/food", |
"/images/animal", |
"/images/landscape" |
}; ┛①
public ServletContext context;
public List<ImageFiles> filesList; ― ②
public ActionResult step4() {
List<ImageFiles> filesList = new
ArrayList<ImageFiles>();
for (String path : PATHS) { ┓
filesList.add(getFiles(path)); |
} ┛③
this.filesList = filesList;
return new Forward("step4.jsp");
}
private ImageFiles getFiles(String path) {
File[] files = new File(context.getRealPath(path))
.listFiles();
return new Files(path, files);
}
public static class ImageFiles {
private final String path;
private final File[] files;
private final long sizeOfFiles;
public ImageFiles(String path, File[] files) {
this.path = path;
this.files = files;
this.sizeOfFiles = FileUtil.
sizeOfFiles(files);
}
public String getPath() {
return this.path;
}
public File[] getFiles() {
return this.files;
}
public long getSizeOfFiles() {
return this.sizeOfFiles;
}
}
}
これで対象の画像ディレクトリが追加されても,定数にパスを追加するだけで対応できますね。
JSPもコレクション(List)を回してデータを出力するようにしたっす(リスト7①)。ちょっと問題だったのが各ディレクトリのラベル文字列。ImageFilesオブジェクトにパスを保持しておき,プロパティファイル(リスト8)から画像ディレクトリのパスをキーにラベル文字列を取得するようにしたっすよ(リスト7② ③)。
リスト7 step4.jsp
<body>
<h1>Good Code Way 6 - Step5</h1>
<c:forEach var="files" items="${filesList}" ┓
<c:set var="labelKey" value="label.${files.path}"/> ― ② |
<h2> |
${messages[labelKey]} ― ③ |
(<fmt:formatNumber |
pattern="0" value="${files.size / 1024}"/>KB) |
</h2> |
<ul> |
<c:forEach var="file" items="${files.files}"> |
<li>${file.name}</li> |
</c:forEach> |
</ul> |
</c:forEach> ┛①
</body>
リスト8 messages.properties
label./images/food=Food Photos
label./images/animal=Animal Photos
label./images/landscape=Landscape Photos
ImageFilesを生成する部分,JSPでビューを描画する部分の2ヵ所が抽象化されてループ構造で処理できるようになったんじゃな。コードは少しだけ長くなったが,配列とコレクションを利用したおかげで変更に強い可読性の高いコードになったのう。君たちの今後の成長が楽しみじゃのう,ふぉっふぉっふぉっ。
考察1:どのタイミングで抽象化するのがベストなの?
今回の例のように簡単なものであれば,最初から抽象化しても問題ないでしょう。抽象化は,「何手先まで読めるか?」「将来の変更がどの程度予想がつくか?」という点がポイントです。早過ぎる抽象化や無駄な抽象化はシステムを複雑にし,メンテナンスを難しくしてしまうので「良いコード」とは言えません。その勘所を知るためには,抽象化がその後の開発でどの程度貢献したかを振り返るとよいでしょう。その過程を繰り返すことで,判断能力や先読み能力が磨かれていきます。
考察2:コードの重複をまとめるな!?
「コードの重複は悪」とよく言われますが,ここには落とし穴があります。同じコードは必ずまとめたほうが良いというわけではありません。本来異なる意味合いを持っているものが「たまたま似たような処理」や「たまたま似たような画面」になっていることがあるからです。これらを間違ってまとめてしまうと,あとで違いが出てきたときにif文だらけになってしまったり,コードが複雑になったりしてたいへん苦労することがあります。
よくある例として「追加画面」「編集画面」をまとめるか分けるか,という問題があります。たとえばユーザの「追加画面」と「編集画面」は似ています。ビューも処理のコードも似ていることが多いので,これらをまとめてしまいがちです。けど,ちょっと待って。「追加画面」と「編集画面」は,似ているように見えて違う場合が結構あるのです。たとえば,追加時にはIDの入力が必須なのに,編集時は変更不可であったり,求められる仕様自体が違う場合がよくあります(注11)。なのであえてまとめないほうがメンテナンス性に優れることも多いのです。
- 注11)
- ただし,これもケースバイケースです。筆者の経験ではマスタメンテナンス画面(データベースの1テーブルに対する追加,編集,削除などの基本的なメンテナンス画面)では追加と編集に違いが少なく問題にならない場合が多く,トランザクション系の画面では追加と編集で「仕様」や「画面遷移」などが異なる場合が多いようです。
考察3:これって単なるループなんじゃない?
そのとおり! グッドクエスチョンです。単なるループも単なる関数も,「複数のデータ間でプログラムの構造を共有する部分を抜き出す」という意味においては立派な「抽象化」です。「ループでまとめて処理する」ためには,「配列/コレクション」のデータやオブジェクトが必要なので,抽象化と配列/コレクションはセットで現れることがよくあります。
なお,今回は誌面の都合もありシンプル過ぎる例でしたが,これくらいシンプルなものできちんと抽象化ができてこそ,さらに複雑な抽象化にチャレンジできるのです。
今回のまとめ
今回は基本的過ぎて無意識に行っている「配列/コレクションを利用した抽象化」について考察しました。このような基本も,よく考えを深めていくことで自分の中で定着し意識的に有効活用できるようになっていきます。
本連載は今回で終了しますが,良いコードへの道にはまだまだ大切な「基本」がたくさん存在します。ぜひ「良いコード」に対する感性を磨き,学び・考え・実践を続けていってください。いつか読者のみなさんが「達人プログラマ」の域まで達することを願っています。それでは最後に「Enjoy Programming!」
お勧めサイト/書籍
- 『達人プログラマー ― システム開発の職人から名匠への道』
Andrew Hunt/David Thomas(著),
村上 雅章(訳),ピアソン・エデュケーション,2000年 - 今回出てきた「DRY原則」はこの本に載っています。本連載で紹介してきた内容も含めて,実践的かつわかりやすい文章で書かれています。超有名な本ですが,まだ読まれていない人は,本連載の次はぜひこの本を読むことを強くお勧めします。
- 『実装パターン』
Kent Beck(著),長瀬 嘉秀/永田 渉(監訳),
株式会社テクノロジックアート(訳),ピアソンエデュケーション,2008年 - 「この本はプログラミングに関する本だ。もう少し正確に言うと,他の人が読んで理解できるコードの作成方法について書かれた本だ」という書き出しから始まる良いコードのための本です。Java中級者~上級者向けではありますが,個人的にぼんやりと感じていた「良いコードの書き方」がはっきり示されていて,目から鱗な感覚でした。第6章「状態」の「共通の状態」の節に,本稿の「ペアになる情報をまとめる(ImageFiles)」に関連する記述があります。

