2022/5/31/Dart/Effective Dart: Design

 

Effective Dart: Design

Here are some guidelines for writing consistent, usable APIs for libraries.

一貫性のある有用なAPI、ライブラリを書くためのガイドラインです。


Names

Naming is an important part of writing readable, maintainable code. The following best practices can help you achieve that goal.

読みやすく、保守性の高いコードを書くために命名規則は大切です。


DO use terms consistently.

Use the same name for the same thing, throughout your code. If a precedent already exists outside your API that users are likely to know, follow that precedent.

コード全体を通して、同じものには同じ名前を使いましょう。ユーザーが知っているような前例がAPIの外に既に存在する場合は、その前例に従います。

//good
pageCount         // A field.
updatePageCount() // Consistent with pageCount.
toSomething()     // Consistent with Iterable's toList().
asSomething()     // Consistent with List's asMap().
Point             // A familiar concept.

 

renumberPages()      // Confusingly different from pageCount.
convertToSomething() // Inconsistent with toX() precedent.
wrappedAsSomething() // Inconsistent with asX() precedent.
Cartesian            // Unfamiliar to most users.

 

The goal is to take advantage of what the user already knows. This includes their knowledge of the problem domain itself, the conventions of the core libraries, and other parts of your own API.

目標は、ユーザーがすでに知っていることを利用することです。これには、問題領域そのものに関する知識、コア・ライブラリの規約、そしてあなた自身のAPIの他の部分が含まれます。

 

By building on top of those, you reduce the amount of new knowledge they have to acquire before they can be productive.

それらの上に構築することにより、生産性を高める前に習得しなければならない新しい知識の量を減らすことができます。


AVOID abbreviations.

略語は使いすぎないようにしましょう。

Unless the abbreviation is more common than the unabbreviated term, don’t abbreviate. If you do abbreviate, capitalize it correctly.

略語が省略されていない用語よりも一般的な場合以外は略語を使わないでください。略す場合は、「正しく」大文字にします。

//good
pageCount
buildRectangles
IOStream
HttpRequest

 

//bad
numPages    // "Num" is an abbreviation of "number (of)".
buildRects
InputOutputStream
HypertextTransferProtocolRequest

 


PREFER putting the most descriptive noun last.

The last word should be the most descriptive of what the thing is. You can prefix it with other words, such as adjectives, to further describe the thing.

最後の単語はそれが何なのかを最もよく表すものであるべきです。形容詞など他の言葉を前につけて、さらにその物事を説明することができます。

//good
pageCount             // A count (of pages).
ConversionSink        // A sink for doing conversions.
ChunkedConversionSink // A ConversionSink that's chunked.
CssFontFaceRule       // A rule for font faces in CSS.

 

//bad
numPages                  // Not a collection of pages.
CanvasRenderingContext2D  // Not a "2D".
RuleFontFaceCss           // Not a CSS.

 


CONSIDER making the code read like a sentence.

文章のように読める感じのコードを書くことを考えてみましょう。

When in doubt about naming, write some code that uses your API, and try to read it like a sentence.

ネーミングに迷ったら、APIを使ったコードを書いて、文章のように読んでみてください。

//good
// "If errors is empty..."
if (errors.isEmpty) ...

// "Hey, subscription, cancel!"
subscription.cancel();

// "Get the monsters where the monster has claws."
monsters.where((monster) => monster.hasClaws);

 

//bad
// Telling errors to empty itself, or asking if it is?
if (errors.empty) ...

// Toggle what? To what?
subscription.toggle();

// Filter the monsters with claws *out* or include *only* those?
monsters.filter((monster) => monster.hasClaws);

It’s helpful to try out your API and see how it “reads” when used in code, but you can go too far.

APIを試してみて、コードで使用したときにどのように「読める」かを確認することは有用ですが、行き過ぎた行為になることもあります。

 

It’s not helpful to add articles and other parts of speech to force your names to literally read like a grammatically correct sentence.

文字通り文法的に正しい文章として名前を無理やり読ませるのは控えましょう。

//bad
if (theCollectionOfErrors.isEmpty) ...

monsters.producesANewSequenceWhereEach((monster) => monster.hasClaws);

 


PREFER a noun phrase for a non-boolean property or variable.

