Phụ lục D: Gỡ lỗi

Trở về Mục lục cuốn sách

Chiến thuật gỡ lỗi hay nhất còn tuỳ thuộc vào loại lỗi bạn mắc phải:

  • Lỗi cú pháp tạo ra bởi trình biên dịch, nhằm chỉ định có trục trặc trong cú pháp của chương trình. Chẳng hạn: bỏ mất dấu chấm phẩy ở cuối câu lệnh.
  • Các biệt lệ được tạo ra nếu có điều gì trục trặc khi chương trình đang chạy. Chẳng hạn: một vòng đệ quy vô hạn cuối cùng sẽ gây nên biệt lệ StackOverflowException.
  • Lỗi logic khiến cho chương trình thực hiện việc làm sai. Chẳng hạn, một biểu thức có thể không được tính toán đúng theo trình tự mà bạn định liệu, cho ra kết quả không lường trước.

Các mục tiếp sau đây được xếp theo kiểu lỗi; có những kĩ thuật xử lý dùng được cho nhiều loại lỗi khác nhau.

D.1  Lỗi cú pháp

Hình thức gỡ lỗi hay nhất là ở đó bạn không phải làm gì, bởi ngay từ đầu bạn đã tránh mắc phải lỗi. Trong mục trước, tôi đã đề xuất những chiến lược phát triển để giảm thiểu lỗi và tạo điều kiện phát hiện sớm lỗi khi mắc phải chúng. Điểm mấu chốt là lấy một chương trình chạy được làm điểm khởi đầu, và mỗi lúc chỉ thêm rất ít mã lệnh. Khi có lỗi, bạn sẽ biết rõ là lỗi này nằm ở đâu.

Dù vậy, bạn có thể sẽ rơi vào một trong những tình huống sau. Với mỗi tình huống, tôi lại có đề xuất cách xử lý thích hợp.

Trình biên dịch bày ra la liệt những thông báo lỗi.

Nếu trình biên dịch có báo đến 100 lỗi đi nữa, thì điều này cũng không có nghĩa là chương trình bạn có 100 lỗi. Mỗi khi trình biên dịch gặp một lỗi, nó thường đi chệch ra khỏi luồng thực thi một quãng. Nó sẽ cố gắng hồi phục và tiếp tục theo luồng thực thi sau lỗi đầu tiên, nhưng đôi khi nó thông báo những lỗi đáng ngờ.

Chỉ có thông báo lỗi đầu tiên mới đáng tin cậy. Tôi gợi ý rằng bạn sửa từng lỗi một, rồi biên dịch lại chương trình. Bạn có thể thấy một dấu chấm phẩy có thể “sửa được” 100 lỗi.

Tôi đang gặp một lỗi biên dịch thật kì quặc và nó chẳng biến đi.

Trước hết, hãy đọc kĩ thông báo lỗi này. Thông báo được viết bằng ngôn ngữ chuyên dụng và rất ngắn ngủi, song thường ẩn chứa một thông tin cốt lõi.

Nếu không có gì khác, thông báo sẽ cho bạn biết trục trặc xảy ra ở đâu trong chương trình. Thực ra, nó cho bạn biết trình biên dịch đang ở đâu khi phát hiện thấy lỗi, chứ không nhất thiết là nơi có lỗi. Hãy dùng thông tin thu nhận từ trình biên dịch như một chỉ dẫn, song nếu bạn không thấy lỗi theo hướng chỉ dẫn đó thì hãy mở rộng việc tìm kiếm ra.

Nói chung lỗi sẽ nằm ở trước vị trí thông báo lỗi, song cũng có những trường hợp mà lỗi nằm ở nơi khác hẳn. Chẳng hạn, nếu bạn nhận được thông báo lỗi ở một lời kích hoạt phương thức, thì có khi lỗi thực sự lại nằm ở lời định nghĩa phương thức.

Nếu bạn chưa nhanh chóng tìm ra được lỗi, thì hãy lấy hơi thật sâu rồi nhìn rộng ra cả chương trình. Hãy đảm bảo chắc rằng chương trình được viết thụt đầu dòng đúng chuẩn; điều này giúp ta phát hiện lỗi cú pháp dễ dàng hơn.

