assert検証
1.4からあったassertをそういえば使ったことがありませんでした。JVMの起動パラメータに-eaがなければ検証ロジックが実行されないので十分テストが終わったメソッドはパフォーマンスがアップするということなのですが、理屈はわかるけどどれくらい効果があるか検証してみることにします。
前提として次のようなメソッドを用意しました。
private static final boolean SWITCH_USE_ASSERT = false; /** * クラスの継承ツリー上に、指定したスーパークラスが存在するか検査する。 * @param clazz 検査対象クラス * @param superClass 継承しているか検査する親クラス * @return true=継承している / false=継承していない */ public static boolean isExtends(Class clazz, Class superClass) { if(SWITCH_USE_ASSERT){ assert clazz != null : "clazzにヌルを指定できません。"; assert superClass != null : "全てのクラスの最上位はnullのため常に戻り値はtrueです。\n" + "superClassにヌルを指定しないでください。"; assert clazz.isInterface() && superClass.isInterface() == false : "interfaceに対してclazz継承を求めました。"; assert clazz.isInterface() == false && superClass.isInterface() : "classに対してinterface継承を求めました。clazz.getInterfacesから検索してください。"; }else{ if (clazz == null) { throw new RuntimeException("clazzにヌルを指定できません。"); } if (superClass == null) { throw new RuntimeException("全てのクラスの最上位はnullのため常に戻り値はtrueです。\n" + "superClassにヌルを指定しないでください。"); } if (clazz.isInterface() && superClass.isInterface() == false) { throw new RuntimeException("interfaceに対してclazz継承を求めました。"); } if (clazz.isInterface() == false && superClass.isInterface()) { throw new RuntimeException( "classに対してinterface継承を求めました。clazz.getInterfacesから検索してください。"); } } boolean result = false; if (superClass.isInterface()) { // ← チェックが通らないとここでヌルポ for(Class i: clazz.getInterfaces()) { if (i == superClass) { result = true; break; } } } while (clazz != null) { if (clazz.getSuperclass() == superClass) { result = true; break; } clazz = clazz.getSuperclass(); } return result; }
呼び出し側のテストは以下。
public class ClassUtilTest extends TestCase { private static final boolean ERROR_CASE = false; public void testTest() throws Throwable { for (int i = 0; i < 100000; i++) { try { // interface継承のテスト if(ERROR_CASE){ assertEquals(true, ClassUtil.isExtends(ClassUtilTest.class, null)); }else{ assertEquals(true, ClassUtil.isExtends(ClassUtilTest.class, Assert.class)); } } catch(Throwable e) { if (i == 9999) { throw e; } } } }
これに対して、SWITCH定数を切り替えつつ、-eaを切り替えつつ、各ケースで掛かった時間を記録しました。結果は以下のようになりました。
ERROR_CASE | SWITCH_USE_ASSERT | -eaオプション | 時間(ミリ秒。5回計測) |
---|---|---|---|
false | false | あり | 141 140 141 140 141 |
false | false | なし | 141 140 140 141 141 |
false | true | あり | 125 125 140 141 125 |
false | true | なし | 109 109 093 094 109 |
true | false | あり | 094 109 094 110 094 |
true | false | なし | 109 094 094 093 109 |
true | true | あり | 125 125 125 141 125 |
true | true | なし | 297 296 297 281 297 |
正常時はチェックルートだけの差になりますが、assertの方が若干早いようです。 -ea オプションをはずすとチェックがなくなるのでさらに早くなっているようです。しかし、例外が起きるケースでは逆転しています。例外を起こす内部のメカニズムは定かではありませんが、AssertionFailedの方が例外処理に時間が掛かっているように見えます。さらに、最後のケースでは最もコストが掛かっています。最後のケースだけはNullPointerExceptionが発生しているケースです。
どちらが良いかか判断しかねる結果に終わりました。外部とインターフェースがなく、開発時にしか起こりえないインターナルなプログラムについてはassertをつければ良いかなと思ったんですが、経験上そういう「絶対ありえない」と思うところこそ、運用時に不具合が出たりするもんです。運用時に -ea はずしていたばかりに原因を追うのにえらい苦労するなんてことになりかねないので、オプションに左右されない今までのスタイルは変えないほうが良いなぁというところに個人的には落ち着きました。
他のところではどうなんでしょうかね。