1.6.8 Solidity编码风格

本节所罗列的一些条款是Solidity中约定俗成的规则,随着新版本Solidity的推出,未来还会有更多的规范加入进来。Solidity中的很多风格和规范都借鉴自Python的pep 8编码风格。这些风格和规范并不要求每一个程序员都一尘不变地遵循,而是指导程序员写出的代码在风格上要能前后一致。

1.6.8.1 代码布局

1)缩进格式

每次缩进四个空格。

2)跳格(Tab)和空格(Space)

建议使用空格缩进,尽量避免跳格与空格混用。

3)空行

定义的首行与上一行之间空两行。如下例所示。

推荐格式:

contract A {

...

}

contract B {

...

}

contract C {

...

}

不推荐格式:

contract A {

...

}

contract B {

...

}

contract C {

...

}

函数之间应该有空行,同类型的单行定义表达式之间不空行。

推荐格式:

contract A {

function spam() public;

function ham() public;

}

contract B is A {

function spam() public {

...

}

function ham() public {

...

}

}

不推荐格式:

contract A {

function spam() public {

...

}

function ham() public {

...

}

}

4)每行最大长度

建议每行最大长度为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(

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

5)源码编码规范

推荐使用UTF-8或ASCII编码。

6)导入操作(Import)

Import语句建议放在文件头。

推荐格式:

import "owned";

contract A {

...

}

contract B is owned {

...

}

不推荐格式:

contract A {

...

}

import "owned";

contract B is owned {

...

}

7)函数排序

良好的函数排序能让用户对该调用什么函数,该怎么调用函数一目了然。建议函数的排序按照其可见性归类,通常顺序如下:构造函数(constructor),回退函数(fallback function)(如果有),外部函数(external),公共函数(public),内部函数(internal)和私有函数(private)。在同一类函数中,用关键字view和pure定义的函数放末尾。

推荐格式:

contract A {

function A() public {

...

}

function() public {

...

}

// External functions

// ...

// External functions that are view

// ...

// External functions that are pure

// ...

// Public functions

// ...

// Internal functions

// ...

// Private functions

// ...

}

不推荐格式:

contract A {

// External functions

// ...

// Private functions

// ...

// Public functions

// ...

function A() public {

...

}

function() public {

...

}

// Internal functions

// ...

}

8)表达式中的空格

在下列情形中,尽量避免多余的空格。

紧挨括号,不用空格,单行函数定义除外。

推荐格式:

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;

回退函数不要加空格。

推荐格式:

function() public {

...

}

不推荐格式:

function () public {

...

}

9)控制结构

标识合约,函数和结构体的花括号尽量遵循下列规则:

- 起始括号和函数定义在同一行。

- 末尾括号单独一行,和定义起始对齐。

- 起始括号前留一个空格。

推荐风格:

contract Coin {

struct Bank {

address owner;

uint balance;

}

}

不推荐风格:

contract Coin

{

struct Bank {

address owner;

uint balance;

}

}

此规则适用于if,else,while和for语句。

另外,在if,while,for关键字后和条件表达式的起始括号之前留一个空格,同时在条件表达式的末尾括号和花括号之间留一个空格。

推荐风格:

if (...) {

...

}

for (...) {

...

}

不推荐风格:

if (...)

{

...

}

while(...){

}

for (...) {

...;}

在流程控制语句中,如果语句只有一行,可以省略括号。

推荐风格:

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;

}

10)函数定义

如果函数定义比较短,建议函数体的起始花括号和函数定义在同一行,且起始花括号前留一个空格。函数体的末尾花括号和函数定义的起始位置对齐。

推荐格式:

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 explicitlyPublic(uint val) public {

doSomething();

}

不推荐风格:

function implicitlyPublic(uint val) {

doSomething();

}

可见性修饰符应该放在所有其它修饰符之前。

推荐风格:

function kill() public onlyowner {

selfdestruct(owner);

}

不推荐风格:

function kill() 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();

}

如果函数有多个返回值,其风格与“每行最大长度”中建议的规则相同。

推荐风格:

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

}

对于继承自基类合约的继承合约,如果基类合约中的构造函数有输入参数,则在定义其继承合约的构造函数时,把每个基类合约的构造函数单独放一行。

推荐风格:

contract A is B, C, D {

function A(uint param1, uint param2, uint param3, uint param4, uint param5)

B(param1)

C(param2, param3)

D(param4)

public

{

// do something with param5

}

}

不推荐风格:

contract A is B, C, D {

function A(uint param1, uint param2, uint param3, uint param4, uint param5)

B(param1)

C(param2, param3)

D(param4)

public

{

// do something with param5

}

}

contract A is B, C, D {

function A(uint param1, uint param2, uint param3, uint param4, uint param5)

B(param1)

C(param2, param3)

D(param4)

public {

// do something with param5

}

}

当一个函数很短,且函数体只有一行语句时,可以将定义与函数体合并放在一行。如下例所示。

function shortFunction() public { doSomething(); }

11)变量定义

在定义数组时,类型与起始括号之间不留空格。

推荐风格:

uint[] x;

不推荐风格:

uint [] x;

12)其它

字符串用双引号而不要用单引号。

推荐风格:

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.6.8.2 命名习惯

当广泛使用一个统一的命名规范时,代码的可读性会极大加强。下列规则供参考。

尽量避免使用小写字母l,大写字母I,大写字母O。这些字母容易和数字1以及数字0相混淆。

合约及库的名字中每个单词首写字母大写,如SimpleToken,SmartBank,BaseLibrary,Player和 CertificateHashRepository。

结构体的名字中每个单词首写字母大写,如MyCoin,Position和PositionXY。

事件(event)的名字中每个单词首写字母大写,如Deposit,Transfer,Approval, BeforeTransfer和AfterTransfer。

非构造函数的函数名中第一个单词首写字母小写,后面的单词每个首写字母大写,如getBalance, transfer,verifyOwner,addMember和changeOwner。

函数参数名中第一个单词首写字母小写,后面的单词每个首写字母大写,如initialSupply,account,recipientAddress,senderAddress和newOwner。

写库函数时,如果该库函数对某个自定义结构体进行操作,则该结构体应该为第一个参数,且命名为‘self’。

局部及状态变量名中第一个单词首写字母小写,后面的单词每个首写字母大写,如totalSupply,remainingSupply,balancesOf,creatorAddress,isPreSale和tokenExchangeRate。

常量(constant)的命名中,每个单词都要大写,且单词间用下划线相连,如:MAX_BLOCKS,TOKEN_NAME,TOKEN_TICKER和CONTRACT_VERSION。

修饰符名中第一个单词首写字母小写,后面的单词每个首写字母大写,如:onlyBy,onlyAfter和onlyDuringThePreSale。

枚举类型名中每个单词的首写字母都大写,如:TokenGroup,Frame,HashStyle和CharacterLocation。

尽量避免使用系统保留的关键字命名。