このような場合に必要な最小限のデータを常に関数に渡す必要があります


81

IsAdminユーザーが管理者であるかどうかをチェックする機能があるとしましょう。また、管理者チェックは、ユーザーID、名前、およびパスワードを何らかのルール(重要ではない)と照合することで行われるとしましょう。

私の頭の中には、このための2つの可能な関数シグネチャがあります。

public bool IsAdmin(User user);
public bool IsAdmin(int id, string name, string password);

私は次のことを考えて、ほとんどの場合、2番目のタイプの署名を使用します。

  • 関数の署名は、読者により多くの情報を提供します
  • 関数内に含まれるロジックは、Userクラスについて知る必要はありません
  • 通常、関数内のコードはわずかに少なくなります

しかし、私は時々このアプローチに疑問を呈し、またある時点でそれが扱いにくくなることを認識しています。たとえば、関数が10個の異なるオブジェクトフィールド間を結果boolにマップする場合、私は明らかにオブジェクト全体を送信します。しかし、そのような厳格な例は別として、実際のオブジェクトを渡す理由がわかりません。

どちらのスタイルについても議論があれば、また一般的な意見をいただければ幸いです。

私はオブジェクト指向スタイルと機能スタイルの両方でプログラミングしているので、質問はありとあらゆるイディオムに関するものと見なされるべきです。


40
トピック外:好奇心から、なぜユーザーの「管理者」ステータスはパスワードに依存するのですか?
stakx

43
@ syb0rg間違っています。C#では、これが命名規則です。
マグナティック

68
@ syb0rg右、彼は言語を指定しなかったので、特定の言語の慣習に違反していると彼に伝えるのはかなり奇妙だと思います。
マグナティック

31
@ syb0rg「関数名は大文字で始めてはならない」という一般的な記述は間違っています。なぜなら、必要な言語があるからです。とにかく、私はあなたを怒らせるつもりはなかった、私はこれを片付けようとしていた。これは、質問自体のトピックからかなり外れています。必要性はわかりませんが、続行するには、議論をチャットに移してください。
マグナティック

6
一般的なポイントと同様に、独自のセキュリティを導入することは通常悪いことです(tm)。ほとんどの言語とフレームワークにはセキュリティ/許可システムが用意されており、よりシンプルなものが必要な場合でもそれらを使用するのには十分な理由があります。
ジェームズスネル

回答:


123

私は個人的にちょうど最初の方法を好む IsAdmin(User user)

使いやすく、IsAdminの基準が後日(おそらくロールまたはisActiveに基づいて)変更される場合、メソッドシグネチャをどこでも書き換える必要はありません。

また、ユーザーが管理者であるかどうかを決定するプロパティを宣伝したり、あらゆる場所でパスワードプロパティを渡したりしないので、おそらくより安全です。そして、シリオンは、あなたidname/にマッチしないときに何が起こるかという良いポイントを作りますpassword

メソッド内のコードの長さは、メソッドが機能する限り、実際には問題ではありません。ヘルパーメソッドコードよりも短くてシンプルなアプリケーションコードが必要です。


3
リファクタリングの問題は非常に良い点だと思います-ありがとう。
アンデルスアルピ

1
そうでない場合は、メソッドシグネチャ引数を作成します。これは、他のプログラマーによって維持されているメソッドに多くの依存関係がある場合に特に役立ちます。これは、人々が互いの邪魔にならないようにするのに役立ちます。+1
jmort253

1
この答えには同意しますが、他のアプローチ(「最小データ」)望ましい2つの状況にフラグを立てさせます。(1)メソッドの結果をキャッシュしたい。オブジェクト全体をキャッシュキーとして使用しないか、呼び出しのたびにオブジェクトのプロパティをチェックする必要があります。(2)メソッド/関数を非同期に実行する必要があります。これには、引数のシリアル化や、テーブルへの格納などが含まれます。保留中のジョブを管理するときに、オブジェクトBLOBよりも簡単かつ簡単に整数を検索できます。
GladstoneKeep

