TỰ TIN HƠN KHI GẶP LỖI TRONG LẬP TRÌNH
Bài viết này dành cho Sinh viên năm 1, 2 chuyên ngành Công nghệ thông tin đã và đang học lập trình C cơ bản và các Sinh viên yêu thích lập trình. Mục tiêu: Bài viết nhằm giúp sinh viên có thể phân biệt các loại lỗi trong lập trình, nhận biết các nguyên nhân lỗi nhanh và có thể sửa chữa triệt để các lỗi cơ bản trong lập trình. Từ đó sinh viên tự tin hơn trong việc giải quyết các lỗi mắc phải và yêu thích công việc lập trình hơn.
1. Vì sao phải tuân thủ đúng quy trình phát triển phần mềm?
Khi bắt đầu giải quyết một bài toán trên máy tính hay thiết kết một phần mềm dù rất nhỏ thì cá nhân hay nhóm lập trình cần tuân thủ đúng các bước trong quy trình phát triển phần mềm sau:
1. Xác định yêu cầu (Specify the problem)
2. Phân tích yêu cầu (Analysis the problem)
3. Thiết kế giải thuật (Design the algorithm)
4. Hiện thực giải thuật (Implement the algorimth)
5. Kiểm thử và kiểm chứng (Test and Verify the program)
6. Bảo trì và cập nhật phần mềm (Maintain and update the program)
Việc tuân thủ các bước trong quy trình này sẽ giúp ta hạn chế các lỗi lớn trong lập trình và giúp ta giải giải quyết các bài toán phức tạp, các chương trình lớn một cách chính xác và đúng với yêu cầu hơn. Bạn cần tránh việc đọc qua loa yêu cầu xong bắt đầu code theo ý nghĩ trong đầu, nghĩ đến đâu code đến đó mà chưa nắm rõ các yêu cầu , cũng như chưa xác định được cách thức cụ thể để giải quyết bài toán hay không biết giải pháp của mình có khả thi hay không. Với thói quen lập trình này này bạn chỉ có thể viết được các chương trình đơn giản và là một rào cản rất lớn để bạn có thể viết được các chương trình có độ khó và phức tạp cao.
Ngay cả khi bạn là một lập trình viên có nhiều năm kinh nghiệm, hiếm khi chương trình bạn chay ngay từ lần thử nghiệm đầu tiên. Hãy luôn nhớ rằng “ Nếu chương trình có thể sai, thì nó sẽ sai” , định luật Murphy dường như rất đúng trong lập trình.
Trong thực tế lỗi phần mềm rất thường xảy ra. Thuật ngữ Bugs đề cập các lỗi các lập trình viên gặp phải trong quá trình sửa lỗi chương trình hay gọi là “debugging”. Khi trình biên dịch tìm ra lỗi, máy tính sẽ hiển thị các thông báo chỉ ra cụ thể lỗi mắc phải hoặc nguyên nhân có thể gây ra lỗi. Nhưng không phải lúc nào lỗi cũng dễ dàng phát hiện ra và đôi khi các thông báo lỗi gây hiểu lầm cho lập trình viên. Khi bạn có kinh nghiệm bạn sẽ trở nên giỏi hơn trong việc định vị lỗi và sữa chữa lỗi.
2. Hiểu rõ hơn về Bugs
Lỗi (Bugs) được phân thành ba loại chính là:
· Lỗi cú pháp (syntax errors)
· Lỗi thực thi (run-time errors)
· Lỗi luận lý (logic errors)
Độ khó để tìm ra lỗi cũng tăng dần theo thứ tự liệt kê các loại lỗi ở trên. Nghĩa là lỗi cú pháp sẽ dễ dàng phát hiện nhất và lỗi luận lý sẽ khó phát hiện nhất. Sau đây chúng ta sẽ phân tích sâu vào nguyên nhân, nguyên tắc để tránh lỗi cũng như một số lỗi thường gặp như là một kinh nghiệm để chúng ta chuẩn đoán lỗi nhanh và chính xác hơn.
3. Lỗi cú pháp (syntax errors)
-
Lỗi cú pháp là gì?
-
Làm thế nào để phát hiện nhanh các lỗi cú pháp?
Xét đoạn code trong ví dụ sau:
Ta nhận được các thông báo lỗi sau từ Visual C++ 2010:
Đoạn code trong ví dụ chỉ tồn tại một lỗi tại dòng code số 1 nhưng ta thấy trình biên dịch báo đến bốn lỗi. Trong Visual C++ ta có thể xem lỗi trong cửa sổ Output hoặc Error List. Thông thường ta lần vết từ thông báo lỗi đầu tiên và số thứ tự của dòng code để chẩn đoán nơi tồn tại lỗi và biên dịch lại mỗi khi sửa xong để tiếp tục tìm lỗi khác.
-
Các lỗi cú pháp và thông báo lỗi nào thường gặp?
Chẩn đoán thông thường | Thông báo | Cách sữa lỗi | |
1 | Thiếu dấu ‘;’ kết thúc câu lệnh | Expected a ‘;’ Missing ‘;’ before identifier |
Thêm dấu ‘;’ trước câu lệnh chứa identifier ‘…’ |
2 | Chưa khai báo kiểu dữ liệu | ‘…’ : undeclared identifier | Khai báo dữ liệu |
3 | Khai báo dữ liệu nhiều lần | '…' : redefinition; multiple initialization |
Dùng tên khác hoặc xóa đi |
4 | Dùng hàm / thành viên không có tồn tại trong struct / class | 'm' : is not a member of 's' |
Thêm ‘m’ vào ‘s’ Hoặc xóa ‘m’ |
5 | Các cặp ‘(‘ và ‘)’ không tương ứng | Syntax Error: ’)’ expected | Thêm hoặc xóa bớt cho tương ứng |
6 | Phép gán không tương đồng về kiểu dữ liệu, không thể gán | '=' : cannot convert from 'type1' to 'type2' |
Làm cho 2 vế của phép gán tương đồng hoặc có thể gán được |
7 | Hàm có kiểu trả về thiếu câu lệnh return | ‘…’ must return a value | Thêm câu lệnh return cuối hàm |
8 | Số lượng đối số truyền cho hàm khác với định nghĩa hàm | ‘…’: Function does not take n argument | Truyền số đối số cho hàm đúng như đã định nghĩa hàm |
9 | Kiểu của đối số truyền vào hàm không đúng | '…' : cannot convert parameter n from ' ‘type 1' to 'type 2' | Chỉnh sưa kiểu ‘type1’ cho giống với ‘type2’ đã định nghĩa trong hàm |
... |
-
Chương trình đã chạy có tồn tại lỗi cú pháp không?
· Sử dụng ‘;’ sau các cấu trúc điều khiển, cấu trúc lặp, làm mất hiệu lực của các cấu trúc:
if (a == 0);
{
c = d;
}
for (int i = 0; i < 3; i ++);
{
printf ("Hello! ");
}
Nếu a khác 0 thì phép gán c = d vẫn thực hiện.
Câu lệnh in chỉ được thực hiện 1 lần thay vì 3 lần như mong muốn.
· Sử dụng nhầm phép so sánh ‘=’ thay vì ‘= =’:
if (a = b)
{
C ;
}
b luôn bằng a, c sẽ không được thực thi nếu b khác 0. Điều này gây nên lỗi logic.
· Sử dụng nhầm phép so sánh ‘=!’ thay vì ‘!=’:
if (a =! b)
{
c;
}
a được gán bằng với not(b). . Điều này gây nên lỗi logic.
· Sử dụng thiếu chặt chẽ trong phép toán luận lý:
if (0 < a < 5)
{
c;
}
c luôn được thực thi vì if luôn đúng (0<a trả về 1, 1 < 5 luôn đúng)
· Hàm không trả về giá trị:
int foo (a){
if (a)
{
return(1);
}
}
Trong một số trường hợp a=0, hàm không trả về giá trị gây lỗi run-time
Tóm lại, để hạn chế gặp lỗi cú trong lập trình ta không nên chỉ dựa vào thông báo lỗi để chắc chắn là chương trình không còn tồn tại lỗi cú pháp mà phải bảo đảm viết chính xác mỗi dòng lệnh trước khi sang dòng lệnh mới.
4. Lỗi thực thi (run-time errors)
-
Lỗi thực thi là gì?
-
Khi nào gặp lỗi run-time?
· Overflow : Kết quả tính toán cho quá lớn không thể lưu trữ vào biến
· Divide by Zero: Chia một số cho 0.
· Error Memory : Thực hiện truy cập vào vùng nhớ không xác định làm cho kết quả trả về không lường được. Ví dụ: truy cập phần tử nằm ngoài vùng giới hạn của mảng; Truy cập vào vùng nhớ heap sau khi đã giải phóng bộ nhớ.
· Uninitialized Data Access : Truy cập vào bộ nhớ trước khi bộ nhớ được khởi tạo vì vậy không lường trước được kết quả trả
Một số ví dụ về lỗi Run-time và lỗi cú pháp trên bộ nhớ:
Overflow:
int main() { int n = 1000; cout << Square(n); return 0; } short int Square (int nSize) { return nSize * nSize; } |
Kết quả :16960 Ta thấy chương trình vẫn được biên dịch và thực thi nhưng cho kết quả sai là 16960 thay vì 1000000. Khắc phục : hàm Square trả về kiểu dữ liệu lớn hơn kiểu int. Ví dụ: long int, hoặc dùng mảng để xử lý số lớn |
Error Memory: Lỗi bộ nhớ có thể phân làm hai loại lỗi trên bộ nhớ Heap và lỗi trên bộ nhớ Stack. Thông thường lỗi xảy ra khi:
· Lỗi thu hồi bộ nhớ : Lệnh cấp phát và thu hồi phải đi cặp với nhau: trong C sử dụng malloc – free, C++ sử dụng new –delete. Tránh sử dụng nhầm lẫn giữa các cặp cấp phát và thu hồi giữa C và C++ vì ta không chắc lệnh thu hồi bộ nhớ thực hiện đúng như đã cấp phát
char *s = (char*) malloc(5); delete s; |
Kết quả : Chương trình vẫn chạy nhưng không chắc bộ nhớ được thu hồi đúng Khắc phục : sử dụng lệnh free để giải phóng bộ nhớ thay vì delete |
· Không thu hồi bộ nhớ đã cấp phát (Memory leak): lỗi này xảy ra khi ta thiếu câu lệnh thu hồi bộ nhớ cho biến đã cấp phát
char *pStr = (char*) malloc(512); return; |
Kết quả : Lỗi cú pháp. Chương trình vẫn thực thi nhưng bộ nhớ không được thu hồi Khắc phục : thêm câu lệnh free(pStr) trước câu lệnh return; |
· Lỗi không thể thu hồi bộ nhớ (Mising Deallocation):
char* pStr = (char*) malloc(20); free(pStr); free(pStr); // Lỗi |
Kết quả : Lỗi thực thi Khắc phục : xóa câu lệnh free(pStr) ở cuối |
void nhap(int *a,int n) { a=new int[n]; for(int i=0;i<n;i++) cin>> *(a+i); } void main(){ int *x; int n=6; // nhap(x,n); delete[] x; } |
Kết quả : Thông báo lỗi: “Run-Time Check Failure #3 - The variable 'x' is being used without being initialized” x chưa trỏ đến biến nào nên không thể thực hiện lệnh delete[] x được. Khắc phục : Chuyển lệnh cấp phát bộ nhớ cho con trỏ x trong main tại vị trí //: x=new int[n]; |
· Không khởi tạo khi truy cập biến nằm trên bộ nhớ heap và stack
void main() { char *pStr=(char*) malloc(512); char c = pStr[0];//không khởi tạo system("pause"); } void func() { int a; int b = a * 4; //không khởi tạo } |
Kết quả : Lỗi thực thi Khắc phục : khởi tạo |
· Lỗi truy cập đến phần tử mảng có chỉ vượt chỉ số mảng đã khai báo:
const int NUM_DAYS = 7; void main() { int temp[NUM_DAYS]; int i; for (i=1; i<= NUM_DAYS; i++){ cout <<"Enter a value: "; cin >> temp[i]; } } |
Kết quả : lỗi thực thi. Thông báo lỗi: “Run-Time Check Failure #2 - Stack around the variable 'temperatures' was corrupted.” Lỗi truy cập đến phần tử temp[i] khi I nằm ngoài mảng temp Khắc phục : for (i = 0;i<NUM_DAYS; i++) |
Làm cách nào để chẩn đoán code gây lỗi run-time?
Việc xác định code gây lỗi run-time rất khó khăn. Cách thông thường nhất là sử dụng công cụ các công cụ debug của phần mềm ngôn ngữ lập trình. Visual C++ 2010 hổ trợ người lập trình ba cách debug như sau:- Tính năng Point-to-view:
Khi chương trình gặp lỗi, sẽ hiển thị thông báo lỗi như sau
Bạn nhấn “Break” để dừng chương trình. Sau đó Visual Studio sẽ thông báo cho bạn vị trí dừng. Rê chuột đến tên của biến, tính năng point-to-view sẽ hiển thị giá trị mà biến đang lưu trữ. Nhờ vậy bạn có thể biết được giá trị biến tại thời điểm xảy ra lỗi và dễ dàng chẩn đoán nguyên nhân lỗi hơn.
- Breaking point :
Tính năng này cho phép chương trình đang thực thi có thể dừng tại những vị trí bạn mong muốn hoặc nghi ngờ có lỗi. Các bước thực hiện như sau:
o Đến dòng lệnh muốn đặt break point, click chuột vào cột bên trái màu xám. Một dấu chấm đỏ như hình trên sẽ xuất hiện. Khi debug, chương trình sẽ chạy tới đó rồi tự động dừng lại. Để xóa break point bạn click chuột lên chấm tròn đỏ một lần nữa.
o Nhấn F5 để biên dịch chương trình. Khi chương trình dừng, bạn quan sát các thông báo trên cửa sổ Breakpoints ở góc phải bên dưới màn hình. Có nhiều chế độ để tiếp tục thực thi chương trình:
o Step Over: nhấn F10 để thực thi từng dòng lệnh. Nhưng không nhảy vào hàm được gọi.
o Step Into: nhấn F11 để tiếp tục thực thi từng dòng lệnh. Nếu có lời gọi hàm thì sẽ nhảy đến hàm được gọi.
o Step Out: nhấn Shift+F11 để tiếp tục thực thi chương trình cho đến khi hàm được gọi trả về kết quả thì chương trình sẽ tiếp tục dừng
ü Cửa sổ Watch : chức năng này tương tự point-to-view nhưng tiện tích hơn là bạn có thể xem giá trị các biến trong cũng một cửa sổ. Để thêm biến vào cửa sổ Watch, bạn click chuột phải ngay tên biến, chọn “Add watch” hoặc gõ tên biến vào mục Name trên cửa sổ Watch.
Tóm lại, các công cụ debug hổ trợ bạn quan sát chương trình thực thi một cách chi tiết hơn. Việc xác định và sửa lỗi thực thi phụ thuộc rất nhiều vào kinh nghiệm lập trình của bạn.
5. Lỗi luận lý (logic errors):
-
Lỗi luận lý là gì?
-
Nguyên nhân gây nên lỗi luận lý ?
· Bạn chưa thật sự hiểu yêu cầu của chương trình
· Bạn chưa hiểu rõ các hoạt động của từng dòng code trong chương trình mình viết
· Bạn đã bất cẩn trong khi lập trình
Lỗi logic phát hiện càng muộn thì càng gây thiệt hại và chi phí để khắc phục cao hơn. Vì vậy hãy kiểm tra chương trình của mình thật cẩn thận trước khi chuyển nó cho khách hàng của bạn sử dụng. Bạn phải tự tin là chương trình tôi viết hoạt động đúng với độ chính xác như khách hang đã mong đợi.
Làm thế nào để tránh lỗi luận lý trong chương trình?
Bạn hãy cố gắng hết sức có thể để tránh lỗi luận lý trong chương trình mình viết. Để làm được việc này bạn phải tuân thủ ba bươc đầu tiên trong thiết kế phần mềm: Xác định yêu cầu, Phân tích yêu cầu và thiết kế giải thuật. Các bước tiếp theo của quy trình là cũng phải rà soát và kiểm chứng xem rằng chương trình đang thực hiện có đang được phát triển đúng hướng hay không. Hãy thực hiện từng bước thật cẩn thận, chính xác trong từng dòng code bạn viết và chay thử nghiệm với tất cả các tình huống có thể xảy ra để đảm bảo tính đúng đắn của chương trình. Để làm được như vậy bạn phải nắm vững những kiến thức cơ bản nhất trong lập trình, đừng ngần ngại khi phải học lai một số điều cơ bản khi bạn có cảm giác mình bị thiếu sót trong quá khứ. Phần mềm là do con người tạo ra, nó không thể không có sai sót, cho nên bạn hãy không ngừng nổ lực phát hiện những sai sót nhưng hãy nhớ rằng nó không thể hoàn hảo trong tất cả mọi khía cạnh mà đôi lúc bạn phải chấp nhận một số sai sót không thể sữa chữa trong điều kiện nhất định hoặc tốn nhiều chi phí, công sức để làm cho nó hoàn hảo. Một việc quan trọng nữa là khách hàng của bạn nên biết và chấp nhận những sai sót này.--
-
030818
-
240618
-
271017
-
271017
-
220317
-
220317
-
120117
-
231216
-
151116
-
151116
Tuyển dụng
Cơ hội việc làm
Bộ môn
chuyên ngành
máy tính
phần mềm
thông tin