Làm thế nào để viết Git commit hiệu quả?

Lý do nên viết commit tốt

Nếu bạn duyệt qua log của một kho lưu trữ (repository, gọi tắt là repo) bất kỳ được quản lý bằng Git, có thể bạn sẽ thấy những commit trong đó ít nhiều giống với một mớ hỗn độn. Ví dụ, hãy xem những commit của tôi từ những ngày đầu tiên làm việc với Spring, tạm gọi là danh sách cũ:

$ git log --oneline -5 --author cbeams --before "Fri Mar 26 2009"

e5f4b49 Re-adding ConfigurationPostProcessorTests after its brief removal in r814. @Ignore-ing the testCglibClassesAreLoadedJustInTimeForEnhancement() method as it turns out this was one of the culprits in the recent build breakage. The classloader hacking causes subtle downstream effects, breaking unrelated tests. The test method is still useful, but should only be run on a manual basis to ensure CGLIB is not prematurely classloaded, and should not be run as part of the automated build.
2db0f12 fixed two build-breaking issues: + reverted ClassMetadataReadingVisitor to revision 794 + eliminated ConfigurationPostProcessorTests until further investigation determines why it causes downstream tests to fail (such as the seemingly unrelated ClassPathXmlApplicationContextTests)
147709f Tweaks to package-info.java files
22b25e0 Consolidated Util and MutableAnnotationUtils classes into existing AsmUtils
7f96f57 polishing

Phù, quả là không dễ chịu tí nào khi đọc những commit như vậy. Bây giờ hãy so sánh với những commit mới hơn, cũng thuộc repo đó, tạm gọi là danh sách mới:

$ git log --oneline -5 --author pwebb --before "Sat Aug 30 2014"

5ba3db6 Fix failing CompositePropertySourceTests
84564a0 Rework @PropertySource early parsing logic
e142fd1 Add tests for ImportSelector meta-data
887815f Update docbook dependency and generate epub
ac8326d Polish mockito usage

Bạn thích xem danh sách nào hơn?

Rõ ràng, những commit cũ có sự biến đổi lớn về độ dài và định dạng, còn những commit mới thì ngắn gọn, tuân theo một phong cách nhất quán, nhìn vào thấy dễ chịu hơn rất nhiều.

Có một thực tế là trong khi vẫn còn nhiều repo mà lịch sử commit nhìn giống như danh sách cũ đã nói ở trên thì cũng có những ngoại lệ. Repo của nhân Linux và chính bản thân Git là những ví dụ tuyệt vời. Những lập trình viên đóng góp cho hai repo này luôn nhận thức được rằng: một commit đủ tốt sẽ là cách hiệu quả để truyền đạt ngữ cảnh phát triển sản phẩm tới đồng đội của mình và chính bản thân họ trong tương lai. Peter Hutterer đã nói về điều này như sau:

Tái tạo lại ngữ cảnh của một đoạn code là việc tốn nhiều thời gian và công sức. Chúng ta không thể tránh né hoàn toàn việc này, do đó hãy nên tìm cách giảm bớt những nỗ lực ấy càng nhiều càng tốt. Git commit có thể đảm nhận tốt việc này, và thông qua đó nó có thể cho biết người lập trình viên tham gia dự án có phải là một chiến hữu tốt hay không, có hỗ trợ hiệu quả cho các thành viên của team hay không.

Nếu bạn chưa dành chú ý cho việc viết ra một commit với nội dung tốt, chắc hẳn bạn chưa sử dụng lệnh git log và những câu lệnh có liên quan khác đủ nhiều. Có một vòng luẩn quẩn như sau: vì nội dung các commit không được cấu trúc tốt và nhất quán, tuân theo một quy ước chung, mỗi thành viên của team sẽ không dành nhiều thời gian và cố gắng để sử dụng hoặc chăm chút hợp lý cho danh sách này. Và do không nhận được sự chú ý đúng mức, nó tiếp tục thiếu cấu trúc và nhất quán, giá trị mang lại đối với team phát triển không cao.

Bởi thế người lập trình viên nên dành ra một chút cố gắng của mình để viết những commit có chất lượng. Khi đó, các câu lệnh như git blame, revert, rebase, log, shortlog... sẽ trở nên vô cùng hữu ích, công việc review những commit và pull request của project trở thành có giá trị và có thể làm một cách độc lập, hầu như không phụ thuộc vào việc đọc code. Lúc này, nếu bạn muốn tìm hiểu xem tại sao điều gì đó lại xảy ra hoặc được tiến hành như vậy cách đây vài tháng, vài năm thì công việc đó trở nên khả thi và có thể thực hiện hiệu quả.

7 quy tắc

1 Sử dụng dòng trắng để phân tách phần tiêu đề và nội dung commit

Trong hướng dẫn sử dụng lệnh git commit có một đoạn như sau: (để hiển thị nội dung này, bạn mở terminal lên, gõ lệnh man git commit)

