คู่มือแนะนำรูปแบบการสร้าง

เลือกไฟล์ DAMP BUILD มากกว่า DRY

หลักการ DRY หรือ "อย่าทำซ้ำ" สนับสนุนความไม่ซ้ำโดย การนำการแยกส่วน เช่น ตัวแปรและฟังก์ชัน มาใช้เพื่อหลีกเลี่ยงความซ้ำซ้อนใน โค้ด

ในทางตรงกันข้าม หลักการ DAMP ซึ่งย่อมาจาก "วลีที่สื่อความหมายและอธิบายได้" จะ ส่งเสริมให้อ่านง่ายมากกว่าความไม่ซ้ำกัน เพื่อให้เข้าใจและ ดูแลรักษาไฟล์ได้ง่ายขึ้น

ไฟล์ 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 ที่มี src รายการเดียว หมายความว่าต้องใช้ชื่อที่ไม่เหมือนกับชื่อไฟล์ที่ไม่มีส่วนขยาย
    • สำหรับกฎ Java *_binary และ *_test ให้ใช้ "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 ด้วยคำต่อท้ายที่เฉพาะเจาะจงภาษา เช่น
      • cc_proto_library: _cc_proto
      • java_proto_library: _java_proto
      • java_lite_proto_library: _java_proto_lite

ระดับการแชร์

ควรจำกัดขอบเขตการมองเห็นให้แคบที่สุดเท่าที่จะทำได้ ในขณะที่ยังคงอนุญาตให้มีการเข้าถึง โดยการทดสอบและการอ้างอิงย้อนกลับ ใช้ __pkg__ และ __subpackages__ ตามความเหมาะสม

หลีกเลี่ยงการตั้งค่าแพ็กเกจ default_visibility เป็น //visibility:public //visibility:public ควรตั้งค่าแยกกันสำหรับเป้าหมายใน API สาธารณะของโปรเจ็กต์เท่านั้น ซึ่งอาจเป็นไลบรารีที่ออกแบบมาให้โปรเจ็กต์ภายนอกต้องพึ่งพา หรือไบนารีที่โปรเจ็กต์ภายนอกใช้ได้ในกระบวนการบิลด์

แท็กเริ่มการทำงาน

ควรจำกัดทรัพยากร Dependency ไว้ที่ทรัพยากร Dependency โดยตรง (ทรัพยากร Dependency ที่แหล่งข้อมูลที่ระบุในกฎต้องใช้) อย่าแสดงทรัพยากร Dependency แบบทรานซิทีฟ

ควรแสดงรายการทรัพยากร Dependency เฉพาะแพ็กเกจก่อนและอ้างอิงในลักษณะที่เข้ากันได้กับส่วนการอ้างอิงเป้าหมายในแพ็กเกจปัจจุบันด้านบน (ไม่ใช่ตามชื่อแพ็กเกจแบบสัมบูรณ์)

ควรแสดงรายการการอ้างอิงโดยตรงเป็นรายการเดียว การใส่การอ้างอิง "ทั่วไป" ของหลายเป้าหมายลงในตัวแปรจะลดความสามารถในการบำรุงรักษา ทำให้ เครื่องมือไม่สามารถเปลี่ยนการอ้างอิงของเป้าหมาย และอาจนำไปสู่ การอ้างอิงที่ไม่ได้ใช้

Globs

ระบุ "ไม่มีเป้าหมาย" ด้วย [] อย่าใช้ Glob ที่ไม่ตรงกับอะไรเลย เพราะจะทำให้เกิดข้อผิดพลาดได้ง่ายกว่าและไม่ชัดเจนเท่ากับรายการที่ว่างเปล่า

แบบเรียกซ้ำ

อย่าใช้ Glob แบบเรียกซ้ำเพื่อจับคู่ไฟล์ต้นฉบับ (เช่น glob(["**/*.java"]))

Glob แบบเรียกซ้ำทำให้ไฟล์ BUILD จัดการได้ยากเนื่องจากจะข้ามไดเรกทอรีย่อยที่มีไฟล์ BUILD

โดยทั่วไปแล้ว Glob แบบเรียกซ้ำจะมีประสิทธิภาพน้อยกว่าการมีไฟล์ BUILD ต่อไดเรกทอรีที่มีการกำหนดกราฟการขึ้นต่อกันระหว่างไฟล์เหล่านั้น เนื่องจากจะช่วยให้แคชระยะไกลและการทำงานแบบขนานมีประสิทธิภาพมากขึ้น

แนวทางปฏิบัติที่ดีคือการสร้างไฟล์ BUILD ในแต่ละไดเรกทอรีและกำหนดกราฟการอ้างอิงระหว่างกัน

แบบไม่เกิดซ้ำ

โดยทั่วไปแล้ว เรายอมรับ Glob ที่ไม่ใช่แบบเรียกซ้ำ

หลีกเลี่ยงการทำความเข้าใจรายการ

หลีกเลี่ยงการใช้ 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 เพื่อจัดกลุ่มการขึ้นต่อกัน

