Solidity編程風格指南


概述


本指南旨在約定 solidity 代碼的編碼規(guī)范。本指南是不斷變化演進的,舊的、過時的編碼規(guī)范會被淘汰,
而新的、有用的規(guī)范會被添加進來。

許多項目會實施他們自己的編碼風格指南。如遇沖突,應優(yōu)先使用具體項目的風格指南。

本風格指南中的結構和許多建議是取自 python 的 pep8 style guide 。

本指南并 不是 以指導正確或最佳的 solidity 編碼方式為目的。
本指南的目的是保持代碼的 一致性
來自 python 的參考文檔 pep8 。很好地闡述了這個概念。

風格指南是關于一致性的。
重要的是與此風格指南保持一致。
但項目中的一致性更重要。
一個模塊或功能內的一致性是最重要的。

但最重要的是:知道什么時候不一致 —— 有時風格指南不適用。如有疑問,請自行判斷??纯雌渌樱Q定什么看起來最好。并應毫不猶豫地詢問他人!

代碼結構

縮進

每個縮進級別使用4個空格。

制表符或空格

空格是首選的縮進方法。

應該避免混合使用制表符和空格。

空行

在 solidity 源碼中合約聲明之間留出兩個空行。

正確寫法:

contract A {
    //...
}


contract B {
    //...
}


contract C {
    //...
}

錯誤寫法:

contract A {
    //...
}
contract B {
    //...
}

contract C {
   // ...
}

在一個合約中的函數(shù)聲明之間留有一個空行。

在相關聯(lián)的各組單行語句之間可以省略空行。(例如抽象合約的 stub 函數(shù))。

正確寫法:

pragma solidity ^0.6.0;

abstract contract A {
    function spam() public virtual pure;
    function ham() public virtual pure;
}


contract B is A {
    function spam() public pure override {
        // ...
    }

    function ham() public pure override {
        // ...
    }
}

錯誤寫法:

pragma solidity >=0.4.0 <0.9.0;

abstract contract A {
    function spam() virtual pure public;
    function ham() public virtual pure;
}


contract B is A {
    function spam() public pure override {
        // ...
    }
    function ham() public pure override {
        // ...
    }
}

代碼行的最大長度

基于 PEP 8 recommendation ,將代碼行的字符長度控制在 79(或 99)字符來幫助讀者閱讀代碼。

折行時應該遵從以下指引:

  1. 第一個參數(shù)不應該緊跟在左括號后邊
  2. 用一個、且只用一個縮進
  3. 每個函數(shù)應該單起一行
  4. 結束符號); 應該單獨放在最后一行

函數(shù)調用

正確寫法:

thisFunctionCallIsReallyLong(
    longArgument1,
    longArgument2,
    longArgument3
);

錯誤寫法:

thisFunctionCallIsReallyLong(longArgument1,
                              longArgument2,
                              longArgument3
);

thisFunctionCallIsReallyLong(longArgument1,
    longArgument2,
    longArgument3
);

thisFunctionCallIsReallyLong(
    longArgument1, longArgument2,
    longArgument3
);

thisFunctionCallIsReallyLong(
longArgument1,
longArgument2,
longArgument3
);

thisFunctionCallIsReallyLong(
    longArgument1,
    longArgument2,
    longArgument3);

賦值語句

正確寫法:

thisIsALongNestedMapping[being][set][to_some_value] = someFunction(
    argument1,
    argument2,
    argument3,
    argument4
);

錯誤寫法:

thisIsALongNestedMapping[being][set][to_some_value] = someFunction(argument1,
                                                                   argument2,
                                                                   argument3,
                                                                   argument4);

事件定義和事件發(fā)生

正確寫法:

event LongAndLotsOfArgs(
    adress sender,
    adress recipient,
    uint256 publicKey,
    uint256 amount,
    bytes32[] options
);

LongAndLotsOfArgs(
    sender,
    recipient,
    publicKey,
    amount,
    options
);

錯誤寫法:

event LongAndLotsOfArgs(adress sender,
                        adress recipient,
                        uint256 publicKey,
                        uint256 amount,
                        bytes32[] options);

LongAndLotsOfArgs(sender,
                  recipient,
                  publicKey,
                  amount,
                  options);

源文件編碼格式

首選 UTF-8 或 ASCII 編碼。

Imports 規(guī)范

Import 語句應始終放在文件的頂部。

正確寫法:

import "owned";


contract A {
    //...
}


contract B is owned {
    //...
}

