Lösen der Probleme, die mit der dyadischen Funktion assertEquals (erwartet, tatsächlich) einhergehen

10

Nach Jahren der Cowboy-Codierung entschied ich mich, ein Buch darüber zu lesen, wie man Code von guter Qualität schreibt. Ich lese Clean Code von Robert Cecil Martin. In Kapitel 3 (Funktionen) gibt es einen Abschnitt über dyadische Funktionen. Hier ist ein Auszug aus dem Buch.

Selbst offensichtliche dyadische Funktionen wie assertEquals(expected, actual)sind problematisch. Wie oft haben Sie den tatsächlichen Wert dort angegeben, wo er erwartet werden sollte? Die beiden Argumente haben keine natürliche Reihenfolge. Die erwartete tatsächliche Bestellung ist eine Konvention, die Übung erfordert, um zu lernen.

Der Autor macht einen überzeugenden Punkt. Ich arbeite im maschinellen Lernen und stoße die ganze Zeit darauf. Beispielsweise müssen Sie bei allen Metrikfunktionen in der sklearn-Bibliothek (wahrscheinlich der am häufigsten verwendeten Python-Bibliothek im Feld) auf die Reihenfolge der Eingaben achten. Als Beispiel nimmt sklearn.metrics.homogeneity_score als Eingaben labels_trueund labels_pred. Was diese Funktion nicht allzu relevant macht, ist relevant, dass beim Umschalten der Reihenfolge der Eingänge kein Fehler ausgegeben wird. Tatsächlich entspricht das Umschalten der Eingänge der Verwendung einer anderen Funktion in der Bibliothek.

Das Buch sagt jedoch keine sinnvolle Lösung für Funktionen wie assertEquals. Ich kann mir keine Lösung für assertEqualsoder für Funktionen vorstellen, auf die ich oft stoße, wie die oben beschriebene. Was sind bewährte Verfahren zur Lösung dieses Problems?

HBeel
quelle

Antworten:

11

Es ist gut, sich eines möglichen Problems bewusst zu sein, auch wenn es keine Lösung gibt. Auf diese Weise können Sie beim Lesen oder Schreiben eines solchen Codes wachsam sein. In diesem speziellen Beispiel gewöhnen Sie sich erst nach einer Weile an die Reihenfolge der Argumente.

Es gibt Möglichkeiten auf Sprachebene, um Verwirrung über die Parameterreihenfolge zu vermeiden: benannte Argumente. Dies wird in vielen Sprachen mit C-Syntax wie Java oder C ++ leider nicht unterstützt. In Python kann jedes Argument ein benanntes Argument sein. Anstatt eine Funktion def foo(a, b)als aufzurufen foo(1, 2), können wir dies tun foo(a=1, b=2). Viele moderne Sprachen wie C # haben eine ähnliche Syntax. Die Smalltalk-Sprachfamilie hat am weitesten benannte Argumente verwendet: Es gibt keine Positionsargumente und alles ist benannt. Dies kann zu Code führen, der der natürlichen Sprache sehr nahe kommt.

Eine praktischere Alternative besteht darin, APIs zu erstellen, die benannte Argumente simulieren. Dies können fließende APIs oder Hilfsfunktionen sein, die einen natürlichen Fluss erzeugen. Der assertEquals(actual, expected)Name ist verwirrend. Einige Alternativen, die ich gesehen habe:

  • assertThat(actual, is(equalTo(expected))): Durch das Umschließen einiger Argumente in Hilfstypen dienen die Umbruchfunktionen effektiv als Parameternamen. Im speziellen Fall von Unit-Test-Assertions wird diese Technik von Hamcrest-Matchern verwendet , um ein erweiterbares und zusammensetzbares Assertion-System bereitzustellen. Der Nachteil hierbei ist, dass Sie viel Verschachtelung erhalten und viele Hilfsfunktionen importieren müssen. Dies ist meine Technik in C ++.

  • expect(actual).to.be(expected): Eine fließende API, in der Sie Funktionsaufrufe miteinander verbinden. Dies vermeidet zwar eine zusätzliche Verschachtelung, ist jedoch nicht sehr erweiterbar. Obwohl ich finde, dass fließende APIs sehr gut lesen, ist das Entwerfen einer guten fließenden API meiner Erfahrung nach sehr aufwändig, da Sie zusätzliche Klassen für Nicht-Terminal-Zustände in der Aufrufkette implementieren müssen. Dieser Aufwand zahlt sich nur im Kontext einer IDE mit automatischer Vervollständigung aus, die die nächsten zulässigen Methodenaufrufe vorschlagen kann.

amon
quelle
4

Es gibt verschiedene Methoden, um dieses Problem zu vermeiden. Eine, die Sie nicht zwingt, die von Ihnen aufgerufene Methode zu ändern:

Eher, als

assertEquals( 42, meaningOfLife() ); 

Benutzen

expected = 42;
actual = meaningOfLife();
assertEquals(expected, actual);

Dies zwingt die Konvention ins Freie, wo es leicht zu erkennen ist, dass sie gewechselt werden. Sicher ist es nicht so einfach zu schreiben, aber es ist leicht zu lesen.

Wenn Sie die aufgerufene Methode ändern können, können Sie das Typisierungssystem verwenden, um eine leicht lesbare Verwendung zu erzwingen.

assertThat( meaningOfLife(), is(42) );

In einigen Sprachen können Sie dies vermeiden, da sie Parameter benannt haben:

assertEquals( expected=42, actual=meaningOfLife() );

Andere simulieren sie nicht:

assertEquals().expected(42).actual( meaningOfLife() );

Was auch immer Sie tun, finden Sie einen Weg, der deutlich macht, was beim Lesen richtig ist. Lassen Sie mich nicht raten, was die Konvention ist. Zeig es mir.

candied_orange
quelle