Exception.printStackTrace () neden kötü uygulama olarak kabul edilir?


127

Bir yoktur sürü ait malzeme dışarı orada bir istisna yığın izini baskı kötü uygulama olduğunu göstermektedir.

Örneğin, Checkstyle'daki RegexpSingleline kontrolünden:

Bu kontrol, ex.printStacktrace () 'i çağırmak gibi yaygın kötü uygulamaları bulmak için [...] kullanılabilir.

Bununla birlikte, istisnaya neyin neden olduğunu bulmak için yığın izlemenin kesinlikle çok yararlı olmasının geçerli bir nedenini veren herhangi bir yer bulmakta zorlanıyorum. Farkında olduğum şeyler:

  1. Bir yığın izleme, son kullanıcılar tarafından hiçbir zaman görünür olmamalıdır (kullanıcı deneyimi ve güvenlik amacıyla)

  2. Yığın izleme oluşturmak nispeten pahalı bir işlemdir (ancak çoğu 'istisnai' durumda bir sorun olma ihtimali düşüktür)

  3. Birçok günlük çerçevesi sizin için yığın izini yazdıracaktır (bizimki bunu yapmaz ve hayır, onu kolayca değiştiremeyiz)

  4. Yığın izlemenin yazdırılması, hata işleme teşkil etmez. Diğer bilgi kaydı ve istisna işleme ile birleştirilmelidir.

Kodunuzda yığın izi yazdırmaktan kaçınmak için başka hangi nedenler var?


12
Düzenli olarak sorun gidermesi gereken biri olarak, bir şeyler ters gittiğinde bir yığın izi yazdırmayı asla ihmal etmem . Evet, bunu kullanıcıya gösterme, ama evet, bir hata günlüğüne dök.
Paul Grime

4
Gerçekten başka nedenlere ihtiyacın var mı? Kendi sorunuzu oldukça iyi cevapladığınızı düşünüyorum. Ancak, istisnai istisnalar dışında yığın izleme yazdırılmalıdır. :)
Neil

Yanıtlar:


125

Throwable.printStackTrace() yığın izini yazar System.err PrintStream'e . System.errYayın ve JVM işlemi altında yatan standart "hata" çıkış akımı ile yeniden yönlendirilebilir

  • çağıran System.setErr()işaret ettiği hedefi değiştirenSystem.err .
  • veya işlemin hata çıktı akışını yeniden yönlendirerek. Hata çıktı akışı bir dosyaya / cihaza yeniden yönlendirilebilir
    • içeriği personel tarafından göz ardı edilebilecek,
    • dosya / cihaz, dosyanın / cihazın mevcut içeriklerini arşivlemeden önce açık dosya / cihaz tutamacını kapatmak için bir işlemin yeniden başlatılması gerektiği sonucuna vararak günlük döndürme yeteneğine sahip olmayabilir.
    • ya da dosya / aygıt aslında kendisine yazılan tüm verileri atar /dev/null.

