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,_unittest,TestयाTestsकी मदद से,_testसफ़िक्स को टारगेट करना_libया_libraryजैसे बेमतलब के सफ़िक्स का इस्तेमाल न करें. हालांकि, अगर_libraryटारगेट और उससे जुड़े_binaryके बीच टकराव से बचने के लिए ऐसा करना ज़रूरी हो, तो किया जा सकता है- प्रोटो से जुड़े टारगेट के लिए:
proto_libraryटारगेट के नाम_protoसे खत्म होने चाहिए- भाषा के हिसाब से
*_proto_libraryके नियम, मूल प्रोटो से मेल खाने चाहिए. हालांकि,_protoकी जगह भाषा के हिसाब से सफ़िक्स का इस्तेमाल करना चाहिए. जैसे:cc_proto_library:_cc_protojava_proto_library:_java_protojava_lite_proto_library:_java_proto_lite
किसको दिखे
विज़िबिलिटी को जितना हो सके उतना सीमित रखना चाहिए. हालांकि, टेस्ट और रिवर्स डिपेंडेंसी के लिए ऐक्सेस की अनुमति देनी चाहिए. ज़रूरत के हिसाब से __pkg__ और __subpackages__ का इस्तेमाल करें.
default_visibility पैकेज को //visibility:public पर सेट न करें.
//visibility:public को सिर्फ़ प्रोजेक्ट के सार्वजनिक एपीआई में मौजूद टारगेट के लिए अलग-अलग सेट किया जाना चाहिए. ये ऐसी लाइब्रेरी हो सकती हैं जिन्हें बाहरी प्रोजेक्ट या बाइनरी पर निर्भर रहने के लिए डिज़ाइन किया गया है. साथ ही, इनका इस्तेमाल बाहरी प्रोजेक्ट की बिल्ड प्रोसेस में किया जा सकता है.
डिपेंडेंसी
डिपेंडेंसी सिर्फ़ डायरेक्ट डिपेंडेंसी तक सीमित होनी चाहिए. डायरेक्ट डिपेंडेंसी का मतलब है कि नियम में शामिल सोर्स के लिए ज़रूरी डिपेंडेंसी. ट्रांज़िटिव डिपेंडेंसी की सूची न बनाएं.
पैकेज-लोकल डिपेंडेंसी को सबसे पहले लिस्ट किया जाना चाहिए. साथ ही, उन्हें ऊपर दिए गए मौजूदा पैकेज में टारगेट के रेफ़रंस सेक्शन के साथ काम करने वाले तरीके से रेफ़र किया जाना चाहिए. इसके लिए, उनके पूरे पैकेज के नाम का इस्तेमाल नहीं किया जाना चाहिए.
डिपेंडेंसी को सीधे तौर पर एक ही सूची के तौर पर दिखाएं. कई टारगेट की "सामान्य" डिपेंडेंसी को एक वैरिएबल में डालने से, उन्हें मैनेज करना मुश्किल हो जाता है. साथ ही, टूल के लिए किसी टारगेट की डिपेंडेंसी को बदलना नामुमकिन हो जाता है. इससे ऐसी डिपेंडेंसी भी बन सकती हैं जिनका इस्तेमाल नहीं किया जाता.
Globs
[] की मदद से, "कोई टारगेट नहीं" बताएं. ऐसे ग्लोब का इस्तेमाल न करें जो किसी भी फ़ाइल से मेल न खाता हो: इसमें गड़बड़ी होने की संभावना ज़्यादा होती है और यह खाली सूची से कम साफ़ तौर पर दिखता है.
बार-बार होने वाला
सोर्स फ़ाइलों को मैच करने के लिए, रिकर्सिव ग्लोब का इस्तेमाल न करें. उदाहरण के लिए,
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, स्ट्रिंग जोड़ने (+) और फ़ॉर्मैट करने (%) के लिए ऑपरेटर उपलब्ध कराता है. हालांकि, इनका इस्तेमाल सावधानी से करें. एक्सप्रेशन को ज़्यादा छोटा बनाने या लंबी लाइनों को तोड़ने के लिए, स्ट्रिंग के सामान्य हिस्सों को अलग करना आसान होता है. फिर भी,
स्ट्रिंग वैल्यू को अलग-अलग हिस्सों में बांटने पर, उन्हें एक नज़र में पढ़ना मुश्किल हो जाता है.
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 एट्रिब्यूट की वैल्यू, लिटरल कॉन्स्टेंट स्ट्रिंग होनी चाहिए. हालांकि, मैक्रो में ऐसा नहीं है. वजह: बाहरी टूल, किसी नियम को रेफ़र करने के लिए, नाम एट्रिब्यूट का इस्तेमाल करते हैं. उन्हें कोड को समझे बिना नियमों का पता लगाना होता है.
बूलियन टाइप के एट्रिब्यूट सेट करते समय, पूर्णांक वैल्यू के बजाय बूलियन वैल्यू का इस्तेमाल करें. लेगसी सिस्टम की वजह से, नियमों में अब भी ज़रूरत के मुताबिक पूर्णांकों को बूलियन में बदला जाता है. हालांकि, ऐसा न करने का सुझाव दिया जाता है. वजह:
flaky = 1को गलत तरीके से पढ़ा जा सकता है. ऐसा लग सकता है कि इसमें कहा गया है कि "इस टारगेट को एक बार फिर से चलाकर, इसमें मौजूद गड़बड़ी को ठीक करें".flaky = Trueमें साफ़ तौर पर बताया गया है कि "यह टेस्ट फ़्लेकी है".
Python स्टाइल गाइड के साथ अंतर
हालांकि, Python स्टाइल गाइड के साथ काम करना एक लक्ष्य है, लेकिन इसमें कुछ अंतर हैं:
लाइन की लंबाई की कोई तय सीमा नहीं है. लंबी टिप्पणियों और लंबी स्ट्रिंग को अक्सर 79 कॉलम में बांटा जाता है. हालांकि, ऐसा करना ज़रूरी नहीं है. इसे कोड की समीक्षा या सबमिट करने से पहले की स्क्रिप्ट में लागू नहीं किया जाना चाहिए. वजह: लेबल लंबे हो सकते हैं और इस सीमा से ज़्यादा हो सकते हैं. आम तौर पर,
BUILDफ़ाइलों को टूल जनरेट करते हैं या उनमें बदलाव करते हैं. इसलिए, लाइन की लंबाई की सीमा तय करना सही नहीं है.स्ट्रिंग को अपने-आप जोड़ने की सुविधा काम नहीं करती.
+ऑपरेटर का इस्तेमाल करें. वजह:BUILDफ़ाइलों में स्ट्रिंग की कई सूचियां शामिल हैं. कॉमा लगाना भूलना आम बात है. इससे नतीजे पूरी तरह से अलग हो जाते हैं. इस वजह से, पिछले समय में कई गड़बड़ियां हुई हैं. यह बातचीत भी देखें.नियमों में कीवर्ड के तर्कों के लिए,
=चिह्न के आस-पास स्पेस का इस्तेमाल करें. वजह: नाम वाले आर्ग्युमेंट, Python की तुलना में ज़्यादा बार इस्तेमाल किए जाते हैं. साथ ही, ये हमेशा अलग लाइन में होते हैं. स्पेस का इस्तेमाल करने से, कॉन्टेंट को आसानी से पढ़ा जा सकता है. यह तरीका लंबे समय से इस्तेमाल किया जा रहा है. इसलिए, सभी मौजूदाBUILDफ़ाइलों में बदलाव करने की ज़रूरत नहीं है.डिफ़ॉल्ट रूप से, स्ट्रिंग के लिए डबल कोटेशन मार्क का इस्तेमाल करें. वजह: Python की स्टाइल गाइड में इसके बारे में नहीं बताया गया है. हालांकि, इसमें एक जैसा फ़ॉर्मैट इस्तेमाल करने का सुझाव दिया गया है. इसलिए, हमने सिर्फ़ डबल कोट वाली स्ट्रिंग का इस्तेमाल करने का फ़ैसला किया है. कई भाषाओं में, स्ट्रिंग लिटरल के लिए डबल कोट का इस्तेमाल किया जाता है.
टॉप-लेवल की दो परिभाषाओं के बीच एक खाली लाइन का इस्तेमाल करें. वजह:
BUILDफ़ाइल का स्ट्रक्चर, सामान्य Python फ़ाइल जैसा नहीं होता. इसमें सिर्फ़ टॉप-लेवल स्टेटमेंट होते हैं. एक खाली लाइन का इस्तेमाल करने से,BUILDफ़ाइलें छोटी हो जाती हैं.