なんでもかんでも配列- 11[C#リファクタリングサンプル]

##なんでもかんでも配列とは
クラスが分からないプログラマの場合、本来クラスにすれば良い部分を配列で全て表現することが多い。例えば、Listを使用せず、拡張可能な配列を使用したり、クラスのメンバーに当たる部分を全て配列にするなどである。
最終的に配列を使用して製造を完了させている点を考えると、配列は万能といえるかもしれない。
一方、保守性が低く、少しの仕様変更でもコードの修正箇所は多くなるため、悪い習慣といえる。サンプルコードで具体的に考えてみる。

##サンプルコード
2次元配列にID,Name,Remarksが入っている配列をTSVに変換する例を考える。

```csharp
        /* 渡されたデータの行に対して、行番号を付与して返す。
         * ただし、Nameを除いたTsv形式とする。
         * valuesには行、列の形式でID,Name,Remarksが格納されている。
         */
        public List<string> GetTsvWithRowNumber(string[][] values)
        {
            var ids = new string[values.Length];
            var remarks = new string[values.Length];
            var rowNumbers = new int[values.Length];

            // 行単位の配列に格納する。
            for (int i = 0; i < values.Length; i++)
            {
                ids[i] = values[i][0];
                remarks[i] = values[i][2];
                rowNumbers[i] = i + 1;
            }

            var returnValues = new List<string>();

            // 行番号を先頭に付与して戻り値を作成する。
            for (int i = 0; i < ids.Length; i++)
            {
                var line =
                    rowNumbers[i] + "\t" +
                    ids[i] + "\t" +
                    remarks[i];
                returnValues.Add(line);
            }

            return returnValues;
        }

```

<details>
<summary>java</summary>
<div>

```java
    /* 渡されたデータの行に対して、行番号を付与して返す。
     * ただし、Nameを除いたTsv形式とする。
     * valuesには行、列の形式でID,Name,Remarksが格納されている。
     */
    public List<String> getTsvWithRowNumber(String[][] values) {
        String[] ids = new String[values.length];
        String[] remarks = new String[values.length];
        int[] rowNumbers = new int[values.length];

        // 行単位の配列に格納する。
        for (int i = 0; i < values.length; i++) {
            ids[i] = values[i][0];
            remarks[i] = values[i][2];
            rowNumbers[i] = i + 1;
        }

        ArrayList<String> returnValues = new ArrayList<String>();

        // 行番号を先頭に付与して戻り値を作成する。
        for (int i = 0; i < ids.length; i++) {
            String line =
                    rowNumbers[i] + "\t" +
                    ids[i] + "\t" +
                    remarks[i];
            returnValues.add(line);
        }

        return returnValues;
    }

```

</div>
</details>

##問題点
このコードは、各項目を配列にしており、全ての配列は行数と一致しなければならない。配列の宣言を間違った時点で、インデックス境界範囲外のエラーが発生する。
項目数が少ないなら何とかなるが、業務アプリケーションでは10項目以上が存在することも普通にあるので、全て配列にするだけでも大変である。

##リファクタリング後
Dtoに変換してから処理すると、可読性が高くなるし、インデックス問題も解決する。また、マジックナンバーを少なくするため、values[i][j++]と修正した。
可読性は下がるが、項目追加時の修正範囲は少なくなる。

```csharp
       /* 渡されたデータの行に対して、行番号を付与して返す。
         * ただし、Nameを除いたTsv形式とする。
         * valuesには行、列の形式でID,Name,Remarksが格納されている。
         */
        public List<string> GetTsvWithRowNumber(string[][] values)
        {
            var dtoes = new List<Dto>(values.Length);

            // Dtoに変換する。
            for (int i = 0; i < values.Length; i++)
            {
                int j = 0;
                var dto = new Dto();
                dto.Id = values[i][j++];
                dto.Name = values[i][j++];
                dto.Remarks = values[i][j++];
                dto.RowNumber = i + 1;

                dtoes.Add(dto);
            }

            var returnValues = new List<string>();

            // 行番号を先頭に付与して戻り値を作成する。
            foreach (var dto in dtoes)
            {
                var line =
                    dto.RowNumber + "\t" +
                    dto.Id + "\t" +
                    dto.Remarks;
                returnValues.Add(line);
            }

            return returnValues;
        }

        /** 処理のための中間クラス **/
        private class Dto
        {
            public int RowNumber { get; set; }
            public string Id { get; set; }
            public string Name { get; set; }
            public string Remarks { get; set; }

        }

```

<details>
<summary>java</summary>
<div>

```java
    /* 渡されたデータの行に対して、行番号を付与して返す。
     * ただし、Nameを除いたTsv形式とする。
     * valuesには行、列の形式でID,Name,Remarksが格納されている。
     */
    public List<String> getTsvWithRowNumber(String[][] values) {
        List<Dto> dtoes = new ArrayList<Dto>(values.length);

        // Dtoに変換する。
        for (int i = 0; i < values.length; i++) {
            int j = 0;
            Dto dto = new Dto();
            dto.setId(values[i][j++]);
            dto.setName(values[i][j++]);
            dto.setRemarks(values[i][j++]);
            dto.setRowNumber(i + 1);

            dtoes.add(dto);
        }

        List<String> returnValues = new ArrayList<String>();

        // 行番号を先頭に付与して戻り値を作成する。
        for (Dto dto : dtoes) {
            String line =
                    dto.getRowNumber() + "\t" +
                    dto.getId() + "\t" +
                    dto.getRemarks();
            returnValues.add(line);
        }

        return returnValues;
    }

    /** 処理のための中間クラス **/
    private class Dto {
        private int rowNumber;
        private String Id;
        private String name;
        private String remarks;

        public int getRowNumber() {
            return rowNumber;
        }

        public void setRowNumber(int rowNumber) {
            this.rowNumber = rowNumber;
        }

        public String getId() {
            return Id;
        }

        public void setId(String id) {
            Id = id;
        }

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }

        public String getRemarks() {
            return remarks;
        }

        public void setRemarks(String remarks) {
            this.remarks = remarks;
        }

```
</div>
</detaisl>

##まとめ
データ構造である場合、まず、クラスにすることを考えるべきである。
クラスが理解できないのであれば、とりあえず、オブジェクト指向の勉強から始めてはどうだろうか。

また、配列を使用する書き方はVB6でよく見られたが、その後、.Netに移った段階でオブジェクト指向にシフトできなかったプログラマに多い気がする。

2017/4/9 追記
コメント欄にて、Dtoに変換する処理はメソッドにするべきではないかという指摘があったため、処理をメソッドにしたコードを挙げておく。

##再リファクタリング後
values => Dtoだけでなく、Dto=>Tsvもメソッドにした。

```csharp
        /* 渡されたデータの行に対して、行番号を付与して返す。
         * ただし、Nameを除いたTsv形式とする。
         * valuesには行、列の形式でID,Name,Remarksが格納されている。
         */
        public List<string> GetTsvWithRowNumber(string[][] values)
        {
            var dtoes = CreateDtoes(values);

            var returnValues = ConvertToTsv(dtoes);

            return returnValues;
        }

        /** Dtoを生成する **/
        private List<Dto> CreateDtoes(string[][] values)
        {
            var dtoes = new List<Dto>(values.Length);

            // Dtoに変換する。
            for (int i = 0; i < values.Length; i++)
            {
                int j = 0;
                var dto = new Dto();
                dto.Id = values[i][j++];
                dto.Name = values[i][j++];
                dto.Remarks = values[i][j++];
                dto.RowNumber = i + 1;

                dtoes.Add(dto);
            }

            return dtoes;
        }

        /** DtoをTsvに変換する。 **/
        private List<string> ConvertToTsv(List<Dto> dtoes)
        {
            var lines = new List<string>();

            // 行番号を先頭に付与して戻り値を作成する。
            foreach (var dto in dtoes)
            {
                var line =
                    dto.RowNumber + "\t" +
                    dto.Id + "\t" +
                    dto.Remarks;
                lines.Add(line);
            }
            return lines;
        }

        /** 処理のための中間クラス **/
        private class Dto
        {
            public int RowNumber { get; set; }
            public string Id { get; set; }
            public string Name { get; set; }
            public string Remarks { get; set; }

        }
```

<details>
<summary>java</summary>
<div>

```java
   /* 渡されたデータの行に対して、行番号を付与して返す。
     * ただし、Nameを除いたTsv形式とする。
     * valuesには行、列の形式でID,Name,Remarksが格納されている。
     */
    public List<String> getTsvWithRowNumber(String[][] values) {

        List<Dto> dtoes = createDtoes(values);

        List<String> returnValues = convertToTsv(dtoes);

        return returnValues;
    }

    private List<Dto> createDtoes(String[][] values) {
        List<Dto> dtoes = new ArrayList<Dto>(values.length);

        // Dtoに変換する。
        for (int i = 0; i < values.length; i++) {
            int j = 0;
            Dto dto = new Dto();
            dto.setId(values[i][j++]);
            dto.setName(values[i][j++]);
            dto.setRemarks(values[i][j++]);
            dto.setRowNumber(i + 1);

            dtoes.add(dto);
        }
        return dtoes;
    }

    private List<String> convertToTsv(List<Dto> dtoes) {
        List<String> lines = new ArrayList<String>();

        // 行番号を先頭に付与して戻り値を作成する。
        for (Dto dto : dtoes) {
            String line =
                    dto.getRowNumber() + "\t" +
                    dto.getId() + "\t" +
                    dto.getRemarks();
            lines.add(line);
        }
        return lines;
    }

    /** 処理のための中間クラス **/
    private class Dto {
        private int rowNumber;
        private String Id;
        private String name;
        private String remarks;

        public int getRowNumber() {
            return rowNumber;
        }

        public void setRowNumber(int rowNumber) {
            this.rowNumber = rowNumber;
        }

        public String getId() {
            return Id;
        }

        public void setId(String id) {
            Id = id;
        }

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }

        public String getRemarks() {
            return remarks;
        }

        public void setRemarks(String remarks) {
            this.remarks = remarks;
        }



```
</div>
</details>

[前の記事(Getter、Setter逆問題)](http://qiita.com/csharpisthebest/items/9e6d6ff9156cfd70b993)

[次の記事(条件分岐後の処理が冗長な場合)](http://qiita.com/csharpisthebest/items/799ec629fed1e1e22f3f)

[目次](http://qiita.com/csharpisthebest/items/57f27a33085f01211166)