智能合约最佳实践 之 Solidity 编码规范
人民干脆面
发表于 2022-11-13 23:49:49
133
0
0
命名规范1 u5 k, E! T3 J8 v- [* d
避免使用
小写的l,大写的I,大写的O 应该避免在命名中单独出现,因为很容易产生混淆。
合约、库、事件、枚举及结构体命名
合约、库、事件及结构体命名应该使用单词首字母大写的方式,这个方式也称为:帕斯卡命名法或大驼峰式命名法,比如:SimpleToken, SmartBank, CertificateHashRepository,Player。
函数、参数、变量及修饰器0 ?; Q9 A* z3 l4 p1 [) o
函数、参数、变量及修饰器应该使用首单词小写后面单词大写的方式,这个方式也称为:(小)驼峰式命名法,是一种混合大小写的方式,如:
函数名应该如:getBalance,transfer,verifyOwner,addMember。参数和变量应该如:initialSupply,senderAddress,account,isPreSale。修饰器应该如:onlyAfter,onlyOwner。- F+ b( a* @4 n3 U6 k
代码格式相关
缩进4 L7 P; H9 J' j5 \* T( {/ B: V
使用空格(spaces)而不是Tab, 缩进应该是4个空格( a, F6 `4 u. o/ G: C- C) D6 n
空行
合约之间应该有空行,例如:( t3 C1 l6 }6 H* W. ]( w0 {+ Q/ f- T4 `: F
contract A {
...- i! R5 ] E/ U( N% Z
}0 p' c& I* P5 A K5 |% _
& n* w4 A2 i$ w ]
: h! S' h; G6 L& m1 D" t
contract B {) {3 _4 {1 a( D
...
}% W& [& r; j5 T+ _ I3 R
$ h: {0 l5 k- t& Z1 o; o+ j
contract C {( X: p# u6 \; H( ?
...- V9 b- i1 Z! J- B
}/ v, ~" p* o$ R& ?4 O. B$ [
而不是使用:
contract A {1 Y [6 t/ j8 K$ P3 [
...
}
contract B {6 C, L. o% g6 G7 n& z1 u' W: J
...
}
contract C {1 C( P' d3 h1 {/ _
...
}6 ~; {( |) {: g5 {& W. _+ G4 A
函数之间应该有空行,例如:+ o* c6 }! ]' u. `5 o# L4 c+ g1 H: y
contract A {6 N$ `. x& M' d# z" {
function spam() public {
...( z. ~- e9 W6 }' i; x7 }! s) Q
}; }8 i. A2 D+ u' F3 ^8 Z
" E" S" o* F" } x \; P8 Z4 a
function ham() public {
...
}
}* |( s% Z) z" t/ u0 i" p
没有实现的话,空行可以省去,如:
contract A {
function spam() public;
function ham() public;
} [' q4 u% C/ `: I7 K! X5 A0 [
而不是: k9 t2 [7 i- |/ L
contract A {
function spam() public {
...
}1 R, Q9 L/ X$ X( i# M T5 n
function ham() public {
...
}" b2 V8 _4 ]0 F; v6 K
}
左括号应该跟定义在一行6 k, r' b) a+ t* ]
定义包括合约定义、函数定义、库定义、结构体定义等等,例如推荐使用:8 ]1 e9 n9 g% N5 s
contract Coin {/ y+ z9 k# s$ T$ K: Z9 e5 W
struct Bank {6 Z2 b! o! h8 y/ B9 G( Z
address owner;
uint balance; e9 M3 i$ A) v; x# }
}" G/ J# [+ g% F9 ^
}3 k3 a) K3 g* o8 z" \! ]& R
而不是:
contract Coin
{
struct Bank {* n" {# d8 W; p! F& j) j/ a' y) d
address owner;
uint balance;
}- ?: d' S" @% I O
}- ?+ i# j. P+ h6 i; y" [
左括号应该跟条件控制在一行
在使用if, else, while, for 时,推荐的写法是:
if (...) {+ S3 y$ V# k" P5 s9 R/ q& V: W% a
...
}$ R) _7 b! R* ?' ~# f: ]3 m1 y$ q; y
for (...) {5 e8 q. o$ d- m
...5 b; Q# O, @( z% K* c3 g' w
}
而不是:
if (...)
{
...
}1 H4 F1 x- x9 t% n5 }9 s
while(...){
}% Y; M4 W! l( T# O8 E
for (...) {
...;}
如果控制语句内只有一行,括号可省略,如:
if (x
但像下面一个语句有多方就不能省略,如:
if (x
表达式内的空格
一个单行的表达里,在小括号、中括号、大括号里应该避免不必要的空格,例如推荐使用:
spam(ham[1], Coin({name: "ham"}));
而不是:
spam( ham[ 1 ], Coin( { name: "ham" } ) );
有一种例外是,结尾的括号跟在结束的分号后面, 应该加一个空格,如下面的方式也是推荐的:
function singleLine() public { spam(); }
分号;前不应该有空格,例如推荐使用:
function spam(uint i, Coin coin) public;
而不是:( g, [/ b$ @2 S4 H
function spam(uint i , Coin coin) public ;
不要为对齐添加不必要的空格,例如推荐使用:3 O/ T( p8 o9 J: W6 C1 [4 Y3 X
x = 1;, n2 Y t/ C- s: U
y = 2;- e! X- v6 I" X. I9 B+ n
long_variable = 3;
而不是:3 @# B3 O3 h, B7 m: w+ l9 B; I
13 G- S# G7 S! W3 \' r
23 a4 D2 U3 o1 r& g9 ^0 [
3) \' r, d( t3 k" n V
x = 1;8 p5 {9 n7 z/ A/ z
y = 2;; a2 ]3 u9 V( y
long_variable = 3;
回退函数不应该有空格,例如推荐使用:9 }- g9 k7 N2 E% R d ^
function() public {, I1 U+ c' b" [
...( F2 x- D w Y; d5 ?
}2 S" `, H0 L, `) G: _ S
而不是:
function () public {6 r/ h8 f0 v0 S5 ^1 W
..." c/ b8 a8 Z! ?: M7 i& R" i
}
控制每一行长度$ U$ l" C/ Q: r/ H _7 j; [
每行不应该太长,最好在79(或99)个字符以内,函数的参数应该是单独的行,且只有一个缩进,例如推荐的方式是:9 d6 P& v, {8 ]; i0 _+ t9 L
thisFunctionCallIsReallyLong(
longArgument1,' M# I6 W& }, ]# R" z7 \. F9 {
longArgument2,+ X% f8 @2 U0 Q( i A
longArgument3
);0 Z5 h n& S1 \) `, e# |
而不是:
thisFunctionCallIsReallyLong(longArgument1, Z, Y& |" V7 c% L K- Y+ ], j
longArgument2,
longArgument3
);
thisFunctionCallIsReallyLong(longArgument1,9 a" G- D8 s1 i0 c* G4 G
longArgument2,
longArgument39 Y; ?: O% i3 V7 D
);
thisFunctionCallIsReallyLong(
longArgument1, longArgument2,
longArgument3
);: _" K. _% E( K: Y% H A
thisFunctionCallIsReallyLong(
longArgument1,/ P* T) k+ Y2 r! Q+ o. O4 b
longArgument2,3 \- A7 r- F7 J1 M# f( m3 S- ~
longArgument3
);
thisFunctionCallIsReallyLong(- l; f( J( r1 \7 X! I1 P
longArgument1,8 y& m; q0 [+ [" f' L( @
longArgument2,3 j6 g/ D6 C: R0 u& N' L( Y# {
longArgument3);2 V- P/ T8 W p+ Z$ k- ~
对应的赋值语句应该是这样写:6 F/ H$ G% ^9 H0 `0 L
thisIsALongNestedMapping[being][set][to_some_value] = someFunction(
argument1,
argument2,
argument3,
argument4
);! N: n) V% u4 m# {
而不是:6 R N7 e7 ~* P" h) j
thisIsALongNestedMapping[being][set][to_some_value] = someFunction(argument1,' v+ v) s% m4 j) s
argument2,
argument3,, r% I3 c5 }% f/ m: j
argument4);' g2 [5 V8 ~. e4 p& c0 t
事件定义也应该遵循同样的原则,例如应该使用:
event LongAndLotsOfArgs(7 j8 E' o0 G ^9 e$ K& o3 Y( H8 `
adress sender,
adress recipient,3 W1 B! t, H% w- p
uint256 publicKey,! D7 T1 s6 T& ?- Y
uint256 amount,
bytes32[] options
);1 v( p8 E7 I1 J
LongAndLotsOfArgs(( f+ x; {3 y/ B
sender,
recipient,1 T. o2 Q2 o" \$ |/ E$ ]
publicKey,
amount,3 ?; D5 ?" u) @4 X0 ?. z
options- _' a9 {+ B6 j1 B
);
而不是:: B& F. U& o v
event LongAndLotsOfArgs(adress sender, e; j) ]8 K7 H
adress recipient,: h8 Z2 X; ]9 t4 o- m$ }
uint256 publicKey,2 x0 ?4 J( z1 @% b% _7 G1 j
uint256 amount,5 L. k- @& A7 D# v
bytes32[] options);7 H* ^$ z Q0 O& }6 j
LongAndLotsOfArgs(sender,
recipient, o g: ]3 T: }- I6 O3 h
publicKey,
amount,. g; J9 Z( @7 w( a& r
options);
文件编码格式
推荐使用utf-8 及 ASCII 编码( \' r# _" X$ _: j
引入文件应该在最上方! N R5 K6 r9 ^# R C
建议使用:2 y" F2 J# k# G. Y' X7 a! z( b
import "owned";( y" Y& e% n: L- |7 _7 @0 F
contract A {
...
}
contract B is owned {
...- b" `% }5 F* s0 J- B
}
而不是:
contract A {; m, e" i% Q6 W9 o7 n: A
...% S6 {6 i& }, N
}3 W1 s$ m8 o. R& H8 A
import "owned";
contract B is owned {
...' @% k' M7 c- B
}
函数编写规范, `+ r+ K3 F. a6 H$ f1 o
函数的顺序" ~3 }/ p/ @2 p' ]' ~- G; Y
在编写函数的时候,应该让大家容易找到构造函数,回退函数,官方推荐的的函数顺序是:! R( @9 ]% a- J9 q4 h+ s8 c$ {
构造函数回退函数 (如果有)外部函数(external)公有函数(public)内部函数(internal)私有函数(private)
同一类函数时,constant函数放在后面, 例如推荐方式为:
contract A {0 {/ P2 t. b& }2 ~ n
// 构造函数
function A() public {* m6 z$ B% L. S6 m1 ^9 {/ X2 f
...
}" f- n: z! S% U: w
// 回退函数5 g2 e' G/ r: A2 n/ P$ x
function() public {" W$ w7 P; X3 s
...
}1 B: U5 z9 ^+ ?3 v" A1 d
// 外部函数
// .... `5 s( ?. K' Y. F& ^
// 带有constant 外部函数
// ...
// 公有函数/ G3 ^* d7 o7 i B3 e# n1 z9 h2 ~
// ...% h4 _2 y1 ]4 G& W2 o( P
// 内部函数
// ...
// 私有函数4 o' e! \$ t- ` J, [5 y: W- N( w
// ...
}
而不是下面的函数顺序:
contract A {+ I$ D- P8 y& s( J3 {- A
// 外部函数' u# I3 Y4 C+ k8 c$ x6 a4 U
// ...2 q1 ^$ V9 F7 h) W3 @- ?
// 公有函数
// ...' t; A7 {% t3 q, i4 {6 K
// 内部函数
// ...* J* V6 i! ^9 o$ G) m
0 s! N$ n5 Y' `1 K
function A() public {
...
}5 Y% L) H0 p" D4 {
function() public {
..., N. R6 D L- C# V# m
}
// 私有函数
// ...
}
明确函数的可见性! Z9 k0 H, i% ~, \' O! C5 O/ n
所有的函数(包括构造函数)应该在定义的时候明确函数的可见性,例如应该使用:
function explicitlyPublic(uint val) public {3 d4 K a. V- J& ^3 _! V; J
doSomething();
}
而不是
function implicitlyPublic(uint val) { s: k" s6 n1 H# D
doSomething();
}+ g1 {: g( `3 F* L2 r- j+ _7 e# [
可见性应该在修饰符前面: j* S* _) M9 ]8 a; j
函数的可见性应该写在自定义的函数修饰符前面,例如:
function kill() public onlyowner {- n1 v8 @! f. m/ d% l
selfdestruct(owner);% p* Q; H6 T$ }+ h
}% v/ ?, G- J3 b
而不是
function kill() onlyowner public {
selfdestruct(owner);
}
区分函数和事件8 g! X4 M2 V1 `, u; T+ Z* i
为了防止函数和事件(Event)产生混淆,声明一个事件使用大写并加入前缀(可使用LOG)。对于函数, 始终以小写字母开头,构造函数除外。( n4 R4 A4 c: h& \0 l$ W
// 不建议' U1 L t: E3 G( \' P
event Transfer() {}
function transfer() {}
// 建议
event LogTransfer() {}1 e: C" F* ]* a! n( b
function transfer() external {}
常量
常量应该使用全大写及下划线分割大词的方式,如:MAX_BLOCKS,TOKEN_NAME, CONTRACT_VERSION。6 G4 J9 V( `! f: {6 y- ~6 J
参考文献
Solidity style-guide
成为第一个吐槽的人