การติดตามสแต็กคืออะไรและฉันจะใช้เพื่อแก้ไขข้อผิดพลาดแอปพลิเคชันของฉันได้อย่างไร


644

บางครั้งเมื่อฉันเรียกใช้แอปพลิเคชันของฉันมันทำให้ฉันมีข้อผิดพลาดที่ดูเหมือนว่า:

Exception in thread "main" java.lang.NullPointerException
        at com.example.myproject.Book.getTitle(Book.java:16)
        at com.example.myproject.Author.getBookTitles(Author.java:25)
        at com.example.myproject.Bootstrap.main(Bootstrap.java:14)

ผู้คนอ้างถึงสิ่งนี้ว่าเป็น "การติดตามสแต็ก" การติดตามสแต็กคืออะไร มีอะไรบอกฉันได้บ้างเกี่ยวกับข้อผิดพลาดที่เกิดขึ้นในโปรแกรมของฉัน


เกี่ยวกับคำถามนี้ - บ่อยครั้งที่ฉันเห็นคำถามเกิดขึ้นโดยที่โปรแกรมเมอร์มือใหม่กำลัง "รับข้อผิดพลาด" และพวกเขาก็วางสแต็กการติดตามและบล็อกโค้ดแบบสุ่มโดยไม่เข้าใจว่าสแต็กการติดตามคืออะไร คำถามนี้มีวัตถุประสงค์เพื่อเป็นข้อมูลอ้างอิงสำหรับโปรแกรมเมอร์มือใหม่ที่อาจต้องการความช่วยเหลือในการทำความเข้าใจคุณค่าของการติดตามสแต็ก


25
นอกจากนี้หากบรรทัดสแต็คเทรซไม่มีชื่อไฟล์และหมายเลขบรรทัดคลาสสำหรับบรรทัดนั้นจะไม่ถูกคอมไพล์ด้วยข้อมูลการดีบัก
Thorbjørn Ravn Andersen

คำตอบ:


589

กล่าวอย่างง่าย ๆ การติดตามสแต็กเป็นรายการของการเรียกเมธอดว่าแอปพลิเคชันอยู่ตรงกลางเมื่อมีการโยนข้อยกเว้น

ตัวอย่างง่ายๆ

ด้วยตัวอย่างที่ให้ไว้ในคำถามเราสามารถกำหนดได้อย่างแม่นยำว่าข้อยกเว้นนั้นถูกส่งออกไปในแอปพลิเคชันอย่างไร ลองดูที่การติดตามสแต็ก:

Exception in thread "main" java.lang.NullPointerException
        at com.example.myproject.Book.getTitle(Book.java:16)
        at com.example.myproject.Author.getBookTitles(Author.java:25)
        at com.example.myproject.Bootstrap.main(Bootstrap.java:14)

นี่คือการติดตามสแต็กที่ง่ายมาก หากเราเริ่มต้นที่จุดเริ่มต้นของรายการ "ที่ ... " เราสามารถบอกได้ว่าเกิดข้อผิดพลาดที่ใด สิ่งที่เรากำลังมองหาคือการเรียกใช้วิธีการสูงสุดที่เป็นส่วนหนึ่งของใบสมัครของเรา ในกรณีนี้มันคือ:

at com.example.myproject.Book.getTitle(Book.java:16)

ในการแก้ไขปัญหานี้เราสามารถเปิดBook.javaและดูที่บรรทัด16ซึ่งก็คือ:

15   public String getTitle() {
16      System.out.println(title.toString());
17      return title;
18   }

สิ่งนี้จะบ่งบอกว่าบางสิ่ง (อาจtitle) อยู่nullในรหัสด้านบน

ตัวอย่างที่มีห่วงโซ่ของข้อยกเว้น

บางครั้งแอปพลิเคชันจะจับข้อยกเว้นและส่งใหม่เป็นสาเหตุของข้อยกเว้นอื่น โดยทั่วไปจะมีลักษณะดังนี้:

34   public void getBookIds(int id) {
35      try {
36         book.getId(id);    // this method it throws a NullPointerException on line 22
37      } catch (NullPointerException e) {
38         throw new IllegalStateException("A book has a null property", e)
39      }
40   }

สิ่งนี้อาจทำให้คุณติดตามสแต็กที่มีลักษณะดังนี้:

Exception in thread "main" java.lang.IllegalStateException: A book has a null property
        at com.example.myproject.Author.getBookIds(Author.java:38)
        at com.example.myproject.Bootstrap.main(Bootstrap.java:14)
