VB.NETトラップ（ゲームブック風）

この記事は、[All About Group（株式会社オールアバウト） Advent Calendar 2020](https://qiita.com/advent-calendar/2020/allabout) 7日目の記事です。


初めての方ははじめまして、そうでない方はお久しぶりです。
@NACKと申します。

今年も「毎月７日は罠（わな＝０７）の日」……と言い張ってみます。
※記念日認定されているわけではありませんので、ご注意ください。

というわけで、今年も罠の話を、昔の[ゲームブック風](https://ja.wikipedia.org/wiki/%E3%82%B2%E3%83%BC%E3%83%A0%E3%83%96%E3%83%83%E3%82%AF)に書いてみます。
今年のお題はVB.NET（Visual Basic .NET）です……が、私はPHPer＆Java屋なので、VB.NET専任の方には、逆に「何が罠なの？」となるかもしれません。
私のように、もともと他の言語が専門で、最近VB.NETにデビューされた方のほうが、楽しんでいただけるかと思います。
「では、行ってらっしゃい！（[1へ](#1)進む）」

「そういうお遊びは興味無いから！」という方は、直接 [罠の解説](#罠の解説)まで飛んでください。
罠の話が列挙されています。


---
# VB.NETトラップ（ゲームブック風）

----


### 1
あなたの会社は、半年以上「原則リモート勤務」という状況が続いている。
今日は、久々に物理出社することになった。

あなたは会社の最寄り駅で電車を降りて、ホームに立っている。
が、会社までの道のりが思い出せない……。

困っていると、会社支給のスマートフォンに以下の文章が届いた。

```vb

' 会社までの道のりを、VB.NETの問題で示す。
' 正しい道を進んだ場合のみ、次の問題を送るのでそのつもりで。

' 問：
' 　このfunctionはエラーにならずに動作するか？

    Private Function DummyFunc() As Boolean
        Dim hoge As String

        Return (hoge Is Nothing Or hoge.ToString())
    End Function

' 「動作する」と思うならホームの階段を下れ。
' 「動作しない」と思うならホームの階段を上れ。
```

さてどうしようか？

- ホームの階段を下る（[#10へ](#10)）
- ホームの階段を上る（[#24へ](#24)）

----
### 2
歩きながら、あなたは、この辺りに、[おいしいラーメン屋](https://tabelog.com/tokyo/A1303/A130302/13044426/)があることを思い出した（[#14へ](#14)）。

※[解説](#日時書式指定文字列)

----
### 3
会社の建物に着いたところで、次の問題が届いた。

```vb

' 問：
'　 このfunctionの戻り値は？

    Private Function DummyFunc() As String
        Dim hoge As String
        hoge = ""

        For i = 0 To 2
            Dim fuga As Integer

            If (i = 0) Then
                fuga = 1
            End If

            hoge = fuga.ToString
        Next i

        Return hoge
    End Function

'  「0」だと思うなら1階の受付に行け。
'  「戻り値を返せない（その前にエラーになる）」だと思うならエスカレーターに乗れ。
'  「1」だと思うならエレベーターに乗れ。
```

さてどうしようか？

- 1階の受付に行く（ [#18へ](#18) ）
- エスカレーターに乗る（[#5へ](#5)）
- エレベーターに乗る（[#16へ](#16)）


----

### 4
コーヒーフレッシュは固まっていた。
あなたはがっかりしながら、黒いままのコーヒーを飲むことにする（[#7へ](#7)）。

※[解説](#おまけコーヒーフレッシュの賞味期限)

----

### 5
[イベントスペース](https://www.ebis303.com/)に着いた。ここでは、いつも様々なイベントが開催されている（[#14へ](#14)）。

※[解説](#ブロックスコープ内の変数有効期間)

----
### 6
コーヒーショップに着いたところで、次の問題が届いた。

```vb
' 問：

Now.ToString("M")

' の戻り値は？
' なお、今は2020年12月07日10時15分だとする。

' 「15」だと思うなら右へ進め。
' 「12」だと思うなら坂道を下れ。
' 「それ以外」だと思うなら階段を下れ。
```

さてどうしようか？

- 右へ進む（[#2へ](#2)）
- 坂道を下る（[#12へ](#12)）
- 階段を下る（[#23へ](#23)）

----
### 7
では、久々の職場を懐かしく思いながら、仕事を開始することにしよう。

（クリア。最後までプレイしていただき、ありがとうございました！）

----
### 8
[違う会社](https://news.mynavi.jp/article/20140807-a150/)についてしまった。
受付のお姉さんの笑顔がまぶしい……（[#14へ](#14)）。

※[解説](#dateとdatetimeの違い)

----

### 9
歩きながら、あなたは、この辺りにおいしいチョコレート専門店があることを思い出した（[#14へ](#14)）。

※[解説](#datetimeの初期値)

----

### 10
改札に着いた。
すぐそばに[お洒落な喫茶店](https://tabelog.com/tokyo/A1303/A130302/13115463/)があるのが見える（[#14へ](#14)）。

※[解説](#論理演算子の短絡評価)

----

### 11
ふぅ～～～～～～っ！

全身で　香りを感じる
幸せな　ひとときだ～！！（[#7へ](#7)）

----

### 12
歩きながら、あなたは、この辺りに、[お肉系の飲食店がたくさん入ったビル](http://www.gems-portal.com/series/detail.php?id=5)があることを思い出した。
正式名称ではないが、職場の人たちは「肉ビル」と呼んでいた気がする（[#14へ](#14)）。

※[解説](#日時書式指定文字列)

----

### 13
お昼によく行っていた、[おいしいイタリアンレストラン](https://take-5.co.jp/redpepper/ebisu/)が見えてきた。
たしか、ラザニアが名物のはずだ（[#14へ](#14)）。

※[解説](#ブロックスコープ内の変数有効範囲)

----

### 14
ただ、通勤時に、この辺りに来た記憶はないし、次の問題も届かない……。
あなたは、会社に「迷ったので、会社までの道を教えて欲しい」と電話することにした。

次は、自力でたどり着けるよう頑張ろう。

----

### 15
あなたは、[お洒落な大型商業施設](https://gardenplace.jp/)に着いた。広場には素敵なシャンデリアが飾られている（[#14へ](#14)）。

※[解説](#日時書式指定文字列)

----

### 16
エレベータに乗ったところで、次の問題が届いた。

```vb
' 問：
' 　このfunctionの戻り値は？

    Private Function DummyFunc() As Boolean
        Dim fugaDate As Date
        fugaDate = Now

        Dim fugaDateTime As DateTime
        fugaDateTime = Now

        Return (fugaDate = fugaDateTime)
    End Function


' 「戻り値を返せない（その前にエラーになる）」だと思うなら3階に行け
' 「TRUE」だと思うなら6階に行け
' 「FALSE」だと思うなら7階に行け
```

さてどうしようか？

- 3階に行く（[#27へ](#27)）
- 6階に行く（[#21へ](#21)）
- 7階に行く（[#8へ](#8)）

----

### 17
大きい交差点に着いたところで、次の問題が届いた。

```vb
' 問：
' 　このfunctionの戻り値は？

    Private Function DummyFunc() As String
        Dim hoge As Date
        hoge = Nothing
        Return hoge.ToString("yyyy/MM/dd")
    End Function

' 「戻り値を返せない（その前にエラーになる）」だと思うなら右へ進め
' 「0001/01/01」だと思うならまっすぐ進め
' 「1970/01/01」だと思うなら左へ進め
```

さてどうしようか？

- 右へ進む（[#26へ](#26)）
- まっすぐ進む（[#20へ](#20)）
- 左へ進む（[#9へ](#9)）

----

### 18
受付のお姉さんが素敵な笑顔で出迎えてくれた。
ここは[車のショールーム](https://www.subaru.jp/showroom/)のようだ。様々な車が並べられており、お土産も売られている（[#14へ](#14)）。

※[解説](#ブロックスコープ内の変数有効期間)

----

### 19
歩きながら、あなたは、この辺りに[おいしいたい焼き専門店](https://twitter.com/taiyaki_hiiragi)があることを思い出した（[#14へ](#14)）。

※[解説](#ブロックスコープ内の変数有効範囲)

----

### 20
小さい交差点に着いたところで、次の問題が届いた。

```vb
' 問：
' 　このfunctionの戻り値は？

    Private Function DummyFunc() As String
        Dim hoge
        hoge = ""

        For i = 0 To 2
            Dim fuga As Integer

            If (i = 0) Then
                fuga = 1
            End If
        Next i

        hoge = fuga.ToString

        Return hoge
    End Function

' 「0」だと思うなら右へ進め
' 「戻り値を返せない（その前にエラーになる）」だと思うならまっすぐ進め
' 「1」だと思うなら左へ進め
```

さてどうしようか？

- 右へ進む（[#13へ](#13)）
- まっすぐ進む（[#3へ](#3)）
- 左へ進む（[#19へ](#19)）

----

### 21
あなたは無事に会社にたどり着いた！
一息つくため、ラウンジのコーヒー（50円）を買って飲んでも良い。
なお、コーヒーを飲む場合、備品のコーヒーフレッシュを使っても良いし、使わなくても良い。

- 飲まない（[#7へ](#7)）
- コーヒーフレッシュを使わない（[#11へ](#11)）
- コーヒーフレッシュを使う（[#4へ](#4)）

----

### 23
コンビニエンスストアに着いたところで、次の問題が届いた。

```vb
' 問：

IsDate("12.345")

' の戻り値は？

' 「TRUE」だと思うならまっすぐ進め
' 「FALSE」だと思うなら左へ進め
```

さてどうしようか？

- まっすぐ進む（[#17へ](#17)）
- 左へ進む（[#25へ](#25)）

----

### 24
改札に着いたところで、次の問題が届いた。
どうやら正しい道を進めているようだ。

```vb
' 問：

Now.ToString("y.M")

' の戻り値は？
' なお、今は2020年12月07日10時15分だとする。

' 「20.15」だと思うなら左へ進め。
' 「20.12」だと思うならエスカレーターを下れ。
' 「それ以外」だと思うなら右へ進め。
```

さてどうしようか？

- 左へ進む（[#28へ](#28)）
- 目の前のエスカレーターを下る（[#6へ](#6)）
- 右へ進む（[#15へ](#15)）

----
### 25
改札に着いた。
すぐそばに[お洒落な喫茶店](https://tabelog.com/tokyo/A1303/A130302/13115463/)があるのが見える（[#14へ](#14)）。

※[解説](#isdateの判定結果)

----
### 26
歩きながら、あなたは、この辺りに、[お肉系の飲食店がたくさん入ったビル](http://www.gems-portal.com/series/detail.php?id=5)があることを思い出した。
正式名称ではないが、職場の人たちは「肉ビル」と呼んでいた気がする（[#14へ](#14)）。

※[解説](#datetimeの初期値)

----
### 27
[イベントスペース](https://www.ebis303.com/)に着いた。ここでは、いつも様々なイベントが開催されている（[#14へ](#14)）。

※[解説](#dateとdatetimeの違い)

----
### 28
改札に着いた。
すぐそばに[お洒落な喫茶店](https://tabelog.com/tokyo/A1303/A130302/13115463/)があるのが見える（[#14へ](#14)）。

※[解説](#日時書式指定文字列)

----


# 罠の解説
## 論理演算子の短絡評価
参考： https://qiita.com/gyu-don/items/a0aed0f94b8b35c43290

PHPやJavaの場合、 `Hoge Or Fuga` （ `Hoge || Fuga` ）と書いた場合、 `Hoge` がTrueであればFugaに対しては何もしません。
そのため、HogeがTrueの場合、実はFugaが実行時エラーになるような内容だったとしても、（何もしないので）エラーは発生しません。

なぜかと言うと、HogeがTrueな時点で、それ以降の値がどんな値だったとしても結果はTrueになることが確定するので、Fugaを評価する必要が無いからです。
これを「短絡評価」と言います。


ただ、VB.NETの場合、 `Hoge Or Fuga` と書いてしまうと、 `Hoge` がTrueであっても `Fuga` の評価をしようとします。
今回、 `hoge Is Nothing Or hoge.ToString()` がエラーになったのはそれが原因です（hogeがNothingなのにToStringメソッドを呼ぼうとして「hogeにはそんなメソッドはない」となってしまった）。

VB.NETでも、PHPやJavaのように短絡評価をしたい場合、 `Or` ではなく `OrElse` を使いましょう。
短絡評価には、エラー回避効果以外に、速度改善の効果もあります（無駄な処理を動かさずに済むようになるので）。

## 日時書式指定文字列
参考： 
https://docs.microsoft.com/ja-jp/dotnet/standard/base-types/standard-date-and-time-format-strings
https://docs.microsoft.com/ja-jp/dotnet/standard/base-types/custom-date-and-time-format-strings

VB.NETの日時書式指定文字列には「標準」と「カスタム」があります。
カスタム書式指定文字列は、1 つ以上のカスタム日時書式指定子で構成されます。 標準の日時書式指定文字列以外の文字列は、すべてカスタム日時書式指定文字列として解釈されます。
大文字と小文字には挙動の違いがあり、「カスタム」の場合、大文字の`M`は「月」、小文字の`m`は「分」を表します。

そこまでは良いのですが、困ったことに「標準」と「カスタム」に同じ文字列が登録されています（例： `M` 、`d` ）。
この場合の「標準」と「カスタム」の判別方法は、「標準の文字列と、全体が完全一致するかどうか」です。

そのため、もともと `Now.ToString("MM")` で「0埋めした月」を取得していたが、0埋めが不要になったので `Now.ToString("M")` に変える……といった作業をすると、想定外の値になります。
なお「標準と完全一致するかどうか」がポイントですので、 `Now.ToString("yyyy/MM")` を `Now.ToString("yyyy/M")` に変えた場合は（全体でみると、標準の `M` と完全一致しないので）想定どおりの値になります。

ちなみに、今回、ゲームブック部分で出題した `Now.ToString("M")` は、日本の場合 `12月7日` といった文字列が返ってきます（外国の場合、違う文字列が返ってきます。実行時に「どの国として扱うか」を指定することも可能です）。

## DateTimeの初期値
参考： https://mseeeen.msen.jp/vb-net-datetime-and-nothing/

String等の変数の場合、初期値は `Nothing` です（PHP等の `null` に該当するもの）。
そのため、「宣言したばかりで、何も値を設定していないStringの変数」に対して「Stringが持っているメソッド（例：ToString）」を実行しようとするとエラーになります。
しかし、DateTime(=Date)の場合、初期値は `Nothing` ではなく `初期化されていないDateTimeの構造体` が設定されるそうです（上記参考ページで詳しく記載されておりますので、興味がある方は是非そちらをご覧ください）。
そのため、結果として `0001年01月01日` が返ってくることになります。
なお、ゲームブック部分の選択肢の `1970年01月01日` は、タイムスタンプ（Unix時間）の初期値です（ 参考：https://ja.wikipedia.org/wiki/UNIX%E6%99%82%E9%96%93 ）。

## IsDateの判定結果
参考： https://takaken.tokyo/dev/isdate-trap/isdate-trap/

IsDateは「引数として渡された値が日付かどうか」を判定してくれます。
が、

- 日付の区切り文字として `.` を認めている
- 「年月日」の全てが揃っていなくても「年月だけ」でもOKとしている

せいで、思いがけない値がTRUEになることがあります。
他にも想定外の結果になることがあります（上記参考ページで詳しく記載されておりますので、興味がある方は是非そちらをご覧ください）。

ちなみに、今回、ゲームブック部分で出題した `12.345` は、 `西暦345年12月` と扱われるため、 `TRUE`になります。

## ブロックスコープ内の変数（有効範囲）
参考： https://docs.microsoft.com/ja-jp/previous-versions/1t0wsc67(v=vs.120)?redirectedfrom=MSDN#%E3%83%96%E3%83%AD%E3%83%83%E3%82%AF-%E3%82%B9%E3%82%B3%E3%83%BC%E3%83%97

上記サイトの「ブロックスコープ」項より引用。

> ブロック内で変数を宣言した場合、その変数はそのブロック内でのみ使用できます。 たとえば、次の例で整数型の変数 cube のスコープは If と End If の間のブロックであるため、実行ステップがこのブロックの外に出てしまうと cube を参照できなくなります。

```vb

If n < 1291 Then
    Dim cube As Integer
    cube = n ^ 3
End If
```

（引用ここまで）

もし変数cubeをIf文の外で使いたい場合は、あらかじめ、If文の外で変数宣言を行っておく必要があります。

なお、PHPの場合、「ブロックスコープ」という概念が存在しません（ローカルスコープ等はあります）。
参考： http://yoshikyoto.github.io/text/php/var_scope.html
そのため、PHPで同じようなコードを書いた場合、If文の外で変数cubeは問題なく参照できます。

## ブロックスコープ内の変数（有効期間）
参考： https://docs.microsoft.com/ja-jp/previous-versions/1t0wsc67(v=vs.120)?redirectedfrom=MSDN#%E3%83%96%E3%83%AD%E3%83%83%E3%82%AF-%E3%82%B9%E3%82%B3%E3%83%BC%E3%83%97

上記サイトの「ブロックスコープ」項より引用（ `ブロックスコープ内の変数（有効範囲）` で引用した箇所の続きです）。

> 注意
>スコープがブロック内に制限されている変数でも、有効期間はプロシージャ全体の有効期間と同じです。プロシージャ内で同じブロックが複数回実行される場合、各ブロック変数には前に実行されたときの値が保持されています。そのような場合に予想外の結果が生じるのを避けるために、ブロックの先頭ではブロック変数を初期化することをお勧めします。

（引用ここまで）

「ブロック内でしか有効でないのに、有効期間はプロシージャ全体の有効期間と同じ」なせいで、上述のとおり、期待した挙動と違う動きになります。
PHPのように「そもそもブロックスコープという概念が無い」上で、この挙動なら納得するのですが……。

## DateとDateTimeの違い
参考： https://windev.just4fun.biz/?.NET/Date%E3%81%A8DateTime%E3%81%AE%E9%81%95%E3%81%84#zf989a22

MySQL等のデータベースにおいて、DateとDateTimeには

- Date型は年月日のみ扱う
- DateTime型は年月日時分秒を扱う

という違いがあります。

```sql
-- 「date型のtarget_dateカラム」と「datetime型のtarget_datetimeカラム」が存在する場合
INSERT INTO hoge (id, target_date, target_datetime) VALUES (1, NOW, NOW);
-- →target_dateには"2020/12/07"、 target_datetimeには"2020/12/07 12:34:56"が入る＝それぞれ違う値が入る。
SELECT (CASE WHEN target_date = target_datetime THEN 1 ELSE 0 END) AS diff_result FROM hoge WHERE id = 1
-- →target_date と target_datetime には違う値が入っているため、比較結果は一致しない。
```

ですが、VB.NETの場合、DateとDateTimeは、なんと「**同じもの**」です。
そのため、「Nowを代入したDate変数とDateTime変数」の比較結果は一致するのです。

## おまけ：コーヒーフレッシュの賞味期限
商品によって異なりますが、90日～120日程度のものが多いようです。
あまりに賞味期限を過ぎてしまうと、水分が蒸発して固まります。
参考： https://www.ucc.co.jp/customer/faq/faq027.html 


なお、こちら、[弊社のアドベントカレンダー](https://qiita.com/advent-calendar/2020/allabout)の[1日目](https://qiita.com/amymd/items/3933c5ac3865fed16332)を書いた @amymd さんの実体験です。

↓実際のコメント
> 会社のコーヒー久々に飲んでみようと思ったら、カフェスペースにおいてあるコーヒーフレッシュ、ほとんど固体になってた… :scream:
> ↑5個ぐらい空けて全部固まってたから、もう全部捨てても良いかもしれない…

> コーヒーフレッシュトラップ…

弊社、今年の2月以降、原則リモート勤務となっておりますので、社内のコーヒーを飲む人が少なくなったことが原因かと思われます（平時は新しいものが補充されています！）。

# （ゲームブック部分の）参考文献
オールアバウトグループのコーポレートサイト &#8211; アクセスマップ
https://corp.allabout.co.jp/corporate/map.html

今年（2020年）のゲームブック部分、実は、本当に「JR恵比寿駅の山手線ホームからオールアバウトへの行き方」になっています（失敗ルートに出てくるお店も、本当にその辺りにあります）。
そのため、VB.NETの知識が無くても、オールアバウトに行ったことがある人ならクリアできるかもしれません。

（2021年6月追記）
※2021年6月に、オールアバウトは恵比寿→恵比寿南に移転しました（この記事を書いた当時は、エビススバルビル6階にありました）。
　そのため、このゲームブックのルートでは行けなくなっておりますのでご注意ください。

# 最後に
ゲームブック部分の [#14（＝失敗）](#14) に書かれている「会社への行き方がわからず迷ってしまい、会社に電話した」は私の実体験です（今の会社ではありません）。
一応、言い訳をさせていただくと、当時は客先に常駐しており、「自分の会社には忘年会の時（＝年に１回）しか行かない」生活だったもので……なお、まだスマホが存在しなかった時代の話です。

ただ、今の会社も、半年以上、物理出社していないので（前述のとおり、弊社は今年の2月以降、原則リモート勤務です）、無事にたどり着けるかどうか……迷いそうになったら、このQiitaを読み返そうと思います。