स्टाइल गाइड बनाएं

DRY के बजाय, DAMP BUILD फ़ाइलों का इस्तेमाल करना

DRY सिद्धांत — "एक ही काम को बार-बार न करें" — कोड में बार-बार दोहराव से बचने के लिए, वैरिएबल और फ़ंक्शन जैसे ऐब्स्ट्रैक्शन का इस्तेमाल करके यूनीक कोड लिखने को बढ़ावा देता है.

इसके उलट, DAMP सिद्धांत — "जानकारी देने वाले और काम के वाक्यांश" — फ़ाइलों को आसानी से समझने और मैनेज करने के लिए, यूनीक कोड लिखने के बजाय, कोड को आसानी से पढ़ने लायक बनाने को बढ़ावा देता है.

BUILD फ़ाइलें कोड नहीं होतीं, बल्कि कॉन्फ़िगरेशन होती हैं. इनकी जांच कोड की तरह नहीं की जाती. हालांकि, इन्हें लोगों और टूल की मदद से मैनेज किया जाता है. इसलिए, इनके लिए DRY के मुकाबले DAMP बेहतर है.

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();' > $@",
)

टारगेट का नामकरण

टारगेट के नाम जानकारी देने वाले होने चाहिए. अगर किसी टारगेट में एक सोर्स फ़ाइल है, तो आम तौर पर टारगेट का नाम उस सोर्स से लिया जाना चाहिए. उदाहरण के लिए, cc_library के लिए chat.cc का नाम chat हो सकता है. इसी तरह, java_library के लिए DirectMessage.java का नाम direct_message हो सकता है.

किसी पैकेज के लिए, उसी नाम का टारगेट (जिसका नाम, डायरेक्ट्री के नाम जैसा हो) डायरेक्ट्री के नाम से बताई गई सुविधा उपलब्ध कराता है. अगर ऐसा कोई टारगेट नहीं है, तो उसी नाम का टारगेट न बनाएं.

उसी नाम के टारगेट को रेफ़र करते समय, छोटे नाम का इस्तेमाल करें. जैसे, //x के बजाय //x:x. अगर आप एक ही पैकेज में हैं, तो लोकल रेफ़रंस का इस्तेमाल करें. जैसे, :x के बजाय //x.

"रिज़र्व" टारगेट के नामों का इस्तेमाल न करें. इनका खास मतलब होता है. इनमें all, __pkg__, और __subpackages__ शामिल हैं. इन नामों का खास मतलब होता है. इनका इस्तेमाल करने पर, भ्रम की स्थिति पैदा हो सकती है और अनचाहे नतीजे मिल सकते हैं.

टीम के मौजूदा कन्वेंशन के न होने पर, यहां कुछ ऐसे सुझाव दिए गए हैं जिनका पालन करना ज़रूरी नहीं है. हालांकि, Google में इनका बड़े पैमाने पर इस्तेमाल किया जाता है:

  • आम तौर पर, "snake_case"
      का इस्तेमाल करें
    • src वाली java_library के लिए, इसका मतलब है कि ऐसा नाम इस्तेमाल करना जो एक्सटेंशन के बिना फ़ाइल के नाम जैसा न हो
    • 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 को सिर्फ़ प्रोजेक्ट के पब्लिक एपीआई में मौजूद टारगेट के लिए सेट किया जाना चाहिए. ये ऐसी लाइब्रेरी हो सकती हैं जिन पर बाहरी प्रोजेक्ट डिपेंड कर सकते हैं या ऐसे बाइनरी हो सकते हैं जिनका इस्तेमाल बाहरी प्रोजेक्ट के बिल्ड प्रोसेस में किया जा सकता है.

डिपेंडेंसी

डिपेंडेंसी को डायरेक्ट डिपेंडेंसी तक सीमित रखें. ये ऐसी डिपेंडेंसी होती हैं जिनकी ज़रूरत, नियम में शामिल सोर्स को होती है. ट्रांज़िटिव डिपेंडेंसी को शामिल न करें.