bool値以外のプロパティや変数には名詞のフレーズを使用しましょう。

The reader’s focus is on what the property is. If the user cares more about how a property is determined, then it should probably be a method with a verb phrase name.

読者の関心は、そのプロパティが何であるかにある。もしユーザーがプロパティの決定方法にもっと関心があるなら、それはおそらく動詞句名を持つメソッドであるべきでしょう。

//good
list.length
context.lineWidth
quest.rampagingSwampBeast

 

//bad
list.deleteItems

 


PREFER a non-imperative verb phrase for a boolean property or variable.

ブール型のプロパティまたは変数には、命令型ではない動詞句を優先します。

Boolean names are often used as conditions in control flow, so you want a name that reads well there. Compare:

bool型変数名は制御フローの中で条件として使われることが多いので、そこでうまく読まれる名前が欲しいところです。比較してみてください。

if (window.closeable) ...  // Adjective.
if (window.canClose) ...   // Verb.

Good names tend to start with one of a few kinds of verbs:

良い名前は、いくつかの種類の動詞の1つで始まる傾向があります。

  • a form of “to be”: isEnabled, wasShown, willFire. These are, by far, the most common.

isEnabled、wasShown、willFireなど、”to be “の形。これらは、非常に一般的です

助動詞を使う。hasElements, canClose, shouldConsume, mustSaveなど。

an active verb: ignoresInput, wroteFile. These are rare because they are usually ambiguous.

能動態動詞:ignoresInput、writeFile。これらは通常あいまいなため、使われるのはまれです。

 

loggedResultis a bad name because it could mean “whether or not a result was logged” or “the result that was logged”.

loggedResultは、「結果が記録されたかどうか」あるいは「記録された結果」のどちらの意味にもなるので悪い名前です(曖昧)。

 

Likewise, closingConnection could be “whether the connection is closing” or “the connection that is closing”.

同様に、closingConnectionは、「接続が閉じて(遮断)いるかどうか」または「閉じている(遮断している)接続」のどちらとも取れます(曖昧)。

 

Active verbs are allowed when the name can only be read as a predicate.

アクティブ動詞は、名前が述語としてしか読めない場合にのみ使いましょう。

上記の例ではlogged,closingは述語ではなく修飾語(形容詞)であり、bool型の変数名にそういう命名方法をすると分かりにくくなるので控えましょう、ということ。

What separates all these verb phrases from method names is that they are not imperative.

これらの動詞を用いたフレーズと、メソッド名の異なる点は、(これらの動詞を用いたフレーズは)命令的でない点です。

 

A boolean name should never sound like a command to tell the object to do something, because accessing a property doesn’t change the object.

プロパティにアクセスしてもオブジェクトは変化しないので、ブーリアン名は決してオブジェクトに何かを指示するコマンド(命令)のように聞こえるべきではありません。

 

(If the property does modify the object in a meaningful way, it should be a method.)

もしプロパティが意味のある方法でオブジェクトを修正するのであれば、それはメソッドであるべきです。

//good
isEmpty
hasElements
canClose
closesWindow
canShowPopup
hasShownPopup

 

//bad
empty         // Adjective or verb?
withElements  // Sounds like it might hold elements.
closeable     // Sounds like an interface.
              // "canClose" reads better as a sentence.
closingWindow // Returns a bool or a window?
showPopup     // Sounds like it shows the popup.

 


CONSIDER omitting the verb for a named boolean parameter.

名前付きブーリアン・パラメーターの動詞を省略することを検討しよう。

This refines the previous rule. For named parameters that are boolean, the name is often just as clear without the verb, and the code reads better at the call site.

これは、前の規則を洗練させたものです。名前付きパラメータがブーリアンである場合、動詞がなくても名前は同じように明確で、呼び出し先でコードが読みやすくなることがよくあります。

//good
Isolate.spawn(entryPoint, message, paused: false);
var copy = List.from(elements, growable: true);
var regExp = RegExp(pattern, caseSensitive: false);

 


PREFER the “positive” name for a boolean property or variable.

Most boolean names have conceptually “positive” and “negative” forms where the former feels like the fundamental concept and the latter is its negation—”open” and “closed”, “enabled” and “disabled”, etc.