Bây giờ, hãy tìm kiếm những lỗi dễ mắc phải:

  1. Kiểm tra tất cả những cặp ngoặc tròn và ngoặc nhọn phải cân xứng và được lồng ghép đúng thứ tự. Tất cả lời định nghĩa phương thức phải được lồng trong một lời định nghĩa lớp. Tất cả các câu lệnh của chương trình phải đặt trong định nghĩa phương thức.
  2. Hãy nhớ rằng viết chữ in thì khác với chữ thường.
  3. Kiểm tra dấu chấm phẩy ở cuối câu lệnh (và không có dấu chấm phẩy theo sau ngoặc nhọn).
  4. Hãy đảm bảo chắc rằng mỗi chuỗi kí tự trong mã lệnh phải có đôi dấu nháy kép. Đảm bảo chắc rằng bạn dùng nháy kép cho chuỗi và nháy đơn cho kí tự.
  5. Với từng câu lệnh gán, hãy đảm bảo rằng kiểu dữ liệu ở bên vế trái cũng giống như kiểu vế phải. Hãy đảm bảo rằng biểu thức bên vế trái là một tên biến hoặc đối tượng nào khác mà bạn có thể gán giá trị vào cho nó (như một phần tử của mảng).
  6. Với từng lần kích hoạt phương thức, hãy đảm bảo rằng các đối số được cung cấp đã xếp đúng vị trí, và có đúng kiểu, và đối tượng mà bạn đang kích hoạt phương thức lên cũng có đúng kiểu.
  7. Nếu bạn đang kích hoạt một phương thức trả giá trị, hãy đảm bảo chắc rằng bạn thao tác với giá trị trả về này. Nếu bạn kích hoạt một phương thức rỗng, hãy đảm bảo chắc rằng mình không thử làm gì với kết quả.
  8. Nếu bạn đang kích hoạt một phương thức đối tượng, hãy chắc rằng bạn đang kích hoạt nó với một đối tượng đúng kiểu. Nếu bạn đang kích hoạt một phương thức lớp từ bên ngoài phương thức mà nó được định nghĩa, hãy đảm bảo chắc rằng bạn đã chỉ định tên lớp này.
  9. Bên trong một phương thức đối tượng, bạn có thể tham chiếu tới các biến thực thể mà không quy định đối tượng nào. Nếu bạn thử làm điều này trong một phương thức lớp, bạn sẽ nhận được thông báo kiểu như, “Tham chiếu tĩnh tới biến không tĩnh.”

Nếu không có giải pháp nào kể trên phát huy tác dụng, hãy xem mục kế tiếp…

Tôi không thể biên dịch được chương trình dù đã cố gắng mọi cách.

Nếu như trình biên dịch nói rằng có lỗi mà bạn không nhìn thấy, thì có khả năng là do bạn và trình biên dịch không cùng nhìn vào đoạn mã lệnh. Hãy kiểm tra môi trường phát triển đang dùng để đảm bảo chắc rằng chương trình bạn đang soạn thảo chính là chương trình đang được biên dịch. Nếu bạn còn chưa chắc chắn, hãy thử cố tình đưa vào một lỗi cú pháp ngay ở đầu chương trình. Bây giờ hãy biên dịch lại. Nếu trình biên dịch vẫn không tìm thấy lỗi mới đó, thì có lẽ bạn đã thiết lập môi trường tích hợp sai quy cách.

Nếu bạn đã kiểm tra mã lệnh một lượt rồi, và chắc chắn là trình biên dịch đang làm việc với đúng mã lệnh mình soạn thảo, thì đã đến lúc dùng phương pháp “tuyệt vọng”: gỡ lỗi bằng cách chia đôi.

  • Hãy tạo một bản sao của file hiện hành. Nếu bạn đang soạn file Bob.java, hãy lập bản sao có tên Bob.java.old.
  • Xóa bớt một nửa mã lệnh từ file Bob.java. Thử biên dịch lại.
    • Nếu giờ đây chương trình biên dịch được thì bạn biết rằng lỗi nằm ở nửa kia. Hãy phục hồi lại nửa vừa xóa rồi lặp lại cách thử.
    • Nếu chương trình vẫn không biên dịch nổi, thì lỗi sai phải nằm ở nửa còn lại này. Hãy xóa đi một nửa số mã lệnh rồi lặp lại cách thử.
  • Một khi bạn đã tìm thấy và sửa được lỗi, thì hãy dần dần phục hồi lại phần mã lệnh đã xóa, từng ít từng ít một.