Caused by: java.lang.NullPointerException
        at com.example.myproject.Book.getId(Book.java:22)
        at com.example.myproject.Author.getBookIds(Author.java:36)
        ... 1 more

สิ่งที่แตกต่างเกี่ยวกับอันนี้คือ "สาเหตุจาก" บางครั้งข้อยกเว้นจะมีส่วน "ที่เกิดจาก" หลายรายการ สำหรับสิ่งเหล่านี้โดยทั่วไปคุณต้องการค้นหา "สาเหตุราก" ซึ่งจะเป็นหนึ่งใน "สาเหตุจาก" ส่วนต่ำสุดในการติดตามสแต็ก ในกรณีของเรามันคือ:

Caused by: java.lang.NullPointerException <-- root cause
        at com.example.myproject.Book.getId(Book.java:22) <-- important line

อีกครั้งด้วยข้อยกเว้นนี้เราต้องการที่จะดูที่บรรทัด22ของBook.javaที่จะเห็นสิ่งที่อาจก่อให้เกิดNullPointerExceptionที่นี่

ตัวอย่างที่น่ากลัวยิ่งกว่าด้วยรหัสห้องสมุด

โดยปกติแล้วการติดตามสแต็กมีความซับซ้อนมากกว่าสองตัวอย่างด้านบน นี่คือตัวอย่าง (มันยาว แต่แสดงให้เห็นถึงข้อยกเว้นหลายระดับของข้อผูกมัด):

javax.servlet.ServletException: Something bad happened
    at com.example.myproject.OpenSessionInViewFilter.doFilter(OpenSessionInViewFilter.java:60)
    at org.mortbay.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1157)
    at com.example.myproject.ExceptionHandlerFilter.doFilter(ExceptionHandlerFilter.java:28)
    at org.mortbay.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1157)
    at com.example.myproject.OutputBufferFilter.doFilter(OutputBufferFilter.java:33)
    at org.mortbay.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1157)
    at org.mortbay.jetty.servlet.ServletHandler.handle(ServletHandler.java:388)
    at org.mortbay.jetty.security.SecurityHandler.handle(SecurityHandler.java:216)
    at org.mortbay.jetty.servlet.SessionHandler.handle(SessionHandler.java:182)
    at org.mortbay.jetty.handler.ContextHandler.handle(ContextHandler.java:765)
    at org.mortbay.jetty.webapp.WebAppContext.handle(WebAppContext.java:418)
    at org.mortbay.jetty.handler.HandlerWrapper.handle(HandlerWrapper.java:152)
    at org.mortbay.jetty.Server.handle(Server.java:326)
    at org.mortbay.jetty.HttpConnection.handleRequest(HttpConnection.java:542)
    at org.mortbay.jetty.HttpConnection$RequestHandler.content(HttpConnection.java:943)
    at org.mortbay.jetty.HttpParser.parseNext(HttpParser.java:756)
    at org.mortbay.jetty.HttpParser.parseAvailable(HttpParser.java:218)
    at org.mortbay.jetty.HttpConnection.handle(HttpConnection.java:404)
    at org.mortbay.jetty.bio.SocketConnector$Connection.run(SocketConnector.java:228)
    at org.mortbay.thread.QueuedThreadPool$PoolThread.run(QueuedThreadPool.java:582)
Caused by: com.example.myproject.MyProjectServletException
    at com.example.myproject.MyServlet.doPost(MyServlet.java:169)
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:727)
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:820)
    at org.mortbay.jetty.servlet.ServletHolder.handle(ServletHolder.java:511)
    at org.mortbay.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1166)
    at com.example.myproject.OpenSessionInViewFilter.doFilter(OpenSessionInViewFilter.java:30)
    ... 27 more