ほとんどのbool型変数の名前は、概念的に「正」と「負」の形を持っており、前者は基本概念、後者はその否定と感じられる。「開く」と「閉じる」、「有効」と「無効」など。

 

Often the latter name literally has a prefix that negates the former: “visible” and “in-visible”, “connected” and “dis-connected”, “zero” and “non-zero”.

多くの場合、後者の名前には、前者を否定する接頭辞があります。「表示」と「非表示」、「接続」と「非接続」、「ゼロ」と「非ゼロ」です。

 

When choosing which of the two cases that true represents—and thus which case the property is named for—prefer the positive or more fundamental one.

“true”を表すのにどちらのケースを使うか(肯定的か否定的か)を選ぶ場合、プロパティをどちらのケース(肯定的か否定的か)で名付けるかを選ぶ場合、肯定的・基本的な方を選んでください。

 

Boolean members are often nested inside logical expressions, including negation operators.

ブール型のメンバーは、否定演算子を含め、論理式内にネストされることがよくあります。

 

If your property itself reads like a negation, it’s harder for the reader to mentally perform the double negation and understand what the code means.

プロパティ自体が否定のように読み取られる場合、読者が二重否定を精神的に実行し、コードの意味を理解するのは困難です。

//good
if (socket.isConnected && database.hasData) {
  socket.write(database.read());
}

 

//bad
if (!socket.isDisconnected && !database.isEmpty) {
  socket.write(database.read());
}

For some properties, there is no obvious positive form. Is a document that has been flushed to disk “saved” or “un-changed”? Is a document that hasn’t been flushed “un-saved” or “changed”? In ambiguous cases, lean towards the choice that is less likely to be negated by users or has the shorter name.

プロパティによっては、明らかな肯定形が存在しないものもある。ディスクにフラッシュされたドキュメントは “saved “なのか “un-changed “なのか?ディスクにフラッシュされていないドキュメントは “未保存 “または “変更済み”?曖昧な場合は、ユーザーから否定されにくい方、あるいは名前の短い方を選択するようにします。

 

Exception: With some properties, the negative form is what users overwhelmingly need to use. Choosing the positive case would force them to negate the property with ! everywhere. Instead, it may be better to use the negative case for that property.

例外 : 一部のプロパティでは、否定形を使用することが圧倒的に必要なものもある。それを正のケースで命名すると、あらゆる場所でそのプロパティを!で否定しなければならなくなります。その代わりに、そのプロパティには否定形を使用する方がよいかもしれません。


PREFER an imperative verb phrase for a function or method whose main purpose is a side effect.

副作用を主目的とする関数やメソッドには、命令形の動詞句を優先させる。

Callable members can return a result to the caller and perform other work or side effects. In an imperative language like Dart, members are often called mainly for their side effect: they may change an object’s internal state, produce some output, or talk to the outside world.

メンバーを呼び出すと「結果を返すこと」を行うし、さらにそれ以外の仕事を行ったり副作用(side effect)を発生させたりすることもある。

インペラティブ(命令的)な言語であるDartは、メンバーをサイドエフェクトのために呼び出すことも多い。それらはオブジェクトの内部的な状態を変更したり、何らかの出力を生み出したり、あるいは外界と交信したりする。

 

Those kinds of members should be named using an imperative verb phrase that clarifies the work the member performs.

これらの種類のメンバーには、メンバーが実行する作業を明確にする命令句を使用して名前を付ける必要があります。

//good
list.add('element');
queue.removeFirst();
window.refresh();

This way, an invocation reads like a command to do that work.

こうすればメソッド呼び出しがその仕事をするように命令しているように読めますね。


PREFER a noun phrase or non-imperative verb phrase for a function or method if returning a value is its primary purpose.

値を返すことが主な目的である場合は、関数またはメソッドの命名に名詞句または非命令動詞句を優先します。

Other callable members have few side effects but return a useful result to the caller.

いくつかのサイドエフェクトはあるが、意味のある結果を呼び出し元に返す役割を持つメンバもあるでしょう。

 

If the member needs no parameters to do that, it should generally be a getter.

そういうメンバーが引数を必要としないのであれば、一般的にそれはゲッターにすべきです。

 

But sometimes a logical “property” needs some parameters. For example,elementAt() returns a piece of data from a collection, but it needs a parameter to know which piece of data to return.

