抽象化はコードの可読性を低下させる必要がありますか?


19

一緒に仕事をしている優秀な開発者が、私たちが継承したコードに機能を実装するのに苦労したことを最近教えてくれました。彼は、問題はコードを追跡するのが難しいということだと言いました。そのことから、私は製品をより深く見て、コードパスを確認することがどれほど難しいかを理解しました。

非常に多くのインターフェースと抽象レイヤーを使用していたため、物事の始まりと終わりを理解しようとするのは非常に困難でした。私は過去のプロジェクト(きれいなコードの原則に気付く前に)を見ていた時間について考えさせられ、主にコードナビゲーションツールが常にインターフェイスに到達するため、プロジェクトを回避することは非常に困難であることがわかりました。具体的な実装や、プラグインタイプのアーキテクチャで何かが接続されている場所を見つけるには、さらに多くの労力が必要です。

この理由から、一部の開発者は依存性注入コンテナを厳密に拒否しています。ソフトウェアのパスを非常に混乱させるため、コードナビゲーションの難易度は指数関数的に増加します。

私の質問は次のとおりです。フレームワークまたはパターンがこのように多くのオーバーヘッドを導入する場合、それは価値がありますか?実装パターンが不十分な場合の症状ですか?

開発者は、フラストレーションを乗り越えるために、抽象化がプロジェクトにもたらすものの全体像を調べる必要があると思います。しかし、通常、彼らにその全体像を見せることは困難です。IOCとDIのニーズをTDDで販売できなかったことを知っています。これらの開発者にとって、これらのツールを使用すると、コードの可読性が非常に低下します。

回答:


17

これは本当に@kevin clineの答えに対する長いコメントです。

言語自体は必ずしもこれを引き起こしたり妨げたりするわけではありませんが、とにかくある程度は言語(または少なくとも言語コミュニティ)に関連しているという彼の考えには何かがあると思います。特に、異なる言語で同じ問題に出くわすことはできますが、多くの場合、異なる言語ではかなり異なる形式を取ります。

たとえば、C ++でこれに遭遇すると、抽象化が多すぎる結果ではなく、賢さが多すぎる結果になる可能性があります。例えば、プログラマーは特別なイテレーターで起こっている(見つけられない)重要な変換を隠しているので、ある場所から別の場所にデータをコピーしているように見えるものには、実際には何の副作用もありませんデータのコピーを行います。物事を面白くするために、これは、あるタイプのオブジェクトを別のタイプにキャストする過程で一時オブジェクトを作成する副作用として作成される出力とインターリーブされます。

対照的に、Javaで実行すると、よく知られている「エンタープライズハローワールド」のバリアントが表示される可能性が高くなります。そして、インターフェイスXを実装し、DIフレームワークなどのファクトリクラスによって作成される具体的な派生クラス。実際の作業を行う10行のコードは、5000行のインフラストラクチャに埋もれています。

その一部は、少なくとも言語と同じくらい環境に依存します。X11やMS Windowsのようなウィンドウ環境で直接作業することは、些細な「ハローワールド」プログラムをほとんど解読できないごみの300行以上に変えることで有名です。時間の経過とともに、さまざまなツールキットも開発してきましたが、1)それらのツールキット自体は非常に重要であり、2)最終結果は依然として大きく、より複雑であるだけでなく、通常は柔軟性が低くなりますテキストモードに相当するものよりも(たとえば、テキストを印刷するだけでも、ファイルにリダイレクトすることはほとんど不可能です/サポートされています)。

元の質問(少なくともその一部)に答えるために:少なくともそれを見たとき、それは単純に、目の前のタスクに不適切なパターンを適用するよりも、パターンの実装の質が悪いという問題ではありませんでした-ほとんど避けられないほど巨大で複雑なプログラムに役立つパターンを適用しようとすることがよくありますが、小さな問題に適用すると、この場合サイズと複雑さは実際には回避できますが、最終的には巨大で複雑になります。


7

