DRY के बजाय DAMP BUILD फ़ाइलों को प्राथमिकता दें
डीआरवाई सिद्धांत — "खुद को दोहराएं नहीं" — कोड में डुप्लीकेट कॉन्टेंट को हटाने के लिए, वैरिएबल और फ़ंक्शन जैसे एब्स्ट्रैक्शन को शामिल करके, यूनीक कॉन्टेंट बनाने के लिए बढ़ावा देता है.
इसके उलट, 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();' > $@",
)
टारगेट को नाम देना
टारगेट के नाम, जानकारी देने वाले होने चाहिए. अगर किसी टारगेट में एक सोर्स फ़ाइल है, तो आम तौर पर टारगेट का नाम उस सोर्स से लिया जाना चाहिए. उदाहरण के लिए, 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_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",
...)
...
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 और कोड सर्च जैसे ऑटोमेटेड टूल को वैल्यू ढूंढने में समस्या होती है. साथ ही, वैल्यू के अलग-अलग होने पर, उन्हें सही तरीके से अपडेट करने में भी समस्या होती है.
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 से ज़्यादा वर्णों के हों. जहां तक हो सके, लेबल स्ट्रिंग लिटरल होने चाहिए. वजह: इससे, टेक्स्ट में किसी शब्द को ढूंढना और उसे बदलना आसान हो जाता है. इससे कॉन्टेंट को पढ़ना भी आसान हो जाता है.
नाम एट्रिब्यूट की वैल्यू, मैक्रो के अलावा, लिटरल कॉन्स्टेंट स्ट्रिंग होनी चाहिए. वजह: बाहरी टूल, किसी नियम का रेफ़रंस देने के लिए नाम एट्रिब्यूट का इस्तेमाल करते हैं. उन्हें कोड को समझे बिना नियम ढूंढने होंगे.
बूलियन टाइप के एट्रिब्यूट सेट करते समय, पूर्णांक वैल्यू के बजाय बूलियन वैल्यू का इस्तेमाल करें. लेगसी वजहों से, नियम अब भी ज़रूरत के हिसाब से पूर्णांकों को बूलियन में बदलते हैं. हालांकि, ऐसा करने का सुझाव नहीं दिया जाता. वजह:
flaky = 1
को गलत तरीके से पढ़ा जा सकता है, जैसे कि "इस टारगेट को फिर से चलाकर, इसे डिफ़्लेक करें".flaky = True
साफ़ तौर पर कहता है कि "यह टेस्ट ठीक से काम नहीं करता".
Python स्टाइल गाइड से अंतर
हालांकि, Python स्टाइल गाइड के साथ काम करने की सुविधा उपलब्ध कराना हमारा लक्ष्य है, लेकिन इसमें कुछ अंतर हैं:
लाइन की लंबाई पर कोई पाबंदी नहीं है. लंबी टिप्पणियों और लंबी स्ट्रिंग को अक्सर 79 कॉलम में बांटा जाता है. हालांकि, ऐसा करना ज़रूरी नहीं है. इसे कोड की समीक्षाओं या सबमिट करने से पहले की जाने वाली स्क्रिप्ट में लागू नहीं किया जाना चाहिए. वजह: लेबल लंबे हो सकते हैं और इस सीमा से ज़्यादा हो सकते हैं. आम तौर पर,
BUILD
फ़ाइलों को टूल की मदद से जनरेट किया जाता है या उनमें बदलाव किया जाता है. ऐसा करने पर, लाइन की लंबाई की सीमा का पालन नहीं किया जा सकता.इंप्लिसिट स्ट्रिंग कनेक्शन की सुविधा काम नहीं करती.
+
ऑपरेटर का इस्तेमाल करें. वजह:BUILD
फ़ाइलों में कई स्ट्रिंग सूचियां होती हैं. कॉमा को आसानी से भूल जाया जा सकता है. इससे आपको पूरी तरह से अलग नतीजा मिलता है. इस वजह से, पहले कई गड़बड़ियां हुई हैं. यह चर्चा भी देखें.नियमों में कीवर्ड के आर्ग्युमेंट के लिए,
=
साइन के दोनों ओर स्पेस का इस्तेमाल करें. वजह: नाम वाले आर्ग्युमेंट, Python के मुकाबले ज़्यादा इस्तेमाल किए जाते हैं और ये हमेशा अलग लाइन में होते हैं. स्पेस का इस्तेमाल करने से, कॉन्टेंट को पढ़ना आसान हो जाता है. यह कॉन्वेंशन काफ़ी समय से है और सभी मौजूदाBUILD
फ़ाइलों में बदलाव करना सही नहीं है.डिफ़ॉल्ट रूप से, स्ट्रिंग के लिए डबल कोटेशन मार्क का इस्तेमाल करें. वजह: Python स्टाइल गाइड में इस बारे में जानकारी नहीं दी गई है. हालांकि, इसमें एक जैसा इस्तेमाल करने का सुझाव दिया गया है. इसलिए, हमने सिर्फ़ डबल कोट वाली स्ट्रिंग का इस्तेमाल करने का फ़ैसला लिया है. कई भाषाओं में, स्ट्रिंग लिटरल के लिए डबल कोट का इस्तेमाल किया जाता है.
दो टॉप-लेवल डेफ़िनिशन के बीच एक खाली लाइन का इस्तेमाल करें. वजह:
BUILD
फ़ाइल का स्ट्रक्चर, सामान्य Python फ़ाइल से अलग होता है. इसमें सिर्फ़ टॉप-लेवल के स्टेटमेंट होते हैं. एक खाली लाइन का इस्तेमाल करने से,BUILD
फ़ाइलें छोटी हो जाती हैं.