इस लेख का उद्देश्य आधिकारिक Solidity Style Guide को दोहराना नहीं है, जिसे आपको पढ़ना चाहिए। बल्कि, इसका उद्देश्य उन सामान्य विचलनों (deviations) का दस्तावेजीकरण करना है जो कोड रिव्यू या ऑडिट के दौरान सामने आते हैं। यहां दी गई कुछ चीजें स्टाइल गाइड में नहीं हैं, लेकिन वे Solidity डेवलपर्स द्वारा की जाने वाली सामान्य शैलीगत (stylistic) गलतियां हैं।
शुरुआती दो लाइनें
1. SPDX-License-Identifier शामिल करें
बेशक आपका कोड इसके बिना भी कंपाइल हो जाएगा, लेकिन आपको एक चेतावनी (warning) मिलेगी, इसलिए बस उस चेतावनी को दूर करें।
2. अगर आप कोई लाइब्रेरी नहीं लिख रहे हैं, तो Solidity pragma को फिक्स करें
आपने शायद निम्नलिखित जैसे pragmas देखे होंगे:
pragma solidity ^0.8.0;
और
pragma solidity 0.8.21;
आपको किसका और कब उपयोग करना चाहिए? यदि आप कॉन्ट्रैक्ट को कंपाइल और डिप्लॉय कर रहे हैं, तो आप उस Solidity वर्जन को जानते हैं जिसके साथ आप कंपाइल कर रहे हैं, इसलिए स्पष्टता के लिए, आपको Solidity वर्जन को उस कंपाइलर पर फिक्स कर देना चाहिए जिसका आप उपयोग कर रहे हैं।
दूसरी ओर, यदि आप अन्य लोगों के उपयोग और विस्तार के लिए कोई लाइब्रेरी बना रहे हैं, जैसे कि OpenZeppelin और Solady करते हैं, तो आपको pragma को फिक्स नहीं करना चाहिए क्योंकि आप नहीं जानते कि अंतिम उपयोगकर्ता (end user) किस कंपाइलर वर्जन का उपयोग करेगा।
इम्पोर्ट्स (Imports)
3. import स्टेटमेंट में स्पष्ट रूप से लाइब्रेरी वर्जन सेट करें
ऐसा करने के बजाय:
import "@openzepplin/contracts/token/ERC20/ERC20.sol";
ऐसा करें:
import "@openzeppelin/[email protected]/token/ERC20/ERC20.sol";
आप github के बाईं ओर ब्रांच ड्रॉपडाउन पर क्लिक करके और tags पर क्लिक करके नवीनतम वर्जन प्राप्त कर सकते हैं, फिर नवीनतम रिलीज़ (latest release) चुनें। नवीनतम क्लीन (non-rc, यानी non-release-candidate) वर्जन का उपयोग करें।

