Tại sao nên có một hệ thống xây dựng?

7.3 · 7.2 · 7.1 · 7.0 · 6.5

Trang này thảo luận về hệ thống xây dựng là gì, chức năng của hệ thống xây dựng, lý do bạn nên sử dụng hệ thống xây dựng, cũng như lý do vì sao trình biên dịch và tập lệnh bản dựng không phải là lựa chọn tốt nhất khi tổ chức của bạn bắt đầu mở rộng quy mô. API này dành cho các nhà phát triển không có nhiều kinh nghiệm về hệ thống xây dựng.

Hệ thống xây dựng là gì?

Về cơ bản, mọi hệ thống xây dựng đều có mục đích đơn giản: chúng biến đổi mã nguồn do các kỹ sư viết thành các tệp nhị phân có thể thực thi mà máy có thể đọc được. Hệ thống xây dựng không chỉ dành cho mã do con người tạo; chúng còn cho phép máy móc tự động tạo bản dựng, dù là để kiểm thử hay phát hành chính thức. Trong một tổ chức có hàng nghìn kỹ sư, thông thường thì hầu hết các bản dựng đều được kích hoạt tự động thay vì do kỹ sư trực tiếp kích hoạt.

Tôi không thể chỉ sử dụng một trình biên dịch phải không?

Nhu cầu xây dựng một hệ thống xây dựng có thể không rõ ràng ngay lập tức. Hầu hết các kỹ sư không sử dụng hệ thống xây dựng trong khi học lập trình: hầu hết bắt đầu bằng cách gọi các công cụ như gcc hoặc javac trực tiếp từ dòng lệnh hoặc tương đương trong môi trường phát triển tích hợp (IDE). Chỉ cần tất cả mã nguồn đều nằm trong cùng một thư mục, thì lệnh như sau sẽ hoạt động tốt:

javac *.java

Thao tác này sẽ hướng dẫn trình biên dịch Java lấy mọi tệp nguồn Java trong thư mục hiện tại và biến tệp đó thành tệp lớp nhị phân. Trong trường hợp đơn giản nhất, đây là tất cả những gì bạn cần.

Tuy nhiên, ngay khi mã mở rộng, các chức năng sẽ bắt đầu. javac đủ thông minh để tìm trong các thư mục con của thư mục hiện tại để tìm mã cần nhập. Tuy nhiên, công cụ này không có cách nào để tìm mã được lưu trữ trong các phần khác của hệ thống tệp (có thể là thư viện được một số dự án dùng chung). Nó chỉ biết cách tạo mã Java. Các hệ thống lớn thường liên quan đến nhiều phần được viết bằng nhiều ngôn ngữ lập trình với mạng lưới các phần phụ thuộc giữa các phần đó, tức là không có trình biên dịch nào cho một ngôn ngữ đơn lẻ có thể xây dựng được toàn bộ hệ thống.

Khi bạn xử lý mã từ nhiều ngôn ngữ hoặc nhiều đơn vị biên dịch, việc tạo mã không còn là một quy trình một bước nữa. Bây giờ, bạn phải đánh giá xem mã của mình phụ thuộc vào phần nào và xây dựng các phần đó theo thứ tự thích hợp, có thể sử dụng một bộ công cụ khác nhau cho mỗi phần. Nếu có phần phụ thuộc nào thay đổi, bạn phải lặp lại quy trình này để tránh phụ thuộc vào các tệp nhị phân cũ. Đối với cơ sở mã có kích thước vừa phải, quá trình này nhanh chóng trở nên tẻ nhạt và dễ xảy ra lỗi.

Trình biên dịch cũng không biết gì về cách xử lý các phần phụ thuộc bên ngoài, chẳng hạn như tệp JAR của bên thứ ba trong Java. Nếu không có hệ thống xây dựng, bạn có thể quản lý việc này bằng cách tải phần phụ thuộc xuống từ Internet, dán phần phụ thuộc đó vào thư mục lib trên ổ đĩa cứng và định cấu hình trình biên dịch để đọc các thư viện từ thư mục đó. Theo thời gian, sẽ rất khó để duy trì bản cập nhật, phiên bản và nguồn của các phần phụ thuộc bên ngoài này.

Còn tập lệnh shell thì sao?

