智能合约最佳实践 之 Solidity 编码规范
人民干脆面
发表于 2022-11-13 23:49:49
159
0
0
命名规范
避免使用
小写的l,大写的I,大写的O 应该避免在命名中单独出现,因为很容易产生混淆。
合约、库、事件、枚举及结构体命名
合约、库、事件及结构体命名应该使用单词首字母大写的方式,这个方式也称为:帕斯卡命名法或大驼峰式命名法,比如:SimpleToken, SmartBank, CertificateHashRepository,Player。3 v: U: r0 d/ F3 v9 c) J' B! U4 k% [
函数、参数、变量及修饰器# \/ s! B: S {# w1 x# g
函数、参数、变量及修饰器应该使用首单词小写后面单词大写的方式,这个方式也称为:(小)驼峰式命名法,是一种混合大小写的方式,如:3 l- ~, s) |: L/ ^( T9 Y2 ?
函数名应该如:getBalance,transfer,verifyOwner,addMember。参数和变量应该如:initialSupply,senderAddress,account,isPreSale。修饰器应该如:onlyAfter,onlyOwner。
代码格式相关3 K4 y! `/ i' e# F; Z
缩进6 R. V% [9 b C; A+ r9 i
使用空格(spaces)而不是Tab, 缩进应该是4个空格
空行
合约之间应该有空行,例如:
contract A {6 g/ M3 a( L# e5 V( F" [# ]1 {
...0 p) w, \. w* H; `+ t8 S) \
}
contract B {
...
}
contract C {$ a' j; y2 M/ c8 ~/ L) b
...7 S4 R( U5 S8 p, s! E
}! y) Z9 [0 p5 w+ A2 N. i y
而不是使用:
contract A {% o) T r& t& Q( @ B
...8 I3 i% N/ G$ @
}# t1 n4 k3 T& t4 g0 P5 n
contract B {; b/ \: `4 Z( I8 G7 U
...
}; H$ i% {0 c% f) y+ G, e
5 Q' m1 l" `1 n4 n0 [/ \3 n
contract C {
...
}% M! N/ B& D- O9 `
函数之间应该有空行,例如:
contract A {. C8 W) m$ A7 R- j# N: L7 T
function spam() public {
...4 u, b8 l/ ]: n3 q. A q
}- |; L6 O: R& G" B- @* n
( B6 A( Z+ x5 k3 t' ^& a
function ham() public {( ~# B5 J7 K7 R! \" Y
...
}
}: a6 t. t8 P( V& q& C9 k C
没有实现的话,空行可以省去,如:' S) U5 P( Y- R4 @7 c8 O8 o
contract A {
function spam() public;5 {8 S) x& @) @; `1 H# I6 k
function ham() public;0 m5 f. c( P) X" m! B# r; b
}
而不是:
contract A {/ {) i" U/ A. ^- H3 k2 f
function spam() public {
...% M5 j O7 c6 B5 o3 d+ ?
}2 V1 a! C8 J1 m: A0 g) G; S% t: R
function ham() public {- p& w# Q; q: f- q7 L6 D! T
...* Z* ^: _& B$ t+ k' O, s
} k8 E# d* U1 H
}: R( Z6 \* v- r7 O# e! f7 ]; A
左括号应该跟定义在一行
定义包括合约定义、函数定义、库定义、结构体定义等等,例如推荐使用:
contract Coin {
struct Bank {) i j6 q; O$ {. ?# K& `& |
address owner;# K4 ]* a! G- r. A3 g; m" q* W0 Z
uint balance;
}! w0 } g$ w: _. Z4 X4 D
}) t0 B7 ^$ O2 b! e
而不是:
contract Coin+ v, V3 \+ ] T
{
struct Bank {
address owner;1 D- M/ [0 |" E. _2 I3 o
uint balance;" X7 E! g0 t/ U. m( B
}8 I- H" H9 a" X* z
}
左括号应该跟条件控制在一行/ x B5 Y8 j* w
在使用if, else, while, for 时,推荐的写法是:
if (...) {
..." P& g& A: d& i* G, M
}
for (...) { Y5 J( c! I+ C% Z
...$ Z, O# K& Q! ~, U) K, A9 a
}
而不是:+ [. N0 h2 B- F
if (...)
{
...8 E1 b2 S: y% i7 o7 F3 ^6 T
}) b/ \9 M% B* N1 i- e
while(...){& m4 M$ u: H$ X! k5 g
}3 ^6 L9 F# c* [# N7 J4 g3 v2 s
for (...) {
...;}
如果控制语句内只有一行,括号可省略,如:
if (x
但像下面一个语句有多方就不能省略,如:
if (x ' X$ K* F6 a, z% F, ?
表达式内的空格
一个单行的表达里,在小括号、中括号、大括号里应该避免不必要的空格,例如推荐使用:
spam(ham[1], Coin({name: "ham"}));
而不是:3 [/ L8 r! z4 w1 K( [
spam( ham[ 1 ], Coin( { name: "ham" } ) );
有一种例外是,结尾的括号跟在结束的分号后面, 应该加一个空格,如下面的方式也是推荐的:. n, W1 m% B- e6 N! t( b
function singleLine() public { spam(); }7 t$ K5 s: ]5 Y# I( ~$ W
分号;前不应该有空格,例如推荐使用:
function spam(uint i, Coin coin) public;
而不是:
function spam(uint i , Coin coin) public ;! s ]) F# O( G$ B; M. p( T. s
不要为对齐添加不必要的空格,例如推荐使用:
x = 1;
y = 2;
long_variable = 3;
而不是:
17 x1 X, w- p& K
2
31 I+ {2 ~4 L# a; [) M: N2 Z
x = 1;
y = 2;, |2 H V6 B$ {2 m1 ?6 h
long_variable = 3;7 Z& {( p$ P, y& a% h2 t
回退函数不应该有空格,例如推荐使用:5 {' V/ m( N" `) P$ @* k
function() public {% f- K1 J5 ?! P( j
...
}' [# G8 E' r- F( s
而不是:
function () public {
...; e6 T9 l9 \. l7 f% @) X
}
控制每一行长度+ H- S! G- p f. H+ [6 N
每行不应该太长,最好在79(或99)个字符以内,函数的参数应该是单独的行,且只有一个缩进,例如推荐的方式是:3 P% R5 o7 A1 E! {" A" T7 e
thisFunctionCallIsReallyLong(
longArgument1,
longArgument2,
longArgument3+ a0 o0 e+ u5 _3 W d$ }
);
而不是:! o+ i" f, Y: Z! p5 N
thisFunctionCallIsReallyLong(longArgument1,4 H+ G9 n6 o5 i. f
longArgument2,% m+ K: ^$ x2 G c: z+ O/ i; O% c7 z% j
longArgument3
);9 m, t& y; `: Z
thisFunctionCallIsReallyLong(longArgument1,
longArgument2,* V+ m) k- z# ~
longArgument38 s& \1 H! K% a+ _$ J
);
thisFunctionCallIsReallyLong(
longArgument1, longArgument2,+ ~( t9 \! y* q' ^* H3 r8 x! M
longArgument3
);0 ~. e. @3 m' U$ B& M
thisFunctionCallIsReallyLong(* T# i# ]& I7 |
longArgument1,- P O( X8 V' d8 C
longArgument2,
longArgument3; i: I2 K/ S5 e9 Q/ p2 S# H
);6 ~2 E/ u; g) h& E6 |
thisFunctionCallIsReallyLong(6 [) r1 B( n) I
longArgument1,2 I. }% Z( B3 t8 K! A6 u G& ]
longArgument2,
longArgument3);
对应的赋值语句应该是这样写:
thisIsALongNestedMapping[being][set][to_some_value] = someFunction(; h6 G+ x( O2 T6 P
argument1,* R4 D/ z9 r0 N8 J& `* P+ n
argument2,- A6 E8 H5 [- l& m- L! |
argument3,
argument4" a, f1 x+ [- k, o* f
);2 q u7 H4 P0 ?3 H9 I! s
而不是:, n7 p: ]% [# T+ M- m" I8 ^
thisIsALongNestedMapping[being][set][to_some_value] = someFunction(argument1,
argument2,
argument3, V* w: G: S B
argument4);
事件定义也应该遵循同样的原则,例如应该使用:& H4 b8 u! A$ P: r. @4 C) t4 S+ d
event LongAndLotsOfArgs(& ]. x1 o& I9 a' k
adress sender,: e n# q' w1 h* L* j
adress recipient,
uint256 publicKey,
uint256 amount,3 `9 j$ _$ Y, Y8 t9 D5 x( i# V
bytes32[] options
);2 A& P6 f+ v' B4 t) y
LongAndLotsOfArgs(
sender,
recipient,
publicKey,4 e- \" H/ \5 K% P* P0 k
amount,
options' p, O* J* k: q6 i/ I
);! U* I2 W2 c7 D7 ]# v0 W0 Q3 I, J
而不是:
event LongAndLotsOfArgs(adress sender,/ F3 K! c8 ~9 u( }+ x) R9 [. V1 p
adress recipient,4 p; r2 _: a: k# F& C4 W, i8 {* a
uint256 publicKey," S( ^. z" C# V/ y* o) s; r
uint256 amount,
bytes32[] options);4 g" E" m9 U( ? |, D7 L6 B) ?
LongAndLotsOfArgs(sender,3 \+ _; p* k; \* |
recipient,
publicKey,9 o6 z* v5 R7 x4 h
amount,
options);% m! O4 G/ B$ D, f4 ~7 s4 A& b- i# }
文件编码格式
推荐使用utf-8 及 ASCII 编码
引入文件应该在最上方
建议使用:
import "owned";
contract A {9 \7 S4 Z) p1 y% b# k- T
...5 G- f. |4 w! N; b
}
contract B is owned {
...& E# H: z$ U/ L! `4 Q' Q" n8 Y( `
}
而不是:
contract A {( e6 ^1 C' r& I7 p5 W
...
}( a, N# `7 Q5 L" @
import "owned";$ n! Y5 }4 r, N G+ t
contract B is owned {3 ~/ W2 l3 y1 q
...( N4 S+ g$ \+ H/ `7 u
}
函数编写规范
函数的顺序
在编写函数的时候,应该让大家容易找到构造函数,回退函数,官方推荐的的函数顺序是:
构造函数回退函数 (如果有)外部函数(external)公有函数(public)内部函数(internal)私有函数(private)
同一类函数时,constant函数放在后面, 例如推荐方式为:
contract A {3 l- X: X* t+ K9 x- ^* V {
// 构造函数
function A() public {1 |: r" r7 W. Q7 W( h+ {! k
...
}9 A% }# ~ ^, \6 t' U; d
// 回退函数
function() public {7 ^" r' |! m( P, M/ J' {1 i& K
...
}
// 外部函数# _) _- l7 M8 S
// ...: O5 n1 G+ V/ F3 G5 M
// 带有constant 外部函数
// ...6 b7 u; C! ?# z/ \
// 公有函数
// ...
// 内部函数
// ...% \+ j0 p6 I! t" ~4 j, s, F7 W
// 私有函数
// ...8 j+ K! U" E, J8 H: D
}
而不是下面的函数顺序:: ~+ R! W3 e i8 O$ g! i
contract A {
// 外部函数
// ...
// 公有函数+ W$ P( V. C$ V1 A/ X1 H% f6 f6 M/ V
// ...
// 内部函数
// ...
7 X! a& x. {% b/ w7 {
function A() public {* {$ }7 c6 ]" L
..., i1 P/ z1 p3 I, J6 ]1 m( J5 l x4 M
}
function() public {$ f0 _0 r1 D. U1 t+ w
.../ ?* U0 F4 |( d! o" G- d+ t
}$ R5 }6 a) O) q4 @2 P
// 私有函数
// ...
}
明确函数的可见性1 ~$ w0 d0 ?- s5 j0 b
所有的函数(包括构造函数)应该在定义的时候明确函数的可见性,例如应该使用:
function explicitlyPublic(uint val) public {$ B& X0 c% _8 z5 t# K, j/ g; _( n. U
doSomething();/ d5 V2 u: L. u+ l1 v: w ]0 Y! w
}
而不是- p A, B: G3 [
function implicitlyPublic(uint val) {
doSomething();* l# u" g4 Z: p* A. N) I. n, D
}+ G! q( l$ v7 T; g" H; C' w
可见性应该在修饰符前面9 T5 T3 L R% r! r/ y* s
函数的可见性应该写在自定义的函数修饰符前面,例如:
function kill() public onlyowner {0 G! ?7 V7 v8 s" J$ R
selfdestruct(owner);. l" {/ Q" Z g) E2 B3 Q1 l
}
而不是1 q3 B7 C7 y; [2 [
function kill() onlyowner public {
selfdestruct(owner);' Q, e2 O/ ^; \8 A& y
}; S B0 {) J4 z' Y2 ]" o
区分函数和事件; j3 D1 ?5 Z W1 o1 ]
为了防止函数和事件(Event)产生混淆,声明一个事件使用大写并加入前缀(可使用LOG)。对于函数, 始终以小写字母开头,构造函数除外。
// 不建议) D* ^9 N; U4 d/ f) K1 Y
event Transfer() {}
function transfer() {}
// 建议- j3 c2 ~5 M; S4 m& |8 Z
event LogTransfer() {}
function transfer() external {}
常量
常量应该使用全大写及下划线分割大词的方式,如:MAX_BLOCKS,TOKEN_NAME, CONTRACT_VERSION。
参考文献, \ H0 {% p$ i, K# ^- O
Solidity style-guide
成为第一个吐槽的人