アプリケーションの定型コードを短くしたい (1)
05/29/2011

このコンテンツの内容を実行して損害が出ても責任はとれません。あらかじめご了解ください。

MVCパターンやMVVMパターンでアプリケーションを作ると、「モデル」が出てきます。
大規模業務アプリケーションともなると、この「モデル」が数百、数千なんてこともあります。
で、このモデル、結構きまりきったことを書かなきゃいかんので面倒くさいです。
なんとかしたい。
まあ、モデル自体の整理をきちんとやってモデル数を減らすとか、定型部分はpartialつかって別ソースでツールから自動生成しよう、とか、いろいろ手を考えるわけです。
そのなかの一環として、Reflectionなどのオブジェクト指向的手法を使って、アプリケーションの定型コードを少なくしてみましょう。

というわけで、最初。
とあるモデルのソースコード(C#)。SomeModelとします。
フツーに?とあるアプリケーションプログラマさんが作ってくれたソースがこれです。
LongClass.SomeModel.cs
1102行あります。そんなに変な作りじゃなくて、業務アプリケーションではごく普通の作りかと思います。
いろいろ規約があって、規約どおりに作ってたらこうなった、的なものです。
SomeModelが継承しているAbstractModelはこっち。LongClass.AbstractModel.cs
これは説明用にかなりはしょってビルドが通るぎりぎりに作ってます。
このAbstractModelは、業務共通フレームワーク側の持ち物という扱いで、アプリケーションプログラマさんは中身をみません。

ごく普通の作りとかいっても、なんとかならんかなー、と思うところは結構あります。
まず、プロパティ保持用の、アンダースコア(「_」)がプレフィクスされたprivateなメンバ変数をなんとかしたい。
C#は3.0から自動実装プロパティがあるので、単純なGetterやSetterだけなら、privateなデータ保持用メンバ変数はいらなくなった…んですが、プロパティのSetterのところを見ると、

        public string SomeCd
        {
            get
            {
                return this._someCd;
            }
            set
            {
                if (this._someCd == value)
                {
                    return;
                }
                this._someCd = value;
                this.OnPropertyChanged(PropertyName.SomeCd);
            }
        }
…なんかやってます。単純じゃないんで、自動実装に頼るわけにいきにくいです。
しかし、このGetter/Setterのところを眺めると、大体おんなじことやってます。対象になるメンバが違うだけで、動作としては同じ。
この部分の動作をAbstractModelに押し込んでみましょうか。
んで、アンダースコアがプレフィクスされたprivateなメンバ変数も、AbstractModelに入れてしまえ。
しかし、AbstractModelは業務共通フレームワークの持ち物なので、SomeModelがどんなメンバ変数を持っていなきゃけないのかは知りません。
なので、AbstractModelでは、メンバ変数をDictionaryコンテナに突っ込むことにします。
あと、PropertyNameクラスを保持してるのも微妙にウザい。
このSetterでPropertyNameを使っているので、ここもまとめてなんとかしましょう。

で、なんとかしたのがこれ。ShortRefl1.SomeModel.cs
行数は610行。結構少なくなりました。
アンダースコアがプレフィクスされたprivateなメンバ変数は消えてます。
その分AbstractModelが増えてる。こんな感じです。ShortRefl1.AbstractModel.cs
SomeModelのプロパティ値をAbstractModelの_propValuesメンバ変数で保持してます。
しかし、AbstractModelはシステムで1つしかないですが、これを継承した業務のモデルは、Some1Model, Some2Model…とすごい数作られます。
なので、AbstractModelが増えても、業務モデルのコード量が減るというのは、生産性としてはメリットといえるでしょう。
あと、地味に、コピーコンストラクタの代入文がウザかったので、リフレクション使ってまとめています。

ところで、さっき邪魔だから消そうとしたPropertyNameクラスが、まだこの段階では健在です。
SomeプロパティのSetterでしか使ってないかなとナメてかかっていたら、なんか下のほうにValidate()というメソッドが定義されていて、その中のcase句で使ってます。

        protected override IEnumerable Validate(string propertyName)
        {
            List<string> message = new List<string>();

            switch (propertyName)
            {
                case PropertyName.SomeCd:
                    // 何かコード
                    if (string.IsNullOrEmpty(this.SomeCd))
                    {
                        message.Add(string.Format(Message.Error.Required, "何かコード"));
                    }
こりゃ困りました。
でも、このコードをよく観察すると、これはプロパティ名をパラメータにとってますから、プロパティごとの値チェックをやってるわけです。
このメソッドの中で、パラメータとして渡されたプロパティ名で分岐してます。 それなら、別にValidate("SomeCd")を呼ばなくても、プロパティごとのValidateメソッドを定義してもらってもいいんじゃないかな。
どうもプロパティを設定したときにチェックするようなので、ValidateするメソッドはSetPropValidate_SomeCdという名前にしましょう。
これをValidateが必要なプロパティ分、今回はSomeCd、SomeAreaCd、Nmj、Nmk、Nmsの5つについて定義します。
そしてAbstractModel側から、もしSomeModel内にSetPropValidateで始まるメソッドがあったら勝手に呼び出してしまうようにする。
そうしたらどうなるだろう。

こうなりました。ShortRefl2.SomeModel.cs
SetPropValidate_SomeCdをはじめ、SomeAreaCd、Nmj、Nmk、Nmsに対応した5つのメソッドが増えています。
そのかわりValidateメソッドは消えました。
プロパティのGetter/Setterは、処理の論理構造を説明する必要がないので一行ずつに圧縮。
そしてPropertyNameクラスはなくなりました。
コピーコンストラクタもちょっとだけ手直し。
392行。まあプロパティのGetter/Setterでズルをしないと584行なんですが。
1102行からくらべるとずいぶんコンパクトです。
これを支えているAbstractModelはこうなっています。ShortRefl2.AbstractModel.cs
Validateが消えて(混乱しないように名前を変えた)、ValidateBaseメソッドを新たに作りました。
ValidateBaseメソッドの中では、SetPropValidate_+プロパティ名のメソッドがあったら呼び出すようにしてます。
このShortRefl2.SomeModel.csは、ルールを知っていれば可読性も悪くなっていなくて、それなりの改善になったのではと思います。
行数が短いほうがいいコードというわけではないのですが、一定時間に作れるコードの行数には限界があるので、読みやすく短く書ければそのほうがいいわけです。
めでたしめでたし。

え。GetterやSetterのオマジナイ的なコードが気に入らない?

        public string SomeCd
        {
            get { return (string)GetPropValue(this.RemoveSetterPrefix(MethodBase.GetCurrentMethod().Name), this); }
            set { SetPropValue(this.RemoveSetterPrefix(MethodBase.GetCurrentMethod().Name), value, this); }
        }
あー、これですね。確かに何やってんのかキモチ悪い。
まあこれはお約束で、こういうもんだ、ルールでこう書くことになっている、ということで一つご勘弁を…。

…ダメですか。
ですよねー。
このGetter/Setter、全部のプロパティに対して同じコードになってることが一見してわかります。
Getterの戻り値のキャストが違うだけ的な違い。
こういう、「全部のプロパティに対して同じことする」のにうってつけなのがアスペクト思考的な考え方。依存性注入とかもこのへん。
幸い、.NETフレームワークには「属性」や「透過プロキシ」というそういった概念を扱うのに便利な機能があるので、これらの便利機能を使ってこのオマジナイをSomeModelから消してしまいましょう。

消してみました。ShortReflAttr.SomeModel.cs
266行まで短くなりました。
Getter/Setterのオマジナイ的なコードもなくなっています。

        public string SomeCd { get; set; }
そのかわり、「[OnPropertyChangedCall]」という属性を作って、それをクラス定義部で付加してます。
namespace CodeCompress.ShortReflAttr
{
    [OnPropertyChangedCall]
    public class SomeModel : AbstractModel
この属性をクラスに付加すると、そのクラスの全プロパティのGetter/Setterの処理時に割り込んで自動的にさっきのオマジナイコードを実行するようにしました。
属性は、ShortReflAttr.OnPropertyChangedCallAttribute.csで作っています。
詳しく説明すると長くなるし、そう難しいことはしていないのでざっと見ていただくとして、Getter/Setterに割り込んで、AbstractModelのGetPropValue/SetPropValueを呼び出すように作っています。
AbstractModelもちょっとだけ直しています。ShortReflAttr.AbstractModel.cs
いままではAbstractModelは何も明示的な継承がなかったのですが、属性と透過プロキシの都合から、ContextBoundObjectを継承しています。
namespace CodeCompress.ShortReflAttr
{
    public abstract class AbstractModel : ContextBoundObject
    {
アプリケーションプログラマさんが読み書きするShortReflAttr.SomeModel.csは、かなりシンプルになり、また「無駄な定型コーディングだ」と思いそうなところがなくなっていると思います。
地味に、開発生産性に影響します。

そうそう、このコードはアプリケーションプログラマの生産性向上を主眼に、検証目的で興味本位で書いたもので、仕事に使えるようなクォリティではないです。
仕事なら性能チューニングや、例外ハンドリング戦略に基づいたコードを書かないといけませんが、なんというか天真爛漫に書いてしまいました。動作確認も適当です。
また、リフレクションやプロキシは上記のようにとても便利ですが、性能面ではかなり不利です。
さらに言えば、partialや定型コード自動作成ウィザードを作る、というアプローチもあります。DSLから一気に変換してもいいでしょう。
いろいろなアプローチの一つとして、こんな方法もあることをご紹介しました。

ソースコードのリスト:


Top of Site
Copyright (c) 2011 Takao Tamura