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

Trang này thảo luận về hệ thống xây dựng, 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 và lý do trình biên dịch và tập lệnh xây 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ô. Trang này dành cho những nhà phát triển chưa 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, tất cả hệ thống xây dựng đều có một mục đích đơn giản: chuyển đổi mã nguồn do kỹ sư viết thành 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 viết mà còn cho phép máy tự động tạo bản dựng, dù là để kiểm thử hay phát hành cho quá trình sản xuất. Trong một tổ chức có hàng nghìn kỹ sư, thông thường hầu hết các bản dựng đều được kích hoạt tự động thay vì trực tiếp do kỹ sư kích hoạt.

Tôi không thể chỉ sử dụng trình biên dịch sao?

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

javac *.java

Lệnh này 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à chuyể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 vấn đề phức tạp 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 nhằm tìm mã để nhập. Nhưng trình biên dịch này không có cách nào tìm thấy mã được lưu trữ trong các phần khác của hệ thống tệp (có thể là một thư viện được chia sẻ bởi nhiều dự án). Trình biên dịch này cũng chỉ biết cách xây dựng mã Java. Các hệ thống lớn thường bao gồm 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 đó, nghĩa là không có trình biên dịch nào cho một ngôn ngữ duy nhất có thể xây dựng 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 xây dựng mã không còn là quy trình một bước nữa. Giờ đây, bạn phải đánh giá những gì mã của bạn phụ thuộc và xây dựng các phần đó theo đúng thứ tự, có thể sử dụng một bộ công cụ khác cho từng phần. Nếu có bất kỳ 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 lỗi thời. Đối với cơ sở mã có kích thước vừa phải, quy 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 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 vào thư mục lib trên ổ cứng và định cấu hình trình biên dịch để đọc thư viện từ thư mục đó. Theo thời gian, bạn khó có thể duy trì các 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ử 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 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ĩ rằng mình cần hệ thống xây dựng và có thể tự động hoá các phần tẻ nhạt bằng một số tập lệnh shell đơn giản giúp xây dựng mọi thứ theo đúng thứ tự. Điều này giúp ích trong một thời gian, nhưng chẳng bao lâu bạn bắt đầu gặp phải nhiều vấn đề hơn:

  • Trở nên tẻ nhạt. Khi hệ thống của bạn trở nên phức tạp hơn, bạn bắt đầu dành gần như cùng một khoảng thời gian để làm việc trên tập lệnh xây 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 giải pháp tạm thời được xếp chồng lên nhau.

  • Chậm. Để đảm bảo rằng bạn không vô tình dựa vào các thư viện lỗi thời, bạn phải xây dựng tập lệnh xây dựng 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 những phần cần được xây dựng 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 nghĩ đến việc chỉ định những phần cần được xây dựng lại mỗi lần, nhưng sau đó bạn lại quay về điểm xuất phát.

  • Tin vui: đã đến lúc phát hành! Tốt hơn hết là bạn nên tìm hiểu 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à nhớ cách tải bản dựng đó lên và đẩy bản dựng đó vào kho lưu trữ trung tâm. Và xây dựng 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ẽ bạn cần một tập lệnh khác...

  • Thảm hoạ! Ổ cứng của bạn gặp sự cố và giờ bạn cần tạo lại toàn bộ hệ thống. Bạn đủ thông minh để lưu giữ tất cả tệp nguồn trong hệ thống kiểm soát phiên bản, nhưng còn những thư viện mà bạn đã tải xuống thì sao? Bạn có thể tìm lại tất cả các thư viện đó và đảm bảo rằng chúng có cùng phiên bản như khi bạn tải xuống lần đầu 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 cùng một môi trường đó để tập lệnh hoạt động trở 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 bình thường rồi quên mất thì sao?

  • Bất chấp các vấn đề, dự án của bạn đủ thành công để bạn có thể bắt đầu thuê thêm kỹ sư. Giờ đây, bạn nhận ra rằng không cần phải có thảm hoạ thì các vấn đề trước đó mới nảy sinh – bạn cần phải trải qua quy trình khởi động tẻ nhạt tương tự mỗi khi một nhà phát triển mới gia nhập nhóm của bạn. Và bất chấp những nỗ lực tốt nhất của bạn, vẫn có những khác biệt nhỏ trong hệ thống của mỗi người. Thường thì những gì hoạt động trên máy của một người lại không hoạt động trên máy của người khác và mỗi lần bạn phải mất vài giờ để gỡ lỗi đường dẫn công cụ hoặc phiên bản thư viện để tìm ra sự 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, điều này đơn giản như việc mua một máy tính mới và thiết lập máy tính đó để chạy tập lệnh xây dựng mỗi đêm bằng cron. Bạn vẫn cần phải trải qua quy trình thiết lập tẻ nhạt, nhưng giờ đây bạn không có lợi thế của bộ não con người có thể phát hiện và giải quyết các vấn đề nhỏ. Giờ đây, mỗi sáng khi bạn đến, bạn thấy rằng bản dựng đêm qua không thành công vì hôm qua một nhà phát triển đã thực hiện thay đổi hoạt động trên 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 đều là một bản sửa lỗi đơn giản, nhưng điều này xảy ra quá thường xuyên khiến bạn phải dành nhiều thời gian mỗi ngày để khám phá và áp dụng các bản sửa lỗi đơn giản này.

  • Các bản dựng ngày càng chậm hơn khi dự án phát triển. Một ngày nọ, trong khi chờ bản dựng hoàn tất, bạn buồn bã nhìn vào màn hình nền không hoạt động của đồng nghiệp đang đi nghỉ và ước rằng có cách 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 cấp dưới vừa 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 tiến xa hơn một chút. Nhưng ngay khi bạn cần phối hợp giữa nhiều nhà phát triển và máy của họ, ngay cả một tập lệnh xây 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, phương pháp đơn giản này sẽ bị phá vỡ và đã đến lúc đầu tư vào một hệ thống xây dựng thực.