web-dev-qa-db-ja.com

クリーンなコードコメントとクラスドキュメント

コメントに関して、新しい同僚といくつか話し合っています。 Clean Codeはどちらも好きで、インラインコードコメントは避け、クラスとメソッドの名前は、それらが何をするかを表すために使用する必要があります。

ただし、私はクラスの目的と実際に何が表現されているかを説明しようとする小さなクラスの要約を追加することの大ファンで、主に 単一の責任の原則 パターン。また、メソッドに1行の要約を追加して、メソッドが何をするかを説明することにも慣れています。典型的な例は簡単な方法です

public Product GetById(int productId) {...}

次のメソッドの概要を追加しています

/// <summary>
/// Retrieves a product by its id, returns null if no product was found.
/// </summary

メソッドがnullを返すという事実は文書化する必要があると思います。メソッドを呼び出したい開発者は、メソッドがnullを返すか例外をスローするかどうかを確認するためにコードを開く必要はありません。時々それがインターフェースの一部であるので、開発者は実行中の基礎となるコードさえ知らないのですか?

しかし、私の同僚は、これらの種類のコメントは「 code smell 」であり、「コメントは常に失敗である」( Robert C. Martin )。

コメントを追加せずに、これらのタイプの知識を表現および伝達する方法はありますか?私はロバートC.マーティンの大ファンなので、少し混乱しています。要約はコメントと同じなので、常に失敗しますか?

これはインラインコメントに関する質問ではありません。

84
Bjorn

他の人が言ったように、APIドキュメント化コメントとインラインコメントには違いがあります。私の観点からの主な違いは、インラインコメントがcodeと一緒に読み取られるのに対し、ドキュメンテーションコメントはコメントは、コメントの対象です。

