## 導入
この記事では、動的プログラミング言語と静的プログラミング言語の概念、この 2 つの主な違い、および利点と落とし穴の観点から各パラダイムが提供するものについて説明します。この探索では、動的プログラミング言語、特にそれが可能にする重要なパターンの 1 つである Monkey Patch にさらに焦点を当てます。このパターンは、JavaScript の例を使用して紹介されます。
動的プログラミング言語と静的プログラミング言語
用語
動的言語と静的言語の構成要素を理解するには、この文脈で一般的に使用されるいくつかの重要な用語、コンパイル時、実行時、**型チェック*について理解を確立する必要があります。 *。
コンパイルとランタイムは、コンパイル時から始まるコンピューター プログラムのライフサイクルのさまざまな段階に対応する 2 つの用語です。
コンパイル時間
コンパイル時はプログラムのライフサイクルの最初のステップです。開発者は、指定されたプログラミング言語でコードを作成します。多くの場合、マシンは高級言語で書かれたコードを理解できないため、専用のコンパイラを使用してコードを実行可能な低レベルの中間形式に変換します。
ランタイム
ランタイムは通常、2 つのステップをカプセル化します。1 つは命令とともに実行に必要なリソースを割り当ててプログラムをメモリにロードし、次にそれらの命令の順序に従ってプログラムを実行するというものです。
次の図は、このプロセスを示しています。
型チェック
型チェックは、ほとんどすべてのプログラミング言語に組み込まれている機能です。これは、特定の変数に割り当てられた値がその変数の正しい型に対応するかどうかをチェックする機能です。プログラミング言語ごとに、メモリ内の特定の型の値を表す方法が異なります。これらのさまざまな表現により、値の型と、その値を代入しようとしている変数の型との対応を確認することができます。
プログラムのライフサイクルと型チェックについて大まかに理解したので、静的プログラミング言語の探索に進むことができます。
静的プログラミング言語
静的プログラミング言語は、静的型付け言語とも呼ばれ、コンパイル段階で述べた型チェックを適用する言語です。これは事実上、変数は宣言の型を保持し、宣言の型の値以外の値を変数に割り当てることができないことを意味します。静的プログラミング言語は、型を扱う際に特別な安全性を提供しますが、これが厳しい制限になると、特定のユースケースで開発プロセスが遅くなる可能性があります。
動的プログラミング言語
一方、動的プログラミング言語は、実行時に型チェックを適用します。これは、プログラム内の任意の時点で、任意の変数が任意の値を保持できることを意味します。これは、静的言語には存在しないレベルの柔軟性を開発者に提供するため、有益です。動的言語は、各変数の型を動的に把握する追加のステップが必要となるため、静的言語よりも実行が遅くなる傾向があります。
モンキーパッチ
静的型付けと動的型付けはプログラミング言語の基本的な特性であり、一方のパラダイムを他方のパラダイムに優先させることで、開発の品質と速度を大幅に向上させるさまざまなパターンや実践が可能になります。また、設計を決定する際に慎重に考慮しないと、多くの制限やアンチパターンが発生する可能性があります。
特に、動的型付けプログラミング言語は、変数を単一の型に制限しないため、より高いレベルの柔軟性を提供することが知られています。この柔軟性には、予期しない動作が発生していないことを確認するためにプログラムを実装およびデバッグする際に、開発者に追加の責任がかかるというコストが伴います。モンキーパッチパターンはこの哲学から生まれました。
モンキー パッチは、実行時にコンポーネントの動作を拡張/変更するプロセスを指します。問題のコンポーネントは、ライブラリ、クラス、メソッド、さらにはモジュールの場合もあります。考え方は同じです。コードは特定のタスクを達成するために作成されます。モンキー パッチの目的は、コード自体を変更することなく、そのコードの動作を変更または拡張して新しいタスクを達成することです。 。
これは、扱うコンポーネントの種類に関係なく、異なる属性を持つオブジェクトの同じ構造を持ち、属性はオブジェクトの新しい動作を実現するために再割り当てできるメソッドを保持できるため、動的プログラミング言語で可能になります。内部構造や実装の詳細には触れません。これは、調整が難しい傾向にあるサードパーティのライブラリやモジュールの場合に特に役立ちます。
次の例では、モンキー パッチ手法を使用することでメリットが得られる一般的な使用例を示します。ここでは実装のために Javascript が使用されていますが、これは他の動的プログラミング言語にも広く適用できるはずです。
## 例
ノードのネイティブ HTTP モジュールを使用して最小限のテスト フレームワークを実装する
単体テストと統合テストは、Monkey パッチ適用のユースケースに該当する可能性があります。通常、統合テストでは複数のサービスにわたるテスト ケース、単体テストでは API やデータベースの依存関係が含まれます。これら 2 つのシナリオでは、そもそもテストの目的を達成するために、テストをこれらの外部リソースから独立させたいと考えます。これを達成する方法はモックを使用することです。モックは外部サービスの動作をシミュレートするので、テストはコードの実際のロジックに集中できます。モンキー パッチは、外部サービスのメソッドを「スタブ」と呼ばれるプレースホルダー メソッドに置き換えることで変更できるため、ここで役立ちます。これらのメソッドはテストケースで期待される結果を返すため、テストのためだけに本番サービスへのリクエストを開始することを避けることができます。
次の例は、NodeJs ネイティブ http モジュールでの Monkey パッチ適用の簡単な実装です。 http モジュールは、NodeJ の http プロトコル メソッドを実装するインターフェイスです。これは主に、ベアボーン http サーバーを作成し、http プロトコルを使用して外部サービスと通信するために使用されます。
以下の例には、外部サービスを呼び出してユーザー ID のリストを取得する単純なテスト ケースがあります。実際のサービスを呼び出すのではなく、http get メソッドにパッチを適用して、ランダムなユーザー ID の配列である期待される結果を返すだけです。データをフェッチしているだけなので、これはそれほど重要ではないように思えるかもしれませんが、何らかのデータの変更を伴う別のテスト ケースを実装すると、テストの実行時に本番環境のデータを誤って変更してしまう可能性があります。
このようにして、実稼働サービスの安全性を確保しながら、機能を実装し、各機能のテストを作成できます。
// import the http module
let http = require("http");
// patch the get method of the http module
http.get = async function(url) {
return {
data: ["1234", "1235", "1236", "1236"]
};
}
// example test suite, call new patched get method for testing
test('get array of user ids from users api', async () => {
const res = await http.get("https://users.api.com/ids");
const userIds = res.data;
expect(userIds).toBeDefined();
expect(userIds.length).toBe(4);
expect(userIds[0]).toBe("1234");
});
上記のコードは単純で、http モジュールをインポートし、ID の配列を返すだけの新しいメソッドで http.get メソッドを再割り当てします。ここで、テスト ケース内で新しいパッチ適用されたメソッドを呼び出し、新しい期待される結果を取得します。
~/SphericalTartWorker$ npm test
> nodejs@1.0.0 test
> jest
PASS ./index.test.js
✓ get array of user ids from users api (25 ms)
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 0.977 s, estimated 2 s
Ran all test suites.
一般的な落とし穴と制限事項
モンキーパッチングには独自の欠陥と制限があることは驚くべきことではありません。ノード モジュール システムのモジュールのコンテキストでは、http などのグローバル モジュールへのパッチ適用は副作用のある操作とみなされます。これは、http はコードベース内のどの時点からでもアクセス可能であり、他のエンティティがそれに依存している可能性があるためです。これらのエンティティは、http モジュールが通常の動作で動作することを期待しており、http メソッドの 1 つを変更することで、コードベース内の他のすべての http 依存関係を効果的に解除します。
動的に型付けされた言語内で操作しているため、すぐには失敗しない可能性があり、むしろデフォルトで予測不可能な動作が発生する可能性があり、デバッグが非常に複雑なタスクになります。他の使用例では、同じ属性上の同じコンポーネントの 2 つの異なるパッチが存在する可能性があります。その場合、どちらのパッチが他のパッチよりも優先されるかを実際に予測することはできず、さらに予測不可能なコードが発生します。
モンキー パッチの動作は、異なるプログラミング言語間ではわずかに異なる可能性があることに言及することも重要です。それはすべて、言語設計と実装の選択によって異なります。たとえば、Python では、パッチが適用されたメソッドを使用するすべてのインスタンスがパッチの影響を受けるわけではありません。インスタンスがパッチ適用されたメソッドを明示的に呼び出す場合、そのインスタンスは新しい更新されたバージョンを取得します。逆に、パッチ適用されたメソッドを指す属性のみを持ち、それを明示的に呼び出していない他のインスタンスは、元のバージョンを取得します。これは、Python のメソッドによるものです。クラス内のバインディングが機能します。
## 結論
この記事では、静的プログラミング言語と動的プログラミング言語の間の高レベルの違いを検討し、動的プログラミング言語がこれらの言語が提供する固有の柔軟性を活用した新しいパラダイムとパターンからどのように恩恵を受けることができるかを見ていきました。私たちが紹介した例は、ソースから変更せずにコードの動作を拡張するために使用されるテクニックであるモンキー パッチに関連したものでした。私たちは、このテクニックの使用が潜在的な欠点とともに有益であるケースを確認しました。ソフトウェア開発はトレードオフがすべてであり、問題に対して適切なソリューションを採用するには、開発者が綿密に検討し、アーキテクチャの原則と基本をよく理解する必要があります。
Code Labs Academy の Web 開発ブートキャンプ で HTML、CSS、JavaScript のスキルを向上させ、将来のキャリアを築きましょう。