Caused by: org.hibernate.exception.ConstraintViolationException: could not insert: [com.example.myproject.MyEntity]
    at org.hibernate.exception.SQLStateConverter.convert(SQLStateConverter.java:96)
    at org.hibernate.exception.JDBCExceptionHelper.convert(JDBCExceptionHelper.java:66)
    at org.hibernate.id.insert.AbstractSelectingDelegate.performInsert(AbstractSelectingDelegate.java:64)
    at org.hibernate.persister.entity.AbstractEntityPersister.insert(AbstractEntityPersister.java:2329)
    at org.hibernate.persister.entity.AbstractEntityPersister.insert(AbstractEntityPersister.java:2822)
    at org.hibernate.action.EntityIdentityInsertAction.execute(EntityIdentityInsertAction.java:71)
    at org.hibernate.engine.ActionQueue.execute(ActionQueue.java:268)
    at org.hibernate.event.def.AbstractSaveEventListener.performSaveOrReplicate(AbstractSaveEventListener.java:321)
    at org.hibernate.event.def.AbstractSaveEventListener.performSave(AbstractSaveEventListener.java:204)
    at org.hibernate.event.def.AbstractSaveEventListener.saveWithGeneratedId(AbstractSaveEventListener.java:130)
    at org.hibernate.event.def.DefaultSaveOrUpdateEventListener.saveWithGeneratedOrRequestedId(DefaultSaveOrUpdateEventListener.java:210)
    at org.hibernate.event.def.DefaultSaveEventListener.saveWithGeneratedOrRequestedId(DefaultSaveEventListener.java:56)
    at org.hibernate.event.def.DefaultSaveOrUpdateEventListener.entityIsTransient(DefaultSaveOrUpdateEventListener.java:195)
    at org.hibernate.event.def.DefaultSaveEventListener.performSaveOrUpdate(DefaultSaveEventListener.java:50)
    at org.hibernate.event.def.DefaultSaveOrUpdateEventListener.onSaveOrUpdate(DefaultSaveOrUpdateEventListener.java:93)
    at org.hibernate.impl.SessionImpl.fireSave(SessionImpl.java:705)
    at org.hibernate.impl.SessionImpl.save(SessionImpl.java:693)
    at org.hibernate.impl.SessionImpl.save(SessionImpl.java:689)
    at sun.reflect.GeneratedMethodAccessor5.invoke(Unknown Source)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
    at java.lang.reflect.Method.invoke(Method.java:597)
    at org.hibernate.context.ThreadLocalSessionContext$TransactionProtectionWrapper.invoke(ThreadLocalSessionContext.java:344)
    at $Proxy19.save(Unknown Source)
    at com.example.myproject.MyEntityService.save(MyEntityService.java:59) <-- relevant call (see notes below)
    at com.example.myproject.MyServlet.doPost(MyServlet.java:164)
    ... 32 more
Caused by: java.sql.SQLException: Violation of unique constraint MY_ENTITY_UK_1: duplicate value(s) for column(s) MY_COLUMN in statement [...]
    at org.hsqldb.jdbc.Util.throwError(Unknown Source)
    at org.hsqldb.jdbc.jdbcPreparedStatement.executeUpdate(Unknown Source)
    at com.mchange.v2.c3p0.impl.NewProxyPreparedStatement.executeUpdate(NewProxyPreparedStatement.java:105)
    at org.hibernate.id.insert.AbstractSelectingDelegate.performInsert(AbstractSelectingDelegate.java:57)
    ... 54 more

ในตัวอย่างนี้มีมากขึ้น สิ่งที่เรากังวลเป็นส่วนใหญ่คือมองหาวิธีการที่มาจากรหัสของเราซึ่งจะเป็นอะไรก็ได้ในcom.example.myprojectแพ็คเกจ จากตัวอย่างที่สอง (ด้านบน) ก่อนอื่นเราต้องมองหาสาเหตุที่แท้จริงซึ่งก็คือ:

Caused by: java.sql.SQLException

อย่างไรก็ตามวิธีการทั้งหมดเรียกภายใต้ว่าเป็นรหัสห้องสมุด ดังนั้นเราจะเลื่อนไปที่ "สาเหตุโดย" เหนือสิ่งนั้นและมองหาวิธีการโทรแรกที่เกิดจากรหัสของเราซึ่งก็คือ:

at com.example.myproject.MyEntityService.save(MyEntityService.java:59)

