智能合约最佳实践 之 Solidity 编码规范
人民干脆面
发表于 2022-11-13 23:49:49
177
0
0
命名规范
避免使用
小写的l,大写的I,大写的O 应该避免在命名中单独出现,因为很容易产生混淆。 e5 d9 O1 `2 H8 {
合约、库、事件、枚举及结构体命名$ k( g- m: `; P6 y1 Y# [1 N
合约、库、事件及结构体命名应该使用单词首字母大写的方式,这个方式也称为:帕斯卡命名法或大驼峰式命名法,比如:SimpleToken, SmartBank, CertificateHashRepository,Player。
函数、参数、变量及修饰器
函数、参数、变量及修饰器应该使用首单词小写后面单词大写的方式,这个方式也称为:(小)驼峰式命名法,是一种混合大小写的方式,如:. f6 N) ~( ^: P" h$ _2 ?( R' W
函数名应该如:getBalance,transfer,verifyOwner,addMember。参数和变量应该如:initialSupply,senderAddress,account,isPreSale。修饰器应该如:onlyAfter,onlyOwner。
代码格式相关, x1 u1 Z; G6 s+ |4 s
缩进3 M) O9 @, N2 ~. ^' A2 x3 r
使用空格(spaces)而不是Tab, 缩进应该是4个空格
空行
合约之间应该有空行,例如:
contract A {
...6 y. Z7 J6 x; {; q0 T
}) p' K; o; M4 n0 ~3 C
3 }1 o1 U8 j" G6 p& M& k
contract B {
...
}- ^8 w9 j/ P8 R0 i M
4 L& C- b- K- G+ F6 F8 ~
contract C {" a$ n# f# T/ G5 C0 T, t' f
...
}
而不是使用:
contract A {6 w" t; s5 {% Z; k- l
...7 l" m0 Q! F4 @& K
} M- C; G V, ]# R8 q3 E, u/ H
contract B {* O: a5 F! e! V
...
}3 {/ i) n1 q& C6 [- K
contract C {# o' R2 h5 \ B% N0 }
...
}
函数之间应该有空行,例如:
contract A {
function spam() public {
...
}
function ham() public {- b0 Q* [; J: w
...
}6 V6 U5 q0 k; D" |8 R/ k
}
没有实现的话,空行可以省去,如:3 m+ x" w" \% I! J7 W
contract A {/ [$ w, p$ k! d) h
function spam() public;7 e- g/ A; D$ L# _
function ham() public;3 h4 @! W% F+ z4 ?9 P) L
}" ?# X! F; S% B9 P, J
而不是:
contract A {
function spam() public {
...
}7 e n9 Q6 G# U# l: |+ s
function ham() public {
...
}
}' B0 ?& p0 s1 [( s9 l6 w- t
左括号应该跟定义在一行: x: j u# J8 `- |
定义包括合约定义、函数定义、库定义、结构体定义等等,例如推荐使用:
contract Coin {
struct Bank {
address owner;1 _: ?! H o" B9 b* b: v
uint balance;. Q7 h$ a: y3 H) R) A
}
}0 {) ?" L0 i/ J+ S$ h$ Y; J
而不是:; P1 a. f3 x, C+ _1 a; i' G- ^
contract Coin2 J9 i5 A/ ~& `3 {! {1 V
{
struct Bank {
address owner;
uint balance;0 u) e1 R( w0 M/ z8 @
}! c! e* U4 j5 M/ @% w" t0 D/ d
}
左括号应该跟条件控制在一行 O1 ?1 ~" O a) E6 _+ C
在使用if, else, while, for 时,推荐的写法是:
if (...) {
...
}) M' Q8 U2 a1 E9 Q' F$ H* I
for (...) {
...6 ]% M# \# s) f2 |% I. k3 v
}! [% N: @$ M- z. a p
而不是:5 \) b: B9 w# f
if (...)
{2 q j! ~7 T+ t0 H$ e" {
...6 X( [3 z8 v: i
}
while(...){
}
for (...) {
...;}, m j6 I* I- K
如果控制语句内只有一行,括号可省略,如: I# o- Q. E( V# w, A0 c" V2 ]6 c
if (x
但像下面一个语句有多方就不能省略,如:3 d; g" i( t5 g5 d/ D, }
if (x
表达式内的空格 @, V6 p. e# x! Y% a
一个单行的表达里,在小括号、中括号、大括号里应该避免不必要的空格,例如推荐使用:
spam(ham[1], Coin({name: "ham"}));* F/ C2 }5 C @& M3 P2 F8 u
而不是:8 X4 M2 k0 `! b A4 q2 p4 N8 I
spam( ham[ 1 ], Coin( { name: "ham" } ) );
有一种例外是,结尾的括号跟在结束的分号后面, 应该加一个空格,如下面的方式也是推荐的:
function singleLine() public { spam(); }
分号;前不应该有空格,例如推荐使用:
function spam(uint i, Coin coin) public;
而不是:1 G4 p$ P6 f* Z( F9 L/ j& ~; b
function spam(uint i , Coin coin) public ;
不要为对齐添加不必要的空格,例如推荐使用:; w- K! |. h {! |2 D: T
x = 1;
y = 2;+ X. Y0 N1 l* f" e2 \
long_variable = 3;0 d" j4 s' q) N( h2 A
而不是:, k) x1 _2 l. \+ w t( w7 |( q
1
2
3$ c8 N# \+ t1 m+ Q) A+ V
x = 1;
y = 2;, z/ B. g- d" Z9 R4 H2 S5 F
long_variable = 3;
回退函数不应该有空格,例如推荐使用:
function() public {
...
}
而不是:
function () public { H% K; _: z9 D" v6 } s! v- Y
...0 q) c' q, L' k0 V- J6 h/ ?4 f
}
控制每一行长度7 U9 o. Y+ k5 d& _
每行不应该太长,最好在79(或99)个字符以内,函数的参数应该是单独的行,且只有一个缩进,例如推荐的方式是:
thisFunctionCallIsReallyLong(
longArgument1,* o, a" S" m7 I0 m# b
longArgument2,
longArgument3, U0 V0 A9 ~$ o! `1 M- N! N
);
而不是:, D0 }1 U' Y: q5 d9 W
thisFunctionCallIsReallyLong(longArgument1,, O7 b3 [) S% ^8 p% b% M9 t- k
longArgument2,4 M/ n) [2 U8 |2 ]7 h8 l' k2 ~" c
longArgument3" U+ f/ f4 p& i6 F* c& _; W- S. W
);
thisFunctionCallIsReallyLong(longArgument1,) b7 A; [" G' `) i: \- V# j; C; G% n
longArgument2,
longArgument3
);
thisFunctionCallIsReallyLong(
longArgument1, longArgument2,
longArgument3$ h( @* V+ L& k0 Y/ E
);- N9 K6 v% j4 k- l, o% m
thisFunctionCallIsReallyLong(
longArgument1,3 x9 o+ ^ Q6 D! ?
longArgument2,
longArgument3) F9 o6 `+ ?& _: s
);
thisFunctionCallIsReallyLong(, o/ r6 [4 {) F
longArgument1,& o( F2 K! R1 s1 T+ X1 X6 C
longArgument2,9 }7 r7 P) Q. C- C* B$ U
longArgument3);
对应的赋值语句应该是这样写:
thisIsALongNestedMapping[being][set][to_some_value] = someFunction(
argument1,6 a: o3 W% ]* ]2 l4 ]
argument2,! |! `, B! I& M1 a7 p
argument3,
argument4" ?0 E7 o1 i9 ~4 _7 L3 `
);& U. N l( I7 r' G& M+ S
而不是:# ?7 Z, B9 H. g
thisIsALongNestedMapping[being][set][to_some_value] = someFunction(argument1,3 E" f: o& M# s8 G) d& {" H3 K3 }; d
argument2,
argument3,8 S% l9 b, g* E1 @
argument4);' |* e) y! d# K' A: z3 y- T |% C& e
事件定义也应该遵循同样的原则,例如应该使用:
event LongAndLotsOfArgs(
adress sender,# i/ F- ~1 y- ?$ u
adress recipient,
uint256 publicKey,/ x+ U7 _3 h3 l
uint256 amount,
bytes32[] options
);9 g1 l7 `* \& J5 Q& e8 n6 v
LongAndLotsOfArgs(6 y7 a" N8 ^) V* A, [
sender,+ l4 u+ m/ c( L8 V+ {6 Z
recipient,- P2 S, `6 L; ~( c, N7 g
publicKey,# H- m) q# ]" W. q
amount,0 o! \) [" W' u1 q3 [! E
options/ u- Q3 Z5 x5 M
);; O7 g/ s, P6 U$ c( A! _. i
而不是:
event LongAndLotsOfArgs(adress sender,
adress recipient,$ N0 L) S) L2 E9 x% c; n
uint256 publicKey,
uint256 amount,
bytes32[] options);
LongAndLotsOfArgs(sender," W& q9 @2 d: x' |( k1 f5 l
recipient,
publicKey,, e1 R8 W. X) A8 _4 p; [! _
amount,! F7 X. P: d# F' \! W
options);
文件编码格式& Z/ [' c7 Q; z- m8 g
推荐使用utf-8 及 ASCII 编码
引入文件应该在最上方# L9 H: ^4 z( y: u1 q' t% X9 a
建议使用:) H( \6 Q- r( i6 [
import "owned";
contract A {; d& t; e$ l) |7 |$ [; Q
...* }! W; ^1 Q+ y* b
}! p/ I8 K3 r8 t' H; P$ S
contract B is owned {
...
}
而不是:! ~3 s# Y, ^( T( a$ ^, O
contract A {
...
}
import "owned";
contract B is owned {5 a7 Q1 m" K" f$ m% z- |/ t
... X( S4 W7 c: r/ t
}6 S7 ?, x( z+ ~& P
函数编写规范
函数的顺序
在编写函数的时候,应该让大家容易找到构造函数,回退函数,官方推荐的的函数顺序是:
构造函数回退函数 (如果有)外部函数(external)公有函数(public)内部函数(internal)私有函数(private)
+ {: C! `/ e* y% y1 E
同一类函数时,constant函数放在后面, 例如推荐方式为:
contract A {7 ~8 [( l7 V# |& O3 U* }, v& Y) B
// 构造函数
function A() public {$ y: C' T- T* C. O( _7 @. R
...
}& K( M6 Y/ V6 y+ k
// 回退函数
function() public {, e/ A) G8 f( |- j5 j* t: g* s t
...$ u, E1 @; g# i
}- \! ]) B; o' b0 g c
// 外部函数
// ...' {% X% `: I3 i- i: I5 G
// 带有constant 外部函数 0 v, n$ G% v# w* O# f, ?
// ...
// 公有函数+ g: b& v1 ?, j
// .... j* `; i1 G# Z7 G% R
// 内部函数9 d- V7 l; f" z! W. M- x
// ...7 Y/ c* Q8 E9 s6 {
// 私有函数8 d! ]) G( p7 {+ N: }1 t
// ...: ]$ Z+ Z2 B! a3 {8 A. U! ?
}( ~3 v. `' c7 t5 P
而不是下面的函数顺序:
contract A {8 i$ \# P/ ^0 O% Z0 }5 e
// 外部函数
// .../ p5 i* ?# x. Y& }3 p" r4 v8 R
// 公有函数: I/ L8 C3 {5 u" g
// ...1 e) Q2 W3 h0 C2 E9 E" _! ~
// 内部函数
// ...% D3 p( d6 `1 u# c9 a
& U7 b9 M \; J6 c( P
function A() public {
...
} n9 n! W/ b9 ?, ~! {3 C( i3 p
function() public {
...! ^5 r- x" P" v4 M8 Y) _ h( U
}: c8 L5 j* V1 T; O
// 私有函数
// ...' z$ S: E# b, s/ x* i; C5 [: }; @
}
明确函数的可见性
所有的函数(包括构造函数)应该在定义的时候明确函数的可见性,例如应该使用:
function explicitlyPublic(uint val) public {
doSomething();
}8 O0 n/ e# {" j1 z& ]# \+ x
而不是
function implicitlyPublic(uint val) {
doSomething();3 s. {: x8 C% X# X/ O, g
}% S' Q) I, V. j
可见性应该在修饰符前面5 r' C" f% x: I- {" q8 u0 h
函数的可见性应该写在自定义的函数修饰符前面,例如:
function kill() public onlyowner { S) g9 Z8 ]" Q8 u/ Z) h7 L ?% E9 a
selfdestruct(owner);
}* J+ K1 z3 \) ]0 Q' b! U
而不是# \9 Z, ^$ `8 q9 y$ l) V5 Z' ^ S
function kill() onlyowner public {4 R) `1 ?9 }( _
selfdestruct(owner);
}
区分函数和事件5 f; i8 V0 p; D- ]7 F8 V; i
为了防止函数和事件(Event)产生混淆,声明一个事件使用大写并加入前缀(可使用LOG)。对于函数, 始终以小写字母开头,构造函数除外。
// 不建议# R3 H, }8 K5 ]+ M
event Transfer() {}9 M: v$ s/ j* l5 E- X9 O
function transfer() {}
// 建议
event LogTransfer() {}
function transfer() external {}0 `; R) L/ B5 n" h5 r% n
常量9 R/ P P$ t' t( z& T$ Y
常量应该使用全大写及下划线分割大词的方式,如:MAX_BLOCKS,TOKEN_NAME, CONTRACT_VERSION。
参考文献5 d6 Y: W# A2 C8 }9 s
Solidity style-guide
成为第一个吐槽的人