1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Assertionで副作用を起こすな

Last updated at Posted at 2020-06-08

TL;DR

boolで実行結果がわかるような、副作用を起こすメソッドmethodThatWontFail()があるとする。
これが失敗して欲しくない時にUnityのAssertionを発生させるという目的で、Assert.IsTrue(methodThatWontFail());とやってはいけない。
ビルドした時に「基本的にAssertは丸ごと取り除かれる」という仕様のおかげでそのメソッドを実行するという事実ごと吹き飛ぶ。

Backgrounds

C#のList<T>()型には、bool Remove(T item)というメソッドがある。その名の通り、itemに指定したもので一番最初にヒットしたものを削除_しようとする_メソッドである。
この「しようとする」という部分が今回重要になってくる。
このメソッドは実際にListの中にitemで指定したものがなくても例外を吐かない仕様となっている。その代わり、boolの戻り値がtrueだと削除成功という扱いになる。

var list = new List<int>{10, 42};
list.Remove(42); // true - 中身は [10]
list.Remove(5); // false - 削除失敗, 中身は [10] のまま

さて、Assertionとはプログラマがコード中に「何があってもtrueとなってほしい条件」を記述するデバッグの手法の一つであるが、UnityにはこれがUnityEngine.Assertions.Assertクラスで提供されている。ちなみにこのクラスでは条件の記述が読みやすくなるようにラッピングされており、例えば

using UnityEngine.Assertions

Assert.AreEqual(4, 2 + 2); // (期待値, 実際値)
Assert.AreEqual(3, 1 + 1); // false - AssertionException!
Assert.IsTrue(5 < 3); // false - AssertionException!

といった記述ができる。

バグの概要

先ほどのList<T>::Removeのコードにおいて、例えば42を取り除こうとする時点で42がリストに存在しない状況を絶対に起こしたくない、という要求があった。これをAssertionを利用して検出することを考え:

var list = new List<int>{10, 42};
Assert.IsTrue(list.Remove(42)); // 削除失敗するとLogExceptionされる...?

とした。

その結果

リリースビルド時にこのコードが動かなくなった。

WHY?!?!?

Assertionは基本的に「完成品でその条件がfalseになるようなことが起こらないようにデバッグ済みである」という前提となっている。そのため、UnityではReleaseでビルドすると基本的に

「すべてのAssertionに関係するコードがそもそもソースに記述されていなかったことにして」ビルドする1

という挙動をとる。

つまり上記のコードの場合

Assert.IsTrue(list.Remove(42)); がなかったことになる

すなわち

list.Remove(42); が実行されない

という事故が起こった。

結論

今回はList<T>::Removeだったが、それに限らず副作用を起こしてその結果をboolなどで返すようなコードをAssertionでチェックしてはいけない。どうしてもチェックする必要があるなら、副作用を起こさないメソッドで確認するのが良い。

例:

var list = new List<int>{10, 42};
Assert.IsTrue(list.Contains(42)); // will fail if 42 is not in list
list.Remove(42); // will ALWAYS return true (otherwise, assertion failure at the line above)
  1. Unityではリリースビルドでも無理やりAssertionを含めるようにすることはできるが、こういう事故が起こった以上、自分はおすすめしない。

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?