大規模な非OOコードベースはどのくらい管理されますか?


27

抽象化は、コードベースを管理するためにオブジェクト指向が提供する非常に便利な機能であるといつも思っています。しかし、大規模な非OOコードベースはどのように管理されていますか?または、それらは最終的に「泥の大玉」になりますか?

更新:
「抽象化」は単なるモジュール化またはデータ隠蔽であると誰もが考えているようです。しかし、私見、それはまた、依存関係の注入とテストのために必須である「抽象クラス」または「インターフェース」の使用を意味します。非OOコードベースはこれをどのように管理しますか?また、抽象化以外に、カプセル化は、データと関数の間の関係を定義および制限するため、大規模なコードベースの管理にも役立ちます。

Cでは、疑似OOコードを書くことが非常に可能です。他の非OO言語についてはあまり知りません。だから、それは大規模なCコードベースを管理する方法ですか?


6
言語に依存しない方法で、オブジェクトを記述してください。それは何ですか、どのように変更され、何を継承し、何を提供する必要がありますか?Linuxカーネルは、完全なヘルパーと関数ポインタの多くが付いて割り当てられた構造の、それはおそらく、ほとんどのオブジェクト指向の定義を満たしていないでしょう。それでも、それは非常によく維持されたコードベースの最良の例の1つです。どうして?なぜなら、すべてのサブシステムのメンテナーは、自分の責任範囲に何があるかを知っているからです。
ティムポスト

言語に依存しない方法で、管理されているコードベースの表示方法と、オブジェクト指向がこれとどう関係するかを説明してください。
デビッドソーンリー

@Tim Post Linuxカーネルのソースコード管理に興味があります。システムについて詳しく説明してください。おそらく例の答えとして?
グルシャン

7
昔は、単体テスト用のモックとスタブに個別のリンクを使用していました。依存性注入は、いくつかの手法のうちの1つにすぎません。条件付きコンパイルも別です。
マクニール

大規模なコードベース(OOまたはそれ以外)を「マネージド」と呼ぶのは一苦労です。あなたの質問の中心的な用語のより良い定義を持っていると良いでしょう。
-tottinge

回答:


43

あなたは、OOPが抽象化を達成する唯一の手段であると考えているようです。

OOPは確かにそれを行うのに非常に優れていますが、決して唯一の方法ではありません。妥協のないモジュール化(どちらも優れているPerlまたはPythonを見て、MLやHaskellのような関数型言語も行う)や、テンプレート(C ++)などのメカニズムを使用することで、大規模プロジェクトを管理しやすくすることもできます。


27
+1また、何をしているのかわからない場合は、OOPを使用して「泥の大玉」を書くこともできます。
ラリーコールマン

Cコードベースはどうですか?
グルシャン

6
@Gulshan:多くの大きなCコードベースはOOPです。Cがクラスを持っていないからといって、OOPを少しの努力で達成できないわけではありません。さらに、Cでは、ヘッダーとPIMPLイディオムを使用した優れたモジュール化が可能です。現代の言語のモジュールほど快適でもパワフルでもありませんが、もう一度十分です。
コンラッドルドルフ

9
Cでは、ファイルレベルでモジュール化できます。インターフェイスは.hファイルに、パブリックに使用可能な関数は.cファイルに、プライベート変数とプライベート関数にはstaticアクセス修飾子が付加されます。
デビッド

1
@Konrad:OOPがそれを行う唯一の方法ではないことに同意しますが、おそらくOPは厳密にCを念頭に置いていたと思います。これは関数型言語でも動的言語でもありません。ですから、PerlとHaskellに言及することは彼/彼女にとって何の役にも立たないと思います。私は実際にあなたのコメントがOPにもっと関連し、有用であると思います(OOPが少しの努力で達成できないことを意味しません); 追加の詳細を含む個別の回答として追加することを検討してください。コードスニペットまたはいくつかのリンクでサポートされる場合があります。それは少なくとも私の投票に勝ち、そしておそらくOPに勝つでしょう。:)
Groo