เช่นในตัวอย่างก่อนหน้านี้เราควรดูที่MyEntityService.javaบรรทัด59เพราะนั่นคือที่ที่ข้อผิดพลาดนี้เกิดขึ้น (อันนี้ค่อนข้างชัดเจนว่าเกิดอะไรขึ้นเนื่องจาก SQLException ระบุข้อผิดพลาด


3
@RobHruska - อธิบายได้ดีมาก +1 คุณรู้จัก parsers ใดบ้างที่ใช้การติดตามการยกเว้นเป็นสตริงและจัดเตรียมวิธีที่มีประโยชน์ในการวิเคราะห์ stacktrace - เช่น getLastCausedBy () หรือ getCausedByForMyAppCode ("com.example.myproject")
Andy Dufresne

1
@AndyDufresne - ฉันยังไม่เจอเลย แต่อีกครั้งฉันก็ไม่ได้มองเช่นกัน
Rob Hruska

1
การปรับปรุงที่แนะนำ: อธิบายบรรทัดแรกของการติดตามสแต็กซึ่งเริ่มต้นด้วยException in thread "main"ในตัวอย่างแรกของคุณ ฉันคิดว่ามันจะเป็นประโยชน์อย่างยิ่งที่จะอธิบายว่าบรรทัดนี้มักจะมาพร้อมกับข้อความเช่นค่าของตัวแปรที่สามารถช่วยวินิจฉัยปัญหา ฉันพยายามแก้ไขด้วยตัวเอง แต่ฉันพยายามดิ้นรนเพื่อปรับความคิดเหล่านี้ให้เป็นโครงสร้างคำตอบของคุณ
โค้ด - ฝึกงาน

5
นอกจากนี้ java 1.7 ยังได้เพิ่ม "ระงับ:" - ซึ่งแสดงรายการข้อยกเว้นการติดตามสแต็กก่อนที่จะแสดง "สาเหตุโดย:" สำหรับข้อยกเว้นนี้ มันถูกใช้โดยอัตโนมัติโดยการสร้างด้วยทรัพยากรแบบลอง: docs.oracle.com/javase/specs/jls/se8/html/ …และมีข้อยกเว้นหากมีการโยนทิ้งระหว่างการปิดทรัพยากร
dhblah

มี JEP openjdk.java.net/jeps/8220715ที่มีจุดมุ่งหมายเพื่อปรับปรุงความเข้าใจโดยเฉพาะอย่างยิ่ง NPEs โดยให้รายละเอียดเช่น "ไม่สามารถเขียนฟิลด์ 'nullInstanceField' เพราะ 'this.nullInstanceField' เป็นโมฆะ
Mahatma_Fatal_Error

80

ฉันโพสต์คำตอบนี้ดังนั้นคำตอบสูงสุด (เมื่อเรียงตามกิจกรรม) ไม่ใช่คำตอบที่ผิดธรรมดา

Stacktrace คืออะไร

stacktrace เป็นเครื่องมือดีบั๊กที่มีประโยชน์มาก มันแสดงให้เห็น call stack (หมายถึง stack ของฟังก์ชั่นที่ถูกเรียกจนถึงจุดนั้น) ในเวลาที่เกิดข้อยกเว้นที่ไม่ได้ตรวจจับ (หรือเวลาที่ stacktrace ถูกสร้างขึ้นด้วยตนเอง) สิ่งนี้มีประโยชน์มากเพราะไม่เพียงแสดงให้คุณเห็นว่ามีข้อผิดพลาดเกิดขึ้น แต่ยังรวมถึงวิธีการที่โปรแกรมสิ้นสุดลงในตำแหน่งของรหัสนั้น สิ่งนี้นำไปสู่คำถามต่อไป:

ข้อยกเว้นคืออะไร

ข้อยกเว้นคือสิ่งที่สภาพแวดล้อมรันไทม์ใช้เพื่อบอกคุณว่ามีข้อผิดพลาดเกิดขึ้น ตัวอย่างที่นิยมคือ NullPointerException, IndexOutOfBoundsException หรือ ArithmeticException สิ่งเหล่านี้เกิดขึ้นเมื่อคุณพยายามทำบางสิ่งที่เป็นไปไม่ได้ ตัวอย่างเช่น NullPointerException จะถูกโยนทิ้งเมื่อคุณพยายามตรวจสอบวัตถุ Null:

Object a = null;
a.toString();                 //this line throws a NullPointerException

Object[] b = new Object[5];
System.out.println(b[10]);    //this line throws an IndexOutOfBoundsException,
                              //because b is only 5 elements long
int ia = 5;
int ib = 0;
ia = ia/ib;                   //this line throws an  ArithmeticException with the 
                              //message "/ by 0", because you are trying to
                              //divide by 0, which is not possible.

ฉันจะจัดการกับ Stacktraces / Exceptions ได้อย่างไร

ในตอนแรกค้นหาสิ่งที่ทำให้เกิดข้อยกเว้น ลอง googleing ชื่อของข้อยกเว้นเพื่อค้นหาสาเหตุของข้อยกเว้นนั้นคืออะไร ส่วนใหญ่มันจะเกิดจากรหัสที่ไม่ถูกต้อง ในตัวอย่างที่ระบุด้านบนข้อยกเว้นทั้งหมดเกิดจากรหัสที่ไม่ถูกต้อง ดังนั้นสำหรับตัวอย่าง NullPointerException คุณสามารถตรวจสอบให้แน่ใจว่าaไม่เคยเป็นโมฆะในเวลานั้น ตัวอย่างเช่นคุณสามารถกำหนดค่าเริ่มต้นaหรือรวมเช็คเช่นนี้:

if (a!=null) {
    a.toString();
}

a==nullวิธีนี้เส้นที่กระทำผิดจะไม่ทำงานถ้า กันไปสำหรับตัวอย่างอื่น ๆ

บางครั้งคุณไม่สามารถแน่ใจได้ว่าคุณจะไม่ได้รับการยกเว้น ตัวอย่างเช่นหากคุณใช้การเชื่อมต่อเครือข่ายในโปรแกรมของคุณคุณจะไม่สามารถหยุดคอมพิวเตอร์จากการสูญเสียการเชื่อมต่ออินเทอร์เน็ต (เช่นคุณไม่สามารถหยุดผู้ใช้จากการยกเลิกการเชื่อมต่อการเชื่อมต่อเครือข่ายของคอมพิวเตอร์) ในกรณีนี้ไลบรารีเครือข่ายอาจมีข้อผิดพลาด ตอนนี้คุณควรจับข้อยกเว้นและจัดการมัน ซึ่งหมายความว่าในตัวอย่างที่มีการเชื่อมต่อเครือข่ายคุณควรลองเปิดการเชื่อมต่ออีกครั้งหรือแจ้งผู้ใช้หรือสิ่งที่ต้องการ นอกจากนี้เมื่อใดก็ตามที่คุณใช้ catch ให้ตรวจจับเฉพาะข้อยกเว้นที่คุณต้องการจับเสมออย่าใช้ข้อความ catch แบบกว้าง ๆcatch (Exception e)ที่จะจับข้อยกเว้นทั้งหมด สิ่งนี้สำคัญมากเพราะไม่เช่นนั้นคุณอาจบังเอิญจับข้อผิดพลาดและตอบโต้ในทางที่ผิด

try {
    Socket x = new Socket("1.1.1.1", 6789);
    x.getInputStream().read()
} catch (IOException e) {
    System.err.println("Connection could not be established, please try again later!")
}

ทำไมฉันจึงไม่ควรใช้catch (Exception e)?

ลองใช้ตัวอย่างเล็ก ๆ เพื่อแสดงว่าทำไมคุณไม่ควรตรวจจับข้อยกเว้นทั้งหมด:

int mult(Integer a,Integer b) {
    try {
        int result = a/b
        return result;
    } catch (Exception e) {
        System.err.println("Error: Division by zero!");
        return 0;
    }
}

อะไรรหัสนี้พยายามที่จะทำคือการจับArithmeticExceptionที่เกิดจากส่วนที่เป็นไปได้โดย 0. แต่ก็ยังจับเป็นไปได้NullPointerExceptionที่จะถูกโยนทิ้งถ้าaหรือมีb nullซึ่งหมายความว่าคุณอาจได้รับNullPointerExceptionแต่คุณจะถือว่าเป็น ArithmeticException และอาจทำสิ่งที่ผิด ในกรณีที่ดีที่สุดคุณยังคงพลาดว่ามี NullPointerException สิ่งที่คล้ายกันนั้นทำให้การดีบักยากขึ้นมากดังนั้นอย่าทำเช่นนั้น

TLDR

  1. คิดออกว่าอะไรคือสาเหตุของข้อยกเว้นและแก้ไขเพื่อไม่ให้เกิดข้อยกเว้นเลย
  2. ถ้า 1. ไม่สามารถทำได้ให้ตรวจจับข้อยกเว้นเฉพาะแล้วจัดการมัน

    • ไม่เพียงแค่เพิ่มลอง / จับแล้วไม่สนใจข้อยกเว้น! อย่าทำอย่างนั้น!
    • ห้ามใช้catch (Exception e)ให้จับข้อยกเว้นเฉพาะเสมอ นั่นจะช่วยให้คุณปวดหัวมาก

1
คำอธิบายที่ดีสำหรับสาเหตุที่เราควรหลีกเลี่ยงการปิดบังข้อผิดพลาด
Sudip Bhandari

2
ฉันโพสต์คำตอบนี้ดังนั้นคำตอบสูงสุด (เมื่อเรียงลำดับตามกิจกรรม) ไม่ใช่คำตอบที่ผิดธรรมดาฉันไม่ทราบว่าคุณกำลังพูดถึงเรื่องใดเนื่องจากตอนนี้อาจเปลี่ยนไปแล้ว แต่คำตอบที่ได้รับการยอมรับนั้นเป็นสิ่งที่รบกวนกันมากขึ้นแน่นอน)
AxelH