錯誤寫法:

contract A {
    //...
}


import "owned";


contract B is owned {
    //...
}

函數(shù)順序

排序有助于讀者識別他們可以調用哪些函數(shù),并更容易地找到構造函數(shù)和 fallback 函數(shù)的定義。

函數(shù)應根據(jù)其可見性和順序進行分組:

  • 構造函數(shù)
  • receive 函數(shù)(如果存在)
  • fallback 函數(shù)(如果存在)
  • 外部函數(shù)(external)
  • 公共函數(shù)(public)
  • 內部(internal)
  • 私有(private)

在一個分組中,把 viewpure 函數(shù)放在最后。

正確寫法:

// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.6.0;
contract A {
    function A() public {
        ...
    }

    receive() external payable {
        // ...
    }

    fallback() external {
        // ...
    }

    // External functions
    // ...

    // External functions that are view
    // ...

    // External functions that are pure
    // ...

    // Public functions
    // ...

    // Internal functions
    // ...

    // Private functions
    // ...
}

錯誤寫法:

// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.6.0;
contract A {

    // External functions
    // ...


    fallback() external {
        // ...
    }
    receive() external payable {
        // ...
    }

    // Private functions
    // ...

    // Public functions
    // ...

    function A() public {
        ...
    }

    function() public {
        ...
    }

    // Internal functions
    // ...
}

表達式中的空格

在以下情況下避免無關的空格:

除單行函數(shù)聲明外,緊接著小括號,中括號或者大括號的內容應該避免使用空格。

正確寫法:

spam(ham[1], Coin({name: "ham"}));

錯誤寫法:

spam( ham[ 1 ], Coin( { name: "ham" } ) );

除外:

function singleLine() public { spam(); }

緊接在逗號,分號之前:

正確寫法:

function spam(uint i, Coin coin) public;

錯誤寫法:

function spam(uint i , Coin coin) public ;

賦值或其他操作符兩邊多于一個的空格:

正確寫法:

x = 1;
y = 2;
long_variable = 3;

錯誤寫法:

x             = 1;
y             = 2;
long_variable = 3;

fallback 和 receive 函數(shù)中不要包含空格:

正確寫法:

receive() external payable {
    //...
}

function() public {
    //...
}

錯誤寫法:

receive () external payable {
    //...
}

function () public {
    //...
}

控制結構

用大括號表示一個合約,庫、函數(shù)和結構。
應該:

  • 開括號與聲明應在同一行。
  • 閉括號在與之前函數(shù)聲明對應的開括號保持同一縮進級別上另起一行。
  • 開括號前應該有一個空格。

正確寫法:

contract Coin {
    struct Bank {
        address owner;
        uint balance;
    }
}

錯誤寫法:

contract Coin
{
    struct Bank {
        address owner;
        uint balance;
    }
}

對于控制結構 ifelse, whilefor 的實施建議與以上相同。

另外,諸如 if, else, while, for 這類的控制結構和條件表達式的塊之間應該有一個單獨的空格,
同樣的,條件表達式的塊和開括號之間也應該有一個空格。

正確寫法:

if (...) {
    //...
}

for (...) {
    //...
}

錯誤寫法:

if (...)
{
    //...
}

while(...){
}

for (...) {
    ...;}

對于控制結構, 如果 其主體內容只包含一行,則可以省略括號。

正確寫法::

if (x < 10)
    x += 1;

錯誤寫法::

if (x < 10)
    someArray.push(Coin({
        name: 'spam',
        value: 42
    }));

對于具有 elseelse if 子句的 if 塊, else 應該是與 if 的閉大括號放在同一行上。 這一規(guī)則區(qū)別于
其他塊狀結構。

正確寫法:

if (x < 3) {
    x += 1;
} else if (x > 7) {
    x -= 1;
} else {
    x = 5;
}


if (x < 3)
    x += 1;
else
    x -= 1;

錯誤寫法:

if (x < 3) {
    x += 1;
}
else {
    x -= 1;
}

函數(shù)聲明

對于簡短的函數(shù)聲明,建議函數(shù)體的開括號與函數(shù)聲明保持在同一行。

閉大括號應該與函數(shù)聲明的縮進級別相同。

開大括號之前應該有一個空格。

正確寫法:

function increment(uint x) public pure returns (uint) {
    return x + 1;
}

function increment(uint x) public pure onlyowner returns (uint) {
    return x + 1;
}

錯誤寫法:

function increment(uint x) public pure returns (uint)
{
    return x + 1;
}

