Difftastic: Diff Hiểu Cú Pháp Code
Difftastic thay thế kiểu diff đỏ-xanh theo từng dòng trong terminal bằng kết quả bám theo cây cú pháp thật, nên một hàm bị format lại chỉ hiện ra như một thay đổi nhỏ thay vì cả mảng đỏ xanh. Nó đáng để đưa vào quy trình review ngay lần đầu một lượt rustfmt hay gofmt không còn trông như viết lại toàn bộ file. Điểm trừ: không xuất ra patch. Bạn vẫn cần `diff` thường cho việc gì cần áp bằng `patch`.
Vì sao diff theo từng dòng bỏ lỡ thay đổi thực sự trong code
`diff` thông thường so sánh file theo từng dòng, nên chỉ cần dời một dấu `}` xuống dòng khác, thụt lề lại một khối lệnh, hay bọc một lời gọi hàm dài thành nhiều dòng là đã hiện ra như xoá và thêm dòng, dù ý nghĩa code chẳng đổi gì. Người review phải dò cả mảng đỏ xanh để tìm đúng dòng thực sự quan trọng. Commit chỉ để format lại gần như không thể review theo cách này. Difftastic ra đời vì diff theo dòng không có khái niệm cú pháp — nó coi một dấu `{` lạc chỗ và một hàm bị đổi tên là như nhau, chỉ là ký tự đã đổi.
Difftastic là gì
Difftastic là công cụ structural diff viết bằng Rust, so sánh file dựa trên cú pháp thay vì văn bản thuần, dùng parser tree-sitter để hiểu đúng ngữ pháp của từng ngôn ngữ. Nó hỗ trợ hơn 30 ngôn ngữ lập trình, và với phần mở rộng file nào nó không nhận ra thì rơi về diff theo dòng kèm highlight từng từ. Bên dưới, nó coi việc diff như một bài toán đồ thị và tìm đường đi bằng thuật toán Dijkstra.
Diff hiểu cú pháp nhờ tree-sitter
- ✓Chỉ highlight đúng phần cú pháp thực sự thay đổi, không phải mọi dòng bị đụng vào vì format lại
- ✓Phân biệt được whitespace nào làm đổi ý nghĩa với whitespace chỉ là thụt lề
- ✓Hiểu trực tiếp các marker merge conflict của git (`<<<<<<<`, `=======`, `>>>>>>>`)
- ✓`--check-only --exit-code` báo hai file có giống nhau về cú pháp hay không mà không cần in ra diff
- ✓Rơi về diff theo dòng kèm highlight từng từ khi không parse được cú pháp của file
- ✓Tìm diff bằng cách coi các thay đổi là một bài toán đồ thị rồi chạy thuật toán Dijkstra trên đó, thay vì heuristic khớp dòng đơn giản
Cài đặt Difftastic
README không liệt kê trực tiếp lệnh cài đặt — nó trỏ đến trang Installation riêng trong manual chính thức. Đây là lựa chọn có chủ đích: việc gói nào có sẵn qua package manager hay bản phân phối Linux nào thay đổi nhanh hơn tốc độ một README có thể theo kịp, nên project giữ danh sách chính thức bên ngoài repo. Nếu bạn đang cân nhắc dùng Difftastic, hãy chuẩn bị rời GitHub và mở manual trước khi có được binary `difft` chạy được. Các bước cài đặt cụ thể không được ghi rõ trong dữ liệu repo hiện có.
Dùng Difftastic với git và trên dòng lệnh
Sau khi cài xong, binary dòng lệnh là `difft`, và README ghi rằng nó tương thích với git và Mercurial, với hướng dẫn cấu hình nằm trong manual chứ không phải trong repo. Một số hành vi được điều khiển qua biến môi trường và flag dùng trực tiếp. Gặp lỗi parse trên một file? Nâng ngưỡng chấp nhận bằng `export DFT_PARSE_ERROR_LIMIT=20` trước khi chạy `difft foo1.c foo2.c`. Đưa cho nó một file còn marker conflict chưa giải quyết, nó sẽ dựng lại cả hai phía: `difft file_with_conflicts.js`. Với CI, bỏ qua phần in diff và chỉ cần tín hiệu pass/fail bằng `difft --check-only --exit-code before.js after.js`, chỉ trả exit code 1 khi AST thực sự đổi. Terminal hiện màu bright thành xám (lỗi quen thuộc với theme Solarized)? Đặt `export DFT_BACKGROUND=light`. Khi diff dữ liệu không có thứ tự cố định như JSON, hãy sort key trước, vì Difftastic coi việc đổi thứ tự là một thay đổi thật: `difft <(jq --sort-keys < file_1.json) <(jq --sort-keys < file_2.json)`.
Điểm Difftastic làm tốt
- ✓Format lại một hàm không còn chôn thay đổi thật giữa một đống đỏ xanh
- ✓Parse trực tiếp marker merge conflict của git, nên bạn xem được cả hai phía mà không cần bước diff ba chiều riêng
- ✓`--check-only --exit-code` là cách nhanh, viết script được, để hỏi "AST có đổi không" trong CI
- ✓Giấy phép MIT, nên đưa thẳng vào toolchain thương mại mà không cần bàn chuyện license
Giới hạn và các trường hợp biên đã biết
- △Hiệu năng giảm rõ trên file có nhiều thay đổi và có thể tốn nhiều bộ nhớ, theo đúng mục Known Issues của project
- △Giao diện side-by-side thường ổn nhưng chính README cũng thừa nhận đôi lúc gây rối mắt
- △Không xuất patch — công cụ này dựng cho việc đọc, không phải để tạo file áp bằng `patch`
- △Không làm AST merging; README trỏ sang công cụ mergiraf riêng cho việc đó
- △README tự mô tả project còn non trẻ, và nói khả năng cao chưa tích hợp sẵn với editor hay công cụ bạn hay dùng
Lựa chọn thay thế cho Difftastic
Câu hỏi thường gặp
Difftastic hỗ trợ sẵn git và Mercurial, nhưng lệnh cấu hình cụ thể nằm trong manual của project chứ không phải trong README. Sau khi cấu hình xong ở đó, chạy `git diff` sẽ tự gọi binary `difft` thay vì diff theo dòng mặc định, còn lại quy trình git bạn dùng hằng ngày không đổi gì thêm.
Difftastic hỗ trợ hơn 30 ngôn ngữ lập trình qua các parser tree-sitter, danh sách đầy đủ được giữ trong manual chính thức chứ không phải README. Một file có phần mở rộng Difftastic không nhận ra vẫn được diff — chỉ là nó rơi về diff theo dòng kèm highlight từng từ thay vì bản diff hiểu cú pháp.
Difftastic rơi về diff theo dòng bất cứ khi nào gặp lỗi parse, một lựa chọn thận trọng có chủ đích để không bao giờ báo sai rằng hai file khác nhau về cú pháp là giống nhau. Lỗi parse xảy ra khi ngôn ngữ dùng tính năng chưa được hỗ trợ, ngôn ngữ cần tiền xử lý như C++, hoặc file có lỗi cú pháp thật. Nâng ngưỡng chấp nhận lỗi nhỏ bằng `export DFT_PARSE_ERROR_LIMIT=20`.
Difftastic có thể parse trực tiếp marker merge conflict của git (`<<<<<<<`, `=======`, `>>>>>>>`) kể từ bản 0.50, phát hành ngày 2023-08-16. Truyền file đang conflict như một đối số duy nhất, ví dụ `difft file_with_conflicts.js`, Difftastic sẽ dựng lại cả hai phía rồi diff chúng, dù nó vẫn không tự thực hiện việc merge.
Difftastic phát hành theo giấy phép MIT và mã nguồn công khai trên GitHub, nên miễn phí để dùng, sửa đổi, và đưa vào công cụ thương mại. Các parser tree-sitter đi kèm trong thư mục vendored_parsers mang giấy phép pha trộn giữa MIT và Apache từ tác giả gốc, đáng kiểm tra lại nếu bạn phân phối lại binary.
Difftastic tự động rơi về diff theo dòng kèm highlight từng từ cho bất kỳ file nào nó không parse được, dù là do phần mở rộng lạ hay file có quá nhiều lỗi cú pháp. Nhờ vậy Difftastic vẫn dùng được ngoài phạm vi ngôn ngữ hỗ trợ, chỉ là không còn phần so sánh hiểu cú pháp vốn là điểm mạnh chính của nó.
Trường hợp dùng tốt nhất
- •Review pull request khi một formatter như rustfmt hay gofmt vừa format lại file, và bạn cần thấy đúng thay đổi logic chứ không phải phần reflow
- •Diff các file config hay source đã bị format lại, vì Difftastic bỏ qua whitespace không có ý nghĩa cú pháp
- •Kiểm tra CI chỉ cần biết AST của file sinh ra có đổi hay không, dùng `--check-only --exit-code` thay vì parse text của diff
- •Tự xử lý merge conflict bằng tay, vì Difftastic parse được cả hai phía của marker conflict theo cấu trúc thay vì văn bản thô
Nên dùng — và nên bỏ qua
Nên thử Difftastic nếu bạn review nhiều pull request ở các ngôn ngữ parse tĩnh được và liên tục gặp diff bị ngợp bởi nhiễu do format lại. Nên bỏ qua nếu bạn cần file patch để áp ở nơi khác, hoặc chủ yếu làm việc với định dạng file ngoài hơn 30 ngôn ngữ nó hỗ trợ, vì khi đó bạn chỉ nhận được fallback diff theo dòng mà thôi.
Repo liên quan
difftastic có đáng để bạn bỏ thời gian?
ChatGPT, Claude và Perplexity đều đọc được trang này. Hỏi thử xem họ nghĩ gì về difftastic.
