Autofixture: Generieren Sie ein Objekt mit komplexen Einschränkungen

Erstellt am 26. Aug. 2018  ·  8Kommentare  ·  Quelle: AutoFixture/AutoFixture

Ich versuche herauszufinden, wie AutoFixture am besten ein Objekt generiert, das im Konstruktor nicht triviale Einschränkungen aufweist. Nehmen wir zum Beispiel an, ich möchte eine PrimeNumber-Datenstruktur verwenden, die ein int akzeptiert und nur Primzahlen akzeptiert.

Was wäre der beste Ansatz, um eine Instanz dieser Art von Struktur in AutoFixture zu generieren? Ich meine, ich werde natürlich eine Anpassung schreiben, aber was würdest du da einfügen?

  • Würden Sie zufällige Ints generieren und eine Schleife erstellen, bis eine von ihnen eine Primzahl ist (oder natürlich einen Primzahlgenerierungsalgorithmus ausführen)? Das könnte für diese Art von Einschränkung akzeptabel sein, aber wenn die Einhaltung der Einschränkung schwieriger wäre, würde dies schnell kostspielig.
  • Würden Sie eine endliche Liste einiger akzeptabler Werte bereitstellen?

Nehmen wir außerdem an, dass ich jetzt versuche, eine Instanz von etwas zu erstellen, das mehrere Argumente akzeptiert, die theoretisch einzeln zufällig sein können, aber das wird eine Validierung dazwischen durchführen (zum Beispiel kann argA nur in diesem Wertebereich liegen, wenn argB ist wahr, und argC muss je nach argA-Wert unterschiedliche Validierungsregeln erfüllen, oder die argC.X-Eigenschaft muss mit der argA.X-Eigenschaft übereinstimmen, so etwas).

Was würden Sie in diesem Fall tun?

  • Eine Anpassung zum Erstellen einer gültigen Instanz jedes Typs (ohne sich um eine externe Validierung zu kümmern) und eine andere, die versuchen würde, das große komplexe Objekt zu erstellen und eine Schleife zu erstellen, bis eine gültige Instanz erstellt wird?
  • Geben Sie wieder eine Liste endlicher akzeptabler Werte an, die eine starke Einschränkung der Amplitude der Möglichkeiten darstellen können
  • Stellen Sie eine spezielle Anpassung bereit, die nur Instanzen von Argumenten erstellt, die zur Validierung des komplexen Objekts passen

Und schließlich (ich hätte mehrere Probleme erstellen können, aber ich hatte das Gefühl, dass all diese Themen unterschiedliche Aspekte desselben Problems sind), diese Art von Anpassungen jedes Mal erstellen und anwenden zu müssen, wenn wir eine neue Klasse hinzufügen, und diese Anpassungen jedes Mal beibehalten zu müssen Die Änderung der Validierungsregeln scheint eine Menge Arbeit zu sein. Wenden Sie einige Techniken an, um dies zu verringern?

Vielen Dank, sorry für den langen und hoffentlich nicht zu chaotischen Beitrag.

question

Hilfreichster Kommentar

Guten Tag! Zum Schluss habe ich der Antwort ein bisschen Typ zugeteilt - sorry für die sehr verspätete Antwort 😊

Beachten Sie zunächst, dass der Kern von AutoFixture ziemlich einfach ist und wir keine integrierte Unterstützung für die komplexen Bäume mit Einschränkungen haben. Kurz gesagt, die Erstellungsstrategie ist wie folgt:

  • Suchen Sie nach einem öffentlichen Konstruktor oder einer statischen Factory-Methode (statische Methode, die eine Instanz des aktuellen Typs zurückgibt).
  • Lösen Sie die Konstruktorargumente auf und aktivieren Sie die Instanz.
  • Füllen Sie die beschreibbaren öffentlichen Eigenschaften und Felder mit generierten Werten.

Mit dem aktuellen Ansatz können Sie, wie Sie bereits festgestellt haben, Abhängigkeitsbeschränkungen nicht irgendwie kontrollieren.

Wir haben einige Anpassungspunkte, um anzugeben, wie die einzelnen Typen erstellt werden, aber sie sind relativ einfach und unterstützen diese komplexen Regeln nicht.