function increment(uint x) public pure returns (uint){
    return x + 1;
}

function increment(uint x) public pure returns (uint) {
    return x + 1;
    }

function increment(uint x) public pure returns (uint) {
    return x + 1;}

你應該嚴格地標示所有函數(shù)的可見性,包括構造函數(shù)。

正確寫法:

function explicitlyPublic(uint val) public {
    doSomething();
}

錯誤寫法:

    function implicitlyPublic(uint val) {
        doSomething();
    }

函數(shù)修改器的順序應該是:

  1. Visibility(可見性)
  2. Mutability(易變性)
  3. Virtual(模擬)
  4. Override(重載)
  5. Custom modifiers(自定義修飾符)

正確寫法:

function balance(uint from) public view override returns (uint)  {
    return balanceOf[from];
}

function shutdown() public onlyowner {
    selfdestruct(owner);
}

錯誤寫法:

function balance(uint from) public override view returns (uint)  {
    return balanceOf[from];
}

function shutdown() onlyowner public {
    selfdestruct(owner);
}

對于長函數(shù)聲明,建議將每個參數(shù)獨立一行并與函數(shù)體保持相同的縮進級別。閉括號和開括號也應該
獨立一行并保持與函數(shù)聲明相同的縮進級別。

正確寫法:

function thisFunctionHasLotsOfArguments(
    address a,
    address b,
    address c,
    address d,
    address e,
    address f
)
    public
{
    doSomething();
}

錯誤寫法:

function thisFunctionHasLotsOfArguments(address a, address b, address c,
    address d, address e, address f) public {
    doSomething();
}

function thisFunctionHasLotsOfArguments(address a,
                                        address b,
                                        address c,
                                        address d,
                                        address e,
                                        address f) public {
    doSomething();
}

function thisFunctionHasLotsOfArguments(
    address a,
    address b,
    address c,
    address d,
    address e,
    address f) public {
    doSomething();
}

如果一個長函數(shù)聲明有修飾符,那么每個修飾符應該下沉到獨立的一行。

正確寫法:

function thisFunctionNameIsReallyLong(address x, address y, address z)
    public
    onlyowner
    priced
    returns (address)
{
    doSomething();
}

function thisFunctionNameIsReallyLong(
    address x,
    address y,
    address z,
)
    public
    onlyowner
    priced
    returns (address)
{
    doSomething();
}

錯誤寫法:

function thisFunctionNameIsReallyLong(address x, address y, address z)
                                      public
                                      onlyowner
                                      priced
                                      returns (address) {
    doSomething();
}

function thisFunctionNameIsReallyLong(address x, address y, address z)
    public onlyowner priced returns (address)
{
    doSomething();
}

function thisFunctionNameIsReallyLong(address x, address y, address z)
    public
    onlyowner
    priced
    returns (address) {
    doSomething();
}

多行輸出參數(shù)和返回值語句應該遵從 代碼行的最大長度 一節(jié)的說明。

正確寫法:

function thisFunctionNameIsReallyLong(
    address a,
    address b,
    address c
)
    public
    returns (
        address someAddressName,
        uint256 LongArgument,
        uint256 Argument
    )
{
    doSomething()

    return (
        veryLongReturnArg1,
        veryLongReturnArg2,
        veryLongReturnArg3
    );
}

錯誤寫法:

function thisFunctionNameIsReallyLong(
    address a,
    address b,
    address c
)
    public
    returns (address someAddressName,
             uint256 LongArgument,
             uint256 Argument)
{
    doSomething()

    return (veryLongReturnArg1,
            veryLongReturnArg1,
            veryLongReturnArg1);
}

對于繼承合約中需要參數(shù)的構造函數(shù),如果函數(shù)聲明很長或難以閱讀,建議將基礎構造函數(shù)像多個修飾符的風格那樣
每個下沉到一個新行上書寫。

正確寫法:

// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.7.0;

// Base contracts just to make this compile
contract B {
    constructor(uint) {
    }
}
contract C {
    constructor(uint, uint) {
    }
}
contract D {
    constructor(uint) {
    }
}

contract A is B, C, D {
    uint x;

    constructor(uint param1, uint param2, uint param3, uint param4, uint param5)
        B(param1)
        C(param2, param3)
        D(param4)
    {
        // do something with param5
        x = param5;
    }
}

錯誤寫法:

// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.22 <0.9.0;


// Base contracts just to make this compile
contract B {
    constructor(uint) {
    }
}


