Tái cấu trúc project Phần 8
Sau bài này bạn sẽ làm được
- Tách một dự án một tệp thành năm module theo thứ tự an toàn
- Kiểm tra ranh giới module bằng nm và bằng sơ đồ phụ thuộc
- Chạy qua danh sách nghiệm thu của một dự án nhiều tệp
- Viết README nói rõ cách build và cấu trúc dự án
Bài này lấy dự án Phần 8 đang nằm trong một tệp và tách nó thành sáu module. Điều quan trọng không phải kết quả cuối cùng, mà là sau mỗi bước chương trình vẫn biên dịch và vẫn chạy.
#Điểm xuất phát
874 main.c 38
chay duoc
Chi cham vao MOT SinhVien (6 ham) -> sinhvien
sv_kiem_mssv, sv_kiem_ten, sv_kiem_diem,
sv_kiem_nam_sinh, sv_diem_tb_x100, sv_xep_loai
Cham vao MANG sinh vien (11 ham) -> quanly
ql_khoi_tao, ql_giai_phong, ql_them, ql_xoa, ql_sua,
ql_tim_mssv, ql_tim_ten, ql_sap_xep, ql_thong_ke,
va hai ham so sanh static
Cham vao TEP nhi phan (6 ham) -> luutru
lt_ghi_nhi_phan, lt_doc_nhi_phan, ghi_u16, ghi_u32,
doc_u16, doc_u32
Cham vao TEP van ban (4 ham) -> csv
csv_ghi, csv_doc, csv_ghi_truong, csv_tach
Cham vao MAN HINH va BAN PHIM (10 ham) -> ui
ui_menu, ui_hien_thi, ui_them, ui_xoa, ui_tim,
doc_dong, doc_so, doc_diem, in_bang, in_mot_dong
Khong thuoc nhom nao (1 ham) -> main
main#Sáu bước tách
Bước 1: tách loi.h, không có tệp .c
Bắt đầu từ tầng thấp nhất, thứ không phụ thuộc gì cả. Kiểu
MaLoivà hàm mô tả lỗi.terminalgcc -std=c11 -Wall -Wextra main.c loi.c -o ql && ./ql du-lieu.svdb < kich-ban.txt | diff - mong-doi.txt && echo DATDAT
Bước 2: tách sinhvien
Chuyển sáu hàm cùng struct
SinhVienvà các hằng số kích thước. Đây là module dễ nhất vì nó không gọi ai./* Sau khi chuyen, kiem ngay bang mot chuong trinh nho: */ gcc -std=c11 -Wall -Wextra sinhvien.c tests/kiem-sinhvien.c -o kiem ./kiemBước 3: tách quanly
Mười một hàm. Chú ý: hai hàm so sánh dùng cho
qsortphải làstatic, vì không ai ngoài module này gọi chúng.Bước 4: tách luutru và csv
Bốn hàm đóng gói số nguyên phải là
statictrongluutru.c. Nếucsv.ccũng cần chúng thì đó là dấu hiệu chúng thuộc về một module thứ ba, hoặc đơn giản là chép lại vì chúng chỉ vài dòng.Bước 5: tách ui
Mười hàm. Bốn hàm nhập liệu là
static. Đây là module duy nhất được phép gọiprintfvàfgetstrênstdin.Bước 6: dọn main.c
main.c, dưới năm mươi dòng#include <stdio.h> #include <string.h> #include "quanly.h" #include "luutru.h" #include "ui.h" int main(int argc, char **argv) { const char *ten_tep = (argc > 1) ? argv[1] : "du-lieu.svdb"; QuanLy q; ql_khoi_tao(&q); MaLoi m = lt_doc_nhi_phan(ten_tep, &q.ds, &q.n); if (m == QL_OK) { q.suc_chua = q.n; printf("Da tai %zu sinh vien tu %s\n", q.n, ten_tep); } else if (m != QL_LOI_MO_TEP) { fprintf(stderr, "%s: %s\n", ten_tep, ql_mo_ta_loi(m)); ql_giai_phong(&q); return 1; } ui_vong_lap(&q, ten_tep); ql_giai_phong(&q); return 0; }terminalwc -l src/*.c include/*.h38 src/main.c 118 src/sinhvien.c 241 src/quanly.c 186 src/luutru.c 132 src/csv.c 208 src/ui.c 14 include/loi.h 36 include/sinhvien.h 28 include/quanly.h 18 include/luutru.h 14 include/csv.h 10 include/ui.h 1043 total
#Kiểm tra ranh giới
csv xuat 2, khai bao 2 loi xuat 1, khai bao 1 luutru xuat 2, khai bao 2 main xuat 1, khai bao 0 quanly xuat 11, khai bao 8 sinhvien xuat 6, khai bao 6 ui xuat 1, khai bao 1
U isdigit
U isspace
U strlen U free
U memmove
U qsort
U realloc
U strcmp
U sv_kiem_tat_ca18/18 phep kiem dat
24/24 phep kiem dat
#Danh sách nghiệm thu
| Hạng mục | Bắt buộc | Cách kiểm |
|---|---|---|
| Mọi header có include guard | Có | for f in include/*.h; do grep -q '^#ifndef' $f || echo $f; done |
| Không có tên guard trùng nhau | Có | grep -rh '^#ifndef' include/ | sort | uniq -d |
| Mỗi header dịch được một mình | Có | Tạo tệp .c chỉ include nó rồi gcc -fsyntax-only |
| Không có định nghĩa biến trong .h | Có | grep -n '^[a-z].*=' include/*.h |
| Số ký hiệu xuất ra bằng số hàm trong .h | Có | nm -g --defined-only, so với header |
| Không có phụ thuộc vòng tròn | Có | Vòng lặp tìm cặp include lẫn nhau ở Bài 20.5 |
| main.c dưới năm mươi dòng | Có | wc -l src/main.c |
| main.c không chứa logic nghiệp vụ | Có | Đọc lại: chỉ có phân tích tham số, gọi hàm, dọn dẹp |
| make clean && make chạy sạch | Có | Không một dòng cảnh báo |
| Kiểm thử được từng module riêng | Có | gcc tests/kiem-x.c src/x.c -o kiem |
| Valgrind không rò rỉ | Có | valgrind --leak-check=full với kịch bản đầy đủ |
| README nói rõ cách build và cấu trúc | Có | Nhờ người khác làm theo |
Một tập lệnh kiểm tất cả
#!/bin/sh
loi=0
echo "== Header guard"
for f in include/*.h; do
grep -q '^#ifndef' "$f" || { echo " THIEU: $f"; loi=1; }
done
echo "== Trung ten guard"
trung=$(grep -rh '^#ifndef' include/ | sort | uniq -d)
[ -n "$trung" ] && { echo " TRUNG: $trung"; loi=1; }
echo "== Header tu du"
for f in include/*.h; do
echo "#include \"$f\"" > /tmp/tu-du.c
gcc -Iinclude -fsyntax-only /tmp/tu-du.c 2>/dev/null \
|| { echo " KHONG TU DU: $f"; loi=1; }
done
echo "== Dinh nghia bien trong header"
if grep -n '^[a-zA-Z].*[^=]=[^=]' include/*.h | grep -v '#define'; then
loi=1
fi
echo "== Bien dich sach"
make clean >/dev/null
make 2>&1 | grep -E 'warning|error' && loi=1
echo "== Ky hieu xuat thua"
for o in build/*.o; do
t=$(basename "$o" .o)
[ -f "include/$t.h" ] || continue
x=$(nm -g --defined-only "$o" | wc -l)
k=$(grep -c ');' "include/$t.h")
[ "$x" -gt "$k" ] && { echo " $t xuat $x, khai bao $k"; loi=1; }
done
echo "== main.c ngan"
n=$(wc -l < src/main.c)
[ "$n" -gt 50 ] && { echo " main.c co $n dong"; loi=1; }
[ "$loi" -eq 0 ] && echo "TAT CA DAT" || echo "CO MUC CHUA DAT"
exit $loi== Header guard == Trung ten guard == Header tu du == Dinh nghia bien trong header == Bien dich sach == Ky hieu xuat thua quanly xuat 11, khai bao 8 == main.c ngan CO MUC CHUA DAT
== Header guard == Trung ten guard == Header tu du == Dinh nghia bien trong header == Bien dich sach == Ky hieu xuat thua == main.c ngan TAT CA DAT
Tự làm thử
- Ghi lại kết quả chạy đúng của dự án Phần 8 vào
mong-doi.txttrước khi sửa gì. - Tách
loi.hvàsinhvien, build lại, vàdiffvới kết quả cũ. - Tách nốt bốn module còn lại, mỗi lần một module, kiểm sau mỗi bước.
- Chạy
nm -g --defined-onlytrên từng tệp.ovà thêmstaticcho mọi ký hiệu không có trong header. - Viết một tệp kiểm thử cho
sinhvienvà liên kết nó chỉ với hai tệp nguồn. - Viết
kiem-du-an.shvà chạy cho tới khi mọi mục đều đạt.
Trình chấm điểm tự động sẽ được bổ sung ở giai đoạn sau. Hiện tại bạn tự chạy thử trên máy.
Tóm tắt
- Tách từng module một, và sau mỗi bước phải build lại rồi so kết quả với bản ghi trước khi sửa.
- Bắt đầu từ tầng thấp nhất, thứ không phụ thuộc gì cả, rồi đi dần lên trên.
- Tổng số dòng tăng khoảng hai mươi phần trăm, và đó là cái giá hợp lý cho ranh giới rõ ràng.
nm -g --defined-onlyso với số hàm trong header cho biết ngay bạn quênstaticở đâu.- Phép thử tốt nhất cho ranh giới module là: kiểm thử nó có cần kéo theo module khác không.