यदि आप import में वर्जन सेट नहीं करते हैं और अंतर्निहित (underlying) लाइब्रेरी अपडेट हो जाती है, तो संभव है कि आपका कोड अब कंपाइल न हो या अप्रत्याशित (unexpected) व्यवहार करे।
4. संपूर्ण नेमस्पेस को इम्पोर्ट करने के बजाय named imports का उपयोग करें
ऐसा करने के बजाय
import "@openzeppelin/[email protected]/token/ERC20/ERC20.sol";
ऐसा करें
import {ERC20} from "@openzeppelin/[email protected]/token/ERC20/ERC20.sol";
यदि import फ़ाइल के अंदर कई कॉन्ट्रैक्ट या लाइब्रेरी परिभाषित हैं, तो आप नेमस्पेस को दूषित (pollute) करेंगे। इसके परिणामस्वरूप डेड कोड (dead code) उत्पन्न होगा यदि कंपाइलर ऑप्टिमाइज़र इसे नहीं हटाता है (जिस पर आपको निर्भर नहीं रहना चाहिए)।
5. अप्रयुक्त (unused) imports को डिलीट करें
यदि आप Slither जैसे स्मार्ट कॉन्ट्रैक्ट सिक्योरिटी टूल का उपयोग करते हैं, तो यह स्वचालित रूप से पकड़ लिया जाएगा। लेकिन इन्हें हटाना सुनिश्चित करें। कोड डिलीट करने से न डरें।
कॉन्ट्रैक्ट लेवल (Contract Level)
6. कॉन्ट्रैक्ट-लेवल NatSpec लागू करें
NatSpec (नेचुरल लैंग्वेज स्पेसिफिकेशन) का उद्देश्य आसानी से पढ़े जाने योग्य (human-readable) इनलाइन दस्तावेज़ीकरण (documentation) प्रदान करना है।
कॉन्ट्रैक्ट के लिए NatSpec का एक उदाहरण नीचे दिया गया है।
/// @title Liquidity token for Foo protocol
/// @author Foo Incorporated
/// @notice Notes for non-technical readers/
/// @dev Notes for people who understand Solidity
contract LiquidityToken {
}
7. स्टाइल गाइड के अनुसार कॉन्ट्रैक्ट स्ट्रक्चर तैयार करें
फ़ंक्शन्स को पहले “externality” और फिर “state-changingness” के आधार पर सॉर्ट किया जाना चाहिए।
उन्हें निम्नलिखित क्रम में व्यवस्थित किया जाना चाहिए: receive और fallback फ़ंक्शन्स (यदि लागू हो), external फ़ंक्शन्स, public फ़ंक्शन्स, internal फ़ंक्शन्स, और private फ़ंक्शन्स।
इन समूहों के भीतर, payable फ़ंक्शन्स सबसे ऊपर रखे जाते हैं, फिर non-payable, फिर view, और फिर pure।
contract ProperLayout {
// type declarations, e.g. using Address for address
// state vars
address internal owner;
uint256 internal _stateVar;
uint256 internal _starteVar2;
// events
event Foo();
event Bar(address indexed sender);
// errors
error NotOwner();
error FooError();
error BarError();
// modifiers
modifier onlyOwner() {
if (msg.sender != owner) {
revert NotOwner();
}
_;
}
// functions
constructor() {
}
receive() external payable {
}
falback() external payable {
}
// functions are first grouped by
// - external
// - public
// - internal
// - private
// note how the external functions "descend" in order of how much they can modify or interact with the state
function foo() external payable {
}
function bar() external {
}
function baz() external view {
}
function qux() external pure {
}
// public functions
function fred() public {
}
function bob() public view {
}
// internal functions
// internal view functions
// internal pure functions
// private functions
// private view functions
// private pure functions
}
कांस्टेंट्स (Constants)
8. मैजिक नंबर्स (magic numbers) को कांस्टेंट्स से बदलें
यदि आप कोड में केवल संख्या 100 लिखा हुआ देखते हैं, तो यह क्या है? 100 प्रतिशत? 100 बेसिस पॉइंट्स?
आमतौर पर, नंबरों को कॉन्ट्रैक्ट के शीर्ष पर एक कांस्टेंट (constant) के रूप में लिखा जाना चाहिए।
9. यदि नंबरों का उपयोग Ether या समय मापने के लिए किया जाता है, तो Solidity कीवर्ड्स का उपयोग करें
यह लिखने के बजाय
uint256 secondsPerDay = 60 * 60 * 24;
यह करें
1 days
यह लिखने के बजाय
require(msg.value == 10**18 / 10, "must send 0.1 ether");
यह करें
require(msg.value == 0.1 ether, "must send 0.1 ether");
10. बड़े नंबरों को आसानी से पढ़ने योग्य बनाने के लिए अंडरस्कोर (underscores) का उपयोग करें
ऐसा करने के बजाय
uint256 private constant BASIS_POINTS_DENOMINATOR = 10000
ऐसा करें
uint256 private constant BASIS_POINTS_DENOMINATOR = 10_000
फ़ंक्शन्स (Functions)
11. उन फ़ंक्शन्स से virtual मॉडिफायर हटाएं जिन्हें ओवरराइड (override) नहीं किया जाएगा
virtual मॉडिफायर का अर्थ है “चाइल्ड कॉन्ट्रैक्ट द्वारा ओवरराइड किया जा सकने वाला (overridable)।” लेकिन यदि आप जानते हैं कि आप फ़ंक्शन को ओवरराइड नहीं करेंगे (क्योंकि आप डिप्लॉयर हैं), तो यह मॉडिफायर अनावश्यक है। इसे बस डिलीट कर दें।
12. फ़ंक्शन मॉडिफायर्स को सही क्रम में रखें: visibility, mutability, virtual, override, कस्टम मॉडिफायर
निम्नलिखित सही है
// visibility (payability), [virtual], [override], [custom]
function foo() public payable onlyAdmin {
}
function bar() internal view virtual override onlyAdmin {
}
13. NatSpec का सही तरीके से उपयोग करें
कभी-कभी इसे “Solidity कमेंट स्टाइल” कहा जाता है, लेकिन इसका सही नाम NatSpec है:
इसके नियम कॉन्ट्रैक्ट NatSpec के समान ही हैं, सिवाय इसके कि हम फ़ंक्शन आर्ग्यूमेंट्स (arguments) और रिटर्न होने वाले मान के आधार पर params भी निर्दिष्ट करते हैं।
यह लंबे आर्ग्यूमेंट वेरिएबल्स का उपयोग किए बिना आर्ग्यूमेंट के नामों का वर्णन करने का एक अच्छा तरीका हो सकता है।
/// @notice Deposit ERC20 tokens
/// @dev emits a Deposit event
/// @dev reverts if the token is not allowlisted
/// @dev reverts if the contract is not approved by the ERC20
/// @param token The address of the ERC20 token to be deposited
/// @param amount The amount of ERC20 tokens to deposit
/// @returns the amount of liquidity tokens the user receives
function deposit(address token, uint256 amount) public returns (uint256) {
}
// If the contract inherits functions, you can also inherit their NatSpec
/// @inheritdoc Lendable
function calculateAccumulatedInterest(address token, uint256 since) public override view returns (uint256 interest) {
}
dev पैरामीटर्स के लिए, यह सूचित करना अच्छा है कि यह किस तरह के स्टेट परिवर्तन कर सकता है, उदाहरण के लिए कोई ईवेंट एमिट (emit) करना, Ether भेजना, selfdestructing आदि।
notice और param NatSpec को Etherscan द्वारा पढ़ा जाता है।

