5

趣味のプロジェクトでの思考実験として、私はこの種の微妙なバグ/タイプミスが起こらないようにする方法を考えていました:

public void MyMethod(int useCaseId)
{
    // Do something with the useCaseId
}

public void SomeOtherMethod()
{
    int userId = 12;
    int useCaseId = 15;
    MyMethod(userId); // Ooops! Used the wrong value!
}

コンパイル時にエラーが発生しないため、このバグを見つけるのは難しく、実行時に例外が発生するとは限りません。「予想外の結果」が得られるだけです。

これを簡単な方法で解決するために、空の列挙型定義を使用して実験しました。ユーザー ID を効果的にデータ型にする (クラスや構造体にまでは行かない):

public enum UseCaseId { // Empty… }

public enum UserId { // Empty… }

public void MyMethod(UseCaseId useCaseId)
{
   // Do something with the useCaseId
}

public void SomeOtherMethod()
{
   UserId userId = (UserId)12;
   UseCaseId useCaseId = (UseCaseId)15;
   MyMethod(userId); // Compile error!!
}

どう思いますか?

4

6 に答える 6

6

保持する新しい型を作成するのが面倒な場合はUserIdUseCaseIdそれらを単純なクラスにし、からの暗黙的な変換演算子を使用して、必要なint構文を与えることができます。

public class UserId
{
    public int Id { get; private set; }

    public UserId(int id)
    {
       id_ = id;
    }

    public static implicit operator UserId(int id)
    {
        return new UserId(id);
    }

    public static void Main()
    {
        UserId id1 = new UserId(1);
        UserId id2 = 2;
    }
}

そうすれば、コードにキャストを散らかすことなく、型の安全性を得ることができます。

于 2010-08-10T11:56:18.807 に答える
2

しばらくの間、似たようなことをしたいと思っていたので、あなたの投稿で次のことを試すようになりました。

 public class Id<T> {
    private readonly int _Id;

    private Id(int id) {
        _Id = id;
    }

    public static implicit operator int(Id<T> id) {
        return id._Id;
    }

    public static implicit operator Id<T>(int id) {
        return new Id<T>(id);
    }
}

次のように使用できます

public void MyMethod(Id<UseCase> useCaseId)
{
   // Do something with the useCaseId
}

public void SomeOtherMethod()
{
   Id<User> userId = 12;
   Id<UseCase> useCaseId = 15;
   MyMethod(userId); // Compile error!!
}

このタイプの Id オブジェクトを渡す方が、ドメイン オブジェクト全体を渡すよりも優れていると思います。呼び出し元と呼び出し先の間の契約がより明確になるからです。ID だけを渡すと、呼び出し先がオブジェクトの他のプロパティにアクセスしていないことがわかります。

于 2011-09-24T00:57:24.097 に答える
2

Haskell でこれをやりたい場合は、次のようにします。

data UserId    = UserId    Int
data UseCaseId = UseCaseId Int

UserIdこのように、関数はInt の代わりにa を受け入れ、 aの作成UserIdは常に明示的です。次のようになります。

doSomething (UserId 12) (UseCaseId 15)

これは、Int をラップする型を作成する Niall C. のソリューションに似ています。ただし、型ごとに実装するのに 10 行もかからなかったらいいのにと思います。

于 2010-08-12T02:43:18.607 に答える
1

個人的には、正直に言う必要はないと思います。
ロジックを適切に実装するのは開発者の責任であり、そのようなバグをコンパイル時のエラーに頼ることはできません。

于 2010-08-10T11:49:58.190 に答える
1

ゲームには遅れていますが、FWIW ... codeplex のこのプロジェクトでは、Angle、Azimuth、Distance、Latitude、Longitude、Radian などのいくつかの「強く型付けされた」スカラーが定義されています。実際、これらのそれぞれは、単一のスカラー メンバーを持つ構造体です。そしてそれを「正しく」操作するためのいくつかのメソッド/プロパティ/コンストラクタ。これらが参照型ではなく値型であるという事実を除けば、これらのそれぞれをクラスにすることとそれほど違いはありません。このフレームワークを使用したことはありませんが、これらの型をファースト クラスの市民にすることの価値は理解できます。

それが最終的に良いアイデアであるかどうかはわかりませんが、型の安全性(および値の安全性)を確保して(元の例と同様に)次のようなコードを記述できると便利なようです。

Angle a = new Angle(45); //45 degrees
SomeObject o = new SomeObject();
o.Rotate(a); //Ensure that only Angle can be sent to Rotate

また

Angle a = (Angle)45.0;

また

Radians r = Math.PI/2.0;
Angle a = (Angle)r;

このパターンは、ドメインに値のセマンティクスを持つ多くのスカラー "型" があり、これらの型のインスタンスが多数存在する可能性がある場合に最も役立つようです。それぞれを単一のスカラーを持つ構造体としてモデル化すると、非常にコンパクトな表現が得られます (それぞれを本格的なクラスにする場合と比較して)。このレベルの抽象化 (単に「ネイキッド」スカラーを使用してドメイン値を表すのではなく) と離散性を実装するのは多少面倒かもしれませんが、結果として得られる API は「正しく」使用する方がはるかに簡単であるように思われます。

于 2010-12-13T18:51:28.667 に答える
0

MyMethod で引数を検証し、エラー状態の場合に適切な例外を発生させたいと思います。

public void MyMethod(int useCaseId)
{
    if(!IsValidUseCaseId(useCaseId))
    {
         throw new ArgumentException("Invalid useCaseId.");
    }
    // Do something with the useCaseId
}

public bool IsValidUseCaseId(int useCaseId)
{
    //perform validation here, and return True or False based on your condition.
}

public void SomeOtherMethod()
{
    int userId = 12;
    int useCaseId = 15;
    MyMethod(userId); // Ooops! Used the wrong value!
}
于 2010-08-10T11:56:05.327 に答える