Was wäre der beste Ansatz, um eine Instanz dieser Art von Struktur in AutoFixture zu generieren? Ich meine, ich werde natürlich eine Anpassung schreiben, aber was würdest du da einfügen?

  • Würden Sie zufällige Ints generieren und eine Schleife erstellen, bis eine von ihnen eine Primzahl ist (oder natürlich einen Primzahlgenerierungsalgorithmus ausführen)? Das könnte für diese Art von Einschränkung akzeptabel sein, aber wenn die Einhaltung der Einschränkung schwieriger wäre, würde dies schnell kostspielig.

  • Würden Sie eine endliche Liste einiger akzeptabler Werte bereitstellen?

Nun, leider sehe ich hier keine Wunderwaffe und die Vorgehensweise hängt von der Situation ab. Wenn Sie sich nicht darauf verlassen, dass der Wert zu zufällig ist oder ein einzelner SUT nur 1-2 Primzahlen verbraucht, ist es möglicherweise in Ordnung, Primzahlen fest zu codieren und daraus auszuwählen (wir haben ElementsBulider<> eingebauten Helfer für diese Fälle). Wenn Sie andererseits eine große Liste von Primzahlen benötigen und mit langen Primzahlenfolgen arbeiten, ist es wahrscheinlich besser, einen Algorithmus zu programmieren, um sie dynamisch zu generieren.

Nehmen wir außerdem an, dass ich jetzt versuche, eine Instanz von etwas zu erstellen, das mehrere Argumente akzeptiert, die theoretisch einzeln zufällig sein können, aber das wird eine Validierung dazwischen durchführen (zum Beispiel kann argA nur in diesem Wertebereich liegen, wenn argB ist wahr, und argC muss je nach argA-Wert unterschiedliche Validierungsregeln erfüllen, oder die argC.X-Eigenschaft muss mit der argA.X-Eigenschaft übereinstimmen, so etwas).

Was würden Sie in diesem Fall tun?

Wirklich eine gute Frage und leider lässt sich AutoFixture nicht auf eine nette Art und Weise out of the box lösen. Normalerweise versuche ich, Anpassungen für jeden Typ zu isolieren, sodass die Anpassung für einen Typ nur die Erstellung für einen einzelnen Typ steuert. Aber in meinen Fällen sind Typen unabhängig und offensichtlich wird es in Ihrem Fall nicht gut funktionieren. Außerdem bietet AutoFixture keinen standardmäßigen Kontext. Wenn Sie also eine Anpassung für einen bestimmten Typ schreiben, können Sie den Kontext, in dem Sie ein Objekt (intern als Muster bezeichnet) erstellen, nicht klar verstehen.

Überhaupt würde ich sagen, dass ich normalerweise die folgende Strategie empfehle:

  • Versuchen Sie, Anpassungen für jeden Typ so zu erstellen, dass sie nur die Erstellung eines einzelnen Objekttyps steuern.
  • Wenn Sie Abhängigkeiten mit bestimmten Einschränkungen erstellen müssen, ist es besser, diese Abhängigkeiten auch in der Anpassung zu aktivieren. Wenn Ihre Abhängigkeit veränderlich ist, können Sie AutoFixture bitten, die Abhängigkeit für Sie zu erstellen und sie später so zu konfigurieren, dass sie kompatibel wird.

Auf diese Weise widersprechen Sie der internen Architektur nicht zu sehr und es wird klar, wie es funktioniert. Natürlich ist dieser Weg möglicherweise sehr ausführlich.

Wenn Fälle mit komplexen Einschränkungen nicht so häufig sind, reichen die vorhandenen Funktionen möglicherweise aus. Aber wenn Ihr Domänenmodell wirklich voller solcher Fälle ist, ist AutoFixture möglicherweise nicht das beste Werkzeug für Sie. Wahrscheinlich gibt es bessere Tools auf dem Markt, die es ermöglichen, solche Probleme auf elegante Weise zu lösen. Erwähnenswert ist natürlich, dass AutoFixture sehr flexibel ist und man fast alles überschreiben kann, man kann also jederzeit auf AutoFixture-Kern eine eigene DSL erstellen...

Lass uns auch @ploeh nach seinen Gedanken fragen. Normalerweise sind Marks Antworten tiefgründig und er versucht zuerst die Ursache zu finden, anstatt die Konsequenzen zu lösen 😅

Bei weiteren Fragen - bitte fragen! Ich werde sie immer gerne beantworten.

PS FWIW, ich habe beschlossen, Ihnen ein Beispiel zur Verfügung zu stellen, in dem ich versucht habe, mit AutoFixture zu spielen und ein ähnliches Problem zu lösen (ich habe versucht, es einfach zu halten, und es funktioniert in Ihrem Fall möglicherweise nicht vollständig):