1
สิ่งที่ฉันหมายถึงถูกลบไปแล้วในตอนนี้เท่าที่ฉันรู้ โดยทั่วไปแล้วจะกล่าวว่า "ให้ลอง {} catch (Exception e) {} และละเว้นข้อผิดพลาดทั้งหมด" คำตอบที่ยอมรับนั้นเก่ากว่าคำตอบของฉันดังนั้นฉันจึงตั้งเป้าที่จะให้มุมมองที่ต่างออกไปเล็กน้อยในเรื่องนี้ ฉันไม่คิดว่ามันจะช่วยให้ทุกคนเพียงแค่คัดลอกคำตอบของคนอื่นหรือครอบคลุมสิ่งที่คนอื่นพูดไปแล้วด้วยดี
Dakkaron

มันทำให้เข้าใจผิดที่จะพูดว่า "อย่าจับข้อยกเว้น" นั่นเป็นเพียงกรณีเดียวที่ใช้ ตัวอย่างของคุณยอดเยี่ยม แต่คุณอยู่ตรงจุดสูงสุดของเธรดวนรอบ (อยู่ในระยะทำงาน) คุณควรตรวจจับข้อยกเว้น (หรืออาจจะเป็น Throwable) ที่นั่นและบันทึกไว้เพื่อให้มันไม่หายไปอย่างล่องหน (ยกเว้นข้อผิดพลาดจากการเรียกใช้โดยทั่วไปจะไม่ถูกบันทึกอย่างถูกต้องเว้นแต่คุณได้ตั้งค่าเธรด / ตัวบันทึกของคุณให้ทำ)
Bill K

