Kembali dari blok akhirnya di Jawa

176

Saya terkejut baru-baru ini menemukan bahwa mungkin untuk memiliki pernyataan kembali di blok akhirnya di Jawa.

Sepertinya banyak orang berpikir itu hal yang buruk untuk dilakukan seperti dijelaskan dalam ' Jangan kembali pada akhirnya klausa '. Menggaruk sedikit lebih dalam, saya juga menemukan ' Java's return tidak selalu ' yang menunjukkan beberapa contoh yang cukup mengerikan dari jenis kontrol aliran lainnya di akhirnya blok.

Jadi, pertanyaan saya adalah, adakah yang bisa memberi saya contoh di mana pernyataan kembali (atau kontrol aliran lainnya) di blok akhirnya menghasilkan kode yang lebih baik / lebih mudah dibaca?

Matt Sheppard
sumber

Jawaban:

89

Contoh yang Anda berikan adalah alasan yang cukup untuk tidak menggunakan kontrol aliran dari akhirnya.

Bahkan jika ada contoh yang dibuat-buat di mana itu "lebih baik," pertimbangkan pengembang yang harus menjaga kode Anda nanti dan yang mungkin tidak menyadari kehalusannya. Pengembang yang buruk itu bahkan mungkin Anda ....

Jason Cohen
sumber
5
Tentu. Saya kira saya bertanya kalau-kalau ada yang bisa memberi saya contoh yang sangat menarik di sisi baik.
Matt Sheppard
@MattSheppard di daos, saya akan sering mencatat keluar dari kueri pada akhirnya
Blake
147

Saya memiliki waktu yang SANGAT sulit untuk melacak bug tahun lalu yang disebabkan oleh ini. Kode itu seperti:

Object problemMethod() {
    Object rtn = null;
    try {
        rtn = somethingThatThrewAnException();
    }
    finally {
        doSomeCleanup();
        return rtn;
    }
}

Apa yang terjadi adalah bahwa pengecualian dilemparkan ke dalam beberapa kode lainnya. Itu sedang ditangkap dan dicatat dan dipasang kembali dalam somethingThatThrewAnException()metode. Tapi pengecualian itu tidak disebarkan di masa lalu problemMethod(). Setelah waktu yang lama melihat ini, kami akhirnya melacaknya ke metode pengembalian. Metode pengembalian di blok akhirnya pada dasarnya menghentikan pengecualian yang terjadi di blok percobaan dari merambat meskipun tidak tertangkap.

Seperti yang dikatakan orang lain, walaupun legal untuk kembali dari blok akhirnya sesuai dengan spesifikasi Java, itu adalah hal yang BURUK dan tidak boleh dilakukan.

John Meagher
sumber
Di mana kita harus mengembalikannya?
parsecer
@parsecer, saya akan mengatakan tepat setelah memanggil somethingThatThrewAnException () di dalam blok try
Tiago Sippert
@parsecer, ?? Lakukan saja seperti biasa, setelah akhirnya.
Pacerier
21

javac akan memperingatkan kembali akhirnya jika Anda menggunakan -Xlint: akhirnya. Awalnya javac tidak mengeluarkan peringatan - jika ada sesuatu yang salah dengan kode, itu harus gagal dikompilasi. Sayangnya kompatibilitas ke belakang berarti bahwa kebodohan yang tulus dan tak terduga tidak dapat dilarang.

Pengecualian dapat dilemparkan dari blok akhirnya, tetapi dalam kasus itu perilaku yang dipamerkan hampir pasti apa yang Anda inginkan.

Tom Hawtin - tackline
sumber
13

Menambahkan struktur kontrol dan mengembalikan pada akhirnya {} blok hanyalah contoh lain dari "hanya karena Anda bisa" pelanggaran yang tersebar di hampir semua bahasa pengembangan. Jason benar dalam menyarankan itu bisa dengan mudah menjadi mimpi buruk pemeliharaan - argumen terhadap pengembalian awal dari fungsi berlaku lebih-begitu untuk kasus "pengembalian terlambat".

Akhirnya ada blok untuk satu tujuan, untuk memungkinkan Anda merapikan diri Anda sepenuhnya, apa pun yang terjadi di semua kode sebelumnya. Pada dasarnya ini adalah menutup / melepaskan file pointer, koneksi database dll, meskipun saya bisa melihatnya diperluas dengan menambahkan audit dipesan lebih dahulu.