Quá trình này thật lộm cộm, nhưng thật ra là nhanh hơn so với bạn nghĩ; và cách này cũng rất đáng tin cậy.

Tôi đã làm theo chỉ dẫn của trình biên dịch mà vẫn chưa có tác dụng.

Một số thông báo của trình biên dịch lại có đoạn lời khuyên nhủ, chẳng hạn “class Golfer must be declared abstract. It does not define int compareTo(java.lang.Object) from interface java.lang.Comparable.” (lớp Golfer phải được khai báo là trừu tượng. Nó không định nghĩa int compareTo(java.lang.Object) từ trong interface java.lang.Comparable.) Nghe có vẻ như trình biên dịch đang bảo bạn khai báo Golfer là lớp trừu tượng, và nếu bạn đọc cách này thì có lẽ chẳng hiểu đó là gì hoặc cách làm thế nào.

Thật may là, trình biên dịch đã sai. Trong trường hợp này, giải pháp là đảm bảo rằng Golfer có một phương thức mang tên compareTo để nhận tham số là một Object.

Đừng để trình biên dịch dắt mũi bạn. Các thông báo lỗi cho bạn chứng cớ là đã có trục trặc, nhưng cách khắc phục mà nó chúng đưa ra đều không đáng tin cậy.

D.2  Lỗi thực thi

Chương trình tôi viết bị treo.

Nếu một chương trình dừng lại và hình như không làm gì, ta nói rằng nó đã bị treo. Thường thì điều này nghĩa là nó mắc phải một vòng lặp vô hạn hoặc đệ quy vô hạn.

  • Nếu có một vòng lặp cụ thể mà bạn nghi ngờ có vấn đề, hãy thêm một lệnh print ngay trước vòng lặp, để in ra “tien vao vong lap” và một lệnh khác ngay sau vòng lặp, in ra “thoat khoi vong lap”. Chạy chương trình. Nếu bạn thấy được thông điệp thứ nhất mà không thấy cái thứ hai thì đã có một vòng lặp vô hạn. Xem tiếp mục “Vòng lặp vô hạn” dưới đây.
  • Ở hầu hết trường hợp, đệ quy vô hạn sẽ làm cho chương trình chạy một lúc và sau đó phát ra biệt lệ StackOverflowException. Nếu điều này xảy ra, hãy xem tiếp mục “Đệ quy vô hạn” sau đây. Nếu bạn không gặp biệt lệ StackOverflowException này nhưng nghi ngờ rằng có vấn đề xảy ra với một phương thức hoặc hàm đệ quy, bạn vẫn có thể sử dụng các kĩ thuật trong mục “Đệ quy vô hạn”.
  • Nếu cách này cũng không có tác dụng thì có thể là bạn chưa hiểu luồng thực hiện của chương trình. Hãy đọc tiếp mục “Luồng thực thi” bên dưới.

Vòng lặp vô hạn

Nếu bạn nghĩ rằng bạn có một vòng lặp vô hạn và cho rằng mình đã biết được vòng lặp nào gây ra vấn đề, thì hãy thêm một lệnh print tại điểm cuối vòng lặp và in ra giá trị các biến trong điều kiện cùng với giá trị của điều kiện.

Chẳng hạn:

    while (x > 0 && y < 0) { 
      // thao tác gì đó với x 
      // thao tác gì đó với y 
      System.out.println("x: " + x); 
      System.out.println("y: " + y); 
      System.out.println("điều kiện: " + (x > 0 && y < 0)); 
   }

Bây giờ khi chạy chương trình, bạn sẽ thấy ba dòng kết quả với mỗi lần chạy qua vòng lặp. Lần cuối cùng chạy qua vòng lặp điều kiện sẽ phải là false. Nếu vòng lặp tiếp tục chạy, bạn sẽ nhìn được các giá trị của x và y, và có thể hình dung được tại sao chúng không được cập nhật đúng.

