1 điểm bởi GN⁺ 5 giờ trước | 1 bình luận | Chia sẻ qua WhatsApp
  • Conventional Commits cố gắng gán ý nghĩa cho thông điệp commit theo dạng <type>[optional scope]: <description>, nhưng lại đặt loại thay đổi lên trước và coi phạm vi là tùy chọn, khiến thông tin cần thiết cho việc tra cứu thực tế bị đẩy xuống sau
  • Người đóng góp, người debug và người ứng phó sự cố đều tìm trong log commit khu vực mã mà thay đổi đã chạm tới; bug có thể phát sinh từ bất kỳ loại thay đổi nào, nên scope quan trọng hơn type
  • Với fix(compiler): prevent namespaced SVG <style> elements from being stripped, chỉ riêng phần mô tả cũng đã cho thấy đây là sửa lỗi; còn refactor(core): Update webmcp support to use document.modelContext cho thấy một commit có thể đồng thời là sửa đổi, refactor và thêm tính năng, nên type vừa trùng lặp vừa hạn chế
  • Việc tự động tạo CHANGELOG và quyết định tăng semantic version gặp vấn đề vì độc giả của log commit và changelog là khác nhau; thêm vào đó, revert, việc vô tình phá vỡ tương thích ngược và việc xử lý lỗi phá vỡ về sau có thể khiến kết quả lệch đi
  • Thông điệp commit theo kiểu tiền tố scope cho thấy chủ thể thay đổi trước tiên, và điều kiện build hoặc deploy cũng nên dựa trên các tệp thay đổi trong git diff thay vì loại ở tiêu đề

Ưu tiên sai thứ tự

  • Conventional Commits đặt mục tiêu gán ý nghĩa cho thông điệp commit để giúp lập trình viên và người dùng cuối hiểu được thay đổi
<type>[optional scope]: <description>

[optional body]

[optional footer(s)]
  • Dòng tiêu đề gồm <type> như fix, feat, chore, docs, refactor, một scope tùy chọn và phần mô tả
  • Khuyết điểm cốt lõi là cấu trúc này ưu tiên loại thay đổi là type hơn scope, tức chủ thể thực sự của thay đổi
  • Việc scope là tùy chọn khiến thông tin quan trọng nhất của commit có thể bị thiếu, còn việc đặt type ở đầu tiêu đề làm đảo ngược thứ tự ưu tiên

Vì sao scope quan trọng hơn type

  • Người đóng góp đọc log commit để tìm các thay đổi kể từ lần đóng góp gần nhất, nắm dòng chảy tổng thể của dự án, hoặc tìm những commit có thể xung đột với phần việc đang làm khi pull hay rebase
  • Người debug tìm những thay đổi đã chạm tới khu vực liên quan đến thành phần nơi bug xuất hiện; vì bug có thể phát sinh từ bất kỳ type thay đổi nào, nên thông tin type không giúp ích nhiều
  • Người ứng phó sự cố sẽ rà log commit quanh thời điểm xảy ra sự cố để tìm khu vực gây ra vấn đề; nếu tại điểm số lỗi API inbound tăng vọt có một commit thuộc scope auth, đó sẽ là một ứng viên nguyên nhân rất đáng nghi
  • Với người đọc log commit, thông tin quan trọng không phải thay đổi thuộc loại gì mà là nó đã chạm tới khu vực nào

Tính trùng lặp và giới hạn của type

  • fix(compiler): prevent namespaced SVG <style> elements from being stripped cho thấy chỉ cần phần mô tả cũng đủ biết đây là sửa lỗi, nên type fix trở nên trùng lặp
  • Không gian trên dòng tiêu đề commit là hữu hạn, nên việc dùng ký tự cho type vốn đã có thể suy ra từ description là không hiệu quả
  • refactor(core): Update webmcp support to use document.modelContext cập nhật tính năng webmcp của thành phần core để hỗ trợ cả document.modelContext lẫn navigator.modelContext
  • Thay đổi này có thể đồng thời được xem là sửa lỗi, refactor và tính năng mới, nhưng thông tin thực sự quan trọng là đây là thay đổi đối với thành phần core/webmcp