Klicken Sie hier, um den Quellcode anzuzeigen

```c#
Verwenden des Systems;
Verwenden von AutoFixture;
mit AutoFixture.Xunit2;
mit Xunit;

Namensraum AutoFixturePlayground
{
öffentliche statische Klasse Util
{
public static bool IsPrime(int Zahl)
{
// Kopiert von https://stackoverflow.com/a/15743238/2009373

        if (number <= 1) return false;
        if (number == 2) return true;
        if (number % 2 == 0) return false;

        var boundary = (int) Math.Floor(Math.Sqrt(number));

        for (int i = 3; i <= boundary; i += 2)
        {
            if (number % i == 0) return false;
        }

        return true;
    }
}

public class DepA
{
    public int Value { get; set; }
}

public class DepB
{
    public int PrimeNumber { get; }
    public int AnyOtherValue { get; }

    public DepB(int primeNumber, int anyOtherValue)
    {
        if (!Util.IsPrime(primeNumber))
            throw new ArgumentOutOfRangeException(nameof(primeNumber), primeNumber, "Number is not prime.");

        PrimeNumber = primeNumber;
        AnyOtherValue = anyOtherValue;
    }
}

public class DepC
{
    public DepA DepA { get; }
    public DepB DepB { get; }

    public DepC(DepA depA, DepB depB)
    {
        if (depB.PrimeNumber < depA.Value)
            throw new ArgumentException("Second should be larger than first.");

        DepA = depA;
        DepB = depB;
    }

    public int GetPrimeNumber() => DepB.PrimeNumber;
}

public class Issue1067
{
    [Theory, CustomAutoData]
    public void ShouldReturnPrimeNumberFromDepB(DepC sut)
    {
        var result = sut.GetPrimeNumber();

        Assert.Equal(sut.DepB.PrimeNumber, result);
    }
}

public class CustomAutoData : AutoDataAttribute
{
    public CustomAutoData() : base(() =>
    {
        var fixture = new Fixture();

        // Add prime numbers generator, returning numbers from the predefined list
        fixture.Customizations.Add(new ElementsBuilder<PrimeNumber>(2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41));

        // Customize DepB to pass prime numbers only to ctor
        fixture.Customize<DepB>(c => c.FromFactory((PrimeNumber pn, int anyNumber) => new DepB(pn, anyNumber)));

        // Customize DepC, so that depA.Value is always less than depB.PrimeNumber
        fixture.Customize<DepC>(c => c.FromFactory((DepA depA, DepB depB, byte diff) =>
        {
            depA.Value = depB.PrimeNumber - diff;
            return new DepC(depA, depB);
        }));

        return fixture;
    })
    {
    }
}

/// <summary>
/// A helper type to represent a prime number, so that you can resolve prime numbers 
/// </summary>
public readonly struct PrimeNumber
{
    public int Value { get; }

    public PrimeNumber(int value)
    {
        Value = value;
    }

    public static implicit operator int(PrimeNumber prime) => prime.Value;
    public static implicit operator PrimeNumber(int value) => new PrimeNumber(value);
}

}
```

Alle 8 Kommentare

Sorry für die Funkstille. Wir leben und ich werde bald antworten - bin in diesen Tagen sehr beschäftigt mit meiner Hauptarbeit. Arbeite auch an der Veröffentlichung von NSubstitute v4, daher ist die Zeit sehr begrenzt.

Danke für die Geduld, bleib dran :wink:

Hi,
Gibt es dazu Neuigkeiten?
Kein Druck (ich kenne den Bohrer 😄 , außerdem blockiert er nicht wirklich, ich hätte nur gerne einen fundierten Rat), es geht nur darum, zu wissen, ob man etwas Sicht hat.
Danke vielmals!

Guten Tag! Zum Schluss habe ich der Antwort ein bisschen Typ zugeteilt - sorry für die sehr verspätete Antwort 😊

Beachten Sie zunächst, dass der Kern von AutoFixture ziemlich einfach ist und wir keine integrierte Unterstützung für die komplexen Bäume mit Einschränkungen haben. Kurz gesagt, die Erstellungsstrategie ist wie folgt:

  • Suchen Sie nach einem öffentlichen Konstruktor oder einer statischen Factory-Methode (statische Methode, die eine Instanz des aktuellen Typs zurückgibt).
  • Lösen Sie die Konstruktorargumente auf und aktivieren Sie die Instanz.
  • Füllen Sie die beschreibbaren öffentlichen Eigenschaften und Felder mit generierten Werten.