Đệ quy vô hạn

Trong nhiều trường hợp, một vòng lặp đệ quy sẽ khiến chương trình phát biệt lệ StackOverflowException. Nhưng nếu chương trình chậm chạp có thể nó sẽ tốn nhiều thời gian để bị đầy ngăn xếp.

Nếu bạn nghi ngờ rằng một hàm hoặc phương thức nào đó gây ra đệ quy vô hạn, hãy bắt đàu kiểm tra để chắc rằng có một trường hợp cơ sở. Nói cách khác, cần phải có điều kiện nào đó để khiến cho hàm hoặc phương thức trả về mà không gọi đệ quy nữa. Nếu không, bạn cần phải nghĩ lại thuật toán và tìm ra một trường hợp cơ sở.

Nếu có một trường hợp cơ sở nhưng chương trình dường như không đạt đến đó, thì hãy thêm câu lệnh print vào điểm đầu của hàm hoặc phương thức để in ra các tham biến. Bây giờ khi chạy chương trình, bạn sẽ thấy một ít dòng kết quả mỗi lần hàm hoặc phương thức được gọi đến, và sẽ thấy giá trị các tham số. Nếu tham số không thay đổi với xu hướng về trường hợp cơ sở, bạn sẽ thấy được tại sao.

Luồng thực thi

Nếu bạn không chắc chắn về luồng thực hiện trong chương trình, hãy thêm các câu lệnh print vào điểm đầu của mỗi hàm với thông báo kiểu như “bắt đầu phương thức foo”, trong đó foo là tên phương thức.

Bây giờ khi chạy chương trình, nó sẽ in ra một dấu vết của mỗi phương thức khi được kích hoạt đến.

Bạn cũng có thể in ra những đối số mà từng phương thức nhận được. Khi chạy chương trình, hãy kiểm tra xem các giá trị này hợp lý không, và kiểm tra một trong những lỗi thường mắc phải nhất—cung cấp các đối số sai thứ tự.

Khi chạy chương trình tôi nhận được một biệt lệ.

Khi có biệt lệ xảy ra, Java sẽ in một thông báo trong đó có tên của biệt lệ, dòng lệnh có vấn đề, và một lần dấu vết trên ngăn xếp (stack trace). Bản thân cái lần vết này chứa thông tin về phương thức đang được chạy, và phương thức kích hoạt nó, rồi phương thức kích hoạt phương thức đó, và cứ như vậy. 

Bước đầu tiên là kiểm tra vị trí trong chương trình nơi mà lỗi xuất hiện, đồng thời thử hình dung điều gì đã xảy ra.

NullPointerException:
Bạn cố gắng truy cập một biến thực thể hoặc kích hoạt một phương thức trên đối tượng mà bản thân nó đang là null. Bạn cần phải hình dung ra biến nào là null rồi hình dung xem bằng cách nào dẫn đến hiện tượng đó. Hãy nhớ rằng khi khai báo một biến với một kiểu đối tượng, thì ban đầu nó vẫn là null đến tận khi bạn gán giá trị cho. Chẳng hạn, đoạn mã sau gây ra biệt lệ NullPointerException:

Point blank; 
System.out.println(blank.x);
ArrayIndexOutOfBoundsException:
Chỉ số mà bạn đang dùng để truy cập một mảng đã lớn hơn array.length-1. Nếu bạn tìm được vị trí lỗi, hãy thêm vào câu lệnh print vào ngay trước lỗi này để hiển thị giá trị của chỉ số cùng với chiều dài của mảng. Liệu mảng này có kích thước đúng chưa? Chỉ số có đúng không? Bây giờ tìm ngược lại dọc chương trình và xem mảng này cùng với chỉ số đó bắt nguồn từ đâu. Hãy tìm lệnh gán gần nhất và xem nó có thực hiện đúng không. Nếu không có cái nào là tham số, thì hãy đến chỗ phương thức được kích hoạt và xem các giá trị này đến từ đâu.
StackOverFlowException:
Xem “Đệ quy vô hạn.”
FileNotFoundException:
Điều này nghĩa là Java không tìm thấy file cần thiết. Nếu bạn đang dùng một môi trường phát triển dựa trên các dự án, như Eclipse, thì có khả năng là bạn sẽ phải nhập file đó vào trong dự án đang mở. Còn không thì hãy đảm bảo chắc rằng file đó tồn tại và đường dẫn đến nó được ghi đúng. Vấn đề này tuỳ thuộc vào hệ thống file trên máy tính của bạn, bởi vậy có thể khó dò tìm.
ArithmeticException:
Biệt lệ phát ra khi có trục trặc với phép toán số học, thường là phép chia cho số không.

