Tải bản đầy đủ (.pdf) (128 trang)

Bài giảng Kỹ thuật lập trình: Chương 4 - TS. Vũ Hương Giang

Bạn đang xem bản rút gọn của tài liệu. Xem và tải ngay bản đầy đủ của tài liệu tại đây (3.58 MB, 128 trang )

• Với mỗi bài toán, làm thế nào để:
– Thiết kế giải thuật nhằm giải quyết bài toán đó
– Cài đặt giải thuật bằng một chương trình máy tính

- Hãy làm cho chương trình
chạy đúng trước khi tăng tính
hiệu quả của chương trình
- Hãy tăng tính hiệu quả của
chương trình và đồng thời thể
hiện tốt phong cách lập trình
của cá nhân
CuuDuongThanCong.com

/>

CHƯƠNG IV.
CÁC KỸ THUẬT KIỂM TRA
TÍNH ĐÚNG ĐẮN VÀ TÍNH AN
TOÀN CỦA CHƯƠNG TRÌNH
PHẦN MỀM
I.
II.
III.
IV.

Bẫy lỗi (error handling)
Lập trình phòng ngừa (defensive programming)
Kiểm thử (Testing)
Gỡ rối (Debugging)
CuuDuongThanCong.com


/>

Mở đầu
• Lỗi: chương trình chạy không đúng như đã định
• Chuỗi kiểm tra chương trình





Bẫy lỗi (error handling)
Lập trình phòng ngừa (defensive programming)
Kiểm thử (testing)
Gỡ rối (debugging)

• Phân biệt:
– Bẫy lỗi: Prevent errors
– Lập trình phòng ngừa: Detect problems as early as
possible
– Kiểm thử: finished code
– Gỡ rối: fixing defects uncovered by testing

CuuDuongThanCong.com

/>

I. BẪY LỖI

CuuDuongThanCong.com


/>

Nguyên tắc
• Khi lỗi xảy ra cần
– Định vị nguồn gây lỗi
– Kiểm soát lỗi

• Luôn có ý thức đề phòng các lỗi hay xảy ra trong
chương trình, nhất là khi đọc file, dữ liệu do
người dùng nhập vào và cấp phát bộ nhớ.
• Áp dụng các biện pháp phòng ngừa ngay cả khi
điều đó có thể dẫn tới việc dừng chương trình
• In các lỗi bằng stderr stream.
fprintf (stderr,"There is an error!\n");

CuuDuongThanCong.com

/>

Kiểm tra cái gì để phát hiện lỗi ?
• Kiểm tra mọi thao tác có thể gây lỗi khi viết CT
– Nhập dữ liệu
– Sử dụng dữ liệu

• Ví dụ:
– Kiểm tra mỗi lần mở một tệp tin hay cấp phát các ô nhớ.

– Kiểm tra các phương thức người dùng nhập dữ liệu vào cho
đến khi không còn nguy cơ gây ra dừng chương trình
– Trong trường hợp tràn bộ nhớ (out of memory), nên in ra

lỗi kết thúc chương trình (-1: error exit);

– Trong trường hợp dữ liệu do người dùng đưa vào bị lỗi, tạo
cơ hội cho người dùng nhập lại dữ liệu (lỗi tên file cũng có
thể do người dùng nhập sai)

CuuDuongThanCong.com

/>

Làm gì khi phát hiện lỗi ?
• Cần có cách xử lý các lỗi mà ta chờ đợi sẽ xảy ra
• Tùy theo tình huống cụ thể, ta có thể










Trả về 1 giá trị trung lập
Thay thế đoạn tiếp theo của dữ liệu hợp lệ
Trả về cùng giá trị như lần trước
Thay thế giá trị hợp lệ gần nhất
Ghi vết 1 cảnh báo vào tệp
Trả về 1 mã lỗi
Gọi 1 thủ tục hay đối tượng xử lý

Hiện thông báo lỗi
Tắt máy

CuuDuongThanCong.com

/>

Một số lỗi nhập dữ liệu phổ biến
• Dữ liệu nhập vào quá lớn (ví dụ, vượt quá kích
thước kích thước lưu trữ cho phép của mảng hay
của biến)
• Dữ liệu nhập vào sai kiểu, giá trị quá nhỏ hoặc
giá trị âm
• Lỗi chia cho số 0 (divide by zero)

CuuDuongThanCong.com

/>

Dùng hàm bao gói (Wrappered
function)
• Hàm bao gói = gọi hàm gốc + bẫy lỗi
• Tại mọi thời điểm cần kiểm tra lỗi của hàm gốc, dùng hàm
bao gói thay vì dùng hàm gốc
• Ví dụ:
• Nếu phải viết đoạn mã kiểm tra lỗi mỗi lần sử dụng malloc
thì rất nhàm chán.
– Kiểm tra
– Thông báo lỗi kiểu như “out of memory”
– Thoát.


• Tự viết hàm safe_malloc và sử dụng hàm này thay vì dùng
malloc có sẵn





Malloc
Kiểm tra
Thông báo lỗi kiểu như “out of memory”
Thoát.
CuuDuongThanCong.com