1
ฉันไม่ได้รวมกรณีพิเศษนี้เพราะมันมีความสำคัญกับมัลติเธรดเท่านั้น ในเธรดเดี่ยวข้อยกเว้น leaked จะฆ่าโปรแกรมและถูกบันทึกอย่างชัดเจน หากมีคนไม่รู้จักวิธีจัดการกับข้อยกเว้นอย่างถูกต้องพวกเขามักจะไม่รู้วิธีใช้มัลติเธรด แต่อย่างใด
Dakkaron

21

เพื่อเพิ่มสิ่งที่ Rob ได้กล่าวถึง การตั้งค่าจุดพักในแอปพลิเคชันของคุณช่วยให้สามารถประมวลผลสแต็กทีละขั้นตอนได้ สิ่งนี้ทำให้นักพัฒนาซอฟต์แวร์สามารถใช้ดีบักเกอร์เพื่อดูว่าจุดใดที่วิธีการทำสิ่งที่ไม่คาดคิด

เนื่องจาก Rob ได้ใช้NullPointerException(NPE) เพื่อแสดงสิ่งทั่วไปเราสามารถช่วยในการลบปัญหานี้ในลักษณะดังต่อไปนี้:

หากเรามีวิธีที่ใช้พารามิเตอร์เช่น: void (String firstName)

ในรหัสของเราเราต้องการประเมินว่าfirstNameมีค่าเราจะทำเช่นนี้:if(firstName == null || firstName.equals("")) return;

ด้านบนทำให้เราไม่สามารถใช้firstNameเป็นพารามิเตอร์ที่ไม่ปลอดภัย ดังนั้นโดยการตรวจสอบ null ก่อนการประมวลผลเราสามารถช่วยให้มั่นใจว่ารหัสของเราจะทำงานอย่างถูกต้อง เพื่อขยายตัวอย่างที่ใช้วัตถุด้วยวิธีการเราสามารถดูที่นี่:

if(dog == null || dog.firstName == null) return;

ข้างต้นเป็นคำสั่งที่เหมาะสมในการตรวจสอบโมฆะเราเริ่มต้นด้วยวัตถุฐานสุนัขในกรณีนี้จากนั้นเริ่มเดินลงต้นไม้ที่มีความเป็นไปได้เพื่อให้แน่ใจว่าทุกอย่างถูกต้องก่อนประมวลผล หากคำสั่งซื้อถูกกลับรายการ NPE อาจถูกโยนทิ้งและโปรแกรมของเราจะขัดข้อง