Mit dem aktuellen Ansatz können Sie, wie Sie bereits festgestellt haben, Abhängigkeitsbeschränkungen nicht irgendwie kontrollieren.

Wir haben einige Anpassungspunkte, um anzugeben, wie die einzelnen Typen erstellt werden, aber sie sind relativ einfach und unterstützen diese komplexen Regeln nicht.

Was wäre der beste Ansatz, um eine Instanz dieser Art von Struktur in AutoFixture zu generieren? Ich meine, ich werde natürlich eine Anpassung schreiben, aber was würdest du da einfügen?

  • Würden Sie zufällige Ints generieren und eine Schleife erstellen, bis eine von ihnen eine Primzahl ist (oder natürlich einen Primzahlgenerierungsalgorithmus ausführen)? Das könnte für diese Art von Einschränkung akzeptabel sein, aber wenn die Einhaltung der Einschränkung schwieriger wäre, würde dies schnell kostspielig.

  • Würden Sie eine endliche Liste einiger akzeptabler Werte bereitstellen?

Nun, leider sehe ich hier keine Wunderwaffe und die Vorgehensweise hängt von der Situation ab. Wenn Sie sich nicht darauf verlassen, dass der Wert zu zufällig ist oder ein einzelner SUT nur 1-2 Primzahlen verbraucht, ist es möglicherweise in Ordnung, Primzahlen fest zu codieren und daraus auszuwählen (wir haben ElementsBulider<> eingebauten Helfer für diese Fälle). Wenn Sie andererseits eine große Liste von Primzahlen benötigen und mit langen Primzahlenfolgen arbeiten, ist es wahrscheinlich besser, einen Algorithmus zu programmieren, um sie dynamisch zu generieren.

Nehmen wir außerdem an, dass ich jetzt versuche, eine Instanz von etwas zu erstellen, das mehrere Argumente akzeptiert, die theoretisch einzeln zufällig sein können, aber das wird eine Validierung dazwischen durchführen (zum Beispiel kann argA nur in diesem Wertebereich liegen, wenn argB ist wahr, und argC muss je nach argA-Wert unterschiedliche Validierungsregeln erfüllen, oder die argC.X-Eigenschaft muss mit der argA.X-Eigenschaft übereinstimmen, so etwas).

Was würden Sie in diesem Fall tun?

Wirklich eine gute Frage und leider lässt sich AutoFixture nicht auf eine nette Art und Weise out of the box lösen. Normalerweise versuche ich, Anpassungen für jeden Typ zu isolieren, sodass die Anpassung für einen Typ nur die Erstellung für einen einzelnen Typ steuert. Aber in meinen Fällen sind Typen unabhängig und offensichtlich wird es in Ihrem Fall nicht gut funktionieren. Außerdem bietet AutoFixture keinen standardmäßigen Kontext. Wenn Sie also eine Anpassung für einen bestimmten Typ schreiben, können Sie den Kontext, in dem Sie ein Objekt (intern als Muster bezeichnet) erstellen, nicht klar verstehen.

Überhaupt würde ich sagen, dass ich normalerweise die folgende Strategie empfehle:

  • Versuchen Sie, Anpassungen für jeden Typ so zu erstellen, dass sie nur die Erstellung eines einzelnen Objekttyps steuern.
  • Wenn Sie Abhängigkeiten mit bestimmten Einschränkungen erstellen müssen, ist es besser, diese Abhängigkeiten auch in der Anpassung zu aktivieren. Wenn Ihre Abhängigkeit veränderlich ist, können Sie AutoFixture bitten, die Abhängigkeit für Sie zu erstellen und sie später so zu konfigurieren, dass sie kompatibel wird.

Auf diese Weise widersprechen Sie der internen Architektur nicht zu sehr und es wird klar, wie es funktioniert. Natürlich ist dieser Weg möglicherweise sehr ausführlich.

Wenn Fälle mit komplexen Einschränkungen nicht so häufig sind, reichen die vorhandenen Funktionen möglicherweise aus. Aber wenn Ihr Domänenmodell wirklich voller solcher Fälle ist, ist AutoFixture möglicherweise nicht das beste Werkzeug für Sie. Wahrscheinlich gibt es bessere Tools auf dem Markt, die es ermöglichen, solche Probleme auf elegante Weise zu lösen. Erwähnenswert ist natürlich, dass AutoFixture sehr flexibel ist und man fast alles überschreiben kann, man kann also jederzeit auf AutoFixture-Kern eine eigene DSL erstellen...