これを前提として、同じ [〜#〜] dry [〜#〜] 原理を適用できます。コメントは署名と同じことを言っていますか?あなたの例を見てみましょう:

IDで製品を取得します

この部分は、名前GetByIdと戻り値のタイプProductからすでに見たものを繰り返すだけです。また、「取得すること」と「取得すること」の違いは何か、その区別にコードとコメントの関係があるかという疑問も生じます。だから、それは不必要で少し混乱しています。どちらかといえば、コメントの実際に役立つ2番目の部分が邪魔になっています。

製品が見つからなかった場合はnullを返します。

ああ!これは、署名だけでは確かにわからないものであり、有用な情報を提供します。


これをさらに一歩進めます。コードのにおいがするときに人々がコメントについて話すとき、問題はコードがそのままコメントが必要かどうかではなく、コメントがコードを示しているかどうかです。コメントで情報を表現するために、よりよく書くことができます。それが「コードのにおい」が意味することです。「これをしないでください!」という意味ではなく、「これを実行している場合、問題があるという兆候である可能性があります」という意味です。

したがって、同僚がnullについてのこのコメントがコードの匂いだと言った場合は、単に「それでは、どうすればこれを表現できるでしょうか?」と尋ねるだけです。彼らが実行可能な答えを持っている場合、あなたは何かを学びました。そうでなければ、それはおそらく彼らの不満を死なせて殺すでしょう。


この特定のケースに関しては、一般的にnullの問題は難しい問題として知られています。コードベースにガード句が散らばっている理由、nullチェックがコードコントラクトの一般的な前提条件である理由、nullの存在が「10億ドルの間違い」と呼ばれている理由があります。実行可能なオプションはそれほど多くありません。 C#で見られる人気の1つはTry...規則です。

public bool TryGetById(int productId, out Product product);

他の言語では、型(OptionalMaybeなどと呼ばれることが多い)を使用して、そこにある場合とない場合がある結果を示すことは慣用的です。

public Optional<Product> GetById(int productId);

したがって、ある意味で、この反コメントのスタンスは私たちをどこかで私たちにもたらしました:私たちは、このコメントがにおいを表すかどうか、そしてどのような代替案が私たちに存在するかもしれないかについて考えました。

元のシグネチャよりも実際に実際に優先するかどうかは、まったく別の論争ですが、少なくとも、いつ発生するかについてコメントするのではなく、コードを通じて表現するオプションがあります製品が見つかりません。これらのオプションのどれがより良いのか、なぜそうであるのかについて同僚と話し合い、うまくいけば、コメントに関する包括的な独断的な発言を超えて前進するのを助けてください。

116
Ben Aaronson

ロバートC.マーティンの引用は文脈から外されています。これは、もう少しコンテキストのある引用です:

適切に配置されたコメントほど役立つものはありません。モジュールを雑然とした独断的なコメント以上に整理することはできません。嘘や誤報を広める古い粗末なコメントほど、それほど害を及ぼすものはありません。

コメントはシンドラーのリストとは異なります。それらは「純粋に良い」ものではありません。実際、コメントはせいぜい必要な悪です。私たちのプログラミング言語が十分に表現力がある場合、または私たちの意図を表現するためにそれらの言語を微妙に駆使する才能がある場合、コメントはあまり必要ありません-おそらくまったく必要ないでしょう。

コメントの適切な使用は、コードで自分自身を表現することに失敗したことを補うことです。 Wordの失敗を使用したことに注意してください。本気で言っているんだ。コメントは常に失敗です。彼らなしでは自分を表現する方法を常に理解できるとは限らないので、私たちはそれらを持っている必要がありますが、それらの使用はお祝いの原因ではありません。

したがって、コメントを書く必要がある位置にいるときは、それをじっくり考えて、表を変えてコードで自分を表現する方法がないかどうかを確認してください。コードで自分を表現するたびに、背中を軽くたたく必要があります。コメントを書くたびに、顔をしかめ、表現力の失敗を感じます。

(コピー ここから 、ただし元の引用は クリーンコード:アジャイルソフトウェアの職人のハンドブック )から

この引用がどのようにして「コメントは常に失敗」になるのかは、一部の人々が賢明な引用を文脈から外して愚かな教義に変える方法の良い例です。


APIドキュメント(javadocのような)は、ユーザーがソースコードを読み取らずにそれを使用できるようにAPIをドキュメント化することになっています。したがって、この場合、ドキュメントでメソッドの動作を説明する必要があります。これで、「IDで製品を取得する」はすでにメソッド名で示されているため冗長であると主張できますが、nullが返される可能性があるという情報は、文書化する上で非常に重要です。明白な方法。

コメントの必要性を避けたい場合は、APIをより明示的にすることにより、根本的な問題(有効な戻り値としてのnullの使用)を取り除く必要があります。たとえば、ある種の_Option<Product>_型を返すことができるため、型シグネチャ自体が、製品が見つからない場合に返されるものを明確に伝えます。

しかし、いずれにしても、メソッド名と型シグネチャだけでAPIを完全に文書化することは現実的ではありません。 doc-commentsを使用して、ユーザーが知っておくべき明白ではない追加情報を取得します。 BCLのDateTime.AddMonths()からのAPIドキュメントを例に挙げます。

AddMonthsメソッドは、うるう年と月の日数を考慮して、結果の月と年を計算し、結果のDateTimeオブジェクトの日の部分を調整します。結果の日がその月の有効な日でない場合、結果の月の最後の有効な日が使用されます。たとえば、3月31日+ 1か月= 4月30日。結果のDateTimeオブジェクトの時刻の部分は、このインスタンスと同じままです。

メソッド名とシグネチャだけを使用してこれを表現する方法はありません!もちろん、クラスのドキュメントはこの詳細レベルを必要としない場合があります。これは単なる例です。


インラインコメントも悪くありません。

悪いコメントは悪いです。たとえば、コードから簡単に見えるものだけを説明するコメントの例です。古典的な例は次のとおりです。

_// increment x by one
x++;
_

変数やメソッドの名前を変更したり、コードを再構築したりすることで明らかになる可能性があることを説明するコメントは、コードのにおいです。

_// data1 is the collection of tasks which failed during execution
var data1 = getData1();
_

これらはコメントの一種ですMartin Railsに対して。コメントは、明確なコードを記述できないことの兆候です-この場合、変数とメソッドにわかりやすい名前を使用します。コメント自体はもちろん問題ではなく、問題はコードを理解するためにコメントが必要なことです。

しかし、コメントは、コードから明らかではないすべてを説明するために使用する必要がありますwhyコードは特定の非自明な方法で書かれています:

_// need to reset foo before calling bar due to a bug in the foo component.
foo.reset()
foo.bar();
_

コードの過度に複雑な部分が何をするかを説明するコメントもにおいですが、修正はコメントを禁止することではなく、修正はコードの修正です!実際のWordでは、複雑なコードが発生します(できれば、リファクタリングまで一時的にのみ)が、通常の開発者が完全なクリーンなコードを初めて作成することはありません。複雑なコードが発生したときは、コメントを書くよりもコメントを書く方がはるかに優れています。このコメントにより、後でリファクタリングするのも簡単になります。

時にはコードはやむを得ず複雑になります。それは複雑なアルゴリズムかもしれませんし、パフォーマンス上の理由から明快さを犠牲にするコードかもしれません。ここでもコメントが必要です。

102
JacquesB

コードのコメントコードの文書化には違いがあります。

  • コメントは、後でコードを維持するために必要です。つまり、コード自体を変更します。

    コメントは確かに問題があると見なされる場合があります。極端な点は、それらがalwaysがコード内(コードが理解し難い)または言語内(言語が不可能)のいずれかに問題があることを示しているということです。たとえば、メソッドがnullを返さないという事実は、C#のコードコントラクトを介して表現できますが、たとえばPHPのコードを介してそれを表現する方法はありません。

  • Documentationは、開発したオブジェクト(クラス、インターフェース)を使用できるようにするために必要です。対象読者は異なります。ここで話しているのは、コードを保守して変更する人ではなく、使用する必要がほとんどない人です。それ。

    コードが十分に明確であるためにドキュメントを削除するのは非常識です。なぜなら、ドキュメントはここ何千行ものコードを読み込まなくてもクラスとインターフェイスを使用できるようにするためだからです。

36

ええと、あなたの同僚は本を読んで、彼らの言ったことを取り入れ、彼が学んだことを何も考えず、文脈を考慮せずに適用しているようです。

関数が何をするかについてのコメントは、実装コードを捨てることができるようなものでなければなりません。私は関数のコメントを読み、何も問題なく実装の代わりを書くことができます。

コメントで例外がスローされるかどうか、またはnilが返されるかどうかがわからない場合、それを行うことはできません。さらに、コメントがyouに例外がスローされるか、nilが返されるかを伝えない場合は、関数を呼び出す場所に必ず確認する必要があります。例外がスローされてもnilが返されても、コードは正しく機能します。

だからあなたの同僚は完全に間違っています。そして先に進んですべての本を読んでください。

PS。私はあなたの行を見ました。「時々それはインターフェースの一部であるので、どのコードが実行されているかさえわかりません。さらに悪いことに、抽象クラスを作成した場合、コードさえありません!したがって、抽象関数を持つ抽象クラスがある場合、抽象関数に追加するコメントはonlyであり、具象クラスの実装者がそれらをガイドする必要があります。これらのコメントは、たとえば、抽象クラスと具体的な実装を返すファクトリしかなくても、実装のソースコードがまったく表示されない場合など、クラスのユーザーをガイドできる唯一のものかもしれません。 (そしてもちろん、私はone実装のソースコードを見る必要はありません)。

13
gnasher729

考慮すべきコメントには2つのタイプがあります。コードを持つユーザーに表示されるコメントと、ドキュメントの生成に使用されるコメントです。

ボブおじさんが言及しているコメントの種類は、コードを知っている人だけが見ることができる種類です。彼が提唱しているのは [〜#〜] dry [〜#〜] の形式です。ソースコードを見ている人にとって、ソースコードは彼らが必要とするドキュメントであるべきです。人々がソースコードにアクセスできる場合でも、コメントは必ずしも悪いわけではありません。場合によっては、アルゴリズムが複雑であったり、非自明なアプローチをとっている理由を把握したりして、バグの修正や新機能の追加を試みても他の人がコードを壊さないようにする必要があります。

説明しているコメントはAPIドキュメントです。これらは、実装を使用しているユーザーには表示されますが、ソースコードにアクセスできない可能性があります。彼らがあなたのソースコードにアクセスできるとしても、彼らは他のモジュールで働いていて、あなたのソースコードを見ていません。これらの人々は、コードを書いているときに、このドキュメントをIDEで入手できると便利です。

12
Thomas Owens

コメントの価値は、コメントが提供する情報の価値から、それを読んだり無視したりするのに必要な労力を差し引いたもので測定されます。コメントを分析すると

/// <summary>
/// Retrieves a product by its id, returns null if no product was found.
/// </summary>

価値とコストについては、3つのことがわかります。

  1. Retrieves a product by its idは、関数の名前が言うことを繰り返しますので、値なしのコストです。削除する必要があります。

  2. returns null if no product was foundは非常に貴重な情報です。これにより、他のコーダーが関数の実装を確認する必要がある時間を削減できます。それ自体が導入する読書コストよりも多くの読書を節約できると私は確信しています。それはとどまるはずです。

  3. 台詞

    /// <summary>
    /// </summary>
    

    情報を一切持ちません。それらはコメントの読者にとって純粋なコストです。ドキュメンテーションジェネレーターで必要な場合は、正当化される可能性がありますが、その場合は、おそらく別のドキュメンテーションジェネレーターについて考える必要があります。

    これが、ドキュメントジェネレーターの使用が疑わしいアイデアである理由です。通常、洗練されたドキュメントのために、情報を含まないか、明白なものを繰り返す多くの追加コメントが必要です。


他のどの回答にも私が見つけていない観察:

コードを理解/使用するために必要なnotであるコメントでさえ、非常に価値があります。以下はそのような例の1つです。

//XXX: The obvious way to do this would have been ...
//     However, since we need this functionality primarily for ...
//     doing this the non-obvious way of ...
//     gives us the advantage of ...

おそらく多くのテキストで、コードを理解/使用するために完全に不要です。ただし、コードがそのように見える理由について説明します。それは、コードを見て、なぜそれが明白な方法で行われていないのか疑問に思う人々を止め、最初にこのようにコードが書かれた理由がわかるまで、コードのリファクタリングを開始します。また、リーダーが十分に賢く、直接リファクタリングにジャンプしない場合でも、コードが現在の状態に留まるのが最善であることに気付く前に、コードがなぜそのように見えるのかを理解する必要があります。このコメントにより、文字通り作業時間を節約できます。したがって、値はコストよりも高くなります。

同様に、コメントはコードの機能だけでなく、コードの意図を伝えることができます。また、通常はコード自体の細部で失われる全体像を描くことができます。そのため、クラスのコメントを支持するのは正しいことです。クラスのコメントは、クラスの意図、他のクラスとの相互作用、使用方法などを説明する場合に最も評価します。残念ながら、私はそのようなコメントの著者ではありません...

コメント化されていないコードは悪いコードです。コードが、たとえば英語とほとんど同じ方法で読み取れるというのは、(普遍的ではないとしても)広く普及している神話です。それは解釈されなければならず、時間と労力を必要とする最も些細なコードを除いて。さらに、言語によって、読み書きの能力が異なります。著者と読者のコーディングスタイルと機能の違いは、正確な解釈を妨げる大きな障害です。また、コードの実装から作成者の意図を導き出すことができるというのも神話です。私の経験では、余分なコメントを追加することはめったに間違っていません。

ロバートマーティン等。コードが変更され、コメントが変更されない可能性があるため、「悪臭」と見なしてください。それは良いことだと思います(家庭用ガスに「悪臭」が追加されてユーザーに漏れを警告するのとまったく同じように)。コメントを読むことは、実際のコードを解釈するための出発点として役立ちます。それらが一致すれば、コードに対する信頼が高まります。それらが異なる場合は、警告のにおいが検出されたため、さらに調査する必要があります。 「悪臭」の治療法は、臭いを取り除くことではなく、漏れを封じることです。

4

一部の言語(F#など)では、このコメント/ドキュメント全体を実際にメソッドシグネチャで表現できます。これは、F#では、特に許可されていない限り、nullは一般に許可された値ではないためです。

F#(および他のいくつかの関数型言語)に共通するのは、nullの代わりに、NoneまたはSome(T)のオプションタイプOption<T>を使用することです。言語はこれを理解し、使用しようとするときに両方のケースで一致することを強制します(または一致しない場合は警告します)。

したがって、たとえばF#では、次のような署名を付けることができます。

val GetProductById: int -> Option<Product>

そして、これは1つのパラメーター(int)を取り、製品または値Noneを返す関数になります。

そして、あなたはこのようにそれを使うことができます

let product = GetProduct 42
match product with
| None -> printfn "No product found!"
| Some p -> DoThingWithProduct p

また、両方のケースに一致しない場合、コンパイラの警告が表示されます。したがって、そこにnull参照例外を取得する方法はありません(もちろん、コンパイラの警告を無視しない限り)。また、関数のシグネチャを見れば、必要なものがすべてわかります。

もちろん、これはあなたの言語がこのように設計されていることを必要とします-C#、Java、C++などのような多くの一般的な言語はそうではありません。したがって、これは現在の状況では役に立たない可能性があります。しかし、(うまくいけば)コメントなどに頼らずに静的に型付けされた方法でこの種の情報を表現できる言語が世に出ていることを知ってうれしいです。:)

3
wasatz

ここにはいくつかの優れた答えがあり、彼らが言うことを繰り返したくありません。しかし、いくつかコメントを追加さ​​せてください。 (しゃれは意図されていません。)

ソフトウェア開発や他の多くの主題について、賢明な人々が多くの発言をしていると思います-コンテキストで理解されたとき非常に良いアドバイスですが、愚かな人々はコンテキストから外れたり、不適切な状況で適用したり、ばかげた極端。

コードは自己文書化すべきであるという考えは、そのような優れた考えの1つです。しかし、実際には、このアイデアの実用性には限界があります。

1つの問題は、言語が明確かつ簡潔に文書化される必要があるものを文書化する機能を提供しない可能性があることです。コンピュータ言語が進歩するにつれて、これはますます問題になります。しかし、完全になくなったとは思いません。私がアセンブラーで書いた当時、「合計価格=非課税品目の価格+課税品目の価格+課税品目の価格*税率」のようなコメントを含めることは非常に理にかなっています。ある時点でレジスターに何があったかは必ずしも明らかではありませんでした。簡単な操作を行うには、多くのステップが必要でした。しかし、もしあなたが現代の言語で書いているなら、そのようなコメントはただ一行のコードの言い直しになるでしょう。

「x = x + 7; // xに7を追加する」のようなコメントを見ると、いつもイライラします。プラス、プラス記号の意味を忘れてしまったら、とても助かったかもしれません。私が本当に混乱するかもしれないのは、「x」が何であるか、なぜこの特定の時間に7を追加する必要があったのかを知ることです。このコードは、「x」にもっと意味のある名前を付け、7の代わりに記号定数を使用することにより、自己文書化される場合があります。「total_price = total_price + MEMBERSHIP_FEE;」と書いた場合と同様に、おそらくコメントは必要ありません。 。

追加のコメントが冗長になるように、関数名は関数が何をするかを正確に示すべきだと言うのは素晴らしいことです。データベースのItemテーブルにアイテム番号が存在するかどうかをチェックし、trueまたはfalseを返すかどうかをチェックする関数を作成したときの思い出は、「ValidateItemNumber」と呼びました。まともな名前のように見えた。次に、他の誰かがやって来て、その機能を変更して、アイテムの注文も作成し、データベースを更新しましたが、名前は変更しませんでした。現在、その名前は非常に誤解を招くものでした。それは実際にはるかに多くのことをしたとき、それは一つの小さなことをしたように思えました。その後、誰かがアイテム番号の検証に関する部分を取り出して、別の場所でそれを行いましたが、それでも名前は変更されませんでした。そのため、関数はアイテム番号の検証とは何の関係もありませんが、それでもValidateItemNumberと呼ばれていました。

しかし、実際には、関数名がその関数を構成するコードである限り、関数名がなければ、関数名がその関数の機能を完全に説明することはしばしば不可能です。名前は、すべてのパラメーターに対して実行される検証を正確に教えてくれるでしょうか?例外条件ではどうなりますか?可能なすべてのあいまいさを詳しく説明しますか?ある時点で名前が長くなりすぎて混乱するだけです。適切な関数シグネチャとしてString BuildFullName(String firstname、String lastname)を受け入れます。名前が「最初から最後」、「最後、最初」、またはその他のバリエーションで表現されているかどうかはわかりませんが、名前の一部または両方が空白である場合、結合された長さに制限があり、それを超えるとどうなるか、そして他に何十もの詳細な質問をするかもしれません。

1
Jay