web-dev-qa-db-ja.com

「コア」ライブラリが悪い考えになるのはいつですか?

ソフトウェアを開発するとき、私は多くの場合、さまざまなプロジェクトで共有および参照できる便利なコードを含む集中型「コア」ライブラリーを持っています。

例:

  • 文字列を操作する関数のセット
  • 一般的に使用される正規表現
  • 一般的な展開コード

しかし、私の同僚の一部はこのアプローチを遠ざけているようです。バグが修正されると、多くのプロジェクトで使用されるコードを再テストすることによるメンテナンスのオーバーヘッドなどの懸念があります。今、私はこれをいつ行うべきかを再考しています。

「コア」ライブラリを使用することが悪い考えとなる問題は何ですか?

8
Alex Angas

コアライブラリは、機能のクリープに悩まされ始めたときは悪く、十分にメンテナンスされていないと非常に悪くなります。

この記事は、拡張された視点(私はこれに全面的に同意します)にとって興味深いかもしれません。

http://www.yosefk.com/blog/redundancy-vs-dependencies-which-is-worse.html


Don Knuth:「私にとって、「再編集可能なコード」は、編集不可能なブラックボックスやツールキットよりもはるかに優れています...再利用可能なコードがほとんど脅威ではないと私が納得することは決してないでしょう。」

12

複数のプロジェクトがコアライブラリに依存している場合にコアライブラリが悪いという考えを使用することは、Web用のjQuery、* nixアプリ内のlibxml、またはその他のフレームワークやライブラリを使用するべきではないと言うようなものです。現代の開発のエコシステム全体(DRY、OOPなど)を見てください。すべてのアプリは、一連のライブラリとフレームワークに基づいて構築されています。

悪いのは、どのタイプの単体テストも、回帰テストも、ライブラリでどのタイプのAPI/ABIも使用していない場合です。すべてのアプリケーションに適切なテストがある場合、ライブラリには適切なテストがあり、関数呼び出しを中断した場合は、APIバージョン番号を適切に更新します。

完全にカバーするには、おそらくライブラリに変更が加えられたときに、APIが壊れていないこと、およびすべてのコードの実行にバグがないことを確認する一連のテストを実行できます。次に、最新のライブラリ更新をアプリケーションに取り込み、同じテストセットを実行できます。 APIを更新する場合は、それを文書化して、それを更新するためにアプリケーションで何をする必要があるかを理解してください。どちらの方法でも、アプリケーションのテストを実行すると、テスト中と同じように、何も壊れていないという自信を持つことができます。

Jquery、mootools、JavaScriptライブラリやフレームワークを使用する場合、新しいバージョンを盲目的に使用するだけでなく、悲しいことに1.6.zのマイナーリリースでも使用できないことがあります。

3
Ryan Gibbons

バグが修正されると、多くのプロジェクトで使用されるコードを再テストすることによるメンテナンスのオーバーヘッドなどの懸念があります。

コアライブラリの包括的な単体テストセットがある場合。それは問題ではありません。すべてのテストに合格しない限り、コードはチェックインされません。欠陥を導入する場合は、失敗したテストを作成して欠陥を再現し、修正します。その後、常にそのエラーについてもテストします。永遠に。

また、説明する機能は単体テストを書くのが非常に簡単です。

副次的な問題として、複数のコアライブラリが必要になる場合があるため、必要でない限り、RegExコードを含める必要はありません。

3
mcottle

これについては少し異なる見方をします。多くの場合、コアライブラリは優れたアイデアです。

2つの別々のプロジェクトがある場合、それらは2つの別々のコードリポジトリにある必要があります。今では、それらは共通の機能に依存しています。例として、パケット処理アプリケーションを考えてみましょう。共通の機能には次のものがあります。

  • メモリアロケータ
  • アドレス解決プロトコル
  • AVLツリー
  • バイナリプロトコルのシリアル化コード
  • 動的配列
  • 単一にリンクされたヘッドと二重にリンクされた中間ノードを持つLinuxカーネルスタイルのハッシュリスト
  • ハッシュ表
  • TCP/IPヘッダー処理コード
  • 二重にリンクされたヘッドと二重にリンクされた中間ノードを持つ通常のリンクリスト
  • ロギングライブラリ
  • その他(私を信じてください、あなたは小さくて些細なもののためにこれを必要とします、さもなければあなたの異なるモジュールの数は100と同じくらい素晴らしいでしょう!)
  • パケットキャプチャライブラリ
  • パケットI/Oインターフェイスライブラリ
  • パケットデータ構造
  • スレッド間通信のブロッキングキュー
  • 乱数ジェネレータ
  • 赤黒木
  • ある種のタイマー実装

現在、異なるパケット処理アプリケーションでは、これらの異なるサブセットが必要になる場合があります。 1つのソースコードリポジトリで1つのコアライブラリを実装する必要がありますか、それともこれらのモジュールごとに18の異なるリポジトリが必要ですか?これらのモジュールには相互依存関係がある可能性があるため、これらのモジュールのほとんどは、たとえばその他のモジュール。