Though not required, it’s a good idea to begin the commit message with a single short (less than 50 character) line summarizing the change, followed by a blank line and then a more thorough description. The text up to the first blank line in a commit message is treated as the commit title, and that title is used throughout Git.

Trước hết, chúng ta sẽ lưu ý rằng không phải mọi commit đều cần có cả tiêu đề và phần mô tả chi tiết, nhất là khi những thay đổi ứng với commit đó khá đơn giản, việc viết mô tả chi tiết là không cần thiết. Ví dụ:

Fix typo in introduction to user guide

Dễ thấy là không cần có thêm mô tả nào ở đây. Nếu người đọc đặt câu hỏi rằng typo trong commit này là gì, họ có thể kiểm tra sự thay đổi về code, nghĩa là có thể dùng lệnh git show, git diff hoặc git log -p.

Nếu bạn cần tạo commit với nội dung có dạng như này ở giao diện dòng lệnh, chỉ cần thêm tùy chọn -m vào lệnh git commit:

$ git commit -m "Fix typo in introduction to user guide"

Tuy nhiên, khi commit cần kèm theo những giải thích, trình bày về ngữ cảnh, bạn cần viết phần mô tả đi kèm, ví dụ như commit sau:

Derezz the master control program

MCP turned out to be evil and had become intent on world domination.
This commit throws Tron's disc into MCP (causing its deresolution)
and turns it back into a chess game.

Trong mọi trường hợp, việc phân tách giữa tiêu đề và thân của commit là việc cần thiết để có thể tạo nên kết quả như mong muốn khi duyệt lịch sử commit. Ví dụ lịch sử đầy đủ sẽ trông giống như sau:

$ git log
commit 42e769bdf4894310333942ffc5a15151222a87be
Author: Kevin Flynn <[email protected]>
Date:   Fri Jan 01 00:00:00 1982 -0200

 Derezz the master control program

 MCP turned out to be evil and had become intent on world domination.
 This commit throws Tron's disc into MCP (causing its deresolution)
 and turns it back into a chess game.

Nếu bạn kiểm tra với lệnh git log --oneline thì terminal chỉ hiển thị dòng tiêu đề của commit:

$ git log --oneline
42e769 Derezz the master control program

Lệnh git shortlog sẽ gom nhóm các commit theo user, mặc định xếp theo thứ tự abc của username. Và lại một lần nữa chỉ có dòng tiêu đề của commit được hiển thị:

$ git shortlog
Kevin Flynn (1):
      Derezz the master control program

Alan Bradley (1):
      Introduce security program "Tron"

Ed Dillinger (3):
      Rename chess program to "MCP"
      Modify chess program
      Upgrade chess program

Walter Gibbs (1):
      Introduce protoype chess program

Có khá nhiều tình huống khi làm việc với Git mà sự phân biệt giữa dòng tiêu đề và phần mô tả của commit là cần thiết. Chúng ta chỉ đạt được điều đó khi chèn một dòng trắng phân cách giữa 2 phần này.

2 Giới hạn tiêu đề commit không quá 50 ký tự

Con số 50 không phải là một giới hạn cứng, chỉ là một quy ước tương đối mà thôi. Việc hạn chế độ dài tiêu đề trong ngưỡng này này sẽ đảm bảo rằng nó ngắn gọn, dễ đọc và khi đó đòi hỏi người viết commit phải suy nghĩ để có thể tìm ra cách diễn đạt súc tích những điều mà họ đã làm.

Tip: nếu bạn cảm thấy khó khăn để đưa ra một tiêu đề phù hợp, có thể bạn sắp thực hiện commit quá nhiều thứ một lúc. Hãy tham khảo bài viết này để có thể viết commit hiệu quả hơn nhé.

Một điều thú vị là giao diện người dùng của web site github.com cũng tuân thủ theo convention có dạng này. Nó sẽ tự động thu gọn dòng tiêu đề dài hơn 72 ký tự, thay phần dài hơn còn lại bằng dấu ba chấm. Do vậy, bạn hãy cố gắng viết tiêu đề commit trong khoảng 50 ký tự và nếu có dài hơn thì cũng không nên vượt quá 72 ký tự.

3 Viết hoa dòng tiêu đề

Quy tắc này đơn giản như chính nội dung của nó vậy. Hãy bắt đầu dòng tiêu đề với một chữ cái in hoa, ví dụ bạn nên viết:

Accelerate to 88 miles per hour

thay vì:

accelerate to 88 miles per hour

4 Không dùng dấu câu để kết thúc dòng tiêu đề

Dấu kết thúc câu là không cần thiết đối với tiêu đề commit. Ví dụ, bạn chỉ nên viết:

Open the pod bay doors

thay vì:

Open the pod bay doors.

5 Viết tiêu đề có dạng câu mệnh lệnh

