Subterranean Flower

[翻訳]Dart APIネーミングガイド

Author
古都こと
ふよんとえんよぷよぐやま!!!

この記事はDart公式サイトのAPI Naming Guideの翻訳です。

このガイドの目標は、あなたがDart APIを作るときに、良い名前を選ぶための手助けをすることです。このアドバイスは主にパブリックなAPIを対象にしていますが、ガイドラインに従うことでどんなコードでも――パブリック、プライベート、あるいはローカルのコードでも――より理解しやすくすることができます。

この記事のガイドラインのほとんどは常識的なものです。目標はいつもどおり、素晴らしく、読みやすく、保守しやすいコードです。もしガイドラインを破ることでコードが読みやすく覚えやすいものになると感じたならば、そのときはそうしてください。ただし熟慮したのちに、です。

次の関連する記事もご覧ください:

  • Guidelines for Doc Comments: APIドキュメントの書き方。
  • Dart Style Guide(※訳注:「Dartコーディングスタイルガイド」にて日本語で簡易的にまとめてあります):一般的なコード規約です。機械的にチェックできるルールのある、Namesセクションを含みます。例えば以下のようなものです:
    • 型名はUpperCamelCaseを使用する。
    • 識別子(定数を含む)はlowerCamelCaseを使用する。
    • ライブラリはlowercase_with_underscoresを使用し、パッケージ名とドット区切りのパスをプレフィックスに付けます。

一般ガイドライン

もっとも重要なルールは、あなたのAPIと一般的に使われているライブラリの先例に、一貫性を持たせることです。

一貫させる

API全体を通して、同じものには同じ名前を使用してください。あなたのAPI外に、あなたのAPIのユーザが知っていそうな先例が既に存在しているならば、その先例に従ってください。

pageCount         // フィールド
updatePageCount() // pageCountと一貫している
toSomething()     // IterableのtoList()と一貫している
asSomething()     // ListのasMap()と一貫している
Point             // 馴染みのある概念
countPages()         // pageCountと異なり紛らわしい
convertToSomething() // 先例のtoX()と一貫していない
wrappedAsSomething   // 先例のasX()と一貫していない
Cartesian            // 多くのユーザには馴染みがない

ひとつのケースが少し長くなったり使いにくくなったとしても、他のケースと一貫している限り、それでも正解です。

略語を避ける

省略形が非省略形よりも一般的な場合をのぞいて、省略形を使わないでください。もし省略する場合は、3文字以上の省略形は単語のように頭文字を大文字にしてください。

pageCount
buildRectangles
IOStream
HttpRequest
numPages // numはnumber(of)の省略形です
buildRects
InputOutputStream
HypertextTransferProtocolRequest

もっとも重要な名詞を最後に書く

最後の単語は、それが何であるのかを最も説明しているべきです。その単語を形容詞のような他の言葉で修飾して、より詳しく説明することができます。

pageCount             // (ページの)カウント
ConversionSink        // 変換を行うsink
ChunkedConversionSink // まとめられたConversionSink
numPages   // ページではない
LinkedList // リストではない

LinkedListの例は、LinkedListListインターフェイスを実装しているなら問題ありませんが、実際には実装していません。Javaの同名のLinkedListクラスはListであるという問題もあります。Javaを経験しているDartプログラマは混乱するかもしれません。

コードを文章のように読む

ネーミングで迷っているときは、APIを使用するコードを書いて、文章のように読んでみてください。

// もしerrorsが空なら…
if (errors.isEmpty)

// _subscriptionをキャンセル!
_subscription.cancel();

// streamのデータをlistenし、そのデータで何かをする。
stream.listen((data) => ...)

// xが偶数である間スキップする
skipWhile((x) => x.isEven)
// errorsのempty?errorsがempty?
if (errors.empty)

// falseの意味は?
_subscription.changeState(false);

// streamが何かしらのデータのためのリスナを登録する?
stream.registerListener((data) => ...)

// サブリストを取得…いつ?何の?
sublistStartingAfter((x) => x.isEven)

このページにある、青いボックスで囲まれたリーダビリティノートで、このガイドラインについてより詳しく触れています。

固有ガイドライン

フィールド、関数(メソッドを含む)、パラメータに名前をつけるときは以下のガイドラインに従ってください。

非boolean

booleanではないフィールド、getter、setter、パラメータには名詞を使用してください。

iterator
length
rampagingSwampBeast

Boolean

booleanのgetterは動詞で始めてください。

bool get isEmpty
bool get hasElements
bool get canClose
bool get willCodeForMoney
empty        // わかりにくい。形容詞?動詞?
withElements // エレメントを保持しているかもしれないように見える
closeable    // インターフェイスのように見える
codeForMoney // 不明瞭

Booleanのフィールドやsetterの名前は、プロパティに依存します。多くの場合、フィールドやsetterを持つ代わりに、getterとひとつ以上の状態を変更するメソッドがあったほうが、よりわかりやすくなります。

