智能合约最佳实践 之 Solidity 编码规范
人民干脆面
发表于 2022-11-13 23:49:49
122
0
0
命名规范! y" _% n3 s/ t. x
避免使用
小写的l,大写的I,大写的O 应该避免在命名中单独出现,因为很容易产生混淆。6 U% }2 y$ x2 T# Z& d/ O
合约、库、事件、枚举及结构体命名" x" t4 N8 N$ v% Z
合约、库、事件及结构体命名应该使用单词首字母大写的方式,这个方式也称为:帕斯卡命名法或大驼峰式命名法,比如:SimpleToken, SmartBank, CertificateHashRepository,Player。
函数、参数、变量及修饰器$ N. M& u" _8 r9 @. t! e
函数、参数、变量及修饰器应该使用首单词小写后面单词大写的方式,这个方式也称为:(小)驼峰式命名法,是一种混合大小写的方式,如:
函数名应该如:getBalance,transfer,verifyOwner,addMember。参数和变量应该如:initialSupply,senderAddress,account,isPreSale。修饰器应该如:onlyAfter,onlyOwner。
代码格式相关
缩进' K" {: H5 w% @7 g h3 {
使用空格(spaces)而不是Tab, 缩进应该是4个空格6 k3 Z% [. W- X4 ~* _8 \ c
空行; Y4 h8 r: g8 t9 S# I/ N; X7 T
合约之间应该有空行,例如:4 g0 ?: _2 _" x: Q! ~+ O5 l
contract A {. X/ n5 F* z9 I" V9 b* f3 d& Z
...
}: w4 i, f; p9 p! L' I9 T0 d
- O# f4 k* N( g' ~$ k0 k
contract B {" ^( K! p1 P: }9 Q" a7 j8 x/ I
...! ^; d0 A+ s( ?
}
- ^0 A4 h7 }$ X1 J8 V) X9 Y: h m0 f
contract C {
... \9 [0 _4 T1 C7 t' s$ J, o+ [
}
而不是使用:
contract A {
...
}) K: C2 D e; m
contract B {
...5 f4 ]5 J7 h& h" F$ }
}4 V! y! _/ _# m; E( S+ A0 S! ^
contract C {, x; o6 A; {0 ^& B% u; j0 J
... }/ J( X2 n4 m p
}
函数之间应该有空行,例如:9 h$ {: \7 {3 k4 B
contract A {0 f1 w5 h* U1 W
function spam() public {
...+ O* \6 A5 \8 z( o% v' V
}2 p, M6 J, i1 r
/ Y0 w( t, {7 u" B6 }0 P+ [. I
function ham() public {
...
}. P, K' P( S( T- x2 s! J7 h
}& m6 V$ k Q# m+ X6 D) m
没有实现的话,空行可以省去,如:
contract A {
function spam() public;
function ham() public;
}' {/ N$ r' R: w" M0 C4 O
而不是:% g$ ]$ y$ X0 O2 E) x- d- `
contract A {
function spam() public {
...
}7 x( V7 d( j8 t$ a9 F! h
function ham() public {' i5 r( \5 t: k; ` k5 L8 \- a D
...* v, x' H/ w: p, U5 _& ~9 h( c; D* ~
}
}- c! Z% `" Y9 r0 g! `( X' Z+ T
左括号应该跟定义在一行4 O9 E' B6 T& ^ n0 J' x
定义包括合约定义、函数定义、库定义、结构体定义等等,例如推荐使用:
contract Coin {$ N* |# S. V- F) L$ |
struct Bank {
address owner;
uint balance;* P7 |& f5 n w. ^8 u
}
}
而不是:% v, w+ O5 W4 S: ?; b
contract Coin
{5 p+ }% h9 W; s. i% M; f
struct Bank {
address owner;8 {: |; J& j% C
uint balance;
}
}
左括号应该跟条件控制在一行
在使用if, else, while, for 时,推荐的写法是:( h- J# a* @- ~$ s0 P
if (...) {
...; U8 Q# j. o1 D, b% r' s6 Q
}) [# L/ M: {% ^0 |- ^2 ^& a
for (...) {
...( P$ x1 A! J0 p k& T
}
而不是:
if (...)
{$ H9 p+ d& `' P, n7 h3 Q! s$ Y
...
}
while(...){
}
for (...) {
...;}; P; [- J4 w( ?4 P2 y; z6 p
如果控制语句内只有一行,括号可省略,如:$ k4 G$ ]* W/ N, Z
if (x
但像下面一个语句有多方就不能省略,如:
if (x 0 t$ _3 i, N4 {+ ` X8 M
表达式内的空格; ]/ c5 I6 v/ _/ @3 W4 }& {
一个单行的表达里,在小括号、中括号、大括号里应该避免不必要的空格,例如推荐使用:
spam(ham[1], Coin({name: "ham"}));' a/ }! W. V5 H! p) X) s
而不是:2 ?4 S; g% D/ g3 L' v
spam( ham[ 1 ], Coin( { name: "ham" } ) );
有一种例外是,结尾的括号跟在结束的分号后面, 应该加一个空格,如下面的方式也是推荐的:
function singleLine() public { spam(); }
分号;前不应该有空格,例如推荐使用:
function spam(uint i, Coin coin) public;
而不是:* J! D4 s3 p( F
function spam(uint i , Coin coin) public ;
不要为对齐添加不必要的空格,例如推荐使用:
x = 1;
y = 2;
long_variable = 3;+ ~* A4 w+ w/ g
而不是:
1! a4 u+ e2 K/ J l+ r
23 }- f X4 `3 b* T+ O. A7 D# g6 b
3$ k0 D5 J& G( @5 B5 _6 L
x = 1;
y = 2;! s* y' ]8 \) {) q+ R2 k+ ]. o. g# K
long_variable = 3;
回退函数不应该有空格,例如推荐使用:
function() public {
...) P- C) H% J* m
}
而不是:. t$ Y; u* ~, w7 S' N, a
function () public {! v/ a. i* b# p; c* ~" e
...
}
控制每一行长度
每行不应该太长,最好在79(或99)个字符以内,函数的参数应该是单独的行,且只有一个缩进,例如推荐的方式是:" ?. V- E3 y1 `8 Y" z$ E
thisFunctionCallIsReallyLong(
longArgument1,
longArgument2,
longArgument37 |! O0 |1 J; j
);
而不是:1 J: U( A8 M* D& e$ n
thisFunctionCallIsReallyLong(longArgument1,: h& \- g4 \0 A0 l% v9 m
longArgument2,# u+ Z w6 n7 v6 F* e2 t
longArgument3" v. Y& ?, _+ `8 F8 R9 r8 X
);7 E5 i! h$ c7 A
thisFunctionCallIsReallyLong(longArgument1,
longArgument2,/ D7 R' F( U2 k
longArgument3
);2 z2 Y; G$ t9 P- n# ~7 T6 q6 ]' [
thisFunctionCallIsReallyLong(; W5 w1 f2 P! W9 I" t
longArgument1, longArgument2,
longArgument3$ o) _! ~ Z3 t6 Y8 [/ e
);/ D: w# M& F4 p1 u
thisFunctionCallIsReallyLong(
longArgument1,
longArgument2,9 z/ v! E$ _4 O" f& k3 y0 ^
longArgument3
);
thisFunctionCallIsReallyLong(8 e, g$ n3 J, {0 ?. s9 d6 t
longArgument1,
longArgument2,
longArgument3);
对应的赋值语句应该是这样写:
thisIsALongNestedMapping[being][set][to_some_value] = someFunction(
argument1,
argument2,2 h1 o$ a: z6 J7 w9 P: }8 Z8 C2 c
argument3,1 t6 M1 i- v6 u; l: L2 V2 `1 G" z
argument4! s; Z! B4 E9 J% }5 M4 j; @
);4 u. [% U: Y" X
而不是: O* s! ?5 Y2 X6 ~
thisIsALongNestedMapping[being][set][to_some_value] = someFunction(argument1,. t1 X9 A" c$ f. X3 R
argument2,. B8 z1 j, n5 {5 Q, A+ U
argument3,
argument4);
事件定义也应该遵循同样的原则,例如应该使用:1 B' P7 ?( a# }: L" N
event LongAndLotsOfArgs(- S* |5 b: i8 m. t( t; \$ \6 G
adress sender,& g1 d- S8 p. F- i ~
adress recipient,0 ~2 n. p; \ D9 ^4 \5 n
uint256 publicKey,4 P5 ]2 B% e4 @1 E4 R- g3 @( E( q
uint256 amount,, M2 u5 P" R: v$ O
bytes32[] options: X- t {' Q# ~( i
);6 _# G3 Q* F O, u0 T. e! ?7 }4 d
LongAndLotsOfArgs(/ }3 h- A, a& k' L( L# o
sender,6 q6 U6 @5 y9 V G. K' P) `7 r
recipient,- L5 P8 M+ `% `9 G! {: _+ G) n
publicKey,* g! w$ I! ?) g) u, o, P4 {' Q X
amount,
options
); v8 p% ~7 G m7 l6 G- ]& `+ z. P0 t
而不是:
event LongAndLotsOfArgs(adress sender,
adress recipient,
uint256 publicKey,
uint256 amount,
bytes32[] options);
LongAndLotsOfArgs(sender, v, J% n: r |3 G& n8 W
recipient," B. M% a- B. V3 [" @
publicKey,. ]( h/ _ |9 T
amount,. o/ c* ?% |' v/ A* D: G6 K& E8 s
options);: H. T5 m3 Y' {9 V; v
文件编码格式
推荐使用utf-8 及 ASCII 编码# u% e: z8 C, m' ?4 i, z9 M
引入文件应该在最上方
建议使用:4 _" n" }/ i7 o$ |$ @7 x% G
import "owned";
contract A {
...
}
contract B is owned {
...* j/ i, E/ q& u: a+ d
}: _* k# p1 F; C, r; _* f, c7 K
而不是: k# y) S1 _' W* G
contract A {) t' h+ q0 p" v% j" f
...) d. N8 z2 @7 S+ _+ Z# g
}
import "owned";. L% {) O& V7 V( y; \2 `# f
contract B is owned {
.... I# Q2 C8 K n% H+ [; N6 [
}4 P8 A, I2 }; V6 t
函数编写规范# U" |4 V- K+ ^' ?+ f" b8 Y
函数的顺序
在编写函数的时候,应该让大家容易找到构造函数,回退函数,官方推荐的的函数顺序是:
构造函数回退函数 (如果有)外部函数(external)公有函数(public)内部函数(internal)私有函数(private)
7 h! r* `* A7 ^( d/ O
同一类函数时,constant函数放在后面, 例如推荐方式为:6 U1 U2 w4 T$ A, u/ D( C8 [
contract A {
// 构造函数8 R) A- A1 K$ v7 h0 X4 R% V
function A() public {% k, Q U$ u4 P; }+ @- f, F. V: G
...' V# Y( L& Z7 q0 R+ e8 m) i: Q6 Z
}/ J8 z0 Z0 ^- k0 S) C
// 回退函数
function() public {6 v1 r- P: i. I e/ V T
...
}4 j; z* E; k' f( w7 r: d, ?& `
// 外部函数
// ...
// 带有constant 外部函数 & u! z' Q$ w$ X9 `0 x
// ...: N: R# G4 Z" A1 l" u
// 公有函数/ R' {4 I+ J/ q6 y5 [& A; q. J
// .../ n; |& {+ |: @
// 内部函数7 q- A4 g, m' M( y' j* M0 X0 y9 y( c* h
// ...& ?8 }1 G( |' N0 J, }
// 私有函数4 z! k0 n$ f/ _4 X+ Y$ q6 E
// ...* F0 i% z$ j7 L$ v9 R6 f) _: e9 V: C
}
而不是下面的函数顺序:) J: I5 G3 K3 D! Z
contract A {
// 外部函数9 Q8 l7 L3 q; Z K) [# ^% Q
// ...# @4 B- |1 s" U- Q- ?- J0 n' X
// 公有函数
// ...; g6 u' `7 F2 h& e- A
// 内部函数
// ...
" k7 _1 c; Q. S2 J& e! E' z
function A() public {/ B3 _6 X& t3 K4 J2 \& ~7 v
...
}
function() public {
...9 g5 H1 l7 l6 v' D9 R3 V" F
}
// 私有函数
// ...
}
明确函数的可见性
所有的函数(包括构造函数)应该在定义的时候明确函数的可见性,例如应该使用:4 K! h' V, H5 K- V. x1 ]' ?5 f
function explicitlyPublic(uint val) public {; ~ Y8 |7 ~5 `5 w6 r
doSomething();
}
而不是# j- z+ }2 t$ n3 Z/ j* [- N% r' r
function implicitlyPublic(uint val) {
doSomething();
}6 D/ ^' |- T [* j5 o
可见性应该在修饰符前面
函数的可见性应该写在自定义的函数修饰符前面,例如:
function kill() public onlyowner {, K5 O! V. y+ u
selfdestruct(owner);
}( a' L3 q8 S1 q
而不是
function kill() onlyowner public {
selfdestruct(owner);
}
区分函数和事件 k3 C, a9 x0 l6 H2 q
为了防止函数和事件(Event)产生混淆,声明一个事件使用大写并加入前缀(可使用LOG)。对于函数, 始终以小写字母开头,构造函数除外。3 |. u$ w( N5 `8 [
// 不建议6 H' G# h% H& ]: {2 Z
event Transfer() {}( u; [7 z. I, z1 I. \
function transfer() {}+ M; K/ d3 U* P# r& h
// 建议
event LogTransfer() {}0 i% m; |! L0 V9 c9 J
function transfer() external {}/ ~) N' U' @ r
常量7 @; E0 e; L4 a' }$ S
常量应该使用全大写及下划线分割大词的方式,如:MAX_BLOCKS,TOKEN_NAME, CONTRACT_VERSION。
参考文献
Solidity style-guide
成为第一个吐槽的人