多くの場合、これはYAGNIアプローチを採用していないことが原因であることがわかりました。具体的な実装は1つしかなく、他の導入を計画している現在の計画はありませんが、インターフェイスを通過するものはすべて、必要ない複雑さを追加する典型的な例です。おそらく異端ですが、依存性注入の多くの使用についても同じように感じています。


単一の参照ポイントでYAGNIと抽象化について言及する場合は+1。抽象化を行う主な役割は、複数のものの共通点を除外することです。抽象化が1つのポイントからのみ参照されている場合、一般的なものをファクタリングすることについて話すことはできません。このような抽象化は、ヨーヨーの問題の一因となります。これはすべての種類の抽象化に当てはまるため、これを拡張します。関数、ジェネリック、マクロなど、何でも...
Calmarius 14

3

さて、どの部分が何をするのか分離できないため、十分な抽象化とコードの理解が困難です。

抽象化が多すぎると、抽象化は見えるがコード自体は見えないため、実際の実行スレッドをたどることが難しくなります。

優れた抽象化を実現するには、 KISSを使用する必要がありますこの種の問題を回避するために従うべきことを知るには、この質問に対する私の答えを参照してください

深い階層と名前の付け方を避けることは、あなたが説明するケースのために最も重要なポイントだと思います。抽象化に適切な名前が付けられていれば、何が起こるかを理解する必要がある抽象化レベルにだけ深く入り込む必要はありません。命名により、このレベルの抽象化がどこにあるかを識別できます。

問題は、すべてのプロセスを本当に理解する必要があるときに、低レベルのコードで発生します。次に、明確に分離されたモジュールによるカプセル化が唯一のヘルプです。


3
さて、どの部分が何をするのか分離できないため、十分な抽象化とコードの理解が困難です。それはカプセル化であり、抽象化ではありません。抽象化することなく、具体的なクラスのパーツを分離できます。
声明

使用している抽象化はクラスだけではありません。関数、モジュール/ライブラリ、サービスなどです。クラスでは通常、各機能をメソッド/関数の背後で抽象化します。
クライム

1
@Statement:データのカプセル化はもちろん抽象化です。
エドS.

ただし、名前空間の階層は非常に優れています。
JAB

2

私にとっては、カップリングの問題であり、設計の粒度に関連しています。結合の最も緩い形式でさえ、あるものから別のものへの依存関係を導入します。それが数百から数千のオブジェクトに対して行われた場合、それらがすべて比較的単純であっても、SRPに準拠し、すべての依存関係が安定した抽象化に向かって流れる場合でも、相互に関連する全体として推論するのが非常に困難なコードベースを生成します。

コードベースの複雑さを測定するのに役立つ実用的なものがありますが、理論上のSEではあまり議論されていません。たとえば、最後に到達する前にコールスタックをどれだけ深くすることができるか、非常に自信を持って、例外の場合を含め、コールスタックのそのレベルで発生する可能性のあるすべての副作用を理解します。

そして、私の経験では、より浅いコールスタックを備えたよりフラットなシステムは、推論するのが非常に簡単である傾向があることがわかりました。極端な例は、コンポーネントが単なる生データであるエンティティコンポーネントシステムです。システムにのみ機能があり、ECSを実装および使用する過程で、数十万行のコードにまたがる複雑なコードベースが基本的に数十のシステムに沸騰することを推論するのは、これまでで最も簡単なシステムであることがわかりましたすべての機能が含まれています。

機能を提供するものが多すぎる

