样式指南
介绍
本指南旨在提供编写稳定代码的编码约定。本指南应被视为一个不断发展的文档,随着发现有用的约定和旧约定的过时,该文档将随着时间的推移而变化。
许多项目将实现自己的风格指南。如果发生冲突,以项目特定的样式指南为准。
本样式指南中的结构和许多建议都是从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)个字符可帮助读者轻松解析代码。
包装线应符合以下准则。
- 第一个参数不应附加到左括号中。 
- 只能使用一个缩进。 
- 每一个论点都应该各自为政。 
- 终端元件, - );,应单独放在最后一行。
函数调用
是:
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 {
    // ...
}
功能顺序
排序有助于读者识别他们可以调用哪些函数,并更容易地找到构造函数和回退定义。
应根据功能的可见性和顺序对其进行分组:
- 构造函数 
- 接收功能(如果存在) 
- 回退函数(如果存在) 
- 外部的 
- 公众的 
- 内部的 
- 私有的 
在分组中,将 view 和 pure 最后一个函数。
是:
// 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;
    }
}
同样的建议也适用于控制结构。 if , else , while 和 for .
此外,控制结构之间应该有一个单独的空间 if , while 和 for 以及表示条件的插入块,以及条件插入块和左大括号之间的单个空间。
是:
if (...) {
    ...
}
for (...) {
    ...
}
否:
if (...)
{
    ...
}
while(...){
}
for (...) {
    ...;}
对于主体包含单个语句的控制结构,省略大括号是可以的。 if 语句包含在一行中。
是:
if (x < 10)
    x += 1;
否:
if (x < 10)
    someArray.push(Coin({
        name: 'spam',
        value: 42
    }));
为了 if 具有 else 或 else 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;}
函数的修饰符顺序应为:
- 能见度 
- 易变性 
- 事实上的 
- 重写 
- 自定义修饰符 
是:
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;
布局顺序
按以下顺序排列合同元素:
- pragma语句 
- 导入语句 
- 界面 
- 类库 
- 合同 
在每个合同、库或接口内,使用以下顺序:
- 类型声明 
- 状态变量 
- 事件 
- 功能 
注解
在事件或状态变量中声明接近其使用的类型可能更清楚。
命名约定
命名约定在被广泛采用和使用时非常强大。使用不同的约定可以传达出显著的 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样式命名。示例: - SimpleToken,- SmartBank,- CertificateHashRepository,- Player,- Congress,- Owned.
- 合同名和库名也应与其文件名匹配。 
- 如果合同文件包含多个合同和/或库,则文件名应与 核心合同 .但是,如果可以避免,则不建议这样做。 
如下面的示例所示,如果合同名称是 Congress 类库的名字是 Owned ,则它们的关联文件名应为 Congress.sol 和 Owned.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样式命名。示例: MyCoin , Position , PositionXY .
事件名称
事件应使用capwords样式命名。示例: Deposit , Transfer , Approval , BeforeTransfer , AfterTransfer .
函数名
函数应该使用mixedCase。示例: getBalance , transfer , verifyOwner , addMember , changeOwner .
函数参数名称
函数参数应使用mixedcase。示例: initialSupply , account , recipientAddress , senderAddress , newOwner .
在编写对自定义结构执行操作的库函数时,该结构应为第一个参数,并且应始终命名为 self .
局部变量名和状态变量名
使用混合数据库。示例: totalSupply , remainingSupply , balancesOf , creatorAddress , isPreSale , tokenExchangeRate .
常量
常量的命名应使用所有大写字母,并用下划线分隔单词。示例: MAX_BLOCKS , TOKEN_NAME , TOKEN_TICKER , CONTRACT_VERSION .
修饰符名称
使用混合数据库。示例: onlyBy , onlyAfter , onlyDuringThePreSale .
枚举类型
应使用capwords样式命名简单类型声明样式的枚举。示例: TokenGroup , Frame , HashStyle , CharacterLocation .
避免命名冲突
- 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 详细解释。