bool get isPaused
pause()
resume()
bool isPaused

Booleanのプロパティはシンプルであることもあります(そしてあり続けます)。そしてプロパティを設定することは副作用なしの明らかな結果になります。この場合、フィールドやsetterは単なるbooleanのgetterのように名づけてください。

bool shouldConsumeInput

Booleanの関数パラメータは動詞を省略してもかまいません。例えばIsolateのspawn()メソッドの第三引数はプロパティではないので、isPausedではなくpausedになっています。(isolateは存在すらしていません。ましてやspawn()が呼ばれるまでプロパティは持っていません。)

Isolate.spawn(function, argument, {paused: false, packageRoot})

関数

ほとんどの関数(メソッドを含む)と関数変数では動詞を使用してください。

add()
remove()
insertAfter()
updateRecord()

もし関数名の一部が(明示的でも暗黙的でも)引数に関連してるなら、引数は同じ順番になるように心がけてください。

collection.remove(element) // collectionがelementを削除する
parent.insertNodeBefore(node1, node2) // parentがnode2の前にnode1を挿入する
collection.removeElement(element) // "Element"は冗長です
parent.insertBefore(node1, node2) // parentはnode1とnode2のどちらの前に挿入する?

パラメータ化されたプロパティ

elementAt()のようなメソッドは、オブジェクトのプロパティを表しています。そしてそれゆえに、プロパティにちなんだ名前を、冗長なgetプレフィックス無しで付けてください。引数なしの場合、こられのメソッドはgetterになりうるでしょうが、Dartにはgetterのための引数をサポートしていません。

elementAt(...)

場合によっては最後のatforを省略してもかまいません。ただしそのメソッドが少なくともひとつの引数が必須である場合に限ります。Listのsublist()メソッドはその一例です。

sublist(int start, [int end])
sublist()

オプショナルパラメータ

オプショナルパラメータはポジショナル(名前なし)と名前付きの両方を指します。

ユーザがたびたび提供し、論理的な連続性がある値には、ポジショナルオプショナルパラメータを使用してください。ユーザは前のポジショナル引数が必要にならない限り、後のものが必要になるべきではありません。

String.fromCharCodes(Iterable source, [int start = 0, int end])
DateTime(year, month, day, [hours, minutes, seconds, milliseconds])

booleanのフラグにはすべて名前付きオプショナルパラメータを使用してください。各々が独立して適用される変数も同様です。

toList({growable: true})
Duration({days: 0, hours: 0, minutes: 0, seconds: 0, milliseconds: 0, microseconds: 0})

APIの選択

このセクションの狙いは、種類の異なるAPIを選択する助けとなることです。現在のところ、ここではgetterとメソッドの選択のみを扱います。

getter vs メソッド

getterにするべきか引数のないメソッドにするべきか確信が持てない場合、以下の質問を考えてください:

  • そのgetter/メソッドは一度に複数回呼び出すと異なる結果をまねきますか?もしそうなら、メソッドにしてください。
  • そのgetter/メソッドには副作用がありますか?もしそうなら、メソッドにしてください。
  • そのgetter/メソッドのユーザは結果よりもgetter/メソッドが実行する計算について気をつけなければなりませんか?もしそうなら、メソッドにしてください。

それ以外の場合は、getterにしてください。

どちらを選んだとしても、固有ガイドライン内の規約にしたがって名付けてください。

従うべき先例

もしあなたのAPIがオブジェクトを変換したり、範囲を指定したり、コレクションを管理するならば、以下の先例に従ってください。

toX()とasX()

既存のAPIとの一貫性を保つため、コピーを作るときはtoX()を使用してください。そして、戻り値が元のオブジェクトから返され、オブジェクト全体を表している場合は、asX()を使用してください。戻り値が元の一部しか表していない場合は、asX()の代わりに通常のフィールドかgetterを使用してください。例としてはMapのkeyvalueプロパティがあります。

toX()asX()はどちらも動詞を持ちませんが、メソッドです。asX()メソッドはgetterにしたほうがいいのかもしれませんが、List.asMap()が従うべき先例となっています。

範囲

連続する範囲を長さで指定するとき、sublist()のような既存のAPIとの一貫性を保つため、start(値を含める)とend(値を含めない)を使用してください。

sublist(int start, [int end])
sublist(int from, [int to])

コレクション

コレクションクラスは普通elementを名前から省略します。コレクションの要素を操作していることは既にわかっているからです。また、コレクションは頻繁に使用されるため、動詞を省略し、一般的に普通より短くする傾向があります。

elementAt       // "at"だけでは短すぎる
first           // 最初の要素を取得する
firstWhere(...) // ...を満たす最初の要素を取得する
forEach(...)    // 各要素に対して...する
where(...)      // ...を満たすすべての要素を取得する