はじめに(Introduction)
以下を翻訳します。
スタイルガイド(Style Guide)
はじめに(Introduction)
このガイドは、Solidity コードを記述するためのコーディング規則を提供することを目的としています。
このガイドは、有用な規則が見つかり、古い規則が時代遅れになるにつれて、時間の経過とともに変化する進化するドキュメントとして考えてください。
多くのプロジェクトでは、独自のスタイル ガイドを実装します。
競合が発生した場合は、プロジェクト固有のスタイル ガイドが優先されます。
このスタイル ガイド内の構造と推奨事項の多くは、Python の pep8 スタイル ガイドから引用されています。
このガイドの目的は、Solidity コードの正しい書き方や最善の方法を示すことではありません。
このガイドの目的は一貫性です。
Python の pep8 からの引用がこの概念をよく表しています。
注(Note)
スタイル ガイドは一貫性に関するものです。
このスタイル ガイドとの一貫性は重要です。
プロジェクト内の一貫性はさらに重要です。
1 つのモジュールまたは機能内の一貫性が最も重要です。
しかし最も重要なのは、一貫性を欠くべきタイミングを知ることです。スタイル ガイドが適用されない場合もあります。
疑問がある場合は、最善の判断を下してください。
他の例を見て、最も見栄えの良いものを決めてください。
遠慮なく質問してください。
コードレイアウト(Code Layout)
インデント(Indentation)
インデント レベルごとに 4 つのスペースを使用します。
タブまたはスペース(Tabs or Spaces)
スペースは推奨されるインデント方法です。
タブとスペースの混在は避けてください。
空白行(Blank Lines)
Solidity ソースのトップレベルの宣言を 2 つの空白行で囲みます。
Yes:
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.0 <0.9.0;
contract A {
// ...
}
contract B {
// ...
}
contract C {
// ...
}
No:
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.0 <0.9.0;
contract A {
// ...
}
contract B {
// ...
}
contract C {
// ...
}
コントラクト内では、関数宣言を 1 行の空白行で囲みます。
関連する1行(one-liners)のグループ間では空白行を省略できます (抽象コントラクトのスタブ関数など)
Yes:
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.6.0 <0.9.0;
abstract contract A {
function spam() public virtual pure;
function ham() public virtual pure;
}
contract B is A {
function spam() public pure override {
// ...
}
function ham() public pure override {
// ...
}
}
No:
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.6.0 <0.9.0;
abstract contract A {
function spam() virtual pure public;
function ham() public virtual pure;
}
contract B is A {
function spam() public pure override {
// ...
}
function ham() public pure override {
// ...
}
}
最大行長(Maximum Line Length)
推奨される最大行長は 120 文字です。
折り返された行は、次のガイドラインに従う必要があります。
- 最初の引数は、開き括弧に付けないでください
- インデントは 1 つだけ使用してください
- 各引数は、それぞれ別の行に記述してください
- 終了要素
);
は、最終行に単独で配置してください
関数呼び出し(Function Calls)
Yes:
thisFunctionCallIsReallyLong(
longArgument1,
longArgument2,
longArgument3
);
No:
thisFunctionCallIsReallyLong(longArgument1,
longArgument2,
longArgument3
);
thisFunctionCallIsReallyLong(longArgument1,
longArgument2,
longArgument3
);
thisFunctionCallIsReallyLong(
longArgument1, longArgument2,
longArgument3
);
thisFunctionCallIsReallyLong(
longArgument1,
longArgument2,
longArgument3
);
thisFunctionCallIsReallyLong(
longArgument1,
longArgument2,
longArgument3);
割り当てステートメント(Assignment Statements)
Yes:
thisIsALongNestedMapping[being][set][toSomeValue] = someFunction(
argument1,
argument2,
argument3,
argument4
);
No:
thisIsALongNestedMapping[being][set][toSomeValue] = someFunction(argument1,
argument2,
argument3,
argument4);
イベント定義とイベントエミッター(Event Definitions and Event Emitters)
Yes:
event LongAndLotsOfArgs(
address sender,
address recipient,
uint256 publicKey,
uint256 amount,
bytes32[] options
);
emit LongAndLotsOfArgs(
sender,
recipient,
publicKey,
amount,
options
);
No:
event LongAndLotsOfArgs(address sender,
address recipient,
uint256 publicKey,
uint256 amount,
bytes32[] options);
emit LongAndLotsOfArgs(sender,
recipient,
publicKey,
amount,
options);
ソース ファイルのエンコード(Source File Encoding)
UTF-8 または ASCII エンコードが推奨されます。
インポート(Imports)
インポート ステートメントは常にファイルの先頭に配置する必要があります。
Yes:
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.0 <0.9.0;
import "./Owned.sol";
contract A {
// ...
}
contract B is Owned {
// ...
}
No:
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.0 <0.9.0;
contract A {
// ...
}
import "./Owned.sol";
contract B is Owned {
// ...
}
関数の順序(Order of Functions)
順序付けにより、読者はどの関数を呼び出すことができるかを特定し、コンストラクターとフォールバックの定義を見つけやすくなります。
関数は可視性に応じてグループ化し、順序付けする必要があります:
- constructor
- receive function (if exists)
- fallback function (if exists)
- external
- public
- internal
- private
グループ内では、ビュー関数と純粋関数を最後に配置します。
Yes:
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.0 <0.9.0;
contract A {
constructor() {
// ...
}
receive() external payable {
// ...
}
fallback() external {
// ...
}
// External functions
// ...
// External functions that are view
// ...
// External functions that are pure
// ...
// Public functions
// ...
// Internal functions
// ...
// Private functions
// ...
}
No:
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.0 <0.9.0;
contract A {
// External functions
// ...
fallback() external {
// ...
}
receive() external payable {
// ...
}
// Private functions
// ...
// Public functions
// ...
constructor() {
// ...
}
// Internal functions
// ...
}
式内の空白(Whitespace in Expressions)
次の状況では余分な空白を避けてください:
1 行の関数宣言を除き、括弧、角括弧、または中括弧のすぐ内側。
Yes:
spam(ham[1], Coin({name: "ham"}));
No:
spam( ham[ 1 ], Coin( { name: "ham" } ) );
例外(Exception):
function singleLine() public { spam(); }
カンマ、セミコロンの直前:
Yes:
function spam(uint i, Coin coin) public;
No:
function spam(uint i , Coin coin) public ;
代入演算子または他の演算子の周囲に複数のスペースを入れて、他の演算子と揃える:
Yes:
x = 1;
y = 2;
longVariable = 3;
No:
x = 1;
y = 2;
longVariable = 3;
受信関数とフォールバック関数に空白を含めないでください。
Yes:
receive() external payable {
...
}
fallback() external {
...
}
No:
receive () external payable {
...
}
fallback () external {
...
}
制御構造(Control Structures)
コントラクト、ライブラリ、関数、構造体の本体を示す中括弧は、次のようになります。
- 宣言と同じ行で開きます
- 宣言の先頭と同じインデント レベルで、独自の行で閉じます
- 開き括弧の前には、1 つのスペースが必要です
Yes:
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.0 <0.9.0;
contract Coin {
struct Bank {
address owner;
uint balance;
}
}
No:
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.0 <0.9.0;
contract Coin
{
struct Bank {
address owner;
uint balance;
}
}
同じ推奨事項が制御構造 if
、else
、while
、および for
にも適用されます。
さらに、制御構造 if
、while
、for
と条件を表す括弧ブロックの間には 1 つのスペースが必要です。また、条件括弧ブロックと開き中括弧の間にも 1 つのスペースが必要です。
Yes:
if (...) {
...
}
for (...) {
...
}
No:
if (...)
{
...
}
while(...){
}
for (...) {
...;}
本体に 1 つのステートメントが含まれる制御構造の場合、ステートメントが 1 行に含まれている場合は中括弧を省略しても問題ありません。
Yes:
if (x < 10)
x += 1;
No:
if (x < 10)
someArray.push(Coin({
name: 'spam',
value: 42
}));
else
または else if
節を含む if
ブロックの場合、else
は if
の閉じ括弧と同じ行に配置する必要があります。これは、他のブロックのような構造のルールと比較すると例外です。
Yes:
if (x < 3) {
x += 1;
} else if (x > 7) {
x -= 1;
} else {
x = 5;
}
if (x < 3)
x += 1;
else
x -= 1;
No:
if (x < 3) {
x += 1;
}
else {
x -= 1;
}
関数宣言(Function Declaration)
短い関数宣言の場合、関数本体の開き中括弧を関数宣言と同じ行に置くことをお勧めします。
閉じ中括弧は関数宣言と同じインデント レベルにする必要があります。
開き中括弧の前には 1 つのスペースが必要です。
Yes:
function increment(uint x) public pure returns (uint) {
return x + 1;
}
function increment(uint x) public pure onlyOwner returns (uint) {
return x + 1;
}
No:
function increment(uint x) public pure returns (uint)
{
return x + 1;
}
function increment(uint x) public pure returns (uint){
return x + 1;
}
function increment(uint x) public pure returns (uint) {
return x + 1;
}
function increment(uint x) public pure returns (uint) {
return x + 1;}
関数の修飾子の順序は次のようになります:
- 可視性(Visibility:
external
public
internal
private
) - 可変性(Mutability:
pure
view
) - 仮想(Virtual)
- オーバーライド(Override)
- カスタム修飾子(Custom modifiers)
Yes:
function balance(uint from) public view override returns (uint) {
return balanceOf[from];
}
function increment(uint x) public pure onlyOwner returns (uint) {
return x + 1;
}
No:
function balance(uint from) public override view returns (uint) {
return balanceOf[from];
}
function increment(uint x) onlyOwner public pure returns (uint) {
return x + 1;
}
長い関数宣言の場合、各引数を関数本体と同じインデント レベルで独自の行に配置することをお勧めします。
閉じ括弧と開き括弧も、関数宣言と同じインデント レベルで独自の行に配置する必要があります。
Yes:
function thisFunctionHasLotsOfArguments(
address a,
address b,
address c,
address d,
address e,
address f
)
public
{
doSomething();
}
No:
function thisFunctionHasLotsOfArguments(address a, address b, address c,
address d, address e, address f) public {
doSomething();
}
function thisFunctionHasLotsOfArguments(address a,
address b,
address c,
address d,
address e,
address f) public {
doSomething();
}
function thisFunctionHasLotsOfArguments(
address a,
address b,
address c,
address d,
address e,
address f) public {
doSomething();
}
長い関数宣言に修飾子がある場合は、各修飾子をそれぞれの行に削除する必要があります。
Yes:
function thisFunctionNameIsReallyLong(address x, address y, address z)
public
onlyOwner
priced
returns (address)
{
doSomething();
}
function thisFunctionNameIsReallyLong(
address x,
address y,
address z
)
public
onlyOwner
priced
returns (address)
{
doSomething();
}
No:
function thisFunctionNameIsReallyLong(address x, address y, address z)
public
onlyOwner
priced
returns (address) {
doSomething();
}
function thisFunctionNameIsReallyLong(address x, address y, address z)
public onlyOwner priced returns (address)
{
doSomething();
}
function thisFunctionNameIsReallyLong(address x, address y, address z)
public
onlyOwner
priced
returns (address) {
doSomething();
}
複数行の出力パラメータと戻りステートメントは、「最大行長」セクションに記載されている長い行を折り返す場合に推奨されるスタイルに従う必要があります。
Yes:
function thisFunctionNameIsReallyLong(
address a,
address b,
address c
)
public
returns (
address someAddressName,
uint256 LongArgument,
uint256 Argument
)
{
doSomething()
return (
veryLongReturnArg1,
veryLongReturnArg2,
veryLongReturnArg3
);
}
No:
function thisFunctionNameIsReallyLong(
address a,
address b,
address c
)
public
returns (address someAddressName,
uint256 LongArgument,
uint256 Argument)
{
doSomething()
return (veryLongReturnArg1,
veryLongReturnArg1,
veryLongReturnArg1);
}
ベースに引数が必要な継承コントラクトのコンストラクター関数の場合、関数宣言が長いか読みにくい場合は、修飾子と同じ方法でベースコンストラクターを新しい行にドロップすることをお勧めします。
Yes:
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.0 <0.9.0;
// Base contracts just to make this compile
contract B {
constructor(uint) {
}
}
contract C {
constructor(uint, uint) {
}
}
contract D {
constructor(uint) {
}
}
contract A is B, C, D {
uint x;
constructor(uint param1, uint param2, uint param3, uint param4, uint param5)
B(param1)
C(param2, param3)
D(param4)
{
// do something with param5
x = param5;
}
}
No:
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.0 <0.9.0;
// Base contracts just to make this compile
contract B {
constructor(uint) {
}
}
contract C {
constructor(uint, uint) {
}
}
contract D {
constructor(uint) {
}
}
contract A is B, C, D {
uint x;
constructor(uint param1, uint param2, uint param3, uint param4, uint param5)
B(param1)
C(param2, param3)
D(param4) {
x = param5;
}
}
contract X is B, C, D {
uint x;
constructor(uint param1, uint param2, uint param3, uint param4, uint param5)
B(param1)
C(param2, param3)
D(param4) {
x = param5;
}
}
短い関数を 1 つのステートメントで宣言する場合は、1 行で実行できます。
許可されるもの(Permissible):
function shortFunction() public { doSomething(); }
関数宣言に関するこれらのガイドラインは、読みやすさを向上させることを目的としています。
このガイドでは関数宣言の可能な組み合わせをすべて網羅するわけではないため、作成者は最善の判断を下す必要があります。
マッピング(Mappings)
変数宣言では、キーワード mapping
とその型をスペースで区切らないでください。
ネストされた mapping
キーワードとその型を空白で区切らないでください。
Yes:
mapping(uint => uint) map;
mapping(address => bool) registeredAddresses;
mapping(uint => mapping(bool => Data[])) public data;
mapping(uint => mapping(uint => s)) data;
No:
mapping (uint => uint) map;
mapping( address => bool ) registeredAddresses;
mapping (uint => mapping (bool => Data[])) public data;
mapping(uint => mapping (uint => s)) data;
変数宣言(Variable Declarations)
配列変数の宣言では、型と括弧の間にスペースを入れないでください。
Yes:
uint[] x;
No:
uint [] x;
その他の推奨事項(Other Recommendations)
- 文字列は、一重引用符ではなく二重引用符で囲む必要があります。
Yes:
str = "foo";
str = "Hamlet says, 'To be or not to be...'";
No:
str = 'bar';
str = '"Be yourself; everyone else is already taken." -Oscar Wilde';
- 演算子の両側に 1 つのスペースを入れます
Yes:
x = 3;
x = 100 / 10;
x += 3 + 4;
x |= y && z;
No:
x=3;
x = 100/10;
x += 3+4;
x |= y&&z;
- 他の演算子よりも優先度の高い演算子は、優先順位を示すために周囲の空白を除外できます
これは、複雑なステートメントの読みやすさを向上させるためのものです
演算子の両側には常に同じ量の空白を使用する必要があります
Yes:
x = 2**3 + 5;
x = 2*y + 3*z;
x = (a+b) * (a-b);
No:
x = 2** 3 + 5;
x = y+z;
x +=1;
レイアウトの順序(Order of Layout)
コントラクト要素は次の順序でレイアウトする必要があります:
- プラグマ ステートメント(Pragma statements)
- インポート ステートメント(Import statements)
- イベント(Events)
- エラー(Errors)
- インターフェイス(Interfaces)
- ライブラリ(Libraries)
- コントラクト(Contracts)
各コントラクト、ライブラリ、またはインターフェイス内では、次の順序を使用します:
- 型宣言(Type declarations)
- 状態変数(State variables)
- イベント(Events)
- エラー(Errors)
- 修飾子(Modifiers)
- 関数(Functions)
注(Note)
イベントや状態変数での使用に近い型を宣言する方が明確になる場合があります。
Yes:
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.8.4 <0.9.0;
abstract contract Math {
error DivideByZero();
function divide(int256 numerator, int256 denominator) public virtual returns (uint256);
}
No:
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.8.4 <0.9.0;
abstract contract Math {
function divide(int256 numerator, int256 denominator) public virtual returns (uint256);
error DivideByZero();
}
命名規則(Naming Conventions)
命名規則は、広く採用され使用されると強力になります。
さまざまな規則を使用すると、すぐには利用できない重要なメタ情報を伝えることができます。
ここで示す命名に関する推奨事項は、読みやすさを向上させることを目的としており、ルールではなく、名前を通じて最大限の情報を伝えるためのガイドラインです。
最後に、コードベース内の一貫性は、このドキュメントで概説されている規則よりも常に優先されます。
命名スタイル(Naming Styles)
混乱を避けるため、異なる命名スタイルを示すために次の名前が使用されます。
-
b
(小文字 1 文字) -
B
(大文字 1 文字) lowercase
UPPERCASE
UPPER_CASE_WITH_UNDERSCORES
-
CapitalizedWords
(または CapWords) -
mixedCase
(先頭の文字が小文字である点が CapitalizedWords と異なります!)
注(Note)
CapWords で頭文字を使用する場合は、頭文字の文字をすべて大文字にします。
したがって、HTTPServerError は HttpServerError よりも適しています。
mixedCase で頭文字を使用する場合は、頭文字の文字をすべて大文字にしますが、名前の先頭の場合は最初の文字を小文字のままにします。
したがって、xmlHTTPRequest は XMLHTTPRequest よりも適しています。
避けるべき名前(Names to Avoid)
-
l
- 小文字の el -
O
- 大文字の oh -
I
- 大文字の eye
1 文字の変数名には、これらのいずれも使用しないでください。
多くの場合、数字の 1 と 0 と区別がつきません。
契約とライブラリの名前(Contract and Library Names)
契約とライブラリは、CapWords スタイルを使用して命名する必要があります。
例: SimpleToken
、SmartBank
、CertificateHashRepository
、Player
、Congress
、Owned
。
契約とライブラリの名前もファイル名と一致する必要があります。
契約ファイルに複数の契約やライブラリが含まれている場合、ファイル名はコア契約と一致する必要があります。
ただし、回避できる場合は推奨されません。
以下の例に示すように、契約名が Congress
でライブラリ名が Owned
の場合、関連するファイル名は Congress.sol
と Owned.sol
になります。
Yes:
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.0 <0.9.0;
// Owned.sol
contract Owned {
address public owner;
modifier onlyOwner {
require(msg.sender == owner);
_;
}
constructor() {
owner = msg.sender;
}
function transferOwnership(address newOwner) public onlyOwner {
owner = newOwner;
}
}
そして Congress.sol
では:
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.0 <0.9.0;
import "./Owned.sol";
contract Congress is Owned, TokenRecipient {
//...
}
No:
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.0 <0.9.0;
// owned.sol
contract owned {
address public owner;
modifier onlyOwner {
require(msg.sender == owner);
_;
}
constructor() {
owner = msg.sender;
}
function transferOwnership(address newOwner) public onlyOwner {
owner = newOwner;
}
}
そして Congress.sol
では:
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.7.0;
import "./owned.sol";
contract Congress is owned, tokenRecipient {
//...
}
構造体名(Struct Names)
構造体は CapWords スタイルを使用して命名する必要があります。
例: MyCoin
、Position
、PositionXY
。
イベント名(Event Names)
イベントは CapWords スタイルを使用して名前を付ける必要があります。
例: Deposit
、Transfer
、Approval
、BeforeTransfer
、AfterTransfer
。
関数名(Function Names)
関数では大文字と小文字を混在させて使用する必要があります。
例: getBalance
、transfer
、verifyOwner
、addMember
、changeOwner
。
関数の引数名(Function Argument Names)
関数の引数には、mixedCase を使用する必要があります。
例: initialSupply
、account
、recipientAddress
、senderAddress
、newOwner
。
カスタム構造体を操作するライブラリ関数を作成する場合、構造体は最初の引数である必要があり、常に self
という名前にする必要があります。
ローカル変数名と状態変数名(Local and State Variable Names)
mixedCase を使用します。
例: totalSupply
、remainingSupply
、balancesOf
、creatorAddress
、isPreSale
、tokenExchangeRate
。
定数(Constants)
定数の名前はすべて大文字で、単語はアンダースコアで区切る必要があります。
例: MAX_BLOCKS
、TOKEN_NAME
、TOKEN_TICKER
、CONTRACT_VERSION
。
修飾子名(Modifier Names)
mixedCase を使用します。
例: onlyBy
、onlyAfter
、onlyDuringThePreSale
。
列挙型(Enums)
列挙型は、単純な型宣言のスタイルで、CapWords スタイルを使用して名前を付ける必要があります。
例: TokenGroup
、Frame
、HashStyle
、CharacterLocation
。
名前の衝突を避ける(Avoiding Naming Collisions)
singleTrailingUnderscore_
この規則は、希望する名前が既存の状態変数、関数、組み込み、またはその他の予約名と衝突する場合に推奨されます。
非外部関数と変数のアンダースコアプレフィックス(Underscore Prefix for Non-external Functions and Variables)
_singleLeadingUnderscore
この規則は、非外部関数と状態変数 (private
または internal
) に推奨されます。
可視性が指定されていない状態変数は、デフォルトで internal
です。
スマート コントラクトを設計する場合、公開 API (どのアカウントでも呼び出せる関数) は重要な考慮事項です。
先頭にアンダースコアを付けると、そのような関数の意図をすぐに認識できますが、さらに重要なのは、関数を非外部から外部 (public
を含む) に変更し、それに応じて名前を変更すると、名前を変更するときにすべての呼び出しサイトを確認する必要があることです。
これは、意図しない外部関数やセキュリティ脆弱性の一般的な原因に対する重要な手動チェックになる可能性があります (この変更では、検索と置換ツールの使用を避けてください)。
NatSpec
Solidity コントラクトには NatSpec コメントも含めることができます。コメントは 3 つのスラッシュ (///
) または 2 つのアスタリスク ブロック (/** ... */
) で記述され、関数の宣言またはステートメントのすぐ上で使用する必要があります。
たとえば、コメントが追加された単純なスマート コントラクトのコントラクトは次のようになります。
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.16 <0.9.0;
/// @author The Solidity Team
/// @title A simple storage example
contract SimpleStorage {
uint storedData;
/// Store `x`.
/// @param x the new value to store
/// @dev stores the number in the state variable `storedData`
function set(uint x) public {
storedData = x;
}
/// Return the stored value.
/// @dev retrieves the value of the state variable `storedData`
/// @return the stored value
function get() public view returns (uint) {
return storedData;
}
}
Solidity コントラクトは、すべてのパブリック インターフェース (ABI 内のすべて) に対して NatSpec を使用して完全に注釈を付けることが推奨されます。
詳細な説明については、NatSpec に関するセクションを参照してください。
まとめ(Conclusion)
コーディング規約はコードの可読性を上げるので基礎はおさえておいた方が良いと思います。
ただほとんどの場合、VSCodeなどの開発ツールでフォーマットをしてくれるので、そちらを採用する方がいいと思いました。
ただ、変数名の_
の使い方や、関数の順序については理解しておいた方がいいと思いました。