แต่ให้แสดงรายการทรัพยากร Dependency แยกกันสำหรับแต่ละเป้าหมาย ดังนี้

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 จะมีตัวดำเนินการสตริงสำหรับการต่อกัน (+) และ การจัดรูปแบบ (%) แต่ควรใช้ด้วยความระมัดระวัง การแยกส่วนสตริงที่ใช้ร่วมกันออกมาเพื่อทำให้นิพจน์กระชับขึ้นหรือแบ่งบรรทัดยาวๆ อาจเป็นสิ่งที่น่าสนใจ อย่างไรก็ตาม

ดังนั้น ควรใช้สตริงที่ชัดเจนและตรงตัวมากกว่าสตริงที่ต่อกันหรือจัดรูปแบบ โดยเฉพาะในแอตทริบิวต์ประเภทป้ายกำกับ เช่น 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 อักขระก็ตาม ป้ายกำกับควรเป็นสตริงตามตัวอักษรทุกครั้งที่เป็นไปได้ เหตุผล: ช่วยให้การค้นหาและแทนที่ทำได้ง่าย และยังช่วยให้อ่านง่ายขึ้นด้วย

  • ค่าของแอตทริบิวต์ชื่อควรเป็นสตริงค่าคงที่ตามตัวอักษร (ยกเว้นในมาโคร) เหตุผล: เครื่องมือภายนอกใช้แอตทริบิวต์ชื่อเพื่ออ้างอิงกฎ โดยผู้ใช้ต้องค้นหากฎได้โดยไม่ต้องตีความโค้ด

  • เมื่อตั้งค่าแอตทริบิวต์ประเภทบูลีน ให้ใช้ค่าบูลีน ไม่ใช่ค่าจำนวนเต็ม ด้วยเหตุผลเดิม กฎยังคงแปลงจำนวนเต็มเป็นบูลีนตามที่จำเป็น แต่เราไม่แนะนำให้ทำเช่นนี้ เหตุผล: flaky = 1 อาจอ่านผิดเป็น "ล้างเป้าหมายนี้โดยการเรียกใช้ซ้ำ 1 ครั้ง" flaky = True ระบุอย่างชัดเจนว่า "การทดสอบนี้ไม่เสถียร"

ความแตกต่างกับคู่มือแนะนำรูปแบบการเขียนโค้ด Python

แม้ว่าเป้าหมายของเราคือการรองรับคู่มือสไตล์ Python แต่ก็มีความแตกต่างกันอยู่บ้าง ดังนี้

  • ไม่มีการจำกัดความยาวบรรทัดอย่างเคร่งครัด โดยปกติแล้ว ระบบจะแบ่งความคิดเห็นและสตริงยาวๆ ออกเป็น 79 คอลัมน์ แต่ก็ไม่จำเป็นเสมอไป ไม่ควรบังคับใช้ในรีวิวโค้ดหรือสคริปต์ก่อนส่ง เหตุผล: ป้ายกำกับอาจยาวและเกินขีดจำกัดนี้ โดยปกติแล้วเครื่องมือจะสร้างหรือแก้ไขไฟล์ BUILD ซึ่งไม่สอดคล้องกับขีดจำกัดความยาวของบรรทัด

  • ระบบไม่รองรับการต่อสตริงโดยนัย ใช้โอเปอเรเตอร์ + เหตุผล: ไฟล์ BUILD มีรายการสตริงจำนวนมาก คุณอาจลืมใส่เครื่องหมายคอมมาได้ง่ายๆ ซึ่งจะทำให้ผลลัพธ์แตกต่างไปจากเดิมโดยสิ้นเชิง ซึ่งทำให้เกิดข้อบกพร่องมากมาย ในอดีต ดูการสนทนานี้ด้วย

  • ใช้ช่องว่างรอบเครื่องหมาย = สำหรับอาร์กิวเมนต์คีย์เวิร์ดในกฎ เหตุผล: อาร์กิวเมนต์ที่มีชื่อจะใช้บ่อยกว่าใน Python และจะอยู่บน บรรทัดแยกเสมอ การเว้นวรรคช่วยให้อ่านง่ายขึ้น รูปแบบนี้ใช้กันมานานแล้ว และไม่คุ้มค่าที่จะแก้ไขไฟล์ BUILD ที่มีอยู่ทั้งหมด

  • โดยค่าเริ่มต้น ให้ใช้เครื่องหมายคำพูดคู่สำหรับสตริง เหตุผล: ไม่ได้ระบุไว้ในคู่มือแนะนำสไตล์ของ Python แต่แนะนำให้ใช้รูปแบบที่สอดคล้องกัน เราจึงตัดสินใจใช้สตริงที่อยู่ในเครื่องหมายคำพูดคู่เท่านั้น หลายภาษาใช้เครื่องหมายคำพูดคู่ สำหรับสตริงลิเทอรัล

  • ใช้บรรทัดว่าง 1 บรรทัดระหว่างคำจำกัดความระดับบนสุด 2 รายการ เหตุผล: โครงสร้างของไฟล์ BUILD ไม่เหมือนกับไฟล์ Python ทั่วไป มีเฉพาะ คำสั่งระดับบนสุด การใช้บรรทัดว่างบรรทัดเดียวจะทำให้ไฟล์ BUILD สั้นลง