chim bay

Monday, October 26, 2015

Một số lỗi và kỹ thuật debug

Programer và Error

Hai người này cực kì thân với nhau, hễ một chút là tới hỏi thăm nhau liền. Có những lỗi Visual có thể báo trong khi biên dịch, mấy cái đó thì dễ xử lý do đó là lỗi cú pháp. Riêng mấy cái mà build xong, F5 cái mới hiện lỗi thì khổ hơn nhiều. Mà tức nữa chứ, đang mừng vì build thành công thế mà lại error! Cheer!
Error gặp hoài, nên đừng ngại, bạn cần phải giết nó, không thể để lộng hành được. Để làm được điều này, bạn cần biết được nguyên nhân của lỗi sau đó thì xử lý. Bình tĩnh rút kinh nghiệm cho lần sau, OK?
Có hai loại lỗi, một là lỗi được phát hiện khi build gọi là Syntax Error (lỗi cú pháp), hai là Run-time Error (lỗi khi thực thi). Syntax Error xuất hiện khi bạn viết code, Visual Studio dựa vào quy định cú pháp của C++ để dò, nên nó luôn luôn có thể thông báo lỗi cho bạn bằng cách in một vệt lươn lẹo màu đỏ bên dưới:
Không xác định được biến
Như ở trên, tôi gặp lỗi cú pháp, thông báo rằng “adffg không xác định”, cứ viết đại là nó sẽ báo thế.
Còn run-time error là cái bảng này:
Lỗi Run-time
Chắc cũng thấy quen quen ha.

Syntax Error

Lỗi cú pháp. Khi mới học C++, lỗi cú pháp chúng ta gặp rất nhiều (kể cả tôi) vì lúc đó đã quen với cách viết đâu. Thường gặp nhất là chuyện quên mất dấu ;
Thiếu dấu ;
Bên trên là trường hợp dễ, Visual có thể dò chính xác, thông báo đúng lỗi cho bạn. Thường thì nó sẽ báo phía sau. Cách giải quyết Syntax Error là gì: bạn cần nắm vững cú pháp C++, không quên cái gì hết. Sau đó, thấy lỗi thì di chuyển tới, xem nội dung lỗi nghĩa là gì (cố gắng dịch hen), rồi quay lại chỉnh sửa cho phù hợp.
Để chắc rằng bạn không còn syntax error nào nữa, cứ thử build chương trình xem, nếu build không thành công, có thể bạn đang gặp lỗi cú pháp đó. Khi build thất bại, bạn có hai lựa chọn:
  1. Dùng cửa số Output, xem nội dung lỗi:
Xem lỗi bằng cửa sổ Output
Sau đó chọn Build như trên, cửa sổ output sẽ hiện ra tất cả thông tin được ghi lại trong quá trình build.
Thường thì, trong dự án lớn, nó sẽ in ra rất nhiều, có khi cả chục dòng. Nếu gặp lỗi, bạn luôn nên xử lý lỗi đầu tiên (các lỗi sau thường do lỗi đầu tiên tạo ra), ấn Next Mesage chuyển nhanh lên trên đầu. Sau đó bạn tìm dòng có khi chữ error, đọc rồi giải quyết nó đi.
Xem lỗi đầu tiên
Gợi ý: nhấp đúp chuột vào nội dung lỗi để Visual Studio chỉ đến chính xác nơi gặp lỗi giùm bạn, khỏi phải tìm tốn công.
  1. Dùng cửa sổ Error List, coi và cập nhật các lỗi đã xảy ra.
Cửa sổ Error List
Đó, có mỗi một dấu ; thôi mà hiện ra tới 2 lỗi. Cũng tương tự, ấn đúp vào lỗi để Viusal mở vị trí lỗi giùm bạn.
Dưới đây là danh sách các lỗi mà các bạn có thể gặp:
  • “Expected a ;” hay “missing ‘;’ before ‘}’” là do bạn thiếu dấu ; kết thúc lệnh, tìm và bổ sung vào.
  • “Identifier “???” is undefined”: có thể bạn viết sai, hoặc chưa khai báo kiểu dữ liệu. Sửa lại cho đúng.
  • “redefinition” hoặc “already defined”: khác với lỗi ở trên, lần này bạn đã khai báo một kiểu dữ liệu, một hàm gì đó đến hai lần, điều này không cho phép. Bạn bắt buộc phải dùng tên khác hoặc xoá nó đi.
  • “has no member ???”: trong struct hoặc class bạn đang dùng không có biến/hàm thành viên đó, coi lại khuôn mẫu (khai báo) của struct/class đó đi, xem có hàm/biến đó không? Không có thì thêm vào.
  • “using without initialzied”: biến khởi tạo nhưng chưa gán giá trị cho nó.
