シンプルさの議論


8

最近、私の会社では、抽象化と単純化について少し議論しました。私が「DRYと抽象化は害を及ぼさない」と特徴付けた1つの考えの学校は、このようなコードにつながります:

def make_foo_binary(binaryName, objFiles, fooLibsToLinkAgainst)
    make_exe_task(binaryName, objFiles.ext('.o'), fooLibsToLinkAgainst)
end

この:

class String
    def escape_space
        return self.gsub(' ', '\ ')
    end
end

私の見解では、このような抽象化を作成すると、1つの場所でのみ使用され、コードが読みにくくなります。これは、読者が(gsub)に慣れている関数呼び出しを、見たことのない別の関数呼び出しに置き換えるためです。前(escape_space)。コードが実際にどのように機能するかを理解したい場合は、これを読む必要があります。置換は基本的に英語(「エスケープスペース」)で記述され、英語はあいまいなことで有名です。たとえば、定義を見ないと、すべての空白をエスケープするのか、スペース文字だけをエスケープするのかわかりません。

DRYと抽象化の賞賛を歌う多くの文章があります。 抽象化の限界を説明する情報源を知っている人はいますか?それは賞賛を歌って、コードをシンプルに保つことの実際的なことを話し合いますか?

編集:私は人生や(英語)ライティングでシンプルさを促進するテキストを見つけることができます。たとえば、Thoreauの「Simplify、Simplify!」またはStrunk and Whiteの「精力的な執筆は簡潔です。」プログラミングに相当するものはどこですか?


1
まだ読んでいない場合は、Robert C. Martinの「クリーンコード」を読むことをお勧めします。ある場合は、最初のいくつかの章を注意深く読んでください。
BrunoSchäpper、2012

Simple Made Easyを見て、単純なコードの記述に関する詳細なディスカッションを行うことをお勧めします。
dan_waterworth 2012

3
「クリーンコード」のファンではありません。過度に独断的。
リグ

回答:


9

確かに:Joel Spolsky(あなたは彼について聞いたことがあるかもしれません)は言います

重要な抽象化はすべて、ある程度、漏れやすいものです。

抽象化は失敗します。時には少し、時にはたくさん。漏れがあります。物事がうまくいかない。あなたが抽象化しているとき、それは至る所で起こります。

また、Jeff Atwoodが論じている KISSとYAGNI 参照してください。

開発者として、私たち自身のソリューションの一般性を評価するのも楽観的すぎる傾向があると思います。そのため、その複雑さのレベルを正当化できないかもしれないものについて、複雑な[ソリューション]を構築することになります。この衝動と戦うために、私はYAGNI(あなたはそれを必要としないだろう)の教義に従うことをお勧めします。必要に応じて必要なものを構築し、作業を進めながら積極的にリファクタリングします。壮大で未知の将来のシナリオの計画に多くの時間を費やさないでください。優れたソフトウェアは、最終的にはそれがどうなるかを進化させることができます。

(正確な引用についてはリンクを参照してください)

これらの引用の私の解釈:

  1. 優れた抽象化を作成するのは本当に難しい-安っぽい抽象化を作成するほうがずっと簡単だ。

  2. 必要がないか間違っている抽象化を追加すると、テスト、デバッグ、および保守が必要な追加のコードが作成されます。また、戻ってリファクタリングする必要がある場合は、より多くの自重があなたを引きずっています。


1
良い情報源。+1
KChaloux 2012

7

私はあなたの例の言語やランタイムに精通していgsubませんが、私には何の意味もありません。ただし、escape_spaceはるかに意味があります。おなじみの関数呼び出しを、これまでにない関数の呼び出しに置き換えてはならないというあなたの主張には完全に同意しません。私がこの議論を極端にするとしたら、プログラムに新しい関数を追加するべきではありません。それはいつも親しみのある関数呼び出しを抽象化し、常にこの新しい未知のユーザー定義の呼び出しで置き換えます。

開発者は、コードのしくみを理解するためにコードを読む必要はありません。最初に、彼または彼女はそれがどのように機能するかについて気にする必要はありませんが、それを使用する方法とそれがどのような効果を持っているかを理解できるはずです。そうでない場合は、関数の名前と署名、またはそのドキュメントに問題があります。

私にとって、抽象化の目標は、内部の詳細を削除し、いくつかの機能へのより明確なインターフェイスを提供することです。将来的には、escape_space関数の内部動作が変更またはオーバーライドされる可能性があります。一部の関数gsubが2つの引数で呼び出されてもかまいません。私は自分のスペースがエスケープされることを気にします。そのため、抽象化が有用になります。

私のお気に入りの言語であるC#では、すべての関数(プライベート関数も含む)に、関数、使用、使用単位、スローされた例外、入力コントラクト、型などを説明するドキュメントを常に追加しいます。次に、Visual StudioのIntelliSenseを使用すると、開発者はコードを読み取らなくても、関数の動作より明確に確認できます。IntelliSenseのようなツールがない場合、開発者は関数名をより具体的にするか、追加のドキュメントをどこかに簡単にアクセスできるようにする必要があります。

抽象化を制限する必要があるとは思いませんし、そのようなソースについては知りません。


2
彼が使用している言語はRubyであり、gsubはStringクラスの非常に一般的に使用される関数です。gsubが「検索してすべてを置換する」ものとして使用されていることをすぐに知らない、少しのRubyよりも多くの人を見つけるのは難しいでしょう。
KChaloux 2012

