Bài 56.328 phút đọc
Chia module và đóng gói
Sau bài này bạn sẽ làm được
- Chia một tệp lớn thành các module có ranh giới rõ
- Áp dụng bốn mức đóng gói của C
- Vẽ và kiểm tra sơ đồ phụ thuộc
- Phát hiện phụ thuộc vòng và phá vỡ nó
C không có class, không có module, không có namespace. Nhưng nó có static, có kiểu không hoàn chỉnh, và có sự tách biệt giữa header và tệp nguồn. Ba thứ đó đủ để đóng gói tốt, nếu bạn dùng chúng có kỷ luật.
#Một module là gì
Module trong C
Một cặp
ten.h và ten.c có đúng một trách nhiệm. Header là hợp đồng công khai; tệp nguồn là hiện thực, và mọi thứ trong đó không nằm trong header phải là static.Ba câu hỏi cho một module
/* 1. No chiu trach nhiem VE cai gi?
Neu ban tra loi bang mot cau co chu "va" thi no lam qua nhieu.
"quan ly danh sach sinh vien VA doc tep VA in bao cao"
-> ba module: danh_sach.c, doc_tep.c, bao_cao.c
2. No CAN nhung gi tu ben ngoai?
Cang it cang tot. Mot module chi phu thuoc vao thu vien chuan
la mot module ban kiem thu duoc de dang.
3. No LO ra nhung gi?
Dem so ky hieu trong header. Neu hon 20 thi hoac module qua
to, hoac ban dang lo ra chi tiet ben trong. */| Dấu hiệu module xấu | Nghĩa là gì |
|---|---|
| Header hơn 300 dòng | Giao diện quá rộng, hoặc struct nên được ẩn |
| Tệp .c hơn 1000 dòng | Nhiều trách nhiệm nằm chung |
| Header include hơn năm header khác | Phụ thuộc quá nhiều, khó kiểm thử |
| Đổi một dòng làm nửa dự án dịch lại | Header lộ chi tiết bên trong |
| Không kiểm thử được nếu không có mạng hoặc tệp | Trộn lẫn logic với vào ra |
| Tên module có chữ util, common, misc | Không có trách nhiệm nào cả |
#Bốn mức đóng gói
| Mức | Ai thấy được | Cách làm |
|---|---|---|
| Biến static trong hàm | Chỉ hàm đó | static int dem; |
| Ký hiệu static trong tệp | Chỉ tệp .c đó | static void giup(void); |
| Kiểu không hoàn chỉnh | Tên thấy, nội dung không | typedef struct Ds Ds; |
| Ký hiệu ẩn trong thư viện dùng chung | Trong thư viện, không ra ngoài | visibility("hidden") |
Mức ba là mức quan trọng nhất: kiểu không hoàn chỉnh
/* ds.h, cong khai */
#ifndef DS_H
#define DS_H
#include <stddef.h>
typedef struct Ds Ds; /* ten thay duoc, noi dung KHONG */
Ds *ds_tao(size_t suc_chua_dau);
void ds_huy(Ds *d);
int ds_them(Ds *d, int x);
size_t ds_so_luong(const Ds *d);
#endif
/* ds.c, hien thuc */
#include "ds.h"
#include <stdlib.h>
struct Ds { /* dinh nghia CHI o day */
int *pt;
size_t co;
size_t suc_chua;
};
Ds *ds_tao(size_t suc_chua_dau) { ... }
size_t ds_so_luong(const Ds *d) { return d->co; }terminal
# Nguoi dung thu truy cap truong ben trong
gcc -std=c11 -Wall -Wextra sua.c sua_a.c sua_b.c -o sua2.exe
sua.c: In function 'main':
sua.c:10:21: error: dereferencing pointer to incomplete type 'A' {aka 'struct A'}
printf("%d\n", a->x);
^~#Viết header đúng
Khuôn mẫu một header
#ifndef DU_AN_DS_H /* bao ve: TIEN TO DU AN + duong dan */
#define DU_AN_DS_H
/* 1. Include TOI THIEU can cho chinh header nay */
#include <stddef.h> /* size_t */
/* 2. Khai bao truoc thay cho include khi chi can con tro */
typedef struct CauHinh CauHinh;
/* 3. Kieu cong khai */
typedef struct Ds Ds;
/* 4. Hang so cong khai */
#define DS_TOI_DA 1000000
/* 5. Ham, kem tai lieu ngan */
/**
* Tao mot danh sach rong.
* @param suc_chua_dau so phan tu cap truoc, 0 nghia la mac dinh
* @return con tro moi, hoac NULL neu het bo nho
* Nguoi goi PHAI goi ds_huy.
*/
Ds *ds_tao(size_t suc_chua_dau);
/** Giai phong. An toan voi NULL. */
void ds_huy(Ds *d);
/**
* Them mot phan tu vao cuoi.
* @return 0 thanh cong, -1 het bo nho, -2 vuot DS_TOI_DA
*/
int ds_them(Ds *d, int x);
#endif /* DU_AN_DS_H */#Sơ đồ phụ thuộc
terminal
# gcc -MM in ra phu thuoc header cua tung tep .c
gcc -MM sua.c sua_a.c sua_b.c
sua.o: sua.c sua_a.h sua_b.h sua_a.o: sua_a.c sua_a.h sua_b.h sua_b.o: sua_b.c sua_b.h sua_a.h
# Do thi include tho, mot dong mot canh
for f in *.c *.h; do grep -o '#include "[^"]*"' $f | sed "s|#include \"|$f -> |; s|\"||"; done
sua.c -> sua_a.h sua.c -> sua_b.h sua_a.c -> sua_a.h sua_a.c -> sua_b.h sua_b.c -> sua_b.h sua_b.c -> sua_a.h vong.c -> vong_a.h vong_a.h -> vong_b.h vong_b.h -> vong_a.h
#Phụ thuộc vòng
vong_a.h và vong_b.h
/* vong_a.h */
#ifndef VONG_A_H
#define VONG_A_H
#include "vong_b.h" /* A can B */
typedef struct { B *b; int x; } A;
A *a_tao(void);
void a_dung_b(A *a);
#endif
/* vong_b.h */
#ifndef VONG_B_H
#define VONG_B_H
#include "vong_a.h" /* B can A: VONG */
typedef struct { A *a; int y; } B;
B *b_tao(void);
void b_dung_a(B *b);
#endifterminal
gcc -std=c11 -Wall -Wextra -c vong.c -o vong.o
In file included from vong_a.h:3,
from vong.c:1:
vong_b.h:4:18: error: unknown type name 'A'
typedef struct { A *a; int y; } B;
^Vòng, phải include lẫn nhau
/* vong_a.h */
#include "vong_b.h"
typedef struct { B *b; int x; } A;
/* vong_b.h */
#include "vong_a.h"
typedef struct { A *a; int y; } B;
Khai báo trước, không include
/* sua_a.h */
typedef struct A A; /* khai bao truoc */
typedef struct B B;
A *a_tao(void);
void a_dung_b(A *a, B *b);
/* sua_b.h */
typedef struct A A;
typedef struct B B;
B *b_tao(void);
void b_dung_a(B *b, A *a);
/* sua_a.c: dinh nghia struct A o day, include CA HAI header */
#include "sua_a.h"
#include "sua_b.h"
struct A { int x; };
terminal
gcc -std=c11 -Wall -Wextra sua.c sua_a.c sua_b.c -o sua.exe
./sua.exe
hai module doc lap, khong con vong
Cách ba, đảo ngược phụ thuộc bằng con trỏ hàm
/* TRUOC: mang.c goi giao_dien_ve() -> mang phu thuoc giao dien
giao_dien.c goi mang_gui() -> giao dien phu thuoc mang
VONG. */
/* SAU: mang.h khong biet gi ve giao dien */
typedef void (*HamNhan)(const char *tin, void *ctx);
void mang_dat_ham_nhan(HamNhan f, void *ctx);
int mang_gui(const char *tin);
/* mang.c */
static HamNhan g_nhan;
static void *g_ctx;
void mang_dat_ham_nhan(HamNhan f, void *ctx) { g_nhan = f; g_ctx = ctx; }
static void khi_co_tin(const char *tin) {
if (g_nhan) g_nhan(tin, g_ctx); /* khong biet ai se nhan */
}
/* giao_dien.c */
#include "mang.h"
static void hien_thi(const char *tin, void *ctx) { ... }
void giao_dien_khoi_tao(void) {
mang_dat_ham_nhan(hien_thi, NULL); /* giao dien -> mang, MOT chieu */
}
/* Bay gio do thi la: giao_dien.c -> mang.h
Khong con vong, va mang.c kiem thu duoc ma khong can giao dien. */Tự làm thử
- Lấy một tệp
.chơn 500 dòng của bạn và tách nó thành ít nhất hai module có ranh giới rõ. - Chuyển một struct công khai của bạn thành kiểu không hoàn chỉnh và sửa mọi chỗ dùng.
- Kiểm tra mọi header có tự đủ không bằng
-fsyntax-only. - Sinh đồ thị include của dự án và vẽ nó ra.
- Tái hiện lỗi phụ thuộc vòng rồi chữa bằng khai báo trước.
- Thêm
-MMD -MPvào Makefile và chứng minh rằng sửa một header làm đúng những tệp cần thiết dịch lại.
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
- Một module là một cặp
.hvà.cvới đúng một trách nhiệm;util.ckhông phải là một trách nhiệm. - Bốn mức đóng gói; quan trọng nhất là kiểu không hoàn chỉnh, đổi lại là bắt buộc cấp phát động.
- Header phải tự đủ, include tối thiểu, và không định nghĩa biến hay hàm.
gcc -MMcho bạn đồ thị phụ thuộc;-MMD -MPcho Make biết khi nào dịch lại.- Phá vòng bằng khai báo trước, tách module chung, đảo ngược bằng con trỏ hàm, hoặc gộp lại.