原始的な強迫観念のアンチパターンは、単体テストにとって恐ろしいものです。
サミュエル

82

最初の署名は、Userオブジェクト自体にユーザー関連のロジックをカプセル化できるため、優れています。コードベースについてばらまかれる「id、name、password」タプルの構築のためのロジックを持つことは有益ではありません。さらに、isAdmin関数が複雑になります。たとえば、にid一致しないを渡すとnameどうなりますか?特定のユーザーが、パスワードを知らないコンテキストの管理者であるかどうかを確認したい場合どうしますか?

さらに、追加の引数を指定したスタイルを支持する3番目のポイントに関するメモとして。「関数内のコードが少なくなります」が、そのロジックはどこに行くのでしょうか?ただ消えることはできません!代わりに、関数が呼び出されるすべての場所に広がります。1つの場所に5行のコードを保存するには、関数の使用ごとに5行を支払いました。


45
あなたの論理に従えuser.IsAdmin()ば、ずっと良くなることはないでしょうか?
アンデルスアルピ

13
そのとおり。
-asthasr

6
@AndersHolmströmそれは、ユーザーが一般的な管理者であるか、現在のページ/フォームの管理者であるかをテストするかどうかに依存します。一般的には、はいは良いでしょうがIsAdmin、特定のフォームまたは機能のみをテストする場合は、ユーザーとは無関係である必要があります
レイチェル

40
@Anders:いいえ。ユーザーは、管理者であるかどうかを決定するべきではありません。特権またはセキュリティシステムが必要です。しっかりクラスに結合されているものは、それ、そのクラスの一部にするために、良いアイデアであることを意味しません
マージャンVenema氏

11
@MarjanVenema-インスタンスが管理者であるかどうかをUserクラスが「決定」してはならないという考えは、ちょっとしたカルトカルティックなものだと思います。あなたは本当にそんな一般化をそんなに強くすることはできません。ニーズが複雑な場合は、別の「システム」にカプセル化することが有益かもしれませんが、KISS + YAGNIを引用して、一般的なルールとしてではなく、実際にその複雑さに直面する決定をしたいと思います:-)。また、ロジックがユーザーの「一部」ではない場合でも、より複雑なシステムに委任するだけのパススルーをユーザーに持たせることは、実用上の問題として依然として有用であることに注意しください。
イーモンネルボンヌ

65

最初の、しかし、他の人が与えた理由のためだけではありません。

public bool IsAdmin(User user);

これはタイプセーフです。特にUserは自分で定義した型なので、引数を転置したり切り替えたりする機会はほとんど、またはまったくありません。これは明らかにユーザーに対して機能し、intおよび2つの文字列を受け入れる汎用関数ではありません。これは、コードのように見えるJavaやC#のようなタイプセーフな言語を使用するポイントの大きな部分です。特定の言語について質問している場合、その言語のタグを質問に追加できます。

public bool IsAdmin(int id, string name, string password);

ここでは、intと2つの文字列で対応します。名前とパスワードを転置できないのは何ですか?2番目の方法は、使用する作業が多く、エラーが発生する可能性が高くなります。

おそらく、両方の関数は、関数内(最初の場合)または関数を呼び出す前(2番目)にuser.getId()、user.getName()、およびuser.getPassword()を呼び出す必要があるため、結合の量はどちらの方法でも同じです。実際、この関数はユーザーでのみ有効であるため、Javaではすべての引数を削除し、これをユーザーオブジェクトのインスタンスメソッドにします。

user.isAdmin();

この関数は既にユーザーと密接に結合されているため、ユーザーの一部に含めることは理にかなっています。