11

モジュール、(外部/内部)関数、サブルーチン...

Konradが言ったように、OOPは大きなコードベースを管理する唯一の方法ではありません。実際のところ、かなり前に(C ++ *の前に)かなり多くのソフトウェアが作成されていました。


*そして、はい、私はC ++がOOPをサポートする唯一のものではないことを知っていますが、どういうわけかそのアプローチは慣性を取り始めました。
ルーク


6

現実的には、まれな変更(社会保障退職計算など)および/または深く根ざした知識のいずれか、またはそのようなシステムを維持する人々がしばらくそうしているためです(シニカルテイクは仕事の安全です)。

より良いソリューションは、反復可能な検証です。つまり、自動テスト(単体テストなど)と、「クリックして壊れる箇所を確認する」のではなく、禁止された手順(回帰テストなど)に従う人体テストを意味します。

既存のコードベースで何らかの自動テストに移行するには、マイケルフェザーの「レガシーコード効果的に動作する」を読むことをお勧めします。これは、モジュール化など、他の人が答えたようなアイデアにつながりますが、本は物事を壊さずにそうするための正しいアプローチを説明しています。


マイケル・フェザーの本の+1。大きないコードベースに落ち込んでいると感じたら(再)読んでください:)
Matthieu

5

インターフェイスまたは抽象クラスに基づく依存性注入は、テストを行うための非常に優れた方法ですが、必ずしも必要ではありません。ほとんどすべての言語が関数ポインターまたはevalを持っていることを忘れないでください。これは、インターフェイスまたは抽象クラスでできることをすべて実行できます(問題は、多くの悪いことを含めて、より多くのことができるということです、 t自体がメタデータを提供します)。このようなプログラムは、これらのメカニズムを使用して実際に依存性注入を実現できます。

メタデータに厳密であることは非常に役立ちます。OO言語では、リフレクションAPIのようなものを持つために十分に標準化された方法で、コードのビット間の関係がクラス構造によって(ある程度)定義されます。手続き型言語では、それらを自分で発明すると役立つ場合があります。

また、手続き型言語では(オブジェクト指向言語と比較して)コード生成がはるかに役立つこともわかりました。これにより、メタデータがコードと同期し(生成に使用されるため)、アスペクト指向プログラミングのカットポイントのようなものが得られます。必要なときにコードを挿入できる場所です。私が理解できるような環境でDRYプログラミングを行う唯一の方法である場合もあります。


3

実際、最近発見したように、依存関係の反転に必要なのは1次関数だけです。

Cは一次関数をサポートし、クロージャーもある程度サポートします。また、Cマクロは、必要な注意を払って処理すれば、汎用プログラミングの強力な機能です。

それがすべてです。SGLIBは、Cを使用して高度に再利用可能なコードを記述する方法の非常に良い例です。そして、もっとたくさんあると思います。


2

抽象化されていなくても、ほとんどのプログラムはある種のセクションに分割されます。これらのセクションは通常、特定のタスクまたはアクティビティに関連しており、抽象化されたプログラムの最も特定の部分で作業するのと同じ方法で作業します。

小規模から中規模のプロジェクトでは、純粋なオブジェクト指向実装を使用すると、実際にこれを行うのが実際に簡単になる場合があります。


2

大規模なコードベースを制御する方法は、抽象化、抽象クラス、依存性注入、カプセル化、インターフェースなどだけではありません。これは、まさにオブジェクト指向の方法です。

主な秘密は、非OOPをコーディングするときにOOPを考えることを避けることです。

モジュール性は、非OO言語の鍵です。Cでは、David Thornleyがコメントで言及したように、これが実現されます。

インターフェイスは.hファイルに、公開されている関数は.cファイルに、プライベート変数とプライベート関数には静的アクセス修飾子が付加されます。


1

