Biến Git và GitHub trở thành công cụ đắc lực cho Software Engineer

1. Git

Khi viết code, sẽ có rất nhiều lúc bạn muốn, hoặc cần phải quay lại đoạn code mình đã viết tại một thời điểm trước đây. Một cách đơn giản, ta hoàn toàn có thể dùng tính năng undo/redo của trình biên soạn (editor) mình đang dùng. Tuy nhiên, editor có thể sẽ không lưu lại lịch sử sau khi đóng, và bạn cũng có thể nhấn nhầm sang một phím khác thay vì nhấn redo, và thế là lịch sử trạng thái được editor lưu lại đã bị ghi đè khiến bạn không thể redo được nữa.

Ngoài ra, khi nhiều người cùng làm việc trong một dự án, các thành viên cần tìm ra cách chia sẻ các thay đổi của mình để đảm bảo rằng tất cả mọi người đang làm việc trên cùng một phiên bản của dự án. Trước đây, lập trình viên thường chia sẻ các thay đổi code bằng cách xuất ra file patch và gửi cho nhau qua email. Khi nhận được một bản patch, các thành viên sẽ áp dụng nó vào trong phiên bản code hiện tại của mình trên máy cá nhân.
Dưới đây là một ví dụ về file patch, được trích ra từ https://bit.ly/34r4SVU.

Mỗi file patch chỉ biết đến hai trạng thái của dự án, là trước và sau khi được trích xuất ra. Như vậy, khi có nhiều file patch, ta cần phải biết thứ tự của chúng để có thể xây dựng lại chính xác một phiên bản của dự án tại bất kì thời điểm nào. Ngoài ra, vì file patch cũng chỉ là file thông thường được lưu trong ổ cứng, nên ta cần một cách để sao lưu lại chúng. Khi có nhiều người cùng làm việc trên một file, ta cần thêm một cơ chế đồng bộ các file patch với nhau, vì rất có thể sẽ có những thay đổi chồng chéo lên nhau.

Để giải quyết các vấn đề nêu trên, ta cần đến một hệ thống quản lý phiên bản. Phần một của bài viết này sẽ giới thiệu đến các bạn Git, một trong những hệ thống quản lý phiên bản phổ biến nhất hiện nay.

