Bài 5 · Cơ bản · 16 phút
Định dạng & cấu trúc
Biên soạn bởi Nguyễn Anh Tuấn
Định dạng dọc (vertical): khái niệm liên quan đứng gần, hàm gọi gần nhau; mật độ & khoảng trống; độ dài dòng; để máy lo bằng Prettier + ESLint + EditorConfig.
Khi lần đầu mở một file lạ, não bạn không đọc từng dòng - nó quét toàn bộ cấu trúc trước: file này dài bao nhiêu, được chia thành những khối nào, khối nào liên quan nhau. Định dạng nhất quán là tín hiệu giúp não làm việc đó nhanh và chính xác.
Điều quan trọng nhất không phải là bạn chọn 2 space hay 4 space - mà là cả đội nhất quán. Code không nhất quán bắt người đọc xử lý thêm một lớp "tiếng ồn" mỗi lần mắt lướt qua.
- ▸Định dạng là tín hiệu cấu trúc - giúp não quét nhanh trước khi đọc chi tiết.
- ▸Nhất quán quan trọng hơn đúng: quy ước sai mà nhất quán còn tốt hơn quy ước đúng mà lộn xộn.
- ▸Đừng tranh cãi về format trong review - để máy quyết định, người review tập trung vào logic.
Báo giấy và bài blog đều có cùng cấu trúc: tổng quát ở trên, chi tiết ở dưới. Đọc tiêu đề và đoạn mở đầu là nắm được 80% - muốn biết sâu hơn thì đọc tiếp. File code tốt cũng vậy: import và khai báo kiểu ở trên cùng, hàm điều phối (orchestrator) kế tiếp, hàm chi tiết ở dưới.
Định dạng dọc: trước và sau
// TRUOC: lon xon - ham chi tiet tren, ham goi no o duoi
function formatName(first: string, last: string): string {
return first.trim() + ' ' + last.trim();
}
const MAX_RETRIES = 3;
function processUser(user: User): string {
// ... goi formatName, validateEmail, buildGreeting
return buildGreeting(validateEmail(user.email), formatName(user.first, user.last));
}
function validateEmail(email: string): boolean {
return email.includes('@');
}
function buildGreeting(valid: boolean, name: string): string {
return valid ? 'Chao ' + name : 'Email khong hop le';
}
// ---
// SAU: tong quat tren, chi tiet duoi
// Nguoi doc thay processUser truoc (buc tranh lon), roi di xuong neu can
const MAX_RETRIES = 3;
function processUser(user: User): string {
return buildGreeting(validateEmail(user.email), formatName(user.first, user.last));
}
function validateEmail(email: string): boolean {
return email.includes('@');
}
function buildGreeting(valid: boolean, name: string): string {
return valid ? 'Chao ' + name : 'Email khong hop le';
}
function formatName(first: string, last: string): string {
return first.trim() + ' ' + last.trim();
} - ▸File đọc như bài báo: tổng quát (export/orchestrator) ở trên, chi tiết ở dưới.
- ▸Hàm gọi đứng GẦN hàm bị gọi - không để người đọc nhảy khắp file để theo luồng.
- ▸Khai báo biến gần chỗ dùng - không khai báo hàng loạt ở đầu hàm rồi dùng 50 dòng sau.
- ▸Dòng trống ngăn cách các "ý" - như đoạn văn; quá nhiều hoặc quá ít đều gây khó đọc.
Định dạng ngang nói về những gì xảy ra trong một dòng: khoảng trắng quanh toán tử, độ dài dòng, và thụt lề thể hiện cấu trúc lồng nhau.
Khoảng trắng ngang - dày đặc vs. có hơi thở
// DAY DAC - kho doc
const result=a+b*c;
if(x>0&&y<10){doSomething(x,y,z);}
const obj={name:'meo',age:2,active:true};
// CO HOI THO - mat di theo cau truc
const result = a + b * c;
if (x > 0 && y < 10) {
doSomething(x, y, z);
}
const obj = { name: 'meo', age: 2, active: true }; Thụt lề thể hiện cấu trúc - không dùng thụt lề để 'căn chỉnh đẹp'
// TRANH: can chinh theo chieu ngang (fragile - doi ten bien la vo)
const firstName = user.firstName;
const lastName = user.lastName;
const emailAddr = user.email;
// NEN: thut deu, de doc, Prettier tu lo
const firstName = user.firstName;
const lastName = user.lastName;
const emailAddr = user.email; - ▸Khoảng trắng quanh toán tử và sau dấu phẩy giúp mắt tách được các phần.
- ▸Thụt lề thể hiện cấu trúc lồng nhau - không dùng để "căn chỉnh đẹp" theo chiều ngang.
- ▸Giới hạn độ dài dòng (80-120 ký tự) tránh scroll ngang, dễ diff hơn khi review.
Tranh cãi về format trong code review là lãng phí. Mỗi lập trình viên có sở thích cá nhân và không ai sai hoàn toàn - nhưng khi làm việc nhóm, quy ước đội thắng sở thích cá nhân. Giải pháp: để máy quyết định và thực thi, người review tập trung vào logic và thiết kế.
.prettierrc - cấu hình đơn giản cho dự án TypeScript
{
"semi": true,
"singleQuote": true,
"tabWidth": 2,
"printWidth": 100,
"trailingComma": "es5",
"arrowParens": "always"
} .editorconfig - áp dụng cho mọi ngôn ngữ trong repo
root = true
[*]
charset = utf-8
end_of_line = lf
indent_style = space
indent_size = 2
trim_trailing_whitespace = true
insert_final_newline = true
[*.md]
trim_trailing_whitespace = false Bộ ba công cụ - phân công rõ ràng
Tích hợp vào package.json - format-on-save và pre-commit
// package.json (phan scripts va lint-staged)
{
"scripts": {
"format": "prettier --write .",
"lint": "eslint . --ext .ts,.tsx",
"check": "tsc --noEmit"
},
"lint-staged": {
"*.{ts,tsx,js}": ["prettier --write", "eslint --fix"],
"*.{json,md,yml}": ["prettier --write"]
}
} - ▸Prettier: format tự động, không tranh cãi về dấu phẩy hay ngoặc nữa.
- ▸ESLint: phát hiện vấn đề chất lượng - để Prettier lo format, ESLint lo logic.
- ▸EditorConfig: cùng charset/line-ending cho mọi người dù dùng editor nào.
- ▸Pre-commit hook (lint-staged): format và lint tự động trước mỗi commit - không ai quên.
Khi format được tự động hoá, code review không còn "comment về dấu phẩy". Thay vào đó, mọi người tập trung vào: thiết kế có đúng không, logic có lỗi không, tên có rõ không. Đó là loại review có giá trị.
Codebase với format nhất quán cũng dễ dùng git hơn: diff chỉ hiện thay đổi logic thật, không bị ô nhiễm bởi thay đổi khoảng trắng của người này sang người khác.
Bài tiếp theo: Object vs Data
Câu hỏi thường gặp
Câu trả lời đúng: quy ước đội quyết định, Prettier/EditorConfig thực thi, và không ai tranh cãi nữa. Trong hầu hết dự án TypeScript/JavaScript hiện đại: 2 space là phổ biến nhất (Prettier mặc định). Tab có lợi cho khả năng tiếp cận (accessibility) - người dùng có thể tuỳ chỉnh độ rộng. Nhưng chọn một rồi dùng nhất quán quan trọng hơn là chọn đúng.
Dùng eslint-config-prettier để tắt các rule ESLint liên quan đến format (chúng xung đột với Prettier). ESLint lo về chất lượng code (biến chưa dùng, lỗi logic); Prettier lo về format. Phân công rõ ràng, không dẫm chân nhau. Thứ tự: ESLint sửa vấn đề chất lượng, Prettier format sau cùng.
80 ký tự là tiêu chuẩn lịch sử (từ màn hình terminal); 100-120 phổ biến hơn với màn hình hiện đại. Prettier mặc định 80. Quan trọng hơn con số cụ thể là nhất quán: nếu đội chọn 100, thì tất cả đều dùng 100. Dòng quá dài thường là dấu hiệu cần tách hàm hoặc biến trung gian.
Tick những điều em tự tin làm được. Càng lên cao, em càng hiểu sâu.
Trả lời vài câu để chắc rằng em đã nắm bài.
Nguyên tắc "file đọc như bài báo" trong định dạng dọc nghĩa là gì?
- 1
Audit file dài nhất
Tìm file TypeScript/JavaScript dài nhất trong dự án của mèo con. Đọc từ trên xuống: có thể đọc như "bài báo" (tổng quát trên, chi tiết dưới) không? Liệt kê 2-3 chỗ vi phạm nguyên tắc định dạng dọc.
Hoàn thành khi: Chỉ ra cụ thể: chỗ nào khai báo biến xa chỗ dùng, chỗ nào hàm gọi nhau đứng cách xa, chỗ nào thiếu/thừa dòng trống.
- 2
Cài đặt Prettier
Thêm Prettier vào một dự án TypeScript (mới hoặc có sẵn): cài package, tạo
.prettierrc, bật format-on-save trong VS Code (hoặc editor mèo con dùng).Hoàn thành khi: Lưu một file TypeScript và nó tự format;
npx prettier --check .báo "All matched files use Prettier code style!" - 3
Tạo .editorconfig
Tạo file
.editorconfigcho dự án của mèo con với ít nhất:root=true, charset UTF-8, indent style/size khớp với Prettier,end_of_line,trim_trailing_whitespace,insert_final_newline.Hoàn thành khi: File
.editorconfigcommit được, editor áp dụng đúng (kiểm bằng cách mở file mới và xem tab/space mặc định). - 4
Sắp xếp lại một file lộn xộn
Lấy một file TypeScript có hàm/khai báo sắp xếp lung tung (hoặc tự tạo một file như vậy). Sắp xếp lại theo nguyên tắc: import → types/interfaces → constants → hàm cấp cao (gọi người khác) → hàm chi tiết (bị gọi). Không đổi logic.
Hoàn thành khi: Diff chỉ là di chuyển, không thay đổi hành vi; file đọc theo thứ tự tự nhiên từ trên xuống.
- 5
Thêm ESLint + phân công rõ
Cài ESLint vào dự án TypeScript có Prettier. Thêm
eslint-config-prettierđể tránh xung đột. Chạyeslint .và sửa ít nhất 1 warning/error thật (không phải chỉ format).Hoàn thành khi:
npx eslint .chạy sạch (0 error); Prettier và ESLint không báo xung đột nhau; phân công rõ: ESLint = chất lượng, Prettier = format. - 6
Thêm pre-commit hook
Dùng
lint-staged+husky(hoặcsimple-git-hooks) để tự động chạy Prettier và ESLint trước mỗi commit. Commit một file chưa format và xem hook tự sửa hoặc chặn.Hoàn thành khi: Thử commit một file có lỗi lint - hook chặn commit và báo lỗi; commit file sạch - hook cho qua.