样式指南
介绍
本指南旨在提供编写稳定代码的编码约定。本指南应被视为一个不断发展的文档,随着发现有用的约定和旧约定的过时,该文档将随着时间的推移而变化。
许多项目将实现自己的风格指南。如果发生冲突,以项目特定的样式指南为准。
本样式指南中的结构和许多建议都是从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
-小写字母ElO
-大写字母OhI
-大写字母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 详细解释。