PSこれは単なる例に過ぎないはずですが、パスワードを保存しているようです。代わりに、暗号で保護されたパスワードハッシュのみを保存する必要があります。ログイン時に、指定されたパスワードは同じ方法でハッシュされ、保存されたハッシュと比較されます。平文でパスワードを送信することは避けてください。このルールに違反し、isAdmin(int Str Str)がユーザー名をログに記録し、コードで名前とパスワードを置き換えた場合、代わりにパスワードがログに記録される可能性があり、セキュリティ上の問題が発生します(ログにパスワードを書き込まないでください) )そして、コンポーネントの代わりにオブジェクトを渡す(またはクラスメソッドを使用する)ことを支持する別の引数です。


11
ユーザーは、管理者であるかどうかを決定するべきではありません。特権またはセキュリティシステムが必要です。クラスに密接に結合されているからといって、それをそのクラスの一部にすることは良い考えではありません。
マルジャンヴェネマ

6
@MarjanVenemaユーザーは何も決定していません。ユーザーオブジェクト/テーブルには、各ユーザーに関するデータが格納されます。実際のユーザーは、自分に関するすべてを変更することはできません。ユーザーが許可されているものを変更した場合でも、電子メールアドレスまたはユーザーIDまたはパスワードを変更すると、電子メールのようなセキュリティアラートがトリガーされる場合があります。特定のオブジェクトタイプに関するすべてをユーザーが魔法のように変更できるシステムを使用していますか?私はそのようなシステムに精通していません。
グレンペターソン

2
@GlenPeterson:あなたは故意に私を誤解していますか?私がユーザーと言ったとき、私はもちろんユーザークラスを意味しました。
マルジャンヴェネマ

2
@GlenPeterson完全にトピックから外れていますが、ハッシュと暗号化機能の複数のラウンドはデータを弱めます。
ガスドール

3
@MarjanVenema:私は意図的に難しくはありません。私たちは非常に異なる視点を持っていると思うので、あなたの理解を深めたいと思います。これについてよりよく議論できる新しい質問をオープンしました:Programmers.stackexchange.com/questions/216443/…–
グレンペターソン

6

選択した言語が値による構造を渡さない場合、(User user)バリアントはより良いようです。いつかユーザー名を破棄し、ID /パスワードを使用してユーザーを識別することにした場合はどうなりますか?

また、このアプローチは必然的に、長くて誇張されたメソッド呼び出し、または不整合につながります。3つの引数を渡しても大丈夫ですか?5はどうですか?または6?オブジェクトの受け渡しがリソースの点で安価な場合(おそらく3つの引数を渡すよりもさらに安い場合)、なぜそれを考えるのでしょうか?

そして、2番目のアプローチが読者にもっと情報を提供することに同意します-直感的に、メソッド呼び出しは「与えられたID /名前/パスワードの組み合わせに管理者権限があるかどうか」ではなく「ユーザーに管理者権限があるかどうか」を尋ねています。

したがって、Userオブジェクトを渡すと大きな構造がコピーされない限り、最初のアプローチはよりクリーンで論理的です。


3

私はおそらく両方を持っているでしょう。外部APIとしての最初のものであり、ある程度の安定性が期待されています。2番目は、外部APIによって呼び出されるプライベート実装です。

後でチェックルールを変更する必要がある場合は、外部から呼び出された新しい署名を使用して新しいプライベート関数を記述するだけです。

これには、内部実装を簡単に変更できるという利点があります。また、両方の機能を同時に使用可能にし、外部コンテキストの変更に応じてどちらか一方を呼び出す必要がある場合もあります(たとえば、異なるフィールドを持つ古いユーザーと新しいユーザーが共存する場合があります)。

質問のタイトルに関しては、どちらの場合も、最低限必要な情報を提供しています。ユーザーは、情報を含む最小のアプリケーション関連データ構造のようです。id、password、nameの3つのフィールドは、実際に必要な最小の実装データのようですが、実際にはアプリケーションレベルのオブジェクトではありません。

言い換えれば、データベースを扱っている場合、ユーザーはレコードになり、ID、パスワード、およびログインはそのレコードのフィールドになります。


