i-Vinci TechBlog
株式会社i-Vinciの技術ブログ

ナベアツネタをプログラミングしてみる

皆様こんにちは。
最近エルガド警備員からパルデア警備員に転職した下位です。

コライドンが可愛すぎて日々悶絶しております。
なんだあのキュートな生物は

さて、本日は昔懐かしナベアツネタをプログラムに起こします。
3 のつく数字と 3 の倍数でお上品になるあれです。

基本の型

まずは基本の型です。
難しいことは考えずに、さくっと作ってしまいましょう。
お上品ケースは ★ で装飾をつけています。

class Program {
    static void Main(string[] args) {
        var list = Enumerable.Range(1, 30).ToList();
        list.ForEach(d =>
        {
            if (IsMetamorphoseOjyohin(d))
            {
                Console.Write($"★{d}★,");
            } else
            {
                Console.Write($"{d},");
            }
        });
    }

    /**
     * お上品になる条件の判定
     **/
    static bool IsMetamorphoseOjyohin(int num)
    {
        if (num % 3 == 0) return true;
        return num.ToString().Contains("3");
    }
}

出力ログです。
検証はしてないですが、良さげですね。

1,2,★3★,4,5,★6★,7,8,★9★,10,11,★12★,★13★,14,★15★,16,17,★18★,19,20,★21★,22,★23★,★24★,25,26,★27★,28,29,★30★,

応用の型

さて、基本の型だけで終わるとあまりにも味気ない記事になりますね。
これでは管理部署からお叱り愛の鞭を頂いてしまいそうなので、もう少し仕様を加えてみます。

大体こんな感じです。

  1. 起動モードを付加する(0=通常, 1=お上品, 2=カウント反転 + お上品)
  2. お上品モードのときは数値を ★ でラップする

ということで、仕様変更版です。

class Program
{
    static readonly ReadOnlyCollection<string> PROC_TYPES = new List<string>() { "0", "1", "2" }.AsReadOnly();

    static void Main(string[] args)
    {
        if (!args.Any() || args.Length > 1)
        {
            throw new ArgumentException($"起動モード未設定または複数の設定:{args.Length}");
        }

        string procType = args.First();
        if (!PROC_TYPES.Contains(procType)) {
            throw new ArgumentException($"不正な起動タイプ:{procType}");
        }

        var list = Enumerable.Range(1, 30).ToList();

        // 反転 + お上品
        if (procType == "2")
        {
            list.Reverse();
            list.ForEach(d =>
            {
                string fmt = IsMetamorphoseOjyohin(d) ? $"★{d}★," : $"{d},";
                Console.Write(fmt);
            });
            return;
        }

        // お上品
        if (procType == "1")
        {
            list.ForEach(d =>
            {
                string fmt = IsMetamorphoseOjyohin(d) ? $"★{d}★," : $"{d},";
                Console.Write(fmt);
            });
            return;
        }

        // ノーマル
        list.ForEach(d =>
        {
            Console.Write($"{d},");
        });
    }

    /**
     * お上品になる条件の判定
     **/
    static bool IsMetamorphoseOjyohin(int num) => num % 3 == 0 || num.ToString().Contains("3");
}

ログです。
引数に対して良き感じで装飾がついてますね。
通常、お上品ケース共に申し分ありません。

$ dotnet run 0
1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,

$ dotnet run 1
1,2,★3★,4,5,★6★,7,8,★9★,10,11,★12★,★13★,14,★15★,16,17,★18★,19,20,★21★,22,★23★,★24★,25,26,★27★,28,29,★30★,

$ dotnet run 2
★30★,29,28,★27★,26,25,★24★,★23★,22,★21★,20,19,★18★,17,16,★15★,14,★13★,★12★,11,10,★9★,8,7,★6★,5,4,★3★,2,1,

じゃぁリファクタリングしましょうか。
上記コードなんて提出したら、ソースレビューでボコボコにされるリテイク要求されること間違いなしです。

リファクタリング編

