これは良いパターンですか?長い関数を一連のラムダに置き換えますか?


14

最近、次のような状況に遭遇しました。

class A{
public:
    void calculate(T inputs);
}

まず、A物理世界のオブジェクトを表します。これは、クラスを分割しないことの強力な議論です。今、calculate()非常に長く複雑な機能であることが判明しました。私は3つの可能な構造を知覚します:

  • テキストの壁としてそれを書く-利点-すべての情報が一箇所にある
  • privateクラスでユーティリティ関数を記述し、それらをcalculateボディで使用する-欠点-クラスの残りの部分は、これらのメソッドを知らない/気遣わない/理解しない
  • calculate次のように書きます:

    void A::calculate(T inputs){    
        auto lambda1 = () [] {};    
        auto lambda2 = () [] {};    
        auto lambda3 = () [] {};
    
        lambda1(inputs.first_logical_chunk);
        lambda2(inputs.second_logical_chunk);
        lambda3(inputs.third_logical_chunk);
    }

これは良いプラクティスか悪いプラクティスか?このアプローチは問題を明らかにしますか?全体として、再び同じ状況に直面したとき、これを良いアプローチと考えるべきですか?


編集:

class A{
    ...
public:
    // Reconfiguration of the algorithm.
    void set_colour(double colour);
    void set_density(double density);
    void set_predelay(unsigned long microseconds);
    void set_reverb_time(double reverb_time, double room_size);
    void set_drywet(double left, double right);
    void set_room_size(double value);;

private:
    // Sub-model objects.
    ...
}

これらすべての方法:

  • 値を得る
  • 状態を使用せずに、他の値を計算します
  • いくつかの「サブモデルオブジェクト」を呼び出して、状態を変更します。

set_room_size()これらのメソッドは、要求された値をサブオブジェクトに渡すだけであることがわかります。set_room_size()一方、あいまいな式の画面をいくつか表示し、(2)サブオブジェクトのセッターを呼び出す画面の半分を実行して、取得したさまざまな結果を適用します。したがって、関数を2つのラムダに分割し、関数の最後で呼び出します。もっと論理的なチャンクに分割できたら、より多くのラムダを分離できたでしょう。

とにかく、現在の質問の目標は、その考え方が持続するべきか、それともせいぜい価値(読みやすさ、保守性、デバッグ能力など)を追加しないかを決定することです。


2
ラムダを使用すると、関数呼び出しでは得られないと思いますか?
Blrfl

1
Firstly, A represents an object in the physical world, which is a strong argument for not splitting the class up.物理的な世界に存在する可能性のあるオブジェクトに関するデータを確実にA表します。実オブジェクトなしののインスタンスとのインスタンスなしの実オブジェクトを持つことができるので、それらを1つの同じものとして扱うのは無意味です。AA
ドーバル

@Blrfl、カプセル化-誰もcalculate()これらのサブ機能について知らないでしょう。
ヴォラック

これらの計算がすべてに関連するA場合、それは少し極端になっています。
Blrfl

1
「まず、A物理世界のオブジェクトを表します。これは、クラスを分割しないことの強力な議論です。」残念ながら、プログラミングを始めたとき、私はこれを言われました。たくさんの馬のホッケーだと気づくのに何年もかかりました。ものをグループ化するのは恐ろしい理由です。少なくとも私が満足する限りでは、物事をグループ化する正当な理由を明確にすることできませんが、それは今すぐ捨てるべきものです。結局のところ、すべてが「良いコード」であるということは、正しく動作し、比較的理解しやすく、比較的簡単に変更できるということです(つまり、変更には奇妙な副作用はありません)。
jpmc26

回答:


13

いいえ、これは一般的に良いパターンではありません

あなたがしているのは、ラムダを使用して関数をより小さな関数に分割することです。ただし、関数を分割するためのはるかに優れたツール、関数があります。

ラムダの仕事は、あなたが見てきたように、彼らは意味ずっとずっとずっとずっとずっと単純にローカルビットに機能を分割以上。ラムダは:

  • 閉鎖。ラムダ内の外部スコープで変数を使用できます。これは非常に強力で非常に複雑です。
  • 再割り当て。あなたの例では割り当てが難しくなりますが、コードはいつでも関数を交換できるという考えに常に注意を払う必要があります。
  • 一流の機能。ラムダ関数を別の関数に渡し、「関数型プログラミング」として知られていることを実行できます。

ラムダをミックスに取り込むと、次にコードを見る開発者は、コードがどのように機能するかを確認するための準備として、これらすべてのルールを直ちに精神的にロードする必要があります。彼らはあなたがあなたがそのすべての機能を使うつもりがないことを知りません。これは、代替案と比較して非常に高価です。

バックホーを使ってガーデニングをするようなものです。今年の花のために小さな穴を掘るためだけにそれを使用していることを知っていますが、隣人は緊張します。

あなたがしているのは、ソースコードを視覚的にグループ化することだけだと考えてください。コンパイラは、実際にラムダを使用してカリー化したことを気にしません。実際、オプティマイザーは、コンパイル時に行ったすべての操作をすぐに取り消すことを期待しています。あなたは純粋に次の読者を対象にしています(方法論に異論があるとしても、そうしてくれてありがとう!コードは書かれているよりもはるかに頻繁に読まれています!)。あなたがしているのは、機能をグループ化することだけです。

  • ソースコードのストリーム内にローカルに配置された関数も、ラムダを呼び出さずに同様に実行します。繰り返しますが、重要なのは、読者がそれを読むことができるということです。
  • 関数の上部にある「この関数を3つの部分に分割している」というコメントの後に、// ------------------各部分の間に長い長い行が続きます。
  • 計算の各部分を独自のスコープに入れることもできます。これには、部品間で変数の共有がないことを疑う余地なくすぐに証明するというボーナスがあります。

