この記事は、All About Group(株式会社オールアバウト) Advent Calendar 2020 7日目の記事です。
初めての方ははじめまして、そうでない方はお久しぶりです。
@NACKと申します。
今年も「毎月7日は罠(わな=07)の日」……と言い張ってみます。
※記念日認定されているわけではありませんので、ご注意ください。
というわけで、今年も罠の話を、昔のゲームブック風に書いてみます。
今年のお題はVB.NET(Visual Basic .NET)です……が、私はPHPer&Java屋なので、VB.NET専任の方には、逆に「何が罠なの?」となるかもしれません。
私のように、もともと他の言語が専門で、最近VB.NETにデビューされた方のほうが、楽しんでいただけるかと思います。
「では、行ってらっしゃい!(1へ進む)」
「そういうお遊びは興味無いから!」という方は、直接 罠の解説まで飛んでください。
罠の話が列挙されています。
VB.NETトラップ(ゲームブック風)
1
あなたの会社は、半年以上「原則リモート勤務」という状況が続いている。
今日は、久々に物理出社することになった。
あなたは会社の最寄り駅で電車を降りて、ホームに立っている。
が、会社までの道のりが思い出せない……。
困っていると、会社支給のスマートフォンに以下の文章が届いた。
' 会社までの道のりを、VB.NETの問題で示す。
' 正しい道を進んだ場合のみ、次の問題を送るのでそのつもりで。
' 問:
' このfunctionはエラーにならずに動作するか?
Private Function DummyFunc() As Boolean
Dim hoge As String
Return (hoge Is Nothing Or hoge.ToString())
End Function
' 「動作する」と思うならホームの階段を下れ。
' 「動作しない」と思うならホームの階段を上れ。
さてどうしようか?
2
歩きながら、あなたは、この辺りに、おいしいラーメン屋があることを思い出した(#14へ)。
※解説
3
会社の建物に着いたところで、次の問題が届いた。
' 問:
' この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」だと思うならエレベーターに乗れ。
さてどうしようか?
4
コーヒーフレッシュは固まっていた。
あなたはがっかりしながら、黒いままのコーヒーを飲むことにする(#7へ)。
※解説
5
イベントスペースに着いた。ここでは、いつも様々なイベントが開催されている(#14へ)。
※解説
6
コーヒーショップに着いたところで、次の問題が届いた。
' 問:
Now.ToString("M")
' の戻り値は?
' なお、今は2020年12月07日10時15分だとする。
' 「15」だと思うなら右へ進め。
' 「12」だと思うなら坂道を下れ。
' 「それ以外」だと思うなら階段を下れ。
さてどうしようか?
7
では、久々の職場を懐かしく思いながら、仕事を開始することにしよう。
(クリア。最後までプレイしていただき、ありがとうございました!)
8
違う会社についてしまった。
受付のお姉さんの笑顔がまぶしい……(#14へ)。
※解説
9
歩きながら、あなたは、この辺りにおいしいチョコレート専門店があることを思い出した(#14へ)。
※解説
10
改札に着いた。
すぐそばにお洒落な喫茶店があるのが見える(#14へ)。
※解説
11
ふぅ~~~~~~っ!
全身で 香りを感じる
幸せな ひとときだ~!!(#7へ)
12
歩きながら、あなたは、この辺りに、お肉系の飲食店がたくさん入ったビルがあることを思い出した。
正式名称ではないが、職場の人たちは「肉ビル」と呼んでいた気がする(#14へ)。
※解説
13
お昼によく行っていた、おいしいイタリアンレストランが見えてきた。
たしか、ラザニアが名物のはずだ(#14へ)。
※解説
14
ただ、通勤時に、この辺りに来た記憶はないし、次の問題も届かない……。
あなたは、会社に「迷ったので、会社までの道を教えて欲しい」と電話することにした。
次は、自力でたどり着けるよう頑張ろう。
15
あなたは、お洒落な大型商業施設に着いた。広場には素敵なシャンデリアが飾られている(#14へ)。
※解説
16
エレベータに乗ったところで、次の問題が届いた。
' 問:
' この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階に行け
さてどうしようか?
17
大きい交差点に着いたところで、次の問題が届いた。
' 問:
' この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」だと思うなら左へ進め
さてどうしようか?
18
受付のお姉さんが素敵な笑顔で出迎えてくれた。
ここは車のショールームのようだ。様々な車が並べられており、お土産も売られている(#14へ)。
※解説
19
歩きながら、あなたは、この辺りにおいしいたい焼き専門店があることを思い出した(#14へ)。
※解説
20
小さい交差点に着いたところで、次の問題が届いた。
' 問:
' この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」だと思うなら左へ進め
さてどうしようか?
21
あなたは無事に会社にたどり着いた!
一息つくため、ラウンジのコーヒー(50円)を買って飲んでも良い。
なお、コーヒーを飲む場合、備品のコーヒーフレッシュを使っても良いし、使わなくても良い。
23
コンビニエンスストアに着いたところで、次の問題が届いた。
' 問:
IsDate("12.345")
' の戻り値は?
' 「TRUE」だと思うならまっすぐ進め
' 「FALSE」だと思うなら左へ進め
さてどうしようか?
24
改札に着いたところで、次の問題が届いた。
どうやら正しい道を進めているようだ。
' 問:
Now.ToString("y.M")
' の戻り値は?
' なお、今は2020年12月07日10時15分だとする。
' 「20.15」だと思うなら左へ進め。
' 「20.12」だと思うならエスカレーターを下れ。
' 「それ以外」だと思うなら右へ進め。
さてどうしようか?
25
改札に着いた。
すぐそばにお洒落な喫茶店があるのが見える(#14へ)。
※解説
26
歩きながら、あなたは、この辺りに、お肉系の飲食店がたくさん入ったビルがあることを思い出した。
正式名称ではないが、職場の人たちは「肉ビル」と呼んでいた気がする(#14へ)。
※解説
27
イベントスペースに着いた。ここでは、いつも様々なイベントが開催されている(#14へ)。
※解説
28
改札に着いた。
すぐそばにお洒落な喫茶店があるのが見える(#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
になります。
ブロックスコープ内の変数(有効範囲)
上記サイトの「ブロックスコープ」項より引用。
ブロック内で変数を宣言した場合、その変数はそのブロック内でのみ使用できます。 たとえば、次の例で整数型の変数 cube のスコープは If と End If の間のブロックであるため、実行ステップがこのブロックの外に出てしまうと cube を参照できなくなります。
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は問題なく参照できます。
ブロックスコープ内の変数(有効期間)
上記サイトの「ブロックスコープ」項より引用( ブロックスコープ内の変数(有効範囲)
で引用した箇所の続きです)。
注意
スコープがブロック内に制限されている変数でも、有効期間はプロシージャ全体の有効期間と同じです。プロシージャ内で同じブロックが複数回実行される場合、各ブロック変数には前に実行されたときの値が保持されています。そのような場合に予想外の結果が生じるのを避けるために、ブロックの先頭ではブロック変数を初期化することをお勧めします。
(引用ここまで)
「ブロック内でしか有効でないのに、有効期間はプロシージャ全体の有効期間と同じ」なせいで、上述のとおり、期待した挙動と違う動きになります。
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型は年月日時分秒を扱う
という違いがあります。
-- 「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
なお、こちら、弊社のアドベントカレンダーの1日目を書いた @amymd さんの実体験です。
↓実際のコメント
会社のコーヒー久々に飲んでみようと思ったら、カフェスペースにおいてあるコーヒーフレッシュ、ほとんど固体になってた…
↑5個ぐらい空けて全部固まってたから、もう全部捨てても良いかもしれない…
コーヒーフレッシュトラップ…
弊社、今年の2月以降、原則リモート勤務となっておりますので、社内のコーヒーを飲む人が少なくなったことが原因かと思われます(平時は新しいものが補充されています!)。
(ゲームブック部分の)参考文献
オールアバウトグループのコーポレートサイト – アクセスマップ
https://corp.allabout.co.jp/corporate/map.html
今年(2020年)のゲームブック部分、実は、本当に「JR恵比寿駅の山手線ホームからオールアバウトへの行き方」になっています(失敗ルートに出てくるお店も、本当にその辺りにあります)。
そのため、VB.NETの知識が無くても、オールアバウトに行ったことがある人ならクリアできるかもしれません。
(2021年6月追記)
※2021年6月に、オールアバウトは恵比寿→恵比寿南に移転しました(この記事を書いた当時は、エビススバルビル6階にありました)。
そのため、このゲームブックのルートでは行けなくなっておりますのでご注意ください。
最後に
ゲームブック部分の #14(=失敗) に書かれている「会社への行き方がわからず迷ってしまい、会社に電話した」は私の実体験です(今の会社ではありません)。
一応、言い訳をさせていただくと、当時は客先に常駐しており、「自分の会社には忘年会の時(=年に1回)しか行かない」生活だったもので……なお、まだスマホが存在しなかった時代の話です。
ただ、今の会社も、半年以上、物理出社していないので(前述のとおり、弊社は今年の2月以降、原則リモート勤務です)、無事にたどり着けるかどうか……迷いそうになったら、このQiitaを読み返そうと思います。