पैकेज-लोकल डिपेंडेंसी को सबसे पहले शामिल किया जाना चाहिए. साथ ही, इन्हें ऊपर दिए गए मौजूदा पैकेज में टारगेट के रेफ़रंस सेक्शन के मुताबिक रेफ़र किया जाना चाहिए. इन्हें इनके ऐब्सलूट पैकेज नेम से रेफ़र नहीं किया जाना चाहिए.

डिपेंडेंसी को सीधे तौर पर, एक ही सूची में शामिल करें. कई टारगेट की "कॉमन" डिपेंडेंसी को वैरिएबल में रखने से, उन्हें मैनेज करना मुश्किल हो जाता है. साथ ही, टूल के लिए किसी टारगेट की डिपेंडेंसी में बदलाव करना नामुमकिन हो जाता है. इससे ऐसी डिपेंडेंसी भी शामिल हो सकती हैं जिनका इस्तेमाल नहीं किया जाता.

ग्लोब

[] का इस्तेमाल करके, "कोई टारगेट नहीं" दिखाएं. ऐसे ग्लोब का इस्तेमाल न करें जो किसी भी टारगेट से मैच न हो. इसमें गड़बड़ी होने की संभावना ज़्यादा होती है. साथ ही, यह खाली सूची के मुकाबले कम साफ़ दिखता है.

बार-बार होने वाला

सोर्स फ़ाइलों को मैच करने के लिए, बार-बार होने वाले ग्लोब का इस्तेमाल न करें. जैसे, glob(["**/*.java"]).

बार-बार होने वाले ग्लोब की वजह से, BUILD फ़ाइलों को समझना मुश्किल हो जाता है, क्योंकि ये BUILD फ़ाइलों वाली सबडायरेक्ट्री को छोड़ देते हैं.

आम तौर पर, बार-बार होने वाले ग्लोब, हर डायरेक्ट्री के लिए BUILD फ़ाइल के मुकाबले कम असरदार होते हैं. ऐसा इसलिए, क्योंकि हर डायरेक्ट्री के लिए BUILD फ़ाइल में डिपेंडेंसी ग्राफ़ तय किया जाता है. इससे बेहतर रिमोट कैशिंग और पैरललिज़म की सुविधा मिलती है.

हर डायरेक्ट्री में BUILD फ़ाइल बनाना और उनके बीच डिपेंडेंसी ग्राफ़ तय करना एक अच्छी प्रैक्टिस है.

बार-बार न होने वाला

आम तौर पर, बार-बार न होने वाले ग्लोब का इस्तेमाल किया जा सकता है.

लिस्ट कंप्रीहेंशन का इस्तेमाल न करना

BUILD.bazel फ़ाइल के टॉप लेवल पर, लिस्ट कंप्रीहेंशन का इस्तेमाल न करें. बार-बार होने वाले कॉल को ऑटोमेट करने के लिए, हर नाम वाले टारगेट को अलग-अलग टॉप-लेवल नियम या मैक्रो कॉल के साथ बनाएं. साफ़ तौर पर जानकारी देने के लिए, हर टारगेट के लिए name पैरामीटर का छोटा नाम इस्तेमाल करें.

लिस्ट कंप्रीहेंशन से इन चीज़ों में कमी आती है:

  • मैनेज करने की सुविधा. लिस्ट कंप्रीहेंशन को सही तरीके से अपडेट करना, लोगों और बड़े पैमाने पर ऑटोमेटेड तरीके से किए जाने वाले बदलावों के लिए मुश्किल या नामुमकिन होता है.
  • खोज की सुविधा. पैटर्न में name पैरामीटर नहीं होते. इसलिए, नियम को नाम से ढूंढना मुश्किल होता है.

लिस्ट कंप्रीहेंशन पैटर्न का इस्तेमाल, आम तौर पर टेस्ट जनरेट करने के लिए किया जाता है. उदाहरण के लिए:

[[java_test(
    name = "test_%s_%s" % (backend, count),
    srcs = [ ... ],
    deps = [ ... ],
    ...
) for backend in [
    "fake",
    "mock",
]] for count in [
    1,
    10,
]]

