样式指南

介绍

本指南旨在提供编写稳定代码的编码约定。本指南应被视为一个不断发展的文档,随着发现有用的约定和旧约定的过时,该文档将随着时间的推移而变化。

许多项目将实现自己的风格指南。如果发生冲突,以项目特定的样式指南为准。

本样式指南中的结构和许多建议都是从Python的 pep8 style guide .

本指南的目标是 not 是编写稳定代码的正确方法或最佳方法。本指南的目标是 一致性 .来自python的一句话 pep8 很好地抓住了这个概念。

注解

风格指南是关于一致性的。与本风格指南的一致性很重要。项目内部的一致性更重要。一个模块或功能的一致性是最重要的。

但最重要的是: 知道什么时候不一致 --有时候风格指南根本不适用。当你有疑问时,用你最好的判断。看看其他的例子,决定什么是最好的。别犹豫,尽管问!

代码布局

缩进

每个缩进级别使用4个空格。

还是空格

空格是首选的缩进方法。

应避免混淆标签和空格。

空行

在solidity源中用两行空行环绕顶级声明。

是:

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

contract A {
    // ...
}


contract B {
    // ...
}


contract C {
    // ...
}

否:

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

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

contract C {
    // ...
}

在一个契约中,用一个空行包围函数声明。

可以省略相关一行程序组之间的空白行(例如抽象合同的存根函数)。

是:

// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.6.0 <0.9.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 {
        // ...
    }
}

否:

// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.6.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. 第一个参数不应附加到左括号中。

  2. 只能使用一个缩进。

  3. 每一个论点都应该各自为政。

  4. 终端元件, ); ,应单独放在最后一行。

函数调用

是:

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);

事件定义和事件发射器

是:

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

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

否:

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

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

源文件编码

首选UTF-8或ASCII编码。

进口

导入语句应始终放在文件的顶部。

是:

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

import "./Owned.sol";

contract A {
    // ...
}

contract B is Owned {
    // ...
}

否:

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

contract A {
    // ...
}


import "./Owned.sol";


contract B is Owned {
    // ...
}

功能顺序

排序有助于读者识别他们可以调用哪些函数,并更容易地找到构造函数和回退定义。

应根据功能的可见性和顺序对其进行分组:

  • 构造函数

  • 接收功能(如果存在)

  • 回退函数(如果存在)

  • 外部的

  • 公众的

  • 内部的

  • 私有的

在分组中,将 viewpure 最后一个函数。

是:

// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.0 <0.9.0;
contract A {
    constructor() {
        // ...
    }

    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.7.0 <0.9.0;
contract A {

    // External functions
    // ...

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

    // Private functions
    // ...

    // Public functions
    // ...

    constructor() {
        // ...
    }

    // Internal functions
    // ...
}

表达式中的空白

在以下情况下,避免出现外来空白:

紧跟在括号、括号或大括号内,但单行函数声明除外。

是:

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;

在receive和fallback函数中不要包含空白:

是:

receive() external payable {
    ...
}

fallback() external {
    ...
}

否:

receive () external payable {
    ...
}

fallback () external {
    ...
}

控制结构

表示合同主体、库、函数和结构的大括号应:

  • 在声明的同一行打开

  • 在声明的开始处以相同的缩进级别结束自己的行。

  • 左大括号前面应该有一个空格。

是:

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

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

否:

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

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

同样的建议也适用于控制结构。 ifelsewhilefor .

此外,控制结构之间应该有一个单独的空间 ifwhilefor 以及表示条件的插入块,以及条件插入块和左大括号之间的单个空间。

是:

if (...) {
    ...
}

for (...) {
    ...
}

否:

if (...)
{
    ...
}

while(...){
}

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

对于主体包含单个语句的控制结构,省略大括号是可以的。 if 语句包含在一行中。

是:

if (x < 10)
    x += 1;

否:

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

为了 if 具有 elseelse if 条款 else 应与 if 的右大括号。与其他类块结构的规则相比,这是一个例外。

是:

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;
}

函数声明

对于短函数声明,建议将函数体的左大括号与函数声明保持在同一行。

右大括号应与函数声明位于同一缩进级别。

左大括号前面应该有一个空格。

是:

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;}

函数的修饰符顺序应为:

  1. 能见度

  2. 易变性

  3. 事实上的

  4. 重写

  5. 自定义修饰符

是:

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);
}

对于长函数声明,建议将每个参数放在与函数体相同缩进级别的行上。右括号和左括号应放在它们自己的行上,并且与函数声明的缩进级别相同。

是:

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();
}

如果一个长函数声明有修饰符,那么每个修饰符都应该放到它自己的行中。

是:

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();
}

多行输出参数和返回语句应遵循在 Maximum Line Length 部分。

是:

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);
}

对于基需要参数的继承协定上的构造函数函数,如果函数声明很长或很难读取,建议将基构造函数以与修饰符相同的方式放到新行上。

是:

// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.0 <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)
    {
        // do something with param5
        x = param5;
    }
}

否:

// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.0 <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) {
        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) {
            x = param5;
        }
}

当用一条语句声明短函数时,允许在一行上声明。

允许的:

function shortFunction() public { doSomething(); }

这些函数声明指南旨在提高可读性。作者应该使用他们的最佳判断,因为本指南不试图涵盖函数声明的所有可能排列。

映射

在变量声明中,不要分隔关键字 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;

说明变量

数组变量的声明在类型和括号之间不应有空格。

是:

uint[] x;

否:

uint [] x;

其他建议

  • 字符串应该用双引号而不是单引号引起来。

是:

str = "foo";
str = "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;
  • 优先级高于其他运算符的运算符可以排除周围的空白以表示优先级。这是为了提高复杂语句的可读性。应始终在运算符的任一侧使用相同数量的空白:

是:

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

否:

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

布局顺序

按以下顺序排列合同元素:

  1. pragma语句

  2. 导入语句

  3. 界面

  4. 类库

  5. 合同

在每个合同、库或接口内,使用以下顺序:

  1. 类型声明

  2. 状态变量

  3. 事件

  4. 功能

注解

在事件或状态变量中声明接近其使用的类型可能更清楚。

命名约定

命名约定在被广泛采用和使用时非常强大。使用不同的约定可以传达出显著的 meta 否则将无法立即获得的信息。

这里给出的命名建议是为了提高可读性,因此它们不是规则,而是通过事物的名称来尝试和帮助传达大多数信息的准则。

最后,代码库中的一致性应始终取代本文档中概述的任何约定。

命名样式

为了避免混淆,以下名称将用于引用不同的命名样式。

  • b (单个小写字母)

  • B (单个大写字母)

  • lowercase

  • lower_case_with_underscores

  • UPPERCASE

  • UPPER_CASE_WITH_UNDERSCORES

  • CapitalizedWords (或大写字母)

  • mixedCase (不同于大写单词的首字母小写字符!)

  • Capitalized_Words_With_Underscores

注解

在CapWords中使用首字母缩写时,请将首字母缩写的所有字母大写。因此HTTPServerError优于HTTPServerError。当在mixedCase中使用首字母时,所有首字母都要大写,但如果首字母是名称的开头,则保留小写字母。因此xmlHTTPRequest优于xmlHTTPRequest。

要避免的名称

  • l -小写字母El

  • O -大写字母Oh

  • I -大写字母Eye

不要将这些用于单字母变量名。它们通常与数字1和0不可区分。

合同和类库名称

  • 合同和类库应使用capwords样式命名。示例: SimpleTokenSmartBankCertificateHashRepositoryPlayerCongressOwned .

  • 合同名和库名也应与其文件名匹配。

  • 如果合同文件包含多个合同和/或库,则文件名应与 核心合同 .但是,如果可以避免,则不建议这样做。

如下面的示例所示,如果合同名称是 Congress 类库的名字是 Owned ,则它们的关联文件名应为 Congress.solOwned.sol .

是:

// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.0 <0.9.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 <0.9.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.7.0;


import "./owned.sol";


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

结构名称

结构应使用capwords样式命名。示例: MyCoinPositionPositionXY .

事件名称

事件应使用capwords样式命名。示例: DepositTransferApprovalBeforeTransferAfterTransfer .

函数名

函数应该使用mixedCase。示例: getBalancetransferverifyOwneraddMemberchangeOwner .

函数参数名称

函数参数应使用mixedcase。示例: initialSupplyaccountrecipientAddresssenderAddressnewOwner .

在编写对自定义结构执行操作的库函数时,该结构应为第一个参数,并且应始终命名为 self .

局部变量名和状态变量名

使用混合数据库。示例: totalSupplyremainingSupplybalancesOfcreatorAddressisPreSaletokenExchangeRate .

常量

常量的命名应使用所有大写字母,并用下划线分隔单词。示例: MAX_BLOCKSTOKEN_NAMETOKEN_TICKERCONTRACT_VERSION .

修饰符名称

使用混合数据库。示例: onlyByonlyAfteronlyDuringThePreSale .

枚举类型

应使用capwords样式命名简单类型声明样式的枚举。示例: TokenGroupFrameHashStyleCharacterLocation .

避免命名冲突

  • single_trailing_underscore_

当所需名称与内置名称或其他保留名称冲突时,建议使用此约定。

NatSpec

稳固性合同还可以包含NatSpec注释。它们是用三个劈开写的 (/// )或双星号挡路 (/** ... */ ),并且它们应该直接在函数声明或语句的上方使用。

例如,来自 a simple smart contract 添加评论后,如下所示:

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

/// @author The Solidity Team
/// @title A simple storage example
contract SimpleStorage {
    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;
    }
}

建议使用 NatSpec 所有公共接口(ABI中的所有内容)。

请参阅关于 NatSpec 详细解释。