Tại sao ngoại lệ.printStackTrace () được coi là thực tiễn xấu?


126

Có một nhiều của các tài liệu hiện mà gợi ý rằng in stack dấu vết của một ngoại lệ là xấu thực hành.

Ví dụ: từ kiểm tra RegapiSingleline trong Checkstyle:

Kiểm tra này có thể được sử dụng [...] để tìm thông lệ xấu phổ biến như gọi ex.printStacktrace ()

Tuy nhiên, tôi đang vật lộn để tìm bất cứ nơi nào đưa ra lý do hợp lệ tại sao vì chắc chắn dấu vết ngăn xếp rất hữu ích trong việc theo dõi những gì gây ra ngoại lệ. Những điều mà tôi biết:

  1. Một dấu vết ngăn xếp không bao giờ được hiển thị cho người dùng cuối (cho mục đích bảo mật và trải nghiệm người dùng)

  2. Tạo theo dõi ngăn xếp là một quá trình tương đối tốn kém (mặc dù không chắc là vấn đề trong hầu hết các trường hợp 'ngoại lệ')

  3. Nhiều khung ghi nhật ký sẽ in theo dõi ngăn xếp cho bạn (chúng tôi không và không, chúng tôi không thể thay đổi dễ dàng)

  4. In dấu vết ngăn xếp không cấu thành xử lý lỗi. Nó nên được kết hợp với ghi nhật ký thông tin khác và xử lý ngoại lệ.

Còn lý do nào khác để tránh in dấu vết ngăn xếp trong mã của bạn?


12
Là một người phải khắc phục sự cố thường xuyên, tôi sẽ không bao giờ bỏ qua việc in một stacktrace khi có sự cố. Có, không hiển thị cho người dùng, nhưng có, kết xuất nó vào nhật ký lỗi.
Paul Grime

4
Bạn có thực sự cần những lý do khác? Tôi nghĩ rằng bạn đã trả lời câu hỏi của riêng bạn khá tốt. Tuy nhiên, stacktrace nên được in trong các trường hợp ngoại lệ đặc biệt. :)
Neil

Câu trả lời:


125

Throwable.printStackTrace()ghi theo dõi ngăn xếp vào System.errPrintStream. Các System.errdòng và cơ bản tiêu chuẩn "lỗi" dòng đầu ra của quá trình JVM có thể được chuyển hướng bởi

  • gọi System.setErr()mà thay đổi đích đến chỉ bởi System.err.
  • hoặc bằng cách chuyển hướng luồng đầu ra lỗi của quá trình. Luồng đầu ra lỗi có thể được chuyển hướng đến một tệp / thiết bị
    • có nội dung có thể bị nhân viên bỏ qua,
    • tệp / thiết bị có thể không có khả năng xoay vòng nhật ký, suy ra rằng cần phải khởi động lại quy trình để đóng tay cầm tệp / thiết bị đang mở, trước khi lưu trữ nội dung hiện có của tệp / thiết bị.
    • hoặc tập tin / thiết bị thực sự loại bỏ tất cả dữ liệu được ghi vào nó, như trường hợp của /dev/null.

Suy ra từ trên, việc gọi Throwable.printStackTrace()là hành vi xử lý ngoại lệ hợp lệ (không tốt / tuyệt vời), chỉ

  • nếu bạn không System.errđược chỉ định lại trong suốt thời gian tồn tại của ứng dụng,
  • và nếu bạn không yêu cầu xoay vòng nhật ký trong khi ứng dụng đang chạy,
  • và nếu thực hành ghi nhật ký được chấp nhận / thiết kế của ứng dụng là ghi vào System.err(và luồng đầu ra lỗi tiêu chuẩn của JVM).

Trong hầu hết các trường hợp, các điều kiện trên không được thỏa mãn. Người ta có thể không nhận thức được mã khác đang chạy trong JVM và người ta không thể dự đoán kích thước của tệp nhật ký hoặc thời gian chạy của quy trình và thực hành ghi nhật ký được thiết kế tốt sẽ xoay quanh việc ghi tệp nhật ký "có thể phân tích bằng máy" (a tốt hơn nhưng tính năng tùy chọn trong một logger) ở một điểm đến đã biết, để hỗ trợ hỗ trợ.

