Mengapa exception.printStackTrace () dianggap sebagai praktik buruk?

126

Ada banyak dari bahan luar sana yang menunjukkan bahwa mencetak jejak tumpukan pengecualian adalah praktek yang buruk.

Misalnya dari pemeriksaan RegexpSingleline di Checkstyle:

Pemeriksaan ini dapat digunakan [...] untuk menemukan praktik buruk umum seperti memanggil ex.printStacktrace ()

Namun, saya berjuang untuk menemukan di mana saja yang memberikan alasan yang sah mengapa karena tentu saja jejak tumpukan sangat berguna dalam melacak apa yang menyebabkan pengecualian. Hal-hal yang saya ketahui:

  1. Jejak tumpukan tidak boleh terlihat oleh pengguna akhir (untuk pengalaman pengguna dan tujuan keamanan)

  2. Menghasilkan jejak tumpukan adalah proses yang relatif mahal (meskipun tidak mungkin menjadi masalah dalam keadaan paling 'luar biasa')

  3. Banyak kerangka kerja pencatatan akan mencetak jejak tumpukan untuk Anda (milik kami tidak dan tidak, kami tidak dapat mengubahnya dengan mudah)

  4. Mencetak jejak tumpukan bukan merupakan penanganan kesalahan. Ini harus dikombinasikan dengan pencatatan informasi dan penanganan pengecualian lainnya.

Apa alasan lain yang ada untuk menghindari pencetakan jejak tumpukan dalam kode Anda?

Chris Knight
sumber
12
Sebagai seseorang yang harus memecahkan masalah secara teratur, saya tidak akan pernah menghilangkan pencetakan stacktrace ketika ada masalah. Ya, jangan tampilkan ke pengguna, tapi ya, buang ke log kesalahan.
Paul Grime
4
Apakah Anda benar-benar membutuhkan alasan lain? Saya pikir Anda telah menjawab pertanyaan Anda dengan cukup baik. Namun, stacktrace harus dicetak dalam pengecualian luar biasa. :)
Neil

Jawaban:

125

Throwable.printStackTrace()menulis jejak stack ke System.errPrintStream. The System.errsungai dan mendasari standar "error" output stream dari proses JVM dapat diarahkan oleh

  • meminta System.setErr()perubahan tujuan yang ditunjukkan oleh System.err.
  • atau dengan mengarahkan aliran output kesalahan proses. Aliran output kesalahan dapat dialihkan ke file / perangkat
    • yang isinya dapat diabaikan oleh personil,
    • file / perangkat mungkin tidak dapat melakukan rotasi log, menyimpulkan bahwa proses restart diperlukan untuk menutup pegangan file / perangkat yang terbuka, sebelum mengarsipkan konten file / perangkat yang ada.
    • atau file / perangkat tersebut benar-benar membuang semua data yang ditulis kepadanya, seperti halnya /dev/null.

Sebagai kesimpulan dari hal di atas, memohon hanya Throwable.printStackTrace()merupakan perilaku penanganan pengecualian yang valid (tidak baik / hebat)

  • jika Anda tidak harus System.errdipindahkan selama masa aplikasi,
  • dan jika Anda tidak memerlukan rotasi log saat aplikasi sedang berjalan,
  • dan jika praktik penebangan yang diterima / dirancang aplikasi adalah untuk menulis System.err(dan aliran output kesalahan standar JVM).

Dalam kebanyakan kasus, kondisi di atas tidak memuaskan. Seseorang mungkin tidak menyadari kode lain yang berjalan di JVM, dan orang tidak dapat memprediksi ukuran file log atau durasi runtime proses, dan praktik logging yang dirancang dengan baik akan berputar di sekitar menulis file log "machine-parseable" (a lebih disukai tetapi fitur opsional dalam logger) di tujuan yang dikenal, untuk membantu dalam dukungan.

Akhirnya, orang harus ingat bahwa output dari Throwable.printStackTrace()pasti akan disisipkan dengan konten lain yang ditulis System.err(dan mungkin bahkan System.outjika keduanya diarahkan ke file / perangkat yang sama). Ini adalah gangguan (untuk aplikasi single-threaded) yang harus dihadapi, karena data di sekitar pengecualian tidak mudah diuraikan dalam peristiwa semacam itu. Lebih buruk lagi, sangat mungkin bahwa aplikasi multi-threaded akan menghasilkan log yang sangat membingungkan karena Throwable.printStackTrace() tidak aman untuk thread .