しかしたまに論理的な「プロパティ」が引数を必要とする場合があります。例えば、elementAt()メソッドはコレクションから一つの要素を返します。しかしどの要素を返すべきかを表す引数を必要とします。

 

This means the member is syntactically a method, but conceptually it is a property, and should be named as such using a phrase that describes what the member returns.

これは、メンバーが構文的にはメソッドであることを意味しますが、概念的にはプロパティであり、メンバーが返すものを説明するフレーズを使用してそのように名前を付ける必要があります。

//good
var element = list.elementAt(3);
var first = list.firstWhere(test);
var char = string.codeUnitAt(4);

This guideline is deliberately softer than the previous one. Sometimes a method has no side effects but is still simpler to name with a verb phrase like list.take() or string.split().

このガイドラインは、前のガイドラインほど厳格ではありません。時には副作用のないメソッドでも、 list.take() や string.split() のような動詞句を使った方がシンプルな命名になることがあります。


CONSIDER an imperative verb phrase for a function or method if you want to draw attention to the work it performs.

その関数やメソッドが行う仕事に注意をひきつけたい場合は、命名に

命令的な動詞を使うことを検討しましょう。

 

When a member produces a result without any side effects, it should usually be a getter or a method with a noun phrase name describing the result it returns.

メンバが副作用なしに結果を生成する場合、通常はゲッターかメソッドにし、その結果を記述する名詞句名を付けるべきです。

 

However, sometimes the work required to produce that result is important. It may be prone to runtime failures, or use heavyweight resources like networking or file I/O.

ただし、その結果を生成するために必要な作業が重要な場合があります。 実行時の障害が発生したり、ネットワーキングやファイルI/Oなどの大量のリソースを使用したりする可能性があります。

 

In cases like this, where you want the caller to think about the work the member is doing, give the member a verb phrase name that describes that work.

このような場合、つまりメンバーが行っている作業について呼び出し元に考えてもらいたい場合は、その作業を説明する動詞句の名前をメンバーに付けます。

//good
var table = database.downloadData();
var packageVersions = packageGraph.solveConstraints();

Note, though, that this guideline is softer than the previous two. The work an operation performs is often an implementation detail that isn’t relevant to the caller, and performance and robustness boundaries change over time. Most of the time, name your members based on what they do for the caller, not how they do it.

このガイドラインは前野二つよりは緩やかなものです。普通はメソッドの仕事は呼び出し元とは関係無い実装の詳細ですし、パフォーマンスや堅牢性の境界は時間と共に変化します。ほとんどの場合はメンバの命名は、そのメンバが「どう仕事をするか」ではなく「何の仕事をするか」を元にして行いましょう。


AVOID starting a method name with get.

getで始まるメソッド名は避けましょう。

In most cases, the method should be a getter with get removed from the name. For example, instead of a method named getBreakfastOrder(), define a getter named breakfastOrder.

ほとんどのケースでは「getから始まるメソッド名」ではなく、getを削除してゲッターとして定義しましょう。具体例としては、

getBreakfastOrder()という名のメソッドではなく、breakfastOrderという名のゲッターとして定義しましょう。

 

Even if the member does need to be a method because it takes arguments or otherwise isn’t a good fit for a getter, you should still avoid get. Like the previous guidelines state, either:

もし、引数が必要であったりそうでなくてもゲッターとして定義できないのでメソッドにする必要がある場合であっても、それでもgetは避けてください。前のガイドラインに書いてあるように、下記のどちらかにしましょう。

  • Simply drop get and use a noun phrase name like breakfastOrder() if the caller mostly cares about the value the method returns.

呼び出し元がメソッドの返り値について関心があるのなら、getを削除して名詞で命名しましょう。

  • Use a verb phrase name if the caller cares about the work being done, but pick a verb that more precisely describes the work than get, like create, download, fetch, calculate, request, aggregate, etc.

呼び出し元が実行中の作業を気にする場合は動詞句名を使用しますが、create, download, fetch, calculate, request, aggregate, など、”get”よりも作業をより正確に説明する動詞を選択します。


PREFER naming a method to___() if it copies the object’s state to a new object.

Linter rule: use_to_and_as_if_applicable

