6

次のバグが私のコードで非常に頻繁に発生していることに気づき、それを回避するための優れた戦略を誰かが知っているかどうか疑問に思いました。

次のようなクラスを想像してください。

public class Quote
{
   public decimal InterestRate { get; set; }
}

ある時点で、次のように利率を利用する文字列を作成します。

public string PrintQuote(Quote quote)
{
    return "The interest rate is " + quote.InterestRate;
}

後日、InterestRate プロパティを 10 進数から独自のクラスにリファクタリングしたと想像してください。

public class Quote
{
    public InterestRate InterestRate { get; set; }
}

...しかし、InterestRate クラスのToStringメソッドをオーバーライドするのを忘れたとしましょう。InterestRate プロパティのすべての使用法を注意深く調べない限り、ある時点で文字列に変換されていることに気付かないでしょう。コンパイラは確かにこれを検出しません。私の救世主の唯一のチャンスは、統合テストによるものです。

次にPrintQuoteメソッドを呼び出すと、次のような文字列が返されます。

「利率は Business.Finance.InterestRate です」 .

ああ。どうすればこれを回避できますか?

4

8 に答える 8

10

IntrestRate クラスで ToString のオーバーライドを作成する。

于 2009-06-29T00:46:50.007 に答える
4

この種の問題を防ぐ方法は、完全にすべてのクラス メンバーに対して単体テストを実行することです。したがって、これにはPrintQuote(Quote quote)メソッドが含まれます。

[TestMethod]
public void PrintQuoteTest()
{
    quote = new Quote();
    quote.InterestRate = 0.05M;
    Assert.AreEqual(
        "The interest rate is 0.05",
        PrintQuote(quote));
}

この場合、新しい InterestRate クラスと System.Decimal の間で暗黙的な変換を定義しない限り、この単体テストは実際にはコンパイルされなくなります。しかし、それは間違いなく信号です!InterestRate クラスと System.Decimal の間の暗黙的な変換を定義したものの、ToStringメソッドをオーバーライドするのを忘れた場合、この単体テストはコンパイルされますが、Assert.AreEqual() 行で (正しく) 失敗します。

絶対にすべてのクラス メンバーに対して単体テストを実行する必要性は、いくら強調してもしすぎることはありません。

于 2009-06-29T01:35:50.173 に答える
3

ぎくしゃくするのではなく、クラスを作成するたびにテストケースを作成します。あなたやあなたのプロジェクトに参加している他の人たちの見落としに立ち入り、それを避けるのは良い習慣です。

于 2009-06-29T01:20:57.443 に答える
3

ToString のオーバーライドの作成は、すべてではないにしてもほとんどのクラスに対して行うことの 1 つにすぎません。確かに、すべての「値」クラスに対して。


ReSharper が多くの定型コードを生成することに注意してください。から:

public class Class1
{
    public string Name { get; set; }
    public int Id { get; set; }
}

等価メンバーの生成、書式設定メンバーの生成、およびコンストラクターの生成を実行した結果は次のとおりです。

public class Class1 : IEquatable<Class1>
{
    public Class1(string name, int id)
    {
        Name = name;
        Id = id;
    }

    public bool Equals(Class1 other)
    {
        if (ReferenceEquals(null, other))
        {
            return false;
        }
        if (ReferenceEquals(this, other))
        {
            return true;
        }
        return Equals(other.Name, Name) && other.Id == Id;
    }

    public override string ToString()
    {
        return string.Format("Name: {0}, Id: {1}", Name, Id);
    }

    public override bool Equals(object obj)
    {
        if (ReferenceEquals(null, obj))
        {
            return false;
        }
        if (ReferenceEquals(this, obj))
        {
            return true;
        }
        if (obj.GetType() != typeof (Class1))
        {
            return false;
        }
        return Equals((Class1) obj);
    }

    public override int GetHashCode()
    {
        unchecked
        {
            return ((Name != null ? Name.GetHashCode() : 0)*397) ^ Id;
        }
    }

