どうも古家です。アドベントカレンダーの記事です。
技術ネタというかプログラミングネタで開発者として仕事を楽にするようなプログラミング力を鍛える投稿ができればと思います。
はじめに
プログラミングは文学っていう考え方、私は学び始めたころから持っていて
その時からどうすればいいコードが書けるのか?を念頭に書いてきました。
私からすれば力業で解決したコードは動いていてもそれは偶然であり、波にさらわれてなくなってしまう砂の城であり、ジェンガやトランプタワーを積んでいるようなものなんですよね。
逆に言うと保守性が高いコードは頑強な建築物です。
そしてプロに求められているのは偽装工事の掘っ立て小屋ではなく、ちゃんとした建物なんだということですね。
さて、話すネタはいろいろあれど現場では時折その砂のお城を見る機会があります。
その人が悪いとは言いませんが、仕事に対するスタンスはいろいろあるのでとやかくは言いません。
ただ個人的にはみんなが出来るだけいいコードを書いて、皆がハッピーなコードを書いていければプロジェクトは炎上しにくくなると考えています。
本稿で述べたいこと
さていろいろいいコードって言ってもいろんな種類があるわけですが話すネタ自体はシンプルにオブジェクト指向プログラミング(OOP)というパラダイムについてです。全てを話すとまた長くなりますので、とりわけ”継承”について話します(これでも長いけど)。
”継承”という概念は基底クラスのものを使うことが出来たりvirtual等の修飾子をつけてメソッドを差し替えたりするということですが、これを使っている人は(過去の自分も含めて)色々間違ってる人が多いと思います。
本題
さてアンチパターンの実例をまず掲載します。よくこういうコード書いている人を見かけますがプロジェクトの規模が大きくなればなるほど阿鼻叫喚が生まれます。
以下はウィンドウというクラスをあるプログラマが表現しました。
ニーズとしては一覧画面からフォーム画面などを表示するような一般的なシステムです。
フォーム画面は横展開される予定であり、製作者は皆に使ってもらおうと基底クラスとしてWindowBaseというクラスを持つことにしました。
そうすることでウィンドウのデザインや重複した処理を統一したいという重いがあります。
基礎が出来上がったためいよいよ横展開をしていったんですが、ここでクライアントに言われたのが「モーダルダイアログやエラーダイアログを出したい」です。
さて、同じウィンドウならばやることなんて単純なのであなたはその二つを実現させようとWindowBaseを継承したクラスを書きます。
WindowBaseはWindowの基底として作成したのでこれは問題ないはずだと設計者は考えました。
またクライアントから追加要望ですが、今度はウィンドウから別ウィンドウに遷移するというケースがあることが発覚。
さらにダイアログを表示中に子ダイアログを表示するということもしなければいけないらしいです。
さらにさらに帳票を表示する専用のウィンドウを作って欲しいというものまで存在します(サーバーとアクセスし帳票データを取得する。ちなみに帳票同士を比較する操作も考え、モーダルダイアログではない)。
こうなってくると継承階層はもうめちゃくちゃです。WindowBaseにWindowBase自体の参照を持たせてみたりとコードがハチャメチャで、実際には使わない基底クラスのメンバが大量に存在し消していいのかどうかも分からなくなります。
何がいけないのか
実は継承というのはあまり多用するものではないのです。ここぞという時に使うべきものなのです。
継承とはすなわち基底クラスのメンバを使いまわすんですが、その乱用が意味するところは子クラスが神クラス化し基底クラスはどこでどう継承されるのかわからないので修正しにくくなるということです。
今回だとまだ意味が分かるのでましな方かもしれないですが、たまにas isの演算子を使って基底クラスが子クラスとして振舞うコードもあります。
さらにさらにisでクラスが判定できるからといって基底クラスをちょっと便利なEnum型みたいに使おうとしているコードも見かけます。
決してそういう使い方はしないでください。SOLID原則も泣いて逃げ出すスパゲッティコードになります。
また、継承で強引に解決しようとしたクラスは実装を差し替えることがとても困難になります。
で、どうするのか
さてどういう実装がいいんでしょうか。
答えは「継承ではなく集約を使う」です。
悪い例のコードのような実装をすると右から左にコードを移動するのがとっても億劫になってきます。
仮にWindowにページング処理が追加されたとしましょう。
そのページングの「前へ」とか「次へ」をやりたいのにPagingWindowBaseを作ってNextとかPrevメソッドを作ってしまっては、実際その操作を行うのにPagerWindowBaseクラスそのものを渡すことになってしまいますね。
その場合やっぱページャ要らないわって仕様変更が起こるとPagerWindowBaseを継承しているクラスは大変です。
そうではなくページャという機能を持ったクラスを作成すること。これが肝です。
冷静に考えると継承なんて使わなくてもそれ専用のクラスを作ってメンバにしとけばいいんです。
そうすると仮にページャならばPagerというクラスのみ受け取ればよいし、仮にやっぱいらないとなればPagerメンバを外すだけでよいのです。以下掲載例です。
継承はやっても2つ、3つまでです(3つだって滅多にしません)。上手く実装するとほぼ2階層で収まります。そうではないクラスを発見したあなたは炎上プロジェクトに片足突っ込んでいるかもしれません。。。レビューでもちゃんと指摘しあなたがコードの守護神となってください。