Yukarıdakilerden çıkarımla, çağırma Throwable.printStackTrace()yalnızca geçerli (iyi / harika değil) istisna işleme davranışı oluşturur

  • Eğer yoksa System.erruygulamanın ömrü süresi boyunca yeniden atanmakta olan,
  • ve uygulama çalışırken günlük rotasyonuna ihtiyaç duymuyorsanız,
  • ve kabul edildiyse / tasarlandıysa, uygulamanın günlük kaydı uygulaması System.err(ve JVM'nin standart hata çıktı akışına) yazmaktır .

Çoğu durumda, yukarıdaki koşullar karşılanmaz. JVM'de çalışan diğer kodların farkında olunmayabilir ve günlük dosyasının boyutunu veya işlemin çalışma zamanı süresini tahmin edemeyebilir ve iyi tasarlanmış bir günlük kaydı uygulaması, "makine tarafından ayrıştırılabilir" günlük dosyalarının yazılması etrafında döner. desteğe yardımcı olmak için bilinen bir hedefte bir kaydedicide tercih edilebilir ancak isteğe bağlı özellik).

Son olarak, öğesinin çıktısının Throwable.printStackTrace()kesinlikle yazılan diğer içerikle System.err(ve muhtemelen System.outher ikisi de aynı dosyaya / aygıta yönlendirilse bile) araya ekleneceği unutulmamalıdır . Bu, istisnalar etrafındaki veriler böyle bir olayda kolayca ayrıştırılamayacağından, başa çıkılması gereken bir sıkıntıdır (tek iş parçacıklı uygulamalar için). Daha da kötüsü, çok iş parçacıklı bir uygulamanın Throwable.printStackTrace() iş parçacığı güvenli olmadığı için çok kafa karıştırıcı günlükler üretme olasılığı yüksektir .

Aynı anda System.errbirden fazla iş parçacığı çağırdığında yığın izleme yazımını senkronize edecek bir senkronizasyon mekanizması yoktur Throwable.printStackTrace(). Bunu çözmek aslında kodunuzun ilişkili monitörde senkronize edilmesini gerektirir System.err(ve ayrıca System.outhedef dosya / cihaz aynıysa) ve bu, günlük dosyası mantığı için ödenmesi oldukça ağır bir bedeldir. Örnek vermek gerekirse, ConsoleHandlerve StreamHandlersınıfları, tarafından sağlanan günlük kaydı tesisinde konsola günlük kayıtlarını eklemekten sorumludur java.util.logging; günlük kayıtlarını yayınlamanın fiili işlemi senkronize edilir - bir günlük kaydı yayınlamaya çalışan her iş parçacığı, aynı zamanda ilgili monitördeki kilidi de almalıdır.StreamHandlerörneği. System.out/ Kullanarak aralıksız günlük kayıtlarına sahip olmanın aynı garantisine sahip System.errolmak istiyorsanız, aynısını sağlamalısınız - mesajlar bu akışlara serileştirilebilir bir şekilde yayınlanır.

Yukarıdakilerin tümü ve Throwable.printStackTrace()aslında yararlı olan çok kısıtlı senaryolar göz önüne alındığında , çoğu zaman onu çağırmanın kötü bir uygulama olduğu ortaya çıkar.


Önceki paragraflardan birindeki argümanı genişletmek, Throwable.printStackTracekonsola yazan bir kaydedici ile birlikte kullanmak da kötü bir seçimdir . Bu, kısmen, kaydedicinin farklı bir monitörde senkronize olmasının nedeni, uygulamanızın (muhtemelen, aralıklı günlük kayıtlarını istemiyorsanız) farklı bir monitörde senkronize edilmesidir. Bu argüman, uygulamanızda aynı hedefe yazan iki farklı kaydedici kullandığınızda da işe yarar.


Harika cevap, teşekkürler. Bununla birlikte, çoğu zaman gürültüsünün gerekli olmadığına katılıyorum, ancak kesinlikle kritik olduğu zamanlar da var.
Chris Knight

1
@Chris Evet, işte ancak kullanamazsınız zamanlar System.out.printlnve Throwable.printStackTraceve tabii ki, geliştirici kararı gereklidir. İplik güvenliği ile ilgili kısmın kaçırıldığından biraz endişelendim. Çoğu günlükçü uygulamasına bakarsanız, günlük kayıtlarının yazıldığı kısmı (konsola bile) senkronize ettiklerini fark edeceksiniz, ancak bunlar üzerinde System.errveya System.out.
Vineet Reynolds

2
Bu nedenlerden hiçbirinin, bir PrintStream veya PrintWriter nesnesini parametre olarak alan printStackTrace geçersiz kılmaları için geçerli olmadığını not etmek yanlış olur mu?
ESRogs

1
@Geek, ikincisi 2 değerine sahip işlem dosyası tanımlayıcısıdır. İlki sadece bir soyutlamadır.
Vineet Reynolds

1
PrintStackTrace () JDK kaynak kodunda, System.err PrintStream'i kilitlemek için senkronize edilmiş kullanılır, bu nedenle iş parçacığı güvenli bir yöntem olmalıdır.
EyouGo

33

Burada birden fazla soruna dokunuyorsunuz:

1) Yığın izleme, son kullanıcılara asla görünür olmamalıdır (kullanıcı deneyimi ve güvenlik amaçları için)

Evet, son kullanıcıların sorunlarını teşhis etmek için erişilebilir olmalı, ancak son kullanıcı bunları iki nedenden dolayı görmemelidir:

  • Çok belirsiz ve okunamazlar, uygulama çok kullanıcı dostu görünmeyecek.
  • Son kullanıcıya yığın izlemesi göstermek, potansiyel bir güvenlik riski oluşturabilir. Yanılıyorsam düzeltin, PHP aslında yığın izlemede işlev parametrelerini yazdırır - zekice, ancak çok tehlikeli - eğer veritabanına bağlanırken bir istisna ile karşılaşırsanız, yığın izlemede büyük olasılıkla ne olur?

2) Yığın izleme oluşturmak nispeten pahalı bir süreçtir (ancak çoğu 'istisnai' durumda bir sorun olma ihtimali düşüktür)

Yığın izleme oluşturmak, istisna oluşturulduğunda / fırlatıldığında gerçekleşir (bu nedenle bir istisna atmanın bir bedeli vardır), yazdırma o kadar pahalı değildir. Aslında, Throwable#fillInStackTrace()bir istisna atmayı neredeyse basit bir GOTO ifadesi kadar ucuz hale getirerek, özel istisnanızı geçersiz kılabilirsiniz .