仕様を満たすプログラムを作れたならリファクタリングです。
えれがんとなこーど(自称) を作る為、極限まで無駄を省きましょう。

えれがんとなこーどが地球を救うことはないですが、同業種のエンジニアは救ってくれます。
(某 24 時間 TV にこんな標語がありましたね。懐かしい)

では個人的に駄目なポイント解説です。コメントふります。

class Program
{
    // ①
    static readonly ReadOnlyCollection<string> PROC_TYPES = new List<string>() { "0", "1", "2" }.AsReadOnly();

    static void Main(string[] args)
    {
        if (!args.Any() || args.Length > 1)
        {
            throw new ArgumentException($"起動モード未設定または複数の設定:{args.Length}");
        }

        string procType = args.First();
        if (!PROC_TYPES.Contains(procType)) {
            throw new ArgumentException($"不正な起動タイプ:{procType}");
        }

        var list = Enumerable.Range(1, 30).ToList();

        // ①
        if (procType == "2")
        {
            list.Reverse();
            // ②
            list.ForEach(d =>
            {
                string fmt = IsMetamorphoseOjyohin(d) ? $"★{d}★," : $"{d},";
                Console.Write(fmt);
            });
            return;
        }

        // ①
        if (procType == "1")
        {
            // ②
            list.ForEach(d =>
            {
                string fmt = IsMetamorphoseOjyohin(d) ? $"★{d}★," : $"{d},";
                Console.Write(fmt);
            });
            return;
        }

        // ①,②
        list.ForEach(d =>
        {
            Console.Write($"{d},");
        });
    }

    /**
     * お上品になる条件の判定
     **/
    static bool IsMetamorphoseOjyohin(int num) => num % 3 == 0 || num.ToString().Contains("3");
}

① 起動タイプを文字列管理

受け入れるべき値がわかっているのであれば、Enum です。
定数管理もありですが、グループ化できる定数なら Enum 使うべきです。

あとリテラル比較は絶対 NG です。記述の背景がわからなくなるので、後任エンジニアが宇宙猫(※)になります。
※気になる人は Google 検索です。

② 重複処理が多すぎる

同じような処理に散見されますね。コピペコードは技術的負債になる可能性が高いです。纏めましょう。

改善版コード

前述のポイントを改善したものがこちら。
リテラルを Enum に置換するだけでもコードの見通しは変わるものです。

namespace Sample {
    enum ProcType {
        NORMAL,
        ELEGANT,
        ELEGANT_REVERSE
    }

    class Program
    {
        static void Main(string[] args)
        {
            if (!args.Any() || args.Count() > 1)
            {
                throw new ArgumentException($"起動モード未設定または複数の設定:{args.Length}");
            }

            if (!(Enum.TryParse(args.First(), false, out ProcType procType) && Enum.IsDefined(typeof(ProcType), procType)))
            {
                throw new ArgumentException($"不正な起動タイプ:{procType}");
            }

            var list = Enumerable.Range(1, 30).ToList();
            // c#でfall through はできないらしい。無念。
            if (procType == ProcType.ELEGANT_REVERSE) list.Reverse();

            switch (procType)
            {
                case ProcType.ELEGANT_REVERSE:
                case ProcType.ELEGANT:
                    list.ForEach(d => Console.Write(IsMetamorphoseOjyohin(d) ? $"★{d}★," : $"{d},"));
                    return;
                case ProcType.NORMAL:
                    list.ForEach(d => Console.Write($"{d},"));
                    return;
            }
        }

        /**
         * お上品になる条件の判定
         **/
        static bool IsMetamorphoseOjyohin(int num) => num % 3 == 0 || num.ToString().Contains("3");
    }
}

まとめ

本日はナベアツプログラムを組んでみました。
技術ネタというより息抜きネタにはなりましたが、偶にはこんなネタもいいんじゃないかなと思います。

IT って頭使う職種ですし、偶には知性を投げ捨てるのも心の健康面で重要です。

ということで、是非皆さんも趣味全開のプログラムを書いてみてください。
遊び仕様とか、遊びの変数名とか仕込むと意外と楽しいですよ。