web-dev-qa-db-ja.com

関数をパラメーターとして他の関数に渡す、悪い習慣?

AS3アプリケーションがバックエンドと通信する方法を変更する過程にあり、RESTシステムを実装して古いものを置き換えるための過程にあります。

悲しいことに、この作業を始めた開発者は現在、長期の病気休暇を取得しており、私に引き渡されました。私はこの1週間ほど作業を続けており、システムを理解していますが、心配なことが1つあります。関数への関数の受け渡しがたくさんあるようです。たとえば、サーバーを呼び出すクラスは、プロセスが完了してエラーが処理されたときにオブジェクトを呼び出して渡す関数を受け取ります。

それは恐ろしい練習だと感じる「悪い感情」を私に与えており、なぜかいくつかの理由を考えることができますが、システムに再加工を提案する前に確認が必要です。誰かがこの起こり得る問題について何か経験があるかどうか疑問に思っていましたか?

41

それは問題ではありません。

これは既知の手法です。これらは 高次関数 (関数をパラメーターとして取る関数)です。

この種の関数は 関数型プログラミング の基本的な構成要素でもあり、 Haskell などの関数型言語で広く使用されています。

このような機能は、悪くもありません。概念やテクニックに一度も遭遇したことがない場合、最初は把握するのは難しいかもしれませんが、非常に強力であり、ツールベルトに組み込むのに適したツールです。

85
Oded

これらは関数型プログラミングに使用されるだけではありません。それらは callbacks としても知られています:

コールバックは、引数として他のコードに渡される実行可能コードの一部であり、都合の良いときに引数をコールバック(実行)することが期待されています。呼び出しは、同期コールバックの場合のように即時に行われる場合と、非同期コールバックの場合のように後で行われる場合があります。

非同期コードについて少し考えてみましょう。たとえば、ユーザーにデータを送信する関数を渡します。コードが完了した場合にのみ、この関数を応答の結果とともに呼び出します。この関数は、この関数を使用してデータをユーザーに送り返します。それは考え方の変化です。

シードボックスからTorrentデータを取得するライブラリを作成しました。非ブロッキングイベントループを使用してこのライブラリを実行し、データを取得してから、ユーザーに返します(たとえば、WebSocketコンテキストで)。このイベントループで5人のユーザーが接続していて、誰かの急流データを停止する要求の1つを想像してください。それはループ全体をブロックします。したがって、非同期で考えてコールバックを使用する必要があります。ループは継続して実行され、「データをユーザーに返す」ことは、関数の実行が終了したときにのみ実行されるため、それを待つ必要はありません。火を忘れて。

30
James

これは悪いことではありません。実際、それはとても良いことです。

関数への関数の受け渡しはプログラミングにとって非常に重要なので、省略形として lambda functions を考案しました。たとえば、 C++アルゴリズムでラムダを使用 して、非常にコンパクトでありながら表現力のあるコードを記述して、一般的なアルゴリズムでローカル変数やその他の状態を使用して、検索や並べ替えなどを行うことができます。

オブジェクト指向ライブラリには callbacks も含まれる場合があります。これは基本的に少数の関数(理想的には1つですが、常にではない)を指定するインターフェースです。次に、そのインターフェイスを実装する単純なクラスを作成し、そのクラスのオブジェクトを関数に渡すことができます。これは イベント駆動型プログラミング の基礎であり、フレームワークレベルのコード(おそらく別のスレッドでも)は、ユーザーアクションに応じて状態を変更するためにオブジェクトを呼び出す必要があります。 Javaの ActionListener インターフェースは、この良い例です。

技術的には、C++ファンクタは、シンタックスシュガーoperator()()を利用して同じことを行う一種のコールバックオブジェクトでもあります。

最後に、Cでのみ使用する必要があるCスタイルの関数ポインターがあります。詳細には触れませんが、完全を期すために言及します。上記の他の抽象化ははるかに優れており、それらを含む言語で使用する必要があります。

他の人たちは、関数型プログラミングと、それらの言語で関数を渡すことが非常に自然であることに言及しました。ラムダとコールバックは手続き型の方法であり、OOP言語はそれを模倣しており、非常に強力で便利です。

11
user22815

すでに述べたように、それは悪い習慣ではありません。それは単に責任を切り離して分離する方法です。たとえば、OOPでは、次のようにします。

public void doSomethingGeneric(ISpecifier specifier) {
    //do generic stuff
    specifier.doSomethingSpecific();
    //do some other generic stuff
}

ジェネリックメソッドは、特定のタスク(何も知らない)を、インターフェイスを実装する別のオブジェクトに委任しています。ジェネリックメソッドはこのインターフェイスのみを認識します。あなたの場合、このインターフェースは呼び出される関数です。

6
Philipp Murry

一般に、関数を他の関数に渡すことには何の問題もありません。非同期呼び出しを行っていて、その結果に対して何かを実行したい場合は、なんらかのコールバックメカニズムが必要になります。

ただし、単純なコールバックにはいくつかの潜在的な欠点があります。

  • 一連の呼び出しを行うには、コールバックを深くネストする必要があります。
  • エラー処理では、一連の呼び出しの呼び出しごとに繰り返しが必要になる場合があります。
  • 複数の呼び出しを調整するのは厄介です。たとえば、複数の呼び出しを同時に行い、すべてが終了したら何かを行うようなものです。
  • 一連の呼び出しをキャンセルする一般的な方法はありません。

単純なWebサービスを使用すると、従来の方法で問題なく機能しますが、より複雑な呼び出しのシーケンス処理が必要になると扱いにくくなります。ただし、いくつかの選択肢があります。たとえばJavaScriptの場合、約束の使用へのシフトがありました( javascript約束のすばらしいところ )。

それらはまだ他の関数に関数を渡すことを含みますが、非同期呼び出しは自分自身で直接コールバックを取るのではなく、コールバックを取る値を返します。これにより、これらの呼び出しを一緒に構成する際の柔軟性が向上します。このようなものは、ActionScriptでかなり簡単に実装できます。

3
fgb