Giả sử rằng dự án sở thích của bạn bắt đầu đủ đơn giản để bạn có thể xây dựng dự án đó chỉ bằng một trình biên dịch, nhưng bạn bắt đầu gặp phải một số vấn đề được mô tả trước đó. Có thể bạn vẫn không nghĩ mình cần một hệ thống xây dựng và có thể tự động hoá các phần tẻ nhạt bằng cách sử dụng một số tập lệnh shell đơn giản để xử lý việc tạo các phần theo đúng thứ tự. Việc này sẽ hữu ích trong một thời gian, nhưng sẽ sớm gặp phải nhiều vấn đề hơn nữa:

  • Việc này trở nên tẻ nhạt. Khi hệ thống trở nên phức tạp hơn, bạn bắt đầu dành gần như nhiều thời gian để xử lý tập lệnh bản dựng như trên mã thực. Việc gỡ lỗi tập lệnh shell rất khó khăn, với ngày càng nhiều bản hack được xếp chồng lên nhau.

  • Chạy chậm. Để đảm bảo bạn không vô tình dựa vào các thư viện cũ, bạn có tập lệnh bản dựng tạo mọi phần phụ thuộc theo thứ tự mỗi khi chạy tập lệnh đó. Bạn nghĩ đến việc thêm một số logic để phát hiện phần nào cần được tạo lại, nhưng điều đó nghe có vẻ phức tạp và dễ xảy ra lỗi đối với một tập lệnh. Hoặc bạn suy nghĩ về việc chỉ định những phần nào cần được tạo lại mỗi lần, nhưng sau đó, bạn sẽ quay lại hình vuông.

  • Tin vui là đã đến lúc phát hành! Tốt hơn là bạn nên tìm ra tất cả các đối số cần truyền đến lệnh jar để tạo bản dựng cuối cùng. Và hãy nhớ cách tải lên và đẩy tệp đó vào kho lưu trữ trung tâm. Đồng thời, tạo và đẩy các bản cập nhật tài liệu, đồng thời gửi thông báo cho người dùng. Hmm, có lẽ cần phải có một tập lệnh khác...

  • Thảm hoạ! Ổ đĩa cứng của bạn gặp sự cố và giờ đây, bạn cần tạo lại toàn bộ hệ thống. Bạn đã đủ thông minh để giữ tất cả tệp nguồn của mình trong chế độ kiểm soát phiên bản, nhưng còn những thư viện bạn đã tải xuống thì sao? Bạn có thể tìm lại tất cả các tệp đó và đảm bảo các tệp đó có cùng phiên bản với lần đầu tải xuống không? Tập lệnh của bạn có thể phụ thuộc vào các công cụ cụ thể được cài đặt ở những vị trí cụ thể – bạn có thể khôi phục chính môi trường đó để tập lệnh hoạt động lại không? Còn tất cả các biến môi trường mà bạn đã đặt từ lâu để trình biên dịch hoạt động đúng cách nhưng sau đó lại quên mất?

  • Mặc dù gặp vấn đề, nhưng dự án của bạn đã thành công đến mức bạn có thể bắt đầu tuyển dụng thêm kỹ sư. Giờ đây, bạn nhận ra rằng không cần phải có sự cố nào xảy ra thì các vấn đề trước đó mới phát sinh. Bạn cần phải trải qua cùng một quy trình khởi động đau đớn mỗi khi có một nhà phát triển mới gia nhập nhóm. Mặc dù đã nỗ lực hết sức, nhưng hệ thống của mỗi người vẫn có những điểm khác biệt nhỏ. Thông thường, những tính năng hoạt động trên máy của người này lại không hoạt động trên máy của người khác, và mỗi lần phải mất vài giờ đường dẫn công cụ gỡ lỗi hoặc phiên bản thư viện để tìm ra điểm khác biệt.

  • Bạn quyết định rằng mình cần tự động hoá hệ thống xây dựng. Về lý thuyết, việc này đơn giản như mua một máy tính mới và thiết lập để chạy tập lệnh bản dựng mỗi tối bằng cron. Bạn vẫn cần phải trải qua quy trình thiết lập rườm rà, nhưng giờ đây, bạn không có lợi ích của bộ não con người có thể phát hiện và giải quyết các vấn đề nhỏ. Bây giờ, mỗi sáng khi truy cập, bạn sẽ thấy bản dựng tối qua không hoạt động được vì hôm qua, một nhà phát triển đã thực hiện một thay đổi có tác dụng với hệ thống của họ nhưng không hoạt động trên hệ thống xây dựng tự động. Mỗi lần chỉ là một cách khắc phục đơn giản, nhưng điều này xảy ra thường xuyên đến mức bạn phải dành nhiều thời gian mỗi ngày để khám phá và áp dụng những cách khắc phục đơn giản này.

  • Các bản dựng sẽ càng chậm lại khi dự án tăng lên. Một ngày nọ, trong khi chờ một bản dựng hoàn tất, bạn nhìn chăm chú vào màn hình máy tính đang ở trạng thái rảnh của đồng nghiệp đang đi nghỉ phép và ước gì có cách nào để tận dụng tất cả sức mạnh tính toán bị lãng phí đó.

Bạn đã gặp phải một vấn đề kinh điển về quy mô. Đối với một nhà phát triển làm việc trên tối đa vài trăm dòng mã trong tối đa một hoặc hai tuần (có thể là toàn bộ kinh nghiệm cho đến nay của một nhà phát triển mới tốt nghiệp đại học), bạn chỉ cần một trình biên dịch. Tập lệnh có thể giúp bạn làm được nhiều việc hơn một chút. Tuy nhiên, ngay khi bạn cần điều phối giữa nhiều nhà phát triển và máy của họ, ngay cả một tập lệnh bản dựng hoàn hảo cũng không đủ vì rất khó để tính đến những khác biệt nhỏ trong các máy đó. Tại thời điểm này, cách tiếp cận đơn giản này sẽ bị hỏng và đã đến lúc đầu tư vào một hệ thống xây dựng thực tế.