1.1. Git là gì?
Git (https://git-scm.com/) là một hệ thống quản lý phiên bản phân tán (distributed version control system), được viết bởi Linus Torvalds (người tạo ra và phát triển chính của Linux kernel) vào năm 2005. Mục đích ban đầu của Git là để hỗ trợ việc phát triển Linux kernel. Ngoài Git còn có các hệ thống khác như Subversion và Mercurial. Các bạn có thể tham khảo bài viết sau về mức độ phổ biến của các hệ thống quản lý phiên bản vào năm 2016:  https://rhodecode.com/insights/version-control-systems-2016; hoặc dữ liệu từ Google Trends: https://trends.google.com/trends/explore?date=all&q=git,svn,mercurial.
Để hiểu rõ hơn về Git, ta sẽ tìm hiểu về từng khía cạnh trong tên gọi hệ thống quản lý phiên bản phân tán.
Hệ thống quản lý phiên bản (version control system, VCS)

A generic square placeholder image with rounded corners in a figure.

Trong cuốn sách kinh điển The Pragmatic Programmer [1], hai tác giả David Thomas và Andrew Hunt đã dành hẳn một phần (Topic 19) để nói về việc quản lý phiên bản. Còn trong một quyển sách khác nổi tiếng không kém [2], tác giả Robert C. Martin cũng đề cập đến việc sử dụng một hệ thống quản lý phiên bản ở ngay đầu phần phụ lục Tooling, và ông khuyên dùng Git để quản lý code.

Git hỗ trợ các lập trình viên bằng cách lưu lại toàn bộ các thay đổi với dự án, được chia nhỏ thành các commit. Mỗi commit là một đơn vị thay đổi, tương tự như một file patch. Tại mỗi commit, ta có một phiên bản của dự án. Bằng việc lưu lại toàn bộ thay đổi trong một hệ thống riêng biệt, ta hoàn toàn có thể quay lại bất cứ phiên bản nào tại thời điểm mà commit được tạo ra. Ngoài ra, Git cũng hỗ trợ làm việc nhóm thông qua tính phân tán.

Tính phân tán (distributed)
Khi làm việc với một dự án, ta sẽ tạo ra một Git repository (kho chứa), thường được gọi tắt là repo. Đây là nơi chứa toàn bộ các thông tin cần thiết để quản lý dự án, ví dụ như các thay đổi đã được nhắc đến ở trên.
Khi làm việc với một dự án được quản lý bởi Git, mỗi lập trình viên sẽ có một bản sao của Git repo trên máy cá nhân, và được đồng bộ với nhau thông qua một repo được lưu trữ trên máy chủ riêng biệt (remote repository). Thay vì chia sẻ các bản patch, lập trình viên sẽ tạo ra các commit, và gửi các commit này lên remote repository. Các lập trình viên khác có thể đồng bộ repo trên máy cá nhân với remote repo để thấy được các commit này, và thêm vào phiên bản của dự án hiện tại trên máy của mình nếu muốn.

1.2. Sử dụng Git như thế nào?Khi bắt đầu sử dụng Git cho một dự án, ta cần khởi tạo một Git repository trên máy cá nhân, bắt đầu với câu lệnh git init. Sau khi được khởi tạo, mọi thay đổi sẽ được Git theo dõi và quản lý. Trong trường hợp bắt đầu làm việc với một dự án đã và đang sử dụng Git, và đã có một remote repository, ta có thể tạo ra một bản sao trên máy cá nhân bằng câu lệnh git clone.

Sau khi thiết lập môi trường làm việc, ta có thể bắt đầu thao tác với Git như sơ đồ bên dưới. Local repository chính là bản sao của Git repo trên máy cá nhân, được đồng bộ với những local repo khác thông qua remote repo. Cũng trên máy cá nhân, ta còn có hai môi trường nữa là thư mục làm việc (working directory) và khu vực chờ (staging area).

Tất cả các thao tác thêm/sửa/xoá để thay đổi code sẽ được thực hiện trên thư mục làm việc. Khi muốn lưu lại những thay đổi này, ta sẽ dùng câu lệnh git add để thêm chúng từ thư mục làm việc sang khu vực chờ. Sau đó sử dụng câu lệnh git commit để lưu từ khu vực chờ vào local repository.

Khu vực chờ là nơi để chuẩn bị cho những thay đổi trước khi chúng được commit. Ví dụ như ta thay đổi ba file: a.py, b.py, và c.py. Tuy nhiên thay đổi ở file c.py tương đối độc lập với hai file còn lại nên ta muốn tách ra một commit riêng. Để làm được việc đó, đầu tiên ta thêm file a và b vào khu vực chờ và commit trước:


Sau đó mới thêm file c vào khu vực chờ và commit:


Tuy nhiên lúc này, những thay đổi mới chỉ đang ở local repository, tức là những lập trình viên khác không biết đến sự tồn tại của chúng. Để đưa những thay đổi này lên remote repository, ta dùng lệnh git push. Và ngược lại, dùng lệnh git pull để cập nhật những thay đổi mới nhất của người khác từ remote repository về máy cá nhân.

Một tính năng rất quan trọng khi làm việc nhóm với Git là tách nhánh (branching). Tất cả Git repository đều có một nhánh mặc định là master, nơi các commit sẽ được lưu lại ở thời điểm ban đầu. Khả năng tách nhánh của Git giúp cho các thành viên của dự án có thể làm việc độc lập vì nếu mỗi người có một nhánh riêng, các thay đổi khi được push lên remote repository sẽ không gây ra xung đột. Ta có thể kiểm tra xem nhánh hiện tại đang làm việc là gì với câu lệnh git branch, và tạo một nhánh mới với câu lệnh git checkout.

Sau khi đã tạo ra một nhánh mới, ta có thể bắt đầu commit những thay đổi lên nhánh này. Khi hoàn thành phần việc của mình trên nhánh cá nhân, ta cần tích hợp các thành phần lại với nhau thông qua việc hợp nhất các nhánh với câu lệnh git merge. Các thay đổi sau khi đã được hợp nhất lại sẽ được các thành viên pull về local repository của mình. Trong hình minh hoạ bên trên, ta có thể thấy các merge commit như Merge branch 'release/v1.1.0' into develop, hoặc Merge branch 'release/v1.1.0'.


1.3. Các nguyên tắc khi làm việc với 
Git Có một số nguyên tắc nhất định giúp cải thiện hiệu suất và nâng cao khả năng làm việc nhóm khi sử dụng Git để quản lý phiên bản cho dự án.

Tách nhánh mới để làm việc. Như đã nói ở trên, việc tách nhánh mới giúp các thành viên có thể làm việc độc lập với nhau. Ngoài ra, bằng cách làm việc trên một nhánh riêng biệt, ta có thể đảm bảo rằng code trên nhánh mặc định (master) luôn là code ổn định và có thể được đưa lên môi trường production.

Viết commit message rõ ràng, dễ hiểu. Mỗi commit đều có một message, là nơi để lập trình viên tóm tắt những thay đổi được lưu lại trong commit này. Commit message rõ ràng và tường minh giúp cho các thành viên khác có thể dễ hình dung về những thay đổi hơn khi nhìn vào git log hoặc git tree. Conventional Commits là một quy ước viết commit message thường được dùng.

Mỗi commit chỉ nên có một trách nhiệm duy nhất. Nguyên tắc Single Responsibility (S trong SOLID) là một nguyên tắc cơ bản trong thiết kế phần mềm. Tạo Git commit là một trong nhiều tính huống khác ta có thể áp dụng nguyên tắc này. Mỗi commit chỉ nên lưu lại những thay đổi nhỏ, có liên quan trực tiếp đến nhau. Thay vì tạo ra một commit với rất nhiều thay đổi (hàng trăm dòng code và hàng chục file được thay đổi), ta nên chia ra thành nhiều commit nhỏ. Điều này giúp dễ quan sát các thay đổi, và việc quay lại một trạng thái trước đây cũng dễ dàng hơn vì lịch sử đã được chia nhỏ.

Tránh commit những thay đổi không cần thiết, hoặc không nên được lưu lại. Giả sử rằng trong dự án của bạn cần lưu lại những cấu hình cho việc kết nối vào cơ sở dữ liệu, hoặc là khoá bí mật dùng trong việc mã hoá thông tin,… Những thông tin này tuyệt đối không được lưu lại với Git, vì một khi bạn đưa những thông tin này lên một remote repository công khai, bất cứ ai cũng có thể xem được và truy cập vào cơ sở dữ liệu của bạn,... Ngoài ra, còn những thông tin khác như các thành phần phụ thuộc của dự án cũng không nên được lưu lại, ví dụ như npm_modules khi làm việc với JavaScript, hoặc venv khi làm việc với Python.

1.4. Các tính năng khác của Git

hooks. Hooks là một tính năng rất mạnh mẽ của Git. Với tính năng này, ta có thể thêm các hành động tuỳ chỉnh vào từng giai đoạn làm việc với Git, ví dụ như trước và sau khi commit/push,... Thường dùng nhất là pre-commit hook, được chạy trước mỗi commit. Thậm chí còn có một framework chuyên để quản lý chúng. Pre-commit hooks thường được sử dụng để format code theo một chuẩn chung (black/ prettier), chạy các chương trình phân tích tĩnh (static analysis) như mypyeslint,... để giảm thiểu lỗi.

annotate/blame. Khi sử dụng câu lệnh này, với mỗi dòng trong một file, ta có thể biết được người gần nhất sửa dòng này là ai, và sửa vào lúc nào. Các kỹ sư ở Got It rất hay dùng để blame nhau, đúng như tên gọi của câu lệnh, ví dụ như: Chỗ này chú J, chú K viết khó đọc thế, chỗ này ai viết mà tinh tế thế, hoá ra là anh M.

cherry pick. Khi dùng câu lệnh git merge để hợp nhất hai nhánh lại làm một, ta sẽ mang tất cả commit của nhánh tính năng vào nhánh chính. Tuy nhiên, không phải lúc nào ta cũng muốn làm vậy, chẳng hạn như khi cần deploy và test trước một số thay đổi chọn lọc từ nhánh tính năng. Đây là lúc ta sử dụng đến câu lệnh cherry-pick để copy thay đổi từ những commit cần thiết sang một nhánh khác. Đơn vị nhỏ nhất có thể cherry pick là một commit, nên nếu commit quá lớn thì ta sẽ không thể chọn ra những thay đổi cần thiết.

Ngoài các tính năng kể trên, bạn đọc có thể tham khảo thêm các tính năng khác như submodule, stash, rebase, status, diff,…

Nguồn: GotIT