【VB.NET】ByRefをなんで使うの...と思った話

# どうして思ったのか
今日、Googleで`VB.NET 複数の値を返す メソッド`で検索した一番上のサイトがByRefを使った値の返し方をしていたからです。

```VB:サイト内で紹介されていたソース
Sub Main()
    Dim a As Integer = 100
    Dim b As Integer = 10
    Dim tasizan As Integer = 0
    Dim hikizan As Integer = 0

    keisan(a, b, tasizan, hikizan)
    Console.WriteLine("{0}, {1}", tasizan, hikizan)
End Sub

Sub keisan(data1 As Integer, data2 As Integer,
           ByRef ans1 As Integer, ByRef ans2 As Integer)
    ans1 = data1 + data2
    ans2 = data1 - data2
End Sub
```

# ByRefは最悪だと思う
個人的に`ByRef`は最悪だと思っています。というよりかはVB.NETのみならずメソッドの引数を参照渡しすることが最悪だと思います。もはやなぜ、ByRefが存在するのか分かりません。
# 理由

理由は3つあります。

1. 知らぬ間に値が変わっている可能性がある
1. 引数が大量になる
1. メソッドの修正がしづらい

### 1. 知らぬ間に値が変わっている可能性がある
例で出したソースの`keisan`メソッドの呼出しを下記のように変更すると変数`a`,`b`の中身が変わってしまいます。

```VB:keisanメソッドの呼出し
keisan(a, b, a, b)
```

これが個人的に一番の悪だと思っていて、コードは読みにくいし(同じ変数をなんで2つ渡してるんだ？ってなる)いつの間にか変数の中身が変わっていてコーディング中の脳内での変数の動きと実際の動きが変わってしまいます。

### 2. 引数が大量になる
現在の`keisan`メソッドは参照返しで足し算の結果と引き算の結果を返していますが、もし掛け算の結果とわり算の結果も返すようなメソッドの修正する場合は引数の数が6つに増えます。

```VB:keisan(掛け算の結果とわり算の結果も返す)
Sub keisan(data1 As Integer, data2 As Integer,
           ByRef ans1 As Integer, ByRef ans2 As Integer,
           ByRef ans3 As Integer, ByRef ans4 As Integer)
    ans1 = data1 + data2
    ans2 = data1 - data2
    ans3 = data1 * data2
    ans4 = data1 / data2
End Sub
```

### 3. メソッドの修正がしづらい
例の`keisan`メソッドを修正して下記のようにすると割り算の結果が変更されていない状態でメソッドを抜ける実装になってしまいます。また、エラーも発生しませんし引数として渡された値が変わらないだけなので単体テストの際も不具合に気付かずにスルーしてしまう可能性があります。

```VB:keisanメソッド(掛け算の結果が5以上ならメソッドを抜ける)
Sub keisan(data1 As Integer, data2 As Integer,
	       ByRef ans1 As Integer, ByRef ans2 As Integer,
	       ByRef ans3 As Integer, ByRef ans4 As Integer)
    ans1 = data1 + data2
    ans2 = data1 - data2
    ans3 = data1 * data2
    
    '掛け算の結果が5以上ならメソッドを抜ける
    If ans3 > 5 Then Exit Sub

    ans4 = data1 / data2
End Sub
```

# じゃあ、どうするのか

Tuple型を使って戻り値を返します。

```VB:Tupleを使った複数の戻り値を返すソース
Module Program
    Sub Main()
        Dim a As Integer = 100
        Dim b As Integer = 10

        Dim ret = keisan(a, b)

        Console.WriteLine("{0}, {1}", ret.tasizan, ret.hikizan)
    End Sub

    Function keisan(data1 As Integer, data2 As Integer) As (tasizan As Integer, hikizan As Integer, kakezan As Integer, warizan As Integer)

        Dim tasizan = data1 + data2
        Dim hikizan = data1 - data2
        Dim kakezan = data1 * data2
        Dim warizan = data1 / data2

        Return (tasizan, hikizan, kakezan, warizan)

    End Function
End Module
```

# 理由が解決しているか確認する
`ByRef`を使いたくない理由として下記の3点をあげていました。

1. 知らぬ間に値が変わっている可能性がある
1. 引数が大量になる
1. メソッド内でソースを読むのが大変

これらが解決しているかを確認していきます。

### 1. 知らぬ間に値が変わっている可能性がある
`keisan`メソッドの引数には値渡ししかないので`keisan`メソッドを初めて使用する開発者でも知らぬ間に値が変わることはありません。

### 2. 引数が大量になる
もし、余りを返すようにメソッドを修正することになったとしても戻り値のTupleに追加するだけなので引数が増えることはありません。

```VB:keisanメソッドに余りを返す機能を追加
Function keisan(data1 As Integer, data2 As Integer) As (tasizan As Integer, hikizan As Integer, kakezan As Integer, warizan As Integer, amari As Integer)

    Dim tasizan = data1 + data2
    Dim hikizan = data1 - data2
    Dim kakezan = data1 * data2
    Dim warizan = data1 / data2
    Dim amari = data1 Mod data2

    Return (tasizan, hikizan, kakezan, warizan, amari)

End Function
```

### 3. メソッドの修正がしづらい

~~戻り値を返さないとメソッドを抜けることができないので~~実装中に割り算の結果がないことに気が付きます。※Exit Functionを使えば抜けることができますが、それは開発者が無意識に実装している訳ではないので例外として下さい。

```VB:実装中に割り算の結果がないことに気が付く
Function keisan(data1 As Integer, data2 As Integer) As (tasizan As Integer, hikizan As Integer, kakezan As Integer, warizan As Integer)

    Dim tasizan = data1 + data2
    Dim hikizan = data1 - data2
    Dim kakezan = data1 * data2

    If kakezan > 5 Then Return (tasizan, hikizan, kakezan, warizan)

    Dim warizan = data1 / data2

    Return (tasizan, hikizan, kakezan, warizan)

End Function
```

変数`warizan`が参照できないのでコンパイルエラーになります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2519245/e79d73e4-5392-7d4c-21a6-8f9074750b3d.png)

# おわりに
もし、`ByRef`を有意義に使う方法を知っている方がいれば教えて頂きたいですm(_ _)m