どうして enum に拘ってるのか…

前回の記事でイミフな間違いをしていたことに気付くのが遅くてちょっと焦ったw @masa711115です。 突っ込みありがとうございました。


さらに、この様な突っ込みもいただきました。ありがとうございます。

そりゃぁそうですよね。前回の記事では「どうして enum に拘って」書いてるのかまでは説明していませんでした。

自分の中でもっといい方法が見つかっていないということもありますが、補足として「どうして enum に拘ってるのか?」についても書いてみようと思います。 ※ソースの内容はすべて C# です。 ※また、Unit Test の記述は Visual Studio の単体テスト機能を使用しています。 ※コメント等が不完全な部分があります。

1. const の値 ( 区分 or コード or 他にもある? ) と名称の組み合わせの塊は一つではない

しかも、10 や 20 ではきかないですよね。 全体的なつくり/設計の問題といってしまえばそれまでですが、システム開発をしていると良くあることだと思います。 私の携わる業務系のシステムで言えば…

  • 処理区分
  • 取引区分
  • 売上区分
  • 伝票区分
  • その他まだまだ…

ましてやそれぞれが値と名称の組み合わせを持ち、少なければ2-3、多いと 5-10 ( もっと多いことも… ) 程度のデータが必要になります。最低限 2つの const の組み合わせと考えるとかなり面倒なことになりますね。 しかも、作る人によってはバラバラの位置に定義されてしまいますし、そのソースを追いかけて追加も変更も削除も全部組み合わせ単位で行わなければなりません(>_<)

 

2. 組み合わせとなるものを別々に管理したくない

上に書いたように人によってバラバラに定義してしまうことも大きな問題です。 単独のシステムの場合にはまだいいのですが、色々なユーザーで使い回しをする場合にはユーザーごとに内容を変更しなければならないことも多々あり、変更箇所がバラけてしまうとそれに伴ってミスが発生します。バグの温床ともなりえるわけです。

enum にカスタム属性を付加する方法であれば、設定しなければならない値は複数の組み合わせであったとしてもその場に定義することになるので嫌でも目に付きますし、そもそもバラバラに定義されてしまうこともありません。

人間がミスをするのはしょうがないですが不要なミスは避けたいですよね。 ※ミスを見つけるためにどうするかよりも、ミスを少なくする方法を考えていきたいですね。

3. クラスにすると変更箇所が多くなりませんか?