Cuối cùng, người ta phải nhớ rằng đầu ra của Throwable.printStackTrace()chắc chắn sẽ được xen kẽ với nội dung khác được ghi vào System.err(và có thể ngay cả System.outkhi cả hai được chuyển hướng đến cùng một tệp / thiết bị). Đây là một phiền toái (đối với các ứng dụng đơn luồng) mà người ta phải giải quyết, vì dữ liệu xung quanh các ngoại lệ không dễ bị phân tích cú pháp trong một sự kiện như vậy. Tồi tệ hơn, rất có khả năng một ứng dụng đa luồng sẽ tạo ra các nhật ký rất khó hiểu vì Throwable.printStackTrace() không an toàn cho luồng .

Không có cơ chế đồng bộ hóa để đồng bộ hóa cách viết của dấu vết ngăn xếp System.errkhi nhiều luồng được gọi Throwable.printStackTrace()cùng một lúc. Giải quyết vấn đề này thực sự đòi hỏi mã của bạn phải đồng bộ hóa trên màn hình được liên kết với System.err(và cũng có thể System.out, nếu tệp / thiết bị đích giống nhau) và đó là một mức giá khá cao để trả cho sự tỉnh táo của tệp nhật ký. Lấy một ví dụ, các lớp ConsoleHandlerStreamHandlerchịu trách nhiệm nối các bản ghi nhật ký vào bàn điều khiển, trong cơ sở ghi nhật ký được cung cấp bởi java.util.logging; hoạt động thực tế của bản ghi nhật ký được đồng bộ hóa - mọi luồng cố gắng xuất bản bản ghi nhật ký cũng phải có được khóa trên màn hình được liên kết vớiStreamHandlerví dụ Nếu bạn muốn có cùng một đảm bảo về việc có các bản ghi nhật ký không xen kẽ bằng cách sử dụng System.out/ System.err, bạn phải đảm bảo như vậy - các thông báo được xuất bản tới các luồng này theo cách tuần tự hóa.

Xem xét tất cả các điều trên, và các kịch bản rất hạn chế trong đó Throwable.printStackTrace()thực sự hữu ích, nó thường chỉ ra rằng việc gọi nó là một thực tiễn xấu.


Mở rộng đối số trong một trong các đoạn trước, nó cũng là một lựa chọn kém để sử dụng Throwable.printStackTracekết hợp với một bộ ghi chép ghi vào bàn điều khiển. Điều này một phần, do lý do bộ ghi sẽ đồng bộ hóa trên một màn hình khác, trong khi ứng dụng của bạn (có thể, nếu bạn không muốn các bản ghi nhật ký xen kẽ) đồng bộ hóa trên một màn hình khác. Đối số cũng tốt khi bạn sử dụng hai logger khác nhau ghi vào cùng một đích trong ứng dụng của bạn.


Câu trả lời tuyệt vời, cảm ơn. Tuy nhiên, trong khi tôi đồng ý rằng hầu hết thời gian tiếng ồn của nó hoặc không cần thiết, có những lúc nó hoàn toàn quan trọng.
Chris Knight

1
@Chris Có, có những lúc bạn không thể nhưng sử dụng System.out.printlnThrowable.printStackTracetất nhiên, cần có sự phán xét của nhà phát triển. Tôi đã có một chút lo lắng rằng phần về an toàn luồng đã bị bỏ lỡ. Nếu bạn nhìn vào hầu hết các triển khai logger, bạn sẽ nhận thấy rằng chúng đồng bộ hóa phần ghi nhật ký nhật ký (ngay cả với bảng điều khiển), mặc dù chúng không thu được màn hình trên System.errhoặc System.out.
Vineet Reynold

2
Tôi có sai không khi lưu ý rằng không có lý do nào trong số những lý do này áp dụng cho các phần ghi đè printStackTrace lấy đối tượng PrintStream hoặc PrintWriter làm tham số?
ESRogs