Ngoại trừ mấy lỗi dễ nhận thấy đó, nhiều lúc bạn vẫn có thể gõ sai, nhưng đối với Visual, bạn vẫn viết đúng cú pháp, mỗi tội nó hiểu không đúng ý bạn.
Lỗi loại này thường gặp nhất là so sánh bằng và gán bằng. So sánh bằng sử dụng hai dấu bằng (==) trong khi đó gán bằng chỉ có 1 (=), nhớ chú ý kĩ nhé, Visual không báo cho bạn biết chuyện này đâu!
Lỗi thứ hai thuộc về logic, khi sử dụng khối lệnh. Bạn đoán xem chương trình dưới đây sẽ in ra giá trị bao nhiêu?
1
2
3
4
5
6
7
8
9
10
11
12
#include <iostream>
using namespace std;
 
void main(){
    int i = 0;
    i++;
    {
        int i = 4;
        i++;
    }
    cout << i;
}
Nhớ tới vòng đời của biến, cái trên thì dễ nhận ra, chứ vòng for như thế này thì coi chừng:
1
2
3
4
5
6
7
8
9
10
11
#include <iostream>
using namespace std;
 
void main(){
    int i = 0;
    i++;
    for (int i = 0; i < 10; i++){
        i++;
    }
    cout << i;
}

Run-time Error

Sau khi build thành công, bạn chỉ mới vượt qua giai đoạn Syntax Error, lỗi cú pháp. Chẳng vui mừng gì khi vẫn còn một dạng lỗi nữa, thường được gọi là lỗi ngữ nghĩa – nó xảy ra do bạn thao tác không đúng khiến chương trình chạy sai mục đích thiết kế ban đầu.
Lỗi run-time nhiều lắm, đa số do con trỏ gây ra không, chịu khó tưởng tượng ra quá trình chạy của chương trình để fix lỗi nhé. Nói ngắn gọn thế thôi.

Các công cụ Debug

Phần quan trọng là chỗ này. Trước tiên, các công cụ debug chỉ hỗ trợ bạn fix mấy cái lỗi run-time (lỗi ngữ nghĩa), còn việc bạn viết sai cú pháp thì nó không chịu trách nhiệm đâu nhé.
Point-to-view
Tính năng này chắc hẳn khá quen thuộc. Khi chương trình bạn gặp lỗi, nó dừng lại (ấn Break). Sau đó Visual sẽ thông báo cho bạn vị trí chương trình đang dừng lại
Ví trí chương trình đang dừng
Ngoài ra, để biết chương trình của bạn vẫn còn chạy, hay bạn đang ở trong edit mode, đó là thanh status bar bên dưới (màu xanh dương là edit mode):
Running mode
Như trên, bạn đang debug chương trình. Không phải edit mode.
Xong, tính năng point-to-view sẽ hiển thị giá trị mà biến đang lưu trữ khi bạn đưa con chuột lên trên tên biến đó:
Công cụ Debug: Point to view
Nhờ vậy bạn có thể lập tức biết giá trị hiện tại vào lúc đó của biến là bao nhiêu, đỡ tốn công in nó ra màn hình. Không chỉ dừng ở các biến bình thường, tính năng này còn có thể hiển thị dữ liệu của mảng, struct, class rất thú vị đó.
Breaking points
Để kích hoạt tính năng point-to-view, bạn phải làm chương trình bị lỗi giống như tôi ở trên. Có cách khác bắt chương trình tạm ngừng đó là break points. Bạn sẽ đặt break point tại vị trí, đoạn code mong muốn, sau đó F5, chương trình sẽ chạy tới đó, tự động dừng lại.
Công cụ Debug: Breaking point
Di chuyển con chuột qua trái, tới cái cột màu xám. Đó là nơi chuyên dụng của break point, khi click vào đó. Một dấu chấm đỏ như hình trên sẽ xuất hiện thông báo cho bạn biết. Khi debug, chương trình sẽ chạy tới đó rồi tự động dừng lại.
Thử xem nào:
Đặt breaking point và chương trình đang dừng ở đó
Đó, chương trình chạy tới dòng chúng ta đã chỉ định (có dấu mũi tên màu vàng đó), lúc này, bạn có thể sử dụng tính năng point-to-view cách thoải mái rồi.
Để xoá break point, bạn chỉ cần click chuột lần nữa vào chấm tròn đỏ đó, chấm tròn biến mất là xong.
Khi chương trình đang tạm ngừng vì break point như thế này, để tiếp tục chạy, bạn ấn F5 hoặc Debug -> Continue (nút play màu xanh lá).
Hoặc bạn có thể dừng nó lại ngay lập tức bằng cách ấn Shift + F5 hoặc:
Dừng Debug
Conditional breaking points
Break point còn có 1 tính năng rất thú vị nữa, đó là khả năng dừng khi thoả 1 điều kiện.
1
2
3
4
5
6
7
8
9
10
11
#include <iostream>
using namespace std;
 
