CodeKataやってみた記事、第十弾です。
ついに、ほぼ半分です。
今回は、Kata10: Hashes vs. Classes
をやっていきます。
今回はコーディング無し問題です。設計について考える問題となります。
そのため、解説長めです。ご了承ください。
また、訳にあまり自信がないです。
意味が分からない部分は、原文を見ていただきたいです。
概要
ビジネスアプリケーションのプログラミングをJava
やC#
のような言語を用いて行う場合、ビジネスオブジェクトをクラスで構築して使うことに慣れている。
これが常に正しい方法か、もしくは、形式的ではないアプローチが良い場合もあるのか?
問題のイメージ
巨大で複雑なデータベースからの、エクスポート機能作成を求められたとする。
30程度のテーブルから、データを読み込む必要がある。(合計100カラムぐらいのデータを、各レコードで取得する必要があると思われる)
出力データとして、
- データベースの値をそのまま出力するもの
- データベース値を元に計算して出力するもの
- 加えて、特定のフラグが立っている場合のみ、追加でデータベースから値を取得するもの
の三種類がある。
取得データは正確である必要があるが、クライアントは柔軟なソリューションを求めている。
ソリューション1
既存のビジネスオブジェクトと既存の永続性メカニズムを利用して、オブジェクトを集約してエクスポートデータを作る高レベルクラスを作成する。
この高レベルオブジェクトは、計算フィールドを仮想フィールドとして持ち、フラグに応じて追加ビジネスオブジェクトを読み込む。
つまり、ラッパーを作り、そのラッパーでリポジトリやドメインオブジェクトを駆使して、エクスポートデータを作る、というものです。
一行ごとに、ラッパーオブジェクトが作成されると思われます。
ソリューション2
アドホッククエリを使用してハッシュに一度にデータ行に読み込み、フィールド名にハッシュキーを入れる。
必要な計算を行い、ハッシュ内に結果を書き戻す場合に、個別の方法で作成できる。
フラグがセットされていたら、追加データをデータベースから取得でき、再びハッシュに追加できる。
ハッシュのコンテンツは、エクスポートレコードを書き込むのに使用され、ループで順次処理していく。
各行で、Dictionary
のような、カラム名をキーとしたハッシュを作り、値をそれぞれ格納します。
それを各行に応じて作ります。
C#
でいうなら、List<Dictionary<string, object>>
みたいな定義になると思われます。
Kataについて
これは考える実験。トップ3の利点とトップ3の欠点を、それぞれのアプローチで考える。
ビジネスアプリケーションで、データ保持をクラスで行っていたとしたら、ハッシュに切り替えた場合のインパクトはどのくらいあるか。その反対の場合も。
この問題は、静的、動的タイピングの議論に関連する?
これが、今回のKata
の概要です。
ソフトウェア設計者を目指している方や、練習したいと思っている方は、ぜひ自分の力で考えてみていただきたいです。
きっと、私の考えとはまた違うものが出てきて、それについて議論すると面白いのだろうと思っています。
以下、私なりの考えを書いていきます。
いつものように、続きは一応隠します。(直接見ると隠れません)
自分なりの考え
実際に作ってはいないので、妄想に近いです。
ソリューション1
メリット
計算過程がわかりやすい
オブジェクト指向やクラスの恩恵を最大限受けられる部分です。ビジネスオブジェクトの変更が、自動的にエクスポートにも反映できる
ビジネスオブジェクトが持つべき値に誤りがあった場合、それを修正するだけで当該項目を参照しているエクスポートも一緒に修正されます。既存オブジェクトの組み合わせで処理が作れる
開発は楽だと思われます。
デメリット
パフォーマンスチューニングが難しい
既存のリポジトリを再利用するため、オブジェクトの境界外のオブジェクトは、個別取得となります。
そのため、エクスポート1行のデータを取得するために、複数のクエリを実行する必要があるかもしれません。
あくまで、既存の仕組みに引きずられます。計算フィールドの変更に対して、高レベルオブジェクトの変更が必要になる
この方法を採用する場合、エクスポートのためにビジネスオブジェクトは修正できません。
既存オブジェクトに無いデータをエクスポートする必要が生じた場合、高レベルオブジェクトに追加となりますが、複雑化していく可能性が高いです。一時的に多量のオブジェクトを永続保持するため、永続性の実装によっては、通常処理への悪影響であったり、遅延を招く可能性がある
既存実装に依存しますが、リポジトリ等の永続性実装が良くないと、本来アクセスしてはいけないキャッシュへのアクセス等もあり得ます。
ソリューション2
メリット
フィールドの変更に対して柔軟
ハッシュを使う場合の、最大の特徴です。
ハッシュのキーを追加するだけで、フィールド追加できます。パフォーマンスチューニングが楽
アドホッククエリを実行すると言い切っているので、クエリのチューニングによる高速化の恩恵を受けられます。クラスの進化(すなわち、新しい概念等)の影響を受けにくくなる
ビジネスオブジェクトは、ビジネスが変わると変化します。
その変化の影響を、エクスポートが受けにくくなります。
デメリット
データの元を追うのが困難
エクスポートデータをクエリで取得するため、そのデータがどこから入力されて、どこで使われるのか等が、ソースコードだけでは追いにくくなります。
また、「必要な計算を行い、ハッシュ内に結果を書き戻す場合に、個別の方法で作成できる。」のような記述が示す通り、あるハッシュ値を後に書き換える処理が入るため、余計に追いにくくなります。
DBとビジネスオブジェクトは、よい設計であるシステムであるほど切り離されます。ビジネスオブジェクトが変更された場合、エクスポートで同じような修正をしなければならなくなる
つまり、二重化してしまい、DRY原則に反します。30程度のテーブルから値を取得するSQLは、可読性が著しく低い可能性が高い
SQLを書いたことがあるならわかると思いますが、30個のものテーブルを結合すると、クエリが読めないです。
複雑すぎます。
いくつかの部分に分けて取得する可能性はあります。しかし、それをやるとソースコードでJOIN
処理が必要となり、結局可読性が低下します。
オブジェクト←→ハッシュへの変更インパクト
私は、得意言語がC#
、ブログで多用する言語がPython
です。
一応、動的言語も静的言語も経験していますし、オブジェクトもハッシュも使ったことがあります。
それを踏まえて、これについて考察してみます。
まず、そもそもインパクトは大きいです。
メリットデメリットは表裏一体な部分があります。
個人的には、ハッシュをオブジェクトでラッピングして、併用すればいいと思います。
オブジェクト→ハッシュ
基本、オブジェクトの信奉者なので、デメリットから。
コーディングがやりづらくなる
ハッシュ値にアクセスするのは、フィールド名の文字列です。
そのため、IntelliSense
等のエディタが持つ予測変換に頼れなくなります。ハッシュだと、型指定が厳しい
ハッシュの値で型指定が必要だと、文字列。数値、日付を同時に保持するのが難しく、何からの妥協が必要になります。
メリットを挙げるなら、
- 開発は高速化する
ハッシュを作り出す処理は大抵共通にできるため、新しいデータを取得・更新するのは、オブジェクトを使う場合と比較して楽です。
ハッシュ→オブジェクト
この変更、あまりやったことがないですが…
デメリットから。
オブジェクト生成が面倒
これに尽きます。
オブジェクト生成するなら、O/Rマッパー等を使ってDB値からオブジェクト生成→ビジネスオブジェクトに変換、とし、さらに更新処理まで作らなければなりません。型指定が面倒
ハッシュを利用している場合、ハッシュのオブジェクトを受け取れば、各所で処理ができます。
しかし、オブジェクトに変更することで、各所で同じ型を使うわけにはいかなくなります。
メリットとしては、
- 可読性の向上
個人的な意見も含みますが、オブジェクトを利用して、型指定したほうが、ソースコード全体の読みやすさは向上します。
面倒さを乗り越える必要はありますが。
動的言語 vs 静的言語?
複雑なドメインを取り扱う、開発が大規模化するなら、静的言語が良いと思います。
動的言語だと、そもそもどの型が来るか分からないという制約から逃れられず、それだけ考慮事項が増えます。
もっとも。そのデメリットを回避すべく、極力処理を小さくするという哲学があります。
一方、静的言語では、型安全を悪用して、神クラス*1のようなものが生み出される危険性があります。
設計技法を駆使して、適切に分割・統合できれば、保守性の確保という恩恵が受けられます。
どっちが良い、というより、利点を生かして利用したいものです。
おわりに
長い文章になりました。
文章を書きながら、ソリューション1、2のそれぞれの利点欠点を思いついたりしました。
単に考察するだけでこの状態なので、実際にコードを書いたらもっと気付きが多いと思います。
前回と同じく、頭の体操にはとても良いので、チャレンジしてみてください。
*1:いろんなオブジェクトのことを知っていて、なんでも処理できちゃう、まさに神のような存在。誰にも触れない神々しさすらある。ただし神を作るのは人間