複雑なコードを説明するコメントの何が問題になっていますか?


236

多くの人が「コメントは「なぜ」ではなく「なぜ」を説明すべきだ」と主張しています。他の人は「コードは自己文書化されるべきである」と言い、コメントは少ないはずです。ロバートC.マーティンは、(コメントは言い換えると)しばしば「コメントは不適切に書かれたコードに対して謝罪する」と主張しています。

私の質問は次のとおりです。

複雑なアルゴリズムや、複雑で複雑なコードを説明的なコメントで説明することの何が問題になっていますか?

この方法では、他の開発者(自分を含む)がアルゴリズム全体を1行ずつ読んでその動作を理解する代わりに、簡単な英語で書いたわかりやすい説明コメントを読むことができます。

英語は、人間が簡単に理解できるように「設計」されています。ただし、Java、Ruby、またはPerlは、人間の可読性とコンピューターの可読性のバランスを取るように設計されているため、テキストの人間の可読性が損なわれます。人間は、同じ意味のコードを理解するよりもはるかに速く英語を理解できます(操作が簡単でない限り)。

だから、部分的に人間が読めるプログラミング言語で書かれた複雑なコードを書いた後、フレンドリーで理解しやすい英語でコードの操作を説明する記述的で簡潔なコメントを追加してみませんか?

「コードを理解するのは難しくない」、「機能を小さくする」、「わかりやすい名前を使用する」、「スパゲッティコードを書かない」と言う人もいます。

しかし、それだけでは十分ではないことはわかっています。これらは単なるガイドラインであり、重要かつ有用なものですが、一部のアルゴリズムが複雑であるという事実は変わりません。したがって、それらを行ごとに読むと理解するのは困難です。

複雑なアルゴリズムを一般的な操作についてのコメントの数行で説明するのは本当に悪いですか?複雑なコードをコメントで説明することの何が問題になっていますか?


14
複雑な場合は、リファクタリングして小さなピースにしてみてください。
ヴォーンヒルツ14

151
理論的には、理論と実践の間に違いはありません。実際にはあります。
スコットリードリー14

5
@mattnz:より直接的に、あなたがコメントを書くとき、あなたはこのコードが解決する問題に浸ります。次回訪問したとき、この問題の能力は低下します。
スティーブジェソップ14

26
関数またはメソッドが何をするかは、その名前から明らかです。その方法は、コードから明らかです。なぜこのように行われるのか、どのような暗黙の仮定が使用されたのか、アルゴリズムを理解するためにどの論文を読む必要があるのか​​、などはコメントにすべきです。
SKロジック14

11
以下の回答の多くは、あなたの質問を意図的に誤解していると感じています。コードにコメントを付けても問題はありません。説明的なコメントを書く必要があると感じた場合は、そうする必要があります。
トニーエニス14

回答:


408

素人の言葉で:

  • コメントに問題はありません自体に。間違っているのは、そのようなコメントを必要とするコードを書くこと、またはわかりやすい英語でわかりやすい説明をする限り、複雑なコードを書くことは問題ないと仮定することです。
  • コードを変更しても、コメントは自動的に更新されません。そのため、コメントがコードと同期していないことがよくあります。
  • コメントは、コードのテストを容易にするものではありません。
  • 謝罪は悪くありません。謝罪(簡単に理解できないコードを書く)が必要なことは悪いことです。
  • 複雑な問題を解決するために簡単なコードを書くことができるプログラマは、複雑なコードを書いてから、彼のコードが何をするかを説明する長いコメントを書くプログラマよりも優れています。

結論:

自分自身を説明するのは良いことですが、そうする必要はありません。


91
良いコメントがより短い時間で仕事をすることができる場合、雇用主のお金を費やしてコードを書き換えることをより自明であると正当化することはしばしば不可能です。忠実なプログラマーは毎回彼女/彼の判断を使わなければなりません。
aecolley 14

34
@aecolleyそもそも説明のつかないコードを書く方が良いです。
Tulainsコルドバ14

127
自明のコードでは、今日のHW&SWの問題を解決するのに十分な効率が得られない場合があります。そして、ビジネスロジックは悪名高い...ツイスティです。洗練されたソフトウェアソリューションを持つ問題のサブセットは、解決するのに経済的に有用な問題のセットよりも大幅に小さくなります。
スコットリードリー14

62
@rwong:逆に、ビジネスロジックでより多くのコメントを書くことがよくあります。これは、コードが規定の要件にどのように一致するかを正確に示すことが重要だからです。刑法」。それが単なるアルゴリズムである場合、プログラマーは、どうしても必要な場合、目的をゼロから理解することができます。ビジネスロジックの場合、弁護士とクライアントが同時に同じ部屋にいる必要があります。おそらく、私の「常識」は、平均的なアプリプログラマーのドメインとは異なる領域にあります;-)
スティーブジェソップ14

29
@ user61852そのコードを書いて最後の$ピリオドを費やしたあなたにとって自明なものは、5年後にそれを維持または編集しなければならないあなたにとって自明ではないかもしれないことを除いて、それを見る必要があるかもしれないあなたではない可能性のある人々。「自明」は、定義の曖昧な聖杯です。
シャドゥール14

110

コードが複雑になったり混乱したりするさまざまな理由があります。最も一般的な的な理由は、最高ではない、あらゆる種類のコメントを追加することによって、それはあまり混乱させるためのコードをリファクタリングすることによって対処されています。

