智能合约最佳实践 之 Solidity 编码规范
人民干脆面
发表于 2022-11-13 23:49:49
120
0
0
命名规范$ P! p. [/ h j; W% Q" H& g% T
避免使用
小写的l,大写的I,大写的O 应该避免在命名中单独出现,因为很容易产生混淆。
合约、库、事件、枚举及结构体命名
合约、库、事件及结构体命名应该使用单词首字母大写的方式,这个方式也称为:帕斯卡命名法或大驼峰式命名法,比如:SimpleToken, SmartBank, CertificateHashRepository,Player。; h& f! o' Z. c2 K7 [
函数、参数、变量及修饰器5 ~& X1 `$ _" m' _ B$ m( D6 F( I
函数、参数、变量及修饰器应该使用首单词小写后面单词大写的方式,这个方式也称为:(小)驼峰式命名法,是一种混合大小写的方式,如:! ?( Z, N$ E! P6 i9 J
函数名应该如:getBalance,transfer,verifyOwner,addMember。参数和变量应该如:initialSupply,senderAddress,account,isPreSale。修饰器应该如:onlyAfter,onlyOwner。
代码格式相关
缩进& C/ [2 K7 V: D' N# G
使用空格(spaces)而不是Tab, 缩进应该是4个空格
空行# M0 n* \: g( O+ e. G4 N+ z; Z0 e
合约之间应该有空行,例如:
contract A {
...( N& v2 u, N/ b! R0 r
}2 g0 O6 t1 N, E$ ]- w5 X. x3 q* ~/ x
# X, J7 i5 F5 R6 f) Y
contract B {: C& T0 O9 S2 H6 U4 q
...
}
contract C {( v) U/ y4 |; d
...0 }; F' n6 h+ @; X
}2 Y- b( s5 y3 S" }7 G
而不是使用:) Z3 D' }: G0 a& y2 q* N
contract A {- S6 N1 a$ B t. v9 S$ E5 ]- P' g
...
}/ P+ f! R) J) y+ Y
contract B {
...
}8 A0 y" A, R6 ]2 B
2 b5 j( o. w# r
contract C {
...
}
函数之间应该有空行,例如:- X( Z: {9 r- w" I9 c
contract A {! V+ o3 Q/ C) D8 } u/ U/ f
function spam() public {
...
}. x0 P' c# s+ z
function ham() public {
...
}- Z+ F+ F) w( U" _5 O1 |/ \
}
没有实现的话,空行可以省去,如:
contract A {
function spam() public;
function ham() public;
}" F1 G! X7 D9 Y9 l8 C
而不是:
contract A {
function spam() public {
...
}: C3 _& E U8 | G5 g0 ]2 e* c1 H% d
function ham() public {- d* s. C+ I% H: V7 g
...
}$ R# J: [' n7 L0 C2 t$ t6 \) K! H
}1 k2 f, B: P: { n/ d& y
左括号应该跟定义在一行
定义包括合约定义、函数定义、库定义、结构体定义等等,例如推荐使用:3 _9 l: p+ t# q
contract Coin {
struct Bank {
address owner;
uint balance;" m! `1 E. F7 q5 ]5 ^
}4 v2 {, {* s+ r7 J: A% n' r+ `
}
而不是:6 t2 m& u% V3 b( q' J3 y
contract Coin
{
struct Bank {
address owner;! i) ^4 o6 w2 U6 s8 Z& u6 y9 \
uint balance;
}
}7 p) Z5 A* u$ l; E! g
左括号应该跟条件控制在一行* ?+ y/ R8 d. R2 L
在使用if, else, while, for 时,推荐的写法是:
if (...) {
..." M, z4 O C1 L6 a# m+ M( x$ b
}
for (...) {
...
}& c2 Q6 e! f# s% _3 Q6 q
而不是:3 ^& i! h# f* z$ T
if (...)
{
...
}$ d9 W% H6 o+ g5 x+ d
while(...){& h2 M1 N. Y9 k6 U U1 l
}
for (...) {0 ~7 b, T3 e! S/ q
...;}5 t6 p7 b% A: P N2 G
如果控制语句内只有一行,括号可省略,如:
if (x
但像下面一个语句有多方就不能省略,如:! I! f0 _9 e/ A( G- A' ~
if (x
表达式内的空格. M, U' U2 z |. |' @4 m5 Q
一个单行的表达里,在小括号、中括号、大括号里应该避免不必要的空格,例如推荐使用:+ I; S. ~( ^9 H- B5 M% T
spam(ham[1], Coin({name: "ham"}));2 o& b4 u$ \5 [% |/ V
而不是:
spam( ham[ 1 ], Coin( { name: "ham" } ) );/ t; g- i7 Y" c' Z/ @
有一种例外是,结尾的括号跟在结束的分号后面, 应该加一个空格,如下面的方式也是推荐的:
function singleLine() public { spam(); }* h. ^6 _$ [$ g8 y0 @: ?
分号;前不应该有空格,例如推荐使用:. Q* g9 Z0 l; H4 y- X
function spam(uint i, Coin coin) public;
而不是:
function spam(uint i , Coin coin) public ;% C1 _ h* r) `/ J2 t7 {
不要为对齐添加不必要的空格,例如推荐使用:
x = 1;3 a8 w" [5 t: B8 @5 `
y = 2;0 g$ O& t6 V, |) r* m
long_variable = 3;' o# o4 m- j- f9 d# [, t2 I
而不是:. m i z/ }+ X9 ?6 u: U
1
2# r4 }! s$ G0 S2 e; J
3
x = 1;
y = 2;& T; V1 P7 `3 S: q& E3 w
long_variable = 3;
回退函数不应该有空格,例如推荐使用:
function() public {) m: W2 w& R3 s: ?" Z% w6 s8 ^
...3 o# T- ~. B5 s1 _* X6 ] c5 h
} V, m9 o; c# K: R* X
而不是:
function () public {
.... @) g3 n; s+ x: }
}
控制每一行长度
每行不应该太长,最好在79(或99)个字符以内,函数的参数应该是单独的行,且只有一个缩进,例如推荐的方式是:% z) x( ^0 q# x
thisFunctionCallIsReallyLong(3 B/ O$ R; ?- C2 H5 Y
longArgument1," _0 z/ I: ^4 W& o4 ^+ N
longArgument2,
longArgument3" ^( i0 W; o% h1 D, r$ m
);
而不是:7 P3 R' s+ I+ ~
thisFunctionCallIsReallyLong(longArgument1,; N- c4 F- {, [, g7 L' q
longArgument2,
longArgument3; l8 r1 m0 w1 `6 v( T, z
);
thisFunctionCallIsReallyLong(longArgument1,
longArgument2,# s3 C; P0 v9 z* r
longArgument3, f5 x, w0 N$ o. y3 z
);
thisFunctionCallIsReallyLong(
longArgument1, longArgument2,
longArgument34 ]" E: _5 W7 H# V
);% H0 E* d q/ ]4 H
thisFunctionCallIsReallyLong(( i) |" }% D o* a# m
longArgument1,% ^* L* q: X$ L: r
longArgument2,
longArgument38 r: R) `& I0 k! Z! G7 X, k
);
thisFunctionCallIsReallyLong($ x! |7 P1 |, D7 n4 P9 @8 x- i" Y
longArgument1,4 H* u* p6 O3 ?5 ~- N& H
longArgument2,# G# B" |! S- l/ g# \3 E7 B4 V
longArgument3);3 E0 ?0 Y2 G c
对应的赋值语句应该是这样写:
thisIsALongNestedMapping[being][set][to_some_value] = someFunction(
argument1,
argument2," s# w: C) g% K+ f/ R, W8 r) Q" C
argument3,
argument4
);
而不是:
thisIsALongNestedMapping[being][set][to_some_value] = someFunction(argument1,
argument2,
argument3,
argument4);
事件定义也应该遵循同样的原则,例如应该使用:
event LongAndLotsOfArgs(6 W+ a- [/ q% k$ z
adress sender,
adress recipient,
uint256 publicKey,2 y* z0 e& f/ A! F+ \8 W9 W1 L z
uint256 amount,6 x$ P# \8 m0 K+ Z+ b
bytes32[] options
);4 |2 D0 E T2 o: R3 D* S
LongAndLotsOfArgs(
sender,
recipient,. x0 N- C. g) ^3 s# D3 n+ l% Z
publicKey,& |$ N+ V) T+ i/ N2 {3 w Z: p
amount,
options7 ^: ~9 c$ C( `4 B, c) I' A
);
而不是:. Z1 Y, C* m4 ^/ k9 b, W( n
event LongAndLotsOfArgs(adress sender,
adress recipient,
uint256 publicKey,
uint256 amount,
bytes32[] options);3 B) h6 t0 v; u1 {) @7 J( b
LongAndLotsOfArgs(sender,
recipient,
publicKey,
amount,
options);
文件编码格式
推荐使用utf-8 及 ASCII 编码
引入文件应该在最上方" o! z. C$ u( {" q: i$ I; x
建议使用:
import "owned";
contract A {
...
}
contract B is owned {7 |4 E2 M' v* \5 s7 Y4 l5 @
...
}
而不是:
contract A {# ?4 p- L$ q& c2 m
...
}
import "owned";6 H$ o9 k* l. R
contract B is owned {
...
}
函数编写规范
函数的顺序* P; ]1 A$ b, ~+ k
在编写函数的时候,应该让大家容易找到构造函数,回退函数,官方推荐的的函数顺序是:
构造函数回退函数 (如果有)外部函数(external)公有函数(public)内部函数(internal)私有函数(private)0 }$ e3 v: S8 y1 Z! R% R
同一类函数时,constant函数放在后面, 例如推荐方式为:
contract A {
// 构造函数
function A() public {2 y( V: Z' n8 f$ u, \5 b
..., ]" v0 o) `: y1 r* v) p2 X O' i
}
// 回退函数3 g" s5 O9 {/ [7 z
function() public {
...
}8 z' }0 {8 P5 h; T k; N2 }- _
// 外部函数2 G) ?1 _/ [. q2 y5 Y' C, T6 y6 G
// .... K* y9 p; a! t4 u, O
// 带有constant 外部函数
// ...
// 公有函数
// ...- S1 ]& z0 m& N
// 内部函数
// ...
// 私有函数
// ...# J- u3 J* E1 }8 L
}9 j/ t: c p6 Y- e9 y# r
而不是下面的函数顺序:4 r8 U; D/ [5 H0 Q; L
contract A {
// 外部函数. @" G9 U+ u P2 Y/ q! |: d$ a. R
// ...
// 公有函数
// ...7 d1 f% w- Y0 y0 c3 m
// 内部函数4 a7 f5 g0 t% N/ Z% w: d' U5 U
// ...9 \+ z' i0 Z9 ~7 l
function A() public {6 H; `+ R( M0 u
...
}1 F& a3 s5 \7 p3 e& X7 `; n) ]
function() public {, U" e, |' g$ K6 X% Q
...
}
// 私有函数7 s. ~9 {1 h$ k; S4 f; N+ P
// ...
}; G4 n+ B% x: I4 q K; O3 q- S5 q
明确函数的可见性
所有的函数(包括构造函数)应该在定义的时候明确函数的可见性,例如应该使用:6 E8 h" o" ^5 O5 ?* y4 P
function explicitlyPublic(uint val) public {5 X4 B/ e- z* Y
doSomething();; _2 s, B4 m7 M( V3 \
}/ Z: A' p. n5 [
而不是
function implicitlyPublic(uint val) {# W* G. k( s2 c5 _* c! ]* S
doSomething();# N9 I4 e4 t6 X. Q% q o0 Z
}
可见性应该在修饰符前面 g7 G! O6 S- B: k1 m# y0 E
函数的可见性应该写在自定义的函数修饰符前面,例如:5 g5 k6 z4 G+ u1 I* Q: i
function kill() public onlyowner {
selfdestruct(owner);
}
而不是! c6 T( P; _9 Y% S( t
function kill() onlyowner public {+ o5 Y" V4 u) R5 S
selfdestruct(owner);
}
区分函数和事件
为了防止函数和事件(Event)产生混淆,声明一个事件使用大写并加入前缀(可使用LOG)。对于函数, 始终以小写字母开头,构造函数除外。
// 不建议/ a" j% j# r$ r" t \8 v5 S
event Transfer() {}
function transfer() {}
// 建议
event LogTransfer() {}: b$ J6 O( B, j# \; n9 R
function transfer() external {}! G5 N) k7 W4 @+ v7 _2 F. p
常量
常量应该使用全大写及下划线分割大词的方式,如:MAX_BLOCKS,TOKEN_NAME, CONTRACT_VERSION。
参考文献
Solidity style-guide
成为第一个吐槽的人