हमारा सुझाव है कि आप आसान विकल्पों का इस्तेमाल करें. उदाहरण के लिए, एक ऐसा मैक्रो तय करें जो एक टेस्ट जनरेट करता है. इसके बाद, हर टॉप-लेवल 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 में स्ट्रिंग को जोड़ने (+) और फ़ॉर्मैट करने (%) के लिए स्ट्रिंग ऑपरेटर दिए जाते हैं, लेकिन इनका इस्तेमाल सावधानी से करें. एक्सप्रेशन को ज़्यादा सटीक बनाने या लंबी लाइनों को तोड़ने के लिए, स्ट्रिंग के कॉमन हिस्सों को फ़ैक्टर आउट करना आसान होता है. हालांकि,

इसलिए, जोड़ी गई या फ़ॉर्मैट की गई स्ट्रिंग के मुकाबले, साफ़ तौर पर बताई गई लिटरल स्ट्रिंग का इस्तेमाल करें. खास तौर पर, लेबल-टाइप एट्रिब्यूट में. जैसे, 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 को गलत तरीके से पढ़ा जा सकता है. जैसे, "इस टारगेट को एक बार फिर से चलाकर, इसमें मौजूद गड़बड़ी को ठीक करें". flaky = True से साफ़ तौर पर पता चलता है कि "इस टेस्ट में गड़बड़ी हो सकती है".

Python स्टाइल गाइड के मुकाबले अंतर

हालांकि, Python स्टाइल गाइड के साथ काम करना एक लक्ष्य है, लेकिन इसमें कुछ अंतर हैं:

  • लाइन की लंबाई की कोई सख्त सीमा नहीं है. आम तौर पर, लंबे कमेंट और लंबी स्ट्रिंग को 79 कॉलम में बांटा जाता है. हालांकि, ऐसा करना ज़रूरी नहीं है. इसे कोड की समीक्षा या सबमिट करने से पहले की स्क्रिप्ट में लागू नहीं किया जाना चाहिए. वजह: लेबल लंबे हो सकते हैं और इस सीमा से ज़्यादा हो सकते हैं. आम तौर पर, BUILD फ़ाइलें टूल की मदद से जनरेट या एडिट की जाती हैं. ऐसे में, लाइन की लंबाई की सीमा लागू करना सही नहीं है.

  • स्ट्रिंग को इंप्लिसिट तरीके से जोड़ने की सुविधा उपलब्ध नहीं है. + ऑपरेटर का इस्तेमाल करें. वजह: BUILD फ़ाइलों में, स्ट्रिंग की कई सूचियां होती हैं. कॉमा लगाना भूलना आसान है. इससे पूरी तरह से अलग नतीजा मिलता है. इससे पहले भी कई गड़बड़ियां हुई हैं. यह बातचीत भी देखें.

  • नियमों में कीवर्ड आर्ग्युमेंट के लिए, = साइन के आस-पास स्पेस का इस्तेमाल करें. वजह: Python के मुकाबले, नाम वाले आर्ग्युमेंट ज़्यादा बार इस्तेमाल किए जाते हैं. साथ ही, ये हमेशा अलग लाइन में होते हैं. स्पेस से कोड को आसानी से पढ़ा जा सकता है. यह कन्वेंशन लंबे समय से मौजूद है. इसलिए, मौजूदा सभी BUILD फ़ाइलों में बदलाव करना सही नहीं है.

  • डिफ़ॉल्ट रूप से, स्ट्रिंग के लिए डबल कोटेशन मार्क का इस्तेमाल करें. वजह: Python स्टाइल गाइड में यह तय नहीं किया गया है. हालांकि, इसमें एक जैसा फ़ॉर्मैट इस्तेमाल करने का सुझाव दिया गया है. इसलिए, हमने सिर्फ़ डबल कोट वाली स्ट्रिंग का इस्तेमाल करने का फ़ैसला किया है. कई भाषाओं में, स्ट्रिंग लिटरल के लिए डबल कोट का इस्तेमाल किया जाता है.

  • टॉप-लेवल की दो परिभाषाओं के बीच, एक खाली लाइन का इस्तेमाल करें. वजह: फ़ाइल का स्ट्रक्चर, आम तौर पर Python फ़ाइल जैसा नहीं होता.BUILD इसमें सिर्फ़ टॉप-लेवल स्टेटमेंट होते हैं. एक खाली लाइन का इस्तेमाल करने से, BUILD फ़ाइलें छोटी हो जाती हैं.