Lass uns auch @ploeh nach seinen Gedanken fragen. Normalerweise sind Marks Antworten tiefgründig und er versucht zuerst die Ursache zu finden, anstatt die Konsequenzen zu lösen 😅

Bei weiteren Fragen - bitte fragen! Ich werde sie immer gerne beantworten.

PS FWIW, ich habe beschlossen, Ihnen ein Beispiel zur Verfügung zu stellen, in dem ich versucht habe, mit AutoFixture zu spielen und ein ähnliches Problem zu lösen (ich habe versucht, es einfach zu halten, und es funktioniert in Ihrem Fall möglicherweise nicht vollständig):


Klicken Sie hier, um den Quellcode anzuzeigen

```c#
Verwenden des Systems;
Verwenden von AutoFixture;
mit AutoFixture.Xunit2;
mit Xunit;

Namensraum AutoFixturePlayground
{
öffentliche statische Klasse Util
{
public static bool IsPrime(int Zahl)
{
// Kopiert von https://stackoverflow.com/a/15743238/2009373

        if (number <= 1) return false;
        if (number == 2) return true;
        if (number % 2 == 0) return false;

        var boundary = (int) Math.Floor(Math.Sqrt(number));

        for (int i = 3; i <= boundary; i += 2)
        {
            if (number % i == 0) return false;
        }

        return true;
    }
}

public class DepA
{
    public int Value { get; set; }
}

public class DepB
{
    public int PrimeNumber { get; }
    public int AnyOtherValue { get; }

    public DepB(int primeNumber, int anyOtherValue)
    {
        if (!Util.IsPrime(primeNumber))
            throw new ArgumentOutOfRangeException(nameof(primeNumber), primeNumber, "Number is not prime.");

        PrimeNumber = primeNumber;
        AnyOtherValue = anyOtherValue;
    }
}

public class DepC
{
    public DepA DepA { get; }
    public DepB DepB { get; }

    public DepC(DepA depA, DepB depB)
    {
        if (depB.PrimeNumber < depA.Value)
            throw new ArgumentException("Second should be larger than first.");

        DepA = depA;
        DepB = depB;
    }

    public int GetPrimeNumber() => DepB.PrimeNumber;
}

public class Issue1067
{
    [Theory, CustomAutoData]
    public void ShouldReturnPrimeNumberFromDepB(DepC sut)
    {
        var result = sut.GetPrimeNumber();

        Assert.Equal(sut.DepB.PrimeNumber, result);
    }
}

public class CustomAutoData : AutoDataAttribute
{
    public CustomAutoData() : base(() =>
    {
        var fixture = new Fixture();

        // Add prime numbers generator, returning numbers from the predefined list
        fixture.Customizations.Add(new ElementsBuilder<PrimeNumber>(2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41));

        // Customize DepB to pass prime numbers only to ctor
        fixture.Customize<DepB>(c => c.FromFactory((PrimeNumber pn, int anyNumber) => new DepB(pn, anyNumber)));

        // Customize DepC, so that depA.Value is always less than depB.PrimeNumber
        fixture.Customize<DepC>(c => c.FromFactory((DepA depA, DepB depB, byte diff) =>
        {
            depA.Value = depB.PrimeNumber - diff;
            return new DepC(depA, depB);
        }));

        return fixture;
    })
    {
    }
}

/// <summary>
/// A helper type to represent a prime number, so that you can resolve prime numbers 
/// </summary>
public readonly struct PrimeNumber
{
    public int Value { get; }

    public PrimeNumber(int value)
    {
        Value = value;
    }

    public static implicit operator int(PrimeNumber prime) => prime.Value;
    public static implicit operator PrimeNumber(int value) => new PrimeNumber(value);
}

}
```

Hallo @zvirja

Wow, danke für die ausführliche Antwort, das ist wirklich interessant. Ich muss ein paar Tests machen und abschätzen, was es wert ist oder nicht, aber alles in allem ist das großartig.

Ich glaube nicht, dass ich so viele Abhängigkeiten zu bewältigen habe, daher könnte Ihr Ansatz ein guter Weg sein. Wenn @ploeh noch etwas hinzuzufügen hat, würde ich mich natürlich geehrt fühlen 👌

