[Java] JavaSE 7 Try-with-Resources Refactoring Hints

原文はこちら。
https://blogs.oracle.com/binkyscave/entry/javase_7_try_with_resources

最近、Oracle Technical Network Java Developer Daysで実施するJava SE 7のHands-onコンテンツのアップグレードを完了しました。このHands-onの目的の一つとして、Project CoinとNIO2といったJava SE 7の新機能を参加者に伝えることがあります。その他の目的は(これはちょっと微妙な目的ではありますが)、NetBeans IDEとそのリファクタリング機能をお伝えするというものです。さらに、アップデートの発表の最近のJavaSE 6のアップデート終了の発表に伴い、Java SE 7にアップグレードするのに併せてご自身のコードをアップグレードすることを考えてらっしゃる方に対し、リファクタリングのヒントをご案内するよい機会というのもあります。

まずはじめに

Java SE 7以前のHands-onの元々の課題は以下のようでした。
private static void tryWithResource6c() {
        System.out.println("TESTING TRY WITH RESOURCES 6c");
        UglyCloser closer1 =  new UglyCloser("First closer");
        UglyCloser closer2 =  new UglyCloser("Second closer");
        try {
            fakeAssert(!closer1.isClosed(), "Closer 1 should be open");
            fakeAssert(!closer2.isClosed(), "Closer 2 should be open");
            closer1.close();
            closer2.close();
        } catch (Exception ex) {
            // quietly ignore
        }
        fakeAssert(closer1.isClosed(), "Closer1 should be closed");
        fakeAssert(closer2.isClosed(), "Closer1 should be closed");
    }
「try-with-resourcesの形式に変換」する候補であることを示すリファクタリングのヒント(黄色の三角形”!”のアイコンを持つ小さな電球)が、コードの行番号の隣に出てくるものと思っていましたが、そのようなアイコンが現れませんでした。リファクタリングが推奨されるコードの種々のパターンを試す必要があるようなので、上記コードのいくつかのバリエーションでリファクタリングする方法の○と×を確認してみました。

例1:
以下のコードでは、リファクタリングのヒントが現れます。
private static void tryWithResource2a() {
         System.out.println("TESTING TRY WITH RESOURCES");
         UglyCloser closer1 =  new UglyCloser("First closer");
         try {
             fakeAssert(!closer1.isClosed(), "Closer 1 should be open");
         } catch (Exception ex) {
             // quietly ignore
         } finally {
             closer1.close();
         }

      }
期待通りに、以下のようにリファクタリングされます。
private static void tryWithResource6a() throws Exception {
        System.out.println("TESTING TRY WITH RESOURCES 6A");
        try (UglyCloser closer1 = new UglyCloser("First closer")) {
            fakeAssert(!closer1.isClosed(), "Closer 1 should be open");
        } catch (Exception ex) {
            // quietly ignore
        }

    }
例2:
しかし、try-finallyブロック外でオブジェクト参照している場合はリファクタリングされません。まぁ、わからなくもないです。
private static void tryWithResource2() {
         System.out.println("TESTING TRY WITH RESOURCES");
         UglyCloser closer1 =  new UglyCloser("First closer");
         try {
             fakeAssert(!closer1.isClosed(), "Closer 1 should be open");
         } catch (Exception ex) {
             // quietly ignore
         } finally {
             closer1.close();
         }
         fakeAssert(closer1.isClosed(), "Closer1 should be closed");
      }
ここで重要なのは、現在のtry-with-resourceの形式では新しいリソース変数を使うということです。try-finallyの外部で変数への参照がある場合、こうした参照はスコープ外になります。この例は、実世界のコードで存在する可能性は低いのですが、コード変換時にスコープ外のリソース変数を探すことを考慮すべきでしょう。

例3:
奇妙なことに、finally節を削除するとリファクタリングされません。closer1を永久に閉じていないので、これはおそらくまずいコードだと思うのですが…
private static void tryWithResource2b() {
         System.out.println("TESTING TRY WITH RESOURCES");
         UglyCloser closer1 =  new UglyCloser("First closer");
         try {
             fakeAssert(!closer1.isClosed(), "Closer 1 should be open");
         } catch (Exception ex) {
             // quietly ignore
         }
      }
この例ではfinally節にcloseがなく、実際にコード・セマンティクスの変更があります。Java SE 6以前では、自動クローズ機能がないということを忘れないで下さい。おそらく、オリジナルコードのプログラマーは間違えてcloseを呼び出さなかったか、もしくは意図的にcloseを使わずに脱出していたのでしょう。いずれの場合においても、さらなる分析が必要です。

例4:
同様に、try文でオブジェクトをcloseすると、closeされません。
private static void tryWithResource2b() {
         System.out.println("TESTING TRY WITH RESOURCES");
         UglyCloser closer1 =  new UglyCloser("First closer");
         try {
             fakeAssert(!closer1.isClosed(), "Closer 1 should be open");
             closer1.close();
         } catch (Exception ex) {
             // quietly ignore
         }
      }
この例では、プログラマが何を考えているのかわかりません。プログラマー側でcloseを呼び出そうとして失敗したのかもしれないし、tryの中にcloseがあることが重要なのかもしれません。この時点では選択の余地がありませんが、リファクタリングの際には、プログラマがどうしたかったのかを考慮する必要があります。