1
@Geek, cái sau là mô tả tệp quy trình có giá trị 2. Cái trước chỉ là một sự trừu tượng.
Vineet Reynold

1
Trong mã nguồn JDK của printStackTrace (), nó sử dụng được đồng bộ hóa để khóa System.err PrintStream, vì vậy nó phải là một phương thức an toàn cho luồng.
EyouGo

33

Bạn đang chạm vào nhiều vấn đề ở đây:

1) Dấu vết ngăn xếp không bao giờ được hiển thị cho người dùng cuối (vì mục đích bảo mật và trải nghiệm người dùng)

Có, có thể truy cập để chẩn đoán sự cố của người dùng cuối, nhưng người dùng cuối không nên xem chúng vì hai lý do:

  • Chúng rất tối nghĩa và không thể đọc được, ứng dụng sẽ trông rất không thân thiện với người dùng.
  • Hiển thị dấu vết ngăn xếp cho người dùng cuối có thể gây ra rủi ro bảo mật tiềm ẩn. Chính xác cho tôi nếu tôi sai, PHP thực sự in các tham số hàm trong theo dõi ngăn xếp - tuyệt vời, nhưng rất nguy hiểm - nếu bạn sẽ có ngoại lệ trong khi kết nối với cơ sở dữ liệu, bạn có khả năng gì trong stacktrace?

2) Tạo theo dõi ngăn xếp là một quá trình tương đối tốn kém (mặc dù không chắc là vấn đề trong hầu hết các trường hợp ngoại lệ)

Tạo dấu vết ngăn xếp xảy ra khi ngoại lệ được tạo / ném (đó là lý do tại sao ném ngoại lệ đi kèm với giá), in ấn không đắt lắm. Trong thực tế, bạn có thể ghi đè lên Throwable#fillInStackTrace()ngoại lệ tùy chỉnh của mình một cách hiệu quả khiến cho việc ném một ngoại lệ gần như rẻ như một tuyên bố GOTO đơn giản.

3) Nhiều khung ghi nhật ký sẽ in dấu vết ngăn xếp cho bạn (chúng tôi không và không, chúng tôi không thể thay đổi dễ dàng)

Điểm rất tốt. Vấn đề chính ở đây là: nếu khung ghi lại ngoại lệ cho bạn, không làm gì cả (nhưng hãy chắc chắn là có!) Nếu bạn muốn tự mình đăng nhập ngoại lệ, hãy sử dụng khung đăng nhập như Logback hoặc Log4J , để không đưa chúng vào bảng điều khiển thô bởi vì nó rất khó kiểm soát nó

Với khung ghi nhật ký, bạn có thể dễ dàng chuyển hướng dấu vết ngăn xếp thành tệp, bảng điều khiển hoặc thậm chí gửi chúng đến một địa chỉ email được chỉ định. Với mã hóa cứng, printStackTrace()bạn phải sống với sysout.

4) In dấu vết ngăn xếp không cấu thành xử lý lỗi. Nó nên được kết hợp với ghi nhật ký thông tin khác và xử lý ngoại lệ.

Một lần nữa: đăng nhập SQLExceptionchính xác (với theo dõi ngăn xếp đầy đủ, sử dụng khung ghi nhật ký) và hiển thị tốt: " Xin lỗi, chúng tôi hiện không thể xử lý yêu cầu của bạn ". Bạn có thực sự nghĩ rằng người dùng quan tâm đến lý do? Bạn đã thấy màn hình lỗi StackOverflow? Nó rất hài hước, nhưng không tiết lộ bất kỳ chi tiết. Tuy nhiên, nó đảm bảo cho người dùng rằng vấn đề sẽ được điều tra.

Nhưng anh ấy sẽ gọi cho bạn ngay lập tức và bạn cần có khả năng chẩn đoán vấn đề. Vì vậy, bạn cần cả hai: ghi nhật ký ngoại lệ phù hợp và tin nhắn thân thiện với người dùng.