コードを管理する1つの方法は、MVC(model-view-controller)アーキテクチャのラインに沿って、コードを次のタイプのコードに分解することです。

  • 入力ハンドラ-このコードは、マウス、キーボード、ネットワークポートなどの入力デバイス、またはシステムイベントなどの高レベルの抽象化を処理します。
  • 出力ハンドラー-このコードは、モニター、ライト、ネットワークポートなどの外部デバイスを操作するためのデータの使用を扱います。
  • モデル-このコードは、永続データの構造の宣言、永続データの検証ルール、および永続データのディスク(または他の永続データデバイス)への保存を扱います。
  • ビュー-このコードは、Webブラウザー(HTML / CSS)、GUI、コマンドライン、通信プロトコルデータ形式(JSON、XML、ASN.1など)などのさまざまな表示方法の要件を満たすためのフォーマットデータを処理します。
  • アルゴリズム-このコードは、入力データセットを可能な限り高速に出力データセットに繰り返し変換します。
  • コントローラー-このコードは、入力ハンドラーを介して入力を受け取り、アルゴリズムを使用して入力を解析し、オプションで入力を永続データと結合するか、単に入力を変換するか、モデルを介して永続的に変換データを保存することにより、他のアルゴリズムでデータを変換しますソフトウェア、およびオプションで、表示ソフトウェアを介してデータを変換し、出力デバイスにレンダリングします。

このコード編成の方法は、オブジェクト指向言語またはオブジェクト指向以外の言語で記述されたソフトウェアに対して適切に機能します。これは、共通の設計パターンが各領域に共通することが多いためです。また、これらの種類のコード境界は、入力からモデル、そして出力にデータ形式をリンクするため、アルゴリズムを除いて最も疎結合であることがよくあります。

システムの進化は、多くの場合、ソフトウェアがより多くの種類の入力またはより多くの種類の出力を処理するという形をとりますが、モデルとビューは同じであり、コントローラーは非常によく似た動作をします。または、入力、モデル、アルゴリズムが同じで、コントローラーとビューが類似していても、システムは時間の経過とともにますます異なる種類の出力をサポートする必要がある場合があります。または、システムを拡張して、同じ入力セット、類似の出力、および類似のビューに新しいモデルとアルゴリズムを追加することもできます。

OOプログラミングがコード編成を困難にする1つの方法は、一部のクラスが永続的なデータ構造に深く結びついているものとそうでないものがあるためです。永続的なデータ構造がカスケード1:N関係やm:n関係などと密接に関連している場合、システムの重要で意味のある部分をコーディングするまで、クラスの境界を決定するのは非常に困難です。 。永続データのスキーマが変更されると、永続データ構造に関連付けられたクラスは進化しにくくなります。アルゴリズム、フォーマット、および解析を処理するクラスは、永続データ構造のスキーマの変更に対して脆弱ではありません。MVCの種類のコード編成を使用すると、最も厄介なコードの変更がモデルコードに分離されます。


0

組み込みの構造と組織機能を持たない言語で作業する場合(名前空間、パッケージ、アセンブリなどがない場合など)、またはこれらのサイズのコードベースを制御下に維持するのに不十分な場合、自然な対応は開発することですコードを整理するための独自の戦略。

この組織戦略には、さまざまなファイルを保存する場所、特定の種類の操作の前後に発生する必要があること、命名規則およびその他のコーディング標準に関連する標準、および多くの「これが設定される方法」が含まれます。 -それを台無しにしないでください!」コメントを入力-理由を説明している限り有効です!

戦略はプロジェクトの特定のニーズ(人、技術、環境など)に合わせて調整される可能性が高いため、大規模なコードベースを管理するための万能ソリューションを提供することは困難です。

したがって、最善のアドバイスは、プロジェクト固有の戦略を採用し、それを管理することを重要な優先事項とすることであると考えています。構造、その理由、変更のプロセスを文書化し、それが遵守されていることを確認するために監査し、そして重要なことは、変更が必要なときに変更することです。

私たちはクラスとメソッドのリファクタリングにほとんど精通していますが、そのような言語の大規模なコードベースでは、必要に応じてリファクタリングする必要があるのは組織化戦略そのものです(ドキュメントを完備)。