コアライブラリを1つ持つことが最善のアプローチであると私は主張します。多くのソースコードリポジトリのオーバーヘッドを削減します。それは依存関係の地獄を減らします:メモリアロケータの特定のバージョンは、雑多なモジュールの特定のバージョンを必要とするかもしれません。その他2.5に依存するメモリアロケータバージョン1.7と、その他2.6に依存するAVLツリーバージョン1.2が必要な場合はどうでしょうか。その他の2.5とその他の2.6を同時にプログラムにリンクできない場合があります。

したがって、先に進み、次の構造を実装します。

  • コアライブラリリポジトリ
  • プロジェクト#1リポジトリ
  • プロジェクト#2リポジトリ
  • ...
  • プロジェクト#Nリポジトリ

構造からこの種の構造への切り替えを確認しました。

  • プロジェクト#1リポジトリ
  • プロジェクト#2リポジトリ
  • ...
  • プロジェクト#Nリポジトリ

非コピーペーストメカニズムによるメンテナンスの削減とコード共有の増加につながりました。

次の構造を使用したプロジェクトも見ました。

  • メモリアロケータリポジトリ
  • アドレス解決プロトコルリポジトリ
  • AVLツリーリポジトリ
  • バイナリプロトコルリポジトリのシリアル化コード
  • 動的配列リポジトリ
  • 単一にリンクされたヘッドと二重にリンクされた中間ノードリポジトリを持つLinuxカーネルスタイルのハッシュリスト
  • ハッシュテーブルリポジトリ
  • TCP/IPヘッダー処理コードリポジトリ
  • 二重にリンクされたヘッドと二重にリンクされた中間ノードリポジトリを持つ通常のリンクリスト
  • ロギングライブラリリポジトリ
  • その他のリポジトリ(信頼してください。小さくて些細なものにはこれが必要です。そうしないと、さまざまなモジュールの数が100にもなります!)
  • パケットキャプチャライブラリリポジトリ
  • パケットI/Oインターフェイスライブラリリポジトリ
  • パケットデータ構造リポジトリ
  • スレッド間通信リポジトリのブロッキングキュー
  • 乱数ジェネレーターのリポジトリ
  • 赤黒木リポジトリ
  • ある種のタイマー実装リポジトリ
  • プロジェクト#1リポジトリ
  • プロジェクト#2リポジトリ
  • ...
  • プロジェクト#Nリポジトリ

...そして依存関係の地獄とリポジトリ数の急増は本当の問題です。

今、あなたはあなた自身のものを書く代わりに既存のオープンソースライブラリを使うべきですか?あなたは考慮する必要があります:

  • ライセンスの問題。 20のライブラリには通常20人の異なる著者がいるため、提供されるドキュメントで著者にクレジットを与えるという単なる要件が多すぎる場合があります。
  • 異なるオペレーティングシステムバージョンのサポート
  • 特定のライブラリの依存関係
  • 特定のライブラリのサイズ:提供された機能には大きすぎませんか?提供する機能が多すぎませんか?
  • 静的リンクは可能ですか?動的リンクは望ましいですか?
  • ライブラリのインターフェースはあなたが望むものですか?場合によっては、希望するインターフェースを提供するラッパーを作成する方が、コンポーネント全体を自分で再作成するよりも簡単な場合があります。
  • ...そして私がこのリストで言及していない他の多くのこと

私は通常、プログラマーの専門知識を超えて何かを必要としない1000行未満のコードはすべて自分で実装するという規則を使用しています。注:1000行には単体テストが含まれています。したがって、単体テストで追加の10 000行が必要な場合は、自分で1000行のコードを書くことを推奨しません。私のパケット処理プログラムでは、これは私が使用した唯一の外部コンポーネントが以下であることを意味します:

  • 標準のLinuxディストリビューションによって提供されるものすべて。コード行が多すぎるため、Linuxを再実装しても意味がありません。 Linuxの再実装の一部も、私の専門知識レベルを超えています。
  • LALR解析は私の専門知識レベルを超え、1000行を超えるコードであるため、Bison/flexです。自分で再帰的降下パーサーを作成することは確かにできますが、Bison/flexは非常に便利なので、便利だと思います。
  • 1000行を超えており、私の専門知識レベルを超えているため、Netmap
  • DPDKからのリストベースのタイマー実装をスキップします。これは、コードが1000行未満ですが、専門知識レベルを超えているためです(スキップリストを使用しない代替タイマー実装があります)。

シンプルであるために自分で実装したものには、次のようなものも含まれます。

  • MurMurHash
  • SipHash
  • メルセンヌツイスター

...これらのカスタム実装は重いインライン化を可能にし、パフォーマンスの向上につながるためです。

私は暗号化を行いません。自分で作成した場合は、何らかの暗号ライブラリをリストに追加します。自分で暗号アルゴリズムを作成すると、公式のアルゴリズムと互換性があることが徹底的なユニットテストで示されていても、キャッシュタイミング攻撃の影響を受けやすいためです。

2
juhist

私はこれが少し違うテイクでチップインしたかったのですが、Denis de Bernardy回答と、依存関係の最小化と冗長性の最小化に関するリンクされた記事(コードの再利用はバランスをとる行為であると私が考えるこの問題に関する私自身の考えを非常に反映しています)。