3) Birçok günlük çerçevesi sizin için yığın izini yazdıracaktır (bizimki yazmaz ve hayır, onu kolayca değiştiremeyiz)

Çok güzel bir nokta. Buradaki ana sorun şudur: eğer çerçeve sizin için istisnayı günlüğe kaydederse, hiçbir şey yapmayın (ama olduğundan emin olun!) İstisnayı kendiniz kaydetmek istiyorsanız , bunları ham konsola koymamak için Logback veya Log4J gibi günlük çerçevesini kullanın. çünkü onu kontrol etmek çok zor.

Günlüğe kaydetme çerçevesi ile yığın izlerini kolayca dosyaya, konsola yönlendirebilir ve hatta belirli bir e-posta adresine gönderebilirsiniz. Kodlanmış printStackTrace()ile birlikte yaşamak zorundasınız sysout.

4) Yığın izlemenin yazdırılması, hata işleme teşkil etmez. Diğer bilgi kaydı ve istisna işleme ile birleştirilmelidir.

Tekrar: SQLExceptiondoğru şekilde oturum açın (günlük çerçevesini kullanarak tam yığın izleme ile) ve güzel gösterin: " Üzgünüz, şu anda isteğinizi işleyemiyoruz " mesajı. Gerçekten kullanıcının nedenlerle ilgilendiğini düşünüyor musunuz? StackOverflow hata ekranını gördünüz mü? Çok komik ama herhangi bir ayrıntı göstermiyor. Ancak kullanıcıya sorunun araştırılmasını sağlar.

Ama o olacak hemen sizi arayıp sorunu teşhis etmek gerekiyor. Yani ikisine de ihtiyacınız var: uygun istisna kaydı ve kullanıcı dostu mesajlar.


Bir şeyleri özetlemek için: İstisnaları her zaman günlüğe kaydedin (tercihen günlüğe kaydetme çerçevesini kullanarak ), ancak bunları son kullanıcıya göstermeyin. Dikkatlice ve GUI'nizdeki hata mesajlarını düşünün, yığın izlerini yalnızca geliştirme modunda gösterin.


cevabın benim aldığım
Blasanka

"çoğu 'istisnai' koşullar". Güzel.
awwsmm

19

İlk olarak, printStackTrace () sizin belirttiğiniz gibi pahalı değildir, çünkü istisna yaratıldığında yığın izleme doldurulur.

Buradaki fikir, günlüklere giden her şeyi bir günlükçü çerçevesinden geçirmektir, böylece günlük kaydı kontrol edilebilir. Bu nedenle, printStackTrace kullanmak yerine, sadeceLogger.log(msg, exception);


11

İstisnanın yığın izlemesini yazdırmak kendi başına kötü bir uygulama değildir, yalnızca yazdırmak bir istisna oluştuğunda aşama izini yazdırmak muhtemelen buradaki sorundur - çoğu zaman yalnızca bir yığın izini yazdırmak yeterli değildir.

Ayrıca, bir catchblokta gerçekleştirilen her şey a ise, uygun istisna işlemenin gerçekleştirilmediğinden şüphelenme eğilimi vardır e.printStackTrace. Yanlış kullanım, en iyi ihtimalle bir sorunun göz ardı edildiği ve en kötü ihtimalle, programın tanımsız veya beklenmedik bir durumda çalışmaya devam ettiği anlamına gelebilir.

Misal

Şu örneği ele alalım:

