第72回 log4perl

ログ出力のためのモジュール

残念な事にうまく説明する事ができないのだが、
log4perlというログ出力にとても便利なものがある。


設定方法などは、ここ↓に、簡単にであるが書いてある。
ビンゴ中西のほげほげlog4perl


今回は、簡単な説明図を載せることにする。
なお、リンク先は、2つのアペンダを使っているが、
下の図は、簡単にするため、1つのアペンダだけを用いたときの図になっている。


例外とログとテスト

うまくまだ自分でまとめられないのだが、

例外は「業務エラー」と「システムエラー」に別けられることができる。
さらに、そのそれぞれのエラーに対して、「ユーザ向けメッセージ」と「運用時向けメッセージ」と「デバッグ時向けメッセージ」が必要になる。
なお、「運用時向けメッセージ」と「デバッグ向けメッセージ」は同じになる場合もあると考えられる。
テストにおいては、「正常系」と「異常系」を考え、ちゃんと例外を処理できている(ハンドリングできている)つまり、「ユーザ向けメッセージ」と「運用時向けメッセージ」と「デバッグ時向けメッセージ」がうまく生成されていることを確認する必要がある(もちろんこれ以外にも確認する内容はある)。


このように、
例外とログとテストは
密接に関係しているのだが、今の私には体系だって説明できないし、
説明されている本やページも見た事がない。
どなたかご存知だったら本当に教えてほしい。




メッセージのパターンを如何に細かくするのか(「SQLに失敗しました」だけか、「INSERTに失敗しました」「UPDATEに失敗しました」と細かく出すのか ※これはユーザ向けメッセージなのでログとは少し違ってくるのだが...)。
などなど考えることも、とりかかる案件によりさまざまに考える必要があるだろう。


またエラーとは関係なく、関数に入るたびにログに出力する。関数から出るたびにログに出力する。
といったことをして、いったい何時にどの処理が走っていたのかをログっておくといったことも考えないといけないし、このあたりは奥深いというか考えないといけないことが多い。

第70回 Javaの例外処理の構造(文法的なお話) その1

Javaの例外処理はかなりきっちりめにできており、
例外が起こった場合の処理を書いておかないとコンパイラさえ通らない。

なお、大きくわけて例外は、処理しなければならない例外と、処理しなくてもいい例外の2つに別けることができる。
処理しなければならない例外を処理しなければコンパイラが通らないという訳である。


今回は、処理しなければならない例外に関しての話を進めていく。
(私の確認もかねている)

例外を起こすメソッド

今、badMethodという内部で例外を起こすメソッドを考える。
これは、

private static void badMethod() throws Exception {
	throw new Exception(); 
}

このように書ける。


これをmainメソッドから呼んでいるのが次の通り

public class Hoge {

	public static void main(String[] args) {
		badMethod();
	}

	private static void badMethod() throws Exception {
		throw new Exception(); 
	}
}

この badMethodをtryしてやることで、例外を処理する。
上記のままではコンパイルが通らないのだ。

public class Hoge {

	public static void main(String[] args) {
		try {
			badMethod();
		} catch (Exception e) {
			System.out.println("例外処理った");
			e.printStackTrace();
		}
	}

	private static void badMethod() throws Exception {
		throw new Exception(); 
	}
}

結果:

例外処理った
java.lang.Exception
	at Hoge.badMethod(Hoge.java:14)
	at Hoge.main(Hoge.java:6)

e.printStackTrace() ってなに?

e.printStackTrace()とは何か気になるところだろう。
eというのはExceptionクラスのインスタンスである。
だから、

e.printStackTrace()

というのは、ExceptionのprintStackTraceメソッドを呼んでいるだけに他ならない。
このメソッドが何をしたかというと。

java.lang.Exception
	at Hoge.badMethod(Hoge.java:14)
	at Hoge.main(Hoge.java:6)

この部分を表示しており、何行目で例外が発生したのかを示している。
例外が発生したのは、badMethodの中でそれは14行目である。さらにmainメソッドの6行目にそれがthrowされているのが読み取れる。


では、なぜeにExceptionのインスタンスが入ったかと言うと次の図を見て欲しい。

badMethodの中では、

throw new Exception(); 

と書かれている。
newなので新しいインスタンスが作られているのである。
throwはそれを呼びだしもとに返しているのである(「投げている」と表現した方がすっきりする)。