ตกลง วิธีการนี้สามารถใช้เพื่อค้นหาว่าการอ้างอิงใดในคำสั่งคือnullเมื่อมีNullPointerExceptionการตรวจสอบตัวอย่างเช่น
Rob Hruska

16
เมื่อจัดการกับ String ถ้าคุณต้องการใช้เท่ากับวิธีฉันคิดว่ามันจะดีกว่าที่จะใช้ค่าคงที่ในด้านซ้ายของการเปรียบเทียบเช่นนี้: แทนที่จะ: ถ้า (firstName == null || firstName.equals ("" )) ผลตอบแทน; ฉันใช้เสมอ: if ((""). เท่ากับ (ชื่อแรก)) สิ่งนี้จะป้องกันข้อยกเว้น Nullpointer
Torres

15

มีอีกหนึ่งคุณสมบัติสแต็คที่เสนอโดยตระกูล Throwable - ความเป็นไปได้ในการจัดการข้อมูลการติดตามสแต็ก

พฤติกรรมมาตรฐาน:

package test.stack.trace;

public class SomeClass {

    public void methodA() {
        methodB();
    }

    public void methodB() {
        methodC();
    }

    public void methodC() {
        throw new RuntimeException();
    }

    public static void main(String[] args) {
        new SomeClass().methodA();
    }
}

การติดตามสแต็ก:

Exception in thread "main" java.lang.RuntimeException
    at test.stack.trace.SomeClass.methodC(SomeClass.java:18)
    at test.stack.trace.SomeClass.methodB(SomeClass.java:13)
    at test.stack.trace.SomeClass.methodA(SomeClass.java:9)
    at test.stack.trace.SomeClass.main(SomeClass.java:27)

การติดตามสแต็กที่จัดการ:

package test.stack.trace;

public class SomeClass {

    ...

    public void methodC() {
        RuntimeException e = new RuntimeException();
        e.setStackTrace(new StackTraceElement[]{
                new StackTraceElement("OtherClass", "methodX", "String.java", 99),
                new StackTraceElement("OtherClass", "methodY", "String.java", 55)
        });
        throw e;
    }

    public static void main(String[] args) {
        new SomeClass().methodA();
    }
}

การติดตามสแต็ก:

Exception in thread "main" java.lang.RuntimeException
    at OtherClass.methodX(String.java:99)
    at OtherClass.methodY(String.java:55)

2
ฉันไม่รู้ว่าฉันรู้สึกอย่างไรเกี่ยวกับสิ่งนี้ ... ด้วยธรรมชาติของเธรดฉันจะแนะนำ devs ใหม่ให้ไม่กำหนดสแต็กการติดตามของตนเอง
PeonProgrammer

15

เพื่อให้เข้าใจชื่อ : การติดตามสแต็กคือรายชื่อของข้อยกเว้น (หรือคุณสามารถพูดรายการ "สาเหตุโดย") จากข้อยกเว้นพื้นผิวส่วนใหญ่ (เช่นการยกเว้นบริการเลเยอร์) ไปยังที่ลึกที่สุด (เช่นข้อยกเว้นฐานข้อมูล) เช่นเดียวกับเหตุผลที่เราเรียกมันว่า 'สแต็ค' เป็นเพราะสแต็คคือ First in Last Out (FILO) ข้อยกเว้นที่ลึกที่สุดเกิดขึ้นในตอนเริ่มต้นจากนั้นมีข้อยกเว้นเกิดขึ้นหลาย ๆ ชุดข้อยกเว้นพื้นผิวเป็นครั้งสุดท้าย เกิดขึ้นในเวลา แต่เราเห็นมันในสถานที่แรก

คีย์ 1 : สิ่งที่ยุ่งยากและสำคัญที่นี่ต้องเข้าใจคือสาเหตุที่ลึกที่สุดอาจไม่ใช่ "สาเหตุที่แท้จริง" เพราะถ้าคุณเขียน "รหัสไม่ดี" บางอย่างมันอาจทำให้เกิดข้อยกเว้นบางอย่างภายใต้ชั้นที่ลึกกว่า ตัวอย่างเช่นเคียวรี sql ที่ไม่ดีอาจทำให้การเชื่อมต่อ SQLServerException รีเซ็ตในส่วนต่อท้ายแทนที่จะเป็นข้อผิดพลาดของ syndax ซึ่งอาจอยู่ตรงกลางของสแต็ก

-> ค้นหาสาเหตุที่ตรงกลางคืองานของคุณ ป้อนคำอธิบายรูปภาพที่นี่

