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

DRY के बजाय DAMP BUILD फ़ाइलों को प्राथमिकता दें

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

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

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

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

टारगेट के नाम, कम शब्दों में ज़्यादा जानकारी देने वाले होने चाहिए. अगर किसी टारगेट में एक सोर्स फ़ाइल है, तो टारगेट का नाम आम तौर पर उस सोर्स से लिया जाना चाहिए. उदाहरण के लिए, chat.cc के लिए cc_library का नाम chat रखा जा सकता है या DirectMessage.java के लिए java_library का नाम 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_library टारगेट के नाम, _proto से खत्म होने चाहिए
    • भाषा के हिसाब से *_proto_library के नियम, मूल प्रोटो से मेल खाने चाहिए. हालांकि, *_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.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",
    ...)
...

डिपेंडेंसी वैरिएबल का इस्तेमाल न करें

सामान्य डिपेंडेंसी को शामिल करने के लिए, लिस्ट वैरिएबल का इस्तेमाल न करें:

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 से ज़्यादा वर्ण हों. जहां तक हो सके, लेबल स्ट्रिंग लिटरल होने चाहिए. वजह: इससे ढूंढने और बदलने की प्रोसेस आसान हो जाती है. इससे पढ़ने में आसानी होती है.

  • name एट्रिब्यूट की वैल्यू, लिटरल कॉन्स्टेंट स्ट्रिंग होनी चाहिए. हालांकि, मैक्रो में ऐसा नहीं होता. वजह: बाहरी टूल, किसी नियम को रेफ़र करने के लिए नाम एट्रिब्यूट का इस्तेमाल करते हैं. उन्हें कोड को समझे बिना नियमों का पता लगाना होता है.

  • बूलियन टाइप के एट्रिब्यूट सेट करते समय, पूर्णांक वैल्यू के बजाय बूलियन वैल्यू का इस्तेमाल करें. लेगसी सिस्टम की वजह से, नियमों में अब भी ज़रूरत के मुताबिक पूर्णांकों को बूलियन में बदला जाता है. हालांकि, ऐसा न करने का सुझाव दिया जाता है. वजह: flaky = 1 को गलत तरीके से पढ़ा जा सकता है. ऐसा लग सकता है कि इसमें कहा गया है कि "इस टारगेट को एक बार फिर से चलाकर, इसमें मौजूद गड़बड़ी को ठीक करें". flaky = True में साफ़ तौर पर बताया गया है कि "यह टेस्ट फ़्लेकी है".

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

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

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

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

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

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

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