Ich möchte noch etwas hinzufügen, das durch andere Antworten angedeutet ist, aber ich glaube, es wurde nicht explizit erwähnt:
@puck sagt "Es gibt immer noch keine Garantie, dass das erste Argument im Funktionsnamen wirklich der erste Parameter ist."
@cbojar sagt "Verwenden Sie Typen anstelle von mehrdeutigen Argumenten"
Das Problem ist, dass Programmiersprachen keine Namen verstehen: Sie werden nur als undurchsichtige, atomare Symbole behandelt. Wie bei Codekommentaren besteht daher nicht notwendigerweise eine Korrelation zwischen dem Namen einer Funktion und ihrer tatsächlichen Funktionsweise.
Vergleichen Sie assertExpectedEqualsActual(foo, bar)
mit einigen Alternativen (von dieser Seite und anderswo), wie:
# Putting the arguments in a labelled structure
assertEquals({expected: foo, actual: bar})
# Using a keyword arguments language feature
assertEquals(expected=foo, actual=bar)
# Giving the arguments different types, forcing us to wrap them
assertEquals(Expected(foo), Actual(bar))
# Breaking the symmetry and attaching the code to one of the arguments
bar.Should().Be(foo)
Diese haben alle mehr Struktur als der wörtliche Name, wodurch die Sprache nicht undurchsichtig wirkt. Die Definition und Verwendung der Funktion hängt auch von dieser Struktur ab, so dass sie nicht mit dem synchronisiert werden kann, was die Implementierung tut (wie ein Name oder ein Kommentar).
Wenn ich auf ein Problem wie dieses stoße oder es sehe, nehme ich mir einen Moment Zeit, um zu fragen, ob es „fair“ ist, der Maschine überhaupt die Schuld zu geben, bevor ich frustriert meinen Computer anschreie. Mit anderen Worten, hat die Maschine genügend Informationen erhalten, um zu unterscheiden, was ich wollte und wonach ich gefragt habe?
Ein Anruf wie assertEqual(expected, actual)
macht genauso viel Sinn wie assertEqual(actual, expected)
, daher ist es für uns einfach, sie durcheinander zu bringen und die Maschine vorwärts zu pflügen und das Falsche zu tun. Wenn wir assertExpectedEqualsActual
stattdessen verwenden, ist es möglicherweise weniger wahrscheinlich, dass wir einen Fehler machen, aber die Maschine erhält keine weiteren Informationen (sie kann kein Englisch verstehen und die Wahl des Namens sollte sich nicht auf die Semantik auswirken).
Was die "strukturierten" Ansätze bevorzugter macht, wie Schlüsselwortargumente, beschriftete Felder, unterschiedliche Typen usw., ist, dass die zusätzlichen Informationen auch maschinenlesbar sind , so dass wir die Maschine falsche Verwendungen erkennen und uns dabei helfen können, die Dinge richtig zu machen. Der assertEqual
Fall ist nicht so schlimm, da das einzige Problem ungenaue Nachrichten wären. Ein unheimlicheres Beispiel könnte sein String replace(String old, String new, String content)
, das leicht zu verwechseln String replace(String content, String old, String new)
ist und eine ganz andere Bedeutung hat. Ein einfaches Mittel wäre, ein Paar zu nehmen [old, new]
, bei dem Fehler sofort einen Fehler auslösen (auch ohne Typen).
Beachten Sie, dass wir selbst bei Typen möglicherweise nicht sagen, was wir wollen. Zum Beispiel behandelt das Anti-Pattern "stringly typed programming" alle Daten als Strings, was es leicht macht, Argumente zu verwechseln (wie in diesem Fall), einen Schritt zu vergessen (zB Escape), um Invarianten versehentlich zu brechen (zB unparseable JSON machen), etc.
Dies hängt auch mit der "Booleschen Blindheit" zusammen, bei der wir eine Reihe von Booleschen Werten (oder Zahlen usw.) in einem Teil des Codes berechnen, aber wenn wir versuchen, sie in einem anderen Teil zu verwenden, ist nicht klar, was sie tatsächlich darstellen, ob Wir haben sie durcheinander gebracht, usw. Vergleichen Sie dies zB mit verschiedenen Aufzählungen, die beschreibende Namen haben (zB LOGGING_DISABLED
anstatt false
) und die eine Fehlermeldung verursachen, wenn wir sie durcheinander bringen.