プライベートメソッドの理由がわかりません。なぜ両方を維持するのですか?プライベートメソッドに別の引数が必要な場合は、パブリック引数を変更する必要があります。そして、あなたは公開されたものを通してそれをテストします。いいえ、後で両方が必要になることは通常ありません。ここで推測しています-YAGNI。
ピョートルペラ

たとえば、ユーザーデータ構造には最初から他のフィールドがないと仮定しています。通常はそうではありません。2番目の関数は、ユーザーの内部部分が管理者であるかどうかを確認するためにチェックされます。典型的なユースケースは次のようなものです:ユーザーの作成時間が特定の日付より前の場合、新しいルールを呼び出さない場合は古いルールを呼び出します(ユーザーの他の部分に依存するか、同じに依存するが別のルールを使用します)。このようにコードを分割すると、最小限の概念APIと最小限の実装関数という2つの最小限の機能が得られます。
クリス

言い換えれば、このようにすると、ユーザーのどの部分が使用されているかどうかが内部関数シグネチャからすぐにわかります。運用コードでは、それは常に明らかではありません。私は現在、数年のメンテナンス履歴を持つ大規模なコードベースに取り組んでおり、この種の分割は長期のメンテナンスに非常に役立ちます。コードを維持するつもりがない場合、それは実際には役に立たない(しかし、安定した概念APIを提供することもおそらく役に立たない)。
クリス

別の言葉:私は確かに、外部メソッドを通して内部メソッドを単体テストしません。それは悪いテスト方法です。
クリス

1
これは良い習慣であり、名前があります。これは「単一責任原則」と呼ばれます。クラスとメソッドの両方に適用できます。メソッドに単一の責任を持たせることは、よりモジュール化された保守可能なコードを作成するための良い方法です。この場合、1つのタスクはユーザーオブジェクトからデータの基本セットを選択し、別のタスクはその基本データセットをチェックして、ユーザーが管理者であるかどうかを確認します。この原則に従うことにより、メソッドを作成してコードの重複を回避できるというボーナスが得られます。これは、@ krissが述べているように、より優れた単体テストが得られることも意味します。
ディエゴレイメンデス14年

3

クラス(参照型)をメソッドに送信することには1つのマイナス点があります。それは、Mass-Assignment Vulnerabilityです。IsAdmin(User user)メソッドの代わりにメソッドがあると想像してくださいUpdateUser(User user)

場合はUser、クラスと呼ばれるブール型プロパティを持っているIsAdmin、と私は私のメソッドの実行中にそれをチェックしない場合、私は大量の割り当てに対して脆弱です。GitHubは、2012年にこのまさに署名によって攻撃されました。


2

私はこのアプローチで行きます:

public bool IsAdmin(this IUser user) { /* ... */ }

public class User: IUser { /* ... */ }

public interface IUser
{
    string Username {get;}
    string Password {get;}
    IID Id {get;}
}
  • インターフェイスタイプをこの関数のパラメーターとして使用すると、ユーザーオブジェクトを渡して、テスト用の模擬ユーザーオブジェクトを定義でき、この関数の使用と保守の両方に関して柔軟性が高まります。

  • this関数をすべてのIUserクラスの拡張メソッドにするためのキーワードも追加しました。この方法により、よりオブジェクト指向の方法でコードを記述し、Linqクエリでこの関数を使用できます。それでも、IUserとUserでこの関数を定義する方が簡単ですが、このメソッドをそのクラスの外部に配置することに決めた理由があると思いますか?

  • IDのプロパティを再定義できるため、IID代わりにカスタムインターフェイスを使用しintます。例えば、あなたは今までから変更する必要があるのintに対してlongGuidまたは何か他のもの。それはおそらくやり過ぎですが、初期の決定に縛られないように物事を柔軟にしようとすることは常に良いことです。

  • 注意:IUserオブジェクトは、異なる名前空間やUserクラスのアセンブリにある可能性があるため、ユーザーコードとIsAdminコードを完全に分離して、1つの参照ライブラリのみを共有するオプションがあります。まさにあなたがそこで行うことは、これらのアイテムがどのように使用されるのかを疑うための呼び出しです。


