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


33

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

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);
        //.............
    }

};

これが悪いコードであることをチームメートに納得させるにはどうすればよいですか?

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

このコードに他の問題がありますか?


21
何が悪いと思う?
ヤニス

@Yannis Rizos、いいと思う?少なくともこれは不必要な複雑さです。既に変数にアクセスできるのに、なぜ変数をメソッドパラメーターとして渡すのですか?これもカプセル化の違反です。
ティカ

2
有効なポイントです。質問を編集してそれらを含めてください。チームメイトを納得させることはできませんが、コードを評価することはできます。それがあなたの質問です。
ヤニス

8
むしろ、定数2 + 2を実行するメソッドとは異なる変数を合計できるメソッドが必要です。メソッドのパラメーターは再利用のためです。
ダンテ

ここで重要だと思う1つのポイントは、そのパラメーターのタイプです。参照型の場合は利点がありませんが、値型の場合は意味があります。変数型を変更すると、コンパイラがコードを壊した場所について警告するからです。
レミ

回答:


3

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

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

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

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


参照された本の影響を見た後、ダウン投票。
フランクヒルマン

私の小さな部分は非常に悲しいですが、答えを書いてから5年後にあなたは2つのインターネットポイントを私から奪いました、私が別の小さな部分があるので、あなたが(おそらく悪い)を示すいくつかの参照を提供できるかどうか興味があります私が参照した本が持っていた影響。公正かつそれらの点はほとんど価値があると思われるもの
DXM

参照は私自身であり、他の開発者への影響を個人的に見ています。そのような本の背後にあるすべての動機は良いですが、すべてのコードが非標準のガイドライン(つまり、実際に目的を果たすガイドライン)に従う必要があることを指定することにより、そのような本は批判的思考の喪失を生むように思われ、一部はカルトのように解釈します。
フランクヒルマン

脳処理の課題に関しては、認知負荷
jxramos

30

メンバーフィールドを(プライベート)メソッドのパラメーターとして渡すことの正当化を考えることができます:メソッドが依存するものを明示します。

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

呼び出し元は、オブジェクトのどの部分が結果の計算に関連するかを知らず、気にもしないため、パブリックメソッドに対して同じ引数を作成しません。


2
残りの暗黙的な依存関係はどうなりますか?私はあなたの例から_someField、結果を計算するために必要な唯一のパラメータであると仮定し、それを明示的にしただけです。(注:これは重要です。依存関係は追加せず、明示的にしました!)
Andres F.

11
-1インスタンスメンバーへの暗黙的な依存関係がない場合、値をパラメーターとして受け取る静的メンバーである必要があります。この場合、あなたは理由を思いつきましたが、それが正当な理由だとは思いません。それは悪いコードです。
スティーブンエバーズ

2
@SnOrfus私は実際には完全にアウトクラスのメソッドをリファクタリング提案
アンドレスF.

5
+1。「...この依存関係を明示的にする方がクリーンではないですか?」まったくおかしくなります。ここで誰が「グローバル変数は一般的なルールとして良い」と言うでしょうか?それはCOBOL-68です。今私を聞いて、後で私を信じてください。非自明なコードでは、クラスグローバル変数が使用されている場所を明示的に伝えるために、時々リファクタリングします。多くの場合、a)プライベートフィールドとそのパブリックプロパティの任意の前後の使用、b)フィールドの変換を「依存関係を非表示にする」ことによって難読化することで、この状況を台無しにしました。次に、これに3〜5の深い継承チェーンを掛けます。
レーダーボブ

2
@Tarionこれについては、ボブおじさんに同意しません。可能な限り、メソッドは関数のようであり、明示的な依存関係のみに依存する必要があります。(OOPでパブリックメソッドを呼び出す場合、これらの依存関係の1つはthis(またはself)ですが、呼び出し自体によって明示されますobj.method(x))。その他の暗黙的な依存関係は、オブジェクトの状態です。これにより、通常、コードが理解しにくくなります。可能な場合はいつでも、そして合理的な範囲内で、依存関係を明示的かつ機能的なスタイルにします。プライベートメソッドの場合、可能であれば、必要なすべてのパラメーターを明示的に渡します。はい、それはそれらをリファクタリングするのに役立ちます。
アンドレスF.

28

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

すべてのメソッドをOOP言語の純粋なメソッドとして使用するのは簡単でも実用的でもありません。しかし、複雑なロジックを純粋に処理し、不変の変数を使用するメソッドを持ち、専用メソッド内で不純な「グローバル」状態処理を分離することで、コードの明確さの面で多くのメリットが得られると思います。

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


5
サプルブの答え。理想的ではありますが、今日のソフトウェアの多くのクラスは、「Globals are Evil」というフェーズが作成されたプログラム全体よりも大きくてugいものです。クラス変数は、すべての実用的な目的のために、クラスインスタンスのスコープ内のグローバルです。純粋な関数で大量の作業を行うと、テスト可能かつ堅牢なコードになります。
-mattnz

@mattnzは、Hwatの参考文献や理想的なプログラミングに関する彼の本へのリンクを提供する方法はありますか?私はインターネットを精査してきましたが、彼には何も見つかりません。Googleは「何」に自動修正しようとし続けます。
バトルブトクス

8

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

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

どこnewStartDateいくつかのローカル変数で、m_StartDateメンバ変数です。

ただし、関数が渡されたメンバー変数でのみ呼び出される場合、それは奇妙です。メンバー関数は、常にメンバー変数で機能します。彼らは(作業している言語に応じて)これを行ってメンバー変数のコピーを取得している可能性があります-その場合で、あなたがそれを見ることができない場合、プロセス全体を明示的にすればコードはより良いかもしれません。


3
メソッドメンバー変数以外のパラメーター呼び出されることは問題ではありません。重要なのは、そのように呼ぶことができるということです。メソッドを作成するときに、メソッドが最終的にどのように呼び出されるかは必ずしもわかりません。
カレブ

「if」条件を「needHandleIncreaseChages(newStartDate)」に置き換えると、引数が保持されなくなる可能性があります。
タリオン

2

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


不足しているのは、このプライベートメンバー変数にパブリックアクセサーがあることです。
ティカ

保護された仮想メソッドは、プライベート変数のパブリックアクセサーに依存する必要があると言っていますか?そして、あなたは現在のコードに問題がありますか?
マイケルブラウン

使用するパブリックアクセサーが作成されます。期間。
ティカ

1

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

invalidateRect(m_frame);

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

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

invalidateFrame();

しかし、より一般的なものを使用できるのに、なぜこのために特別なメソッドを追加するのinvalidateRect()ですか または、提供することを選択した場合invalidateFrame()、より一般的な観点から実装する可能性が非常に高くなりますinvalidateRect()

View::invalidateFrame(void)
{
    invalidateRect(m_frame)
}

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

あなたは必要があり、独自のメソッドのパラメータとしてインスタンス変数を渡す場合の方法は、そのインスタンス変数には、具体的に動作しません。上記の例では、invalidateRect()メソッドに関する限り、ビューのフレームは単なる別の長方形です。


1

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

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

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


1

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

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

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


1

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

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

0

パラメーター( "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);
        //.............
    }

};

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

「セッター」には、単なる割り当てではなく追加のメソッドがあり、場合によっては仮想メソッドであったり、仮想メソッドを呼び出したりすることもあります。

注:プロパティの内部フィールドは「保護」のままにすることをお勧めします。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.