    public static bool operator ==(Class1 left, Class1 right)
    {
        return Equals(left, right);
    }

    public static bool operator !=(Class1 left, Class1 right)
    {
        return !Equals(left, right);
    }

    public string Name { get; set; }
    public int Id { get; set; }
}

1 つのバグがあることに注意してください。デフォルトのコンストラクターを作成するように提案されているはずです。ReSharper でさえ完璧にはなりません。

于 2009-06-29T00:54:58.260 に答える
1

まあ、他の人が言っているように、あなたはそれをしなければなりません。しかし、ここにあなたがそれを確実に行うのを助けるためのいくつかのアイデアがあります:

1)toStringをオーバーライドし、たとえば例外をスローするすべての値クラスのベースオブジェクトを使用します。これは、再度オーバーライドするように通知するのに役立ちます。

2)FXCop(無料のMicrosoft静的コード分析ツール)のカスタムルールを作成して、特定の種類のクラスのtoStringメソッドをチェックします。どのタイプのクラスがtoStringをオーバーライドする必要があるかを決定する方法は、学生の演習として残されています。:)

于 2009-06-29T01:14:06.067 に答える
0

非常に異なる観点から、すべての ToString'ing をアプリケーションの別の問題に任せることができます。StatePrinter ( https://github.com/kbilsted/StatePrinter ) は、印刷するタイプに応じてデフォルトを使用または構成できる API の 1 つです。

var car = new Car(new SteeringWheel(new FoamGrip("Plastic")));
car.Brand = "Toyota";

それからそれを印刷する

StatePrinter printer = new StatePrinter();
Console.WriteLine(printer.PrintObject(car));

次の出力が得られます

new Car() {
    StereoAmplifiers = null
    steeringWheel = new SteeringWheel()
    {
        Size = 3
        Grip = new FoamGrip()
        {
            Material = ""Plastic""
        }
        Weight = 525
    }
    Brand = ""Toyota"" }

IValueConverter 抽象化を使用すると、型がどのようにプリンタに出力されるかを定義でき、FieldHarvester を使用すると、どのフィールドを文字列に含めるかを定義できます。

于 2014-07-12T20:21:34.323 に答える
0

あなたの例のように、 として静的に型指定されたもので ToString が呼び出された場合、または anがキャストされてすぐにstring.Format のようなものへのパラメーターとして使用されるInterestRate特定の関連ケースでは、問題をおそらく検出できます静的分析。必要なものに近いカスタム FxCop ルールを検索するか、独自のルールを作成することができます。InterestRateObject

分析を壊す十分に動的な呼び出しパターンを考案することは常に可能であり、おそらく非常に複雑なものでさえないことに注意してください;)、しかし、最も簡単な結果を見つけることは十分に簡単なはずです。

とはいえ、徹底的なテストがおそらくこの特定の問題への最良のアプローチであるという他のコメンターの一部に同意します。

于 2009-06-30T23:33:07.733 に答える
-1

率直に言って、あなたの質問に対する答えは、最初の設計に欠陥があったということです。最初に、プロパティをプリミティブ型として公開しました。これは間違っていると考える人もいます。結局のところ、あなたのコードはこれを許可しています...

var double = quote.InterestRate * quote.InterestRate;

それに関する問題は、結果の単位は何ですか? 関心^2? 設計の 2 番目の問題は、暗黙的な ToString() 変換に依存していることです。暗黙的な変換に依存する問題は、C++ でよりよく知られていますが (たとえば)、ご指摘のとおり、C# でも問題が発生する可能性があります。おそらく、あなたのコードがもともと...

return "The interest rate is " + quote.InterestRate.ToString();

...リファクタリングで気づいたでしょう。肝心なのは、元の設計に設計上の問題がある場合、それらはリファクタリングで捕捉される場合とそうでない場合があるということです。最善の策は、そもそもそれらを行わないことです。

于 2009-06-29T01:06:59.390 に答える