/>

safe_malloc
#include<stdlib.h>
#include<stdio.h>
void *safe_malloc (size_t);
/* Bẫy lỗi khi dùng hàm malloc */
void *safe_malloc (size_t size)
/* Cấp phát bộ nhớ hoặc báo lỗi và thoát */
{
void *ptr;
ptr= malloc(size);
if (ptr == NULL) {
fprintf (stderr,
“Không đủ bộ nhớ để thực hiện dòng lệnh :%d file :%s\n",
__LINE__, __FILE__);

exit(-1);
}
return ptr;
}
CuuDuongThanCong.com
/>

Tạo giao diện rõ ràng
• Các LTV giỏi luôn tìm cách làm cho mã nguồn của họ
trở nên hữu dụng với những LTV khác.
• Cách tốt nhất để làm việc này là viết các hàm dễ hiểu,
có khả năng tái sử dụng
• Các hàm tốt không cần đến quá nhiều tham số truyền
vào
• Để viết tốt các hàm, cần tư duy theo hướng:
– Cần truyền cái gì vào để thực hiện hàm ?
– Có thể lấy được cái gì ra sau khi thực hiện hàm

• Nếu LTV có khả năng viết được một giao diện rõ ràng
thì các hàm tự bản thân nó trở nên hiệu quả:
– Các hàm được cung cấp
– Cách thức truy nhập chức năng muốn cung cấp

CuuDuongThanCong.com

/>

Ví dụ: giao diện thể hiện được cấu trúc
của chương trình


pay.cpp
#include "pay.h"
int main()
Lấy dữ liệu vào từ người
dùng
Gọi các chương trình con
hợp lý

pay.h
Header file enums, structs,
prototypes

fileio.cpp
#include "pay.h"
Đọc các records khi được gọi
Ghi vào các records
Tạo bản sao
CuuDuongThanCong.com

update.cpp
#include "pay.h"
Tạo file mới cho mỗi NV mới
Ghi vào file đang có cho NV
đã có tên
printout.cpp
#include "pay.h"
In bảng lương của tất cả các nhân
viên
In các bảng lương của từng nhân
viên để đối chiếu

/>

Đơn giản hóa các hàm bằng cách cấu
trúc hóa chương trình
• Đôi khi cần truyền rất nhiều tham số vào một
hàm
void write_record (FILE *fptr, char name[], int wage,
int hire_date, int increment_date, int pay_scale,
char address[])
/* Hàm ghi thông tin liên quan đến 1 NV */
{
Sẽ tốt hơn nếu xây dựng 1 struct và thao tác trên struct đó

}
void write_record (FILE *fptr, EMPLOYEE this_employee)
/* Hàm ghi thông tin liên quan đến 1 NV */
{
}
CuuDuongThanCong.com

/>

Các hàm phải nhất quán (consistent)
• Nếu cần tạo ra loạt các hàm tương tự nhau, thì
nên tổ chức mã nguồn của các hàm đó sao cho
logic hay quy trình nghiệp vụ của các hàm đó là
như nhau

int write_record (char fname[],EMPLOYEE employee)
/* Trả về -1 nếu ghi bị lỗi, trả về 0 nếu ghi thành công*/

{
Hàm thứ 2 khác hẳn hàm thứ nhất

}
int add_record (EMPLOYEE employee, char fname[])
/* Trả về 0 nếu lỗi, trả về 1 nếu thành công*/
{
}

Khi tái sử dụng, khả năng gây lỗi là rất cao
CuuDuongThanCong.com

/>

Không tùy tiện thay đổi cách thức hoạt
động của hàm
• Chỉ thay đổi tham số của hàm nếu không còn lựa
chọn nào khác.
• Không thay đổi mục tiêu, nhiệm vụ của hàm: ví
dụ: nếu mục tiêu ban đầu không phải là cập nhật
mảng, thì trong hàm đừng thực hiện việc cập
nhật mảng
FILE *fptr;
char fname[]= "file.txt";
fptr= fopen (fname, "r");
if (fptr == NULL) {
printf ("Can't open %s\n",fname);
return -1;
}
Nếu fopen làm thay đổi không báo trước giá trị lưu

trong fname, điều gì sẽ xảy ra?
CuuDuongThanCong.com

/>

Buffer Overflow & vấn đề truy nhập
trái phép
• Hacker có thể khai thác các lỗi (bug) của hệ điều hành
(Windows/Unix/Mac OS) hay phần mềm để truy nhập
trái phép vào máy tính
• Một trong những lỗi được khai thác rất nhiều là "buffer
overflows".
• Có nhiều cách gây ra buffer overflows
– Sử dụng cẩu thả "strcpy" (sao chép 1 xâu lớn hơn vào 1
biến có kích thước nhỏ hơn).
– Dùng lệnh gets (thư viện stdio)
– Quên không kiểm tra độ lớn của xâu vào
– ..

• Kiểm tra dữ liệu vào từ người dùng là một trong
những cách hạn chế truy nhập trái phép

