智能合约最佳实践 之 Solidity 编码规范
人民干脆面
发表于 2022-11-13 23:49:49
178
0
0
命名规范
避免使用
小写的l,大写的I,大写的O 应该避免在命名中单独出现,因为很容易产生混淆。# ]2 I5 o2 e% C% w5 k; A3 i
合约、库、事件、枚举及结构体命名
合约、库、事件及结构体命名应该使用单词首字母大写的方式,这个方式也称为:帕斯卡命名法或大驼峰式命名法,比如:SimpleToken, SmartBank, CertificateHashRepository,Player。
函数、参数、变量及修饰器
函数、参数、变量及修饰器应该使用首单词小写后面单词大写的方式,这个方式也称为:(小)驼峰式命名法,是一种混合大小写的方式,如:; a& @8 K7 j( ^4 T) k
函数名应该如:getBalance,transfer,verifyOwner,addMember。参数和变量应该如:initialSupply,senderAddress,account,isPreSale。修饰器应该如:onlyAfter,onlyOwner。
' l7 ?) t$ v; W8 {
代码格式相关1 Y$ z% U& s1 P9 f* k3 u( n
缩进8 P7 h9 N. k: ]3 i1 s& ~
使用空格(spaces)而不是Tab, 缩进应该是4个空格
空行; f/ V$ W; l: b6 I+ S
合约之间应该有空行,例如:( y: N" T1 j/ R' [' K$ y
contract A {
...
}
contract B {: E$ N5 X# P/ _
..." H6 k1 F$ r g+ y
}
. L8 f" d5 n( |! {
contract C {/ Z0 O- s4 s, [& |' S) `9 t
...( g4 O) y W4 h! I' n2 ~% Y- l
} u M7 R6 X6 x: a1 [& o
而不是使用:: A4 v2 o' m: n" b4 G0 q
contract A {
...
}( J$ W6 H, B( r4 c3 a* ^7 y
contract B {
...
}
: H4 J7 d" B- \0 l
contract C {
..." x5 y9 l, k4 N9 y
}
函数之间应该有空行,例如:
contract A {
function spam() public {
...
}
function ham() public {$ z/ v/ D, t Z$ L3 x) j
...
}
}
没有实现的话,空行可以省去,如:' W' f R( o7 r; p# _
contract A {
function spam() public;
function ham() public;
} @# }; v. ?! x1 |; i q$ i
而不是: j: q" U0 t Z- [+ t, r& O. M& u
contract A {3 {8 E8 ]+ j9 V4 m. M8 w
function spam() public {" |7 H# Y/ V0 m) x# H, Z
...
}9 I/ [2 }: O( v* M& L
function ham() public {6 _1 v# p* H1 E; h3 L2 r6 M- q
...' E3 w; f$ N8 [8 ]6 ~: X. n; x
}6 }) J8 [5 y4 ]2 q" |6 w. j
}
左括号应该跟定义在一行' T7 g% h* p. Y
定义包括合约定义、函数定义、库定义、结构体定义等等,例如推荐使用:' Y4 ^6 G/ n9 C8 X: S
contract Coin {
struct Bank {
address owner;
uint balance;
}/ V/ P3 W* L6 K) ^/ J4 K: } }
}8 v7 Z9 D$ J2 j) M. @! z! J* @' V. R3 |
而不是:% U: S* Z- f9 x8 B" W
contract Coin7 o1 z. F! |7 e" r' d
{1 P2 z$ i' f& [6 W2 p" z
struct Bank {3 e6 ^. g. X, s+ E
address owner;
uint balance;7 X9 \" r% p0 f5 R, u4 S
}
}6 U/ U% v- B6 M7 ^
左括号应该跟条件控制在一行
在使用if, else, while, for 时,推荐的写法是: p: i, D4 s) N3 U
if (...) {+ s; n( @/ _ S% k ?( S* S$ V/ y
...0 x+ v; v' Z8 I
}% M, b% x5 H; V. W' S' m
for (...) {) t( \" \: a% K* \. P, L9 V3 f3 U
...8 e# g/ T0 @- [! E$ D$ I& q- O& @
}. T( d6 s/ u; ^5 o: F( G3 ]
而不是:
if (...)
{6 m ^* Y9 N# h5 s( `6 R$ n
.../ O/ a# R$ d. H
}
while(...){
}
for (...) {! w( ]: k+ F* a6 ~( v. h
...;}* `- j8 [! n( b5 D$ n& G: c8 ^) t
如果控制语句内只有一行,括号可省略,如:
if (x
但像下面一个语句有多方就不能省略,如:
if (x , ]! v1 Y/ K# p, ^9 v4 p
表达式内的空格2 t8 I+ `0 I6 H& n
一个单行的表达里,在小括号、中括号、大括号里应该避免不必要的空格,例如推荐使用:( } R) S" l M3 ^3 j
spam(ham[1], Coin({name: "ham"}));) F; Q: h( j5 }6 l ~: b* v( y! O% T# L8 f
而不是:! \2 N, g/ |' y7 T
spam( ham[ 1 ], Coin( { name: "ham" } ) );
有一种例外是,结尾的括号跟在结束的分号后面, 应该加一个空格,如下面的方式也是推荐的:% g5 @! L. P4 f: P$ B/ q- o
function singleLine() public { spam(); }/ V' A% X7 b9 z6 s0 Z+ s% ]
分号;前不应该有空格,例如推荐使用:
function spam(uint i, Coin coin) public; s* z6 ~4 _/ |& z
而不是:8 {# J" }0 D: d% Q
function spam(uint i , Coin coin) public ;
不要为对齐添加不必要的空格,例如推荐使用:
x = 1;
y = 2;
long_variable = 3;
而不是:
1+ p2 U$ N9 T- i
2, C7 t5 m; r: h6 J4 S
3+ S' H+ x' H( w6 k
x = 1;
y = 2;9 u5 a- [/ e, }" z. v
long_variable = 3;$ t7 }+ Y6 _) n
回退函数不应该有空格,例如推荐使用:7 ~ a `7 A9 P1 w" x4 t) P
function() public {
...
}
而不是:) M' A) e& X6 x& [# d
function () public {
...
}
控制每一行长度
每行不应该太长,最好在79(或99)个字符以内,函数的参数应该是单独的行,且只有一个缩进,例如推荐的方式是:
thisFunctionCallIsReallyLong(
longArgument1,+ `" }8 w5 [- O" F! I
longArgument2,
longArgument3; A8 u" h8 \+ D7 X: {
);
而不是:
thisFunctionCallIsReallyLong(longArgument1,3 c) }: w: A6 I+ D8 p
longArgument2,; Y, d1 T6 W. K, f: g# a$ O4 N7 z) I# W
longArgument3
);
thisFunctionCallIsReallyLong(longArgument1,8 G: q8 b2 M0 t6 n( H! k1 f. I
longArgument2,
longArgument33 T) c2 Y7 X1 d2 z
);: z: c0 S! `/ r( ]
thisFunctionCallIsReallyLong(9 K1 `3 v6 U" w. C0 w
longArgument1, longArgument2,
longArgument39 k: w7 |! z# I
);! j( o' S9 l* H9 `+ B
thisFunctionCallIsReallyLong(
longArgument1,6 }# L! S/ R" O# S3 N, o
longArgument2," t. J, U# s$ Y% v3 J: W
longArgument3
);; K! N5 {0 M& G; |. {
thisFunctionCallIsReallyLong(9 Y+ ?2 A) M( J/ B3 H, s
longArgument1,! v* }2 F' K7 B. E1 q) {
longArgument2,) C+ v' w+ c# e: y; _& A! Z
longArgument3);
对应的赋值语句应该是这样写:
thisIsALongNestedMapping[being][set][to_some_value] = someFunction(6 T2 T0 A9 A( o
argument1,
argument2,
argument3,
argument4. S5 d( \( u; B) j, [; j
);
而不是:
thisIsALongNestedMapping[being][set][to_some_value] = someFunction(argument1," l2 h" |' z) c# P' i) v* ^8 x+ ?
argument2,5 D2 q# V3 h( {! E6 L$ s
argument3,- j! O# |; x5 Y% {* E1 J% T
argument4);9 Z# d: h- I+ [" j
事件定义也应该遵循同样的原则,例如应该使用:0 J' y0 H5 t6 H$ j) j2 f7 M, o
event LongAndLotsOfArgs(
adress sender,
adress recipient,& s& p9 @3 z* Y' A- C5 t- ]) [
uint256 publicKey,
uint256 amount,7 ^5 x V: p+ G+ g
bytes32[] options
);
LongAndLotsOfArgs(9 V. ~8 I& W8 z1 e) {) \
sender,, j: O& _$ {- Z; m, g0 U" k$ |
recipient,' I$ G* ^* o0 ~8 s# A6 `& V
publicKey,
amount,
options
);
而不是:( L& k4 }3 E! q
event LongAndLotsOfArgs(adress sender,& o# K2 P% @! i+ y
adress recipient, q3 k! {/ ]$ V- d
uint256 publicKey,
uint256 amount,
bytes32[] options);- i* q. }; W. F/ f& r6 x
LongAndLotsOfArgs(sender,
recipient,
publicKey,0 N6 S% ~$ ?8 O5 T2 g9 E
amount,- p( d1 e3 g7 {: `6 A' ^* j' ^' c
options);8 Y- }/ u: N8 _+ G# [5 h
文件编码格式
推荐使用utf-8 及 ASCII 编码
引入文件应该在最上方
建议使用:
import "owned";, U2 b3 g( x1 W8 m+ e
contract A {
..." O+ X8 m, D& p! ]( ?6 W. l
}, [& k2 O' g/ j- p. ?0 e
contract B is owned {% S) P) i- s, p# |
...$ U& @) r& a T B4 L
}
而不是:
contract A {* a1 T$ n6 A# Y" [1 r, c9 J& J
..., [, n" i$ M# y# A- c% g
}9 x6 v4 h0 l/ k* J* b
import "owned";( z3 A# q# i- M3 R) ^3 z M% @
contract B is owned {
...
}
函数编写规范$ t) O& I ]4 M( M. {
函数的顺序
在编写函数的时候,应该让大家容易找到构造函数,回退函数,官方推荐的的函数顺序是:
构造函数回退函数 (如果有)外部函数(external)公有函数(public)内部函数(internal)私有函数(private)9 v/ i. G7 ~ o; T8 k
同一类函数时,constant函数放在后面, 例如推荐方式为:
contract A {' H9 ~0 x+ a, J4 i+ [
// 构造函数
function A() public {- A/ ?. a; X/ i: a0 C' y
...
}. @; X8 y2 Y; A* a Z
// 回退函数# H+ B5 S, m8 r3 F& A: u# T! D( F/ c9 R
function() public {) b; E1 S) w- ~2 i6 a5 S
...
}
// 外部函数& I! J3 a _( C# I% H6 w
// ...
// 带有constant 外部函数
// ...
// 公有函数
// ...
// 内部函数
// ...
// 私有函数3 U# u1 e, K) j: A6 _- {' e# i
// ...' s) D2 J- w! h8 r% b! `
}- y8 F* ~1 \% a1 {
而不是下面的函数顺序:- d: _ U$ n' t9 l
contract A {
// 外部函数
// ...0 x$ p/ l2 F8 K; x
// 公有函数" d$ L! a1 K ?1 x9 q0 g6 }
// ...
// 内部函数
// ...
function A() public {( l3 G9 L9 [" [! y2 _: {' d) g
...
}1 H% a. M9 t+ N0 q, n, `
function() public {
...5 b! N: n$ s% h; K2 p
}
// 私有函数( J4 B8 B7 K, }# K, C/ Y
// ...
}
明确函数的可见性$ k( R' P$ b1 O; `3 d% \" w
所有的函数(包括构造函数)应该在定义的时候明确函数的可见性,例如应该使用:, a+ z+ J* U9 G4 r" `$ U, }
function explicitlyPublic(uint val) public {
doSomething();
}
而不是; W! \: w- z( D! O
function implicitlyPublic(uint val) {+ \- R9 T( Y2 a+ e: y& Y/ v' c
doSomething();2 d0 {/ W& i6 R: T% `5 d- x
}
可见性应该在修饰符前面
函数的可见性应该写在自定义的函数修饰符前面,例如:: F: |) k/ F# R5 [6 [
function kill() public onlyowner {
selfdestruct(owner);" j, Q) U/ s8 F1 Q: y% I
}: [" D6 \2 _6 i7 {& ]4 p
而不是
function kill() onlyowner public {5 n6 H. s0 l. L9 K
selfdestruct(owner);7 K7 T" S& W1 v z/ i
}0 _" y6 _- p8 K( j
区分函数和事件; |& t1 g6 {, E) [+ \4 h! D: I- ?
为了防止函数和事件(Event)产生混淆,声明一个事件使用大写并加入前缀(可使用LOG)。对于函数, 始终以小写字母开头,构造函数除外。
// 不建议
event Transfer() {}
function transfer() {}
// 建议
event LogTransfer() {}
function transfer() external {}* N9 h) O9 u" J- ~5 u
常量
常量应该使用全大写及下划线分割大词的方式,如:MAX_BLOCKS,TOKEN_NAME, CONTRACT_VERSION。
参考文献( ]2 k9 j a I% E: `5 @" |/ h0 T
Solidity style-guide
成为第一个吐槽的人