contract C {
    constructor(uint, uint) {
    }
}


contract D {
    constructor(uint) {
    }
}


contract A is B, C, D {
    uint x;

    constructor(uint param1, uint param2, uint param3, uint param4, uint param5)
    B(param1)
    C(param2, param3)
    D(param4)
    public {
        x = param5;
    }
}


contract X is B, C, D {
    uint x;

    constructor(uint param1, uint param2, uint param3, uint param4, uint param5)
        B(param1)
        C(param2, param3)
        D(param4)
        public {
            x = param5;
        }
}

當用單個語句聲明簡短函數(shù)時,允許在一行中完成。

允許:

function shortFunction() public { doSomething(); }

這些函數(shù)聲明的準則旨在提高可讀性。
因為本指南不會涵蓋所有內容,作者應該自行作出最佳判斷。

映射

在變量聲明中,關鍵字“mapping”后面不要跟空格鍵。任何嵌套的mapping關鍵字后也不要跟空格鍵。

正確寫法:

mapping(uint => uint) map;
mapping(address => bool) registeredAddresses;
mapping(uint => mapping(bool => Data[])) public data;
mapping(uint => mapping(uint => s)) data;

錯誤寫法:

mapping (uint => uint) map;
mapping( address => bool ) registeredAddresses;
mapping (uint => mapping (bool => Data[])) public data;
mapping(uint => mapping (uint => s)) data;

變量聲明

數(shù)組變量的聲明在變量類型和括號之間不應該有空格。

正確寫法:

uint[] x;

錯誤寫法:

uint [] x;

其他建議

  • 字符串應該用雙引號而不是單引號。

正確寫法:

str = "foo";
tr = "Hamlet says, 'To be or not to be...'";

錯誤寫法:

str = 'bar';
str = '"Be yourself; everyone else is already taken." -Oscar Wilde';
  • 操作符兩邊應該各有一個空格。

正確寫法:

x = 3;
x = 100 / 10;
x += 3 + 4;
x |= y && z;

錯誤寫法:

x=3;
x = 100/10;
x += 3+4;
x |= y&&z;
  • 為了表示優(yōu)先級,高優(yōu)先級操作符兩邊可以省略空格。這樣可以提高復雜語句的可讀性。你應該在操作符兩邊總是使用相同的空格數(shù):

正確寫法:

x = 2**3 + 5;
x = 2*y + 3*z;
x = (a+b) * (a-b);

錯誤寫法:

x = 2** 3 + 5;
x = y+z;
x +=1;

Order of Layout(布局順序)

按以下順序布置合同結構:

  1. Pragma statements(版本聲明)
  2. Import statements(導入聲明)
  3. Interfaces(接口)
  4. Libraries(庫)
  5. Contracts(合同代碼)

在每個合同、庫或接口中,使用以下順序:

  1. Type declarations(類型聲明)
  2. State variables(狀態(tài)變量)
  3. Events(活動事件)
  4. Functions(函數(shù))

命名規(guī)范

當完全采納和使用命名規(guī)范時會產生強大的作用。
當使用不同的規(guī)范時,則不會立即獲取代碼中傳達的重要 信息。
這里給出的命名建議旨在提高可讀性,因此它們不是規(guī)則,而是透過名稱來嘗試和幫助傳達最多的信息。
最后,基于代碼庫中的一致性,本文檔中的任何規(guī)范總是可以被(代碼庫中的規(guī)范)取代。

命名風格

為了避免混淆,下面的名字用來指明不同的命名方式。

  • b (單個小寫字母)
  • B (單個大寫字母)
  • lowercase (小寫)
  • lower_case_with_underscores (小寫和下劃線)
  • UPPERCASE (大寫)
  • UPPER_CASE_WITH_UNDERSCORES (大寫和下劃線)
  • CapitalizedWords (駝峰式,首字母大寫)
  • mixedCase (混合式,與駝峰式的區(qū)別在于首字母小寫!)
  • Capitalized_Words_With_Underscores (首字母大寫和下劃線)

當在駝峰式命名中使用縮寫時,應該將縮寫中的所有字母都大寫。 因此 HTTPServerError 比 HttpServerError 好。
當在混合式命名中使用縮寫時,除了第一個縮寫中的字母小寫(如果它是整個名稱的開頭的話)以外,其他縮寫中的字母均大寫。
因此 xmlHTTPRequest 比 XMLHTTPRequest 更好。

應避免的名稱

  • l - el的小寫方式
  • O - oh的大寫方式
  • I - eye的大寫方式