Tôi đã thêm vào quá nhiều lệnh print đến nỗi bây giờ ngập tràn kết quả đầu ra.

Một trong những vấn đề khi dùng lệnh print để gỡ lỗi là việc bạn có thể bị chìm trong kết quả ra. Có hai cách tiếp tục: đơn giản hóa đầu ra hoặc đơn giản hóa chương trình.

Để giản hóa kết quả đầu ra, bạn cần xóa bỏ hoặc đưa vào chú thích những dòng lệnh print vốn không có tác dụng, hoặc kết hợp chúng lại, hoặc sửa định dạng đầu ra để dễ hiểu hơn.

Để giản hóa chương trình, có vài cách làm được. Trước hết, hãy giảm quy mô của bài toán xuống. Chẳng hạn, nếu bạn cần tìm kiếm trong mảng, hãy làm với một mảng nhỏ. Nếu chương trình nhận đầu vào từ phía người dùng, hãy cho những dữ liệu vào đơn giản mà gây ra lỗi.

Đồng thời hãy dọn dẹp chương trình. Hãy bỏ những đoạn mã chết và tổ chức lại chương trình để nó càng dễ đọc càng tốt. Chẳng hạn, nếu bạn nghi rằng vấn đề nằm ở một đoạn nằm sâu trong chương trình, hãy thử viết lại nó với cấu trúc đơn giản hơn. Nếu bạn nghi ngờ rằng có một phương thức lớn, hãy thử chẻ nhỏ thành những phương thức nhỏ và kiểm tra lần lượt.

Thông thường quá trình tìm ra trường hợp thử đơn giản nhất sẽ dẫn bạn đến điểm gây lỗi. Chẳng hạn, nếu bạn thấy chương trình chạy được trong trường hợp số phần tử trong mảng là chẵn nhưng không được khi số phần tử là lẻ, thì điều đó sẽ là dấu vết cho thấy điều gì đang diễn ra.

Việc tổ chức lại một đoạn mã có thể giúp bạn phát hiện những lỗi nhỏ. Nếu bạn thực hiện sửa đổi mà nghĩ rằng nó không ảnh hưởng gì đến chương trình, và lúc có ảnh hưởng thì đó sẽ là bài học cho bạn.

D.3  Lỗi logic

Chương trình tôi viết ra không hoạt động đúng.

Lỗi logic rất khó tìm, vì trình biên dịch và hệ thống lúc thực thi không cung cấp thông tin gì về sự trục trặc. Chỉ có bạn mới biết rằng chương trinh cần phải thực hiện điều gì.

Bước đầu tiên là tạo lập một kết nối giữa nội dung chương trình và biểu hiện mà bạn quan sát được. Bạn cần giả thiết về điều thật sự mà chương trình đang thực hiện. Bạn cần tự hỏi mình những điều sau:

  • Có điều gì mà chương trình cần phải làm nhưng dường như nó không làm hay không? Hãy tìm ra đoạn mã lệnh thực hiện tính năng đó và chắc rằng nó được thực thi khi bạn nghĩ rằng lẽ ra nó phải chạy.
  • Có điều gì đang diễn ra mà lẽ ra không nên có nó? Hãy tìm đoạn mã trong chương trình mà thực hiện tính năng đó rồi xem liệu nó có được thực khi trong khi đáng lẽ thì không.
  • Có đoạn mã nào tạo ra một hiệu ứng mà không như bạn mong đợi không? Hãy chắc rằng bạn hiểu được đoạn mã nghi vấn, đặc biệt khi nó liên quan đến việc kích hoạt phương thức Java. Hãy đọc tài liệu về những phương thức, rồi thử bằng những trường hợp kiểm tra đơn giản. Có khi chúng lại không làm việc mà bạn nghĩ rằng chúng sẽ làm.

