智能合约最佳实践 之 Solidity 编码规范
人民干脆面
发表于 2022-11-13 23:49:49
121
0
0
命名规范
避免使用" r' u4 Y& e# |4 P _
小写的l,大写的I,大写的O 应该避免在命名中单独出现,因为很容易产生混淆。% y' T% {. k* K3 b+ `2 X) z( c( T
合约、库、事件、枚举及结构体命名6 W8 ], ]7 X7 [7 @4 D% B- R
合约、库、事件及结构体命名应该使用单词首字母大写的方式,这个方式也称为:帕斯卡命名法或大驼峰式命名法,比如:SimpleToken, SmartBank, CertificateHashRepository,Player。: j% t5 o2 p9 g% [1 c
函数、参数、变量及修饰器$ `+ ?! r1 e, ~8 W# V& t' `% Y
函数、参数、变量及修饰器应该使用首单词小写后面单词大写的方式,这个方式也称为:(小)驼峰式命名法,是一种混合大小写的方式,如:
函数名应该如:getBalance,transfer,verifyOwner,addMember。参数和变量应该如:initialSupply,senderAddress,account,isPreSale。修饰器应该如:onlyAfter,onlyOwner。' y/ Y9 J2 L3 w* y2 d- j+ g0 z
代码格式相关% ?( y% G' D. c: a0 H3 f: V0 z
缩进
使用空格(spaces)而不是Tab, 缩进应该是4个空格0 O7 H7 W& Q' t8 T& ]
空行* m2 E0 e9 Q: n7 {" e
合约之间应该有空行,例如:
contract A {6 |- k( y( H, A: _( \
...$ [! ?+ J5 z) [( q
}1 Z% U. K& R0 X- E
# h: x% d1 K3 E; m% K6 E0 ^
contract B {% m: Z" d6 ], r% d: ^' Z$ a1 u
...
}3 v. k2 f+ E6 n; Z) N. e# c0 s
contract C {* R; J4 d1 B. G. S! Y+ ~: i, [9 e
...
} q9 h* _; a% Z0 ]" I5 Q7 j
而不是使用:
contract A {2 o* V/ |/ j9 ^4 o
...9 s" h9 o% H8 V% s9 H u# i
}
contract B {) @* I" m3 o$ h8 a7 Q
...# K- S) B2 k) ]9 y# T. L* `
}
5 X! Y0 {. K( b" C6 Z2 I$ h
contract C {# y5 [0 |- Z( i E. z, b2 a
...
}
函数之间应该有空行,例如:' [/ k" ~. @$ `- \2 v' I' o( ?
contract A {' K( K5 K8 \0 [% o) S
function spam() public {$ d/ D) _; R& v. o
...4 i8 d/ g% G, p6 Q) w b
}
k, \; W8 W) T! U* u2 F
function ham() public {1 a! ^& Q, u. }: c4 @
...
}* G( c# U7 W' {4 w
}6 \& o6 K P3 l& ]
没有实现的话,空行可以省去,如:" O; G( T0 G2 k/ _
contract A {" |& `$ b" `2 |0 q' f: ~2 p1 B; t
function spam() public;$ k# ]$ `9 }8 b5 g9 L m7 k, q
function ham() public;# V8 b8 }, K0 m6 ?
}' P2 m- f. \; R$ b* f
而不是:: Y& ~+ i8 b1 A! R" j# d2 v& D& L
contract A {3 e4 k+ C( g$ P; u& `' ?7 m# m' W
function spam() public {% r% W. h- M8 z% M' A" b
..." K5 U9 r$ N) M0 W% c$ [* C+ X- P
}
function ham() public {
...
}0 ~! V. @% h! p0 f- F
}# z' Z7 q2 m1 l
左括号应该跟定义在一行8 I/ |4 S. y/ |: h8 j4 B( K( `9 k
定义包括合约定义、函数定义、库定义、结构体定义等等,例如推荐使用:
contract Coin {
struct Bank {
address owner;
uint balance;
} {. k- b2 N) S% S4 C; G
}5 @4 K' c. E" g) r, W
而不是:
contract Coin
{. v: q" h7 f' r I- f- n' i, p
struct Bank {2 q4 ^4 w5 x0 B' C) {. U; I6 D9 K
address owner;
uint balance;
}
}) M: c a1 P: X: ]. D1 n! V2 R
左括号应该跟条件控制在一行+ r' x6 O, ~$ ^1 Y) S3 K. K2 j y
在使用if, else, while, for 时,推荐的写法是:
if (...) {" s/ q7 S4 d5 \
...
}
for (...) {- {$ p& v3 W7 Z. W) f
...7 T" v/ J0 t3 {' S' D4 y0 o& ?
}0 a4 `4 J# x6 w
而不是:+ n- k: U, b( Y; K
if (...)4 j5 T% D5 @, p( x3 ^1 @7 I
{) k& q% ?. p& D& }& S
...1 E& \4 X Q1 c- [
}
while(...){& b; I. W f* w
}- [, D) B! H4 h; L0 Q, w
for (...) {
...;}
如果控制语句内只有一行,括号可省略,如:. B. s$ @' z' |
if (x - }: {: g4 G! m! O& d' H" d
但像下面一个语句有多方就不能省略,如:0 a8 F4 }/ p" m& H
if (x # s* ?. p- t* X) P W/ ?
表达式内的空格% F# H: s3 @3 n* Z
一个单行的表达里,在小括号、中括号、大括号里应该避免不必要的空格,例如推荐使用:
spam(ham[1], Coin({name: "ham"}));
而不是:$ T( [6 j/ b1 N: {* [; t+ _
spam( ham[ 1 ], Coin( { name: "ham" } ) );* f8 Z" v5 c* E8 s
有一种例外是,结尾的括号跟在结束的分号后面, 应该加一个空格,如下面的方式也是推荐的:% Q, c, z5 s) a+ o8 v
function singleLine() public { spam(); }
分号;前不应该有空格,例如推荐使用:
function spam(uint i, Coin coin) public;+ P$ K5 J' L0 _
而不是:
function spam(uint i , Coin coin) public ;6 R0 Z& g$ W% ?1 K7 Z& s$ X
不要为对齐添加不必要的空格,例如推荐使用:+ F( U( c n( o* F
x = 1;1 b# r0 L- r) I" L! ?
y = 2;
long_variable = 3;
而不是:
17 U7 A4 D* i' j" ~, g" o) I6 f
24 }6 p4 V& N5 a; m8 P
39 W: E% `6 p( ~( x: W3 g2 d# o
x = 1;* O9 U/ o! ^; }$ K% h5 F `
y = 2;1 }6 i' w' M6 I; E7 H" Q
long_variable = 3;
回退函数不应该有空格,例如推荐使用:
function() public {
...
}# j. P8 H* V2 }& n; R
而不是:
function () public {
.../ u! I7 `4 Q: v4 b" g1 o3 m" r
}. B R$ E" x' `& L7 m
控制每一行长度4 h% f. o! @3 E4 \; L1 S
每行不应该太长,最好在79(或99)个字符以内,函数的参数应该是单独的行,且只有一个缩进,例如推荐的方式是: | c& q, p4 q) z5 |, U! F
thisFunctionCallIsReallyLong(: o3 [( h- ?9 e
longArgument1,& Y+ E4 L/ k- T7 I7 Z( L- G
longArgument2,1 c& ^. x% V7 _
longArgument3! U5 ~8 m7 `5 M4 N! x* l
);
而不是:% R1 u' o1 K" Z' [5 I+ ^
thisFunctionCallIsReallyLong(longArgument1,
longArgument2,
longArgument3
);
thisFunctionCallIsReallyLong(longArgument1,6 m2 d. d3 v" g& C% q# |- f2 h
longArgument2,
longArgument3' P5 ?0 J, C# h- y
);& d# F/ a+ W6 m! r
thisFunctionCallIsReallyLong(! z0 Z! {7 ~( Y& Z
longArgument1, longArgument2,4 ^0 J S' a- O) t) s
longArgument3/ r* E! [ G- E! t& {
);
thisFunctionCallIsReallyLong(
longArgument1,
longArgument2,6 n2 }) y1 i& e! J! h! S6 _, x
longArgument3
);/ H: B, @8 g/ @4 e) X4 c
thisFunctionCallIsReallyLong(
longArgument1," G/ q* |5 Q! `. w7 K4 r5 X
longArgument2,8 w$ A% U4 }; H% t
longArgument3);
对应的赋值语句应该是这样写:: Y2 ?3 }% }3 ~. }
thisIsALongNestedMapping[being][set][to_some_value] = someFunction(
argument1,0 a( G; y6 ]6 u& A
argument2,
argument3,' V9 Y% l+ B+ J0 R, ~5 v4 p( v
argument4' ~9 r3 }) \$ V4 j l2 J
);) ~# l5 y/ l/ t+ ]
而不是:
thisIsALongNestedMapping[being][set][to_some_value] = someFunction(argument1,: z5 F1 O. [, @) k
argument2,3 g b. h, g: H' [
argument3,. k7 A- \7 a: i+ G9 n+ g
argument4);- \! b# h8 ]. \- U
事件定义也应该遵循同样的原则,例如应该使用:
event LongAndLotsOfArgs(
adress sender,
adress recipient,
uint256 publicKey,+ C# G" P4 c7 ^% ^
uint256 amount," Z( |8 y4 i1 d2 k* A% s* T, ~5 b
bytes32[] options
);
LongAndLotsOfArgs(
sender,+ V- n8 Z) G" `7 \) d+ ]# r* R7 l
recipient,
publicKey,
amount,
options
);) X8 m! \9 Q6 R4 K! f) X/ g
而不是:3 U, ~6 K; b8 `* N z% g
event LongAndLotsOfArgs(adress sender,
adress recipient,
uint256 publicKey,
uint256 amount,) F" ?9 ?/ w; E, N- p
bytes32[] options);
LongAndLotsOfArgs(sender,
recipient,$ K% C" ?; j" J b" f6 f8 `
publicKey,/ h1 o, z& y( ^6 w+ R
amount,: ]4 ]& I" O. |' \$ L
options);
文件编码格式5 T) _$ o: E- T6 ?6 o
推荐使用utf-8 及 ASCII 编码
引入文件应该在最上方
建议使用:
import "owned";, X4 q" A' j& Z5 n: T/ S
contract A {
...
}% _: U( I: T! p' S' p
contract B is owned {
...
}
而不是:
contract A {/ L L4 I0 t9 ^
...
}/ E& ?5 Q. y( B( Q5 C
import "owned";0 a- j" y0 z1 r
contract B is owned {" G$ d- ]( c, g- X) Z
...
}% }7 L d& n( C0 G# R
函数编写规范. m/ g+ j7 q" Y
函数的顺序; L$ P% P" u& N" ]* m
在编写函数的时候,应该让大家容易找到构造函数,回退函数,官方推荐的的函数顺序是:6 ]4 c+ S n2 W6 L7 Z. k! |
构造函数回退函数 (如果有)外部函数(external)公有函数(public)内部函数(internal)私有函数(private)
' b* E6 e7 A- u' u# i$ l
同一类函数时,constant函数放在后面, 例如推荐方式为:
contract A {( i9 k% j& t0 r9 w
// 构造函数
function A() public {5 u; Y. C, ], n& v" t
..." S: n3 }) I* ~* e
}7 r5 L" K! N3 @" X
// 回退函数! K; W- s1 _( |- T; }4 Z! t
function() public {( y, c9 T* `1 [) W7 Z5 p/ a7 \
...
}
// 外部函数
// ...
// 带有constant 外部函数
// ...
// 公有函数7 b0 Y' j- g( {0 M% H0 E7 ^
// ...: [* ^% j9 Q w( j: ]
// 内部函数
// ...
// 私有函数
// ...
}- ]$ k5 ^* ]% h3 `7 m
而不是下面的函数顺序:0 v4 J6 N1 X) h# r
contract A {: g* ^/ D$ e- U0 C( z! ~$ }
// 外部函数) B5 G$ o1 \% Q
// ...
// 公有函数( x* J1 {% K: @; h
// ...( c/ \3 E0 l+ u0 p1 q" `; k4 h( I
// 内部函数
// ...
$ y& |( W7 |# f' ?, g) A
function A() public {
...
}
function() public {. i. ~5 S* [" _1 M( w8 {
...1 I. [; b0 g* Y4 Q6 `% e
}
// 私有函数
// ...& t% K9 W8 Z9 J9 f! p3 s6 T" z
}7 B: l2 h9 g# E
明确函数的可见性- M+ l; g- x( n3 y) c) `1 _
所有的函数(包括构造函数)应该在定义的时候明确函数的可见性,例如应该使用:
function explicitlyPublic(uint val) public {
doSomething();; ]9 ]# `( A0 A; ]" _ |% I
}% B0 }+ g+ N: f( W1 k- i6 ` t
而不是) y+ k2 f* a" T. T0 P
function implicitlyPublic(uint val) {
doSomething();; ?+ ^# `5 b7 K/ F3 X0 x8 ~; [
}
可见性应该在修饰符前面. b8 J5 J1 x, G3 i0 A, W6 x( u
函数的可见性应该写在自定义的函数修饰符前面,例如:# ]' ?( x' T0 O
function kill() public onlyowner {
selfdestruct(owner);, r2 Q5 s! J) K) X) `
}
而不是- x# @8 e! q; d% H
function kill() onlyowner public {% @; H) K8 w$ c/ i" R! S6 ~
selfdestruct(owner);
}
区分函数和事件
为了防止函数和事件(Event)产生混淆,声明一个事件使用大写并加入前缀(可使用LOG)。对于函数, 始终以小写字母开头,构造函数除外。
// 不建议
event Transfer() {}' Q& y/ s# m8 ?1 H! ]
function transfer() {}
// 建议
event LogTransfer() {}1 @% _; I8 g8 O9 G9 o( s
function transfer() external {}9 m2 {- c- q. U% ?/ ]
常量) t- K. b3 D8 q, s& w: _
常量应该使用全大写及下划线分割大词的方式,如:MAX_BLOCKS,TOKEN_NAME, CONTRACT_VERSION。
参考文献$ i4 r9 }7 i3 m( P! u' C( y/ p1 Y
Solidity style-guide
成为第一个吐槽的人