web-dev-qa-db-ja.com

メンバー変数をメソッドパラメーターとして渡す

プロジェクトで、次のようなコードを見つけました。

class SomeClass
{
    private SomeType _someField;

    public SomeType SomeField
    {
        get { return _someField; }
        set { _someField = value; }
    }

    protected virtual void SomeMethod(/*...., */SomeType someVar)
    {
    }

    private void SomeAnotherMethod()
    {
        //.............
        SomeMethod(_someField);
        //.............
    }

};

これが悪いコードであることをチームメイトにどのように説得しますか?

これは不要な合併症だと思います。メンバー変数に既にアクセスできるのに、なぜそれをメソッドパラメーターとして渡すのですか?これもカプセル化の違反です。

このコードで他の問題が発生していますか?

33
tika

これは有効なトピックだと思いますが、混合回答が表示されるのは、質問の作成方法が原因です。個人的には、私のチームと同じ経験をしましたが、メンバーを引数として渡す必要はなく、コードを複雑にしていました。メンバーのセットで機能するクラスがありますが、一部の関数はメンバーに直接アクセスし、他の関数はパラメーターを介して同じメンバーを変更します(つまり、完全に異なる名前を使用します)。そのための技術的な理由はまったくありませんでした。技術的な理由で、私はケイトが提供した例を意味します。

一歩下がって、パラメーターとしてのメンバーの受け渡しに厳密に焦点を合わせるのではなく、明確さと読みやすさについてチームとの話し合いを開始することをお勧めします。より正式に、またはただ廊下で、いくつかのコードセグメントが読みやすくなり、他のコードセグメントがより困難になる理由について説明します。次に、チームとして努力したいクリーンなコードの品質指標または属性を特定します。結局のところ、グリーンフィールドプロジェクトに取り組んでいるときでも、90%以上の時間を費やして読み、コードが記述されると(たとえば10〜15分後に)、読みやすさがさらに重要な保守作業に入ります。

したがって、ここでの特定の例について、私が使用する議論は、より少ないコードは常により多くのコードよりも読みやすいということです。パラメータが3つある関数は、パラメータが1つもない関数や1つしかない関数よりも脳の処理が困難です。別の変数名がある場合、コードを読み取るときに脳はさらに別のことを追跡する必要があります。したがって、「int m_value」を覚えてから「int localValue」を覚えて、一方が本当にもう一方があなたの脳にとって常により高価であることを覚えてから、単に「m_value」で作業することを覚えておいてください。

より多くの弾薬とアイデアのために、ボブおじさんの クリーンコード のコピーを手に入れることをお勧めします。

5
DXM

(プライベート)メソッドのパラメーターとしてメンバーフィールドを渡すことの正当化を考えることができます。これにより、メソッドの依存先が明確になります。

あなたが言うように、オブジェクト全体がそうであるので、すべてのメンバーフィールドはあなたのメソッドの暗黙のパラメータです。しかし、結果を計算するために完全なオブジェクトが本当に必要ですか? SomeMethod_someFieldにのみ依存する内部メソッドである場合、この依存関係を明示的にする方がきれいではありませんか?実際、この依存関係を明示的にすると、このコードの一部をクラスから実際にリファクタリングできることも示唆されます。 (ここではゲッターやセッターについて話していないと思いますが、実際に何かを計算するコードです)

呼び出し側はオブジェクトのどの部分が結果の計算に関連するのかを知らず、気にもしないので、私はパブリックメソッドに対して同じ議論をしません...

31
Andres F.

メンバー変数を関数の引数としてprivateメソッド-関数の純粋性に渡す理由の1つがよくわかります。メンバー変数は、実質的には、観点から見るとグローバル状態です。さらに、メソッドの実行中にメンバーが変化した場合、変更可能なグローバル状態になります。メンバー変数参照をメソッドパラメーターで置き換えることにより、関数を効果的に純粋にすることができます。純粋な関数は外部状態に依存せず、副作用もありません。同じ入力パラメーターのセットを指定すると常に同じ結果を返すため、テストと将来のメンテナンスが容易になります。

