Commit Git tôi yêu thích nhất (2019)
(dhwthompson.com)- Commit “Convert template to US-ASCII to fix error” của Dan Carley, xuất hiện khi làm việc trên GOV.UK, cho thấy ngay cả một thay đổi mã nhỏ cũng có thể trở thành tài liệu dài hạn cho codebase nhờ thông điệp commit chi tiết
- Một thông điệp tốt không chỉ ghi lại đã thay đổi gì mà còn để lại lý do tại sao thay đổi; trường hợp này ghi chép cụ thể lỗi
invalid byte sequence in US-ASCIIxảy ra khi chạybundle exec rakevà điều kiện để tái hiện - Vì chuỗi lỗi và quá trình điều tra được lưu lại cùng nhau, người gặp lại cùng vấn đề sau này có thể dùng
git log --grephoặc tìm kiếm commit trên GitHub để tìm hồ sơ giải quyết trước đó - Thông điệp còn bao gồm quá trình dùng các công cụ Unix như
find -exec,file --mime,iconv, giúp reviewer và người đọc về sau có thể lần theo luồng xử lý sự cố - Không phải mọi commit đều cần dài đến mức này, nhưng những thông điệp giữ lại bối cảnh, quá trình điều tra và cả cảm xúc sẽ giúp xây dựng sự thấu hiểu chung về codebase và niềm tin trong nhóm
Một commit biến thay đổi nhỏ thành tài liệu dài hạn
- Nếu được viết tốt, thông điệp commit Git có thể trở thành một công cụ tài liệu hóa mạnh mẽ tồn tại suốt vòng đời của codebase
- Commit được nêu làm ví dụ là một thay đổi từ thời làm việc trên GOV.UK tại Government Digital Service
- Tác giả là Dan Carley, và tiêu đề là “Convert template to US-ASCII to fix error”
- Nhờ cách phát triển công khai của GDS, những trường hợp nội bộ của tổ chức như thế này cũng có thể được chia sẻ ra bên ngoài
Bối cảnh quan trọng hơn độ dài
- So với lượng mã thay đổi, thông điệp của commit này khá dài, nhưng giá trị cốt lõi không nằm ở độ dài mà ở bối cảnh hữu ích
- Cùng một thay đổi đó ở nơi khác có thể đã bị ghi ngắn gọn kiểu
change whitespace,fix bug - Dan để lại bản ghi chi tiết để đồng nghiệp xung quanh và độc giả về sau có thể hiểu nguyên nhân vấn đề và quá trình xử lý
Không chỉ là thay đổi gì mà là vì sao thay đổi
- Một thông điệp commit tốt không chỉ giải thích nội dung thay đổi mà còn cả lý do của thay đổi
- Commit này ghi lại rằng sau khi thêm một bài kiểm thử để khớp với nội dung
/etc/nginx/router_routes.conftrong feature branch, kết quả lại khác nhau tùy cách chạy- Chạy bằng
bundle exec rake spechoặcbundle exec rspec modules/router/specthì hoạt động bình thường - Chạy bằng
bundle exec rakethì từng khốishouldđều thất bại - Lỗi là
ArgumentError: invalid byte sequence in US-ASCII
- Chạy bằng
- Nếu không có phần giải thích này, rất có thể người ta sẽ phải đoán xem công cụ nào đã gặp lỗi parse gì
- Bối cảnh công việc ban đầu rất dễ biến mất khi con người quên đi, chuyển nhóm hoặc rời tổ chức
Hồ sơ lỗi có thể tìm kiếm được
- Phần đầu thông điệp commit chứa nguyên văn thông báo lỗi đã dẫn tới thay đổi
- Người gặp lại cùng lỗi có thể tìm lịch sử trước đó trong codebase theo các cách sau
git log --grep "invalid byte sequence"- GitHub commit search
- Theo kết quả tìm kiếm, đã có nhiều người tìm lỗi này, và cũng có thể xác nhận ai là người gặp nó trước và đã thực hiện biện pháp gì
Ghi lại quá trình giải quyết vấn đề như một câu chuyện
- Thông điệp được cấu trúc để người đọc có thể lần theo vấn đề xuất hiện ra sao, đã điều tra theo thứ tự nào và cuối cùng được sửa thế nào
- Ví dụ, nó bao gồm việc lỗi biến mất khi bỏ matcher
.with_content(//), file spec không có ký tự lạ, và việc có thể tái hiện khi require Puppet trong cùng một interpreter - Vì thông điệp commit ghi lại chính thay đổi chứ không phải chỉ một file, hàm hay một dòng mã cụ thể, đây là vị trí phù hợp để lưu lại hành trình mà codebase đã trải qua
Tác dụng phụ là mở rộng tri thức của cả nhóm
- Dan đã ghi lại các lệnh được chạy ở từng bước điều tra, và đây có thể là cách lan truyền kiến thức nhẹ nhàng trong nhóm
- Chỉ từ thông điệp commit, người đọc cũng có thể học cách dùng công cụ Unix
- Có thể truyền đối số
-execchofindđể chạy lệnh trên từng file được tìm thấy - Thêm
\\+ở cuối lệnh sẽ truyền nhiều tên file vào một lệnhfilethay vì chạy một lần cho từng file file --mimecho biết kiểu MIME của file- Có tồn tại một công cụ tên là
iconv
- Có thể truyền đối số
- Không chỉ người review thay đổi, mà cả người tìm lại commit này sau đó cũng nhận được cùng lượng thông tin
- Khi có đủ thời gian và đủ commit tích lũy, những thông điệp như vậy có thể trở thành thiết bị khuếch đại tri thức của cả nhóm
Ghi chép tạo sự đồng cảm và niềm tin
- Ở cuối commit có câu: “Now the tests work! One hour of my life I won't get back..”
- Câu này truyền tải sự bực bội của một lập trình viên đã dành một giờ lần theo một bug khó chịu, cùng cảm giác thỏa mãn khi xử lý xong
- Kể cả khi các đoạn hack ngắn hạn hay mã prototype đã đi vào production và bén rễ ở đó, những thông điệp như vậy vẫn nhắc ta rằng đằng sau mọi thay đổi đều từng có một con người đã đưa ra quyết định tốt nhất có thể với thông tin họ có lúc bấy giờ
Không phải mọi commit đều phải dài
- Đây là một ví dụ khá cực đoan, và đặc biệt không cần kỳ vọng mọi commit có quy mô như thế đều phải chi tiết ở cùng mức độ
- Dù vậy, đây vẫn là một ví dụ tốt về việc giải thích bối cảnh phía sau thay đổi, giúp người khác học hỏi, và đóng góp vào mô hình tinh thần chung về codebase của cả nhóm
- Nếu bạn quan tâm đến thông điệp commit tốt và các công cụ để cấu trúc thay đổi, có thể xem các tài liệu sau
- Telling stories through your commits by Joel Chippindale
- A branch in time by Tekin Süleyman
1 bình luận
Ý kiến trên Hacker News
Dù tốt hay xấu, từ góc nhìn của một đồng sáng lập GitHub và là người viết sách về Git như Pro Git, thông điệp commit Git là một con đường độc đáo để tài liệu hóa mã nguồn, nhưng lại rất kém hiệu quả.
Vấn đề cốt lõi là hầu hết công cụ như Git, GitHub thường chỉ hiển thị dòng đầu tiên. Commit ví dụ cũng chỉ hiện một dòng chung chung như “US-ASCII error”, còn phần nội dung còn lại được bài viết khen ngợi thì trong các công cụ hiện đại gần như chẳng ai thấy.
Git được thiết kế để thông điệp commit là phần thân email mà mọi người trong dự án đều đọc, nhưng ngày nay nhìn chung nó không còn đóng vai trò đó. Trừ khi thay đổi được thảo luận dưới dạng chuỗi patch trên mailing list, gần như không ai đọc gì ngoài 50 ký tự đầu của tiêu đề.
Trong
git blame, dù có tìm xem nên dùng-w -C -C -Chay hai-Cđể lần theo thông điệp liên quan, bạn vẫn chỉ thấy dòng đầu tiên; cuối cùng phải tìm SHA rồi chạygit showmới xem được thông điệp dài. Ngay cả khi viết tốt, việc tìm lại thông tin quá khó là một trong những phàn nàn lớn nhất của tôi về Git.Nhìn vào lịch sử dự án Git, thông điệp commit hầu như lần nào cũng xuất sắc, nhưng chỉ chạy
blametrên một file cũng rất khó để lần theo bối cảnh của phần triển khai một hàm. Tài liệu hóa mà Jeff King để lại trong 10 năm qua đạt mức thiên tài, nhưng thật tệ là gần như không ai thưởng thức được.Tôi không biết giải pháp chính xác là gì, nhưng trong phần lớn cộng đồng, việc viết tài liệu xuất sắc bằng thông điệp commit đáng buồn là gần như lãng phí thời gian. Vì quá khó tìm.
git blame,git log,git showtrong terminal; việc lần theo lịch sử file rất dễ, và dùnggit log -Gđể tìm thời điểm thêm/xóa chỉ mất vài giây.Điều khó chịu là khi đã tìm được commit mà thông điệp chỉ là “bleh” hoặc “add a thing”. Giá mà lập trình viên dành 60 giây để ghi lý do vì sao họ làm vậy.
Ngược lại, khi tìm thấy một thông điệp commit giải thích chi tiết vì sao một thay đổi được thực hiện, tôi thật sự rất vui. Một thông điệp commit tốt có thể tiết kiệm hàng giờ, thậm chí hàng ngày làm việc.
GitHub cũng góp phần vào vấn đề thông điệp commit tệ. Nếu may mắn thì mô tả PR có chi tiết, nhưng nó không nằm ngay cạnh commit log; thường thì PR dẫn tới link Jira, Jira dẫn tới link cuộc trò chuyện Slack, rồi Slack dẫn tới link tài liệu Google.
Ngành này thật sự rất kém trong việc tài liệu hóa, nhưng những người như Jeff King đang chiến đấu đúng cách. Cuối cùng tôi nghĩ đây không phải vấn đề kỹ thuật mà là vấn đề con người. Viết tài liệu không có phần thưởng tức thì nên có cảm giác như việc làm thêm, còn lợi ích chỉ xuất hiện sau vài ngày, vài tuần hoặc vài tháng.
Xin hãy viết thông điệp commit tốt. Chỉ cần dành 1 phút ghi lý do vì sao đã làm, không phải commit nào cũng trở thành một bài giải cho cái hàng rào chết tiệt của Chesterton. Hãy đặt nó vào thông điệp commit dễ tìm, bản thân bạn trong tương lai và tôi sẽ biết ơn.
Nói thêm, tôi cũng không đồng ý với lập luận rằng thông điệp commit khó tìm. Tôi gần như không gặp khó khăn khi lần theo lịch sử của một dòng mã, một hàm hay một file; và ngay cả khi thông điệp commit không được đọc lại, nó vẫn có giá trị tại thời điểm viết. Viết thông điệp tốt khiến tôi kiểm tra xem mình có thật sự viết đúng đoạn mã như dự định không, và cũng có giá trị với người review code.
Nói rằng khó tìm tùy chọn
git blamecũng nghe như đùa. Một IDE tốt sẽ gắn thông tin blame vào từng dòng, cho xem diff bằng một nút bấm, và từ diff đó phải có thể blame đệ quy các dòng ngữ cảnh hoặc các dòng đã bị xóa. Những công cụ như Tig làm đúng việc đó.Tôi thừa nhận GitHub khiến việc xem thông điệp commit trở nên khó khăn.
Tài liệu gắn vào commit không phải được viết cho vui. Nó là cơ chế giảm rủi ro khi nhận patch từ một người có thể sau này không còn ở đó, đồng thời bộc lộ các suy nghĩ liên quan để người khác có thể làm tiếp trên nền đó. Nếu giấu suy nghĩ, quyền sở hữu độc quyền đối với mã sẽ tích tụ, và thường đó không phải điều tốt. Thông điệp commit cũng đóng vai trò bằng chứng công việc, và có thể trở nên quan trọng khi có quá nhiều patch. Trong các dự án thương mại, một phần tầm quan trọng này có thể thấp hơn.
Thật ra không có chỗ nào khác để đặt loại tài liệu này. Nó không phù hợp với comment trong mã. Nhưng giờ đã có một thế hệ lập trình viên nghĩ rằng Git chính là GitHub, và mục đích duy nhất của Git là đẩy thay đổi lên GitHub.
Git không tuyệt, nhưng vẫn đỡ tệ hơn rất nhiều so với các thứ khác. Cần quay lại nền tảng và hiểu mục đích thực sự của quản lý phiên bản.
Vì vậy hiện tại nó có thể không giúp ích nhiều cho cộng đồng, nhưng sẽ có ích cho người gỡ lỗi vài năm sau.
Tôi đồng ý với mục đích tổng thể, nhưng sẽ tốt hơn nếu đặt một BLUF cụ thể hơn ở đầu. Ví dụ viết kiểu “Fix test issues caused by non-breaking space character \xa0”
Có thể hiểu ngay vấn đề là gì, và nếu muốn biết thêm thì vẫn có quyền đọc phần bên dưới
Vì vậy, để tìm kiếm trong lịch sử thì chỉ cần một thông điệp dòng đầu tốt là đủ. Tôi thấy câu chuyện ngắn về việc đi đến tận Narnia rồi quay về để tìm nguyên nhân gốc của bug không liên quan lắm. Tuy vậy, tôi cũng không phản đối việc viết nó vào phần mô tả PR hoặc phần thông điệp mở rộng của commit như một cách xả bực
Tôi từng cảm thấy tự hào khi viết được một thông điệp commit xuất sắc, nhưng ít chắc chắn hơn về giá trị nó mang lại cho người khác. Khi gặp một thông báo lỗi lạ, thêm tính năng mới, hoặc gần như trong bất kỳ tình huống nào, tôi nghĩ đa số mọi người không tìm kiếm trong thông điệp commit
Hơi buồn, nhưng tôi ngày càng nghi ngờ rằng những thông điệp commit đẹp đẽ có phần giống sự phù phiếm của lập trình viên. Người chủ yếu trầm trồ là chính tác giả, còn người khác thì lướt qua mà không nhận ra
Đôi khi vẫn có chỗ cho kiểu trang trí thẩm mỹ đó, nhưng tôi không chắc nó có giá trị thực dụng lớn. Giờ nếu người khác commit với nội dung “fix whitespace issue” thì tôi cũng không còn quá bận tâm nữa, và nhờ vậy có lẽ tôi đã trở thành một đồng nghiệp tốt hơn
Với các dự án có đội ngũ phân tán khổng lồ và vô số commit như Git hay Linux thì có thể khác. Những dự án tôi quen thuộc thường có 1–100 người đóng góp và phần lớn thuộc cùng một tổ chức
Khi Google hay Stack Overflow không cho ra đáp án, tôi cũng tìm trên GitHub xem có nội dung tương tự trong PR hoặc thông điệp commit không. Tính cả các kho riêng tư mà tôi có quyền truy cập, tôi đã được giúp vô số lần. Thông điệp commit tốt chắc chắn có giá trị vượt xa sự phù phiếm. Nếu nhiều lập trình viên không xem chúng thì đó là thiệt thòi của họ, và hy vọng khi tích lũy kinh nghiệm, một ngày nào đó họ sẽ nhận ra. Cũng có thể dạy lập trình viên junior cách tìm kiếm hoặc gửi bài gốc cho họ
Từ đó về sau, tôi cố viết thông điệp commit sao cho ít nhất bản thân trong tương lai có thể nhớ ra vì sao thay đổi đó là cần thiết
git blamethì bạn đang làm saiNhiều lần, khi tôi định sửa một bug rõ ràng, ngay trước lúc commit tôi xem lại commit trước đó và phát hiện “bug” đó được đưa vào có chủ ý, còn task JIRA liên kết thì giải thích rất rõ vì sao thay đổi tưởng như hiển nhiên của tôi sẽ tái tạo một bug từ 2 năm trước
git blametrong IDE để nắm chuyện gì đã xảy ra. Khi gặp một thông điệp commit tốt, tôi thấy biết ơnDòng đầu của thông điệp commit là quan trọng nhất vì nó giúp
git logxử lý hàng rào Chesterton. Trong trường hợp này, tôi nghĩ tác giả đã vung hụtĐiểm mấu chốt là dòng đầu nên nói vì sao làm, chứ không phải đã làm gì. Ai quan tâm cái gì đã thay đổi thì có thể xem code hoặc diff
Ví dụ có thể viết “nginx .conf files must be in us-ascii”, rồi tiếp theo là “changed blahblah.erb to remove nonbreaking space character”, sau đó nối tiếp phần còn lại của thông điệp commit vốn khá tốt
Nên nghĩ như một bài báo. Hãy giả định độc giả có thể dừng đọc ở bất kỳ điểm nào, và viết theo thứ tự mức độ quan trọng giảm dần, còn chi tiết tăng dần
Bài báo cũng giải thích cái gì ở tiêu đề, chứ không phải vì sao
Trong trường hợp này, dòng đầu của bản gốc là vừa đúng. Khi lướt
git log, bạn có thể đoán commit này nhiều khả năng không thay đổi hành vi chức năng của test và bỏ qua“nginx .conf files must be in us-ascii” có thể là tiêu đề bug hoặc PR tốt, nhưng nó cũng có thể tương ứng với nhiều commit làm các việc khác nhau, và không cho biết thực sự chuyện gì đã xảy ra. Không biết đó là chuyển file sang US-ASCII, tạo công cụ chuyển đổi, cập nhật tài liệu, tạo test, hay một tổ hợp các việc đó. Bắt đầu bằng cái gì thay vì vì sao sẽ giảm sự mơ hồ đó
“The files must be in us-ascii” hay “Changed the files to us-ascii” cũng không khác nhau nhiều. Cả hai đều cho biết rõ rằng các file đã được đổi sang US-ASCII
Nhược điểm của cách tài liệu hóa này là thông điệp commit một khi đã viết thì về thực chất không thể sửa được
Tất nhiên có thể làm bằng
rebase, nhưng liệu có thật sự muốn sửa một nội dung đã viết từ 1 năm trước và đã được merge vàomain, gây đau khổ cho các nhánh tính năng của mọi người khôngSo với tài liệu lưu trong file
.md, Wiki hay Confluence, tôi có thể cải thiện nội dung đồng nghiệp viết, và đồng nghiệp khác cũng có thể cải thiện nội dung tôi viếtTrong trường hợp này, lỗi có thể đã được sửa và không xuất hiện lại. Nhưng khi commit một component cụ thể, có cám dỗ muốn giải thích thiết kế của nó, và giờ tôi tránh làm vậy. Nếu sau này component đó thay đổi vì yêu cầu kinh doanh đổi khác thì sao. Tài liệu trong commit có chỉ còn giải thích phần khác biệt không. Khi đó thành viên mới muốn hiểu hệ thống sẽ phải đọc nhiều thông điệp commit rồi tự merge chúng trong đầu
So sánh với file
.md, Wiki hay Confluence chẳng khác nào so sánh xe đạp với con ngỗngNhững cân nhắc hay đánh đổi ở thời điểm tạo component, hoặc việc không hề có những thứ đó, thường hữu ích để hiểu lỗi gốc, các tiến hóa về sau, và những khiếm khuyết phát sinh từ thay đổi trong use case
Nếu thành viên mới muốn hiểu hệ thống thì chỉ cần duy trì một tài liệu “hiện tại” riêng. Tài liệu đó không cần đề cập cả các đánh đổi trong triển khai hay chuyện bản nháp được viết dưới áp lực thời gian
Squash commit giúp bản ghi tốt hơn như một câu chuyện về việc thêm tính năng, merge bảo toàn log bất biến của thay đổi code thực tế, còn rebase thì làm mỗi thứ một chút
Không có lý do gì lại không thể có cả hai. Chúng ta là những người lập trình máy tính, nên có thể tạo ra thứ mình muốn
Nếu viết lại Git từ đầu, tôi sẽ chia commit thành hai phần. Lịch sử thay đổi sẽ được giữ bất biến bằng cấu trúc kiểu Merkle DAG như Git, còn thông điệp commit sẽ được lưu trong một kho dữ liệu liên kết riêng, thành một log hợp lý và có thể chỉnh sửa để mô tả workflow. Có thể nhóm commit theo tag tính năng, sửa lỗi chính tả, thay đổi thông điệp tùy ý, nhưng vẫn giữ nguyên log diff bên dưới, tức “những gì thật sự đã thay đổi trong code”
git notes. Tuy nhiên nếu thông điệp dài đến vậy thì có vẻ ít khả năng có ai nhận raCó một điểm tôi không đồng ý. Đó là đoạn “không kỳ vọng mức độ chi tiết như thế này ở mọi commit, đặc biệt là commit cỡ này”
Theo kinh nghiệm của tôi, ngược lại, những thứ nhỏ và trông vô hại thường lại cần phần giải thích tương đối dài hơn
Hôm qua tôi đã viết ba đoạn để giải thích vì sao thêm
--limit=999vàogh pr list. Vì nó dễ gây nhầm lẫn. Ngay trong đối số--jqđã cólimit(, và nếu giả định tổng số PR là vô hạn, thì giá trị càng cao kết quả cuối cùng thực ra lại càng thấpTôi cũng viết comment trong code, và có lẽ thời gian suy nghĩ, trau chuốt còn lâu hơn thời gian viết. Mong rằng đây sẽ được nhớ như một ví dụ tiếp theo mỗi khi ai đó ám chỉ công việc này chỉ là bắn code ào ào
“This was a non-ascii whitespace character that caused
ArgumentError: invalid byte sequence in US-ASCIIwhen runningbundle exec rake” là đủ. Có từ khóa để sau này tìm các vấn đề tương tự, có nguyên nhân gốc, và không quá dài nên ít có khả năng bị mọi người lướt quaTôi không nghĩ đây là một Git commit xuất sắc
So với lượng văn bản kia, dòng đầu “Convert template to US-ASCII to fix error” có thể tốt hơn. Chỉ cần thêm vài từ nói ký tự khoảng trắng nào gây lỗi và lỗi là gì. Chỉ riêng phần giải thích đó cộng với diff đã đủ bối cảnh cần thiết
Thành thật mà nói, phần còn lại gần như vô dụng. Nó không gây hại, nhưng giá trị cũng không lớn. Tác giả đã tài liệu hóa hành trình truy vết lỗi này, nhưng tôi tự hỏi ai sẽ quan tâm
Trong bài còn có liên kết kết quả tìm kiếm cho thấy nhiều commit do những người đã học được từ bản sửa đó để lại
Thông điệp commit đó là một kho tri thức
Muốn xem thông điệp commit xuất sắc thì hãy xem lịch sử Git của nhân Linux. Ở đó, đây là tiêu chuẩn
Dòng đầu luôn nhắc đến subsystem bị ảnh hưởng bởi thay đổi, rồi tiếp theo là một tóm tắt một dòng ở thể mệnh lệnh. Sau đó trả lời ba câu hỏi càng chi tiết càng tốt
Ví dụ có thể viết như: “Code hiện tại làm X. Khi chạy test case T, quan sát thấy hành vi ngoài dự kiến U. Điều này là do lý do R. Sửa bằng cách làm F”
Gần đây có một người đóng góp nói rằng cách tôi viết thông điệp Git, tức các yêu cầu của tôi, là “độc đáo”. Có vẻ nền tảng Linux kernel của tôi đã lộ ra, nhưng tất cả thông điệp commit của tôi đều trông như trong bài này
Nếu là nội dung liên quan đến mã hiện có, phần chú thích nên đi cùng mã. Những nội dung liên quan đến quy trình, chẳng hạn như mã đã bị xóa hoặc mã từng không hoạt động, nên nằm trong commit
Điều quan trọng nhất là thông điệp commit có thể tham chiếu đến issue cho tiện, nhưng nhất định phải tái hiện các chi tiết cốt lõi. GitHub chỉ mang tính tạm thời, còn thông điệp Git thì không
Một thông điệp commit đầy đủ ngữ cảnh có ở đây[1]
Chuyện này phổ biến đến mức maintainer đã viết bài này[2]
[1] https://github.com/git/git/commit/d70f554cdf38b0b05cfaa8e8eb...
[2] https://lore.kernel.org/git/xmqqedevo8ps.fsf@gitster.g/