How we evolved our code: Notion's ratcheting system using custom ESLint rules
Bài tóm tắt Notion 2025 3. Quy mô lớn Độ khó: Dễ
Notion · 2025 · 3. Quy mô lớn · Độ khó: Dễ — bài gốc
Bối cảnh
- Codebase lớn dần thì pattern cũ tích lại. Cách thường làm là vài người chạy một đợt migrate lớn trên cả codebase, dễ gây lỗi và lấy người khỏi việc sản phẩm. Notion muốn đi tới trạng thái lý tưởng (ví dụ "không còn React class component") bằng cách siết dần, không quay lui.
Thay đổi chính
- Bốn phần của hệ "ratchet": lint rule (tự viết và có sẵn); baseline lưu số lỗi đang có theo từng file và từng rule, coi là tạm chấp nhận nhưng cấm thêm; tự thực thi: sửa bớt lỗi thì pre-commit tự hạ số cho phép, thêm lỗi mới thì CI chặn merge; theo dõi: số liệu đẩy sang Datadog và một database Notion.
- Chỉ có error, không có warning: theo Notion, warning bị coi là gợi ý và bị lờ đi, chồng thành nhiễu. Mọi rule báo error; lỗi nằm trong hạn mức của baseline mới bị hạ thành warning, nên mọi thứ developer thấy đều cần xử lý.
- Ratchet file đổi từ JSON sang TSV: JSON hay xung đột merge (khoảng trắng, dấu phẩy) khi nhiều người cùng sửa lỗi lint. TSV mỗi dòng là một cặp file + rule + số lỗi cho phép, nên sửa song song chạm các dòng khác nhau.
- Thực thi bằng custom processor của ESLint: processor hạ error thành warning khi tổng số trong file ≤ số cho phép. Vì dùng tính năng gốc của ESLint, nó chạy y hệt trong IDE, dòng lệnh và CI, không thêm công cụ.
- Codemod dần dần: viết rule phát hiện pattern xấu; lỗi đơn giản có autofixer, chạy trên toàn codebase ngay khi bật rule; lỗi phức tạp để lại kèm thông báo hướng dẫn, được sửa dần trong lúc phát triển thường ngày bởi người hiểu đoạn code đó. Theo Notion, codemod kiểu JSCodeShift buộc xử lý ca khó ngay và không chống được tái phạm nếu không viết thêm rule.
- Ma sát vừa đủ: tăng số lỗi trong ratchet file phải có người duyệt; đổi tên/di chuyển file thì phải sửa đường dẫn trong ratchet file, họ chấp nhận chi phí này. Hệ đã dùng cho class → function component, bỏ
any/astrong TypeScript, bỏ hàng chục file/module cũ, thêm nhãn accessibility. Mã nguồn mở thànheslint-seatbelt.
Vì sao
- Migration code theo kiểu "baseline + cấm thêm + tự hạ" chia công việc cho cả team theo nhịp làm việc bình thường, trong khi CI bảo đảm không quay lui (Quản lý fleet và migrate code hàng loạt, CI và build ở quy mô lớn).
Bài học cho SWE
- Cổng CI chặn cái mới tốt hơn yêu cầu dọn cái cũ ngay; hạn mức chỉ được đi xuống.
- Định dạng file trạng thái dùng chung nên theo dòng để tránh xung đột merge.
- Đặt cơ chế vào công cụ đã có (processor của ESLint) để nó chạy giống nhau ở IDE, CLI và CI.
Liên quan
- Allegro 2024 · Spotify 2023 · Slack 2023 (mặc định mới + danh sách ngoại lệ co dần)
- Quản lý fleet và migrate code hàng loạt · CI và build ở quy mô lớn
#Ratcheting#ESLint#Custom processor#Baseline#TSV ratchet file#Codemod#Autofixer#Pre-commit#CI gate#eslint-seatbelt
Được nhắc tới trong
Công ty (1)