CuuDuongThanCong.com

/>

Buffer Overflow
• Cái gì xảy ra nếu xâu lưu trong str2 dài hơn xâu
lưu trong str1?


void strcpy(char str1[], char str2[])
/* Copy xâu từ str2 vào str1 */
{
int i= 0;
while ((str1[i]= str2[i]) != '\0')
i++;
}
Another complex line which
assigns and compares.
CuuDuongThanCong.com

/>

Buffer overflows
• Cái gì xảy ra nếu người dùng nhập vào 10 ký tự ?
100 ký tự ?
#include <stdio.h>
int main(void) {
char name [25];
printf("Enter your name: ");
fflush(stdout);
if (gets(name) != NULL)
printf("Hello and Goodbye %s\n", name);
return 0;
}

CuuDuongThanCong.com

/>


safe-gets
• Dùng fgets() thay thế cho gets().
– fgets() không tự loại bỏ \n (new line) ở cuối xâu như
gets()
#include <stdio.h>
#include <string.h>
char *safeget(char *buffer, int count) {
char *result = buffer, *np;
if ((buffer == NULL) || (count < 1))
result = NULL;
else if (count == 1) *result = '\0';
else if ((result = fgets(buffer, count, stdin)) != NULL)
if (np = strchr(buffer, '\n'))
*np = '\0';
return result;
}
CuuDuongThanCong.com

/>

Khai thác lỗi overflow như thế nào ?
Bộ nhớ máy tính

Mảng có
khả năng bị
tràn

Code khác

Chương trình đang chạy


Ghi vào mảng (gửi dữ liệu có chứa chương
trình gây hại vào CT đang chạy )

Lots of "no operations"

Chương trình gây hại

Chương trình đang chạy thử tiếp tục chạy trên phần bộ nhớ dành
cho nó, nhưng mà phần bộ nhớ này đã bị chương trình gây hại
chiếmCuuDuongThanCong.com
mất khi thực hiện phép ghi vào mảng />

II. LẬP TRÌNH PHÒNG
NGỪA
CuuDuongThanCong.com

/>

1. Khái niệm
• Xuất phát từ khái niệm
defensive driving.
– Khi lái xe bạn luôn phải
tâm niệm rằng bạn không
bao giờ biết chắc được
người lái xe khác sẽ làm
gì. Bằng cách đó, bạn có
thể chắc chắn rằng khi họ
làm điều gì nguy hiểm, thì
bạn sẽ không bị ảnh hưởng

(tai nạn).
– Bạn có trách nhiệm bảo vệ
bản thân, ngay cả khi
người khác có lỗi

CuuDuongThanCong.com

/>

1. Khái niệm
• Ý tưởng chính: nếu chương trình (CTC) nhận dữ
liệu vào bị lỗi thì nó vẫn chạy thông, ngay cả khi
CT khác cũng nhận dữ liệu đầu vào đó đã bị lỗi.
• Lập trình phòng ngừa là cách tự bảo vệ CT của
mình khỏi
– các ảnh hưởng tiêu cực của dữ liệu không hợp lệ
– các rủi ro đến từ các sự kiện tưởng như "không bao giờ"
xảy ra
– sai lầm của các lập trình viên khác

CuuDuongThanCong.com

/>

2. Các lỗi có thể phòng ngừa
• Lỗi liên quan đến phần cứng
– Đảm bảo các lỗi như buffer overflows hay divide by zero
được kiểm soát

• Lỗi liên quan đến chương trình

– Đảm bảo giá trị gán cho các biến luôn nằm trong vùng
kiểm soát
– Do not trust anything; verify everything

• Lỗi liên quan đến người dùng
– Đừng cho rằng người dùng luôn thực hiện đúng các thao
tác theo chỉ dẫn, hãy kiểm tra mọi thao tác của họ

• Lỗi liên quan đến các kỹ thuật phòng ngừa!
– Mã nguồn cài đặt các kỹ thuật phòng ngừa cũng có khả
năng gây lỗi, kiểm tra kỹ phần này

CuuDuongThanCong.com

/>

3. Các giai đoạn lập trình phòng ngừa
• Lập kế hoạch thực hiện công việc:
– Dành thời gian để kiểm tra và gỡ rối chương trình cẩn thận : hoàn
thành chương trình trước ít nhất 3 ngày so với hạn nộp

• Thiết kế chương trình:
– Thiết kế giải thuật trước khi viết bằng ngôn ngữ lập trình cụ thể

• Giữ vững cấu trúc chương trình:
– Viết và kiểm thử từng phần chương trình: phần chương trình nào
dùng để làm gì
– Viết và kiểm thử mối liên kết giữa các phần trong chương trình:
quy trình nghiệp vụ như thế nào
– Phòng ngừa bằng các điều kiện trước và sau khi gọi mỗi phần

chương trình: điều gì phải đúng trước khi gọi chương trình, điều gì
xảy ra sau khi chương trình thực hiện xong
– Dùng chú thích để miêu tả cấu trúc chương trình khi viết chương
trình

CuuDuongThanCong.com

/>

×