編集:サンプルコードで編集を見ることから、コメントが示唆する境界を強制するための括弧を使用して、最もクリーンなコメント表記に傾倒しています。ただし、機能のいずれかが他の機能で再利用可能な場合は、代わりに機能を使用することをお勧めします

void A::set_room_size(double value)
{
    {
        // Part 1: {description of part 1}
        ...
    }
    // ------------------------
    {
        // Part 2: {description of part 2}
        ...
    }
    // ------------------------
    {
        // Part 3: {description of part 3}
        ...
    }
}

したがって、客観的な数字ではありませんが、ラムダ関数が存在するだけで、コードの各行に約10倍の注意を払うようになると主観的に主張します。無邪気なコード行(のようなcount++
Cort Ammon-モニカの復職

素晴らしい点。私はそれらをラムダのアプローチのいくつかの利点と考えています-(1)ローカルに隣接し、ローカルスコープを持つコード(これはファイルレベルの関数によって失われます)(2)コンパイラはコードセグメント間でローカル変数が共有されないことを保証します。したがって、これらの利点はcalculate(){}ブロックに分割し、calculate()スコープで共有データを宣言することで維持できます。ラムダがキャプチャしないことを確認することで、読者はラムダの力に邪魔されないだろうと思いました。
ヴォラック

「私は、ラムダがキャプチャしないのを見ることで、読者はラムダの力に邪魔されることはないだろう」それは実際には公平であるが、論争の多い声明であり、言語学の核心に突き当たります。言葉には通常、その意味を超えた意味合いがあります。私の含意lambdaが不公平であるか、またはあなたが人々に厳格な表現に従うように無礼に強制しているのかどうかは、答えるのは簡単な質問ではありません。実際、あなたの会社でそのように使用することは完全に受け入れられるかもしれませんし、私の会社ではまったく受け入れられないかもしれません。lambda
コートアンモン-モニカ

ラムダの意味に関する私の意見は、私がC + 11ではなくC ++ 03で育ったという事実に由来しています。関数lambdaなど、C ++が不足しているためにC ++が傷つけられた特定の場所に対する感謝の気持ちを長年かけて開発してきましたfor_each。したがって、lambdaこれらの見つけやすいトラブルのケースのいずれにも当てはまらないものを見たとき、私が最初に思いつくのは、それが関数プログラミングに使用される可能性が高いということです。多くの開発者にとって、関数型プログラミングは手続き型プログラミングやオブジェクト指向プログラミングとはまったく異なる考え方です。
コートアンモン-モニカ

説明してくれてありがとう。私は初心者のプログラマーであり、今では私が尋ねたことをうれしく思っています。
ヴォラック

20

あなたは間違った仮定をしたと思います:

まず、Aは物理世界のオブジェクトを表します。これは、クラスを分割しないことの強力な議論です。

私はこれに同意しません。たとえば、車を表すクラスがある場合、タイヤを表す小さなクラスが必ず必要になるため、確実に分割する必要があります。

この関数をより小さなプライベート関数に分割する必要があります。それが本当にクラスの他の部分から分離されているように見える場合、それはクラスが分離されるべきであるというサインかもしれません。もちろん、明確な例を使わずに伝えるのは難しいです。

この場合、ラムダ関数を使用する利点は実際にはわかりません。ラムダ関数を使用すると、コードが実際にきれいにならないからです。関数型プログラミングを支援するために作成されましたが、これはそうではありません。

あなたが書いたものは、Javascriptスタイルのネストされた関数オブジェクトに少し似ています。これもまた、それらが密接に関係していることを示しています。あなたは彼らのために別個のクラスを作るべきではないことを確信していますか?

要約すると、これが良いパターンだとは思いません。

更新

この機能を意味のあるクラスにカプセル化する方法が見つからない場合は、クラスのメンバーではないファイルスコープのヘルパー関数を作成できます。結局、これはC ++であり、オブジェクト指向設計は必須ではありません。


理にかなっているように聞こえますが、実装を想像するのは困難です。静的メソッドを持つファイルスコープクラス?内部で定義されたクラスA(他のすべてのメソッドがプライベートなファンクターでさえあるかもしれません)?内部で宣言および定義されたクラスcalculate()(これは私のラムダの例と非常によく似ています)。説明としてはcalculate()、メソッド(calculate_1(), calculate_2()など)のファミリーの1つであり、そのすべてが単純です。これは、2画面の数式です。
ヴォラック

@Vorac:なぜcalculate()他のすべての方法よりもずっと長いのですか?
ケビン

@Voracあなたのコードを見ずに助けるのは本当に難しいです。投稿することもできますか?
ガボールアンギャル

@Kevin、すべての式は要件に記載されています。コードが投稿されました。
ヴォラック

4
できればこれを100回支持します。「現実世界でのオブジェクトのモデリング」は、オブジェクトデザインのデススパイラルの始まりです。どのコードでも大きな赤い旗です。
フレッドマジックワンダードッグ
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.