void main(){
    int i = 0;
    i++;
    for (i = 10; i > 0; i--){
        cout << i;
    }
    i = 5 / i;
}
Bạn hãy đặt 1 break point bên trong vòng for và chạy thử, cứ mỗi vòng lặp, nó lại dừng. Điều này có vẻ khá nhức nhối, tại sao lại không dùng Conditional Break Point?
Sau khi đặt break point thành công, bạn ấn phải vào nó (chấm tròn đỏ), rồi chọn Condition… Trong ô condition, tôi ghi i == 5, nghĩa là tôi muốn dừng lại khi i = 5. Bên dưới cứ để nguyên “Is true”. Ấn OK và F5 thử liền nào.
Chương trình đã dừng lại, trỏ con chuột tới biến i, nó ghi giá trị của i là 5. Như vậy điều kiện đã có hiệu lực.
Step
Công cụ Debug: Step
Có hai loại là Step Into (F11) và Step Over (F10). Khi chương trình đang tạm ngừng, ấn F10 sẽ khiến cho chương trình của bạn chạy thêm 1 lệnh nữa rồi lại ngưng. OK, đơn giản có thế. F10 sẽ chạy một lệnh trong hàm đó, nếu bạn có gọi một hàm bên ngoài nữa, nó vẫn không nhảy vô hàm bạn gọi, mà cố thủ trong hàm mà nó đang dừng. Để nhảy vô bên trong hàm bạn gọi, thay vì ấn F10, hãy ấn F11.
Để kiểm tra, bạn đặt break point tại hàm cout. Sau đó F5 chờ chương trình break. Ấn F10 vài lần để hiểu rõ hơn chức năng của nó.
Calling stack
Thêm một công cụ nữa rất mạnh, đó là cửa số Calling Stack. Tôi có chương trình đệ quy dưới đây:
1
2
3
4
5
6
7
8
9
10
11
12
#include <iostream>
using namespace std;
 
void Dec(int i)
{
    if (i == 0) return;
    Dec(i - 1);
}
 
void main(){
    Dec(10);
}
Bạn hãy đặt 1 conditional break point tại lệnh if, điều kiện là i == 5. Xong chạy chương trình và chờ nó break.
Công cụ Debug: Call Stack
Khi chương trình tạm ngưng, Call Stack sẽ như thế này. Bạn đoán ra nó muốn thông báo cái gì chưa? Quay lại chương trình của chúng ta, tôi gọi hàm Dec với tham số 10, hàm tạm dừng và gọi Dec với tham số 9, cứ thế có tổng cộng 5 lần hàm Dec phải tạm dừng. Đếm thử bên trên xem có mấy hàm Dec nào, có tới 6 lận vì lần thứ 6 dừng do gặp break point, không tính. Coi kĩ lại những gì mà Calling Stack muốn nói, bạn để ý tới chữ ‘Line 7’ không, nó là nơi mà hàm đang tạm dừng để chờ Dec kia kết thúc đó.
Cửa số này mô tả quá trình gọi hàm, thông báo cho bạn biết thứ tự gọi hàm là gì. Bắt đầu từ hệ thống, chúng ta trải qua 2 hàm phụ (của trình biên dịch) trước khi thực thi hàm main. Đúng không, tiếp đó, hàm main gọi hàm Dec, Dec lại gọi Dec,… cứ thế. Dấu chấm tròn đỏ có mũi tên vàng chỉ thị nơi chương trình đang dừng, oh, nó đang dừng ở đỉnh stack đó.
Bạn có thể nhấp đôi vào từng dòng, Visual sẽ hiển thị đoạn mã cho bạn, khỏi phải tìm. Cửa sổ này thực sự hữu ích khi bạn cần biết chương trình mình đang đi đâu, gọi hàm có đúng không,… Còn nhiều lắm mà chính tôi cũng không thể mô tả. Thật đó, có những lúc các công cụ debug trên không thể giúp được gì, mà Calling Stack có thể đó.
Watch
Tính năng này tương tự như cái point-to-view. Thay vì bạn phải di chuyển con chuột, nó tự hiển thị cho bạn luôn. Để hiển thị biến trong Watch, bạn chỉ cần ấn phải vào biến, chọn Add Watch… Tôi rất ít khi dùng tính năng này, thật sự nó cũng rất hữu ích, nhưng vì sự xuất hiện của bảng Autos với Local khiến tôi phải bỏ bê nó rồi.
Cửa sổ Auto sẽ tư động Watch những biến mà nó cho rằng bạn sẽ cần, nên tôi không phải thêm vào watch. Còn nữa, cửa sổ Locals sẽ hiện tất cả các biến cục bộ trong hàm mà chương trình bạn đang dừng. Thế là xong, tôi bỏ watch luôn.
Công cụ Debug: Auto Watch
Chiến lược debug
Trên đó là vài tính năng cơ bản cần thiết với bạn. Tuy nhiên, thật sự lãng phí nếu các bạn chưa có một chiến lược debug hoàn hảo để có thể nhanh chóng khoanh vùng vị trí lỗi. Tiếp theo đây tôi sẽ chỉ cho bạn một số mẹo vặt khá hữu ích.
Để biết hàm đó có chạy hay không, hãy để 1 break point tại đầu hàm, ấn F5. Nếu chương trình dừng lại, OK, nó có chạy. Không thì có vấn đề rồi.
Nếu bạn gặp một lỗi khá oái oăm (gặp rất nhiều đấy), nó kiến cho giá trị của biến bị thay đổi, gây lỗi hàng loại cho phần code đằng sau. Nhiệm vụ hàng đầu là phải tìm ra nơi đã thực hiện việc đó. Đầu tiên là một vùng lớn, từ lúc chạy cho tới chỗ bị lỗi. Bắt đầu khoanh vùng như sau:
  • Đánh giá một vị trí nào đó, nếu nghi ngờ, đặt break point tại khu vực bên trên. Chạy thử.
  • Nếu trước khi vào khu vực đó, biến chưa bị thay đổi, như vậy phần đằng trước OK, chạy tốt không vấn đề, đưa break point xuống dưới, thu hẹp phạm vi. Nếu đã bị thay đổi, dừng chương trình, dịch chuyển break point lên trên.
  • Sau khi xác định được vùng gây sự và vùng đó đủ nhỏ, đặt break point, sau đó kết hợp F10, chạy từng lệnh một cho đến khi phát hiện ra ví trí biến bị thay đổi không mong muốn.