Tidak ada mekanisme sinkronisasi untuk menyinkronkan penulisan jejak stack System.errsaat beberapa utas meminta secara Throwable.printStackTrace()bersamaan. Menyelesaikan ini sebenarnya membutuhkan kode Anda untuk melakukan sinkronisasi pada monitor yang terkait dengan instance. Jika Anda ingin memiliki jaminan yang sama untuk menggunakan catatan log non-interleavedSystem.err (dan juga System.out, jika file / perangkat tujuan sama), dan itu adalah harga yang cukup mahal untuk membayar kewarasan file log. Untuk mengambil contoh, kelas ConsoleHandlerdan StreamHandlerbertanggung jawab untuk menambahkan catatan log ke konsol, di fasilitas logging yang disediakan oleh java.util.logging; operasi aktual penerbitan catatan log disinkronkan - setiap utas yang berupaya untuk mempublikasikan catatan log juga harus mendapatkan kunci pada monitor yang terkait denganStreamHandlerSystem.out / System.err, Anda harus memastikan hal yang sama - pesan diterbitkan ke stream ini secara serial.

Mempertimbangkan semua hal di atas, dan skenario yang sangat terbatas di mana Throwable.printStackTrace()sebenarnya berguna, seringkali ternyata memohon itu adalah praktik yang buruk.


Memperluas argumen di salah satu paragraf sebelumnya, itu juga merupakan pilihan yang buruk untuk digunakan Throwable.printStackTracebersama dengan logger yang menulis ke konsol. Ini sebagian, karena alasan bahwa logger akan menyinkronkan pada monitor yang berbeda, sementara aplikasi Anda akan (mungkin, jika Anda tidak ingin catatan log yang disisipkan) melakukan sinkronisasi pada monitor yang berbeda. Argumen ini juga berlaku ketika Anda menggunakan dua penebang berbeda yang menulis ke tujuan yang sama, dalam aplikasi Anda.

Vineet Reynolds
sumber
Jawaban yang bagus, terima kasih. Namun, sementara saya setuju bahwa sebagian besar waktu itu berisik atau tidak perlu, ada saat-saat ketika itu benar-benar kritis.
Chris Knight
1
@ Chris Ya, ada saatnya Anda tidak bisa tidak menggunakan System.out.printlndan Throwable.printStackTracedan tentu saja, penilaian pengembang diperlukan. Saya agak khawatir bahwa bagian tentang keamanan benang tidak terjawab. Jika Anda melihat sebagian besar implementasi logger, Anda akan melihat bahwa mereka menyinkronkan bagian di mana catatan log ditulis (bahkan ke konsol), meskipun mereka tidak mendapatkan monitor pada System.erratau System.out.
Vineet Reynolds
2
Apakah saya salah untuk mencatat bahwa tidak ada alasan di atas yang berlaku pada printStackTrace yang menggantikan objek PrintStream atau PrintWriter sebagai parameter?
ESRogs
1
@ Geek, yang terakhir adalah deskriptor file proses dengan nilai 2. Yang pertama hanya sebuah abstraksi.
Vineet Reynolds
1
Dalam kode sumber JDK printStackTrace (), itu menggunakan disinkronkan untuk mengunci PrintStream System.err, sehingga harus menjadi metode yang aman.
EyouGo
33

Anda menyentuh beberapa masalah di sini:

1) Jejak tumpukan tidak boleh terlihat oleh pengguna akhir (untuk pengalaman pengguna dan tujuan keamanan)

Ya, harus dapat diakses untuk mendiagnosis masalah pengguna akhir, tetapi pengguna akhir tidak boleh melihatnya karena dua alasan:

  • Mereka sangat tidak jelas dan tidak dapat dibaca, aplikasi akan terlihat sangat tidak ramah pengguna.
  • Menampilkan jejak tumpukan kepada pengguna akhir dapat menimbulkan risiko keamanan potensial. Koreksi saya jika saya salah, PHP benar-benar mencetak parameter fungsi dalam jejak tumpukan - brilian, tetapi sangat berbahaya - jika Anda mendapatkan pengecualian saat menyambungkan ke database, apa yang mungkin Anda lakukan dalam stacktrace?

2) Menghasilkan tumpukan jejak adalah proses yang relatif mahal (meskipun tidak mungkin menjadi masalah dalam sebagian besar keadaan 'pengecualian')

Menghasilkan jejak tumpukan terjadi ketika pengecualian sedang dibuat / dilempar (itu sebabnya melempar pengecualian datang dengan harga), pencetakan tidak terlalu mahal. Bahkan Anda dapat mengganti Throwable#fillInStackTrace()pengecualian kustom Anda secara efektif membuat melemparkan pengecualian hampir semurah pernyataan GOTO sederhana.

3) Banyak kerangka kerja logging akan mencetak jejak tumpukan untuk Anda (milik kami tidak dan tidak, kami tidak dapat mengubahnya dengan mudah)