Dạng mệnh lệnh có nghĩa là nói hay viết giống như đang đưa ra một mệnh lệnh hoặc hướng dẫn. Đây là một vài ví dụ trong tiếng anh:

  • Clean your room
  • Close the door
  • Take out the trash

Mỗi một quy tắc trong số 7 quy tắc được trình bày trong bài này đều được viết ở dạng mệnh lệnh.

Nếu áp dụng vào giao tiếp hàng ngày, bạn có thể cảm thấy cách nói / cách viết này có chút gì đó hơi thô lỗ, cục cằn. Đó là lý do tại sao chúng ta không nên thường xuyên sử dụng chúng. Nhưng với dòng tiêu đề của commit, đây là một sự lựa chọn tốt. Một lý do nữa để chúng ta làm như vậy bởi vì bản thân Git cũng sử dụng những câu có dạng mệnh lệnh khi nó tự tạo những commit.

Ví dụ, nội dung mặc định được tạo khi sử dụng lệnh git merge là:

Merge branch 'myfeature'

Đối với lệnh git revert:

Revert "Add the thing with the stuff"

This reverts commit cc87791524aedd593cff5a74532befe7ab69ce9d.

Do đó, khi bạn viết nội dung commit ở dạng mệnh lệnh, bạn đã tuân theo convention của chính Git. Hãy cảm thấy thoải mái với những nội dung như vậy nhé. Một vài ví dụ để bạn tham khảo thêm:

  • Refactor subsystem X for readability
  • Update getting started documentation
  • Remove deprecated methods
  • Release version 1.0.0

6 Trình bày phần mô tả của commit bằng những dòng không quá 72 ký tự

Git không tự động giới hạn số ký tự trên một dòng. Khi viết phần mô tả của commit, bạn nên áng chừng giới hạn lề phải của nó và chủ động ngắt dòng. Tôi đưa ra đề xuất cho giới hạn này là 72 ký tự, lúc đó sẽ có nhiều khoảng trống để bạn có thể lùi đầu dòng nhằm trình bày mô tả commit rõ ràng hơn trong khi vẫn giữ cho nội dung tổng thể không vượt quá 80 ký tự.

Một text editor tốt sẽ hỗ trợ bạn hiệu quả để làm việc này. Ví dụ, bạn có thể dễ dàng cấu hình cho Vim để giới hạn phần text trên mỗi dòng là 72 kí tự khi viết một commit với Git. Việc sử dụng các IDE cũng là một lựa chọn phù hợp.

7 Sử dụng phần mô tả của commit để trả lời cho nhưng câu hỏi cái gì, như thế nào, tại sao

Commit lấy từ Bitcoin Core là một ví dụ tốt để minh họa cho quy tắc này:

commit eb0b56b19017ab5c16c745e6da39c53126924ed6
Author: Pieter Wuille <[email protected]>
Date:   Fri Aug 1 22:57:55 2014 +0200

   Simplify serialize.h's exception handling

   Remove the 'state' and 'exceptmask' from serialize.h's stream
   implementations, as well as related methods.

   As exceptmask always included 'failbit', and setstate was always
   called with bits = failbit, all it did was immediately raise an
   exception. Get rid of those variables, and replace the setstate
   with direct exception throwing (which also removes some dead
   code).

   As a result, good() is never reached after a failure (there are
   only 2 calls, one of which is in tests), and can just be replaced
   by !eof().

   fail(), clear(n) and exceptions() are just never called. Delete
   them.

Bạn hãy xem commit này với lệnh full diff rồi nghĩ về việc tác giả của nó đã tiết kiệm được biết bao thời gian, sức lực cho các thành viên hiện tại và những người sẽ join dự án sau này bằng việc dành ra một chút nỗ lực để cung cấp ngữ cảnh, tóm tắt những việc đã thực hiện ở commit này. Nếu anh ấy không làm vậy, những thông tin này có thể sẽ không bao giờ được ghi lại bởi vì theo thời gian, không còn ai nhớ được lúc đó anh ấy đang làm gì.

Tóm lại, với đa số các trường hợp, bạn có thể viết mô tả chi tiết về việc những thay đổi trong chương trình đã diễn ra như thế nào. Người lập trình nên cố gắng tạo ra những đoạn code có thể tự giải thích, comment cho chính nó, nhưng nếu khi code trở nên phức tạp thì nó cần được giải thích bằng văn xuôi, giống như việc viết documentation cho một ngôn ngữ lập trình hoặc framework vậy. Hãy tập trung vào lý do tại sao bạn lại đưa ra những thay đổi như vậy ở mã nguồn. Trong tương lai, những người bảo trì hệ thống sẽ cảm ơn bạn rất nhiều vì điều đó, chắc chắn là như vậy!

Mình hi vọng bài viết có thể giúp ích, cảm ơn các bạn đã đón đọc.