coreライブラリで私が抱えている最大の問題は次のとおりです。

いつ完成しますか?必要なすべてのことを行い、効果的に「完了」する安定点にいつ到達しますか?

そして、答えは「never」になる可能性が非常に高いと思います。特にこのライブラリがソフトウェアの開発中に十分に予測された目標を持たずに進化している場合は特に、この曖昧なアイデアをモデル化しているため、人々は常にそれに追加したくなるかもしれません。そして、ライブラリへの追加は、ライブラリへの既存の依存関係を壊さないので、世界で最悪のものではないかもしれませんが、そのような曖昧な目標を考えると、ライブラリはますます折衷的になり、醜くなり、興味のある誰かの異なる機能を提供する可能性がありますライブラリを使用しても、ニーズに適用できるライブラリのほんの一部しか見つからない場合があります。

コードベースの依存関係は、理想的には非常に安定したパッケージに向かって流れる必要があります。 coreパッケージは、コードベースの大部分に依存関係が流れている一方で、非常に不安定になる可能性があります。

それで、ライブラリをより均一なライブラリに分割する価値があると思います"人々が頻繁に必要とするかもしれないもののコアライブラリ"より均一な方向に成長できるようにチームメイトの間でより正確に何をすべきか、そしてより重要なことにすべきではないを調整し、十分にテストされ、何かがあるように感じない安定点に達する可能性がある比較的「完全」で安定している(変更されないなど)には、さらに追加する必要があります。

1
user204677

複数のプロジェクトがコアライブラリに依存している場合、コアライブラリは不良になる可能性があります。コアへの変更をテストする必要があるだけでなく、依存するすべてのプロジェクトを回帰テストする必要もあります。次に、すべての依存プロジェクトをリファクタリングする必要があるため、コアAPIが変更されることはありません。ライブラリを使用するプロジェクトが多いほど、トラップは深くなります。

もう1つの問題は、コアライブラリにすべて「共通」を投げ込み始め、それを膨らませ、小さな断片を取り込むのが難しくなる傾向です。たまたま、その多くのコアライブラリのいずれかに触れることが怖くなった場所を聞いたとき、QA回帰テストのオーバーヘッドが非常に大きかったと言います。

代わりに、コードスニペットリソースを作成して、プロジェクトチームが必要なコードを検索して取り込んで、メンテナンスやリグレッションの問題から切り離すことができますか?とにかく、私は家でそうしています。

1
Patrick Hughes

まだ言及されていない1つの点は、埋め込まれたマイクロコントローラーのROMで実行されているものが文字通り唯一であっても、コードはsomethingに依存することになるということです。コントローラーの製造元がコードが依存する動作を変更した場合、変更後に製造されたチップで動作するようにコードを変更する必要があります。そうでない場合、コードを使用するデバイスの製造元は、なんらかの理由でチップを取得する必要があります。変更を組み込む-おそらく彼らに価格プレミアムを支払う。

ライブラリを使用してさまざまなハードウェア機能を実行すると、コードはライブラリに依存するようになりますが、以前はライブラリに依存していませんでしたが、コードとハードウェア間の依存関係も解消される可能性があります。たとえば、チップメーカーは、特定のI/O機能を常に特定の方法で実行する、現在および将来のすべてのチップのライブラリを提供することを約束する場合があります。それらのI/O関数を実行するためにそのライブラリを使用するコードは、そのライブラリの適切なバージョンを提供する製造元に依存するようになりますが、それらの機能の同じハードウェア実装を使用する製造元に依存しなくなります。

残念ながら、将来を保証するコードに対する正しいアプローチがどれであるかを知ることはしばしば困難です。変更されたチップにアクセスするために使用されていたとしても、チップベンダーがライブラリの動作を(新しいチップに対応するために)変更するケースを見てきました。チップメーカーがハードウェアの動作を変更した場合も見ましたが、提供されたライブラリは適切に調整されたため、ライブラリルーチンを使用するコードは変更なしで機能し続け、ハードウェアに直接アクセスするコードは調整する必要がありました。

Windowsアプリケーションでも同様の状況が存在します。 Microsoftは、アプリケーションが物事を行うために必要な方法を変更することを好む場合があります。このようなものに特定のライブラリを使用するコードは、ライブラリを更新するだけでアップグレードできますが、更新されたライブラリを使用しないコードは手動で更新する必要があります。

1
supercat

文字列やリンクされたリストのような基本的なもののためのライブラリを書くことは、この千年紀にはかなりばかげています。すでにコア機能を備えている、バッテリーに含まれるプログラミング言語を使用します。

コアランタイムサポートライブラリを単に楽しみながら書くのが好きなら、新しいプログラミング言語を設計してください。アプリケーションでそれを行うと、本質的には言語をその側から成長させていることになります。

その上、誰かがあなたが使っている言語ですでにN個の異なるコアライブラリを書いていないのではないですか?既存のフレームワークを調査し、最適なフレームワークを選択することは、ゼロから行うよりも時間を有効に活用できる場合があります。

0
Kaz