Poin yang sangat bagus. Masalah utama di sini adalah: jika framework mencatat pengecualian untuk Anda, jangan lakukan apa-apa (tapi pastikan ya!) Jika Anda ingin mencatat pengecualian sendiri, gunakan framework logging seperti Logback atau Log4J , untuk tidak meletakkannya di konsol mentah karena sangat sulit untuk mengendalikannya.

Dengan kerangka logging, Anda dapat dengan mudah mengarahkan jejak stack ke file, konsol atau bahkan mengirimnya ke alamat email tertentu. Dengan hardcoded printStackTrace()Anda harus hidup dengan sysout.

4) Mencetak jejak tumpukan bukan merupakan penanganan kesalahan. Ini harus dikombinasikan dengan pencatatan informasi dan penanganan pengecualian lainnya.

Sekali lagi: masuk SQLExceptiondengan benar (dengan jejak tumpukan penuh, menggunakan kerangka logging) dan tunjukkan bagus: " Maaf, kami saat ini tidak dapat memproses permintaan Anda " pesan. Apakah Anda benar-benar berpikir pengguna tertarik pada alasannya? Pernahkah Anda melihat layar kesalahan StackOverflow? Ini sangat lucu, tetapi tidak mengungkapkan detail apa pun . Namun itu memastikan pengguna bahwa masalahnya akan diselidiki.

Tetapi dia akan segera menghubungi Anda dan Anda harus dapat mendiagnosis masalahnya. Jadi, Anda membutuhkan keduanya: pencatatan pengecualian yang tepat dan pesan yang ramah pengguna.


Untuk menyelesaikannya: selalu catat pengecualian (lebih disukai menggunakan kerangka logging ), tetapi jangan memaparkannya kepada pengguna akhir. Pikirkan baik-baik dan tentang pesan kesalahan di GUI Anda, tampilkan jejak tumpukan hanya dalam mode pengembangan.

Tomasz Nurkiewicz
sumber
jawaban Anda adalah yang saya dapatkan.
Blasanka
"sebagian besar 'keadaan pengecualian'". bagus.
awwsmm
19

Hal pertama printStackTrace () tidak mahal seperti yang Anda sebutkan, karena jejak stack diisi ketika pengecualian dibuat sendiri.

Idenya adalah untuk melewatkan apa pun yang masuk ke log melalui kerangka logger, sehingga logging dapat dikontrol. Karenanya alih-alih menggunakan printStackTrace, cukup gunakan sesuatu sepertiLogger.log(msg, exception);

Suraj Chandran
sumber
11

Mencetak jejak stack pengecualian itu sendiri bukan merupakan praktik yang buruk, tetapi hanya mencetak jejak stace ketika pengecualian terjadi mungkin masalah di sini - sering kali, hanya mencetak jejak stack tidak cukup.

Juga, ada kecenderungan untuk mencurigai bahwa penanganan pengecualian yang tepat tidak dilakukan jika semua yang dilakukan dalam suatu catchblok adalah a e.printStackTrace. Penanganan yang tidak tepat bisa berarti masalah yang terbaik sedang diabaikan, dan paling buruk program yang terus dijalankan dalam keadaan yang tidak ditentukan atau tidak terduga.

Contoh

Mari kita perhatikan contoh berikut:

try {
  initializeState();

} catch (TheSkyIsFallingEndOfTheWorldException e) {
  e.printStackTrace();
}

continueProcessingAssumingThatTheStateIsCorrect();

Di sini, kami ingin melakukan beberapa pemrosesan inisialisasi sebelum melanjutkan ke beberapa pemrosesan yang mengharuskan inisialisasi dilakukan.

Dalam kode di atas, pengecualian seharusnya ditangkap dan ditangani dengan benar untuk mencegah program melanjutkan ke continueProcessingAssumingThatTheStateIsCorrect metode yang dapat kita asumsikan akan menyebabkan masalah.

Dalam banyak kasus, e.printStackTrace()merupakan indikasi bahwa beberapa pengecualian sedang ditelan dan pemrosesan diizinkan untuk melanjutkan seolah-olah tidak ada masalah setiap terjadi.

Mengapa ini menjadi masalah?

Mungkin salah satu alasan terbesar bahwa penanganan pengecualian yang buruk menjadi lebih umum adalah karena bagaimana IDE seperti Eclipse akan secara otomatis menghasilkan kode yang akan melakukan e.printStackTracepenanganan pengecualian:

try {
  Thread.sleep(1000);
} catch (InterruptedException e) {
  // TODO Auto-generated catch block
  e.printStackTrace();
}

(Di atas adalah yang try-catchdihasilkan secara otomatis oleh Eclipse untuk menangani yang InterruptedExceptiondilemparkan olehThread.sleep .)