Để lập trình, bạn phải có một mô hình tưởng tượng về cách thức hoạt động của chương trình. Nếu bạn viết một chương trình mà không thực hiện đúng việc bạn mong đợi, thì thường là vấn đề không nằm ở chương trình; nó nằm ở mô hình tưởng tượng của bạn.

Cách tốt nhất để sửa mô hình tưởng tượng cho đúng là chia chương trình thành những bộ phận (thường là các lớp và phương thức) rồi kiểm tra chạy thử từng bộ phận một cách độc lập. Một khi bạn thấy sự khác biệt giữa mô hình và thực tế, bạn sẽ có thể giải quyết vấn đề.

Sau đây là một số lỗi logic thông thường cần phải kiểm tra:

  • Luôn nhớ rằng phép chia nguyên làm tròn xuống. Nếu bạn muốn cả phần thập phân, hãy dùng số double.
  • Số phẩy động chỉ là gần đúng, nên bạn đừng lệ thuộc vào độ chính xác tuyệt đối.
  • Nói chung, hãy dùng số nguyên cho những thứ đếm được và dùng số phẩy động cho thứ đo được.
  • Nếu bạn dùng toán tử gán (=) thay vì toán tử bằng (==) trong điều kiện của một lệnh ifwhile, hoặc for, bạn có thể sẽ nhận một biểu thức về mặt cú pháp thì đúng nhưng về ngữ nghĩa thì sai.
  • Khi bạn áp dụng toán tử bằng (==) với đối tượng, nó sẽ kiểm tra identity. Nếu bạn có ý muốn kiểm tra độ tương đương, hãy dùng phương thức equals.
  • Đối với các kiểu dữ liệu do người dùng định nghĩa, equals sẽ kiểm tra identity. Nếu bạn muốn một kí hiệu khác cho tương đồng, bạn phải ghi đè lên nó.
  • Kế thừa có thể dẫn đến những lỗi logic rất chi li, bởi bạn có thể chạy mã lệnh được kế thừa mà không nhận ra nó. Hãy xem mục “Luồng thực thi” ở trên.

Tôi có một biểu thức lớn và gai góc mà chẳng hoạt động theo sự mong đợi.

Việc viết những biểu thức phức tạp cũng tốt miễn là chúng dễ đọc, nhưng chúng có thể làm việc gỡ lỗi gặp khó khăn. Thông thường nên chẻ nhỏ một biểu thức thành một loạt các lệnh gán cho những biến tạm thời.

Chẳng hạn:

    rect.setLocation(rect.getLocation().translate( -rect.getWidth(), -rect.getHeight()));

Có thể được viết lại thành

    int dx = -rect.getWidth(); 
    int dy = -rect.getHeight(); 
    Point location = rect.getLocation(); 
    Point newLocation = location.translate(dx, dy); 
    rect.setLocation(newLocation);

Dạng mã lệnh chi tiết thì dễ đọc hơn vì tên biến cho ta bản thân đã giúp giải thích rõ thêm, và cũng dễ gỡ lỗi hơn vì bạn có thể kiểm tra kiểu của những biến trung gian cùng việc hiển thị giá trị của chúng.

Một vấn đề khác có thể xảy ra với những biểu thức lớn là thứ tự thực hiện phép tính có thể không như bạn mong muốn. Chẳng hạn, để lượng giá biểu thức x / 2π , có thể bạn đã viết:

    double y = x / 2 * Math.PI;

Điều này không đúng vì các phép nhân và chia có cùng thứ tự ưu tiên và được lượng giá từ trái sang phải. Vì vậy biểu thức này sẽ tính xπ / 2.

Một cách hay để gỡ lỗi biểu thức là thêm vào những cặp ngoặc đơn để giúp cho thứ tự lượng giá được rõ ràng:

    double y = x / (2 * Math.PI);

Phiên bản này thì đúng đắn, và dễ đọc hơn đối với người không ghi nhớ thứ tự thực hiện phép toán.

Phương thức tôi đã viết không trả lại giá trị như dự kiến.