Giới hạn của lời hứa tự động hóa

  • Ý tưởng tự động sinh CHANGELOG từ commit bằng các công cụ như git-cliff hay conventional-changelog có vấn đề ở chỗ độc giả của log commit và changelog là hai nhóm khác nhau
  • CHANGELOG hướng tới người dùng, tập trung vào việc hiểu khác biệt về mặt chức năng và kinh doanh giữa các phiên bản
  • Log commit hướng tới lập trình viên, tập trung vào việc đọc hiểu codebase đã thay đổi theo thời gian ra sao và dòng chảy thay đổi theo góc nhìn scope
  • Trong các dự án có độ phức tạp từ trung bình trở lên, một tính năng có ý nghĩa thường được đưa vào qua nhiều commit; với lập trình viên, quá trình triển khai là hữu ích, nhưng với người dùng cuối thì chỉ tính năng hoàn chỉnh mới quan trọng
  • Commit revert quan trọng với lập trình viên vì nó là một phần của dòng chảy trong log commit, nhưng với người dùng cuối thì một thay đổi đã bị hoàn tác cũng giống như chưa từng được tạo ra
  • Việc tăng semantic version dựa trên type của commit có thể dẫn tới các vấn đề như tăng major dù thay đổi phá vỡ tương thích ngược đã bị revert, tăng minor hoặc patch sai vì chỉ về sau mới phát hiện ra sự phá vỡ, hoặc đánh dấu là breaking change dù nó đã được trung hòa khi gộp với các commit tiếp theo
  • Trong những tình huống đó có thể sửa lịch sử bằng rebase, nhưng workflow có thể ngăn cản hoặc làm hỏng việc này, đồng thời làm giảm độ tin cậy của dòng chảy mà log commit truyền tải
  • Nếu dùng type trong tiêu đề commit để kích hoạt quy trình build hoặc deploy, thì một commit mang tiêu đề docs: fix typos vẫn có thể lén đưa lỗ hổng vào hệ thống xác thực và qua mặt công cụ tự động
  • Điều kiện build và deploy nên được quyết định bằng cách xác định các tệp đã thay đổi qua git diff hơn là dựa vào tiêu đề commit

Vấn đề áp dụng và phương án thay thế

  • Conventional Commits cho phép dự án tự định nghĩa tập type riêng, nhưng nhiều dự án chỉ lấy nguyên bộ type mặc định của commitlint, điều này có thể không phù hợp với đặc thù từng dự án
  • Đặc tả Conventional Commits về mặt kỹ thuật chỉ định nghĩa fixfeat, còn các type bổ sung được giao cho từng dự án tự quyết
  • Trong môi trường doanh nghiệp, yêu cầu quản lý thay đổi và kiểm toán đôi khi buộc mọi thông điệp commit phải có mã ticket; nếu <scope> bị dùng làm chỗ cho mã ticket thì metadata hữu ích sẽ biến mất
  • Linux, FreeBSD, Git, Go, NixOS và Node.js đều dùng thông điệp commit với tiền tố scope phù hợp với dự án
  • Trong Linux kernel, scope tự nhiên là subsystem; trong dự án Go là package path; còn trong kiến trúc microservice thì đó là tên microservice
  • scopedcommits.com cổ vũ việc quay lại định dạng thông điệp commit lấy scope làm trung tâm và tách riêng việc tạo CHANGELOG khỏi quản lý log commit
  • Những lợi thế được cho là của Conventional Commits đã không chuyển hóa thành lợi ích thực tế, còn sự phổ biến trong các dự án mã nguồn mở và xu hướng AI chọn mặc định kiểu này lại khiến thông điệp commit pha trộn anti-pattern lan rộng