例5:
最後に、try-catchブロック内で複数のオブジェクトを取り扱う場合、一つだけがリファクタリングされますが、これはおそらくNetBeansのバグでしょう。もちろん、上記のすべての単一バリアントの中断も適用されます。
private static void tryWithResource1a() {
         System.out.println("TESTING TRY WITH RESOURCES");
         UglyCloser closer1 =  new UglyCloser("First closer");
         UglyCloser closer2 =  new UglyCloser("Second closer");
         try {
             fakeAssert(!closer1.isClosed(), "Closer 1 should be open");
             fakeAssert(!closer2.isClosed(), "Closer 2 should be open");
         } catch (Exception ex) {
             // quietly ignore
         } finally {
             closer1.close();
             closer2.close();
         }
     }
以下のようにリファクタリングします。
private static void tryWithResource6b() throws Exception {
        System.out.println("TESTING TRY WITH RESOURCES 6a");
        UglyCloser closer1 =  new UglyCloser("First closer");
        try (UglyCloser closer2 = new UglyCloser("Second closer")) {
            fakeAssert(!closer1.isClosed(), "Closer 1 should be open");
            fakeAssert(!closer2.isClosed(), "Closer 2 should be open");
        } catch (Exception ex) {
            // quietly ignore
        } finally {
            closer1.close();
        }
    }
これはcloser1がリファクタリングされておらず、部分的なリファクタリングです。おそらくNetBeansのバグではないかと思いますが、もしtry-with-resourcesを最大限に活用したい場合には、手作業でのリファクタリングを追加する必要があります。

新しいHands-onでは…

Hands-onのコンテンツのために、try-catchブロックのリファクタリングで予想される3種類のリファクタリング体験をご紹介しましょう。

1. 完全なリファクタリング
private static void tryWithResource6a() throws Exception {
        System.out.println("TESTING TRY WITH RESOURCES 6A");
        UglyCloser closer1 =  new UglyCloser("First closer");
        try {
            fakeAssert(!closer1.isClosed(), "Closer 1 should be open");
        } catch (Exception ex) {
            // quietly ignore
        } finally {
            closer1.close();
        }
     } 
以下のようにリファクタリングされます。
private static void tryWithResource6a() throws Exception {
        System.out.println("TESTING TRY WITH RESOURCES 6A");
        try (UglyCloser closer1 = new UglyCloser("First closer")) {
            fakeAssert(!closer1.isClosed(), "Closer 1 should be open");
        } catch (Exception ex) {
            // quietly ignore
        }
    }
2. 部分的なリファクタリング
private static void tryWithResource6a() throws Exception {
        System.out.println("TESTING TRY WITH RESOURCES 6A");
        UglyCloser closer1 =  new UglyCloser("First closer");
        try {
            fakeAssert(!closer1.isClosed(), "Closer 1 should be open");
        } catch (Exception ex) {
            // quietly ignore
        } finally {
            closer1.close();
        }
     } 
以下のような不完全なリファクタリングではなく
private static void tryWithResource6a() throws Exception {
        System.out.println("TESTING TRY WITH RESOURCES 6A");
        try (UglyCloser closer1 = new UglyCloser("First closer")) {
            fakeAssert(!closer1.isClosed(), "Closer 1 should be open");
        } catch (Exception ex) {
            // quietly ignore
        }
     } 
手作業で以下のように変更する必要があります。
private static void tryWithResource6b() throws Exception {
        System.out.println("TESTING TRY WITH RESOURCES 6b");
        try (UglyCloser closer1 = new UglyCloser("First closer");
                UglyCloser closer2 = new UglyCloser("Second closer")) {
            fakeAssert(!closer1.isClosed(), "Closer 1 should be open");
            fakeAssert(!closer2.isClosed(), "Closer 2 should be open");
        } catch (Exception ex) {
            // quietly ignore
        }
    }
3. リファクタリングしない
private static void tryWithResource6c() {
        System.out.println("TESTING TRY WITH RESOURCES 6c");
        UglyCloser closer1 =  new UglyCloser("First closer");
        UglyCloser closer2 =  new UglyCloser("Second closer");
        try {
            fakeAssert(!closer1.isClosed(), "Closer 1 should be open");
            fakeAssert(!closer2.isClosed(), "Closer 2 should be open");
            closer1.close();
            closer2.close();
        } catch (Exception ex) {
            // quietly ignore
        }
        fakeAssert(closer1.isClosed(), "Closer1 should be closed");
        fakeAssert(closer2.isClosed(), "Closer1 should be closed");
    }
手作業で以下のようにリファクタリングする必要があります。
private static void tryWithResource6b() throws Exception {
        System.out.println("TESTING TRY WITH RESOURCES 6b");
        try (UglyCloser closer1 = new UglyCloser("First closer");
                UglyCloser closer2 = new UglyCloser("Second closer")) {
            fakeAssert(!closer1.isClosed(), "Closer 1 should be open");
            fakeAssert(!closer2.isClosed(), "Closer 2 should be open");
        } catch (Exception ex) {
            // quietly ignore
        }
    }

まとめ

このHands-onで使われない部分ですが、ご自身のコードのtry-catchブロックをJava SE 7用にリファクタリングする必要が出てきた場合、結果を確認する必要があります。あなたは幸運にも完全なリファクタリングを実施しているかもしれません。リソースを1個だけ使用している場合は一般的に正しくリファクタリングされているでしょうが、複数のリソースを使用している場合、ツールによるリファクタリングに対し、手作業の追加が必要になるでしょう。try-catchブロックのスコープ外のリソースの場合は、手作業でのリファクタリングが必要になるでしょう。

0 件のコメント:

コメントを投稿