เลือกใช้ไฟล์ DAMP BUILD แทน DRY
หลักการ DRY ("Don't Repeat Yourself") ส่งเสริมความไม่ซ้ำซ้อนโดยการนำการแยกย่อย เช่น ตัวแปรและฟังก์ชัน มาใช้เพื่อหลีกเลี่ยงความซ้ำซ้อนในโค้ด
ในทางตรงกันข้าม หลักการ DAMP ("Descriptive and Meaningful Phrases") ส่งเสริมความสามารถในการอ่านมากกว่าความไม่ซ้ำซ้อน เพื่อให้เข้าใจและดูแลรักษาไฟล์ได้ง่ายขึ้น
ไฟล์ BUILD ไม่ใช่โค้ด แต่เป็นการกำหนดค่า จึงไม่ได้ทดสอบเหมือนโค้ด แต่ต้องได้รับการดูแลรักษาโดยบุคคลและเครื่องมือ ซึ่งทำให้ DAMP เหมาะสมกว่า DRY
การจัดรูปแบบไฟล์ BUILD.bazel
การจัดรูปแบบไฟล์ BUILD เป็นไปตามแนวทางเดียวกับ Go ซึ่งเครื่องมือมาตรฐานจะจัดการปัญหาการจัดรูปแบบส่วนใหญ่
Buildifier เป็นเครื่องมือที่แยกวิเคราะห์และ
แสดงซอร์สโค้ดในรูปแบบมาตรฐาน ดังนั้นไฟล์ BUILD ทุกไฟล์จึงได้รับการจัดรูปแบบด้วยวิธีอัตโนมัติแบบเดียวกัน ซึ่งทำให้การจัดรูปแบบไม่ใช่ปัญหาในระหว่างการตรวจสอบโค้ด นอกจากนี้ยังช่วยให้เครื่องมือต่างๆ เข้าใจ แก้ไข และสร้างไฟล์ BUILD ได้ง่ายขึ้นด้วย
การจัดรูปแบบไฟล์ BUILD ต้องตรงกับเอาต์พุตของ buildifier
ตัวอย่างการจัดรูปแบบ
# 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",
],
)
โครงสร้างของไฟล์
คำแนะนำ: ใช้ลำดับต่อไปนี้ (ทุกองค์ประกอบไม่บังคับ)
คำอธิบายแพ็กเกจ (ความคิดเห็น)
คำสั่ง
load()ทั้งหมดฟังก์ชัน
package()การเรียกกฎและมาโคร
Buildifier แยกความแตกต่างระหว่างความคิดเห็นแบบสแตนด์อโลนกับความคิดเห็นที่แนบมากับองค์ประกอบ หากความคิดเห็นไม่ได้แนบมากับองค์ประกอบที่เฉพาะเจาะจง ให้ใช้บรรทัดว่างหลังความคิดเห็น การแยกความแตกต่างนี้มีความสำคัญเมื่อทำการเปลี่ยนแปลงอัตโนมัติ (เช่น การเก็บหรือนำความคิดเห็นออกเมื่อลบกฎ)
# Standalone comment (such as to make a section in a file)
# Comment for the cc_library below
cc_library(name = "cc")
การอ้างอิงเป้าหมายในแพ็กเกจปัจจุบัน
ควรอ้างอิงไฟล์ตามเส้นทางที่สัมพันธ์กับไดเรกทอรีแพ็กเกจ (โดยไม่ใช้การอ้างอิงขึ้น เช่น ..) ไฟล์ที่สร้างขึ้นควรมีคำนำหน้าเป็น ":" เพื่อระบุว่าไม่ใช่ไฟล์ต้นฉบับ ไฟล์ต้นฉบับไม่ควรมีคำนำหน้าเป็น : กฎควรมีคำนำหน้าเป็น : ตัวอย่างเช่น สมมติว่า x.cc เป็นไฟล์ต้นฉบับ
cc_library(
name = "lib",
srcs = ["x.cc"],
hdrs = [":gen_header"],
)
genrule(
name = "gen_header",
srcs = [],
outs = ["x.h"],
cmd = "echo 'int x();' > $@",
)
การตั้งชื่อเป้าหมาย
ชื่อเป้าหมายควรสื่อความหมาย หากเป้าหมายมีไฟล์ต้นฉบับ 1 ไฟล์
โดยทั่วไปเป้าหมายควรมีชื่อที่ได้จากไฟล์ต้นฉบับนั้น (เช่น
cc_library สำหรับ chat.cc อาจชื่อ chat หรือ java_library สำหรับ
DirectMessage.java อาจชื่อ direct_message)
เป้าหมายที่มีชื่อเดียวกับแพ็กเกจ (เป้าหมายที่มีชื่อเดียวกับไดเรกทอรีที่บรรจุอยู่) ควรมีฟังก์ชันการทำงานตามที่อธิบายไว้ในชื่อไดเรกทอรี หากไม่มีเป้าหมายดังกล่าว ให้สร้างเป้าหมายที่มีชื่อเดียวกับแพ็กเกจ
ควรใช้ชื่อย่อเมื่ออ้างอิงเป้าหมายที่มีชื่อเดียวกับแพ็กเกจ (//x
แทน //x:x) หากอยู่ในแพ็กเกจเดียวกัน ให้ใช้การอ้างอิงในเครื่อง (:x แทน //x)
หลีกเลี่ยงการใช้ชื่อเป้าหมายที่ "สงวนไว้" ซึ่งมีความหมายพิเศษ ซึ่งรวมถึง all, __pkg__ และ __subpackages__ ชื่อเหล่านี้มีความหมายพิเศษและอาจทำให้เกิดความสับสนและพฤติกรรมที่ไม่คาดคิดเมื่อนำไปใช้
หากไม่มีข้อตกลงของทีมที่ใช้กันทั่วไป นี่คือคำแนะนำที่ไม่ผูกมัดซึ่งใช้กันอย่างแพร่หลายใน Google
- โดยทั่วไปให้ใช้ "snake_case"
- สำหรับ
java_libraryที่มีsrc1 รายการ หมายความว่าให้ใช้ชื่อที่ไม่เหมือนกับชื่อไฟล์โดยไม่มีนามสกุล - สำหรับกฎ
*_binaryและ*_testของ Java ให้ใช้ "Upper CamelCase" ซึ่งจะช่วยให้ชื่อเป้าหมายตรงกับsrcรายการใดรายการหนึ่ง สำหรับjava_testจะทำให้ระบบอนุมานแอตทริบิวต์test_classจากชื่อเป้าหมายได้
- สำหรับ
- หากเป้าหมายหนึ่งๆ มีหลายตัวแปร ให้เพิ่มคำต่อท้ายเพื่อแยกความแตกต่าง (เช่น
:foo_dev,:foo_prodหรือ:bar_x86,:bar_x64) - เพิ่มคำต่อท้าย
_test,_unittest,TestหรือTestsให้เป้าหมาย_test - หลีกเลี่ยงคำต่อท้ายที่ไม่มีความหมาย เช่น
_libหรือ_library(ยกเว้นในกรณีที่จำเป็นเพื่อ หลีกเลี่ยงความขัดแย้งระหว่างเป้าหมาย_libraryกับเป้าหมาย_binaryที่เกี่ยวข้อง) - สำหรับเป้าหมายที่เกี่ยวข้องกับ Proto
- เป้าหมาย
proto_libraryควรมีชื่อที่ลงท้ายด้วย_proto - กฎ
*_proto_libraryที่เฉพาะเจาะจงตามภาษาควรตรงกับ Proto พื้นฐาน แต่ให้แทนที่_protoด้วยคำต่อท้ายที่เฉพาะเจาะจงตามภาษา เช่น:cc_proto_library:_cc_protojava_proto_library:_java_protojava_lite_proto_library:_java_proto_lite
- เป้าหมาย
การมองเห็น
ระดับการแชร์ควรมีขอบเขตที่แคบที่สุดเท่าที่จะทำได้ แต่ยังคงอนุญาตให้การทดสอบและการอ้างอิงย้อนกลับเข้าถึงได้ ใช้ __pkg__ และ __subpackages__ ตามความเหมาะสม
หลีกเลี่ยงการตั้งค่า default_visibility ของแพ็กเกจเป็น //visibility:public
ควรตั้งค่า //visibility:public เป็นรายบุคคลสำหรับเป้าหมายใน API สาธารณะของโปรเจ็กต์เท่านั้น ซึ่งอาจเป็นไลบรารีที่ออกแบบมาเพื่อให้โปรเจ็กต์ภายนอกอ้างอิง หรือไบนารีที่กระบวนการบิลด์ของโปรเจ็กต์ภายนอกอาจใช้
แท็กเริ่มการทำงาน
แท็กเริ่มการทำงานควรจำกัดไว้ที่แท็กเริ่มการทำงานโดยตรง (แท็กเริ่มการทำงานที่แหล่งที่มาระบุไว้ในกฎจำเป็นต้องใช้) อย่าระบุแท็กเริ่มการทำงานแบบทรานซิทีฟ
ควรระบุแท็กเริ่มการทำงานโดยตรงเป็นรายการเดียว การใส่แท็กเริ่มการทำงาน "ทั่วไป" ของเป้าหมายหลายรายการลงในตัวแปรจะลดความสามารถในการดูแลรักษา ทำให้เครื่องมือต่างๆ เปลี่ยนแท็กเริ่มการทำงานของเป้าหมายไม่ได้ และอาจทำให้เกิดแท็กเริ่มการทำงานที่ไม่ได้ใช้
Globs
ระบุ "ไม่มีเป้าหมาย" ด้วย [] อย่าใช้ Glob ที่ไม่ตรงกับสิ่งใดเลย เนื่องจากมีแนวโน้มที่จะเกิดข้อผิดพลาดมากกว่าและไม่ชัดเจนเท่ากับรายการว่าง
แบบเรียกซ้ำ
อย่าใช้ Globs แบบเรียกซ้ำเพื่อจับคู่ไฟล์ต้นฉบับ (เช่น
glob(["**/*.java"]))
Globs แบบเรียกซ้ำทำให้ไฟล์ BUILD เข้าใจยาก เนื่องจากจะข้ามไดเรกทอรีย่อยที่มีไฟล์ BUILD
โดยทั่วไป Globs แบบเรียกซ้ำจะมีประสิทธิภาพน้อยกว่าการมีไฟล์ BUILD ต่อไดเรกทอรีที่มีกราฟการพึ่งพาที่กำหนดไว้ระหว่างกัน เนื่องจากวิธีนี้ช่วยให้แคชระยะไกลและการทำงานแบบขนานมีประสิทธิภาพมากขึ้น
การสร้างไฟล์ BUILD ในแต่ละไดเรกทอรีและกำหนดกราฟทรัพยากร Dependency ระหว่างไฟล์เหล่านั้นถือเป็นแนวทางปฏิบัติที่ดี
แบบไม่เรียกซ้ำ
โดยทั่วไป Globs แบบไม่เรียกซ้ำเป็นที่ยอมรับ
หลีกเลี่ยงการใช้ List Comprehension
หลีกเลี่ยงการใช้ List Comprehension ที่ระดับบนสุดของไฟล์ BUILD.bazel
สร้างเป้าหมายที่มีชื่อแต่ละรายการด้วยกฎระดับบนสุดหรือการเรียกมาโครแยกกันเพื่อทำให้การเรียกซ้ำๆ เป็นไปโดยอัตโนมัติ กำหนดพารามิเตอร์ name สั้นๆ ให้แต่ละรายการเพื่อความชัดเจน
List Comprehension จะลดสิ่งต่อไปนี้
- ความสามารถในการดูแลรักษา ผู้ดูแลระบบและเครื่องมืออัตโนมัติขนาดใหญ่จะอัปเดต List Comprehension อย่างถูกต้องได้ยากหรือเป็นไปไม่ได้
- การค้นพบได้ เนื่องจากรูปแบบไม่มีพารามิเตอร์
nameจึงค้นหากฎตามชื่อได้ยาก
การใช้งานทั่วไปของรูปแบบ List Comprehension คือการสร้างการทดสอบ เช่น
[[java_test(
name = "test_%s_%s" % (backend, count),
srcs = [ ... ],
deps = [ ... ],
...
) for backend in [
"fake",
"mock",
]] for count in [
1,
10,
]]
เราขอแนะนำให้ใช้ตัวเลือกที่ง่ายกว่า เช่น กำหนดมาโครที่สร้างการทดสอบ 1 รายการ แล้วเรียกใช้มาโครนั้นสำหรับ name ระดับบนสุดแต่ละรายการ
my_java_test(name = "test_fake_1",
...)
my_java_test(name = "test_fake_10",
...)
...
อย่าใช้ตัวแปร Deps
อย่าใช้ตัวแปรรายการเพื่อห่อหุ้มแท็กเริ่มการทำงานทั่วไป
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 + [ ... ],
)
ในทำนองเดียวกัน อย่าใช้เป้าหมายไลบรารีที่มี
exports เพื่อจัดกลุ่มแท็กเริ่มการทำงาน
แต่ให้ระบุแท็กเริ่มการทำงานแยกกันสำหรับแต่ละเป้าหมาย
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",
...
],
)
ปล่อยให้ Gazelle และเครื่องมืออื่นๆ ดูแลรักษาแท็กเริ่มการทำงาน แม้จะมีการทำซ้ำ แต่คุณก็ไม่ต้องกังวลว่าจะจัดการแท็กเริ่มการทำงานอย่างไร
เลือกใช้สตริงตัวอักษร
แม้ว่า Starlark จะมีตัวดำเนินการสตริงสำหรับการเชื่อมต่อ (+) และการจัดรูปแบบ (%) แต่ให้ใช้ด้วยความระมัดระวัง คุณอาจต้องการแยกส่วนสตริงทั่วไปออกเพื่อให้การแสดงออกกระชับขึ้นหรือแบ่งบรรทัดยาวๆ อย่างไรก็ตาม
การอ่านค่าสตริงที่แบ่งออกเป็นส่วนๆ จะยากขึ้น
เครื่องมืออัตโนมัติ เช่น buildozer และ Code Search พบปัญหาในการค้นหาค่าและ อัปเดตค่าอย่างถูกต้องเมื่อค่าถูกแบ่งออกเป็นส่วนๆ
ในไฟล์
BUILDความสามารถในการอ่านมีความสำคัญมากกว่าการหลีกเลี่ยงการทำซ้ำ (ดู DAMP เทียบกับ DRY)คู่มือสไตล์นี้ เตือนไม่ให้แยกสตริงที่มีค่าเป็นป้ายกำกับ และ อนุญาตให้ใช้บรรทัดยาวๆ ได้อย่างชัดเจน
Buildifier จะรวมสตริงที่เชื่อมต่อกันโดยอัตโนมัติเมื่อตรวจพบว่าเป็นป้ายกำกับ
ดังนั้น ให้เลือกใช้สตริงตัวอักษรที่ชัดเจนแทนสตริงที่เชื่อมต่อหรือจัดรูปแบบ โดยเฉพาะอย่างยิ่งในแอตทริบิวต์ประเภทป้ายกำกับ เช่น name และ deps ตัวอย่างเช่น ส่วนย่อย BUILD นี้
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",
)
ควรเขียนใหม่เป็น
proto_library(
name = "foo_proto",
deps = ["//a/b:other_proto"],
alt_dep = "//surprisingly/long/chain/of/package/names:extravagantly_long_target_name",
)
จำกัดสัญลักษณ์ที่ส่งออกโดยไฟล์ .bzl แต่ละไฟล์
ลดจำนวนสัญลักษณ์ (กฎ มาโคร ค่าคงที่ ฟังก์ชัน) ที่ส่งออกโดยไฟล์ .bzl (Starlark) สาธารณะแต่ละไฟล์ เราขอแนะนำว่าไฟล์ควรส่งออกสัญลักษณ์หลายรายการก็ต่อเมื่อมั่นใจว่าจะใช้สัญลักษณ์เหล่านั้นร่วมกัน มิฉะนั้น ให้แยก
ไฟล์ออกเป็นไฟล์ .bzl หลายไฟล์ โดยแต่ละไฟล์จะมี bzl_library ของตัวเอง
สัญลักษณ์ที่มากเกินไปอาจทำให้ไฟล์ .bzl ขยายใหญ่ขึ้นเป็น "ไลบรารี" สัญลักษณ์ขนาดใหญ่ ซึ่งจะทำให้การเปลี่ยนแปลงไฟล์เดียวบังคับให้ Bazel สร้างเป้าหมายหลายรายการใหม่
ข้อกำหนดอื่นๆ
ใช้ตัวพิมพ์ใหญ่และขีดล่างเพื่อประกาศค่าคงที่ (เช่น
GLOBAL_CONSTANT) และใช้ตัวพิมพ์เล็กและขีดล่างเพื่อประกาศตัวแปร (เช่นmy_variable)ไม่ควรแยกป้ายกำกับ แม้ว่าป้ายกำกับจะยาวเกิน 79 อักขระก็ตาม ป้ายกำกับควรเป็นสตริงตัวอักษรทุกครั้งที่ทำได้ เหตุผล: ทำให้การ ค้นหาและแทนที่ทำได้ง่าย และยังช่วยให้อ่านง่ายขึ้นด้วย
ค่าของแอตทริบิวต์ name ควรเป็นสตริงค่าคงที่ตัวอักษร (ยกเว้นในมาโคร) เหตุผล: เครื่องมือภายนอกใช้แอตทริบิวต์ name เพื่ออ้างอิง กฎ เครื่องมือเหล่านี้จำเป็นต้องค้นหากฎโดยไม่ต้องตีความโค้ด
เมื่อตั้งค่าแอตทริบิวต์ประเภทบูลีน ให้ใช้ค่าบูลีน ไม่ใช่ค่าจำนวนเต็ม กฎยังคงแปลงจำนวนเต็มเป็นบูลีนตามความจำเป็นด้วยเหตุผลด้านความเข้ากันได้กับเวอร์ชันก่อนหน้า แต่เราไม่แนะนำให้ทำเช่นนี้ เหตุผล:
flaky = 1อาจอ่านผิดเป็น "เลิกทำเครื่องหมายเป้าหมายนี้ว่าไม่เสถียรโดยการเรียกใช้เป้าหมายอีกครั้ง 1 ครั้ง"flaky = Trueบอกอย่างชัดเจนว่า "การทดสอบนี้ไม่เสถียร"
ความแตกต่างกับคู่มือสไตล์ Python
แม้ว่าความเข้ากันได้กับ คู่มือสไตล์ Python จะเป็นเป้าหมาย แต่ก็มีความแตกต่างบางประการดังนี้
ไม่มีการจำกัดความยาวบรรทัดอย่างเข้มงวด ความคิดเห็นและสตริงยาวๆ มักจะถูกแบ่งออกเป็น 79 คอลัมน์ แต่ไม่บังคับ ไม่ควรบังคับใช้ในการตรวจสอบโค้ดหรือสคริปต์ก่อนส่ง เหตุผล: ป้ายกำกับอาจยาวและเกินขีดจำกัดนี้ ไฟล์
BUILDมักจะสร้างหรือแก้ไขโดยเครื่องมือ ซึ่งไม่เหมาะกับการจำกัดความยาวบรรทัดระบบไม่รองรับการเชื่อมต่อสตริงโดยนัย ให้ใช้ตัวดำเนินการ
+เหตุผล: ไฟล์BUILDมีรายการสตริงจำนวนมาก คุณอาจลืมใส่คอมมาได้ง่ายๆ ซึ่งจะทำให้ได้ผลลัพธ์ที่แตกต่างไปโดยสิ้นเชิง และเคยทำให้เกิดข้อบกพร่องมากมายในอดีต ดูการสนทนานี้ด้วยใช้ช่องว่างรอบๆ เครื่องหมาย
=สำหรับอาร์กิวเมนต์คีย์เวิร์ดในกฎ เหตุผล: อาร์กิวเมนต์ที่มีชื่อใช้บ่อยกว่าใน Python มากและอยู่ใน บรรทัดแยกกันเสมอ ช่องว่างช่วยให้อ่านง่ายขึ้น ข้อกำหนดนี้ใช้กันมานานแล้ว และไม่คุ้มค่าที่จะแก้ไขไฟล์BUILDที่มีอยู่ทั้งหมดใช้เครื่องหมายคำพูดคู่สำหรับสตริงโดยค่าเริ่มต้น เหตุผล: คู่มือสไตล์ Python ไม่ได้ ระบุไว้ แต่แนะนำให้ใช้แบบเดียวกัน ดังนั้นเราจึงตัดสินใจใช้เฉพาะสตริงที่อยู่ในเครื่องหมายคำพูดคู่ หลายภาษาใช้เครื่องหมายคำพูดคู่สำหรับสตริงตัวอักษร
ใช้บรรทัดว่าง 1 บรรทัดระหว่างคำจำกัดความระดับบนสุด 2 รายการ เหตุผล: โครงสร้างของไฟล์
BUILDไม่เหมือนกับไฟล์ Python ทั่วไป โดยมีเพียงคำสั่งระดับบนสุด การใช้บรรทัดว่าง 1 บรรทัดทำให้ไฟล์BUILDสั้นลง