@KChalouxこれは非Rubyユーザーにも推測可能です:コンテキスト(渡された引数)に加えて、 'g' + "substitute"のように見えます-代替コマンド(たとえば、in sedとvim's :s)では、 'g'は「すべて」として使用
イズカタ2012

確かにそれは何かを置き換えたと思います。しかし、関数の機能を推測するのは好きではありません。これは、以前の時代からの成果物である必要がありますが、関数名は好きgsubatoiwcstombsずっと悪いより命名されていますescape_space
Daniel AA Pelsmaeker

2
@Virtlink一方、gsubは一般的なコマンドです。あなたが見るとき、あなたは"something".gsub(' ', '\ ')あなたが得ている出力を正確に知っています。スペースのエスケープは、私が頻繁に目にするものではありません。`\`文字、または他のエスケープ文字を使用していますか?改行をエスケープしますか?言うまでもなく、これはデフォルトのStringクラスのモンキーパッチであり、特定の開発者によって悪い習慣と見なされています。
KChaloux 2012

5

抽象化について話すとき、私は2つの用語を定義することが重要だと思います:本質的および付随的な複雑さ。固有の複雑さは、抽象化が解決している問題の複雑さであり、付随的な複雑さは、抽象化が隠している複雑さです。

優れた抽象化とは、付随する複雑さの多くを隠し、適切な問題の解決を試みて、固有の複雑さを軽減する抽象化です。ここでイライラしているのは、浅すぎる抽象化です。それらは付随的な複雑さをあまり隠さず、その抽象化を理解することは、その実装を理解することと同じくらい困難です。


2

私の答えは個人的な経験から離れて行くつもりですが、簡単な検索でこのブログが見つかりました。これは、抽象化が過度に進んだ場合についてかなり詳細に進んでいるようです(リンクされたエントリだけでなく、いくつかの関連するエントリがあります)。

基本的に、抽象化は良いことです。しかし、他のものと同じように、それに夢中になります。あなたの例は、抽象化が行き過ぎている良い例です。あなたは基本的に問題の関数の名前を変更するだけの「パススルー」を作成しています。

カスタム関数の場合、古い名前付けスキームを廃止して新しい名前付けスキームを優先する場合(escape_spaceをescapeSpaceまたはescWhitespaceなどに変更するなど)、これが必要になることがあります。しかし、ライブラリ関数については?やりすぎ。逆効果は言うまでもありません。その抽象化から何を得ましたか?それはそれはやっているすべてだ場合は、すべてあなたが得てきたその機能を覚えているし、他の(ライブラリ関数で、開発者はすでにどちらかそれらを認識している、または入力することができるはず、一方で誰かにその知識を渡すことの頭痛があるruby gsubにGoogleとその意味をご覧ください)。

何かを抽象化するタイミングを決定することについて、よりインテリジェントになることをお勧めします。たとえば、2〜3行以上で、少なくとも2回使用されているもの(わずかな違いがあっても、多くの場合、それらの違いはパラメーター化できます)は、一般的に優れた抽象化候補です。ときどき、読みやすさと「1つの仕事」の原則のために、大きな関数内で特定の処理を行う大きなコードブロックを検討し、それらを同じクラス内の保護された関数に抽象化します(多くの場合、それを使用する機能の近く)。

また、YAGNI(あなたはそれを必要としないでしょう)と時期尚早の最適化を忘れないでください。これは現在複数回使用されていますか?番号?そして、今それを抽象化することについて心配しないでください(そうすることで、読みやすさが大幅に向上する場合を除きます)。「しかし、後で必要になるかもしれません!」次に、実際にどこかで必要になったときにそれを抽象化します。それまでは、実際には何のメリットもないポインタへのポインタで終わるだけです。

このコインの反対側では、何かを抽象化するとき、必要が明らかになったらすぐに抽象化します。15に比べて2つまたは3つのコードブロックを移動して微調整する方が簡単です。一般的に、どこかからコードブロックをコピーしたり、見慣れたものを書いたりするとすぐに、それを抽象化する方法について考え始めます。


2
数年前、私はあなたに完全に同意し、あなたがそれを説明するように働きました。その間に私はロバート・マーティンのいくつかのことを読み、プログラミングの方法を再考し始めました。関数が一度使用された場合でも、適切な名前で非常に小さな関数を作成することには、1つの大きな利点があります。必要なコメントがはるかに少なくなります。また、その関数の呼び出し元をよりシンプルで読みやすくするのに役立ちます。それは明らかに「時期尚早の最適化」ではありません(Knuthはコードの品質ではなく、パフォーマンスについて話していました)。もちろんYAGNIを検討する必要がありますが、優れたリファクタリングツールを使用すると、このような変更を非常に簡単に行うことができます。
Doc Brown

1
@DocBrown-私たちはあなたが思うよりももっと同意するかもしれません。抽象化の長所と短所については、何時間も何日も話し合うことができるでしょう。私はそれよりも簡潔になるように努めていましたが、読みやすさと「1つの仕事」/単一責任の原則について述べたとき、私はあなたの非常に懸念に触れました。一般的に私が線を引くのは、関数が別の関数のパススルーとして機能しているときです(ほとんどの場合、これは抽象化のために抽象化です)。
Shauna 2012

2

最初に、ロバートC.マーティンの次の例を見てください。

http://blog.objectmentor.com/articles/2009/09/11/one-thing-extract-till-you-drop

この種のリファクタリングの理由は、結果として抽象化を構築して非常に小さな関数を作成し、すべての関数が1つのことだけを実行するようにすることです。これは、上記で示したような機能につながります。これは良いことだと思います。別の関数内で呼び出された各関数が同じ抽象化レベルにあるコードを作成するのに役立ちます。

結果として、のような名前を使用しmake_foo_binaryたりescape_space、コードを読みにくくしたりする場合はescape_space_chars_with_backslash、抽象化を放棄するのではなく、より適切な名前(例:)を選択してください。

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