A conversion method is one that returns a new object containing a copy of almost all of the state of the receiver but usually in some different form or representation.

変換メソッドは、receiverの状態のほとんどすべてのコピーを含む新しいオブジェクトを返すものですが、通常は何らかの異なる形式または表現で返されます。

下記のサンプルを見るとわかる通り「異なる形式や表現」というのは基本的に「別の型」ということ。

The core libraries have a convention that these methods are named starting with to followed by the kind of result.

コアライブラリには、「toの後に結果の種類を表す文字列」という命名法の慣習があります。

 

If you define a conversion method, it’s helpful to follow that convention.

もしあなたが、変換メソッドを定義する場合、その命名法の慣習に従うと良いでしょう。

//good
list.toSet();
stackTrace.toString();
dateTime.toLocal();

receiverとはそのメンバメソッドが属しているオブジェクトのこと。list.toSet()の場合、toSet()メソッドのreceiverはlistのこと。

メソッド呼び出し(toSet())を命令・指示と捉えて、その命令を受けるもの(receiver)は、そのメソッドを有しているオブジェクト(list)である、という発想。


PREFER naming a method as___() if it returns a different representation backed by the original object.

メソッドが元のオブジェクトに裏打ちされた別の表現を返す場合は、 as__()と命名することを推奨します。

Linter rule: use_to_and_as_if_applicable

Conversion methods are “snapshots”. The resulting object has its own copy of the original object’s state.

変換メソッドは「スナップショット」です。結果として返されるオブジェクトは自分の「オリジナルのオブジェクトの状態のコピー(複製)」を持ちます。

一連の説明より、ここでの「コピー(複製)」は、「(参照ではない、)元のオブジェクトと値が同じではあるが、元のオブジェクトとは独立したもの」ということだと思われる。

 

There are other conversion-like methods that return views—they provide a new object, but that object refers back to the original.

viewを返すconversion-like methodもあります。それらは新しいオブジェクトを返しますが、そのオブジェクトはオリジナルを参照しています。

viewとは何なのか?という話だが、結局「オリジナルオブジェクトのメンバーを参照するメンバーを持つオブジェクト」ということになる。

 

Later changes to the original object are reflected in the view.

後でオリジナルのオブジェクトを変更したらそれがviewに反映されます。

 

The core library convention for you to follow is as___().

あなたが真似するべき、そういうメソッドの命名方法の慣習はas___()です。

//good
var map = table.asMap();
var list = bytes.asFloat32List();
var future = subscription.asFuture();

本来の命名規則の話とは別の論点だが、

いまいちここの説明がピンと来なかったので確認コードを書いてみた。

サンプルとしてasMap()が挙げられている。

void main(){
  List<int> list1 = [1,3,6,7];

  Map<int,int> map1 = list1.asMap();
  print(map1);//{0: 1, 1: 3, 2: 6, 3: 7}

  list1[0] = 100;

  print(map1);//{0: 100, 1: 3, 2: 6, 3: 7}
}

確かに説明の通りの挙動になった。

ということで、asMap()の返り値の要素はreciever( list1)の要素と紐づいている。

この挙動は認識している必要がある。

「list1の要素と同じ値の要素を持つが、list1とは独立したMapが欲しい」場合は、asMapの返り値をそのまま使ってはいけない、ということになる。

そういう場合は、下記のようにする必要がある。

こうする↓

void main(){
  List<int> list1 = [1,3,6,7];

  Map<int,int> map1 = {
    0 : list1[0],
    1 : list1[1],
    2 : list1[2],
    3 : list1[3],
  };

  print('1:$map1');//{0: 1, 1: 3, 2: 6, 3: 7}

  list1[0] = 100;

  print('2:$list1');//[100, 3, 6, 7]
  print('3:$map1');
}

あるいはこうする↓

void main(){
  List<int> list1 = [1,3,6,7];

  Map<int,int> map1 = {};

  int index = 0;
  for(final e in list1){
    map1.addAll({
      index:e,
    });
    index++;
  }

  print('(1):$map1');//(1):{0: 1, 1: 3, 2: 6, 3: 7}

  list1[0] = 100;

  print('(2):$list1');//(2):[100, 3, 6, 7]
  print('(3):$map1');//(3):{0: 1, 1: 3, 2: 6, 3: 7}
}