切勿將任何這些用于單個字母的變量名稱。 他們經常難以與數(shù)字 1 和 0 區(qū)分開。

合約和庫名稱

合約和庫名稱應該使用駝峰式風格。比如:SimpleTokenSmartBank,CertificateHashRepositoryPlayer,Congress, Owned

  • 合同和庫名稱也應與其文件名匹配.
  • 如果合同文件包含多個合同和/或庫,則文件名應與核心合同匹配。但如果可以避免,則不建議這樣做.

如下面的示例所示,如果合同名為Congress,庫名為Owned,那么它們關聯(lián)的文件名應該是Congress.solOwned.sol

正確寫法:

// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.7.0;


// Owned.sol
contract Owned {
    address public owner;

    constructor() {
        owner = msg.sender;
    }

    modifier onlyOwner {
        require(msg.sender == owner);
        _;
    }

    function transferOwnership(address newOwner) public onlyOwner {
        owner = newOwner;
    }
}

Congress.sol 中:

// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.0 <0.9.0;

import "./Owned.sol";


contract Congress is Owned, TokenRecipient {
    //...
}

錯誤寫法:

// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.7.0;


// owned.sol
contract owned {
    address public owner;

    constructor() {
        owner = msg.sender;
    }

    modifier onlyOwner {
        require(msg.sender == owner);
        _;
    }

    function transferOwnership(address newOwner) public onlyOwner {
        owner = newOwner;
    }
}

Congress.sol中:

import "./owned.sol";


contract Congress is owned, tokenRecipient {
    //...
}

結構體名稱

結構體名稱應該使用駝峰式風格。比如:MyCoin,Position,PositionXY。

事件名稱

事件名稱應該使用駝峰式風格。比如:Deposit,Transfer,ApprovalBeforeTransfer,AfterTransfer

函數(shù)名稱

函數(shù)應該使用混合式命名風格。比如:getBalance,transferverifyOwneraddMember,changeOwner。

函數(shù)參數(shù)命名

函數(shù)參數(shù)命名應該使用混合式命名風格。比如:initialSupplyaccount,recipientAddress,senderAddressnewOwner。
在編寫操作自定義結構的庫函數(shù)時,這個結構體應該作為函數(shù)的第一個參數(shù),并且應該始終命名為 self。

局部變量和狀態(tài)變量名稱

使用混合式命名風格。比如:totalSupplyremainingSupply,balancesOf,creatorAddressisPreSale,tokenExchangeRate。

常量命名

常量應該全都使用大寫字母書寫,并用下劃線分割單詞。比如:MAX_BLOCKSTOKEN_NAME,TOKEN_TICKERCONTRACT_VERSION。

修飾符命名

使用混合式命名風格。比如:onlyBy,onlyAfter,onlyDuringThePreSale。

枚舉命名

在聲明簡單類型時,枚舉應該使用駝峰式風格。比如:TokenGroup,FrameHashStyleCharacterLocation。

避免命名沖突

  • single_trailing_underscore_

當所起名稱與內建或保留關鍵字相沖突時,建議照此慣例在名稱后邊添加下劃線。


描述注釋 NatSpec


Solidity 智能合約有一種基于以太坊自然語言說明格式(Ethereum Natural Language Specification Format)的注釋形式。

在函數(shù)和合約上添加注釋配置符號,
單行或者連續(xù)多行可以使用`///`,
或者使用多行注釋以`/**`開始,以`*/`結尾。 

它們應該直接在函數(shù)聲明或語句上使用。 可在注釋中使用 Doxygen 樣式的標簽來文檔化函數(shù)、 標注形式校驗通過的條件,和提供一個當用戶試圖調用一個函數(shù)時顯示給用戶的 確認性文字。

下面是一個簡單的智能合約,添加配置注釋的樣子:

    pragma solidity >=0.4.16 <0.9.0;

    /// @author The Solidity Team
    /// @title A simple storage example
    contract TinyStorage {
        uint storedData;

        /// Store `x`.
        /// @param x the new value to store
        /// @dev stores the number in the state variable `storedData`
        function set(uint x) public {
            storedData = x;
        }

        /// Return the stored value.
        /// @dev retrieves the value of the state variable `storedData`
        /// @return the stored value
        function get() public view returns (uint) {
            return storedData;
        }
    }

建議對所有公共接口(ABI 中的所有內容)使用上面說推薦的方式對 Solidity 合約進行完全注釋。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容