ただし、適切に選択されたコメントが最良の選択である場合があります。

  • 実装だけでなく、複雑でわかりにくいアルゴリズムそのものである場合(数学ジャーナルに記載され、後にMbogoのアルゴリズムと呼ばれる種類の場合)、実装の最初にコメントを入力します。 「これはウィジェットをフロボニケートするためのMbogoのアルゴリズムです。元々ここで説明しました。[論文のURL]。この実装にはAlice and Carolによる改良が含まれています[別の論文のURL]。」それ以上詳細に説明しようとしないでください。誰かがより詳細な情報を必要とする場合、おそらく論文全体を読む必要があります。

  • 特殊な表記法で1行または2行として記述できるものを取り、それを命令コードの大きなグロブに展開した場合、関数の上のコメントにこれらの1行または2行の特殊表記を入れるのが良い方法です何読者伝えるはず行うことを。これは、「ただし、コメントがコードと同期しなくなった場合」引数の例外です。なぜなら、特殊な表記法はおそらくコードよりもバグを見つけるのがはるかに簡単だからです。(代わりに英語で仕様を書いた場合は逆です。)良い例はこちらです:https : //dxr.mozilla.org/mozilla-central/source/layout/style/nsCSSScanner.cpp#1057 ...

    /**
     * Scan a unicode-range token.  These match the regular expression
     *
     *     u\+[0-9a-f?]{1,6}(-[0-9a-f]{1,6})?
     *
     * However, some such tokens are "invalid".  There are three valid forms:
     *
     *     u+[0-9a-f]{x}              1 <= x <= 6
     *     u+[0-9a-f]{x}\?{y}         1 <= x+y <= 6
     *     u+[0-9a-f]{x}-[0-9a-f]{y}  1 <= x <= 6, 1 <= y <= 6
    
  • コードが全体的に単純であるが、過度に複雑、不要、または単に間違っているように見えるが、理由のためにそのようにする必要がある1つまたは2つのものが含まれている場合、疑わしいビットのすぐ上にコメントを入れます理由述べてください。以下に簡単な例を示します。ここで説明する必要があるのは、定数に特定の値がある理由だけです。

    /* s1*s2 <= SIZE_MAX if s1 < K and s2 < K, where K = sqrt(SIZE_MAX+1) */
    const size_t MUL_NO_OVERFLOW = ((size_t)1) << (sizeof(size_t) * 4);
    if ((nmemb >= MUL_NO_OVERFLOW || size >= MUL_NO_OVERFLOW) &&
        nmemb > 0 && SIZE_MAX / nmemb < size)
      abort();
    

25
それは怒りです、そう4すべきですCHAR_BIT / 2;-)
スティーブジェソップ14

@SteveJessop:実装を妨げるものはありますか? CHAR_BITS 16でsizeof(size_t)が2でますが、size_tの最大値は2 ^ 20 [12のパディングビットを含むsize_t]でしたか?
supercat 14

2
@supercat C99で明らかにそれを妨げるものは見当たりません。つまり、この例技術的に間違っています。たまたまOpenBSD(のわずかに変更されたバージョン)から取得されてreallocarrayおり、OpenBSDは一般に ABI。
zwol

3
@Zack:コードはPOSIXの仮定に基づいて設計されている場合は、コードは8以外の値を使用して仕事ができるような印象を与えるかもしれないCHAR_BITSを使用して
supercat

2
@Zack:正確な幅の符号なしの型が有用であるためには、それらのセマンティクスはのサイズに関係なく定義される必要がありintます。そのままでは、uint32_t x,y,z;の意味は(x-y) > zのサイズに依存しますint。さらに、堅牢なコードを作成するために設計された言語は、プログラマーが計算が型の範囲を超えると予想される型とサイレントにラップする型と、型の範囲を超える計算がトラップする型と計算型を区別できるようにする必要があります型の範囲を超えることは想定されていませんが、...
supercat

61

では、コメントで複雑なコードを説明することの何が問題になっていますか?

それは、正しいか間違っているかという問題ではなく、ウィキペディアの記事で定義されている「ベストプラクティス」の問題です。

ベストプラクティスは、他の手段で達成された結果よりも優れた結果を一貫して示し、ベンチマークとして使用される方法または手法です。

したがって、最初にコードを改善し、それが不可能な場合は英語を使用することをお勧めします。

法律ではありませんが、コメントを必要とするリファクタリングコードよりも、リファクタリングを必要とするコメント付きコードを見つける方がはるかに一般的です。ベストプラクティスはこれを反映しています。


42
「それはコメントを必要とリファクタリング、コードよりもリファクタリングを必要とコメントしたコードを見つけるためにはるかに一般的です」のために1
ブランドン

7
さて、どのように頻繁にすると、そのコメントは以下のとおりです。 //This code seriously needs a refactor
エリックReppen

2
もちろん、厳密な科学的研究によって裏付けられていない、いわゆるベストプラクティスは単なる意見です。
Blrfl

54

美しく、完璧に作成され、適切に構造化された読み取り可能なコードが機能しなくなる日が来るでしょう。または、十分に機能しません。または、機能せず、調整が必要な特別なケースが発生します。

その時点で、正しく動作するように物事を変更する何かをする必要があります。特にパフォーマンスの問題がある場合だけでなく、使用しているライブラリ、API、Webサービス、gem、またはオペレーティングシステムのいずれかが期待どおりに動作しないシナリオでも、そうでない提案を行うことができます。必ずしも優雅ではありませんが、直感に反するか自明ではありません。

そのアプローチを選んだ理由を説明するコメントがなければ、将来誰かが(そして誰かがあなたかもしれませんが)コードを見る可能性が非常に高いです。より読みやすくエレガントな修正は、修正のように見えないため、うっかり修正を取り消します

誰もが常に完璧なコードを書いていれば、不完全に見えるコードが現実世界からのトリッキーな介入を回避していることは明らかですが、それは物事の仕組みではありません。ほとんどのプログラマーはしばしば混乱したりややもつれたコードを書くので、これに遭遇したときそれを片付けるのは自然な傾向です。私が書いた古いコードを読むときはいつも、過去の自分は実際の馬鹿だと誓います。