理由はリファクタリングの場合と同じです:システムの全体的な構成が混乱していると感じた場合、システムの小さな部分で作業するように精神的なブロックを開発し、最終的にはそれを悪化させることができますそれ)。

警告も同じです。リグレッションテストを使用し、リファクタリングがうまくいかなかった場合に簡単に元に戻せることを確認し、そもそもリファクタリングを容易にするように設計します(または、リファクタリングをしないだけです!)。

私はそれが直接コードをリファクタリングするよりもはるかにトリッキーであり、それを行う必要がある理由を理解していない可能性のあるマネージャー/クライアントから時間を検証/隠蔽することは困難ですが、これらはソフトウェア腐敗を最も起こしやすいプロジェクトのタイプでもあります柔軟性のないトップレベルの設計が原因です...


0

大規模なコードベースの管理について質問している場合、コードベースを比較的粗いレベルで適切に構造化する方法を求めています(ライブラリ/モジュール/サブシステムの構築/名前空間の使用/適切な場所に適切なドキュメントを配置する等。)。オブジェクト指向の原則、特に「抽象クラス」または「インターフェース」は、非常に詳細なレベルで内部的にコードをきれいに保つための原則です。したがって、大規模なコードベースを管理しやすくするための手法は、OOコードでも非OOコードでも変わりません。


0

処理方法は、使用する要素の境界を見つけることです。たとえば、C ++の次の要素には明確な境界線があり、境界線の外側の依存関係は慎重に検討する必要があります。

  1. フリー機能
  2. メンバー関数
  3. クラス
  4. 対象
  5. インタフェース
  6. 表現
  7. コンストラクター呼び出し/オブジェクトの作成
  8. 関数呼び出し
  9. テンプレートパラメータタイプ

これらの要素を組み合わせて境界線を再認識すれば、C ++内で必要なほぼすべてのプログラミングスタイルを作成できます。

関数の例としては、関数から他の関数​​を呼び出すのが悪いことを再認識することです。これは依存関係を引き起こすため、代わりに、元の関数のパラメーターのメンバー関数のみを呼び出す必要があります。


-1

最大の技術的課題は、名前空間の問題です。これを回避するには、部分リンクを使用できます。より良いアプローチは、コーディング標準を使用して設計することです。そうしないと、すべてのシンボルが混乱します。


-2

Emacsはこの良い例です。

Emacsアーキテクチャ

Emacsコンポーネント

Emacs Lispテストはskip-unlesslet-bind機能検出およびテストフィクスチャを使用して実行します。