すべてのメソッドを純粋なメソッドとしてOOP言語で記述することは、簡単でも実用的でもありません。しかし、複雑なロジックを純粋に処理するメソッドを使用し、不変の変数。専用のメソッド内で分離された不純な「グローバル」状態の処理を維持します。

ただし、関数を外部から呼び出すときにメンバー変数を同じオブジェクトのパブリック関数に渡すと、私の意見では主要なコードの臭いになります。

30
scrwtp

関数が何度も呼び出され、そのメンバー変数が渡されたり、別のメンバー変数が渡されたりする場合は、問題ありません。たとえば、私はこれをまったく悪いとは思わないでしょう:

if ( CalculateCharges(newStartDate) > CalculateCharges(m_StartDate) )
{
     //handle increase in charges
}

ここで、newStartDateはローカル変数であり、m_StartDateはメンバー変数です。

ただし、メンバー変数が渡されて関数が呼び出されるだけの場合は、奇妙です。メンバー関数は常にメンバー変数を処理します。メンバー変数のコピーを取得するために(作業している言語に応じて)これを行っている可能性があります。そうであり、それがわからない場合は、プロセス全体を明示的にすると、コードがより適切になる可能性があります。

8
Kate Gregory

誰も触れなかったことは、SomeMethodが仮想的に保護されていることです。つまり、派生クラスはそれを使用でき、その機能を再実装できます。派生クラスはプライベート変数にアクセスできないため、プライベート変数に依存するSomeMethodのカスタム実装を提供できません。宣言では、プライベート変数に依存する代わりに、呼び出し元がそれを渡す必要があります。

2
Michael Brown

クラスにメンバー変数を設定する主な理由は、メンバー変数を1か所に設定し、その値をクラス内の他のすべてのメソッドで使用できるようにするためです。したがって、一般的には、メンバー変数をクラスのメソッドに渡す必要がないことが予想されます。

ただし、メンバー変数をクラスの別のメソッドに渡す必要がある理由はいくつか考えられます。 1つ目は、メソッドがプロセスのある時点で実際のメンバー変数値を変更する必要がある場合でも、呼び出されたメソッドで使用するときにメンバー変数の値を変更せずに使用する必要があることを保証する必要がある場合です。 2番目の理由は、たとえば流暢な構文を実装する場合に、メソッドチェーンのスコープ内で値の不変性を保証したいという点で、1番目の理由に関連しています。

これらすべてを念頭に置いて、メンバー変数をクラスのメソッドの1つに渡した場合、コード自体が「悪い」とは言えません。ただし、たとえばパラメーターがローカル変数に割り当てられるコードの重複が多くなり、余分なパラメーターがコードに不要なコードに「ノイズ」を追加するため、一般的には理想的ではないことをお勧めします。あなたがクリーンコードブックのファンであれば、メソッドパラメータの数を最小限に抑える必要があること、およびメソッドがパラメータにアクセスするための別の賢明な方法がない場合にのみ言及することを知っているでしょう。 。

1
S.Robins

通常、GUIフレームワークには、画面上に描画されたものを表す「View」クラスがあり、そのクラスは通常、描画領域の一部を再描画する必要があることを示すinvalidateRect(Rect r)のようなメソッドを提供します。クライアントはそのメソッドを呼び出して、ビューの一部の更新を要求する場合があります。ただし、ビューは次のような独自のメソッドを呼び出す場合もあります。

_invalidateRect(m_frame);
_

エリア全体を再描画します。たとえば、ビュー階層に最初に追加されたときにこれを行う場合があります。

これを行うことには何の問題もありません。ビューのフレームは有効な長方形であり、ビュー自体はそれ自体を再描画したいことを認識しています。 Viewクラスは、パラメーターを取らず、代わりにビューのフレームを使用する別のメソッドを提供できます。