Để kết thúc mọi thứ: luôn ghi nhật ký ngoại lệ (tốt nhất là sử dụng khung ghi nhật ký ), nhưng không để lộ chúng cho người dùng cuối. Hãy suy nghĩ cẩn thận và về các thông báo lỗi trong GUI của bạn, chỉ hiển thị dấu vết ngăn xếp trong chế độ phát triển.


Câu trả lời của bạn là câu trả lời mà tôi nhận được.
Blasanka

"Hầu hết các trường hợp ngoại lệ". đẹp.
awwsmm

19

Điều đầu tiên printStackTrace () không đắt như bạn nêu, bởi vì dấu vết ngăn xếp được điền khi ngoại lệ được tạo.

Ý tưởng là để vượt qua bất cứ thứ gì đi đến nhật ký thông qua khung logger, để việc ghi nhật ký có thể được kiểm soát. Do đó, thay vì sử dụng printStackTrace, chỉ cần sử dụng một cái gì đó nhưLogger.log(msg, exception);


11

Bản thân việc in dấu vết ngăn xếp của ngoại lệ không tạo thành thông lệ xấu, mà chỉ in dấu vết dấu vết khi có ngoại lệ xảy ra có lẽ là vấn đề ở đây - thường thì chỉ in dấu vết ngăn xếp là không đủ.

Ngoài ra, có xu hướng nghi ngờ rằng việc xử lý ngoại lệ phù hợp sẽ không được thực hiện nếu tất cả những gì đang được thực hiện trong một catchkhối là a e.printStackTrace. Xử lý không đúng cách có thể có nghĩa là một vấn đề đang bị bỏ qua và tệ nhất là một chương trình tiếp tục thực thi trong trạng thái không xác định hoặc không mong muốn.

Thí dụ

Hãy xem xét ví dụ sau:

try {
  initializeState();

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

continueProcessingAssumingThatTheStateIsCorrect();

Ở đây, chúng tôi muốn thực hiện một số xử lý khởi tạo trước khi chúng tôi tiếp tục một số xử lý yêu cầu việc khởi tạo đã diễn ra.

Trong đoạn mã trên, ngoại lệ phải được bắt và xử lý đúng cách để ngăn chương trình tiến tới continueProcessingAssumingThatTheStateIsCorrectphương thức mà chúng ta có thể cho là sẽ gây ra sự cố.

Trong nhiều trường hợp, e.printStackTrace()là một dấu hiệu cho thấy một số ngoại lệ đang bị nuốt và việc xử lý được phép tiến hành như thể không có vấn đề gì xảy ra.

Tại sao điều này trở thành một vấn đề?

Có lẽ một trong những lý do lớn nhất khiến việc xử lý ngoại lệ kém trở nên phổ biến là do cách các IDE như Eclipse sẽ tự động tạo mã sẽ thực hiện e.printStackTracexử lý ngoại lệ:

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

(Trên đây là một thực tế try-catchđược tạo ra bởi Eclipse để xử lý một cú InterruptedExceptionném Thread.sleep.)

Đối với hầu hết các ứng dụng, chỉ cần in dấu vết ngăn xếp thành lỗi tiêu chuẩn có lẽ sẽ không đủ. Xử lý ngoại lệ không đúng cách trong nhiều trường hợp có thể dẫn đến một ứng dụng đang chạy trong trạng thái bất ngờ và có thể dẫn đến hành vi không mong muốn và không xác định.


1
Điều này. Khá thường xuyên chỉ không bắt được ngoại lệ, ít nhất là ở cấp độ phương thức, tốt hơn là bắt, thực hiện in dấu vết ngăn xếp và tiếp tục như thể không có vấn đề gì xảy ra.
eis

10

Tôi nghĩ rằng danh sách các lý do của bạn là một lý do khá toàn diện.

Một ví dụ đặc biệt tồi tệ mà tôi đã gặp hơn một lần như thế này:

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

Vấn đề với các mã trên là việc xử lý bao gồm hoàn toàn các printStackTracecuộc gọi: ngoại trừ là không thực sự xử lý đúng cách cũng không phải là nó cho phép để trốn thoát.

Mặt khác, theo quy tắc, tôi luôn ghi nhật ký theo dõi ngăn xếp bất cứ khi nào có ngoại lệ không mong muốn trong mã của tôi. Trong những năm qua, chính sách này đã giúp tôi tiết kiệm rất nhiều thời gian gỡ lỗi.

Cuối cùng, trên một ghi chú nhẹ hơn, Ngoại lệ hoàn hảo của Chúa .


"ngoại lệ không được phép thoát" - ý bạn là ngoại lệ đó được truyền lên ngăn xếp? đăng nhập khác với printStackTrace () như thế nào?
MasterJoe

5

printStackTrace()in ra bàn điều khiển Trong cài đặt sản xuất, không ai từng xem tại đó. Suraj là chính xác, nên chuyển thông tin này cho một logger.


Điểm hợp lệ, mặc dù chúng tôi cẩn thận xem đầu ra giao diện điều khiển sản xuất của chúng tôi.
Chris Knight

Bạn có thể giải thích những gì bạn có nghĩa là bằng cách xem cẩn thận? Làm thế nào và ai? Ở tần số nào?
Oh Chin Boon

Bằng cách theo dõi cẩn thận, tôi nên nói khi có yêu cầu. Đây là một tệp nhật ký cuộn là một trong một số cổng của cuộc gọi nếu có sự cố với ứng dụng của chúng tôi.
Chris Knight

3

Trong các ứng dụng máy chủ, stacktrace sẽ làm nổ tập tin stdout / stderr của bạn. Nó có thể trở nên lớn hơn và lớn hơn và chứa đầy dữ liệu vô dụng vì thông thường bạn không có ngữ cảnh và không có dấu thời gian, v.v.

ví dụ catalina.out khi sử dụng tomcat làm vật chứa


Điểm tốt. Việc sử dụng lạm dụng printStackTrace () có thể thổi các tệp nhật ký của chúng tôi hoặc ít nhất là lấp đầy nó bằng các luồng thông tin vô dụng
Chris Knight

@ChrisKnight - bạn có thể vui lòng đề xuất một bài viết giải thích làm thế nào các tệp nhật ký có thể bị nổ tung với thông tin vô dụng? cảm ơn.
MasterJoe

3

Đó không phải là thực tiễn tồi vì có gì đó 'sai' về PrintStackTrace (), nhưng vì đó là 'mùi mã'. Hầu hết thời gian cuộc gọi PrintStackTrace () ở đó vì ai đó không thể xử lý ngoại lệ đúng cách. Khi bạn xử lý ngoại lệ theo cách thích hợp, bạn thường không quan tâm đến StackTrace nữa.

Ngoài ra, hiển thị stacktrace trên stderr thường chỉ hữu ích khi gỡ lỗi, không phải trong sản xuất vì rất thường stderr không đi đến đâu. Ghi nhật ký nó có ý nghĩa hơn. Nhưng chỉ cần thay thế PrintStackTrace () bằng cách ghi nhật ký ngoại lệ vẫn để lại cho bạn một ứng dụng bị lỗi nhưng vẫn chạy như không có gì xảy ra.


0

Như một số kẻ đã đề cập ở đây, vấn đề là ngoại lệ nuốt trong trường hợp bạn chỉ cần gọi e.printStackTrace()trong catchkhối. Nó sẽ không dừng thực thi luồng và sẽ tiếp tục sau khối thử như trong điều kiện bình thường.

Thay vào đó, bạn cần cố gắng khôi phục từ ngoại lệ (trong trường hợp có thể khôi phục được) hoặc ném RuntimeExceptionhoặc đánh bóng ngoại lệ cho người gọi để tránh sự cố im lặng (ví dụ: do cấu hình logger không đúng).


0

Để tránh sự cố luồng đầu ra bị rối được gọi bởi @Vineet Reynold

bạn có thể in nó ra thiết bị xuất chuẩn: e.printStackTrace(System.out);

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.