Untuk sebagian besar aplikasi, hanya mencetak jejak stack ke kesalahan standar mungkin tidak akan cukup. Dalam banyak kasus, penanganan pengecualian yang tidak benar dapat menyebabkan aplikasi berjalan dalam keadaan yang tidak terduga dan dapat mengarah pada perilaku yang tidak terduga dan tidak terdefinisi.

coobird
sumber
1
Ini. Cukup sering tidak menangkap pengecualian sama sekali, setidaknya pada tingkat metode, lebih baik daripada menangkap, melakukan cetak jejak tumpukan dan melanjutkan seolah-olah tidak ada masalah terjadi.
eis
10

Saya pikir daftar alasan Anda cukup komprehensif.

Salah satu contoh buruk yang saya temui lebih dari sekali adalah seperti ini:

    try {
      // do stuff
    } catch (Exception e) {
        e.printStackTrace(); // and swallow the exception
    }

Masalah dengan kode di atas adalah bahwa penanganan seluruhnya terdiri dari printStackTracepanggilan: pengecualian tidak benar-benar ditangani dengan baik dan tidak diizinkan untuk melarikan diri.

Di sisi lain, sebagai aturan saya selalu mencatat jejak stack setiap kali ada pengecualian yang tidak terduga dalam kode saya. Selama bertahun-tahun kebijakan ini telah menyelamatkan saya banyak waktu debugging.

Akhirnya, dengan nada yang lebih ringan, Pengecualian Sempurna Allah .

NPE
sumber
"pengecualian tidak diizinkan untuk melarikan diri" - apakah maksud Anda pengecualian tersebut disebarkan ke tumpukan? bagaimana perbedaan logging dari printStackTrace ()?
MasterJoe
5

printStackTrace()mencetak ke konsol. Dalam pengaturan produksi, tidak ada yang pernah menonton itu. Suraj benar, harus meneruskan informasi ini ke logger.

Oh Chin Boon
sumber
Poin yang valid, meskipun kami dengan cermat menonton output konsol produksi kami.
Chris Knight
Bisakah Anda menjelaskan apa yang Anda maksud dengan menonton dengan cermat? Bagaimana dan siapa? Berapa frekuensi?
Oh Chin Boon
Dengan memperhatikan dengan seksama, saya seharusnya mengatakan kapan diperlukan. Ini file log bergulir yang merupakan salah satu dari beberapa port panggilan jika ada yang salah dengan aplikasi kami.
Chris Knight
3

Dalam aplikasi server stacktrace meledakkan file stdout / stderr Anda. Mungkin menjadi lebih besar dan lebih besar dan diisi dengan data yang tidak berguna karena biasanya Anda tidak memiliki konteks dan tidak ada cap waktu dan sebagainya.

misalnya catalina.out saat menggunakan tomcat sebagai wadah

Hajo Thelen
sumber
Poin yang bagus. Penggunaan printStackTrace () yang kasar dapat meledakkan file-file logging kami, atau paling tidak mengisinya dengan rim informasi yang tidak berguna
Chris Knight
@ ChrisKnight - dapatkah Anda menyarankan artikel yang menjelaskan bagaimana cara logging file bisa meledak dengan info yang tidak berguna? Terima kasih.
MasterJoe
3

Ini bukan praktik buruk karena ada sesuatu yang 'salah' tentang PrintStackTrace (), tetapi karena 'kode bau'. Sebagian besar waktu panggilan PrintStackTrace () ada karena seseorang gagal menangani pengecualian dengan benar. Setelah Anda menangani pengecualian dengan cara yang benar, Anda biasanya tidak peduli lagi dengan StackTrace.

Selain itu, menampilkan stacktrace pada stderr umumnya hanya berguna ketika debugging, bukan dalam produksi karena sangat sering stderr tidak ke mana-mana. Logging lebih masuk akal. Tetapi hanya mengganti PrintStackTrace () dengan logging pengecualian masih menyisakan Anda dengan aplikasi yang gagal tetapi terus berjalan seperti tidak ada yang terjadi.

AVee
sumber
0

Seperti beberapa orang telah disebutkan di sini masalahnya adalah dengan menelan pengecualian dalam kasus Anda hanya memanggil e.printStackTrace()dicatch blok. Itu tidak akan menghentikan eksekusi utas dan akan berlanjut setelah blok coba seperti dalam kondisi normal.

Alih-alih itu Anda perlu mencoba untuk memulihkan dari pengecualian (jika itu dapat dipulihkan), atau untuk melempar RuntimeException, atau untuk menggelembungkan pengecualian ke pemanggil untuk menghindari crash diam (misalnya, karena konfigurasi logger yang tidak tepat).

permen karet
sumber
0

Untuk menghindari masalah aliran output kusut yang dirujuk oleh @Vineet Reynolds

Anda dapat mencetaknya di stdout: e.printStackTrace(System.out);

Traeyee
sumber