Nếu bạn viết một câu lệnh return (trả về) với một biểu thức phức tạp, thì bạn đã không có cơ hội in ra giá trị này trước khi trả nó về. Một lần nữa, bạn có thể dùng biến tạm. Chẳng hạn, thay vì

  public Rectangle intersection(Rectangle a, Rectangle b) { 
    return new Rectangle( Math.min(a.x, b.x), Math.min(a.y, b.y), Math.max(a.x+a.width, b.x+b.width)-Math.min(a.x, b.x) Math.max(a.y+a.height, b.y+b.height)-Math.min(a.y, b.y) ); 
  }

bạn đã có thể viết

  public Rectangle intersection(Rectangle a, Rectangle b) { 
    int x1 = Math.min(a.x, b.x); 
    int y2 = Math.min(a.y, b.y); 
    int x2 = Math.max(a.x+a.width, b.x+b.width); 
    int y2 = Math.max(a.y+a.height, b.y+b.height); 
    Rectangle rect = new Rectangle(x1, y1, x2-x1, y2-y1); 
    return rect; 
  }

Giờ thì bạn đã có cơ hội hiển thị bất kì biến trung gian nào trước khi trả về. Và bằng cách dùng lại x1 cùng y1, bạn cũng làm mã lệnh gọn hơn

Câu lệnh print mà tôi viết chẳng làm được gì cả

Nếu bạn dùng phương thức println, kết quả đầu ra sẽ hiện lên ngay; nhưng nếu bạn dùng print (ít nhất là có những môi trường phát triển như vậy), kết quả sẽ được lưu lại mà không hiện lên cho đến tận khi có dấu xuống dòng tiếp theo. Nếu chương trình kết thúc mà không in ra một dòng mới thì có thể bạn chẳng còn nhìn thấy được kết quả lưu lại nữa.

Nếu bạn nghi ngờ là đã có điều này xảy ra, hãy chuyển một số hoặc tất cả các lệnh print trong chương trình thành println.

THẬT SỰ TÔI RẤT, RẤT VƯỚNG MẮC VÀ CẦN ĐƯỢC GIÚP ĐỠ.

Trước hết, hãy thử rời khỏi máy tính trong vài phút. Máy tính phát ra sóng từ gây ảnh hưởng đến não, với các triệu chứng sau:

  • Cáu giận.
  • Tin tưởng vào lực siêu nhiên (“máy tính này ghét tôi”) và những ảo tưởng (“chương trình chỉ chạy khi tôi đội ngược mũ”).
  • Lập trình bước ngẫu nhiên (nỗ lực lập trình bằng cách viết tất cả các trường hợp chương trình có thể có và chọn ra một phiên bản hoạt động đúng).

Nếu bạn tự thấy mình mắc phải một trong số các triệu chứng trên, hãy đứng dậy và đi dạo. Khi đã tĩnh tâm hẳn, hãy nghĩ lại chương trình. Nó đang làm điều gì? Đâu là các nguyên nhân gây ra biểu hiện đó? Lần cuối cùng chương trình cọn chạy được là lúc nào, và sau đó bạn thực hiện những điều gì?

Đôi khi phát hiện lỗi chỉ là vấn đề thời gian. Tôi thường tìm thấy lỗi trong lúc rời xa khỏi máy tính và để trí óc khuây khỏa. Một số nơi tốt nhất để thoát khỏi máy gồm có trên tàu, khi đi tắm, và trước khi đi ngủ.

KHÔNG, TÔI THẬT SỰ MUỐN GIÚP ĐỠ.

Điều đó xảy ra. Ngay cả những lập trình viên giỏi nhất đôi lúc cũng bị bí. Đôi khi bạn làm một chương trình lâu quá đến nỗi không thể phát hiện ra lỗi. Tìm một người có góc nhìn khác chính là điều cần thiết.

Trước khi yêu cầu giúp đỡ, bạn hãy chuẩn bị kĩ. Chương trình phải càng đơn giản càng tốt, và hãy phân tích trên dữ liệu đầu vào nhỏ nhất có thể gây lỗi. Bạn cần có các lệnh print ở những vị trí thích hợp (và kết quả đầu ra phải dễ hiểu). Bạn cần hiểu rõ vấn đề để có thể diễn đạt nó một cách ngắn gọn.

