เลือกใช้ไฟล์ 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ให้กับเป้าหมาย_test,_unittest,TestหรือTests - หลีกเลี่ยงคำต่อท้ายที่ไม่มีความหมาย เช่น
_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 ที่ไม่ตรงกับสิ่งใดเลย เนื่องจากมีแนวโน้มที่จะเกิดข้อผิดพลาดมากกว่าและไม่ชัดเจนเท่ากับรายการว่าง
แบบเกิดซ้ำ
อย่าใช้ Glob แบบเกิดซ้ำเพื่อจับคู่ไฟล์ต้นฉบับ (เช่น
glob(["**/*.java"]))
Glob แบบเกิดซ้ำทำให้ไฟล์ BUILD เข้าใจได้ยาก เนื่องจากจะข้ามไดเรกทอรีย่อยที่มีไฟล์ BUILD
โดยทั่วไป Glob แบบเกิดซ้ำจะมีประสิทธิภาพน้อยกว่าการมีไฟล์ BUILD ต่อไดเรกทอรีพร้อมกราฟการอ้างอิงที่กำหนดไว้ระหว่างไฟล์เหล่านั้น เนื่องจากวิธีนี้ช่วยให้การแคชระยะไกลและการทำงานแบบขนานมีประสิทธิภาพมากขึ้น
การสร้างไฟล์ BUILD ในแต่ละไดเรกทอรีและกำหนดกราฟการอ้างอิงระหว่างไฟล์เหล่านั้นถือเป็นแนวทางปฏิบัติที่ดี
แบบไม่เกิดซ้ำ
โดยทั่วไป Glob แบบไม่เกิดซ้ำเป็นที่ยอมรับ
หลีกเลี่ยงการใช้ 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อาจอ่านผิดเป็น "deflake เป้าหมายนี้โดยการเรียกใช้ซ้ำ 1 ครั้ง"flaky = Trueบอกได้อย่างชัดเจนว่า "การทดสอบนี้ไม่เสถียร"
ความแตกต่างกับคู่มือสไตล์ของ Python
แม้ว่าความเข้ากันได้กับ คู่มือสไตล์ของ Python จะเป็นเป้าหมายของเรา แต่ก็มีความแตกต่างบางประการดังนี้
ไม่มีการจำกัดความยาวบรรทัดอย่างเข้มงวด ความคิดเห็นและสตริงยาวๆ มักจะถูกแบ่งออกเป็น 79 คอลัมน์ แต่ไม่จำเป็น และไม่ควรบังคับใช้ในการตรวจสอบโค้ดหรือสคริปต์ก่อนส่ง เหตุผล: ป้ายกำกับอาจยาวและเกินขีดจำกัดนี้ ไฟล์
BUILDมักจะสร้างหรือแก้ไขโดยเครื่องมือ ซึ่งไม่เหมาะกับการจำกัดความยาวบรรทัดระบบไม่รองรับการเชื่อมต่อสตริงโดยนัย ให้ใช้ตัวดำเนินการ
+เหตุผล: ไฟล์BUILDมีรายการสตริงจำนวนมาก และอาจลืมใส่คอมมาได้ง่าย ซึ่งจะทำให้ผลลัพธ์แตกต่างไปอย่างสิ้นเชิง ซึ่งเคยทำให้เกิดข้อบกพร่องมากมายในอดีต ดูการสนทนานี้ด้วยใช้ช่องว่างรอบๆ เครื่องหมาย
=สำหรับอาร์กิวเมนต์คีย์เวิร์ดในกฎ เหตุผล: อาร์กิวเมนต์ที่มีชื่อใช้บ่อยกว่าใน Python มาก และมักจะอยู่ใน บรรทัดแยกกัน ช่องว่างช่วยให้อ่านง่ายขึ้น ข้อตกลงนี้ใช้กันมานานแล้ว และไม่คุ้มค่าที่จะแก้ไขไฟล์BUILDที่มีอยู่ทั้งหมดใช้เครื่องหมายคำพูดคู่สำหรับสตริงโดยค่าเริ่มต้น เหตุผล: คู่มือสไตล์ของ Python ไม่ได้ ระบุไว้ แต่แนะนำให้ใช้แบบเดียวกัน ดังนั้นเราจึงตัดสินใจใช้เฉพาะสตริงที่อยู่ในเครื่องหมายคำพูดคู่ เนื่องจากหลายภาษาใช้เครื่องหมายคำพูดคู่สำหรับสตริงตัวอักษร
ใช้บรรทัดว่าง 1 บรรทัดระหว่างคำจำกัดความระดับบนสุด 2 รายการ เหตุผล: โครงสร้างของไฟล์
BUILDไม่เหมือนกับไฟล์ Python ทั่วไป เนื่องจากมีเพียงคำสั่งระดับบนสุดเท่านั้น การใช้บรรทัดว่าง 1 บรรทัดจะทำให้ไฟล์BUILDสั้นลง