Edited at

UiPath Developer Community 第12回ワークショップ 覚え書き。例外処理のfinally節問題。


概要

UiPath Developer Community 第12回ワークショップ で聞いてきた内容の覚え書き。。

いってきました。第12回。今回は LTへ登壇させてもらったり といろいろあったんですが、いちばん驚いたのが、、下記の例外処理について。


残念なお知らせ(として説明されていた内容)

コレです。

「try/catchアクティビティは、try節、catch節、いずれかを最後まで実行しないと、finally節が呼び出されない」というナゾ仕様のはなし。



いやいや、、、ありえないっしょ、、、。ということでやってみました。



Tryで例外をスローするコード。Finally節がよばれれば「Finally.」が出力されるはずです。

実行してみると、、

うん、ホントだ実行されてない。

ありえない仕様だと思います。。しりませんでした。

try/finallyでなくてtry/catch/finally とした場合も、catchで再度例外をスローすると、try/catchいずれも最後まで実行できていないので、同じ結果となります。これは「例外を再度throw」でも「再スローのアクティビティ」でも「new Exception("hoge",exception)」など別の例外にWrapしてスローしても、結果は同じです。

「try節、catch節、いずれかを最後まで実行しないと、finally節が呼び出されない」ってことは例外が発生しないで正常終了するか、発生しても上(呼び元)に投げない(ってつまり正常終了じゃん) 場合のみfinallyがよばれるってことですよね。。ちょっと意味が。。。ホントに残念なお知らせだと思います。


ちなみにJavaやC#だと

ちなみに、たとえばJavaだと、try/catch のfinally節は基本的に try/catch節がどうなろうと、必ず呼び出されます。


  • 例1: tryを最後まで実行しないとき

public class Main {

public static void main(String[] args) {
System.out.println("UiPathだとFinallyがよばれない例1");
execute1();
}

private static void execute1() {
try {
System.out.println("Start.");
throw new RuntimeException("Error!");
} finally {
System.out.println("Finally.");
}
}
}

実行すると、

Exception in thread "main" 

UiPathだとFinallyがよばれない例1
Start.
Finally.
java.lang.RuntimeException: Error!
at Main.execute1(Main.java:10)
at Main.main(Main.java:4)

うん、当然こうなります。


  • 例2: tryを最後まで実行しない、かつcatchも最後まで実行しない とき

public class Main {

public static void main(String[] args) {
System.out.println("UiPathだとFinallyがよばれない例2");
execute2();
}

private static void execute2() {
try {
System.out.println("Start.");
throw new RuntimeException("Error!");
} catch (RuntimeException e) {
throw e;
} finally {
System.out.println("Finally.");
}
}
}

実行すると、

UiPathだとFinallyがよばれない例2

Exception in thread "main"
Start.
Finally.
java.lang.RuntimeException: Error!
at Main.execute2(Main.java:10)
at Main.main(Main.java:4)

当然C#でも、Javaとおなじ。

using System;

using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ConsoleApp1
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("UiPathだとFinallyがよばれない例1");
execute1();
}

private static void execute1()
{
try
{
Console.WriteLine("Start.");
throw new Exception("Error!");
}
finally
{
Console.WriteLine("Finally.");
//Console.ReadKey();
}
}
}
}

UiPathだとFinallyがよばれない例1

Start.

ハンドルされていない例外: System.Exception: Error!
場所 ConsoleApp1.Program.execute2()
場所 ConsoleApp1.Program.execute1()
場所 ConsoleApp1.Program.Main(String[] args)
Finally.


というわけで対応策

というわけでfinallyをちゃんと実行しつつ上(呼びだし元)にエラーを投げたいときは、catchで例外を投げるのではなく、例外のインスタンスを一旦変数に保管し、finallyもしくはさらにその後で、その例外をスローします。

まずJavaで書くとこんな感じ。

public class Main {

public static void main(String[] args) {
System.out.println("そのための対策をしたコード");
execute3();
}

private static void execute3() {
RuntimeException exception = null;
try {
System.out.println("Start.");
throw new RuntimeException("Error!");
} catch (RuntimeException e) {
exception = e;
} finally {
System.out.println("Finally.");
if (exception != null) {
throw exception;
}
}
}
}

実行してみると、

そのための対策をしたコード

Start.
Finally.
Exception in thread "main" java.lang.RuntimeException: Error!
at Main.execute3(Main.java:11)
at Main.main(Main.java:4)

こういうことですね。

UiPathでやるとこうです。try節は元のワークフローと同じ。トライキャッチのスコープであからじめexception変数を定義しておき、catchでスローされた例外 eを、その変数に格納します。

そしてfinally節で、その変数がnullでなければ「例外が発生していたはず」ということであらためて例外をスローします。

結果は、、、

よさそうですね。


そういえば Attended Frameworkもそうなってた :-)

そういえば、Attended FrameworkもProcess.xaml をtry/catchで囲んでる箇所も、まったく同じ構造になっていました。

Catchでは発生した例外を変数に入れておいて、

finallyで再度スローしてますね。

この処理の流れ、なんでこんなメンドイ流れなのかなー??「正常終了と異常終了(業務とシステム例外)」の3パタンを一箇所に集めるためかなー??って理解してたんですが、まさかfinallyをちゃんと呼べないバグの対応だったとは、、。。

なるほど、色々勉強になりました。


新たなユーザコミュニティについて

新しいユーザコミュニティをつくって、もっともっとユーザの声が届きやすくする動きがあるようですね!

まずは名前を決めましょうということで、名前を投票できるようです。下記のQRからいけるので興味がある方はアクセスしてみてください。

以上です。。今回の記事は、、例外処理の件というUiPath本体外のところが中心になっちゃいました。。

おつかれさまでした。


関連リンク