あるいはこうする↓

void main() {
  List<int> list1 = [1, 3, 6, 7];

  Map<int, int> map1 = {};

  list1.asMap().forEach((key, value) {
    map1.addAll({key: value,});
  });

  print('(1):  $map1');//(1):  {0: 1, 1: 3, 2: 6, 3: 7}

  list1[0] = 100;

  print('(2):  $list1');//(2):  [100, 3, 6, 7]
  print('(3):  $map1');//(3):  {0: 1, 1: 3, 2: 6, 3: 7}
}

新しいMapインスタンスを生成する必要がある、ということですね、はい。


AVOID describing the parameters in the function’s or method’s name.

関数名、メソッド名の中で引数名を使うのは避けましょう。

The user will see the argument at the call site, so it usually doesn’t help readability to also refer to it in the name itself.

下記のサンプルの通り、呼び出し箇所で引数を見るので、関数(メソッド)名の中でそれに言及しても可読性の向上にはつながらない(むしろ冗長になってしまう)

//good
list.add(element);
map.remove(key);

 

//bad
list.addElement(element)
map.removeKey(key)

 

However, it can be useful to mention a parameter to disambiguate it from other similarly-named methods that take different types:

しかし、異なる型を取る他の類似の名前のメソッドと区別するために、パラメータについて言及することは有用である場合があります。

//good

map.containsKey(key);
map.containsValue(value);

 


DO follow existing mnemonic conventions when naming type parameters.

型引数の命名には既存のmnemonicコンベンション(慣習)に従ってください。

Single letter names aren’t exactly illuminating, but almost all generic types use them.

一文字の名前は正確にわかりやすいわけではありませんが、ほとんどすべてのgenericタイプがそれらを使用しています。

 

Fortunately, they mostly use them in a consistent, mnemonic way. The conventions are:

幸いなことに、彼らは主に一貫したニーモニックな方法でそれらを使用します。 規則は次のとおりです。

 

E for the element type in a collection:

コレクションの要素の型をEと表す。

//good
class IterableBase<E> {}
class List<E> {}
class HashSet<E> {}
class RedBlackTree<E> {}

 

K and V for the key and value types in an associative collection:

//good
class Map<K, V> {}
class Multimap<K, V> {}
class MapEntry<K, V> {}

 

R for a type used as the return type of a function or a class’s methods. This isn’t common, but appears in typedefs sometimes and in classes that implement the visitor pattern:

関数やクラスのメソッドの戻り値の型として使用される型の R。これはあまり一般的ではないが、typedefやvisitorパターンを実装したクラスに時々登場する。

//good
abstract class ExpressionVisitor<R> {
  R visitBinary(BinaryExpression node);
  R visitLiteral(LiteralExpression node);
  R visitUnary(UnaryExpression node);
}

 

Otherwise, use T, S, and U for generics that have a single type parameter and where the surrounding type makes its meaning obvious.

それ以外の場合、単一の型パラメータを持ち、周囲の型によってその意味が明らかになるようなジェネリックには、T、S、Uを使用します。

 

There are multiple letters here to allow nesting without shadowing a surrounding name. For example:

ネストのため複数の文字(型引数)が必要だが、他の型引数に隠れるのを防ぐために複数の文字が用意されます。

//good
class Future<T> {
  Future<S> then<S>(FutureOr<S> onValue(T value)) => ...
}

Here, the generic method then<S>() uses S to avoid shadowing the T on Future<T>.

ここでgenericメソッドのthen<S>()は、Future<T>のT型と被らないようにSを使っています。

 

If none of the above cases are a good fit, then either another single-letter mnemonic name or a descriptive name is fine:

上記のケースに該当しない場合は、別の一文字の頭文字をとった名前か、あるいは説明的な型引数名をつけましょう。

//good
class Graph<N, E> {
  final List<N> nodes = [];
  final List<E> edges = [];
}

class Graph<Node, Edge> {
  final List<Node> nodes = [];
  final List<Edge> edges = [];
}

 

 


パート2へ>>

参考

https://dart.dev/guides/language/effective-dart/design

カテゴリーDart

コメントを残す

メールアドレスが公開されることはありません。