Ưu tiên tệp DAMP BUILD hơn tệp DRY
Nguyên tắc DRY – "Đừng lặp lại chính mình" – khuyến khích tính duy nhất bằng cách giới thiệu các khái niệm trừu tượng như biến và hàm để tránh tình trạng dư thừa trong mã.
Ngược lại, nguyên tắc DAMP – "Cụm từ mô tả và có ý nghĩa" – khuyến khích khả năng đọc thay vì tính độc đáo để giúp các tệp dễ hiểu và dễ duy trì hơn.
Tệp BUILD
không phải là mã mà là cấu hình. Các cấu hình này không được kiểm thử như mã, nhưng cần được con người và công cụ duy trì. Điều đó khiến DAMP phù hợp hơn với họ so với DRY.
Định dạng tệp BUILD.bazel
Việc định dạng tệp BUILD
tuân theo phương pháp tương tự như Go, trong đó một công cụ được chuẩn hoá sẽ xử lý hầu hết các vấn đề về định dạng.
Buildifier là một công cụ phân tích cú pháp và phát mã nguồn theo kiểu chuẩn. Do đó, mọi tệp BUILD
đều được định dạng theo cùng một cách tự động, giúp việc định dạng không phải là vấn đề trong quá trình xem xét mã. Điều này cũng giúp các công cụ dễ hiểu, chỉnh sửa và tạo tệp BUILD
hơn.
Định dạng tệp BUILD
phải khớp với đầu ra của buildifier
.
Ví dụ về định dạng
# Test code implementing the Foo controller.
package(default_testonly = True)
py_test(
name = "foo_test",
srcs = glob(["*.py"]),
data = [
"//data/production/foo:startfoo",
"//foo",
"//third_party/java/jdk:jdk-k8",
],
flaky = True,
deps = [
":check_bar_lib",
":foo_data_check",
":pick_foo_port",
"//pyglib",
"//testing/pybase",
],
)
Cấu trúc tệp
Đề xuất: Sử dụng thứ tự sau (mọi phần tử đều không bắt buộc):
Nội dung mô tả gói (một nhận xét)
Tất cả câu lệnh
load()
Hàm
package()
.Lệnh gọi đến quy tắc và macro
Buildifier phân biệt giữa một nhận xét độc lập và một nhận xét được đính kèm vào một phần tử. Nếu một nhận xét không được đính kèm vào một phần tử cụ thể, hãy sử dụng một dòng trống sau phần tử đó. Sự khác biệt này rất quan trọng khi thực hiện các thay đổi tự động (ví dụ: giữ lại hoặc xoá nhận xét khi xoá một quy tắc).
# Standalone comment (such as to make a section in a file)
# Comment for the cc_library below
cc_library(name = "cc")
Tham chiếu đến các mục tiêu trong gói hiện tại
Các tệp phải được tham chiếu theo đường dẫn tương ứng với thư mục gói (không bao giờ sử dụng tham chiếu lên, chẳng hạn như ..
). Các tệp được tạo phải có tiền tố là ":
" để cho biết rằng đó không phải là nguồn. Tệp nguồn không được có tiền tố là :
. Các quy tắc phải có tiền tố là :
. Ví dụ: giả sử x.cc
là một tệp nguồn:
cc_library(
name = "lib",
srcs = ["x.cc"],
hdrs = [":gen_header"],
)
genrule(
name = "gen_header",
srcs = [],
outs = ["x.h"],
cmd = "echo 'int x();' > $@",
)
Đặt tên mục tiêu
Tên mục tiêu phải mang tính mô tả. Nếu một mục tiêu chứa một tệp nguồn, thì mục tiêu đó thường phải có tên bắt nguồn từ nguồn đó (ví dụ: cc_library
cho chat.cc
có thể được đặt tên là chat
hoặc java_library
cho DirectMessage.java
có thể được đặt tên là direct_message
).
Mục tiêu cùng tên cho một gói (mục tiêu có cùng tên với thư mục chứa) phải cung cấp chức năng được mô tả bằng tên thư mục. Nếu không có mục tiêu nào như vậy, đừng tạo mục tiêu cùng tên.
Ưu tiên sử dụng tên ngắn khi tham chiếu đến một mục tiêu cùng tên (//x
thay vì //x:x
). Nếu bạn đang ở cùng một gói, hãy ưu tiên tham chiếu cục bộ (:x
thay vì //x
).
Tránh sử dụng tên mục tiêu "đã đặt trước" có ý nghĩa đặc biệt. Các tên này bao gồm all
, __pkg__
và __subpackages__
. Các tên này có ngữ nghĩa đặc biệt và có thể gây nhầm lẫn cũng như hành vi không mong muốn khi được sử dụng.
Nếu không có quy ước chung của nhóm, sau đây là một số đề xuất không ràng buộc được sử dụng rộng rãi tại Google:
- Nhìn chung, hãy sử dụng "snake_case"
- Đối với
java_library
có mộtsrc
, điều này có nghĩa là sử dụng tên không giống với tên tệp mà không có đuôi - Đối với quy tắc
*_binary
và*_test
của Java, hãy sử dụng "Upper CamelCase". Điều này cho phép tên mục tiêu khớp với một trong cácsrc
. Đối vớijava_test
, điều này cho phép suy ra thuộc tínhtest_class
từ tên của mục tiêu.
- Đối với
- Nếu có nhiều biến thể của một mục tiêu cụ thể, hãy thêm hậu tố để phân biệt (chẳng hạn như.
:foo_dev
,:foo_prod
hoặc:bar_x86
,:bar_x64
) - Hậu tố
_test
nhắm đến_test
,_unittest
,Test
hoặcTests
- Tránh sử dụng các hậu tố vô nghĩa như
_lib
hoặc_library
(trừ phi cần thiết để tránh xung đột giữa mục tiêu_library
và_binary
tương ứng) - Đối với các mục tiêu liên quan đến proto:
- Mục tiêu
proto_library
phải có tên kết thúc bằng_proto
- Các quy tắc
*_proto_library
dành riêng cho ngôn ngữ phải khớp với proto cơ bản nhưng thay thế_proto
bằng hậu tố dành riêng cho ngôn ngữ, chẳng hạn như:cc_proto_library
:_cc_proto
java_proto_library
:_java_proto
java_lite_proto_library
:_java_proto_lite
- Mục tiêu
Chế độ hiển thị
Phạm vi hiển thị phải được thu hẹp nhất có thể, trong khi vẫn cho phép truy cập bằng các bài kiểm thử và phần phụ thuộc đảo ngược. Sử dụng __pkg__
và __subpackages__
khi thích hợp.
Tránh đặt gói default_visibility
thành //visibility:public
.
Bạn chỉ nên đặt //visibility:public
riêng lẻ cho các mục tiêu trong API công khai của dự án. Đây có thể là các thư viện được thiết kế để các dự án bên ngoài phụ thuộc vào hoặc các tệp nhị phân có thể được sử dụng trong quá trình xây dựng của một dự án bên ngoài.
Phần phụ thuộc
Các phần phụ thuộc phải được giới hạn ở các phần phụ thuộc trực tiếp (các phần phụ thuộc mà nguồn cần có trong quy tắc). Không liệt kê các phần phụ thuộc bắc cầu.
Các phần phụ thuộc cục bộ của gói phải được liệt kê trước và được tham chiếu theo cách tương thích với phần Tham chiếu đến các mục tiêu trong gói hiện tại ở trên (không phải theo tên gói tuyệt đối).
Ưu tiên liệt kê các phần phụ thuộc trực tiếp dưới dạng một danh sách duy nhất. Việc đặt các phần phụ thuộc "phổ biến" của một số mục tiêu vào một biến sẽ làm giảm khả năng bảo trì, khiến các công cụ không thể thay đổi các phần phụ thuộc của một mục tiêu và có thể dẫn đến các phần phụ thuộc không được sử dụng.
Glob
Cho biết "không có mục tiêu" bằng []
. Đừng sử dụng glob không khớp với bất kỳ giá trị nào: loại glob này dễ gặp lỗi và khó nhận biết hơn so với danh sách trống.
Lặp lại
Không sử dụng glob đệ quy để so khớp tệp nguồn (ví dụ: glob(["**/*.java"])
).
Glob đệ quy khiến các tệp BUILD
khó lý giải vì chúng bỏ qua các thư mục con chứa tệp BUILD
.
Glob đệ quy thường kém hiệu quả hơn so với việc có một tệp BUILD
cho mỗi thư mục với biểu đồ phần phụ thuộc được xác định giữa các tệp này vì điều này cho phép lưu vào bộ nhớ đệm từ xa và song song tốt hơn.
Bạn nên tạo một tệp BUILD
trong mỗi thư mục và xác định biểu đồ phần phụ thuộc giữa các thư mục đó.
Không đệ quy
Các glob không đệ quy thường được chấp nhận.
Tránh dùng cú pháp liệt kê
Tránh sử dụng câu lệnh liệt kê ở cấp cao nhất của tệp BUILD.bazel
.
Tự động hoá các lệnh gọi lặp lại bằng cách tạo từng mục tiêu được đặt tên bằng một quy tắc cấp cao nhất hoặc lệnh gọi macro riêng biệt. Đặt cho mỗi phần tử một tham số name
ngắn để rõ ràng.
Hàm liệt kê giúp giảm thiểu những vấn đề sau:
- Khả năng bảo trì. Rất khó hoặc không thể để người bảo trì và các thay đổi tự động trên quy mô lớn cập nhật chính xác các biểu thức comprehension danh sách.
- Khả năng khám phá. Vì mẫu không có tham số
name
, nên rất khó tìm thấy quy tắc theo tên.
Một ứng dụng phổ biến của mẫu hiểu biết về danh sách là tạo kiểm thử. Ví dụ:
[[java_test(
name = "test_%s_%s" % (backend, count),
srcs = [ ... ],
deps = [ ... ],
...
) for backend in [
"fake",
"mock",
]] for count in [
1,
10,
]]
Bạn nên sử dụng các phương án thay thế đơn giản hơn. Ví dụ: xác định một macro tạo một chương trình kiểm thử và gọi chương trình kiểm thử đó cho mỗi name
cấp cao nhất:
my_java_test(name = "test_fake_1",
...)
my_java_test(name = "test_fake_10",
...)
...
Không sử dụng biến phần phụ thuộc
Không sử dụng biến danh sách để đóng gói các phần phụ thuộc phổ biến:
COMMON_DEPS = [
"//d:e",
"//x/y:z",
]
cc_library(name = "a",
srcs = ["a.cc"],
deps = COMMON_DEPS + [ ... ],
)
cc_library(name = "b",
srcs = ["b.cc"],
deps = COMMON_DEPS + [ ... ],
)
Tương tự, đừng sử dụng mục tiêu thư viện có exports
để nhóm các phần phụ thuộc.
Thay vào đó, hãy liệt kê các phần phụ thuộc riêng cho từng mục tiêu:
cc_library(name = "a",
srcs = ["a.cc"],
deps = [
"//a:b",
"//x/y:z",
...
],
)
cc_library(name = "b",
srcs = ["b.cc"],
deps = [
"//a:b",
"//x/y:z",
...
],
)
Hãy để Gazelle và các công cụ khác duy trì các tệp đó. Sẽ có sự lặp lại, nhưng bạn sẽ không phải suy nghĩ về cách quản lý các phần phụ thuộc.
Ưu tiên chuỗi cố định
Mặc dù Starlark cung cấp các toán tử chuỗi để nối (+
) và định dạng (%
), nhưng hãy sử dụng các toán tử này một cách thận trọng. Bạn nên tách các phần chuỗi phổ biến để biểu thức ngắn gọn hơn hoặc ngắt các dòng dài. Tuy nhiên,
Khó đọc nhanh các giá trị chuỗi bị phân tách.
Các công cụ tự động như buildozer và Tìm kiếm mã gặp sự cố khi tìm và cập nhật chính xác các giá trị khi các giá trị bị phân tách.
Trong tệp
BUILD
, khả năng đọc dễ dàng quan trọng hơn việc tránh lặp lại (xem phần DAMP so với DRY).Hướng dẫn về phong cách này nhắc nhở bạn không được tách các chuỗi có giá trị nhãn và cho phép rõ ràng các dòng dài.
Buildifier tự động hợp nhất các chuỗi được nối khi phát hiện ra rằng đó là nhãn.
Do đó, hãy ưu tiên các chuỗi rõ ràng, cố định thay vì các chuỗi được nối hoặc định dạng, đặc biệt là trong các thuộc tính loại nhãn như name
và deps
. Ví dụ: mảnh BUILD
này:
NAME = "foo"
PACKAGE = "//a/b"
proto_library(
name = "%s_proto" % NAME,
deps = [PACKAGE + ":other_proto"],
alt_dep = "//surprisingly/long/chain/of/package/names:" +
"extravagantly_long_target_name",
)
sẽ tốt hơn nếu viết lại thành
proto_library(
name = "foo_proto",
deps = ["//a/b:other_proto"],
alt_dep = "//surprisingly/long/chain/of/package/names:extravagantly_long_target_name",
)
Giới hạn các ký hiệu được xuất bởi mỗi tệp .bzl
Giảm thiểu số lượng ký hiệu (quy tắc, macro, hằng số, hàm) do mỗi tệp .bzl
(Starlark) công khai xuất. Bạn chỉ nên xuất nhiều ký hiệu trong một tệp nếu chắc chắn các ký hiệu đó sẽ được sử dụng cùng nhau. Nếu không, hãy chia tệp này thành nhiều tệp .bzl
, mỗi tệp có một bzl_library riêng.
Quá nhiều biểu tượng có thể khiến tệp .bzl
phát triển thành "thư viện" rộng lớn của các biểu tượng, khiến các thay đổi đối với một tệp buộc Bazel phải tạo lại nhiều mục tiêu.
Các quy ước khác
Sử dụng chữ hoa và dấu gạch dưới để khai báo hằng số (chẳng hạn như
GLOBAL_CONSTANT
), sử dụng chữ thường và dấu gạch dưới để khai báo biến (chẳng hạn nhưmy_variable
).Không bao giờ được tách nhãn, ngay cả khi nhãn dài hơn 79 ký tự. Nhãn phải là chuỗi cố định bất cứ khi nào có thể. Lý do: Giúp tìm và thay thế dễ dàng. Điều này cũng giúp cải thiện khả năng đọc.
Giá trị của thuộc tính tên phải là một chuỗi hằng số cố định (ngoại trừ trong macro). Lý do: Các công cụ bên ngoài sử dụng thuộc tính tên để tham chiếu một quy tắc. Chúng cần tìm thấy các quy tắc mà không cần phải diễn giải mã.
Khi đặt thuộc tính kiểu boolean, hãy sử dụng giá trị boolean chứ không phải giá trị số nguyên. Vì lý do cũ, các quy tắc vẫn chuyển đổi số nguyên thành boolean nếu cần, nhưng bạn không nên làm như vậy. Lý do:
flaky = 1
có thể bị hiểu nhầm là "xoá mục tiêu này bằng cách chạy lại một lần".flaky = True
nói rõ ràng rằng "kiểm thử này không ổn định".
Điểm khác biệt với hướng dẫn về kiểu Python
Mặc dù khả năng tương thích với hướng dẫn về phong cách Python là một mục tiêu, nhưng có một vài điểm khác biệt:
Không có giới hạn nghiêm ngặt về độ dài dòng. Các nhận xét dài và chuỗi dài thường được chia thành 79 cột, nhưng không bắt buộc. Bạn không nên thực thi quy tắc này trong quy trình xem xét mã hoặc tập lệnh gửi trước. Lý do: Nhãn có thể dài và vượt quá giới hạn này. Thông thường, các tệp
BUILD
được tạo hoặc chỉnh sửa bằng các công cụ, điều này không phù hợp với giới hạn độ dài dòng.Không hỗ trợ nối chuỗi ngầm ẩn. Sử dụng toán tử
+
. Lý do: TệpBUILD
chứa nhiều danh sách chuỗi. Bạn rất dễ quên dấu phẩy, dẫn đến kết quả hoàn toàn khác. Điều này đã gây ra nhiều lỗi trong quá khứ. Xem thêm cuộc thảo luận này.Sử dụng khoảng trắng xung quanh dấu
=
cho các đối số từ khoá trong quy tắc. Lý do: Các đối số được đặt tên xuất hiện thường xuyên hơn nhiều so với trong Python và luôn nằm trên một dòng riêng biệt. Dấu cách giúp cải thiện khả năng đọc. Quy ước này đã tồn tại từ lâu và không đáng để sửa đổi tất cả tệpBUILD
hiện có.Theo mặc định, hãy sử dụng dấu ngoặc kép cho chuỗi. Lý do: Điều này không được chỉ định trong hướng dẫn về phong cách Python, nhưng bạn nên sử dụng nhất quán. Vì vậy, chúng tôi quyết định chỉ sử dụng chuỗi có dấu ngoặc kép. Nhiều ngôn ngữ sử dụng dấu ngoặc kép cho chuỗi ký tự.
Sử dụng một dòng trống giữa hai định nghĩa cấp cao nhất. Lý do: Cấu trúc của tệp
BUILD
không giống như tệp Python thông thường. Tệp này chỉ có các câu lệnh cấp cao nhất. Việc sử dụng một dòng trống duy nhất sẽ giúp tệpBUILD
ngắn hơn.