BlogNguyên tắc lập trình C/C++ — Từ cơ bản đến chuyên biệt
Bố cục bài viết
Trang này
Ngày tạo
13/08/2026
Cập nhật
Lượt xem
5
Đọc
22m
Nguyên tắc lập trình C/C++ — Từ cơ bản đến chuyên biệt (Embedded / RTOS / Viễn thông / An ninh mạng)
Tài liệu này dành cho người mới bắt đầu nhưng muốn viết code C/C++ theo chuẩn mực chuyên nghiệp: an toàn – tối ưu – dễ đọc. Đọc theo thứ tự: Phần 1 là nền móng bắt buộc phải nắm, Phần 2 là quy tắc áp dụng cho mọi dự án, Phần 3 dành riêng cho hệ thống nhúng/thời gian thực/viễn thông/an ninh mạng.
Mục lục
- Tầng 1 — Nguyên tắc cơ bản (Nền móng bắt buộc)
- Tầng 2 — Nguyên tắc chung (Áp dụng cho mọi dự án C/C++)
- Tầng 3 — Nguyên tắc chuyên biệt (Embedded / RTOS / Viễn thông / An ninh mạng)
- Quy tắc đặt tên biến, hàm, kiểu dữ liệu
- Checklist review code (in ra dán cạnh bàn)
Tầng 1 — Nguyên tắc cơ bản (Nền móng bắt buộc)
Đây là những điều bắt buộc phải hiểu trước khi viết bất kỳ dòng code C/C++ nào, vì C/C++ không có “lưới an toàn” như Python/Java — sai một chút là chương trình có thể crash hoặc chạy sai âm thầm.
1.1. Hiểu “Undefined Behavior” (UB) là gì và vì sao đáng sợ
Trong C/C++, có những hành vi mà ngôn ngữ không quy định kết quả — chương trình có thể chạy đúng, crash, hoặc cho kết quả sai tùy máy/tùy lần chạy. Đây là nguồn gốc của rất nhiều bug khó tìm nhất.
Các lỗi UB kinh điển cần tránh ngay từ đầu:
| Lỗi | Ví dụ | Hậu quả |
|---|---|---|
| Truy cập mảng ngoài biên | arr[10] khi mảng chỉ có 5 phần tử | Crash, đọc/ghi đè dữ liệu khác, lỗ hổng bảo mật |
| Dùng biến chưa khởi tạo | int x; printf("%d", x); | Giá trị rác, không đoán trước được |
| Dùng con trỏ sau khi đã free | Use-after-free | Crash, có thể bị khai thác (exploit) |
Dereference con trỏ nullptr | int* p = nullptr; *p = 5; | Crash ngay lập tức |
| Chia cho 0 với số nguyên | int x = 5 / 0; | UB (khác với float, sẽ ra inf) |
| Tràn số nguyên có dấu (signed overflow) | INT_MAX + 1 | UB (không “quay vòng” một cách an toàn) |
Nguyên tắc gốc: Nếu không chắc một hành vi có an toàn hay không, hãy tra cứu, đừng đoán mò hay tin vào “chạy được trên máy mình là được”.
1.2. Luôn khởi tạo biến
// Sai — biến x chứa giá trị rác
int x;
// Đúng
int x = 0;
1.3. Một biến, một mục đích, một phạm vi nhỏ nhất có thể
Khai báo biến càng gần nơi sử dụng càng tốt, không khai báo hàng loạt biến ở đầu hàm rồi dùng rải rác — dễ nhầm lẫn và khó theo dõi vòng đời biến.
1.4. Luôn kiểm tra giá trị trả về của hàm có thể thất bại
FILE* f = fopen("data.txt", "r");
if (f == NULL) {
// xử lý lỗi, không được bỏ qua!
return -1;
}
Áp dụng cho: malloc, fopen, hàm đọc/ghi mạng, hàm parse dữ liệu… Rất nhiều lỗi bảo mật nghiêm trọng bắt nguồn từ việc bỏ qua giá trị lỗi trả về.
1.5. Biên dịch với cảnh báo (warning) ở mức cao nhất — và coi cảnh báo như lỗi
g++ -Wall -Wextra -Wpedantic -Werror -std=c++20 main.cpp
Đây là thói quen quan trọng bậc nhất cho người mới: trình biên dịch thường “nhìn thấy” lỗi tiềm ẩn trước khi bạn kịp chạy chương trình.
Tầng 2 — Nguyên tắc chung (Áp dụng cho mọi dự án C/C++)
Khi đã nắm vững Tầng 1, đây là các nguyên tắc kinh điển giúp code an toàn, tối ưu, và dễ đọc ở quy mô dự án thực tế.
2.1. Quản lý bộ nhớ & tài nguyên
RAII (Resource Acquisition Is Initialization) — nguyên tắc số 1 của C++
Ý tưởng: gắn tài nguyên (bộ nhớ, file, khóa mutex…) vào vòng đời của một object. Object được tạo ra thì tài nguyên được cấp; object bị hủy (ra khỏi scope) thì tài nguyên tự động được giải phóng — không cần bạn nhớ gọi
free/closethủ công.
// Cách cũ (C-style) — dễ quên free, dễ leak khi có exception
int* arr = new int[100];
// ... nếu có lỗi/exception ở đây, arr sẽ bị leak
delete[] arr;
// Cách RAII (khuyến nghị)
std::vector<int> arr(100); // tự động giải phóng khi ra khỏi scope
- Không dùng
new/deletethô trong code nghiệp vụ. Dùngstd::unique_ptr,std::shared_ptr,std::vector,std::string. std::unique_ptr: sở hữu tài nguyên duy nhất, không copy được (chỉ move). Dùng khi chỉ một nơi “sở hữu” đối tượng.std::shared_ptr: nhiều nơi cùng sở hữu, tự đếm tham chiếu. Dùng khi thực sự cần chia sẻ quyền sở hữu (cẩn thận vòng lặp tham chiếu → dùngstd::weak_ptrđể phá vòng lặp).
Rule of Zero / Rule of Five
- Rule of Zero: Nếu class của bạn không trực tiếp quản lý tài nguyên thô (chỉ chứa các thành viên là
std::vector,std::string, smart pointer…), đừng viết destructor/copy/move — để compiler tự sinh đúng và đủ. - Rule of Five: Nếu class quản lý tài nguyên thô (con trỏ raw, file handle…), phải định nghĩa đủ cả 5: destructor, copy constructor, copy assignment, move constructor, move assignment (hoặc đánh dấu
= deleteđể cấm nếu không hợp lý).
Con trỏ
- Ưu tiên tham chiếu (
&) hoặc smart pointer hơn con trỏ thô (raw pointer). - Con trỏ thô chỉ nên dùng để “quan sát” (không sở hữu), không bao giờ dùng nó để sở hữu tài nguyên cần giải phóng.
- Không bao giờ trả về con trỏ/tham chiếu tới biến cục bộ (local) — biến đó đã “chết” khi hàm kết thúc.
// SAI — con trỏ trỏ đến vùng nhớ đã bị hủy
int* create() {
int x = 5;
return &x; // x là biến local, sẽ bị hủy khi hàm return
}
// ĐÚNG
std::unique_ptr<int> create() {
return std::make_unique<int>(5);
}
2.2. An toàn kiểu dữ liệu (Type Safety)
- Không dùng ép kiểu kiểu C
(int)x. Dùng các cast của C++ vì mỗi loại có mục đích rõ ràng, dễ tìm kiếm khi review code:static_cast<T>(): ép kiểu thông thường, an toàn khi kiểu tương thích.dynamic_cast<T>(): ép kiểu đa hình (polymorphism), có kiểm tra runtime.const_cast<T>(): bỏconst(hiếm khi cần, cẩn trọng).reinterpret_cast<T>(): ép kiểu cấp thấp (bit pattern), rủi ro cao, dùng tối thiểu.
- Dùng
enum classthay vìenumthường — tránh tự động chuyển đổi ngầm sangintvà tránh xung đột tên giữa các enum khác nhau. - Dùng
constexpr/constthay vì macro#definecho hằng số — vì có kiểm tra kiểu, dễ debug hơn.
// Không tốt
#define MAX_SIZE 100
// Tốt hơn
constexpr int kMaxSize = 100;
2.3. Const-correctness (đánh dấu const mọi nơi có thể)
- Biến không đổi giá trị → khai báo
const. - Tham số truyền vào hàm nhưng không sửa đổi bên trong → dùng
const T&(tránh copy tốn kém với object lớn, đồng thời thông báo rõ ý định “hàm này không sửa dữ liệu của bạn”). - Method của class không làm thay đổi trạng thái object → đánh dấu
const.
class Buffer {
public:
size_t size() const { return size_; } // không sửa gì, nên const
void print(const std::string& name) const; // tham số const&, method const
private:
size_t size_;
};
Lợi ích: compiler tối ưu tốt hơn, code tự “kể” ý định của người viết, và compiler sẽ báo lỗi ngay nếu bạn lỡ tay sửa nhầm giá trị không nên sửa.
2.4. Xử lý lỗi (Error Handling)
- Exception dùng cho lỗi thực sự ngoại lệ, không dùng để điều khiển luồng chương trình thông thường (ví dụ không nên dùng exception để thoát vòng lặp).
- Tuyệt đối không throw exception trong destructor — có thể khiến chương trình gọi
std::terminate()và crash ngay lập tức nếu đang trong quá trình “unwind” do một exception khác. - Với C thuần hoặc code hiệu năng cao, dùng mã lỗi trả về (error code / status enum) thay vì exception.
2.5. Tối ưu hiệu năng — nhưng đúng cách
- Đo trước khi tối ưu (profiling). Câu nói kinh điển của Donald Knuth: “tối ưu sớm là nguồn gốc của mọi cái ác” — chỉ tối ưu đúng chỗ đang thực sự chậm (hotspot), không đoán mò.
- Tránh copy dữ liệu thừa: dùng
std::movekhi chuyển quyền sở hữu, truyền tham số lớn bằngconst&. - Gọi
vector.reserve(n)trước khi thêm nhiều phần tử để tránh cấp phát lại bộ nhớ nhiều lần. - Hiểu độ phức tạp thuật toán (Big-O) trước khi “vặn vẹo” từng dòng code — thuật toán tốt hơn luôn thắng micro-optimization.
- Code đơn giản, rõ ràng thường được trình biên dịch tối ưu tốt hơn code “khôn lỏi” viết tay — đừng cố qua mặt compiler.
2.6. Đa luồng (Concurrency) — nếu dự án có dùng thread
- Dữ liệu chia sẻ giữa các thread luôn cần đồng bộ hóa:
std::mutexkết hợpstd::lock_guard/std::unique_lock(áp dụng RAII cho khóa — tự động unlock khi ra khỏi scope), hoặcstd::atomiccho biến đơn giản. - Luôn lock các mutex theo cùng một thứ tự ở mọi nơi trong code để tránh deadlock; dùng
std::lock()khi cần khóa nhiều mutex cùng lúc. - Dùng công cụ
-fsanitize=threadkhi test để tự động phát hiện race condition.
2.7. Viết code dễ đọc (Readability)
- Đặt tên rõ ràng, nhất quán trong toàn dự án (xem Mục 4).
- Mỗi hàm chỉ nên làm một việc (Single Responsibility). Nếu hàm dài quá một màn hình, cân nhắc tách nhỏ.
- Tránh số “ma thuật” (magic number) — luôn đặt tên cho hằng số.
- Hạn chế lồng nhau (nested if/else) quá sâu — ưu tiên “early return” để code phẳng, dễ đọc.
- Comment nên giải thích “tại sao” làm vậy, không phải “cái gì” đang xảy ra (code rõ ràng đã tự nói lên “cái gì”).
// Không tốt — lồng nhau sâu, khó đọc
void process(int x) {
if (x > 0) {
if (x < 100) {
// xử lý
}
}
}
// Tốt hơn — early return, phẳng, dễ đọc
void process(int x) {
if (x <= 0) return;
if (x >= 100) return;
// xử lý
}
2.8. Dùng chuẩn C++ hiện đại (Modern C++)
- Ưu tiên C++17/20 trở lên:
auto, structured bindings,std::optional,std::variant, ranges. - Tận dụng thư viện chuẩn (STL) — không tự viết lại container/thuật toán cơ bản (linked list, sort…) trừ khi có lý do đặc biệt.
- Dùng
#pragma once(hoặc include guard đầy đủ) ở đầu mọi file header để tránh include lặp.
2.9. Công cụ nên dùng ngay từ đầu dự án
| Công cụ | Mục đích |
|---|---|
-Wall -Wextra -Wpedantic -Werror | Bắt lỗi tiềm ẩn ngay lúc biên dịch |
clang-format | Tự động định dạng code, tránh tranh cãi style |
clang-tidy, cppcheck | Phân tích tĩnh, phát hiện bug/code smell |
-fsanitize=address,undefined | Bắt lỗi bộ nhớ, UB khi chạy test |
| Google Test / Catch2 | Viết unit test cho logic quan trọng |
Tầng 3 — Nguyên tắc chuyên biệt (Embedded / RTOS / Viễn thông / An ninh mạng)
Đây là những quy tắc bắt buộc khi làm việc trong lĩnh vực nhúng, hệ thống thời gian thực, viễn thông, hoặc bất cứ nơi nào xử lý dữ liệu từ nguồn không tin cậy (mạng, RF…). Sai sót ở đây không chỉ gây crash mà có thể gây sự cố an toàn (safety) hoặc lỗ hổng bảo mật (security) nghiêm trọng.
3.1. Nguyên tắc nền tảng cho Embedded/RTOS
Không cấp phát bộ nhớ động sau khi khởi tạo hệ thống
Vì sao?
malloc/newcó thời gian thực thi không xác định trước (có thể nhanh, có thể chậm tùy tình trạng heap), gây phân mảnh bộ nhớ theo thời gian. Điều này vi phạm yêu cầu “thời gian thực” (real-time) — hệ thống phải phản hồi trong thời gian đảm bảo.
- Cấm
malloc/free/new/deletetrong vòng lặp chính hoặc trong ngắt (interrupt). - Cấp phát toàn bộ bộ nhớ cần thiết tĩnh hoặc dùng memory pool đã chuẩn bị sẵn từ lúc khởi động.
Không dùng exception và RTTI trong code lõi thời gian thực
try/catch/throwcó overhead không dự đoán được, làm tăng kích thước binary và khó tính toán WCET (Worst-Case Execution Time — thời gian thực thi tệ nhất).- Nhiều toolchain nhúng build với cờ
-fno-exceptions -fno-rtti. - Thay vào đó: trả về mã lỗi (error code) hoặc enum trạng thái.
Tính xác định (Determinism) là ưu tiên số một
- Tránh đệ quy không giới hạn độ sâu → dùng vòng lặp hoặc giới hạn số lần đệ quy rõ ràng (tránh tràn stack).
- Tránh
std::vector/std::string(có thể tự resize, thời gian không xác định) trong đường xử lý thời gian thực cứng — dùng mảng cố định kích thước (std::array) hoặc buffer đã cấp phát sẵn.
Ngắt (Interrupt Service Routine — ISR)
- ISR phải cực ngắn: không gọi hàm blocking, không cấp phát bộ nhớ, không dùng mutex thông thường.
- Biến được chia sẻ giữa ISR và chương trình chính phải khai báo
volatile, và đồng bộ bằng cơ chế an toàn cho ngắt (ví dụ thao tác atomic, tắt/bật ngắt trong đoạn code ngắn — gọi là “critical section”).
Lưu ý quan trọng:
volatilekhông phải công cụ đồng bộ đa luồng — nó chỉ báo cho compiler “đừng tối ưu/cache giá trị này”. Với đa luồng thật sự, phải dùngstd::atomic.
Chuẩn MISRA C/C++ và AUTOSAR C++14
- Nếu làm trong lĩnh vực an toàn cao (automotive, hàng không, viễn thông hạ tầng), dự án thường phải tuân thủ chuẩn MISRA (cấm
goto, cấm biểu thức có side-effect trong điều kiệnif, yêu cầu ép kiểu tường minh…). - Dùng công cụ kiểm tra tuân thủ tự động trong quy trình CI: Polyspace, Coverity, PC-lint Plus.
3.2. Bộ nhớ & phần cứng cấp thấp
- Alignment (căn chỉnh bộ nhớ): khi struct ánh xạ tới thanh ghi phần cứng hoặc gói tin mạng, phải hiểu rõ cách compiler sắp xếp bộ nhớ (padding). Kiểm tra bằng
static_assert(sizeof(MyStruct) == kExpectedSize). - Endianness (thứ tự byte): dữ liệu qua mạng/giao thức viễn thông phải xử lý rõ ràng thứ tự byte (
htons/ntohshoặc serialize thủ công từng byte) — không bao giờ giả định máy gửi và máy nhận dùng cùng thứ tự byte. - Không ép kiểu con trỏ trực tiếp lên struct khi đọc dữ liệu từ buffer mạng/cảm biến — vi phạm “strict aliasing rule” của C++, gây UB. Thay vào đó, dùng
memcpyđể sao chép an toàn vào biến đã khai báo đúng kiểu.
// SAI — vi phạm strict aliasing, có thể UB hoặc sai do alignment
Header* h = reinterpret_cast<Header*>(raw_buffer);
// ĐÚNG — sao chép an toàn
Header h;
std::memcpy(&h, raw_buffer, sizeof(Header));
h.length = ntohs(h.length); // chuyển đúng thứ tự byte
3.3. An ninh mạng / An ninh viễn thông (Secure Coding)
Nguyên tắc bao trùm: Không bao giờ tin dữ liệu đến từ bên ngoài (mạng, RF, người dùng, cảm biến…). Mọi input phải được coi là “thù địch” cho đến khi được kiểm chứng.
Chống tràn bộ đệm (Buffer Overflow) — lỗi bảo mật kinh điển và nguy hiểm nhất
- Cấm tuyệt đối:
strcpy,strcat,sprintf,gets— các hàm này không kiểm tra biên bộ nhớ đích. - Dùng thay thế:
snprintf(có giới hạn kích thước),std::string, hoặcstd::span(C++20, mang theo thông tin độ dài khi làm việc với buffer thô). - Luôn kiểm tra độ dài khai báo trong header của gói tin có khớp với độ dài thực tế nhận được trước khi đọc payload — tấn công giả mạo độ dài là kỹ thuật phổ biến.
Xử lý dữ liệu nhạy cảm (khóa mã hóa, mật khẩu, token)
- Không dùng
std::stringthông thường để chứa secret — dữ liệu có thể bị copy nhiều lần trong bộ nhớ và không bị xóa sạch khi “xóa” (vìstd::stringkhông đảm bảo ghi đè vùng nhớ cũ). - Xóa vùng nhớ chứa secret bằng hàm chuyên dụng như
explicit_bzero/memset_s— không dùngmemsetthường, vì compiler có thể tối ưu bỏ qua lệnh ghi đè “vô nghĩa” (dữ liệu không được đọc lại sau đó) này. - Không bao giờ log secret/khóa/mật khẩu dưới bất kỳ hình thức nào, kể cả trong log debug.
So sánh dữ liệu bảo mật phải dùng constant-time comparison
Vì sao? Hàm so sánh thông thường (
==,memcmp) dừng lại ngay khi phát hiện byte đầu tiên khác nhau — kẻ tấn công có thể đo thời gian phản hồi để đoán dần từng byte của mã bí mật (gọi là timing attack).
// SAI — dễ bị timing attack (so sánh dừng sớm khi gặp byte sai)
if (memcmp(computed_mac, received_mac, len) == 0) { ... }
// ĐÚNG — so sánh với thời gian không đổi bất kể dữ liệu
if (CRYPTO_memcmp(computed_mac, received_mac, len) == 0) { ... } // hàm của OpenSSL
Không tự viết thuật toán mã hóa (crypto)
- Luôn dùng thư viện đã được kiểm định kỹ lưỡng: OpenSSL, mbedTLS, BoringSSL (cho embedded: wolfSSL, mbedTLS).
- Không tự implement AES/RSA/hash — kể cả khi hiểu rõ thuật toán trên lý thuyết, việc cài đặt an toàn (chống side-channel, timing attack…) đòi hỏi chuyên môn sâu.
- Sinh số ngẫu nhiên cho mục đích bảo mật (khóa, token, nonce) phải dùng bộ sinh số ngẫu nhiên an toàn về mật mã (CSPRNG) như
RAND_bytes(OpenSSL) hoặc bộ sinh ngẫu nhiên phần cứng — không bao giờ dùngrand()thông thường cho việc này (dễ đoán được).
Fail-safe / Fail-secure — nguyên tắc xử lý khi có lỗi
- Khi phát hiện lỗi parse, xác thực thất bại, hoặc dữ liệu bất thường → hệ thống phải chuyển ngay về trạng thái an toàn (từ chối gói tin, ngắt kết nối) — không bao giờ “cố xử lý tiếp” với dữ liệu chưa được xác nhận hợp lệ.
- Giới hạn tốc độ xử lý gói tin bất thường (rate limiting) ở tầng ứng dụng để chống tấn công từ chối dịch vụ (DoS).
Giao thức viễn thông cụ thể (SS7, Diameter, SIP, TLV, ASN.1…)
- Ưu tiên dùng bộ decode đã được kiểm định hoặc sinh tự động bằng compiler ASN.1, thay vì tự parse tay (rất dễ lỗi và là nguồn gốc nhiều lỗ hổng thực tế trong hạ tầng viễn thông).
- Với cấu trúc dữ liệu lồng nhau (TLV lồng TLV…), luôn đặt giới hạn cứng về độ sâu lồng nhau và số lượng phần tử lặp để chống kiểu tấn công gây bùng nổ tài nguyên khi parse (tương tự kiểu tấn công “billion laughs” trong XML).
3.4. Kiểm thử bắt buộc cho hệ thống này
| Công cụ | Mục đích | Ghi chú |
|---|---|---|
-fsanitize=address,undefined | Phát hiện overflow, UB | Dùng khi test, không dùng trên bản production |
clang-tidy, cppcheck, PVS-Studio | Phân tích tĩnh | Chạy tự động trong CI |
Polyspace, Coverity | Chứng nhận an toàn (safety-critical) | Bắt buộc với automotive/hàng không |
Fuzzing (libFuzzer, AFL++) | Ném dữ liệu ngẫu nhiên/bất thường vào hàm parse | Bắt buộc, không phải tùy chọn, cho mọi hàm xử lý dữ liệu từ mạng |
-fsanitize=thread | Phát hiện race condition | Dùng khi có đa luồng |
Ghi nhớ: Với bất kỳ hàm nào nhận dữ liệu từ bên ngoài (mạng, giao thức viễn thông, file cấu hình không tin cậy), fuzzing là yêu cầu bắt buộc, không phải “nice to have”.
4. Quy tắc đặt tên biến, hàm, kiểu dữ liệu
Không có chuẩn “đúng tuyệt đối” — điều quan trọng nhất là nhất quán trong toàn bộ dự án. Dưới đây là convention phổ biến, khuyến nghị cho hệ thống nhúng/lớn (theo phong cách Google C++ Style Guide kết hợp thực tiễn embedded).
4.1. Biến
| Loại biến | Convention | Ví dụ |
|---|---|---|
| Biến cục bộ (local) | snake_case | packet_length, retry_count |
| Biến thành viên của class | snake_case_ (gạch dưới ở cuối) | buffer_size_, is_connected_ |
| Biến toàn cục (hạn chế tối đa việc dùng) | prefix g_ | g_system_state |
| Biến static trong phạm vi file | prefix s_ | s_instance_count |
Hằng số / constexpr | kPascalCase hoặc UPPER_SNAKE_CASE | kMaxRetries, MAX_PACKET_SIZE |
| Con trỏ (một số chuẩn MISRA/embedded) | prefix p_ | p_header, p_next |
| Cờ/bit trạng thái phần cứng | mô tả rõ ý nghĩa, dạng câu hỏi | is_tx_ready, has_crc_error |
4.2. Hàm
- Dùng
snake_case()hoặccamelCase()— chọn một kiểu và dùng xuyên suốt dự án, không trộn lẫn. - Tên hàm nên là động từ + danh từ, mô tả rõ hành động hàm thực hiện:
parse_packet(),validate_checksum(),send_heartbeat()
- Hàm trả về kiểu
boolnên đặt tên dạng câu hỏi để dễ đọc tại nơi gọi:is_valid(),has_timeout(),can_retry()
- Phân biệt rõ hàm chỉ đọc dữ liệu (không side-effect) và hàm có làm thay đổi trạng thái:
get_state()(chỉ đọc, không đổi gì) khác vớiupdate_state()(có thay đổi trạng thái).
4.3. Type / Class / Struct / Enum
- Dùng
PascalCasecho tên kiểu dữ liệu:class PacketParser,struct FrameHeader,enum class ConnectionState. - Tổ chức theo namespace rõ ràng theo module, tránh tên chung chung dễ đụng độ:
- Tốt:
telecom::sip::MessageParser - Không tốt:
Parser(tên quá chung, dễ trùng khi dự án lớn dần)
- Tốt:
4.4. Macro
- Chỉ dùng macro cho include guard hoặc biên dịch có điều kiện (conditional compilation) — hạn chế tối đa cho việc khác (ưu tiên
constexpr/inline/template). - Luôn viết
UPPER_SNAKE_CASE, và luôn prefix theo tên module để tránh xung đột tên toàn cục:TELECOM_SIP_MAX_HEADER_LENthay vì chỉMAX_HEADER_LEN
4.5. File
- Đặt tên file theo
snake_case.h/snake_case.cpp. - Tên file nên khớp với tên class/module chính bên trong nó:
- File
packet_parser.hnên chứaclass PacketParser.
- File
4.6. Nguyên tắc chung khi đặt tên (áp dụng cho mọi trường hợp trên)
- Không viết tắt mơ hồ: tên như
tmp,data,val,flagkhông nói lên điều gì cả — ngoại trừ biến đếm vòng lặp ngắn quen thuộc (i,j,k) hoặc các từ viết tắt phổ biến trong ngành (ctx= context,cfg= config). - Luôn ghi rõ đơn vị đo trong tên biến — đây là quy tắc cực kỳ quan trọng với embedded/real-time, vì nhầm đơn vị là một trong những lỗi gây hậu quả nghiêm trọng nhất trong lịch sử kỹ thuật (ví dụ nổi tiếng: tàu vũ trụ Mars Climate Orbiter của NASA bị mất do một đội dùng đơn vị Anh, đội kia dùng đơn vị mét mà không thống nhất).
- Tốt:
timeout_ms,delay_us,buffer_size_bytes,frequency_hz - Không tốt:
timeout,delay,size(không rõ đơn vị gì)
- Tốt:
- Biến chứa dữ liệu nhạy cảm nên có tên rõ ràng để dễ rà soát bảo mật (audit) và giúp công cụ tự động phát hiện, tránh vô tình log ra:
secret_key,auth_token,private_key
- Với hàm xử lý dữ liệu từ nguồn không tin cậy, có thể thêm hậu tố nhắc nhở người đọc code cẩn trọng, ví dụ đặt trong namespace
untrusted_input::hoặc hậu tố_from_network.
5. Checklist review code
In ra hoặc lưu lại, dùng để tự kiểm tra trước khi commit code hoặc khi review code người khác trong dự án embedded/viễn thông/an ninh mạng.
Cơ bản (mọi dự án)
- Mọi biến đều được khởi tạo trước khi dùng?
- Đã kiểm tra giá trị trả về của mọi hàm có thể thất bại (
malloc,fopen, hàm mạng…)? - Code biên dịch sạch với
-Wall -Wextra -Wpedantic -Werror? - Không còn con trỏ nào bị dangling (trỏ tới vùng nhớ đã giải phóng/đã hết scope)?
Chung (dự án C++ hiện đại)
- Có dùng RAII/smart pointer thay vì
new/deletethô không? - Tham số truyền vào hàm đã dùng
const&khi không cần sửa đổi chưa? - Class quản lý tài nguyên thô đã tuân thủ Rule of Five (hoặc Rule of Zero nếu không cần)?
- Tên biến/hàm có rõ ràng, nhất quán theo convention của dự án không?
Chuyên biệt (Embedded/RTOS/Viễn thông/An ninh mạng)
- Có cấp phát bộ nhớ động (
malloc/new) trong đường xử lý thời gian thực không? → phải loại bỏ. - Dữ liệu nhận từ mạng/giao thức đã được validate độ dài và kiểu trước khi xử lý chưa?
- Có dùng hàm string không an toàn (
strcpy,sprintf,gets…) ở đâu không? - So sánh dữ liệu bảo mật (MAC, token, hash) đã dùng constant-time comparison chưa?
- Secret/khóa/mật khẩu có bị log ra ở đâu không? Có được xóa an toàn khỏi bộ nhớ sau khi dùng xong không?
- ISR (interrupt handler) có gọi hàm blocking hoặc cấp phát bộ nhớ không?
- Biến chia sẻ giữa ISR và task chính đã khai báo
volatilevà đồng bộ đúng cách chưa? - Hàm parse dữ liệu từ bên ngoài đã được fuzz test chưa?
- Có ép kiểu con trỏ trực tiếp lên struct từ buffer nhận từ mạng không? (vi phạm alignment/strict aliasing)
- Tên biến thời gian/kích thước có ghi rõ đơn vị đo (ms/us/bytes/Hz) không?
Tài liệu này mang tính tổng hợp nguyên tắc kinh điển, nên được điều chỉnh theo coding style guide cụ thể của từng công ty/dự án (ví dụ: Google C++ Style Guide, MISRA C:2012, AUTOSAR C++14) khi áp dụng thực tế.