場合によっては、前提条件が欠落しているためにテストを実行しても意味がありません。必要なEmacs機能がコンパイルされていない可能性があります。テスト対象の関数は、テストマシンでは使用できない外部バイナリを呼び出す可能性があります。この場合、マクロskip-unlessを使用してテストをスキップできます。

 (ert-deftest test-dbus ()
   "A test that checks D-BUS functionality."
   (skip-unless (featurep 'dbusbind))
   ...)

テストの実行結果は環境の現在の状態に依存するべきではなく、各テストはその環境をそれが見つかったのと同じ状態のままにしておくべきです。特に、テストはEmacsカスタマイズ変数やフックに依存してはいけません。 Emacsの状態またはEmacsの外部の状態(ファイルシステムなど)に変更を加える必要がある場合、成功したか失敗したかにかかわらず、戻る前にこれらの変更を元に戻す必要があります。

テストは環境に依存するべきではありません。そのような依存関係があると、テストが不安定になったり、特定の状況でのみ発生し、再現が困難な障害につながる可能性があります。もちろん、テスト対象のコードには、その動作に影響する設定がある場合があります。その場合、テストlet-bind期間中に特定の構成をセットアップするには、そのようなすべての設定変数をテストするのが最善です。また、テストではさまざまな構成を設定し、それぞれでテスト対象のコードを実行できます。

SQLiteも同様です。これがデザインです:

  1. sqlite3_open()→新規または既存のSQLiteデータベースへの接続を開きます。sqlite3のコンストラクター。

  2. sqlite3→データベース接続オブジェクト。sqlite3_open()によって作成され、sqlite3_close()によって破棄されます。

  3. sqlite3_stmt→準備されたステートメントオブジェクト。sqlite3_prepare()によって作成され、sqlite3_finalize()によって破棄されます。

  4. sqlite3_prepare()→SQLテキストをバイトコードにコンパイルし、データベースのクエリまたは更新の作業を行います。sqlite3_stmtのコンストラクター。

  5. sqlite3_bind()→アプリケーションデータを元のSQLのパラメーターに保存します。

  6. sqlite3_step()→sqlite3_stmtを次の結果行または完了まで進めます。

  7. sqlite3_column()→sqlite3_stmtの現在の結果行の列値。

  8. sqlite3_finalize()→sqlite3_stmtのデストラクタ。

  9. sqlite3_exec()→1つ以上のSQLステートメントの文字列に対してsqlite3_prepare()、sqlite3_step()、sqlite3_column()、およびsqlite3_finalize()を実行するラッパー関数。

  10. sqlite3_close()→sqlite3のデストラクタ。

sqlite3アーキテクチャ

Tokenizer、Parser、およびCode Generatorコンポーネントは、SQLステートメントを処理し、それらを仮想マシン言語またはバイトコードの実行可能プログラムに変換するために使用されます。大まかに言うと、これらの上位3層はsqlite3_prepare_v2()を実装しています。上位3層で生成されたバイトコードは準備されたステートメントです。仮想マシンモジュールは、SQLステートメントのバイトコードを実行します。Bツリーモジュールは、データベースファイルを、順序付けられたキーと対数パフォーマンスを備えた複数のキー/値ストアに編成します。Pagerモジュールは、データベースファイルのページをメモリにロードし、トランザクションを実装および制御し、クラッシュまたは停電後のデータベース破損を防止するジャーナルファイルを作成および維持します。OSインターフェースは、SQLiteを異なるオペレーティングシステムで実行するように適合させるためのルーチンの共通セットを提供する薄い抽象概念です。大まかに言うと、下の4つのレイヤーはsqlite3_step()を実装します。

sqlite3仮想テーブル

仮想テーブルは、開いているSQLiteデータベース接続に登録されているオブジェクトです。SQLステートメントの観点から見ると、仮想テーブルオブジェクトは他のテーブルまたはビューのように見えます。しかし、舞台裏では、仮想テーブルのクエリと更新は、データベースファイルの読み取りと書き込みの代わりに、仮想テーブルオブジェクトのコールバックメソッドを呼び出します。

仮想テーブルは、メモリ内のデータ構造を表す場合があります。または、SQLite形式ではないディスク上のデータのビューを表す場合があります。または、アプリケーションが仮想テーブルのコンテンツをオンデマンドで計算する場合があります。

以下に、仮想テーブルの既存および想定される使用例を示します。

全文検索インターフェース
Rツリーを使用した空間インデックス
SQLiteデータベースファイル(dbstat仮想テーブル)のディスクコンテンツをイントロスペクトします
コンマ区切り値(CSV)ファイルの内容を読み書きする
データベーステーブルであるかのように、ホストコンピューターのファイルシステムにアクセスする
Rなどの統計パッケージ内のデータのSQL操作を有効にする

SQLiteは、以下を含むさまざまなテスト手法を使用します。

独自に開発された3つのテストハーネス
展開された構成での100%ブランチテストカバレッジ
何百万ものテストケース
メモリ不足テスト
I / Oエラーテスト
クラッシュおよび電力損失テスト
ファズテスト
境界値テスト
無効化された最適化テスト
回帰テスト
不正なデータベーステスト
assert()およびランタイムチェックの広範な使用
バルグラインド分析
未定義の動作チェック
チェックリスト

参照資料

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