give IT a try

プログラミング、リモートワーク、田舎暮らし、音楽、etc.

共通関数継承のデメリットを考える

id:katzchang さんのエントリに触発されて、おいらも共通関数継承のデメリットを考えてみました。
共通関数継承のデメリットを説明せよ - @katzchang.contexts

共通関数継承のデメリット

  • 低凝集度、密結合のクラスができあがる。その結果、変更に弱く、再利用性が低いクラスとなる。
  • 単一継承しかできない言語の場合、別の機能を拡張したり、フレームワークを活用しようとしたときに新たに継承を行うことが困難になる。
  • 継承に関するルールがない場合、各開発者が好き勝手に既存のクラスを継承して機能を拡張しようとする。その結果、複雑で一貫性のないクラス階層ができあがってしまう。


おいらが考える大きなデメリットはこんなところですかね〜?
一番大きなデメリットは最初の凝集度や結合度の問題かな。


テスト容易性が落ちる、という意見もあるようですが、これは継承が直接もたらすデメリットではなく、何らかのフレームワークがないとコードを実行できない場合に発生する問題であるような気がします。

共通関数継承から疑われる根本的な設計上の問題について

そもそも共通関数継承が登場するということはおそらくオブジェクト指向分析やオブジェクト指向設計を採用せずに開発が進んでいる可能性が高いです。
きっとクラスをモジュールやライブラリのような機能の集合体だと勘違いしている人が設計していると思われます。


となると、オブジェクト指向における設計の原則*1は全く守られない、というか発想のベースが根本的に異なるので、まず最初から作り直さないと原則も適用できません。


共通関数継承を改善するには共通で使われる機能を別クラスに切り出せば、結合度が下がり、再利用性も向上しますが、おそらくそれ以上に「もっとまずい何か」潜んでいるでしょうね。

なぜアンチパターンとしてのインパクトに欠けるのか

ところでなぜこの問題のインパクトが弱いのか、相手を説得するための決定打を出しにくいのか、という点についても考えてみました。
おいらが考えた理由は


「共通関数継承のデメリットは主に変更容易性や再利用性に関連するものであり、可読性や不具合とは直結しにくいから」


というものです。まあ、これはこれで何かインパクトに欠ける気もしますけどね。。。


さらに、問題を指摘した相手がオブジェクト指向設計に興味がなければ、「ふ〜ん、あっそう」で終わってしまいそう。
やっぱり決定打は出ないのかな〜?う〜ん。。。

参考文献

最後にこの手の話を議論する際に参考になりそうな書籍をいくつか挙げておきます。
このエントリを書く際にも参考にしました。


オブジェクト指向開発の落とし穴

オブジェクト指向開発の落とし穴

  • 作者: ブルース・F.ウェブスター,Bruce F. Webster,細井拓史
  • 出版社/メーカー: ピアソンエデュケーション
  • 発売日: 2000/04
  • メディア: 単行本
  • 購入: 2人 クリック: 37回
  • この商品を含むブログ (15件) を見る
正しく学ぶソフトウエア設計 ─オブジェクト指向分析/設計を根本から理解する (日経BPパソコンベストムック)

正しく学ぶソフトウエア設計 ─オブジェクト指向分析/設計を根本から理解する (日経BPパソコンベストムック)

  • 作者: 天野勝,平澤章,平鍋健児,矢沢久雄,山本啓二,日経ソフトウエア
  • 出版社/メーカー: 日経BP社
  • 発売日: 2005/10/07
  • メディア: ムック
  • 購入: 1人 クリック: 23回
  • この商品を含むブログ (15件) を見る
実践UML 第3版 オブジェクト指向分析設計と反復型開発入門

実践UML 第3版 オブジェクト指向分析設計と反復型開発入門

  • 作者: クレーグ・ラーマン,依田智夫,今野睦,依田光江
  • 出版社/メーカー: ピアソンエデュケーション
  • 発売日: 2007/11/12
  • メディア: 単行本(ソフトカバー)
  • 購入: 8人 クリック: 179回
  • この商品を含むブログ (29件) を見る