try {
  initializeState();

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

continueProcessingAssumingThatTheStateIsCorrect();

Burada, başlatmanın gerçekleşmesini gerektiren bazı işlemlere geçmeden önce bazı başlatma işlemleri yapmak istiyoruz.

Yukarıdaki kodda, programın devam etmesini önlemek için istisna yakalanmış ve uygun şekilde ele alınmış olmalıdır. continueProcessingAssumingThatTheStateIsCorrect sorunlara neden olacağını varsayabileceğimiz yönteme .

Çoğu durumda, e.printStackTrace()bazı istisnaların yutulduğunun ve işlemenin her sorun çıkmamış gibi devam etmesine izin verildiğinin bir göstergesidir.

Bu neden bir sorun haline geldi?

Muhtemelen, kötü istisna işlemenin daha yaygın hale gelmesinin en büyük nedenlerinden biri, Eclipse gibi IDE'lerin e.printStackTraceistisna işleme için otomatik olarak bir kod oluşturmasıdır :

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

(Yukarıda, gerçek olan try-catchotomatik olarak üretilen bir işlemek için Eclipse tarafından InterruptedExceptiontarafından atılan Thread.sleep.)

Çoğu uygulama için, yalnızca yığın izini standart hataya yazdırmak muhtemelen yeterli olmayacaktır. Yanlış istisna işleme, birçok durumda uygulamanın beklenmedik bir durumda çalışmasına ve beklenmedik ve tanımsız davranışlara yol açmasına neden olabilir.


1
Bu. Çoğu zaman istisnayı hiç yakalayamamak , en azından yöntem düzeyinde, yakalamaktan, yığın izlemesi yazdırmaktan ve hiçbir sorun çıkmamış gibi devam etmekten daha iyidir.
eis

10

Bence nedenler listeniz oldukça kapsamlı.

Bir kereden fazla karşılaştığım özellikle kötü bir örnek şudur:

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

Yukarıdaki kodla ilgili sorun, işlemin tamamenprintStackTrace aramadan oluşmasıdır : istisna gerçekten düzgün bir şekilde ele alınmaz ve kaçmasına izin verilmez.

Öte yandan, bir kural olarak, kodumda beklenmedik bir istisna olduğunda her zaman yığın izlemeyi günlüğe kaydederim. Yıllar geçtikçe bu politika bana çok fazla hata ayıklama zamanı kazandırdı.

Son olarak, daha hafif bir not olarak, Tanrı'nın Mükemmel İstisnası .


"istisnanın kaçmasına izin verilmez" - istisnanın yığına yayıldığını mı kastettiniz? loglama, printStackTrace () 'den ne kadar farklıdır?
MasterJoe

5

printStackTrace()bir konsola yazdırır. Prodüksiyon ortamlarında kimse bunu izlemiyor. Suraj haklı, bu bilgiyi bir kaydediciye iletmeli.


Geçerli bir nokta olsa da üretim konsolu çıktımızı dikkatle izliyoruz.
Chris Knight

Dikkatle izlemekle ne demek istediğini açıklayabilir misin? Nasıl ve kim? Hangi frekansta?
Oh Chin Boon

Dikkatle izleyerek, ne zaman gerektiğini söylemeliydim. Uygulamamızda bir şeyler ters giderse, birkaç arama bağlantı noktasından biri olan haddeleme günlük dosyası.
Chris Knight

3

Sunucu uygulamalarında yığın izleme, stdout / stderr dosyanızı patlatır. Daha büyük ve daha büyük hale gelebilir ve işe yaramaz verilerle doldurulur, çünkü genellikle hiçbir bağlamınız ve zaman damganız yoktur, vb.

Örneğin, tomcat'i kap olarak kullanırken catalina.out


İyi bir nokta. PrintStackTrace () işlevinin kötüye kullanılması günlük dosyalarımızı mahvedebilir veya en azından onu gereksiz bilgilerle doldurabilir
Chris Knight

@ChrisKnight - günlük dosyalarının işe yaramaz bilgilerle nasıl patlayabileceğini açıklayan bir makale önerebilir misiniz? Teşekkürler.
MasterJoe

3

Kötü bir uygulama değildir çünkü PrintStackTrace () hakkında bir şeyler 'yanlış', ama 'kod kokusu' olduğu için. Çoğu zaman PrintStackTrace () çağrısı oradadır çünkü birisi istisnayı düzgün bir şekilde işleyememiştir. İstisnayı uygun bir şekilde ele aldığınızda, genellikle artık StackTrace ile ilgilenmezsiniz.

Ek olarak, stderr üzerinde yığın izlemenin görüntülenmesi genellikle yalnızca hata ayıklama sırasında yararlıdır, üretimde değil, çünkü çoğu zaman stderr hiçbir yere gitmez. Günlüğe kaydetmek daha mantıklı. Ancak PrintStackTrace () 'i istisnayı günlüğe kaydetme ile değiştirmek, sizi hala başarısız olan ancak hiçbir şey olmamış gibi çalışmaya devam eden bir uygulamayla bırakır.


0

Burada bazılarının daha önce bahsettiği gibi, sorun, sadece telefonla aramanız durumunda yutma istisnasıdır e.printStackTrace().catch bloğun. İş parçacığı yürütmeyi durdurmaz ve normal durumda olduğu gibi try bloğundan sonra devam eder.

Bunun yerine, ya istisnadan kurtarmayı denemeniz (kurtarılabilir olması durumunda) ya RuntimeExceptionda sessiz çökmeleri önlemek için (örneğin, yanlış kaydedici yapılandırması nedeniyle) istisnayı arayana göndermeniz ya da çağrıyı yapana kabarcık atmanız gerekir.


0

@Vineet Reynolds tarafından belirtilen karışık çıkış akışı sorununu önlemek için

stdout'a yazdırabilirsiniz: e.printStackTrace(System.out);

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.