智能合约最佳实践 之 Solidity 编码规范
人民干脆面
发表于 2022-11-13 23:49:49
175
0
0
命名规范; ?" \3 |- E4 e& Z2 r- g3 G4 j" }7 f
避免使用
小写的l,大写的I,大写的O 应该避免在命名中单独出现,因为很容易产生混淆。0 J9 j, K9 _2 R$ l9 S4 ~7 l
合约、库、事件、枚举及结构体命名! f8 r. z; r% e5 ~% }& O7 z0 `
合约、库、事件及结构体命名应该使用单词首字母大写的方式,这个方式也称为:帕斯卡命名法或大驼峰式命名法,比如:SimpleToken, SmartBank, CertificateHashRepository,Player。
函数、参数、变量及修饰器
函数、参数、变量及修饰器应该使用首单词小写后面单词大写的方式,这个方式也称为:(小)驼峰式命名法,是一种混合大小写的方式,如:1 [+ ?9 S+ i0 h( s1 T
函数名应该如:getBalance,transfer,verifyOwner,addMember。参数和变量应该如:initialSupply,senderAddress,account,isPreSale。修饰器应该如:onlyAfter,onlyOwner。
代码格式相关9 c; k8 L7 N- }) b) K9 a. f7 c
缩进! Z- }! f. c" E! t9 e- d, _: e
使用空格(spaces)而不是Tab, 缩进应该是4个空格
空行4 `. Z* I" e7 s& _8 ]8 n# w( B& `5 X
合约之间应该有空行,例如:
contract A {
...* I' G" Z& {7 t2 w* m
}
contract B {( a: k# h3 ~( J# ?9 }
... w9 ], H' { D6 Y) S
}
}% Y9 w" _; z4 L' ~+ @( u+ [
contract C {& z/ Z8 g2 C0 | e; b+ u! l
...$ v# _2 p6 ~3 ~0 q, O* [: l
}
而不是使用:
contract A {" j8 \) h# X( N" @5 L. ~
...
}
contract B {6 ?, N0 R7 \ N
...
}
contract C {6 k4 W d, r) _4 V+ s$ T
...& Y5 P [3 o9 {: Q, [9 P
}6 j4 r; a( [) S/ g
函数之间应该有空行,例如:
contract A {1 j% F. q2 W. i2 N- M, q
function spam() public {7 q; { m# J+ X, Q- E8 E
...
}
" F7 g6 F9 J/ d6 l7 @4 {) T
function ham() public {# b4 q3 y g. G) `2 C+ A! L
...7 _, P5 s8 O) M# o" R9 V: {' {
}
}
没有实现的话,空行可以省去,如:
contract A {
function spam() public;0 a* z! h/ E0 z0 f9 |. `% I. F
function ham() public;& F" ]2 c4 H Y+ c" E. ?- i0 T
}
而不是:( j) ?: Q9 ]" o. x
contract A {
function spam() public {
...
}; j8 g6 x6 s/ g
function ham() public {' f" `+ R/ A. t b7 c
...
}
}8 T$ s- ]5 u$ C; c0 u: q- b( F& A) `; t
左括号应该跟定义在一行2 ]& j. |+ @2 ?9 g# |
定义包括合约定义、函数定义、库定义、结构体定义等等,例如推荐使用:2 [5 h- M1 O5 M& W& [
contract Coin {# Y+ ~6 w' j* f
struct Bank {' }# i8 W) }5 p* ?* i3 \; d. i4 z
address owner;
uint balance;
}3 O- n" d# |9 f5 ^+ K" R
}, q/ k0 c- p5 x
而不是:
contract Coin2 b% f3 Y) g: _
{/ ~/ f+ Q8 T2 R9 y3 p( f2 O& _
struct Bank {
address owner;$ _4 x* q- s0 S5 Q5 w7 Z* B, |
uint balance;
} c5 T3 ` c Y- J4 ^
}9 _) y A4 ]. d
左括号应该跟条件控制在一行
在使用if, else, while, for 时,推荐的写法是:
if (...) {
...
}
for (...) {
...; `4 f1 ?% l$ p0 E K6 F9 k7 `9 W
}+ S- F7 I, }. q1 g% J3 O8 |
而不是:
if (...)
{+ v- y! m o1 g# r F$ ]8 g
...0 q! t% f6 \4 s) W
}7 L2 Y) }: e) g# g$ d+ h8 i
while(...){; a! {$ z$ b. P# B# E; I$ P; p
}
for (...) {0 a7 G A( g2 V c
...;}$ y' r1 N! U6 m7 A1 Z$ q
如果控制语句内只有一行,括号可省略,如:
if (x * r4 k8 c' v- C5 Q3 {" V) E/ a
但像下面一个语句有多方就不能省略,如:
if (x " ^ j$ M" b* e
表达式内的空格) I2 U$ ?, Q# ?2 B# B1 Y
一个单行的表达里,在小括号、中括号、大括号里应该避免不必要的空格,例如推荐使用:- W3 M! z. x; ~! g" I
spam(ham[1], Coin({name: "ham"}));- y. `6 H; C6 g0 G- X' U
而不是:1 R" @9 v$ v( n8 M
spam( ham[ 1 ], Coin( { name: "ham" } ) );
有一种例外是,结尾的括号跟在结束的分号后面, 应该加一个空格,如下面的方式也是推荐的:- \+ N. c9 \ Q1 {- G ~0 i# U5 `
function singleLine() public { spam(); }5 z5 | l# g6 i7 M) c+ z. s
分号;前不应该有空格,例如推荐使用:
function spam(uint i, Coin coin) public;
而不是:
function spam(uint i , Coin coin) public ;+ p7 r% ~4 T! T8 U: T. W
不要为对齐添加不必要的空格,例如推荐使用:5 H/ M( c% X }; D
x = 1;
y = 2;; _! _6 A! W6 i# t$ q Q4 A
long_variable = 3;* V c( o, n0 o2 W0 c4 C
而不是:- W* s: B" R/ e0 ~. l1 y E
1
2
3
x = 1;
y = 2;3 ?2 T0 b/ H L- Q+ F) R; e; ~* ~2 E
long_variable = 3;
回退函数不应该有空格,例如推荐使用:- r/ x! r3 t9 n9 P& s
function() public {
.... c& Q# ?; {, j) R: |( p) v
}
而不是:
function () public {
.../ e/ H2 e& N% i% z; W0 S. z8 c. P
}
控制每一行长度
每行不应该太长,最好在79(或99)个字符以内,函数的参数应该是单独的行,且只有一个缩进,例如推荐的方式是:
thisFunctionCallIsReallyLong(6 O& q) r2 |; a' m5 |; t& ~
longArgument1,
longArgument2,% X" @7 b5 b0 _5 h
longArgument3
);
而不是:
thisFunctionCallIsReallyLong(longArgument1,% s# N4 `- D& d( Q
longArgument2,8 @. z+ Y% H! A; [/ L5 W
longArgument30 D' ?% A/ {( N4 {8 F
);9 M8 \) K& b- u& Z2 ^& k
thisFunctionCallIsReallyLong(longArgument1,
longArgument2,$ l7 s! Y( J# X4 D
longArgument3
);
thisFunctionCallIsReallyLong(
longArgument1, longArgument2,
longArgument3: o9 s; E9 |" k0 u p
);1 T( i& l& ]( ? P
thisFunctionCallIsReallyLong(
longArgument1,
longArgument2,7 }$ S8 F d7 c. Q }, @
longArgument3# G% m& Y: ~ d! v; y6 S5 w* _
);, L& K' X3 f$ d! x
thisFunctionCallIsReallyLong(
longArgument1,2 r* W T( l3 n8 f& o$ k
longArgument2,4 w; o7 K8 } c) v9 a5 L' q
longArgument3);
对应的赋值语句应该是这样写:/ b7 _/ J7 v1 w3 a- X
thisIsALongNestedMapping[being][set][to_some_value] = someFunction(, [9 L3 S' R7 q; O1 K+ K1 @3 E
argument1,# u, f: ~% J3 M6 N
argument2,& x t3 ]4 N1 ?! ]
argument3,/ g8 j( [/ \. d% c3 \" w
argument4
);
而不是:
thisIsALongNestedMapping[being][set][to_some_value] = someFunction(argument1,, E6 ^8 N8 }0 u* l% v3 ^3 K# h
argument2,
argument3,; `8 W# {& q5 G8 O
argument4);
事件定义也应该遵循同样的原则,例如应该使用:* i i/ V/ ]% y- T* z2 b
event LongAndLotsOfArgs(
adress sender,
adress recipient,( M, T2 M, |% b1 f8 O5 o$ y
uint256 publicKey,
uint256 amount,2 N( R* S5 e6 L
bytes32[] options. v0 E4 Y: ?# q$ E& T
);. j3 O8 Y9 B3 G: Z: @. m
LongAndLotsOfArgs(+ Y: d! {3 K1 ?0 x
sender,3 y2 C- Y3 |- C
recipient,& u( s: T1 j& n. @- m4 l1 @
publicKey,4 K& W& h1 S1 R# i6 b9 O1 W& b; H
amount,' c- d# G" }# t; Z+ _2 o
options
);+ I3 U# T) [( Y3 p# }. ~$ b/ d
而不是:' C) f# {- p( G* j: v$ c; z" _
event LongAndLotsOfArgs(adress sender,
adress recipient,6 H" \0 J: R) v* q1 s, i
uint256 publicKey,
uint256 amount,+ V5 A/ G2 D& K8 {& a5 `
bytes32[] options);( U* i$ G1 ~! C! d, B$ [
LongAndLotsOfArgs(sender,/ W" J0 i" J9 |7 d! T
recipient,2 s& l) r" R9 Z: k7 _5 l4 }% Y& c6 w
publicKey,* i6 X9 c3 \ c* ~. X
amount,
options);
文件编码格式1 p' e! w( u `+ v4 M
推荐使用utf-8 及 ASCII 编码2 o+ d! G+ r# E/ T
引入文件应该在最上方3 ^+ w% t" N6 X
建议使用:! X. \, e& |# L; B9 P6 E* b
import "owned";
contract A {
...
}
contract B is owned {
...
}, m2 d' y& }* J3 N6 i
而不是:
contract A { R$ g- N# x& }, p9 k, N/ X
...4 @; W1 l8 d8 H
}- \: {* M i2 C) W$ I6 Z
import "owned";
contract B is owned {
... \; u8 F. R; W# w" F7 H
}5 p5 k: E9 a2 Q9 C2 ?8 t' U
函数编写规范
函数的顺序
在编写函数的时候,应该让大家容易找到构造函数,回退函数,官方推荐的的函数顺序是:
构造函数回退函数 (如果有)外部函数(external)公有函数(public)内部函数(internal)私有函数(private)
( ~3 M, P( F8 ?3 v2 ~
同一类函数时,constant函数放在后面, 例如推荐方式为:
contract A {" A9 u" L3 W) a V3 r$ S: o
// 构造函数
function A() public {- \5 J* p0 Z* j$ v
...0 _) b8 M; u7 l4 ]! c
}) s4 p( m& K) g3 m
// 回退函数
function() public {3 v' P+ ]. e4 }* b& Z7 L0 H
...% p, M! D( ]( K" X
}
// 外部函数
// ...8 R- [- Z" K* t8 r" i
// 带有constant 外部函数 , s4 C1 U, t1 _/ U
// ...- G. @5 l! e9 t6 \
// 公有函数
// .... S9 u6 j& m' L
// 内部函数
// ...
// 私有函数
// ...
}
而不是下面的函数顺序:; |2 q7 K& F, {
contract A {" N% P0 \: Q$ k `8 v
// 外部函数
// ...
// 公有函数$ T& S! y: U4 ^# t& ?
// ... {! `5 c: b% D+ z( ?# s
// 内部函数
// ...
2 M" I; l0 C3 J, s2 {# c Y
function A() public {; m, S# K% Q: C1 s
..., O! F7 z8 P; q1 I6 N- b$ B
}
function() public {$ r* E) W- H7 }) S& G
...; e2 H7 a X8 d5 U, `& k
}; y! [3 {1 T" R9 s
// 私有函数( `, o+ K, r; d
// ...7 z5 Z( ?, B9 g b+ B$ _( [0 O
}
明确函数的可见性5 ~- S. K+ A: x+ `; h/ \ A
所有的函数(包括构造函数)应该在定义的时候明确函数的可见性,例如应该使用:+ L/ p8 d0 g1 |8 i e
function explicitlyPublic(uint val) public {
doSomething();
}
而不是 N6 Q0 i1 Q8 Y4 O
function implicitlyPublic(uint val) {
doSomething();( y5 P% W5 B+ r: w5 ~
}
可见性应该在修饰符前面
函数的可见性应该写在自定义的函数修饰符前面,例如:) ^$ U- T6 Z, M2 ]. t x# q" R
function kill() public onlyowner { q$ V: f( K, C/ a2 U
selfdestruct(owner);
}: A; |* K4 U3 v7 y6 n, v
而不是
function kill() onlyowner public {6 ]; ]* t6 O- ~& J
selfdestruct(owner);' D R; k* e W( [" b6 Q
}
区分函数和事件9 [$ d/ M' v* _7 G) p+ Q9 c
为了防止函数和事件(Event)产生混淆,声明一个事件使用大写并加入前缀(可使用LOG)。对于函数, 始终以小写字母开头,构造函数除外。
// 不建议$ k9 B" y& _& m- J# O2 O, M
event Transfer() {}0 B8 O) E1 f1 U( Z# |8 O5 K7 C( R
function transfer() {}
// 建议) E# Z3 } P2 Q: d
event LogTransfer() {}
function transfer() external {}
常量
常量应该使用全大写及下划线分割大词的方式,如:MAX_BLOCKS,TOKEN_NAME, CONTRACT_VERSION。
参考文献
Solidity style-guide
成为第一个吐槽的人