智能合约最佳实践 之 Solidity 编码规范
人民干脆面
发表于 2022-11-13 23:49:49
176
0
0
命名规范
避免使用
小写的l,大写的I,大写的O 应该避免在命名中单独出现,因为很容易产生混淆。
合约、库、事件、枚举及结构体命名" J8 e, R3 e; ` U# a
合约、库、事件及结构体命名应该使用单词首字母大写的方式,这个方式也称为:帕斯卡命名法或大驼峰式命名法,比如:SimpleToken, SmartBank, CertificateHashRepository,Player。: h* \, v0 ^7 P, p
函数、参数、变量及修饰器
函数、参数、变量及修饰器应该使用首单词小写后面单词大写的方式,这个方式也称为:(小)驼峰式命名法,是一种混合大小写的方式,如:
函数名应该如:getBalance,transfer,verifyOwner,addMember。参数和变量应该如:initialSupply,senderAddress,account,isPreSale。修饰器应该如:onlyAfter,onlyOwner。1 k" }/ Z' g# m# p. a1 j" m
5 }6 K9 c- M. l8 N( l: `
代码格式相关) k8 D! C5 k5 s- Z$ r
缩进
使用空格(spaces)而不是Tab, 缩进应该是4个空格' O3 s: m3 k3 k' t) n, a
空行. k& [" E8 f A j. U% {
合约之间应该有空行,例如:
contract A {
...- A7 l4 X# }. M8 J. x; I" T
}
: d/ g! z5 [, c. I
contract B {
..., j" f/ v/ E" N' F. B, G
}
& p: O/ q: ~7 L6 k" m) b
contract C {* }# r# H# s! K) r$ k5 O
...
}
而不是使用:+ i8 L% f6 k: H; I8 ~- ]) }9 I
contract A {* c3 Q9 |0 A# a
...$ O7 X# e% {# w1 y# G, t; S+ W
}- }0 @# W# W J! d' T
contract B {% U3 A* |1 O+ k2 h
...
}
contract C {
...
}! D o0 W; g2 E) ~: `* C- c! R
函数之间应该有空行,例如:
contract A {3 {7 i$ y/ r y }2 D/ j0 m
function spam() public {
...& c8 x3 Y4 E+ n c1 F
}' `; f0 P% p) y! \( @
7 A+ l9 b- o9 ~! y" o+ _0 g
function ham() public {1 O) G: q" H1 d" w* N% h$ b
...
}
}, [0 }1 h9 F" A
没有实现的话,空行可以省去,如:
contract A {4 v; `6 A( z( q" \; @
function spam() public;
function ham() public;* p$ q# j0 S" C( @6 ^1 Q
}
而不是:
contract A {" L. v; r( \3 c3 Z0 O4 _# G) }0 F
function spam() public {3 W8 w- h1 i5 i& ?6 w2 s
...
}
function ham() public {5 X! d1 b0 f0 v) g! ^
...
}
}5 [) ^8 t# ?" u! q- h
左括号应该跟定义在一行* s5 r. |/ r% ~
定义包括合约定义、函数定义、库定义、结构体定义等等,例如推荐使用:7 ]1 x2 ?0 ]3 }+ P! Q q+ w
contract Coin {
struct Bank {
address owner;
uint balance;+ q9 N' `: O. K+ `2 Q0 [5 K
}
}' \1 ^( P5 p, ]% O- p! R& ~
而不是:
contract Coin
{
struct Bank {( w w1 w( u/ r# P$ {
address owner;
uint balance;3 w7 r" D$ O( c t6 Y
}4 n' d- U7 e* k I
}
左括号应该跟条件控制在一行! J: ]* r( X0 a3 w4 p7 x
在使用if, else, while, for 时,推荐的写法是:) A {$ l/ c( S: M' `6 g
if (...) {
...$ K/ O; f; x8 v$ g9 T9 U
}
for (...) {& c* L2 K" w2 A& x1 }6 Y1 L( s
...
}
而不是:
if (...)$ X4 ^% t1 b5 \# ^" A8 U: j N$ p
{
...5 R5 _. w0 n; J% X* i
}
while(...){
}
for (...) {% z4 l4 I: n ] z5 B
...;}
如果控制语句内只有一行,括号可省略,如:
if (x 1 q$ s% H" Y1 N& ]9 X* x7 t
但像下面一个语句有多方就不能省略,如:
if (x 7 x( [7 ^9 T. {/ C6 D' r/ c; X
表达式内的空格
一个单行的表达里,在小括号、中括号、大括号里应该避免不必要的空格,例如推荐使用:
spam(ham[1], Coin({name: "ham"}));
而不是:
spam( ham[ 1 ], Coin( { name: "ham" } ) );4 Z+ w, a* e6 y+ L
有一种例外是,结尾的括号跟在结束的分号后面, 应该加一个空格,如下面的方式也是推荐的:
function singleLine() public { spam(); } n% T8 d3 n2 D0 X6 s& Z z
分号;前不应该有空格,例如推荐使用:! a3 s3 F* B2 X! L7 J8 `" f' Q
function spam(uint i, Coin coin) public;
而不是:
function spam(uint i , Coin coin) public ;
不要为对齐添加不必要的空格,例如推荐使用:
x = 1;
y = 2;
long_variable = 3;
而不是:
1( l% f [: }" F6 F8 A& Z' i' n
22 m: E" o$ V5 o3 g* L; X+ g, \; \/ \8 x
3
x = 1;
y = 2;3 C* C" c' a; X' `
long_variable = 3;
回退函数不应该有空格,例如推荐使用:
function() public {
...
}2 C9 \7 C& K9 f2 h1 X% t
而不是:
function () public {6 l T) D& w6 W' L: i
...8 M J6 ]; l) C+ a1 b# J9 R$ _
}
控制每一行长度
每行不应该太长,最好在79(或99)个字符以内,函数的参数应该是单独的行,且只有一个缩进,例如推荐的方式是:
thisFunctionCallIsReallyLong(+ g( w( N8 n" y1 [8 I0 _
longArgument1,
longArgument2,: L! n0 `! x" A# x$ x
longArgument3
);9 d4 g: Y; ^4 J- J* A1 T
而不是:
thisFunctionCallIsReallyLong(longArgument1,
longArgument2,
longArgument36 H9 D; b$ ^% ^+ P% e
);
thisFunctionCallIsReallyLong(longArgument1,9 C' m- y C( o6 J' q1 j9 h) E/ y
longArgument2,1 s# M* M; Z+ E5 P8 p' G$ s T
longArgument30 e7 P, P" F. |9 E5 t* _+ { Q+ U% c
);
thisFunctionCallIsReallyLong(
longArgument1, longArgument2,
longArgument3
);
thisFunctionCallIsReallyLong(. [+ M$ O: C" E' l+ F
longArgument1,
longArgument2,
longArgument3% B2 d# a0 B2 M4 t0 E
);
thisFunctionCallIsReallyLong(
longArgument1,0 Z% O4 P( E! a
longArgument2,
longArgument3);0 D9 s: V) @# x8 d; x
对应的赋值语句应该是这样写:6 M4 C0 D+ I# Z. e! G/ B
thisIsALongNestedMapping[being][set][to_some_value] = someFunction(
argument1," _2 ~% k2 q' o+ \- g% d
argument2,% _6 U. N9 n9 k T; p4 E# k, R
argument3,2 P9 A4 Q. ?6 Z. Q1 T) Z* n& ]) Q& s( V
argument41 }: w' k" t) i8 J
);
而不是:! s/ S$ q! @* q5 f1 W/ ]- k
thisIsALongNestedMapping[being][set][to_some_value] = someFunction(argument1,2 {1 V; e, u& |) A/ {
argument2,
argument3, e r9 s3 F- R' G2 s/ O
argument4);& V( u3 h% v2 @9 M5 {; [4 _' O
事件定义也应该遵循同样的原则,例如应该使用:' {6 C0 p u4 Q
event LongAndLotsOfArgs(
adress sender,1 Y0 J: p5 Y/ Z7 S
adress recipient,
uint256 publicKey,
uint256 amount,5 h- `4 S6 c3 I+ `3 Y4 d( V1 c
bytes32[] options
);* b# c0 D( u. v A9 A3 f2 |( w
LongAndLotsOfArgs(
sender,1 S+ E; B8 ]9 N1 r
recipient,9 B. d5 F% }! P, {7 k) k Q
publicKey,2 V9 M* U' }/ G2 ^. [! }0 G1 \/ V. h
amount,9 w$ L8 o9 {3 R$ r0 z' U
options6 \; m0 M% q5 W/ [% M; t
);
而不是:
event LongAndLotsOfArgs(adress sender,
adress recipient,
uint256 publicKey,* O5 ^1 K! D7 R+ ^: e! A
uint256 amount,0 v# p" P! [+ j$ i3 S' d0 K
bytes32[] options);
LongAndLotsOfArgs(sender,
recipient,
publicKey,
amount,: n. S! Z% H3 L* m* N5 e. R+ [
options);
文件编码格式! b4 ]) B& I L
推荐使用utf-8 及 ASCII 编码& a& m$ ?8 q; W( K/ b
引入文件应该在最上方
建议使用:
import "owned";1 d& A3 K( m V/ h4 h, V1 m
contract A {4 v7 P/ z m/ b
...
}
contract B is owned {
...- n+ _$ p7 Q _* C
}
而不是:
contract A {
...' n# H7 |9 W0 J1 c$ o% T
}" B( G }8 i& H1 M4 J: U
import "owned";1 s4 K* ?' a/ \- L2 E7 P! }# K
contract B is owned {
...
}$ O" j) e, O2 O- S
函数编写规范' |! K0 O: ^4 i$ K$ i9 m. L# s
函数的顺序# u3 y1 f7 O- U4 D$ f4 C6 P, o" {: D
在编写函数的时候,应该让大家容易找到构造函数,回退函数,官方推荐的的函数顺序是: Z x) _/ a9 S# ]" x! j
构造函数回退函数 (如果有)外部函数(external)公有函数(public)内部函数(internal)私有函数(private)
7 t1 k+ U G& y4 m& M' a! d
同一类函数时,constant函数放在后面, 例如推荐方式为:
contract A {
// 构造函数
function A() public {
...) F" b" Q" C/ z) v& r. c$ }
}
// 回退函数6 x# h# t$ \+ Q4 {5 T; h# [* d% F, n, g
function() public {
...
}* O( C7 P2 K9 r: V! P' u
// 外部函数+ o8 j4 G, F# C7 y
// ..." J+ P: O* B* ^9 ]* i
// 带有constant 外部函数
// ...) s6 e" E7 J0 H" D% H7 M! @% u
// 公有函数
// ...- ~" ~7 A: q$ j* t; B- M0 M8 x+ y
// 内部函数
// ...; J; f! T) E7 J) k* r
// 私有函数# X% D r+ ~$ B3 K$ ^
// ...
}
而不是下面的函数顺序:
contract A {
// 外部函数 j2 R) e9 P; |; @+ C* z% w$ a9 }$ n8 _! ?0 m
// ...
// 公有函数
// ...
// 内部函数( J$ X9 K T1 o0 s' g* ]( w" I! z
// ...
function A() public {
...
}
function() public {
...
}
// 私有函数
// ...
}
明确函数的可见性
所有的函数(包括构造函数)应该在定义的时候明确函数的可见性,例如应该使用:
function explicitlyPublic(uint val) public {. y% N$ e! J! A- }! {
doSomething();
}6 j, W& `+ S- M8 l
而不是
function implicitlyPublic(uint val) {
doSomething();
}
可见性应该在修饰符前面; y0 B" I3 m5 t. i" }: }7 R
函数的可见性应该写在自定义的函数修饰符前面,例如:
function kill() public onlyowner {, R4 f8 Z: e' f" g- a4 @9 ]
selfdestruct(owner);
}
而不是
function kill() onlyowner public {
selfdestruct(owner);2 q7 F4 c+ A# W& r
}
区分函数和事件
为了防止函数和事件(Event)产生混淆,声明一个事件使用大写并加入前缀(可使用LOG)。对于函数, 始终以小写字母开头,构造函数除外。2 W/ ]; d) Y- S P! G) B
// 不建议
event Transfer() {} z" V7 d2 d- Z4 [( h% U6 t5 H F
function transfer() {}9 a6 y# F h, c8 l" j
// 建议9 h+ j# S) b3 c" P% E
event LogTransfer() {}
function transfer() external {}5 W# B$ ^9 C1 i7 b% l
常量. Q1 v7 d) f$ U1 R/ I5 { T
常量应该使用全大写及下划线分割大词的方式,如:MAX_BLOCKS,TOKEN_NAME, CONTRACT_VERSION。+ T1 W6 y, J% g) a
参考文献; t4 [+ r* \& O$ O# l; F9 k
Solidity style-guide
成为第一个吐槽的人