智能合约最佳实践 之 Solidity 编码规范
人民干脆面
发表于 2022-11-13 23:49:49
119
0
0
命名规范 s( M5 C& s3 O4 j, Y! H- m0 f
避免使用
小写的l,大写的I,大写的O 应该避免在命名中单独出现,因为很容易产生混淆。
合约、库、事件、枚举及结构体命名+ `; T% j( \5 ^& I: |3 \$ l
合约、库、事件及结构体命名应该使用单词首字母大写的方式,这个方式也称为:帕斯卡命名法或大驼峰式命名法,比如:SimpleToken, SmartBank, CertificateHashRepository,Player。/ _- M* M' v: R9 v1 ^0 H& D1 d3 m
函数、参数、变量及修饰器1 B+ e- m6 |3 i1 p5 k2 S
函数、参数、变量及修饰器应该使用首单词小写后面单词大写的方式,这个方式也称为:(小)驼峰式命名法,是一种混合大小写的方式,如:
函数名应该如:getBalance,transfer,verifyOwner,addMember。参数和变量应该如:initialSupply,senderAddress,account,isPreSale。修饰器应该如:onlyAfter,onlyOwner。
E, b+ r1 s+ X! f9 h
代码格式相关
缩进
使用空格(spaces)而不是Tab, 缩进应该是4个空格7 a( L. Z6 _! n1 B/ E
空行
合约之间应该有空行,例如:
contract A {# F! }- Y+ k0 H* t& V
...
}3 H8 [3 F' k4 d, y
contract B {7 b6 N8 v8 L6 b( ^
...+ A n9 \8 m4 N1 _1 f e) x
}
: Y7 I7 C# v5 o9 o O
contract C {3 Q+ J+ V2 ]% U0 g2 s- h, u
...
}% t! t8 O9 \/ F* Q" H# M/ l
而不是使用:8 ~' a3 Y: T! X+ l
contract A {
...
}
contract B {7 G9 F! B. B# K- k
...
}+ o. t, [. x% K0 x
contract C {
...
}
函数之间应该有空行,例如:) o0 A% U: c% I5 ]! g7 X. d9 E
contract A {8 m4 V$ U# x; M3 O( T, `" z q& J M9 J
function spam() public {
...
}
function ham() public {& Q8 r: j5 i5 U4 x4 z/ P4 [
...
}
}4 k' ?' o1 I, N
没有实现的话,空行可以省去,如:, T& }; c! V9 e
contract A {
function spam() public;
function ham() public;
}6 ]" z: ?+ }' a
而不是:! J3 F6 j+ u- M& z- r
contract A { m! G7 G4 h( B0 ]: y$ Q
function spam() public {" ]) n# v' K/ O6 T" W1 ? U1 z S1 C
...
}: w3 |& T$ s* G2 g6 R
function ham() public {' v8 E4 Z# ~4 U1 a" o
...
}" ^0 R9 X( t& S9 U3 g9 |! G
} S( n* c/ t: p! l8 n- {
左括号应该跟定义在一行
定义包括合约定义、函数定义、库定义、结构体定义等等,例如推荐使用:0 b) y; a! A2 I$ v
contract Coin {9 O" C% J- F4 b% q. `8 U6 M
struct Bank {
address owner;
uint balance;
}1 Q+ l5 F/ D0 p$ D% V& h8 K+ L. E
}
而不是:
contract Coin
{
struct Bank {
address owner;! x5 I$ F: _" @- ^
uint balance;
}- Q. R- x' A1 S% Y1 _: L L5 }
}
左括号应该跟条件控制在一行. n! h6 {/ z+ J! M, M% I
在使用if, else, while, for 时,推荐的写法是:
if (...) {0 Y, a- v/ J) _+ h, e; |
...
}
for (...) {0 s' c. L" S7 s; h: B
...
}( t3 _9 Z- ?' w5 U$ a8 x, o8 t# n- \
而不是:8 C) F0 \/ A1 K9 I! \9 Z
if (...)- E# K% C- q) g- t3 X$ X& i
{
...5 s$ l g( f- H; y+ L
}$ Y4 e2 j) F3 ]! \+ u& d6 }
while(...){" G$ G- c7 c3 A( a* N& l
}% X* [* p F* y
for (...) {
...;}
如果控制语句内只有一行,括号可省略,如:+ H+ p) ?" k% u9 Z4 _ s4 s9 G! o8 Z, x
if (x , y/ Y5 O/ G& R1 f. D" j; R
但像下面一个语句有多方就不能省略,如:
if (x 7 x3 s- @) D0 y
表达式内的空格3 P Q% m! z! F: K
一个单行的表达里,在小括号、中括号、大括号里应该避免不必要的空格,例如推荐使用:' O' I* M* g( A% O) [
spam(ham[1], Coin({name: "ham"}));
而不是:/ \+ [3 l9 Q1 [6 h2 V( c- O4 ]
spam( ham[ 1 ], Coin( { name: "ham" } ) );
有一种例外是,结尾的括号跟在结束的分号后面, 应该加一个空格,如下面的方式也是推荐的:
function singleLine() public { spam(); }5 i; `6 n+ g, n2 N3 Z1 k& j
分号;前不应该有空格,例如推荐使用:
function spam(uint i, Coin coin) public;5 H6 n8 d2 `; s2 [- `
而不是:
function spam(uint i , Coin coin) public ;- h: F6 ]/ g, C" _
不要为对齐添加不必要的空格,例如推荐使用:
x = 1;& h" G0 `9 K' ]: A' {
y = 2;0 X, n, l+ k7 o, l5 h7 p$ q8 r
long_variable = 3;
而不是:1 J" a: A' d; [ Y
1
2
3* H& q$ S0 Z( F/ i
x = 1;6 ^, A: R! F$ W) @( }
y = 2;
long_variable = 3;
回退函数不应该有空格,例如推荐使用:% ]) m/ `. E$ u# V0 k
function() public {
...
}
而不是:
function () public {
...
}
控制每一行长度( O5 E, c9 D! ~% e! V+ `% X
每行不应该太长,最好在79(或99)个字符以内,函数的参数应该是单独的行,且只有一个缩进,例如推荐的方式是:+ f( T* T7 d: p$ _& Q$ B
thisFunctionCallIsReallyLong( u! k& T0 b8 x5 b
longArgument1,
longArgument2,
longArgument3! x0 r: V7 Y. m+ @
);
而不是:
thisFunctionCallIsReallyLong(longArgument1,
longArgument2,6 v- I% A' ^' z }. B
longArgument3# F1 q" }. R2 r- g( y
);& ~9 `% z' y7 f% {* |. a7 T
thisFunctionCallIsReallyLong(longArgument1,: i o- P* ~' s) ^
longArgument2,
longArgument39 h, w! `! h4 V( Z( h
);
thisFunctionCallIsReallyLong(
longArgument1, longArgument2,+ ]' X* o' n( | D+ s
longArgument3
);; s6 e& p# L% {6 d; E
thisFunctionCallIsReallyLong(
longArgument1,2 M$ i( N; I5 z5 o7 N. |: m
longArgument2,3 e6 U% I- R8 P6 ^, y
longArgument3
);
thisFunctionCallIsReallyLong(
longArgument1,$ ~. }( f" p$ W
longArgument2,
longArgument3);
对应的赋值语句应该是这样写:8 i" f6 j0 M- Y& s9 a! F
thisIsALongNestedMapping[being][set][to_some_value] = someFunction(1 j0 {$ p% ~: C4 ?( G
argument1,& Z2 u0 p. ~3 E+ I
argument2,% u) p C2 Z0 z& d+ Q# m# Z# f
argument3,
argument4
);# }, F2 p$ r: a7 b9 B @: g
而不是:
thisIsALongNestedMapping[being][set][to_some_value] = someFunction(argument1,, @. H3 W3 h k7 M
argument2,2 n% P9 W" |/ M# K( R p" W
argument3,2 V% N0 u; t, h
argument4);9 z9 p, B( T& T- i# Z; c2 t! i
事件定义也应该遵循同样的原则,例如应该使用:
event LongAndLotsOfArgs($ b( p% e m$ I q
adress sender,0 S3 f7 L1 N+ H9 }- K, j% o
adress recipient,: a6 L4 f" @' {$ X( F1 w* a
uint256 publicKey,3 _# u8 `$ _3 f. c
uint256 amount,
bytes32[] options2 V3 Y* r! P4 d: E' V$ Q X, `1 [
);
LongAndLotsOfArgs(: F# W$ J$ f/ r
sender,4 l; u* q% ]) H& C. A2 x$ ~
recipient,7 X' E2 U6 n3 M/ X; l
publicKey,' Q! |* p2 e9 D
amount,$ p2 b, I% r G0 { n4 @& b
options+ I, f. U l( u* X
);
而不是:
event LongAndLotsOfArgs(adress sender,
adress recipient,( e* |$ w3 A( ~
uint256 publicKey,
uint256 amount,5 N8 h$ L- @1 \7 d; ]' {/ B$ i
bytes32[] options);& E- Z" o9 }" @0 ?
LongAndLotsOfArgs(sender,
recipient,3 F6 {7 K9 @3 I
publicKey,
amount,
options);6 R4 I1 o& ]- `+ E, M
文件编码格式
推荐使用utf-8 及 ASCII 编码
引入文件应该在最上方
建议使用:
import "owned";
contract A {8 d+ D% h* f" Y4 T7 `7 h0 L/ ^
...4 {/ _" V5 J6 R5 o; u! w
}* l" E P" d1 i/ g$ Q
contract B is owned {
...
}" \& ~5 s4 X9 c0 ]# b
而不是:5 Q7 m1 i( J! F2 m) `: a' E
contract A {4 {2 G/ {! e& \3 q7 O8 B2 K# ?) v
...3 k) [. \ b, ?- n, B3 E ]! j
}1 Z1 T0 p2 e# P6 X+ {8 J3 J
import "owned";
contract B is owned {* W# Z; N |- G' a0 I9 d8 z
...
}
函数编写规范) f+ m6 I: Q9 l! i4 [ w! ^
函数的顺序0 }# A V8 E x7 Z1 b
在编写函数的时候,应该让大家容易找到构造函数,回退函数,官方推荐的的函数顺序是:
构造函数回退函数 (如果有)外部函数(external)公有函数(public)内部函数(internal)私有函数(private)
同一类函数时,constant函数放在后面, 例如推荐方式为:0 ?: @ D' x9 K3 g' e& `1 u) ?
contract A {% |( x4 G) d3 Q, j+ w$ G9 I
// 构造函数
function A() public {$ R) i& C6 b, K7 }: D
...
}
// 回退函数
function() public {
...( p+ n# o u1 ]0 a0 l) w+ `" Y& d: u
}- {- v( o; w. M T- }& L; Z
// 外部函数" {; n0 z) ^1 ^' t9 ~+ a- T. [
// ...% d* s: J; g% E- P+ H$ L
// 带有constant 外部函数
// ...7 R+ ^* @3 N+ d! V
// 公有函数1 e4 g2 G; `8 _% c
// ...( {- o6 J! T5 D- u( w
// 内部函数9 ?) b( u/ |7 E8 M
// ...4 f: H2 S; ?0 L! f) s# ~
// 私有函数2 G+ x8 h) Z+ u! Y
// ...
}3 `8 v) J" E8 A6 ?& r" Z1 p: g
而不是下面的函数顺序:
contract A {8 N4 {# Y% |% b- C0 j* ^1 b
// 外部函数
// ...
// 公有函数' T% h9 p% u7 S7 f# X
// ...3 q |/ c3 s2 ^9 K% Z B
// 内部函数+ O) v* j4 H: `+ Q1 T+ b3 x0 w
// ...) }0 E( `/ t" L+ d
function A() public {
...
}3 U" \$ \* T7 F% z/ r" ~
function() public {: R! b9 a0 j; m- l0 a D c {' z
...
}
// 私有函数- @9 B* g0 @" n. k) J; {
// ...
}) \ N6 u5 \4 B5 o+ X% b
明确函数的可见性& Y+ i$ w) D7 M/ d" g# q! N" l
所有的函数(包括构造函数)应该在定义的时候明确函数的可见性,例如应该使用:( @! t! K3 M9 R0 L# s
function explicitlyPublic(uint val) public {- p4 B: `, x8 h
doSomething();; C6 D; R8 w" Q4 k8 b
}# d3 K, t& o# {+ K6 [: I
而不是. H$ H1 S k2 l: x" ~" \/ T4 R
function implicitlyPublic(uint val) {
doSomething();; j1 M7 e/ { c! G o3 L" [9 }) ]
}
可见性应该在修饰符前面
函数的可见性应该写在自定义的函数修饰符前面,例如:( X* E% }2 j+ i' E3 u: P [% t1 w. G
function kill() public onlyowner {# q" M0 N" @: o7 E
selfdestruct(owner);
}, L, C, S! Z9 f. D3 Y4 X4 y
而不是& L; L8 u& s. `- }0 r! R$ z
function kill() onlyowner public {
selfdestruct(owner);
}
区分函数和事件
为了防止函数和事件(Event)产生混淆,声明一个事件使用大写并加入前缀(可使用LOG)。对于函数, 始终以小写字母开头,构造函数除外。
// 不建议. a6 q' W! N% N2 K7 a) {5 k, v
event Transfer() {}
function transfer() {}/ V. w( l! t! s# u5 }/ ?7 C
// 建议5 d5 O" ]! Q4 ~
event LogTransfer() {}5 k1 S; m6 {& f) u% M U
function transfer() external {}% p8 _$ D# D* D n; ]5 \ s
常量2 x( {$ B% U0 N) n. d( [
常量应该使用全大写及下划线分割大词的方式,如:MAX_BLOCKS,TOKEN_NAME, CONTRACT_VERSION。
参考文献
Solidity style-guide
成为第一个吐槽的人