Điều quan trọng là bạn phải hiểu dòng chảy chương trình của bạn. Nó đi đâu, làm gì bạn phải quản lý được. Thế nhé.
Set next to run
Khi chương trình đang tạm dừng (break), một dấu mũi tên màu vàng hiện lên, thông báo nơi mà chương trình của bạn đang tạm ngừng. Ngoài chức năng đó, chỉ cần sử dụng chuột, kéo thả nó ra vị trí mới. Lúc này, bạn đã thay đổi vị trí đang thực hiện của chương trình, nếu ấn F5, mũi tên màu vàng ở đâu, chương trình của bạn sẽ bắt đầu chạy tại đó.
Edit and run
Công cụ này tôi ít khi dùng, mà đôi lúc lại hữu ích lắm. Giờ, chương trình đang break, bạn vào code, sửa biến sửa sửa gì đó. Sau đó ấn F5, Visual Studio sẽ biên dịch lại phần đã chỉnh sửa đó rồi cho chương trình bạn chạy tiếp mà không phải khởi động lại. Hay quá luôn.

Lời kết

Các công cụ Debug ở trên tôi chỉ giới thiệu cách sơ qua cho bạn hiểu chức năng của chúng. Các kĩ năng sẽ dần hoàn thiện nếu bạn tiếp tục coding, coding.
Chuỗi bài học C++ cơ bản tới đây là kết thúc, rất vui nếu bạn có thể theo học tới đây. Tôi chắc rằng bạn đã có một khối lượng kinh nghiệm khá lớn rồi. Tự tin không? những gì bạn được nghe tôi trình bày chỉ là những thứ cơ bản, là nền móng để bạn có thể tiếp tục nghiên cứu sâu hơn. Thế giới C++ còn rất rộng lớn mà chuỗi bài cơ bản này không thể giải quyết hết được. Còn rất nhiều khái niệm, từ khoá nữa, nếu muốn tìm hiểu, bạn có thể lên cplusplus chẳng hạn (google đi). Nhớ rằng nên tìm kiếm bằng tiếng anh, kết quả thu được sẽ nhiều hơn bạn tưởng tượng đấy. Các bài viết tiếng anh rất chi tiết và dễ hiểu (đa số), bạn cứ tự tin, không có gì phải sợ cả.
Giả sử như bạn muốn tìm hiểu template, lên google gõ “template c++”, enter và khám phá nhé. Hay “Inheritance” thì gõ y chữ đó, thêm cái đuôi C++ vô là xong.

2 comments:

  1. khi mình debug mà nó không thực hiện không báo lỗi gì cả thi phải làm sao ?

    ReplyDelete
  2. Yes, you are right; the demand for freelance angular js developers' is continuously rising as the tech world evolves and businesses become increasingly reliant on technology. A developer's income depends on the role and the software they are working on. You should check out Eiliana; they showcase your skills to the right people.

    ReplyDelete