Nochmals vielen Dank, mach weiter so!

Meine Erfahrung sowohl mit AutoFixture als auch mit eigenschaftsbasierten Tests ist, dass es im Wesentlichen zwei Möglichkeiten gibt, Probleme wie diese anzugehen:

  • Filtern
  • Algorithmische Erstellung

(Während ich schreibe, deutet meine Intuition darauf hin, dass dies _Katamorphismen_ bzw. _Anamorphismen_ sein könnten, aber ich muss noch etwas darüber nachdenken, also ist dies nebenbei hauptsächlich eine Anmerkung für mich.)

Wenn _die meisten_ zufällig generierten Werte alle Einschränkungen erfüllen würden, in die man passen muss, dann könnte die Verwendung eines vorhandenen Generators, aber das Wegwerfen gelegentlich ungeeigneter Werte der einfachste Weg sein, das Problem zu lösen.

Wenn andererseits ein Filter das Wegwerfen der meisten Zufallsdaten bedeuten würde, müssten Sie stattdessen einen Algorithmus entwickeln, der, vielleicht basierend auf zufälligen Startwerten, Werte generiert, die den fraglichen Einschränkungen entsprechen.

Vor einigen Jahren habe ich einen Vortrag gehalten, der einige einfache Beispiele für beide Ansätze im Kontext von FsCheck zeigte . Diese Präsentation ist eigentlich eine Weiterentwicklung eines Vortrags, der den gleichen Ansatz verfolgte, jedoch stattdessen mit AutoFixture. Leider existiert von diesem Gespräch keine Aufzeichnung.

Man könnte die Primzahlanforderung auf beide Arten angehen.

Der Filteransatz besteht darin, uneingeschränkte Zahlen zu generieren und dann Zahlen wegzuwerfen, bis Sie eine erhalten, die tatsächlich eine Primzahl ist.

Der algorithmische Ansatz besteht darin, einen Algorithmus wie ein Primzahlsieb zu verwenden, um eine Primzahl zu generieren. Dies ist jedoch nicht zufällig, also möchte man vielleicht herausfinden, wie man es randomisiert.

Die allgemeine Frage, wie mit eingeschränkten Werten in AutoFixture umgegangen werden soll, kam fast sofort auf, als andere Leute anfingen, sich die Bibliothek anzusehen, und ich damals einen Artikel schrieb, auf den ich mich immer noch beziehe: http://blog.ploeh.dk/2009/ 05/01/Umgang mit eingeschränkter Eingabe

Bezüglich der Frage nach mehreren Werten, die aufeinander bezogen sind, möchte ich keine allgemeine Orientierung geben. Solche Fragen sind oft XY-Probleme. In vielen Fällen könnte ein alternatives Design, sobald ich die Besonderheiten verstanden habe, Probleme nicht nur mit AutoFixture, sondern auch mit der Produktionscodebasis selbst lösen.

Selbst bei XY-Problemen wird es jedoch immer noch Situationen geben, in denen dies ein berechtigtes Anliegen sein kann, aber ich würde diese eher von Fall zu Fall behandeln, da sie meiner Erfahrung nach so sind Selten.

Wenn Sie also ein konkretes Beispiel dafür haben, kann ich Ihnen vielleicht helfen, aber ich glaube nicht, dass ich die allgemeine Frage sinnvoll beantworten kann.

@ploeh Vielen Dank für diese Antwort, die die von mir in Betracht gezogenen Ansätze bestätigt (und du hast mich neugierig auf Kata- und Anamorphismen gemacht 😃).
Ich stimme voll und ganz zu, dass voneinander abhängige Werte meistens ein XY-Problem sind (zumindest in meinem Fall), die Sache ist die, dass der Umgang mit diesen Werten bei der Arbeit an Legacy-Code (ungetestet 😢) sowieso ein guter Anfang war, um einige Tests zu schreiben, bis wir die Zeit, dies richtig umzugestalten.

Wie auch immer, Ihre beiden Antworten sprechen das Problem recht gut an, ich denke, ich kann von dort aus gut fortfahren.
Vielen Dank!

Übrigens, ich habe vergessen zu erwähnen, dass ich meine Antwort nur als Ergänzung zu @zvirja gemeint habe. Da ist es schon eine gute Antwort 👍

Ich habe es nicht anders verstanden

War diese Seite hilfreich?
0 / 5 - 0 Bewertungen