आप कोड के निम्नलिखित स्क्रीनशॉट में देख सकते हैं कि Etherscan को वह जानकारी कहाँ से मिली।

सामान्य स्वच्छता (General cleanliness)
14. कमेंट किए गए (commented out) कोड को हटाएं
यह स्वतः स्पष्ट होना चाहिए। यदि कोड कमेंट किया गया है, तो यह केवल अव्यवस्था (clutter) है।
15. वेरिएबल के नामों के बारे में सावधानी से विचार करें
चीजों का नामकरण करना अच्छे कोड लिखने के सबसे कठिन पहलुओं में से एक है, लेकिन यह पठनीयता (readability) के लिए चमत्कार करेगा।
कुछ टिप्स:
-
“user” जैसे “सामान्य संज्ञाओं (generic nouns)” से बचें। अधिक सटीक (precise) रहें, उदाहरण के लिए “admin”, “buyer”, “seller”।
-
“data” शब्द आमतौर पर अस्पष्टता का संकेत है। “userData” के बजाय “userAccount” का उपयोग करें।
-
एक ही वास्तविक दुनिया की इकाई (real-world entity) के लिए दो अलग-अलग संज्ञाओं का उपयोग न करें। उदाहरण के लिए, यदि वास्तविक दुनिया में “depositor” और “liquidityProvider” एक ही इकाई को संदर्भित करते हैं, तो बस एक ही शब्द पर टिके रहें, कोड में दोनों का उपयोग न करें।
-
वेरिएबल के नामों में इकाइयां (units) शामिल करें। “interestRate” के बजाय “interestRatesBasisPoints” या “feeInWei” का उपयोग करें।
-
स्टेट-चेंजिंग (State changing) फ़ंक्शन्स के नाम में एक क्रिया (verb) होनी चाहिए।
-
इंटरनल वेरिएबल्स और फ़ंक्शन्स बनाम स्टेट वेरिएबल्स को ओवरशेडो करने वाले फ़ंक्शन आर्ग्यूमेंट्स के बीच अंतर करने के लिए अंडरस्कोर के उपयोग के बारे में सुसंगत (consistent) रहें। यदि किसी वेरिएबल के पहले अंडरस्कोर लगाने का अर्थ “internal” है, तो सुनिश्चित करें कि इसका उपयोग किसी अन्य संदर्भ में किसी और चीज़ के लिए नहीं किया जाता है, उदाहरण के लिए, ऐसे फ़ंक्शन आर्ग्यूमेंट्स जिनका नाम स्टेट वेरिएबल के समान है।
-
डेटा देखने के लिए “get” और डेटा बदलने के लिए “set” का उपयोग करना एक व्यापक रूप से पालन किया जाने वाला प्रोग्रामिंग कन्वेंशन है। इसे शामिल करने पर विचार करें।
-
अपना कोड लिखना समाप्त करने के बाद, कंप्यूटर से दूर हट जाएं, फिर 15 मिनट बाद वापस आएं और प्रत्येक वेरिएबल और फ़ंक्शन के नाम के लिए खुद से पूछें कि क्या आप जितना संभव हो उतना सटीक थे। यह सुविचारित (deliberate) प्रयास आपके लिए किसी भी चेकलिस्ट से अधिक लाभकारी होगा, क्योंकि आप कोडबेस के इरादे को किसी और से बेहतर जानते हैं।
बड़े कोडबेस को व्यवस्थित करने के लिए अतिरिक्त ट्रिक्स
-
यदि आपके पास बहुत सारे स्टोरेज वेरिएबल्स हैं, तो आप एक ही कॉन्ट्रैक्ट में सभी स्टोरेज वेरिएबल्स को परिभाषित कर सकते हैं, और फिर उन स्टोरेज वेरिएबल्स तक पहुंच प्राप्त करने के लिए उस कॉन्ट्रैक्ट को इनहेरिट (inherit) कर सकते हैं।
-
यदि आपके फ़ंक्शन्स में बहुत अधिक पैरामीटर्स की आवश्यकता है, तो जानकारी पास करने के लिए struct का उपयोग करें।
-
यदि आपको बहुत सारे imports की आवश्यकता है, तो आप सभी फ़ाइलों और प्रकारों (types) को एक Solidity फ़ाइल में import कर सकते हैं, फिर उस फ़ाइल को import कर सकते हैं (इसके लिए आपको named imports के नियम को जानबूझकर तोड़ना होगा)।
-
समान श्रेणी के फ़ंक्शन्स को एक साथ समूहित (group) करने और फ़ाइलों को छोटा बनाने के लिए लाइब्रेरीज़ का उपयोग करें।
बड़े कोडबेस को व्यवस्थित करना एक कला है। इसे सीखने का सबसे अच्छा तरीका बड़े स्थापित प्रोजेक्ट्स के कोडबेस का अध्ययन करना है।
RareSkills के साथ और जानें
इस चेकलिस्ट का उपयोग हमारे एडवांस्ड Solidity bootcamp में कोड रिव्यू के लिए किया जाता है।
मूल रूप से 12 अगस्त, 2024 को प्रकाशित