Khi đưa người đến giúp, hãy chắc chắn rằng bạn cung cấp đủ thông tin mà họ cần:

  • Nếu có thông báo lỗi, thông báo đó là gì và nó chỉ định phần nào trong chương trình?
  • Việc cuối cùng mà bạn thao tác trước khi lỗi này xảy ra là gì? Những dòng lệnh nào bạn vừa mới viết gần đây nhất, hay trường hợp chạy thử gần đây nhất mới bị thất bại là gì?
  • Bạn đã thử những biện pháp gì rồi, và thu hoạch được gì?

Đến khi bạn giải thích được khúc mắc cho người ta, có thể bạn sẽ thấy kết quả. Hiện tượng này thường gặp đến nỗi người ta gợi ý một kĩ thuật gỡ lỗi có tên “vịt cao su.” Sau đây là cách hoạt động:

  1. Mua một con vịt cao su chuẩn.
  2. Khi bạn thực sự đã vướng mắc trong lập trình, hãy đặt con vịt cao su trước mặt rồi nói, “Vịt ơi, tao đang vướng mắc đây. Hoàn cảnh là như thế này…”
  3. Trình bày vấn đề cho con vịt.
  4. Tìm thấy hướng giải quyết.
  5. Cám ơn con vịt cao su.

Tôi không hề nói đùa. Hãy xem http://en.wikipedia.org/wiki/Rubber_duck_debugging.

Tôi đã tìm thấy lỗi rồi!

Khi bạn tìm thấy lỗi, thông thường cách sửa sẽ là hiển nhiên. Nhưng không phải luôn luôn như vậy. Đôi khi cái mà có vẻ như lỗi lại là một dấu hiệu cho thấy bạn chưa hiểu chương trình viết ra, hoặc là có một lỗi trong thuật toán bạn dùng. Với những trường hợp này, bạn có thể sẽ phải nghĩ lại thuật toán, hay chỉnh lại mô hình nhận thức của mình. Hãy dành thời gian rời xa máy tính, để suy nghĩ, tự tính tay các phép thử, hoặc vẽ sơ đồ biểu diễn bài toán.

Sau khi sửa xong lỗi, bạn đừng chuyển sang lỗi mới. Hãy nghĩ một lát xem vừa rồi là loại lỗi gì, tại sao bạn mắc phải lỗi này, làm thế nào mà lỗi đã lộ diện, và đáng ra bạn có thể tìm lỗi này nhanh hơn bằng cách nào. Lần sau khi bạn thấy điều tương tự, có thể bạn sẽ chóng phát hiện ra lỗi hơn.

9 phản hồi

Filed under Think Java

9 responses to “Phụ lục D: Gỡ lỗi

  1. Pingback: Think Java: Cách suy nghĩ như nhà khoa học máy tính | Blog của Chiến

  2. Tuấn

    Thank bác rất nhiều, bác có thể đóng gói thành pdf để mình và mọi người có thể down về tiện tra cứu đc không ạ?

  3. cám ơn bạn, bài viết hay🙂

  4. Server vừa qua trải qua bảo dưỡng kĩ thuật nên có thể bạn chưa tìm thấy tập tin pdf của cuốn sách này. Bây giờ bạn lại có thể truy cập bình thường và tải về từ http://50.57.233.96/thinkjava.pdf

  5. Phúc

    Bản dịch rất hay và kỳ công. Nhưng file PDF link die rồi anh Chiến, anh có thể upload lại được không ?

  6. trung

    anh ơi link die rồi ạ anh up lại đc ko ạ

Gửi phản hồi

Mời bạn điền thông tin vào ô dưới đây hoặc kích vào một biểu tượng để đăng nhập:

WordPress.com Logo

Bạn đang bình luận bằng tài khoản WordPress.com Log Out / Thay đổi )

Twitter picture

Bạn đang bình luận bằng tài khoản Twitter Log Out / Thay đổi )

Facebook photo

Bạn đang bình luận bằng tài khoản Facebook Log Out / Thay đổi )

Google+ photo

Bạn đang bình luận bằng tài khoản Google+ Log Out / Thay đổi )

Connecting to %s