だから私はコメントを悪いコードの謝罪とは思わないが、おそらくあなたが明白なことをしなかった理由の説明と思う。持っている// The standard approach doesn't work against the 64 bit version of the Frobosticate Libraryにより、将来の自分を含む将来の開発者がコードのその部分に注意を払い、そのライブラリに対してテストできるようになります。もちろん、ソース管理のコミットにもコメントを入れることができますが、人々は何かがうまくいかなかった後にのみそれらを見るでしょう。彼らは、コードを変更すると、コードのコメントを読みます。

理論的に完璧なコードを常に書くべきだと言う人は、実際の環境でプログラミングの経験が豊富な人とは限りません。特定のレベルで動作するコードを記述する必要がある場合もあれば、不完全なシステムと相互運用する必要がある場合もあります。だからといって、これをエレガントでうまく書かれた方法で実行できないわけではありませんが、非自明なソリューションには説明が必要です。

誰も読まないことを知っている趣味のプロジェクトのコードを書いているとき、私は紛らわしいと思う部分をまだコメントしています-たとえば、3Dジオメトリには、私が完全に家にいるわけではない数学が含まれます-私は戻ってくるときを知っているので6か月後には、この方法を完全に忘れてしまいます。それは悪いコードの謝罪ではなく、個人的な制限の承認です。コメントを外したままにしておくのは、将来自分のために仕事を増やすことだけです。私は今それを避けることができれば、将来の自己が不必要に何かを再学習しなければならないことを望みません。それはどのような価値がありますか?


5
@クリスチャンですか?最初の行は確かにその文を参照していますが、それを超えて、私が理解しているように、それは少し広範です。
グレナトロン14

9
「私が書いた古いコードを読むときはいつも、過去の自分は馬鹿だと誓います。」開発キャリアを始めてから4年が経ちましたが、これは6か月以上前のものを見ると必ず起こることです。
ケン14

6
多くの場合、最も有益で有用な履歴情報は、考慮されているが決定されていないものに関連しています。誰かが何かのためにアプローチXを選択し、他のアプローチYがより良いと思われる多くの場合があります。これらのケースのいくつかでは、YはXよりも「ほぼ」うまく動作しますが、克服できない問題があることがわかります。Yが原因でこれらの問題を回避された場合は、そのような知識はアプローチYを実装するために失敗した試みに自分の時間を無駄に他の人を防ぐことができます
supercat

4
日々作業中のコメントも頻繁に使用します-長期的にはそうではありませんが、TODOノートまたは短いセクションにドロップして、次に何をしようとしているかを思い出させると便利です朝のリマインダー。
グレナトロン14

1
@Lilienthalが、私は最後のパラがされていることを考えていない制限付きの個人的なプロジェクト-彼は言ったに「...私はまだ、私は混乱を見つけるの部分をコメント。」
ワイルドカード

29

コメントの必要性は、コードの抽象化レベルに反比例します。

たとえば、アセンブリ言語は、ほとんどの実用的な目的のために、コメントなしではわかりません。フィボナッチ数列の項を計算して出力する小さなプログラムからの抜粋を次に示します

main:   
; initializes the two numbers and the counter.  Note that this assumes
; that the counter and num1 and num2 areas are contiguous!
;
    mov ax,'00'                     ; initialize to all ASCII zeroes
    mov di,counter                  ; including the counter
    mov cx,digits+cntDigits/2       ; two bytes at a time
    cld                             ; initialize from low to high memory
    rep stosw                       ; write the data
    inc ax                          ; make sure ASCII zero is in al
    mov [num1 + digits - 1],al      ; last digit is one
    mov [num2 + digits - 1],al      ; 
    mov [counter + cntDigits - 1],al

    jmp .bottom         ; done with initialization, so begin

.top
    ; add num1 to num2
    mov di,num1+digits-1
    mov si,num2+digits-1
    mov cx,digits       ; 
    call    AddNumbers  ; num2 += num1
    mov bp,num2         ;
    call    PrintLine   ;
    dec dword [term]    ; decrement loop counter
    jz  .done           ;

    ; add num2 to num1
    mov di,num2+digits-1
    mov si,num1+digits-1
    mov cx,digits       ;
    call    AddNumbers  ; num1 += num2
.bottom
    mov bp,num1         ;
    call    PrintLine   ;
    dec dword [term]    ; decrement loop counter
    jnz .top            ;
.done
    call    CRLF        ; finish off with CRLF
    mov ax,4c00h        ; terminate
    int 21h             ;

コメントがあっても、理解するのは非常に複雑です。

現代の例:正規表現は多くの場合、非常に低い抽象概念です(小文字、数字0、1、2、改行など)。彼らはおそらくサンプルの形でコメントを必要としています(IIRCのボブ・マーティンはこれを認めています)。HTTP(S)とFTP URLに一致する(と思う)正規表現を次に示します。