この投げられたExceptionのインスタンスがtryで囲まれていると、それが、

} catch (Exception e) {

のeに入るのである。これは代入されたとも考えられる。



PerlのError.pmを用いれば、この代入されている勘がもろに味わえるだろう。

use strict;
use Error qw(:try);

try{
  die 'OH';
} otherwise {
  my $e = shift;
  print $e;
};

結果:

OH at e.pl line 5.

第71回 Javaの例外処理の構造(文法的なお話) その2

それでは、try{ } catch { } の構造を使うと、
どのようにプログラムが流れていくのか見てみよう。

public class Hoge {

	public static void main(String[] args) {
		try {
			badMethod();
			System.out.println("例外起こらないでー");
		} catch (Exception e) {
			System.out.println("例外処理った");
			e.printStackTrace();
		}
		System.out.println("ここまでこれるかな");
	}

	private static void badMethod() throws Exception {
		throw new Exception(); 
	}
}

結果:

例外処理った
java.lang.Exception
	at Hoge.badMethod(Hoge.java:16)
	at Hoge.main(Hoge.java:6)
ここまでこれるかな

おわかりだろうか?

System.out.println("例外起こらないでー");

の部分が実行されていないのである。
tryの中で例外が発生すると(今回は、badMethod();の部分)それより以下の行は処理されない(すっとばされるのである)。

finally

ちなみにfinallyというものが存在する。

catchは例外が起こったときに処理される部分であるが、
finallyは、例外が起こったときも起こらなかったときも、最後に必ず実行されるのである。


それでは、例外が起こった場合からみてみよう。

public class Hoge {

	public static void main(String[] args) {
		try {
			badMethod();
			System.out.println("例外起こらないでー");
		} catch (Exception e) {
			System.out.println("例外処理った");
			e.printStackTrace();
		} finally {
			System.out.println("Hey!! みんなのfinally");
		}
		System.out.println("ここまでこれるかな");
	}

	private static void badMethod() throws Exception {
		throw new Exception(); 
	}
}

結果:

例外処理った
java.lang.Exception
	at Hoge.badMethod(Hoge.java:18)
	at Hoge.main(Hoge.java:6)
Hey!! みんなのfinally
ここまでこれるかな

では、次に例外が起こらなかった場合である。

public class Hoge {

	public static void main(String[] args) {
		try {
			badMethod();
			System.out.println("例外起こらないでー");
		} catch (Exception e) {
			System.out.println("例外処理った");
			e.printStackTrace();
		} finally {
			System.out.println("Hey!! みんなのfinally");
		}
		System.out.println("ここまでこれるかな");
	}

	private static void badMethod() throws Exception {
		// throw new Exception();  コメントアウトした 
	}
}

結果:

例外起こらないでー
Hey!! みんなのfinally
ここまでこれるかな

このように、どちらの場合もfinallyの中は実行されているのがわかる。


finallyは必要なのか?

それでは、

try{
 A
} catch {
 B
} finally {
 F
}
 D

と書くのも、

try {
 A
} catch {
 B
}
F
D

と書くのも同じように思えるが、
次のような違いがあるようだ。

Javaの道>掲示板(finallyの使い道)


なるほど。例外をさらに上に投げるときにfinallyは使えるらしい。
やってみよう。

public class Hoge {

	public static void main(String[] args) {
		try {
			giveUp();
			System.out.println("main() : 例外起こらないでー");
		} catch (Exception e) {
			System.out.println("main() : 例外処理った");
			e.printStackTrace();
		} 
	}

	private static void badMethod() throws Exception {
		throw new Exception();
	}
	
	private static void giveUp() throws Exception {
		try{
			badMethod();
		} catch (Exception e) {
			System.out.println("giveUp() : catchした!");
			throw e;
		} finally {
			System.out.println("giveUp() : finally!!");
		}
		System.out.println("giveUp() : 例外処理後");
	}
	
}

新たに、giveUpメソッドを用意し、
そのgiveUpメソッドがbadMethodを呼んでいるが、自分では結局処理せず、
呼び出し元に任せている(throwしている)。


結果:

giveUp() : catchした!
giveUp() : finally!!
main() : 例外処理った
java.lang.Exception
	at Hoge.badMethod(Hoge.java:15)
	at Hoge.giveUp(Hoge.java:20)
	at Hoge.main(Hoge.java:6)

たしかに、呼び出し元に投げているにもかかわらず、
投げる前に、finallyが実行されている。



では、Error.pmはどうかというと、

use strict;
use Error qw(:try);

sub bad_sub {
  die 'OH';
}

sub give_up {
  try {
    bad_sub();
  } otherwise {
    my $e = shift;

    print 'give_up() :  catchした', "\n";
    die $e;
  } finally {
    print 'geve_up() :  finally', "\n";
  };
}


try {
  give_up();
} otherwise {
  my $e = shift;

  print 'main() : 例外処理った', "\n";
  print $e;
};

結果:

give_up() :  catchした
geve_up() :  finally
main() : 例外処理った
OH at e.pl line 5.

確かに、Error.pmもそのような挙動をする。



ちなみに、上に投げる方法は、tryを書かずに

use strict;
use Error qw(:try);

sub bad_sub {
  die 'OH';
}

sub give_up {
  bad_sub();
  print 'ここは通らない', "\n";
}


try {
  give_up();
} otherwise {
  my $e = shift;

  print 'main() : 例外処理った', "\n";
  print $e;
};

結果:

main() : 例外処理った
OH at e.pl line 5.

このようにも書けるが、これはあまりよいとは思えないような気がする。
が、どうなんだろう.....