3
IUserはここでは役に立たず、複雑さを追加するだけです。テストで実際のユーザーを使用できなかった理由がわかりません。
ピョートルペラ

1
わずかな労力で将来の柔軟性が追加されるため、現在価値を追加することなく、害はありません。個人的には、すべてのシナリオの将来の可能性を考える必要がないため、常にこれを行うことを好みます(要件の予測不可能な性質を考えると、これはほとんど不可能であり、部分的に良い答えを得るのにはるかに時間がかかりますインターフェイスに固執する)。
JohnLBevan

@Periの実際のUserクラスは作成が複雑な場合があります。インターフェイスを取得することで、モックを作成できるため、テストをシンプルに保つことができます
jk。

@JohnLBevan:ヤグニ!!!
ピョートルペラ

@jk .:ユーザーを構築するのが難しい場合、何かがおかしい
Piotr Perak

1

免責事項:

返信では、スタイルの問題に焦点を当て、セキュリティの観点から、ID、ログイン、およびプレーンパスワードが使用する適切なパラメーターセットであるかどうかを忘れます。ユーザーの特権について把握するために使用している基本的なデータセットに対する私の答えを推定できるはずです...

またuser.isAdmin()、と同等であると考えIsAdmin(User user)ます。それはあなたが選ぶべき選択です。

応答:

私の推奨事項は、どちらかのユーザーのみです。

public bool IsAdmin(User user);

または、次の行に沿って両方を使用します。

public bool IsAdmin(User user); // calls the private method below
private bool IsAdmin(int id, string name, string password);

パブリックメソッドの理由:

パブリックメソッドを記述するときは、通常、それらがどのように使用されるかを考えるのが良いでしょう。

この特定のケースでは、通常このメソッドを呼び出す理由は、特定のユーザーが管理者であるかどうかを把握するためです。それはあなたが必要とする方法です。各呼び出し元が送信する適切なパラメーターを選択する必要はありません(コードの重複によるミスのリスクもあります)。

プライベートメソッドの理由:

必要なパラメーターの最小セットのみを受け取るプライベートメソッドは、良いことです。時々そんなにありません。

これらのメソッドを分割する利点の1つは、同じクラスの複数のパブリックメソッドからプライベートバージョンを呼び出すことができることです。あなたがのためにわずかに異なるロジックを持つように(何らかの理由で)をしたかった場合例えばLocaluserRemoteUser(?多分、リモート管理者をオン/オフにする設定があります):

public bool IsAdmin(Localuser user); // Just call private method
public bool IsAdmin(RemoteUser user); // Check if setting is on, then call private method
private bool IsAdmin(int id, string name, string password);

さらに、何らかの理由でprivateメソッドをpublicにする必要がある場合は...できます。に変更privateするのと同じくらい簡単publicです。この場合、それほど大きな利点とは思えないかもしれませんが、実際にはそうなる場合があります。

また、単体テストを行うと、よりアトミックなテストを行うことができます。通常、このメソッドでテストを実行するために完全なユーザーオブジェクトを作成する必要がないのは非常に便利です。また、完全なカバレッジが必要な場合は、両方の通話をテストできます。


0
public bool IsAdmin(int id, string name, string password);

リストされた理由により悪い。それは意味しません

public bool IsAdmin(User user);

自動的に良好です。:特に、ユーザーは、のようなメソッドを持つオブジェクトである場合getOrders()getFriends()getAdresses()、...

ID、名前、パスワードのみを含むUserCredentialsタイプでドメインをリファクタリングし、不要なユーザーデータすべてではなくIsAdminに渡すことを検討することもできます。

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