1 bình luận

 
Ý kiến trên Lobste.rs
  • Thật mừng vì đây là một bài viết sắp xếp các lập luận phản bác conventional commits bằng lý lẽ thay vì chỉ là cảm giác bài xích theo bản năng
    Tôi chưa từng nghĩ sâu về việc vì sao mình ghét nó, và cũng tự hỏi có phải vì tôi đã bắt đầu liên hệ nó với mã do LLM tạo ra hay không. Đặc biệt tôi ghét nhất chore:; mong là chúng ta đừng tái phát minh ký pháp Hungarian. Ngay từ đầu nó đã không nên tồn tại

    • Đặc biệt, chore: thậm chí không còn trong Angular commit style guide nữa, có lẽ vì họ nhận ra nó quá mơ hồ nên đã gộp vào build:
      Ngay cả khi còn trong style của Angular, mô tả cho chore: cũng nêu ra các mục đích sử dụng khá cụ thể, nhưng ở một số dự án mã nguồn mở thì có vẻ người ta gắn nó theo cảm tính cho những việc theo đúng nghĩa là thấy ngại làm
  • Tôi không thích conventional commits, nhưng phương án thay thế được đề xuất có vẻ bỏ qua lý do scope là tùy chọn
    Trong các dự án nhỏ không có nhiều module tách bạch, khái niệm “scope” không hữu ích lắm. Một thực hành hữu ích mà cả hai bên đều bỏ sót là thêm số issue hoặc số ticket vào tiêu đề commit, vì như vậy sẽ dễ nắm được ngữ cảnh bổ sung của thay đổi và đặc biệt hữu ích khi review code. Tuy vậy, tôi không thích việc biến số ticket thành bắt buộc, vì như thế sẽ sinh ra hàng loạt ticket vô dụng cho các thay đổi nhỏ nhặt; nhưng nếu một thay đổi xử lý một bug hay một task cụ thể thì nó nên được liên kết với bug hoặc task đó

    • Nếu không cần scope thì cứ bỏ qua thôi
      Dù sao nó vẫn tốt hơn “type” commit bị lặp ý, thứ mà lẽ ra chỉ cần nhìn dòng tiêu đề là đã thấy rồi
    • Lý tưởng nhất là không có kiểu commit bị quy định sẵn nào cả, và cứ dùng cách diễn đạt phù hợp với từng commit cụ thể
      Nếu thay đổi khớp rõ ràng với một ticket thì dùng commit kiểu “số ticket”, còn không thì dùng cách khác. Có thay đổi hợp với type hơn nhưng ít hợp với scope hơn, và ngược lại, nên cũng có thể trộn scoped commits với conventional commits
  • Tôi chỉ muốn nói là “đừng dùng phông chữ đơn cách cho văn bản dạng đoạn”
    Dù vậy, nhìn chung tôi vẫn đồng ý với tiền đề của bài viết

  • Ngay cả khi commit message không hay, nếu muốn hình dung phạm vi thay đổi thì tôi khuyên nên dùng git log --name-only hoặc git log --stat thường xuyên
    Nhìn tên file cũng giúp khá nhiều để biết mỗi commit đã thay đổi gì mà không cần mở từng commit ra xem hết

  • Cách tôi thực sự thích là bắt buộc dùng kiểu conventional commit cho tiêu đề PR
    Tiêu đề PR vẫn có thể được maintainer chỉnh sửa sau khi merge, không cần viết lại lịch sử commit, và nếu dùng cùng các công cụ như release-drafter thì có thể tự động hóa changelog có ý nghĩa trong GitHub Releases. Nó cung cấp mức độ chi tiết phù hợp với các bên liên quan mà tác giả nhắc đến, tức là tách riêng tính năng, bản sửa lỗi và thay đổi phá vỡ tương thích, đồng thời cũng tự động xử lý bản nháp GitHub Release tiếp theo với semver hợp lý
    Nhận xét trong bài rằng một thành phần như parse-lib không nên là tùy chọn là đúng, và tôi cũng đồng ý rằng việc ép buộc conventional commits có thể làm người đóng góp mới nản lòng. Nhưng các phương án thay thế cũng không hẳn tốt hơn
    Dù vậy, định danh thay đổi phá vỡ tương thích như fix!(parse-lib): Don't leave sparse holes when parsing JSON arrays vẫn truyền tải khá nhiều thông tin. Đó là bản sửa lỗi cho một thành phần cụ thể, việc sửa lỗi đó kéo theo một thay đổi phá vỡ tương thích không thể tránh khỏi, và còn hàm ý một mức tăng semver kiểu minor nữa. Những thứ như vậy có thể dùng ở tiêu đề PR

  • Tôi thừa nhận mình đã quá sa đà vào conventional commits như một cách khuyến khích kỷ luật commit, và rồi nó thành thói quen
    Giờ đây đôi khi tôi thấy nó mang tính hạn chế và tùy tiện. Ở vài dự án tôi thậm chí không rõ đó có phải là thông lệ thực sự hay không, và dần chuyển sang gần với phong cách Linux/Go/Node hơn; trong các monorepo có nhiều cấu hình khác nhau, việc viết [service]: [what changed] có vẻ tự nhiên hơn là cố ép ra một type. Từ giờ tôi định thử nghiệm nhiều hơn với phong cách commit cá nhân dựa trên tiêu chí cái gì trông hữu ích, thay vì cố khớp vào một quy ước cứng nhắc, và scoped commits có vẻ là một điểm khởi đầu tốt

  • chore(lobsters): add my 2 cents on conventionals commits [JIRA-69420]
    Tôi đồng ý với gần như mọi thứ, nhưng có một điểm tôi nhìn khác đi: đoạn nói rằng “nó cho người đóng góp thấy một hồ sơ mang tính xét lại, làm giảm độ tin cậy của câu chuyện mà commit log kể lại”. Có vẻ tác giả chủ yếu đang nói về các nhánh công khai, và nếu là nhánh công khai thì đó là lời khuyên hợp lý. Nhưng điều đó không nên áp dụng cho nhánh riêng tư. Chỉ cần làm sao để người review thay đổi cuối cùng — tức maintainer hoặc chính tôi của 10 năm sau — có thể hiểu được là đủ; không cần giữ lại một dòng suy nghĩ trước sau không khớp, hoặc tệ hơn là cả một chùm commit address review

  • Câu trả lời cho “vì sao scope là tùy chọn?” là trong các dự án nhỏ thì toàn bộ dự án chính là scope
    Tôi đồng ý rằng “type” của commit không quá hữu ích, nhưng cũng không chắc giữa scoped commits và conventional commits có khác biệt lớn đến vậy không. Scoped thực chất chỉ là conventional bỏ đi “type”, còn các phân loại như fix, feat, refactor, chore thì vẫn là những phân loại chấp nhận được
    Nếu mọi người đều chỉ bê nguyên mặc định của commitlint vào dùng, thì chẳng phải chỉ cần làm cho người ta dùng nó tốt hơn hay sao?