Apa pun yang memengaruhi pengembalian fungsi harus terletak pada blok coba {}. Bahkan jika Anda memiliki metode di mana Anda memeriksa keadaan eksternal, melakukan operasi yang memakan waktu, lalu memeriksa negara itu lagi jika itu menjadi tidak valid, Anda masih ingin pemeriksaan kedua di dalam coba {} - jika itu akhirnya masuk {} dan operasi yang lama gagal, Anda kemudian akan memeriksa keadaan itu untuk kedua kalinya tanpa perlu.

Ian
sumber
6

Tes Groovy sederhana:

public class Instance {

  List<String> runningThreads = new ArrayList<String>()

  void test(boolean returnInFinally) {

    println "\ntest(returnInFinally: $returnInFinally)"
    println "--------------------------------------------------------------------------"
    println "before execute"
    String result = execute(returnInFinally, false)
    println "after execute -> result: " + result
    println "--------------------------------------------------------------------------"

    println "before execute"
    try {
      result = execute(returnInFinally, true)
      println "after execute -> result: " + result
    } catch (Exception ex) {
      println "execute threw exception: " + ex.getMessage()
    }  
    println "--------------------------------------------------------------------------\n"

  }

  String execute(boolean returnInFinally, boolean throwError) {
      String thread = Thread.currentThread().getName()
      println "...execute(returnInFinally: $returnInFinally, throwError: $throwError) - thread: $thread"
      runningThreads.add(thread)
      try {
        if (throwError) {
          println "...error in execute, throw exception"
          throw new Exception("as you liked :-)")
        }
        println "...return 'OK' from execute"
        return "OK"
      } finally {
        println "...pass finally block"
        if (returnInFinally) return "return value from FINALLY ^^"
        // runningThreads.remove(thread)
      }
  }
}

Instance instance = new Instance()
instance.test(false)
instance.test(true)

Keluaran:

test(returnInFinally: false)
-----------------------------------------------------------------------------
before execute
...execute(returnInFinally: false, throwError: false) - thread: Thread-116
...return 'OK' from execute
...pass finally block
after execute -> result: OK
-----------------------------------------------------------------------------
before execute
...execute(returnInFinally: false, throwError: true) - thread: Thread-116
...error in execute, throw exception
...pass finally block
execute threw exception: as you liked :-)
-----------------------------------------------------------------------------


test(returnInFinally: true)
-----------------------------------------------------------------------------
before execute
...execute(returnInFinally: true, throwError: false) - thread: Thread-116
...return 'OK' from execute
...pass finally block
after execute -> result: return value from FINALLY ^^
-----------------------------------------------------------------------------
before execute
...execute(returnInFinally: true, throwError: true) - thread: Thread-116
...error in execute, throw exception
...pass finally block
after execute -> result: return value from FINALLY ^^
-----------------------------------------------------------------------------

Pertanyaan:

Satu hal yang menarik bagi saya adalah untuk melihat bagaimana Groovy berurusan dengan pengembalian implisit. Dalam Groovy dimungkinkan untuk "kembali" dari suatu metode hanya meninggalkan nilai di akhir (tanpa kembali). Menurut Anda apa yang terjadi, jika Anda menghapus tanda komentar pada baris runningThreads.remove (..) pada pernyataan terakhir - apakah ini akan menimpa nilai pengembalian reguler ("OK") dan mencakup pengecualian ?!

Prof. Ondino
sumber
0

Kembali dari dalam finallyblok akan menyebabkan exceptionshilang.

Pernyataan pengembalian di dalam blok akhirnya akan menyebabkan pengecualian yang mungkin dibuang dalam blok coba atau tangkap dibuang.

Menurut Spesifikasi Bahasa Jawa:

Jika eksekusi blok coba selesai tiba-tiba karena alasan lain R, maka blok akhirnya dieksekusi, dan kemudian ada pilihan:

   If the finally block completes normally, then the try statement
   completes  abruptly for reason R.

   If the finally block completes abruptly for reason S, then the try
   statement  completes abruptly for reason S (and reason R is
   discarded).

Catatan: Sesuai JLS 14.17 - pernyataan pengembalian selalu lengkap secara tiba-tiba.

Ankur Lathi
sumber