私のスキルが不足しているということである可能性は否定しません。発想が貧困である可能性も(^^;; そういうご指摘があれば甘んじて受け入れますが、それぞれの const の塊をクラスとして定義すると変更が発生した場合に不必要な量の変更が発生すると感じています。

以下になぜそう感じるのか、サンプルを記載します。

パターン①: もっとも簡単に安直な方法で記述してみました

using System.Collections.Generic;

namespace ClassLibrary1
{
    /// 
    /// Sample1 クラス
    /// 
    /// 
    /// enum + カスタム属性での拡張に対する記事の補足のために(パターン①)
    /// 従来 const の組み合わせで表現していたシステム上のパラメータをあらわしたい
    /// こんなカタチにしてしまうことはないだろうけどw
    /// 
    public static class Sample1
    {
        public static string Code1
        {
            get { return "01"; }
        }

        public static string Name1
        {
            get { return "HOGE"; }
        }

        public static string Code2
        {
            get { return "02"; }
        }

        public static string Name2
        {
            get { return "HOGEHOGE"; }
        }
    }
}
        [TestMethod]
        public void Test_Sample1()
        {

            Assert.AreEqual("01", ClassLibrary1.Sample1.Code1);
            Assert.AreEqual("HOGE", ClassLibrary1.Sample1.Name1);
            Assert.AreEqual("02", ClassLibrary1.Sample1.Code2);
            Assert.AreEqual("HOGEHOGE", ClassLibrary1.Sample1.Name2);

        }

const の場合と比較してどうでしょうか? 書かなければならないコードの量が増えただけだと私は感じます。 クラスの中に const も定義するならはなから const でいいと思いますし… さらに、データの量が多ければ一つのクラスでもっと増えますし、見通しが悪いですよね。 > Code1 と Name1 をセットで変更しなければならないことにぱっと見て気付けますか?

でっかいクラスにして、インナークラスでそれぞれの塊を定義しますか? > たいして違いはないと思っています。

パターン①-1: データの内容だけでも…

using System.Collections.Generic;

namespace ClassLibrary1
{
    /// 
    /// 扱う内容を少し汎用的に List<KeyValuePair<string, string>> にしてみた
    /// 
    /// 
    /// このままでは 1アイテムに 2値までしか保持できない。ただし、最大で 3~4個
    /// 保持できればいいとは思うけどね。それ以上なら素直にプログラム外に持った方が...
    /// 
    public static class Sample1_1
    {

        private static readonly List<KeyValuePair<string, string>>
        _list = new List<KeyValuePair<string, string>>()
                    {
                        new KeyValuePair<string, string>("01", "HOGE")
                        ,
                        new KeyValuePair<string, string>("02", "HOGEHOGE")
                    };

        public static string Code1
        {
            get { return _list[0].Key; }
        }

        public static string Name1
        {
            get { return _list[0].Value; }
        }

        public static string Code2
        {
            get { return _list[1].Key; }
        }

        public static string Name2
        {
            get { return _list[1].Value; }
        }
    }

}
        [TestMethod]
        public void Test_Sample1_1()
        {

            Assert.AreEqual("01", ClassLibrary1.Sample1_1.Code1);
            Assert.AreEqual("HOGE", ClassLibrary1.Sample1_1.Name1);
            Assert.AreEqual("02", ClassLibrary1.Sample1_1.Code2);
            Assert.AreEqual("HOGEHOGE", ClassLibrary1.Sample1_1.Name2);

        }

パターン①-2: もう少しなんとか…

using System.Collections.Generic;

namespace ClassLibrary1
{
    /// 
    /// もう少し汎用的に List<Dictionary<string, string>> にしてみた
    /// 
    public static class Sample1_2
    {
        private const string ITEM_NAME_CODE = "CODE";
        private const string ITEM_NAME_NAME = "NAME";

        private static readonly List<Dictionary<string, string>>
        _list = new List<Dictionary<string, string>>()
                        {
                            new Dictionary<string, string>(){{ITEM_NAME_CODE, "01"}
                                                            , {ITEM_NAME_NAME,"HOGE"}}
                            ,
                            new Dictionary<string, string>(){{ITEM_NAME_CODE,"02"}
                                                            , {ITEM_NAME_NAME,"HOGEHOGE"}}
                        };

        private static string Item(int index, string itemKey)
        {
            return _list[index][itemKey];
        }

        public static string Code1
        {
            get { return Item(0, ITEM_NAME_CODE); }
        }

        public static string Name1
        {
            get { return Item(0, ITEM_NAME_NAME); }
        }

        public static string Code2
        {
            get { return Item(1, ITEM_NAME_CODE); }
        }

        public static string Name2
        {
            get { return Item(1, ITEM_NAME_NAME); }
        }
    }
}
        [TestMethod]
        public void Test_Sample1_2()
        {

            Assert.AreEqual("01", ClassLibrary1.Sample1_2.Code1);
            Assert.AreEqual("HOGE", ClassLibrary1.Sample1_2.Name1);
            Assert.AreEqual("02", ClassLibrary1.Sample1_2.Code2);
            Assert.AreEqual("HOGEHOGE", ClassLibrary1.Sample1_2.Name2);

        }

もう一つ。汎化されてないので後々面倒な気が… インターフェースでどうにかできる部分もあればどうにも出来ない部分もありますよね?

パターン②: 汎化できるようにしてみますか?

using System;
using System.Collections.Generic;

namespace ClassLibrary1
{
    /// 
    /// 擬似的に定数を扱うための抽象クラス
    /// 
    /// 
    /// enum + カスタム属性での拡張に対する記事の補足のために(パターン②)
    /// 従来 const の組み合わせで表現していたシステム上のパラメータをあらわしたい
    /// 
    public abstract class AbstractSample2
    {

        private readonly List<KeyValuePair<string, string>> _list;

        protected AbstractSample2(List<KeyValuePair<string, string>> list)
        {
            _list = list;
        }

        public string Code(int index)
        {
            return _list[index].Key;
        }

        public string Name(int index)
        {
            return _list[index].Value;
        }

        public int Count
        {
            get{return _list.Count;}
        }

        public List<KeyValuePair<string,string>> List
        {
            get { return _list; }
        }

    }

    /// 
    /// 擬似的に定数を扱うための具象クラス
    /// 
    /// 
    /// enum + カスタム属性での拡張に対する記事の補足のために(パターン②)
    /// 従来 const の組み合わせで表現していたシステム上のパラメータをあらわしたい
    /// 
    public class Sample2 : AbstractSample2
    {

        private static readonly AbstractSample2 _instance = new Sample2();

        /// 
        /// 実際に扱う定数値を設定しておく
        /// 
        private Sample2()
            : base(new List<KeyValuePair<string,string>>()
            {new KeyValuePair<string,string>("01","HOGE")
                ,new KeyValuePair<string,string>("02","HOGEHOGE")}){}

        public static new string Code(int index)
        {
            try
            {
                return _instance.Code(index);
            }
            catch (Exception)
            {
                
                throw;
            }
        }

        public static new string Name(int index)
        {
            try
            {
                return _instance.Name(index);
            }
            catch (Exception)
            {
                
                throw;
            }
        }

        public static new int Count
        {
            get { return _instance.Count; }
        }

        public static new List<KeyValuePair<string,string>> List
        {
            get { return _instance.List; }
        }

    }
}
        [TestMethod]
        public void Test_Sample2()
        {

            Assert.AreEqual("01", ClassLibrary1.Sample2.Code(0));
            Assert.AreEqual("HOGE", ClassLibrary1.Sample2.Name(0));
            Assert.AreEqual("02", ClassLibrary1.Sample2.Code(1));
            Assert.AreEqual("HOGEHOGE", ClassLibrary1.Sample2.Name(1));

            Assert.AreEqual(2, ClassLibrary1.Sample2.Count);

        }

        [TestMethod]
        [ExpectedException(typeof(ArgumentOutOfRangeException))]
        public void Test_Sample2_Exception()
        {

            Assert.AreEqual("03", ClassLibrary1.Sample2.Code(2));

        }


マジックナンバー消したいのにまた復活した! ここで、enum とか const 定義したら本末転倒な気もw

パターン③: ついでにこんな感じも!

using System;
using System.Collections.Generic;

namespace ClassLibrary1
{
    ///
    /// 擬似的に定数を扱うためのクラス
    ///
    ///
    /// enum + カスタム属性での拡張に対する記事の補足のために(パターン①)
    /// 従来 const の組み合わせで表現していたシステム上のパラメータをあらわしたい
    ///
    public static class Sample3
    {

        private const string ITEM_NAME_CODE = "CODE";
        private const string ITEM_NAME_NAME = "NAME";

        ///
        /// 定数値の保持用リスト
        ///
        private static readonly List<Dictionary<string, string>> _list;

        ///
        /// 実際に扱う定数値を設定しておく
        ///
        static Sample3()
        {
            _list = new List<Dictionary<string, string>>()
                        {
                            new Dictionary<string, string>(){{ITEM_NAME_CODE, "01"}
                                                            , {ITEM_NAME_NAME, "HOGE"}}
                            ,
                            new Dictionary<string, string>(){{ITEM_NAME_CODE, "02"}
                                                            , {ITEM_NAME_NAME, "HOGEHOGE"}}
                        };
        }

        ///
        /// コードの取得メソッド
        ///
        ///
        ///
        public static string Code(int index)
        {
            try
            {
                return _list[index][ITEM_NAME_CODE];
            }
            catch (Exception)
            {

                throw;
            }
        }

        ///
        /// 名称の取得メソッド
        ///
        ///
        /// 
        public static string Name(int index)
        {
            try
            {
                return _list[index][ITEM_NAME_NAME];
            }
            catch (Exception)
            {

                throw;
            }
        }

        /// 
        /// 項目件数
        /// 
        public static int Count
        {
            get { return _list.Count; }
        }

        /// 
        /// 値のリスト
        /// 
        /// 
        /// 値を列挙したいことがあるよね!
        /// 
        public static List<Dictionary<string, string>> List
        {
            get { return _list; }
        }

    }
}
        [TestMethod]
        public void Test_Sample3()
        {

            Assert.AreEqual("01", ClassLibrary1.Sample3.Code(0));
            Assert.AreEqual("HOGE", ClassLibrary1.Sample3.Name(0));
            Assert.AreEqual("02", ClassLibrary1.Sample3.Code(1));
            Assert.AreEqual("HOGEHOGE", ClassLibrary1.Sample3.Name(1));

            Assert.AreEqual(2, ClassLibrary1.Sample3.Count);

        }

        [TestMethod]
        [ExpectedException(typeof(ArgumentOutOfRangeException))]
        public void Test_Sample3_Exception()
        {

            Assert.AreEqual("03", ClassLibrary1.Sample3.Code(2));

        }

これも肝心の部分は改善できず。

そして… enum では難しいことしなくても foreach で列挙できますよね!

私の発想では思いつくのはこのくらいなんですけど、他にいい方法があったら教えてください。

4. まとめ

色々と書きましたけど、根底にあるものは “もっと楽をしたい”w ってことなんですよね。 未来で手を抜くために今現在に苦労するのはなんでもないんです。 しかし、大勢の人は今の苦労を恐れて毎回同じ苦労をしているんです。 目先のほんのちょっとのことに惑わされて…

進む方向が間違っていてもっと苦労しているなんて可能性もあるわけですがw

P.S.

厳しい突っ込みなど絶賛募集中です。 あ!もちろん感想をいただけるのも大歓迎ですよ!

どうして enum に拘ってるのか…” への2件のコメント

    • コメントありがとうございます。
      前回記事に hoge 様よりコメントいただいた方法も、今回増田様にいただいた内容もまた Twitter 上で @wonderful_panda 様に教えていただいた内容も私はイメージできませんでした。その上で、余計なコードをたくさん書いたのでドツボにはまった感はありますが(^^;;

      ただ、増田様が記事でお書きになられているように、enum を元にした方が自分で実装すべき手間は省けるのかなぁと感じています。

増田 にコメントする コメントをキャンセル

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です

次のHTML タグと属性が使えます: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>