^(((ht|f)tp(s?))\://)?(www.|[a-zA-Z].)[a-zA-Z0-9\-\.]+\.(com|edu|gov|m
+il|net|org|biz|info|name|museum|us|ca|uk)(\:[0-9]+)*(/($|[a-zA-Z0-9\.
+\,\;\?\'\\\+&amp;%\$#\=~_\-]+))*$

言語が抽象化階層を進むにつれて、プログラマーは誘発的な抽象化(変数名、関数名、クラス名、モジュール名、インターフェース、コールバックなど)を使用して組み込みのドキュメントを提供できます。これを利用することを怠り、それについて紙にコメントを書くのは怠け者であり、メンテナーに不名誉であり、無礼です。

私はと考えていますCで数値のレシピをほとんど逐語的な翻訳C ++での数値のレシピとして始まった私が推測する、数値レシピすべての変数で、(FORTAN中)aaabccc各バージョンによって維持、など。アルゴリズムは正しいかもしれませんが、言語が提供する抽象化を活用していませんでした。そして、彼らは私を追い払った。ドブス博士の記事のサンプル-高速フーリエ変換

void four1(double* data, unsigned long nn)
{
    unsigned long n, mmax, m, j, istep, i;
    double wtemp, wr, wpr, wpi, wi, theta;
    double tempr, tempi;

    // reverse-binary reindexing
    n = nn<<1;
    j=1;
    for (i=1; i<n; i+=2) {
        if (j>i) {
            swap(data[j-1], data[i-1]);
            swap(data[j], data[i]);
        }
        m = nn;
        while (m>=2 && j>m) {
            j -= m;
            m >>= 1;
        }
        j += m;
    };

    // here begins the Danielson-Lanczos section
    mmax=2;
    while (n>mmax) {
        istep = mmax<<1;
        theta = -(2*M_PI/mmax);
        wtemp = sin(0.5*theta);
        wpr = -2.0*wtemp*wtemp;
        wpi = sin(theta);
        wr = 1.0;
        wi = 0.0;
        for (m=1; m < mmax; m += 2) {
            for (i=m; i <= n; i += istep) {
                j=i+mmax;
                tempr = wr*data[j-1] - wi*data[j];
                tempi = wr * data[j] + wi*data[j-1];

                data[j-1] = data[i-1] - tempr;
                data[j] = data[i] - tempi;
                data[i-1] += tempr;
                data[i] += tempi;
            }
            wtemp=wr;
            wr += wr*wpr - wi*wpi;
            wi += wi*wpr + wtemp*wpi;
        }
        mmax=istep;
    }
}

抽象化に関する特別なケースとして、すべての言語には特定の一般的なタスク(Cでの動的リンクリストの削除)のイディオム/標準的なコードスニペットがあります。非公式には言語の一部であるため、プログラマはこれらのイディオムを学ぶ必要があります。

それで、重要なのは、避けられない低レベルの構築ブロックから構築された非慣用的なコードにはコメントが必要だということです。そして、これは起こるよりも少ないWAAAAYが必要です。


1
アセンブリ言語でこのような行を書く人はいませんdec dword [term] ; decrement loop counter。一方、アセンブリ言語の例にないのは、次のコードブロックが何をするかを説明する各「コードパラグラフ」の前のコメントです。その場合、コメントは通常、などの擬似コードの1行に相当し;clear the screen、その後に実際に画面をクリアするために必要な7行が続きます。
スコットホイットロック14

1
はい、アセンブリのサンプルには不必要なコメントがあると思いますが、公平に言うと、これは「良い」アセンブリスタイルのかなり代表的なものです。1行または2行の段落プロローグを使用しても、コードを追うのは非常に困難です。ASMのサンプルはFFTの例よりもよく理解できました。私は大学院でC ++でFFTをプログラミングしましたが、このようには見えませんでしたが、その後、STL、イテレーター、ファンクターをかなりの数のメソッド呼び出しで使用していました。モノリシック機能ほど高速ではありませんが、はるかに読みやすくなっています。NRinC ++サンプルと対比するために追加してみます。
クリスチャンH 14

という意味^(((ht|f)tps?)\:\/\/)?(www\.)*[a-zA-Z0-9\-\.]+\.(com|edu|gov|mil|net|org|biz|info|name|museum|us|ca|uk)(\:[0-9]+)*(\/($|[a-zA-Z0-9\.\,\;\?\'\\\+&%\$#\=~_\-]+))*$ですか?数値アドレスに注意してください。
izabera 14

多かれ少なかれ私のポイント:非常に低レベルの抽象化から構築されたものは、読みやすく、検証しにくいものです。コメント(そして、あまりにも軌道に乗らないようにするため、TESTS)は有用であり、不利益ではありません。同時に、利用可能な高レベルの抽象化(:alpha::num:が利用可能な場合)を使用しないと、高レベルの抽象化を使用するよりも、良いコメントであっても理解しにくくなります。
クリスチャンH 14

3
+1:"The need for comments is inversely proportional to the abstraction level of the code." すぐそこにすべてをまとめます。
ジェラート14

21

コード内のコメントに何か問題があるとは思わない。私の意見ではコメントがなんとなく悪いという考えは、プログラマーがやりすぎだからです。この業界には、特に極端な見方をめぐって、多くの大騒ぎがあります。途中のどこかで、コメントされたコードは悪いコードと同等になり、その理由はわかりません。

コメントには問題があります-コメントが参照するコードを更新するとき、それらを更新し続ける必要があります。wikiまたは何かは、コードに関する詳細なドキュメントを作成するためのより適切なリソースです。あなたのコードはコメントを必要とせずに読めるはずです。バージョン管理または改訂ノートは、行ったコードの変更を説明する場所である必要があります。

ただし、上記のいずれもコメントの使用を無効にしません。私たちは理想的な世界に住んでいないので、上記のいずれかが何らかの理由で失敗した場合、私はむしろ後退するコメントがあります。


18

あなたは彼が言っていることを少し読みすぎていると思います。苦情には2つの異なる部分があります。

(1)複雑なアルゴリズム、または(2)記述的コメント付きの長く複雑なコードの説明の何が問題になっていますか?

(1)は避けられません。マーティンがあなたに反対するとは思わない。高速逆平方根のようなものを書いている場合は、たとえ「悪の浮動小数点ビットレベルハッキング」であっても、コメントが必要になります。DFSやバイナリ検索のような単純なものを除けば、コードを読んでいる人がそのアルゴリズムを経験する可能性は低いので、少なくともそれが何であるかについてのコメントに言及すべきだと思います。

ただし、ほとんどのコードは(1)ではありません。手巻きのミューテックスの実装、ライブラリサポートが不十分な線形代数演算、および会社の研究グループのみが知っている新しいアルゴリズムに他ならないソフトウェアを作成することはほとんどありません。ほとんどのコードは、ライブラリ/フレームワーク/ API呼び出し、IO、ボイラープレート、およびユニットテストで構成されています。

これは、Martinが話している種類のコードです。そして、彼はあなたの質問に章の上部にあるカーニハンと笑いからの引用で答えます:

悪いコードをコメントしないでください。書き直してください。

コードに長く複雑なセクションがある場合、コードをクリーンに保つことに失敗しています。この問題の最善の解決策は、将来の開発者が混乱するのを助けるために、ファイルの先頭に段落長のコメントを書くことではありません。最善の解決策はそれを書き換えることです。

そして、これはまさにマーティンが言っていることです。

コメントの適切な使用は、コードで自分自身を表現できないことを補うことです... コメントは常に失敗です。私たちは彼らなしで自分を表現する方法を常に理解することはできないので、彼らを持たなければなりませんが、彼らの使用はお祝いの原因ではありません。

これはあなた(2)です。マーティンは、長く複雑なコードにはコメントが必要であることに同意しますが、そのコードの責任は、それを書いたプログラマーの肩にかかっています。彼は次のように主張しています:

コメントの少ない明確で表現力豊かなコードは、多くのコメントがある雑然とした複雑なコードよりもはるかに優れています。あなたが作った混乱を説明するコメントを書くのに時間を費やすのではなく、その混乱を掃除することに費やしてください。


3
私と一緒に働いていた開発者が単に「邪悪な浮動小数点ビットレベルのハッキング」を書いて高速平方根アルゴリズムを説明してくれたなら、彼らは私に話を聞いたでしょう。役に立つ場所への参照が含まれている限り、私は喜んでいます。
マイケルアンダーソン14

8
ある意味では賛成できません-何か悪いことがどのように機能するかを説明するコメントの方がずっと早いです。再び触れられない可能性のあるコード(私が推測するほとんどのコード)を考えると、コメントは大規模なリファクタリングよりも優れたビジネスソリューションであり、しばしばバグを導入します(依存するバグを殺す修正は依然としてバグです)。完全に理解可能なコードの完璧な世界は私たちには利用できません。
gbjbaanb 14

2
@trysis haha​​、はい、しかし、プログラマーが責任を持ち、ビジネスマンではない世界では、完璧を求める無駄な探求の中で常にリファクタリングされたコードベースを永遠に金メッキしているので、彼らは決して出荷しません。
gbjbaanb

4
@PatrickCollins私がウェブで読んだほぼすべてのことは、初めてそれを正しく行うことに関するものです。混乱の修正に関する記事を書きたがる人はほとんどいません!物理学者は、「完全な球体与えられた...」Comp.Scientistsは「グリーンフィールドの開発を与えられた...」と言うと言う
gbjbaanb

2
最善の解決策は、無限の時間を与えて書き換えることです。しかし、他の誰かのコードベース、典型的な企業の締め切り、現実を考えると、時々、最も良いことはコメントで、TODO:Refactorを追加して、そのリファクターを次のリリースに入れることです。その修正は昨日、今やる必要がありました。リファクタリングに関するこの理想的な話のすべてについてのことは、職場で物事が実際にどのように機能するかを説明していないということです。時々、より高い優先度があり、すぐに十分な期限があり、古い品質の悪いコードの修正を先取りします。それだけです。
hsanders 14

8

複雑なアルゴリズムや、複雑で複雑なコードを説明的なコメントで説明することの何が問題になっていますか?

そのようなものは何もありません。作業を文書化することは良い習慣です。

とはいえ、ここには誤った二分法があります。クリーンなコードの作成と文書化されたコードの作成-この2つは対立していません。

注目すべきは、「複雑なコードはコメントされている限り問題ありません」と考えるのではなく、複雑なコードを単純化して単純なコードに抽象化することです。

理想的には、コードはシンプル文書化されている必要があります。

この方法では、他の開発者(自分を含む)がアルゴリズム全体を1行ずつ読んでその動作を理解する代わりに、簡単な英語で書いたわかりやすい説明コメントを読むことができます。

本当です。これが、すべてのパブリックAPIアルゴリズムをドキュメントで説明する必要がある理由です。

だから、部分的に人間が読めるプログラミング言語で書かれた複雑なコードを書いた後、フレンドリーで理解しやすい英語でコードの操作を説明する記述的で簡潔なコメントを追加してみませんか?

理想的には、複雑なコードを記述した後、次のようにする必要があります(完全なリストではありません)。

  • それをドラフトと見なします(つまり、書き直す予定です)
  • アルゴリズムのエントリポイント/インターフェイス/役割などを形式化します(インターフェイスの分析と最適化、抽象化の形式化、前提条件、事後条件および副作用の文書化、エラー事例の文書化)。
  • テストを書く
  • クリーンアップとリファクタリング

これらの手順のいずれも実行するのは簡単ではありません(つまり、それぞれに数時間かかる場合があります)。また、それらを実行するための報酬はすぐに得られません。そのため、これらの手順は(ほとんど)常に妥協されます(開発者が角を切り、管理者が角を切り、締め切り、市場の制約/その他の現実世界の状況、経験不足など)。

[...]いくつかのアルゴリズムは複雑です。したがって、それらを行ごとに読むと理解するのは困難です。

APIの機能を理解するために実装を読むことに頼る必要はありません。これを行うと、(インターフェイスの代わりに)実装に基づいてクライアントコードを実装することになり、モジュールカップリングが既に地獄に投げ込まれていることを意味し、作成するコードの新しい行ごとに文書化されていない依存関係を導入する可能性がありますすでに技術的な負債を追加しています。

複雑なアルゴリズムを一般的な操作についてのコメントの数行で説明するのは本当に悪いですか?

いいえ-それは良いことです。ただし、数行のコメントを追加するだけでは十分ではありません。

複雑なコードをコメントで説明することの何が問題になっていますか?

回避できれば、複雑なコードを使用するべきではないという事実。

複雑なコードを避けるために、インターフェイスを形式化し、実装に費やすよりも8倍以上API設計に費やし(Stepanovは、実装に比べて少なくとも10倍を費やすことを提案しました)、次の知識を持つプロジェクトの開発に進みますアルゴリズムを書くだけでなく、プロジェクトを作成しています。

プロジェクトには、APIドキュメント、機能ドキュメント、コード/品質測定、プロジェクト管理などが含まれます。これらのプロセスはいずれも、1回限りの迅速な手順ではありません(すべて時間がかかり、事前の考慮と計画が必要であり、すべて定期的に戻って詳細を修正/完了する必要があります)。


3
「APIの機能を理解するために実装を読むことに頼る必要はありません。」時々、これはあなたが使用することにコミットしているアップストリームによってあなたに与えられます。「ベンダーが主張しているにも関わらず、simpleAPI()はこのハードウェアで適切に動作しないため、次のfollowingいHeath Robinsonコードが存在する」という形式のコメントが散らかった、特に不満なプロジェクトがありました。
pjc50

6

他の開発者(自分を含む)がアルゴリズム全体を1行ずつ読んでその動作を理解する代わりに、彼らは簡単な英語で書いたわかりやすい説明コメントを読むことができます。

これは「コメント」のわずかな乱用だと思います。プログラマがアルゴリズム全体なく何かを読みたい場合は、それが関数のドキュメントの目的です。OK、それで関数ドキュメントは実際にはソースのコメントに表示されるかもしれません(おそらくdocツールによる抽出の場合)が、構文的にはコンパイラに関する限りコメントですが、それらは別々の目的で別々のものと考える必要があります。「コメントが不足している」とは、必ずしも「文書が不足している」または「著作権表示が不足している」ことを意味するものではないと思います。

関数内のコメントは、コードと同様に誰かが読むためのものです。したがって、コード内に理解しにくい数行があり、それらを理解しやすくすることができない場合、読者はそれらの行のプレースホルダーとして使用するコメントが役立ちます。これは読者が一般的な要点を把握しようとしている間は非常に便利ですが、いくつかの問題があります。

  • コメントは必ずしも真実ではありませんが、コードはそれがすることを行います。そのため、読者はあなたの言葉を信じていますが、これは理想的ではありません。
  • 読者はまだコード自体を理解していないため、後で戻ってくるまで、コードを変更または再利用する資格はありません。どの場合、彼らはそれを読んで何をしていますか?

例外もありますが、ほとんどの読者はコード自体を理解する必要があります。コメントは、それを置き換えるのではなく、それを支援するために作成する必要があります。そのため、コメントは「なぜそれをしているのか」と言うべきだと一般にアドバイスされています。コードの次の数行の動機を知っている読者は、彼らが何をどのように行うかを見る可能性が高くなります。


5
コメントの便利な場所の1つ:科学的なコードでは、多くの変数を含む非常に複雑な計算を行うことができます。プログラマーの健全性のために、変数名を本当に短くすることは理にかなっているので、名前ではなく数学を見ることができます。しかし、それは読者にとって理解するのを本当に難しくします。そのため、何が起こっているのかを簡単に説明する(または、より良いのは、雑誌の記事などの方程式を参照する)ことは、本当に役立ちます。
naught101 14

1
@ naught101:はい。特に、あなたが言及している論文でもおそらく1文字の変数名を使用しているためです。通常、同じ名前を使用した場合、コードが実際に論文に従っていることを確認するのは簡単ですが、それはコードが自明であるという目標と矛盾しています(代わりに論文で説明されています)。この場合、各名前が定義されているコメントは、それが実際に何を意味するかを述べて、意味のある名前に置き換えます。
スティーブジェソップ14

1
コードで特定の何かを検索するとき(この特定のケースはどこで処理されますか?)、コードの段落を読んで理解したくありません。次の段落が何をしているのかを一行にまとめたコメントが必要です。この方法で、問題に関連するコードの部分をすばやく見つけて、興味のない詳細をスキップします。
フロリアンF 14

1
@FlorianF:従来の応答では、変数名と関数名はコードの大まかな内容を示す必要があるため、探しているものではないものをざっと目を通すことができます。これは常に成功するとは限らないことに同意しますが、検索やスキムリーディングを支援するためにすべてのコードをコメントする必要があると強く思うわけではありません。しかし、あなたは正しいです、それは誰かがあなたのコードを(ある種)読んでいて、合法的にそれを理解する必要がない場合です。
スティーブジェソップ14

2
@Snowman人々は変数名を使ってそれを行うことができます。変数listOfApplesにバナナのリストが含まれるコードを見てきました。誰かがリンゴのリストを処理するコードをコピーし、変数名を変更することなくバナナに適合させました。
フロリアンF 14

5

多くの場合、複雑なことをしなければなりません。将来の理解のためにそれらを文書化することは確かに正しいです。このドキュメントの適切な場所はコード内である場合があり、ドキュメントをコードに合わせて最新の状態に保つことができます。しかし、個別のドキュメントを検討する価値は間違いありません。これは、図、カラー写真などを含め、他の人に提示するのも簡単です。次に、コメントは次のとおりです。

// This code implements the algorithm described in requirements document 239.

または単に

void doPRD239Algorithm() { ...

確かに人々は、MatchStringKnuthMorrisPrattまたはencryptAESまたはという名前の関数に満足していますpartitionBSP。より曖昧な名前はコメントで説明する価値があります。また、書誌データと、アルゴリズムを実装した論文へのリンクを追加することもできます。

アルゴリズムが複雑で斬新で明白ではない場合、社内での回覧専用であっても、間違いなくドキュメントの価値があります。紛失が心配な場合は、ドキュメントをソース管理にチェックインしてください。

官僚的なほどアルゴリズム的ではない別のカテゴリのコードがあります。別のシステムのパラメーターを設定するか、他の誰かのバグと相互運用する必要があります。

/* Configure the beam controller and turn on the laser.
The sequence is timing-critical and this code must run with interrupts disabled.
Note that the constant 0xef45ab87 differs from the vendor documentation; the vendor
is wrong in this case.
Some of these operations write the same value multiple times. Do not attempt
to optimise this code by removing seemingly redundant operations.
*/

2
内部アルゴリズムに基づいて関数/メソッドに名前を付けることに反対します。ほとんどの場合、使用されるメソッドは内部の懸念であるはずです、使用されるメソッドを使用して関数の最上部を必ず記録しますが、それdoPRD239Algorithmを呼び出すことはありませんアルゴリズムを調べなくても機能については何もありません。理由MatchStringKnuthMorrisPrattencryptAES仕事は、彼らが何をするかの説明から始めて、方法論の説明をフォローすることです。
scragar

5

私はそれを読んだが、そこにどこ私は忘れているあなたのコードで表示されなければならないとコメントとして何が表示されるべきものとの間にシャープでクリアなラインが。

アルゴリズムではなく、意図コメントしてください。すなわち、あなたが何をするつもりであったかではなく、あなたが何をするつもりだったかをコメントしてください

例えば:

// The getter.
public <V> V get(final K key, Class<V> type) {
  // Has it run yet?
  Future<Object> f = multitons.get(key);
  if (f == null) {
    // No! Make the task that runs it.
    FutureTask<Object> ft = new FutureTask<Object>(
            new Callable() {

              public Object call() throws Exception {
                // Only do the create when called to do so.
                return key.create();
              }

            });
    // Only put if not there.
    f = multitons.putIfAbsent(key, ft);
    if (f == null) {
      // We replaced null so we successfully put. We were first!
      f = ft;
      // Initiate the task.
      ft.run();
    }
  }
  try {
    /**
     * If code gets here and hangs due to f.status = 0 (FutureTask.NEW)
     * then you are trying to get from your Multiton in your creator.
     *
     * Cannot check for that without unnecessarily complex code.
     *
     * Perhaps could use get with timeout.
     */
    // Cast here to force the right type.
    return (V) f.get();
  } catch (Exception ex) {
    // Hide exceptions without discarding them.
    throw Throwables.asRuntimeException(ex);
  }
}

ここでは、各ステップが実行どのような状態にする試みは、それが述べて、すべてのは、されているもので、存在しないはず行うこと。

PS:私が言及しているソースを見つけました- コーディングホラー:コードはあなたに方法を教え、コメントはあなたに理由を教えます


8
最初のコメント:まだ実行されていますか?まだ何が実行されていますか?他のコメントについても同じです。コードが何をするのか分からない人にとっては、これは役に立ちません。
gnasher729

1
@ gnasher729-ほとんどすべてのコメントは文脈から外れると役に立たなくなります-このコードは、説明しようとするのでなく、意図を示すコメントを追加するデモです。あなたには何の役にも立ちません。
-OldCurmudgeon

2
そのコードのメンテナーはコンテキストを持ちません。コードが何をするのかを理解することは特に難しいことではありませんが、コメントは役に立ちません。コメントを書く場合は、時間をかけて、書くときに集中してください。
gnasher729 14

BTW- まだ実行されているというコメントはを参照してFutureおり、にget()続いてチェックが実行されたnullかどうかを検出することを示しFutureます- プロセスではなく意図を正しく文書化します
OldCurmudgeon

1
@OldCurmudgeon:あなたの返事は私が考えていたものに十分に近いので、あなたのポイントの例としてこのコメントを追加するだけです。きれいなコードを説明するためにコメントは必要ありませんが、コーディングが一方向に行われた理由を説明するのにコメントは役立ちます。私の限られた経験では、コメントはしばしば、コードが取り組んでいるデータセットの特異性、またはコードが実施することを意図しているビジネスルールを説明するのに役立ちます。データに関する仮定が間違っていたためにバグが発生した場合、バグを修正するために追加されたコメントコードは良い例です。
ランドール・スチュワート

4

しかし、それだけでは十分ではないことは誰もが知っています。

本当に?いつから?

適切な名前の適切に設計されたコードは、ほとんどの場合十分です。コメントの使用に対する議論はよく知られ、文書化されています(参照するとおり)。

しかし、これらは(他の何かと同様に)ガイドラインです。まれなケース(私の経験では、2年に1回程度)では、パフォーマンスや結束の必要性のために、より読みやすい機能にリファクタリングすると事態が悪化する場合があります。 (およびなぜベストプラクティスに違反しているのか)。


7
私はそれが十分ではないことを知っています。
フロリアンF 14

2
いつから?どうやら、あなたはすでにその答えを知っています。「大部分の場合、適切に設計された適切な名前のコードで十分です。」そのため、少数のケースではおそらく十分ではありません。これは、質問者が求めていることとまったく同じです。
エルセディル14

3
2年に1回以上コメントを追加したかった他の人のコードを解読しようとしています。
オーガ詩33 33 14

@ OgrePsalm33-小さなメソッドがあり、適切な名前を使用していますか?コメントに関係なく、悪いコードは悪いです。
テラスティン14

2
@Telastyn残念ながら、大きなコードベースで作業する場合、「小さな」メソッドと「良い」名前は各開発者にとって主観的です(そのため、良いコメントです)。Flarbiganグラフィカル処理アルゴリズムコードを7年間書いている開発者は、彼や同様の開発者には完全に明確なことを書くことができますが、Perbianグリッドインフラストラクチャコードの開発に4年を費やした新人にとっては不可解です。その後、2週間後、フラビガンの専門家は終了します。
オーガ詩sal 33 14

2

コードの主な目的は、コンピューターに何かを行うように命令することです。そのため、コメントは実行できないため、良いコメントが良いコードの代わりになることはありません。

そうは言っても、ソース内のコメントは、他のプログラマー(自分を含む)にとってのドキュメンテーションの1つの形式です。コメントが各ステップでコードが行っていることよりも抽象的な問題に関するものである場合、あなたは平均よりもうまくやっています。その抽象化のレベルは、使用しているツールによって異なります。アセンブリ言語ルーチンに付随するコメントは、一般に、たとえばこのAPLよりも低いレベルの「抽象化」を持っていますA←0⋄A⊣{2⊤⍵:1+3×⍵⋄⍵÷2}⍣{⍺=A+←1}⎕。おそらく、それが解決しようとしている問題についてのコメントに値すると思います。


2

コードが簡単な場合、説明のコメントは必要ありません。コードが自明ではない場合、説明コメントも自明ではないでしょう。

さて、非自明な自然言語の問題は、私たちの多くがそれを読んだり書いたりするのがあまり得意ではないことです。あなたの書かれたコミュニケーションスキルは優れていると確信していますが、それでも書かれた言語をあまり理解していない人はあなたの言葉を誤解するかもしれません。

誤解されない自然言語を非常に熱心に書こうとすると、法的文書のようなものになります(そして、それらはコードよりも冗長で理解しにくいことがわかっています)。

コードはロジックの最も簡潔な記述である必要があり、コンパイラとプラットフォームには最終決定権があるため、コードの意味についてはあまり議論すべきではありません。

個人的には、コメントを書くべきではないとは言いません。コードにコメントが必要な理由と、それを修正する方法を検討する必要があります。これは、ここでの回答の共通のテーマのようです。


「人間は英語の一部をはるかに速く理解できるので、同じ意味のコードを理解できる(操作が簡単でない限り)」という文に同意しなかったときに私が考えていたもの常に曖昧さを減らし、より簡潔にします。
スティーブンバイエル14

0

まだ言及されていない点の1つは、言語が特定の構文を複数の目的に使用する場合に、コードの一部を正確にコメントすることが役立つ場合があるということです。たとえば、すべての変数がtype floatであると仮定すると、次を考慮します。

f1 = (float)(f2+f3); // Force result to be rounded to single precision
f4 = f1-f2;

floatto を明示的にキャストする効果はfloat、結果を強制的に単精度に丸めることです。したがって、コメントはコードが何をするのかを単に言っていると見なすことができます。一方、そのコードを次と比較します。

thing.someFloatProperty = (float)(f2*0.1); // Divide by ten

ここで、キャストの目的は、コンパイラが正確に計算する最も効率的な方法(f2 / 10)でスコーキングを防ぐことです[0.1fを乗算するよりも正確で、ほとんどのマシンでは10.0fで除算するよりも高速です]。

コメントがなければ、以前のコードをレビューしていた人は、コンパイラがスコーキングを防ぐために必要であり、必要ではないと誤って信じてキャストが追加されたと思うかもしれません。実際、キャストは、言語仕様に記述されているとおりに実行するという目的に役立ちます。結果をより高い精度で保持するよりも丸めが高価になるマシンでも、計算の結果を単精度に丸めます。キャストにfloatはさまざまな意味と目的があるため、特定のシナリオでどの意味を意図するかをコメントで指定すると、実際の意味が意図に沿っていることを明確にするのに役立ちます。


J.ランダムプログラマーが2番目の例を見て、元のプログラマーが 'f'を入力するのを忘れたのではなく、正当な理由で定数が0.1で記述されていることに気付くかどうかはわかりません。
デビッドK 14

特にデバッグ中は、正当な理由で何かが行われたと想定することはありません。
gnasher729 14

@DavidK:2番目のサンプルコードの目的は、最初のコードと比較することでした。2番目のコードでは、プログラマの意図はおそらく、できるsomeFloatProperty限り正確な表現を保持するf2/10ことです。したがって、2番目のキャストの主な目的は、単にコードをコンパイルすることです。ただし、最初の例では、オペランドが既にであるため、キャストは通常​​の目的(コンパイル時の型を別の型に変更する)には明らかに必要ありませんfloat。コメントは、キャスト二次的な目的(丸め)に必要であることを明確にするのに役立ちます。
supercat

(float)2番目の例では、キャストについてコメントする必要はないという考え方に同意します。問題はリテラル定数に関するもの0.1です。(テキストの次の段落で)「なぜ0.10.1fを掛けるよりも正確です」と書く理由を説明しました。私はそれらがコメントにあるべき単語であることを提案しています。
デビッドK 14

@DavidK:0.1fが容認できないほど不正確であることを知っていれば必ずコメントを含め、精度の損失が許容可能であり、実際に0.1fが0.1より実質的に速いことを知っていれば0.1fを使用します。これらのいずれかが当てはまらない場合、コーディング習慣はdouble、値が次のように表現できない可能性がある定数または中間計算に使用することをお勧めしますfloat[ただし、迷惑な明示的なdouble-to-floatキャスト、遅延float速度のためではなく、煩わしさを最小限に抑えるために定数を使用することをプッシュするかもしれません。
supercat

-1

コードの機能を説明するコメントは、複製の形式です。コードを変更してからコメントの更新を忘れると、混乱を招く可能性があります。私はそれらを使用しないで、ただ慎重に使用することを言っているのではありません。ボブおじさんの格言を購読します:「コードが言うことができないことだけをコメントしてください」。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.