以前のコードベースで働いていたときの代替は、数百から数千のほとんど小さなオブジェクトを持つシステムでしたが、いくつかのオブジェクトが1つのオブジェクトから別のMessageオブジェクト(たとえば、独自の公開インターフェース)。これは基本的に、コンポーネントに機能があり、エンティティ内のコンポーネントの一意の組み合わせがそれぞれ独自のオブジェクトタイプを生成するポイントにECSを戻すときに類推されます。そして、それは、小さなアイデアをモデル化するオブジェクトの無限の組み合わせ(Particleオブジェクトvs.Physics System、例えば)。ただし、相互依存関係の複雑なグラフが生成される傾向があり、コードベースには実際に何かを行うことができるために何か間違ったことをすることができるため、広範なレベルから何が起こるかを推論するのが難しくなります- -「データ」タイプではなく、関連する機能を持つ「オブジェクト」タイプのタイプ。機能が関連付けられていない純粋なデータとして機能する型は、独自に何もできないため、間違いを起こすことはありません。

純粋なインターフェースは、この理解の問題をそれほど助けません。それにより、「コンパイル時の依存関係」がより複雑にならず、変更や拡張のためのより大きな空間を提供しても、「実行時の依存関係」と相互作用がそれほど複雑にならないためです。クライアントオブジェクトは、それらが呼び出されている場合でも、具象アカウントオブジェクトの関数を呼び出すことになりますIAccount。ポリモーフィズムと抽象インターフェースには用途がありますが、特定の時点で発生する可能性のあるすべての副作用について推論するのに本当に役立つ方法で物事を分離することはありません。このタイプの効果的な分離を実現するには、機能を含むものがはるかに少ないコードベースが必要です。

より多くのデータ、より少ない機能

ECSアプローチは、たとえ完全に適用しなくても、非常に役立つものであることがわかりました。何百ものオブジェクトになっていたものを、粗く設計された粗いシステムですべての生データに変換するためです。機能。これにより、「データ」タイプの数が最大化され、「オブジェクト」タイプの数が最小化されるため、システム内で実際に問題が発生する可能性のある場所の数が絶対に最小化されます。最終結果は、依存関係の複雑なグラフを持たない非常に「フラットな」システムであり、コンポーネントからシステムへのシステムのみであり、その逆も決してなく、他のコンポーネントへのコンポーネントもありません。基本的に、生データがはるかに多く、抽象化がはるかに少ないため、コードベースの機能を主要な領域、主要な抽象化に集中化およびフラット化する効果があります。

30の単純なものは、複雑なものが独立している間にそれらの30の単純なものが相互に関連している場合、必ずしも1つのより複雑なものよりも推論するのが単純ではありません。したがって、私の提案は、実際には、オブジェクト間の相互作用から複雑さを移し、質量分離を達成するために他のものと相互作用する必要のないより大きなオブジェクトに、「システム」全体(モノリスや神オブジェクトではなく、 200個のメソッドを持つクラスではなく、a Messageまたはa よりもかなり高いレベルのParticle、最小限のインターフェイスを備えています)。そして、より単純な古いデータ型を支持します。それらに依存するほど、カップリングは少なくなります。それがいくつかのSEのアイデアと矛盾する場合でも、私はそれが本当に多くの助けになることがわかりました。


0

私の質問は、フレームワークまたはパターンがこのようなオーバーヘッドをもたらす場合、それだけの価値があるのでしょうか?実装パターンが不十分な場合の症状ですか?

たぶん、それは間違ったプログラミング言語を選択することの症状です。


1
これが選択した言語とどう関係するかわかりません。抽象化は、言語に依存しない高レベルの概念です。
エドS.

@Ed:一部の抽象化は、他の言語よりも一部の言語でより簡単に実現可能です。
ケビンクライン

はい。ただし、これらの言語で完全に保守可能で簡単に理解できる抽象化を書くことができないわけではありません。私のポイントは、あなたの答えが質問に答えたり、OPを助けたりしないことです。
エドS.

0

設計パターンの不十分な理解は、この問題の主な原因になる傾向があります。このヨーヨーに見られる最悪の事態の1つは、具体的なデータがほとんどない状態でインターフェイス間をバウンスすることで、OracleのGrid Controlの拡張機能でした。
正直なところ、誰かが私のJavaコード全体に抽象ファクトリメソッドとデコレータパターンオーガズムを持っているように見えました。そして、それはまるで空虚で孤独な気持ちにさせられました。


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