Bỏ qua điều hướng, tới nội dung chính
Học C
Bài 56.226 phút đọc

Quy ước viết mã

Sau bài này bạn sẽ làm được

  • Viết tệp .clang-format cho dự án
  • Đưa định dạng vào hệ thống dựng và vào tích hợp liên tục
  • Biết những quy ước nào thật sự ảnh hưởng tới lỗi
  • Định dạng lại một dự án cũ mà không làm hỏng lịch sử

Tranh luận về vị trí dấu ngoặc nhọn là cách tốn thời gian đội nhóm hiệu quả nhất từng được phát minh. Cách chữa không phải là thắng cuộc tranh luận, mà là giao việc đó cho một chương trình rồi không bao giờ nhắc lại.

#Thứ không nên tranh cãi

Quyết địnhCó ảnh hưởng tới lỗi khôngNên làm gì
Vị trí dấu ngoặc nhọnKhôngĐể công cụ quyết định
Bốn dấu cách hay tabKhông, nếu nhất quánĐể công cụ quyết định
Giới hạn độ dài dòngKhôngĐể công cụ quyết định
Khoảng trắng quanh toán tửKhôngĐể công cụ quyết định
Con trỏ dính tên hay dính kiểuKhôngĐể công cụ quyết định
Luôn dùng ngoặc nhọn cho ifCÓBắt buộc, xem mục cuối
Khai báo biến ở đâuCÓSát chỗ dùng
Một câu lệnh mỗi dòngCÓBắt buộc
Gán trong điều kiệnCÓCấm, hoặc bắt ngoặc kép

#clang-format

xau.c, trước khi định dạng
#include <stdio.h>
#include <stdlib.h>
struct Diem{int x;int y;};
typedef struct{char ten[32];int tuoi;double diem;}SinhVien;
int  tong( int *a,int n ){int s=0;for(int i=0;i<n;i++){s+=a[i];}return s;}
static void in( SinhVien *sv,int n )
{
for(int i=0;i<n;i++)
{
if(sv[i].diem>=8.0){printf("%s: gioi\n",sv[i].ten);}
else if(sv[i].diem>=6.5) printf("%s: kha\n",sv[i].ten);
    else {
        printf("%s: trung binh\n",sv[i].ten);
    }
}
}
int main(void){SinhVien sv[]={{"an",20,8.5},{"binh",21,7.0},{"chi",20,5.5}};
int a[]={1,2,3,4,5};
printf("%d\n",tong(a,5));in(sv,3);return 0;}
terminal
clang-format --version
clang-format version 15.0.0
# -i sua tai cho, khong co -i thi in ra man hinh
clang-format -i dep.c
dep.c, sau khi định dạng
#include <stdio.h>
#include <stdlib.h>
struct Diem {
    int x;
    int y;
};
typedef struct {
    char   ten[32];
    int    tuoi;
    double diem;
} SinhVien;
int tong(int *a, int n) {
    int s = 0;
    for (int i = 0; i < n; i++) {
        s += a[i];
    }
    return s;
}
static void in(SinhVien *sv, int n) {
    for (int i = 0; i < n; i++) {
        if (sv[i].diem >= 8.0) {
            printf("%s: gioi\n", sv[i].ten);
        } else if (sv[i].diem >= 6.5)
            printf("%s: kha\n", sv[i].ten);
        else {
            printf("%s: trung binh\n", sv[i].ten);
        }
    }
}
int main(void) {
    SinhVien sv[] = {{"an", 20, 8.5}, {"binh", 21, 7.0}, {"chi", 20, 5.5}};
    int      a[] = {1, 2, 3, 4, 5};
    printf("%d\n", tong(a, 5));
    in(sv, 3);
    return 0;
}

#Tệp cấu hình

.clang-format, đặt ở gốc dự án
---
Language:        Cpp                  # C dung chung bo may voi C++
BasedOnStyle:    LLVM                 # nen tang, roi ghi de ben duoi
IndentWidth:     4
ColumnLimit:     88
AlignConsecutiveDeclarations: true    # can le cot ten bien
AlignConsecutiveAssignments:  false
AllowShortFunctionsOnASingleLine: None
AllowShortIfStatementsOnASingleLine: Never
AllowShortLoopsOnASingleLine: false
BreakBeforeBraces: Attach             # ngoac mo cung dong
PointerAlignment: Right               # char *s, khong phai char* s
SpaceAfterCStyleCast: false
SortIncludes:    true
BasedOnStyleNguồn gốcĐặc điểm nổi bật
LLVMDự án LLVM2 dấu cách, 80 cột, ngoặc cùng dòng
GoogleHướng dẫn của Google2 dấu cách, 80 cột, gần LLVM
GNUDự án GNUNgoặc xuống dòng và thụt vào, ít người thích
MozillaFirefox2 dấu cách, ngoặc hàm xuống dòng
WebKitSafari4 dấu cách, ngoặc hàm xuống dòng
MicrosoftVisual Studio4 dấu cách, ngoặc luôn xuống dòng

#EditorConfig

.editorconfig, đặt ở gốc dự án
root = true

[*]
charset = utf-8
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
indent_style = space
indent_size = 4

[*.{c,h}]
indent_size = 4
max_line_length = 88

[Makefile]
indent_style = tab          # Make BAT BUOC dung tab

[*.{yml,yaml,json}]
indent_size = 2

[*.md]
trim_trailing_whitespace = false   # hai dau cach cuoi dong = xuong dong

#Đưa vào quy trình

Makefile
SRC := $(shell find src include -name '*.c' -o -name '*.h')

.PHONY: dinh-dang kiem-dinh-dang

dinh-dang:
	clang-format -i $(SRC)

kiem-dinh-dang:
	clang-format --dry-run -Werror $(SRC)

kiem-tra: kiem-dinh-dang canh-bao kiem-thu
.github/workflows/kiem-tra.yml
name: kiem tra
on: [push, pull_request]

jobs:
  dinh-dang:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: cai clang-format
        run: sudo apt-get update && sudo apt-get install -y clang-format
      - name: kiem tra dinh dang
        run: clang-format --dry-run -Werror $(git ls-files '*.c' '*.h')

  dung-va-kiem-thu:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: make kiem-tra

#Quy ước thật sự gây lỗi

Tự làm thử

  1. Viết một tệp .clang-format cho dự án của bạn, bắt đầu từ --dump-config của LLVM.
  2. Chạy clang-format --dry-run -Werror trên dự án của bạn và đếm số tệp không đạt.
  3. Thêm mục tiêu dinh-dang và kiem-dinh-dang vào Makefile.
  4. Định dạng lại toàn bộ dự án trong một commit riêng và thiết lập .git-blame-ignore-revs.
  5. Viết .editorconfig và .gitattributes cho dự án.
  6. Tái hiện lỗi goto fail bằng một hàm nhỏ và xác nhận -Wmisleading-indentation bắt được nó.

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

  • Định dạng là chuyện thẩm mỹ; giao cho clang-format và không tranh cãi nữa.
  • clang-format không bao giờ đổi ý nghĩa mã, nên định dạng lại là thao tác an toàn; nó cũng không tự thêm ngoặc nhọn.
  • --dry-run -Werror là chế độ kiểm tra dùng cho CI.
  • Định dạng lại dự án cũ trong một commit riêng, kèm .git-blame-ignore-revs.
  • Bốn quy ước có ảnh hưởng thật: luôn dùng ngoặc nhọn, khai báo sát chỗ dùng, một lệnh mỗi dòng, và không gán ẩn trong điều kiện.