คีย์ 2 : สิ่งที่ยุ่งยาก แต่สำคัญอีกอย่างหนึ่งอยู่ในแต่ละบล็อก "สาเหตุโดย" บรรทัดแรกคือเลเยอร์ที่ลึกที่สุดและเกิดขึ้นเป็นอันดับแรกสำหรับบล็อกนี้ ตัวอย่างเช่น

Exception in thread "main" java.lang.NullPointerException
        at com.example.myproject.Book.getTitle(Book.java:16)
           at com.example.myproject.Author.getBookTitles(Author.java:25)
               at com.example.myproject.Bootstrap.main(Bootstrap.java:14)

Book.java:16 ถูกเรียกโดย Auther.java:25 ซึ่งถูกเรียกโดย Bootstrap.java:14, Book.java:16 เป็นสาเหตุหลัก ที่นี่แนบไดอะแกรมเรียงลำดับสแต็กการติดตามตามลำดับเวลา ป้อนคำอธิบายรูปภาพที่นี่


8

เพียงเพื่อเพิ่มไปยังตัวอย่างอื่น ๆ มีชั้นเรียน (ซ้อน) ภายในที่ปรากฏพร้อมกับ$เครื่องหมาย ตัวอย่างเช่น:

public class Test {

    private static void privateMethod() {
        throw new RuntimeException();
    }

    public static void main(String[] args) throws Exception {
        Runnable runnable = new Runnable() {
            @Override public void run() {
                privateMethod();
            }
        };
        runnable.run();
    }
}

จะส่งผลให้การติดตามสแต็กนี้:

Exception in thread "main" java.lang.RuntimeException
        at Test.privateMethod(Test.java:4)
        at Test.access$000(Test.java:1)
        at Test$1.run(Test.java:10)
        at Test.main(Test.java:13)

5

โพสต์อื่น ๆ จะอธิบายว่าการติดตามสแต็กคืออะไร แต่ก็ยังสามารถใช้งานได้ยาก

ถ้าคุณได้รับกองติดตามและต้องการที่จะติดตามสาเหตุของข้อยกเว้นที่เป็นจุดเริ่มต้นที่ดีในการทำความเข้าใจว่ามันคือการใช้Trace คอนโซล Java สแต็คในEclipse หากคุณใช้ IDE อื่นอาจมีคุณสมบัติที่คล้ายกัน แต่คำตอบนี้เกี่ยวกับ Eclipse

ขั้นแรกตรวจสอบให้แน่ใจว่าคุณมีแหล่ง Java ของคุณทั้งหมดที่สามารถเข้าถึงได้ในโครงการ Eclipse

จากนั้นในเปอร์สเปคทีฟ Javaคลิกที่แท็บคอนโซล (โดยปกติจะอยู่ด้านล่าง) ถ้ามุมมองคอนโซลไม่สามารถมองเห็นไปที่ตัวเลือกเมนูWindow -> แสดงดูและเลือกคอนโซล

จากนั้นในหน้าต่างคอนโซลคลิกที่ปุ่มต่อไปนี้ (ด้านขวา)

ปุ่มคอนโซล

จากนั้นเลือกJava Stack Trace Consoleจากรายการดร็อปดาวน์

วางการติดตามสแต็กของคุณลงในคอนโซล จากนั้นจะแสดงรายการลิงก์ในซอร์สโค้ดของคุณและซอร์สโค้ดอื่น ๆ

นี่คือสิ่งที่คุณอาจเห็น (ภาพจากเอกสารประกอบ Eclipse):

ไดอะแกรมจากเอกสาร Eclipse

การเรียกเมธอดล่าสุดที่ทำจะอยู่ด้านบนสุดของสแต็กซึ่งเป็นบรรทัดบนสุด (ไม่รวมข้อความ) การลงไปกองซ้อนก็ย้อนเวลากลับไป บรรทัดที่สองเป็นวิธีการที่เรียกบรรทัดแรก ฯลฯ

หากคุณใช้ซอฟต์แวร์โอเพนซอร์ซคุณอาจต้องดาวน์โหลดและแนบไฟล์แนบกับโครงการของคุณหากคุณต้องการตรวจสอบ ดาวน์โหลดขวดซอร์สในโครงการของคุณเปิดโฟลเดอร์อ้างอิงห้องสมุดเพื่อค้นหา jar ของคุณสำหรับโมดูลโอเพนซอร์ซของคุณ (อันที่มีไฟล์คลาส) จากนั้นคลิกขวาเลือกคุณสมบัติและแนบ jar ต้นทาง

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.