_invalidateFrame();
_

しかし、より一般的なinvalidateRect()を使用できるのに、なぜこのためだけに特別なメソッドを追加するのでしょうか。または、invalidateFrame()を提供することを選択した場合は、より一般的なinvalidateRect()の観点から実装することになるでしょう。

_View::invalidateFrame(void)
{
    invalidateRect(m_frame)
}
_

すでにアクセスできるのに、なぜ変数をメソッドパラメータとして渡すのですか?

あなたはshouldインスタンス変数をパラメータとして独自のメソッドに渡しますifメソッドはそのインスタンス変数に対して特に動作しません。上記の例では、invalidateRect()メソッドに関する限り、ビューのフレームは別の長方形です。

1
Caleb

メソッドがユーティリティメソッドである場合、これは理にかなっています。たとえば、いくつかのフリーテキスト文字列から一意の短い名前を派生する必要があるとします。

文字列ごとに個別の実装をコーディングするのではなく、代わりに文字列を共通のメソッドに渡すのが理にかなっています。

ただし、メソッドが常に単一のメンバーで機能する場合、パラメーターとして渡すのは少し馬鹿げているように見えます。

1
James Anderson

これについて考えるとき私に来るいくつかの事柄:

  1. 一般に、パラメータが少ないメソッドシグネチャは理解しやすいです。メソッドが発明された大きな理由は、長いパラメーターリストを、それらが操作するデータと結合することによって排除することでした。
  2. メソッドシグネチャをメンバー変数に依存させると、メソッド内だけでなく、メソッドが呼び出されるすべての場所でも変更する必要があるため、将来これらの変数を変更することが難しくなります。また、例のSomeMethodは保護されているため、サブクラスも変更する必要があります。
  3. クラスの内部に依存しないメソッド(パブリックまたはプライベート)は、そのクラスにある必要はありません。それらはユーティリティメソッドに組み入れられ、同様に幸せになる可能性があります。彼らはそのクラスに属しているビジネスはほとんどありません。メソッドを移動した後、他のメソッドがその変数に依存していない場合、その変数も移動するはずです。ほとんどの場合、変数はそれ自体のオブジェクト上にある必要があり、そのメソッドを操作するメソッドは、パブリックになり、親クラスによって構成されます。
  4. クラスのようなさまざまな関数にデータを渡すことは、OO設計に直面してグローバル変数が飛ぶだけの手続き型プログラムです。これは、メンバー変数とメンバー関数の意図ではありません(上記を参照)、データとメソッドをグループ化するためのより良い方法を示唆しているのはコードのにおいだと思います。
1
Colin Hunt

パラメーター( "argument")が参照または読み取り専用の場合は、欠落しています。

class SomeClass
{
    protected SomeType _someField;
    public SomeType SomeField
    {
        get { return _someField; }

        set {
          if (doSomeValidation(value))
          {
            _someField = value;
          }
        }
    }

    protected virtual void ModifyMethod(/*...., */ ref SomeType someVar)
    { 
      // ...
    }    

    protected virtual void ReadMethod(/*...., */ SomeType someVar)
    { 
      // ...
    }

    private void SomeAnotherMethod()
    {
        //.............

        // not recommended, but, may be required in some cases
        ModifyMethod(ref this._someField);

        //.............

        // recommended, but, verbose
        SomeType SomeVar = this.someField;
        ModifyMethod(ref SomeVar);
        this.someField = SomeVar;

        //.............

        ReadMethod(this.someField);
        //.............
    }

};

多くの開発者は、通常、コンストラクターメソッドで変数の内部フィールドを直接割り当てます。いくつかの例外があります。

「セッター」には、割り当てだけでなく、追加のメソッドを含めることができ、場合によっては仮想メソッドになることや、仮想メソッドを呼び出すこともあることに注意してください。

注:プロパティの内部フィールドを「保護された」状態に保つことをお勧めします。

0
umlcat