アジャイルソフトウェア開発の奥義 第2版 オブジェクト指向開発の神髄と匠の技

アジャイルソフトウェア開発の奥義 第2版 オブジェクト指向開発の神髄と匠の技

  • 作者: ロバート・C・マーチン,Robert C. Martin,瀬谷啓介
  • 出版社/メーカー: SBクリエイティブ
  • 発売日: 2008/07/01
  • メディア: 単行本
  • 購入: 18人 クリック: 586回
  • この商品を含むブログ (72件) を見る
オブジェクト指向入門 第2版 原則・コンセプト (IT Architect’Archive クラシックモダン・コンピューティング)

オブジェクト指向入門 第2版 原則・コンセプト (IT Architect’Archive クラシックモダン・コンピューティング)

オブジェクト指向入門 第2版 方法論・実践 (IT Architects' Archiveクラシックモダン・コンピューティング)

オブジェクト指向入門 第2版 方法論・実践 (IT Architects' Archiveクラシックモダン・コンピューティング)


こうした本で勉強して、分かりやすく変更に強いシステムを設計しましょう♪

*1:オブジェクト指向設計の原則については http://www.syboos.jp/sysdesign/category/20080607211127406.html とか http://ja.wikipedia.org/wiki/GRASP を参照

O/Rマッピングツールに対する誤解をときたい -実装編 Part9-

Part8を読む

これまでのまとめ

当初考えていた以上に長いシリーズとなってしまったので、一度全体を振り返ってみます。
また、その後でO/Rマッピングツールを採用するかどうかの判断基準や考慮点、アンチパターンなんかも書いてみたいと思います。

これまでに書いてきたエントリの概要

O/Rマッピングツールに対する誤解をときたい
このシリーズの出発点となった「元ネタ」です。
とあるコラムの議論を読んでいて、「なんか論点がおかしいな〜」と感じたのでこのエントリを書きました。
O/RマッピングツールはSQLを書きたくない人のためのツールではなく、インピーダンスミスマッチ問題を解決するためのツールであるということを文章で説明しています。


実装編 Part1
Part1ではサンプルプログラムの仕様について説明しました。
またインピーダンスミスマッチ問題を解決する過程よりもむしろ、O/Rマッピングツールが実現する「オブジェクトの透過的な永続化」の説明に重点をおき、オブジェクト指向プログラミングやO/Rマッピングツールの有用性を理解してもらおうと考えました。


実装編 Part2
Part2ではサンプルプログラムのプログラム設計を紹介しています。
ドメインモデルのクラス設計では継承の概念が登場しています。
また、Smart UIパターンではなくUIとビジネスロジックを分離するレイヤー化アーキテクチャを選択しました。
処理シーケンスの中ではビジネスロジックの実装にポリモーフィズムを活用することを説明しています。


実装編 Part3
Part3ではUI側の実装コードを載せています。
レイヤー化アーキテクチャを採用しているため、UI側のコードには基本的に画面制御に関わるコードしか出てきません。
ビジネスロジックを実現するためにUI側でしなければならないのは、Facadeクラスのメソッドを呼び出すことだけです。


実装編 Part4
Part4ではドメインモデルクラスの実装コードを載せています。
Part2で設計した通り、書籍クラスと和書クラス、洋書クラスは継承関係にあり、「発送予定日を計算する」メソッドではポリモーフィズムが使えるようになっています。


実装編 Part5
Part5では注文Facadeクラスの実装コードを載せています。
また、ここからNHibernateを使ったコードが登場します。
Part2で説明した処理シーケンスとほぼ同じコードがFacade内で実装されており、書籍オブジェクトに対する操作にはポリモーフィズムを活用しています。
また、NHibernateを利用したおかげで、オブジェクトの保存や取得が非常に簡潔なコードで実現できています。


実装編 Part6
Part6では書籍Facadeの実装コードを通じて、NHibernateのHQLを説明しています。
HQLとSQLは「似て非なるもの」であり、HQLはデータベース上のテーブルやカラムを操作するのではなく、プログラム上のクラスやプロパティを操作していることを説明しました。


実装編 Part7
Part7ではテーブル設計やNHibernateのマッピングファイル等、データベースの設計や設定に関する説明をしています。
また、プログラム上のクラスとデータベース上のテーブルは完全には一致しないことも説明しています。(このあたりがいわゆる「インピーダンスミスマッチ」です。)


実装編 Part8
Part8ではNHibernateに関する補足説明を行いました。
Lazy Loading機能、ポリモーフィックなHQL、select節を使用するHQL、パフォーマンス問題等に遭遇したときの参考資料などを紹介しています。

O/Rマッピングツールを採用するかどうかの判断基準について

このシリーズでは「O/Rマッピングツールに対する誤解をとく」のが目的であって、O/Rマッピングツールを銀の弾丸として賞賛しようとしているのではありません。
それどころかむしろ、O/Rマッピングツールを開発業務で採用するには多くの障壁が存在すると考えています。


4〜5年前にHibernateを開発業務で使った経験から、O/Rマッピングツールを採用するかどうかの判断基準を自分なりに紹介してみたいと思います。


理想的には上記の項目がすべて「YES」になることですね。
逆に「NO」が多ければ多いほど、O/Rマッピングツールを導入したけど逆効果、ということになりかねません。
このあたりの話は元ネタの後半部分でも述べています。

O/Rマッピングツールの考慮点

続いて、O/Rマッピングツールを採用する前に十分に考慮が必要な項目を挙げてみたいと思います。
ただし、以下のようなネガティブな要素があるからといって「O/Rマッピングツールはダメだ」とすぐに結論づけないでください。
O/Rマッピングツールや他の選択肢とのメリットとデメリットを比較し、最も適切な技術を選択するバランス感覚が、技術者に求められる重要なスキルの一つなのではないでしょうか。


実行パフォーマンス
O/RマッピングツールはSQLよりも遅い」と頭から決めつけるのは危険な考え方だと思います。
例えばHibernateNHibernateにはキャッシュ機能があるので、同じオブジェクトはデータベースにクエリを発行せずに取得できたりします。
パフォーマンスを効率化するための戦略が色々と用意されていることはPart8でも述べた通りです。
また、O/RマッピングツールがSQLよりも実際に遅いケースがあったとしても、システムの要件からすると特別問題がないレベルだったりします。
さらに、開発者のスキルによってはヘタなSQLを書くよりも、ツールが出力したSQLの方が高効率ということもあるかもしれません。


ただし、実際問題としてO/Rマッピングツールのデフォルトの挙動がボトルネックになってしまうということはよくある話だと思います。
そういう問題に遭遇したときに最初から匙を投げるのではなく、Part8で紹介したような資料を使って有効なオプションを探っていく姿勢が重要だと思います。


他システムとの連携
テーブル設計をO/Rマッピングツールに最適化させた場合、状況によっては少し奇妙なテーブル設計に見えることがあるかもしれません。
O/Rマッピングツールにとっては都合が良くても、同じツールを使っていない他のシステムからそのテーブルを利用しようとすると扱いづらいということが起こりえます。
ただし、クラス設計がきれいにできていれば、自ずとテーブル設計もきれいなものになっていくはずです(たぶん)。
また、テーブルだけでなくロジックも他のシステムから再利用するのであれば、ストアドとして実装されていた方が利用しやすい、というケースもおそらくあるでしょう。


システムの寿命とO/Rマッピングツールの寿命
システム開発に関わる要素のすべてに寿命があります。
データベース、開発言語、O/Rマッピングツールにもそれぞれ寿命があるわけです。
そしてその中にシステムの寿命よりも寿命が短いものがあると、将来的に移行コストが発生します。
未来のことは誰にも分かりませんが、感覚的に言って寿命の長さは「データベース(SQL) > 開発言語 > O/Rマッピングツール」となりそうな気がします。
よって採用したO/Rマッピングツールのメンテナンスが終了し、最新のプラットフォームに対応できなくなるというリスクを事前に考慮しておいた方が良いと思います。
システムの寿命が長そうであれば、最初からSQLやストアドを使って実装したり、DAOパターンなどを使ってデータアクセス層を容易に置換できる設計を採用したりする選択肢を考える必要があります。

O/Rマッピングツールのアンチパターン

O/Rマッピングツールのアンチパターンは言わずもがな、「O/RマッピングツールはSQLを書きたくない人のためのツールだ」と誤解することだと思います。
そして、上で書いた判断基準で「NO」が多ければ多いほど、泥沼にはまっていくことになると思います。
Web上の簡単な紹介記事だけを読んで単純に「あ、良さげ」と思いこんで、採用したりすると結構痛い目を見るのではないでしょうか?


一つのテーブルを操作するだけの簡単なシナリオであれば、Web記事レベルの知識で事足りますが、実際の開発業務ではWeb記事とは比較にならないような複雑なシナリオだらけです。
SQLを書かずに楽しちゃおう」という考えしか持たずに実戦に挑んでも、すぐにツールのクセや制約という壁にぶつかって「最初からSQLを書いてた方が速かった」というオチが待っているだけだと思います。


SQLでできることをO/Rマッピングツールを使ってやろうとするなら、O/Rマッピングツールが間に入るぶん全部ムダです。
そうではなくて、「SQLじゃできないことをやりたい、だからO/Rマッピングツールを使う」という動機が必要になると思います。
ここで言う「SQLでは出来ないこと」というのは、継承やポリモーフィズムといったオブジェクト指向プログラミングのテクニックを駆使することです。

まとめのまとめ

自分の言いたいことはすでにPart7で書いています。
つまり、


O/RマッピングツールはSQLを書きたくない人のためのツール


ではなく、O/Rマッピングツールは

  • 最初からプログラムをオブジェクト指向で設計できる
  • 実装する上でもデータベースのことは気にせず、クラスやオブジェクトを操作することだけに開発者が集中できる
  • ポリモーフィズムや継承など、オブジェクト指向プログラミングのメリットを存分に活用することができる
  • 結果、プログラムの開発効率や保守性、拡張性を高めることができる


といったことを可能にしてくれるツールだということです。
O/RマッピングツールはSQLを書きたくない人のためのツール」だと思い込んでいると、「わざわざO/Rマッピングツールを使うメリットなんてない」というおかしな結論に至ってしまうのは必然です。


こうしたことはこのシリーズを通じて何度も言っていることですし、このシリーズを通して実現したかったのはネット上でよく見かける「O/Rマッピングツールに対する誤解」をとき、多くの方々にO/Rマッピングツールの目的やメリットを正しく理解してもらうことです。
そして、もしO/Rマッピングツールに関する議論や批判をするならば、あくまでこうした前提を共通認識として持ってもらった上で議論してほしいと考えています。


以上、長々としたシリーズになってしまいましたが、こうしたブログが少しでもみなさんのお役に立てば幸いです。

O/Rマッピングツールに対する誤解をときたい -実装編 Part8-

Part7を読む

NHibernateに関する補足説明

できればサンプルプログラムを1から10まで説明していきたいのですが、そうするとボリュームが凄いことになってしまうので、注目すべき部分のみをピックアップしてみたいと思います。

Lazy Loading機能

Lazy LoadingについてはPart6Part7で少し言及しました。
SQLではテーブルをJOINして複数のテーブルにまたがるデータを一度に取得するのが普通ですが、NHibernateでは必要になったタイミングで必要なデータを取得するというのがデフォルトの挙動です。


これを聞くと「なんて非効率な!」と思われる人が確実に出てきますが、大量のデータを一度に処理したり、Criticalなパフォーマンス要件が存在したりするのでなければ、それほど問題にはならないはずです。
Lazy Loading機能を活用すれば、明示的に取得するのは親(ルート)となるオブジェクトだけで良くなり、子や孫のオブジェクトは芋づる式にNHibernateが自動取得してくれるので、コードをシンプルに保てます。



画面遷移図の一番右下にある注文情報ページを見てください。
この画面は前の画面から注文IDを受け取って注文情報の詳細を表示します。



クラス図はこのようになっています。
注文クラスが親と考えると、注文明細と書籍がそれぞれ子と孫、という関係になっていますね。
このページを表示するためには親、子、孫の全オブジェクトが必要になります。


先ほどもお話しした通り、子や孫はNHibernateが自動的に取得してくれるので、必要になってくるのは親を取得するコードだけです。
注文Facadeの実装は以下のようになります。

// 指定された注文IDの注文情報を返す
public BookOrder FindBookOrderByID(int bookOrderID)
{
    ISession session = NHibernateHelper.GetCurrentSession();
    return session.Get<BookOrder>(bookOrderID);
}


画面側のコードを載せると長くのでここには載せませんが、Lazy Loadingのイメージにはこんな感じです。

// 注文を取得する
BookOrderFacade bookOrderFacade = new BookOrderFacade();
BookOrder bookOrder = bookOrderFacade.FindBookOrderByID(123);

// 注文明細を取得する (NHibernateが自動取得)
IList<BookOrderItem> bookOrderItems = bookOrder.Items;
foreach (BookOrderItem bookOrderItem in bookOrderItems)
{
    // 書籍を取得する (NHibernateが自動取得)
    Book book = bookOrderItem.Book;
}


SQLを確認すると、確かにSQLが別々に発行されています。

-- 注文の取得
SELECT bookorder0_.book_order_id as book1_0_0_, bookorder0_.customer_name as customer2_0_0_, bookorder0_.order_date as order3_0_0_ FROM book_order bookorder0_ WHERE bookorder0_.book_order_id=@p0;@p0 = 449

-- 注文明細の取得
SELECT items0_.book_order_id as book4_1_, items0_.book_order_item_id as book1_1_, items0_.item_order as item5_1_, items0_.book_order_item_id as book1_2_0_, items0_.shipping_date as shipping2_2_0_, items0_.book_id as book3_2_0_ FROM book_order_item items0_ WHERE items0_.book_order_id=@p0;@p0 = 449

-- 書籍の取得
SELECT book0_.book_id as book1_1_0_, book0_.book_name as book3_1_0_, book0_.stock_count as stock4_1_0_, book0_.book_type as book2_1_0_ FROM book book0_ WHERE book0_.book_id=@p0;@p0 = 1238
SELECT book0_.book_id as book1_1_0_, book0_.book_name as book3_1_0_, book0_.stock_count as stock4_1_0_, book0_.book_type as book2_1_0_ FROM book book0_ WHERE book0_.book_id=@p0;@p0 = 1242


コード量が少なくなればコードの可読性が高まり、保守性も上がります。
またコードが少ないほど不具合の発生率も低くなります。
それだけではなく、開発者は「親、子、孫、どこまでデータを事前に取得しておけばいいのか?どうやって取得すればいいのか?」と悩む必要がなくなります。


SQL原理主義的な観点からすれば「こんなSQLは使い物にならん!」という風に見えるかもしれませんが、決してデメリットばかりではないということを理解してもらえると嬉しいです。


ちなみにSessionがクローズされてしまうとLazyLoadingが機能しなくなるので注意が必要です。
Part6Part7に書いていたのはこのことだったわけです。

ポリモーフィックなクエリ

Part6ではHQLに関して以下のような説明をしました。

ちなみに和書だけを抽出したいなら「from JapaneseBook」となります。
同じく洋書を抽出する場合は「from ForeignBook」です。

書籍から和書、洋書へと進むと親クラスからサブクラスへ進むことになり、対象となるデータが限定されますが、逆に書籍から親クラスへ進んだ場合はどうなるでしょうか?
つまりこうです。


from System.Object


一見何のことだか分からないと思いますが、これは「NHibernateの管理下にあるすべてのオブジェクト」を意味することになります。
よって書籍も注文も注文明細も含めて全部です。
通常、こんなクエリがあっても役には立ちませんが、実はユニットテストコードを書く上では便利だったりします。

[SetUp]
public void SetUp()
{
    // DB上の全テーブル、全データを削除する
    NHibernateHelper.ExecuteTransaction(delegate(ISession session)
    {
        string hql = "from System.Object";
        session.Delete(hql);
    });

    // テストデータを作成する
    this.CreateTestData();
}


こんな感じでテストデータ作成前の全件削除に使えたりします。
もちろん、親子関係を考慮しながら削除してくれるので、削除中に外部キー制約違反が発生したりすることもありません。
こういった芸当はNHibernateならでは、という感じです。

selectを指定するHQL

HQLではselectが不要、というのはPart6で説明した通りです。

よって「select *」みたいな記述は原則不要となります(必要に応じてselectを書くこともできますがここでは割愛します)。

「selectを書くことができる」とも書きましたが、では一体どういうときに使うのでしょうか?
サンプルプログラムでも使っている場所があるので、ひとつ例を紹介してみたいと思います。



注文履歴一覧ページには注文者を選択するドロップダウンリストがあります。
このリストを作成するためには注文情報から重複のない注文者のリストを抽出する必要があります。
注文Facade内のメソッド実装は以下のようになります。

// 注文者の一覧を返す
public IList<string> FindCustomorNames()
 {
    string hql = @"select   distinct 
                            bookOrder.CustomerName 
                   from     BookOrder as bookOrder 
                   order by bookOrder.CustomerName";
    ISession session = NHibernateHelper.GetCurrentSession();
    IQuery query = session.CreateQuery(hql);
    return query.List<string>();
}


このように書くと重複のない注文者のリストを抽出することができます。
しかしselectで必要な項目だけを取得すると、クラスのインスタンスとしてデータを取得することはできません。
あまり頻繁にselectを使うようであればクラス設計を見直す必要があるかもしれません。

パフォーマンス問題について

O/Rマッピングツールにはメリットもデメリットもあります。
その中でも実行パフォーマンスについてはよく批判される部分だと思います。
しかし、まず理解してもらいたいのはO/Rマッピングツールにも様々なメリットがあるということです。
ただし、「SQLを書かなくても良い」ことが一番のメリットと勘違いしてはいけないことは、何度も何度も繰り返し述べてきている通りです。


さてパフォーマンス問題ですが、ツールの開発者もそれをそのまま放置しておくほど愚かではありません。
世間でよく遭遇する問題については色々と対処してくれています。
NHibernateに関していえば、以下のページで具体的なパフォーマンスの向上戦略を説明してくれています。


http://nhforge.org/doc/nh/en/index.html#performance


パフォーマンスの問題に限らず、実際の開発業務でNHibernateを使い始めると「こういうときはどう実装したらいいの?」とか「どうしてこんな挙動をするのか理解できない」といった問題によく遭遇すると思います。
NHibernateで本格的な開発をする場合は以下のようなマニュアルや書籍を精読し、すぐに問題を解決できる知識を貯えておく必要があります。


http://nhforge.org/doc/nh/en/index.html


NHibernate in Action

NHibernate in Action


NHibernateに関する日本語の情報はまだまだ少ないと思うので、Java版のHibernateに関する情報を調べてみるのもありだと思います。


HIBERNATE イン アクション

HIBERNATE イン アクション

  • 作者: Christain Bauer,Gavin Ki,倉橋央,勝嶌和彦
  • 出版社/メーカー: ソフトバンク クリエイティブ
  • 発売日: 2005/12/28
  • メディア: 大型本
  • 購入: 3人 クリック: 109回
  • この商品を含むブログ (35件) を見る


Page Redirection
※日本語版のReferenceも載っています。


またNHibernateやHibernateの機能に精通することで「これは便利!」とか「こんな機能があったのか」と開発業務をさらに効率化してくれるテクニックに気づくこともあると思います。
「だからO/Rマッピングツールは使えない」とぼやく前に、まずは徹底的にツールについて研究してみましょう。



さて、NHibernateの話をするだけでかなりの文量になってしまったので、これまでのまとめは次回にしたいと思います。