Kontext
In Clean Code , Seite 35, heißt es
Dies impliziert, dass die Blöcke in if-Anweisungen, else-Anweisungen, while-Anweisungen usw. eine Zeile lang sein sollten. Wahrscheinlich sollte diese Zeile ein Funktionsaufruf sein. Dies hält nicht nur die umschließende Funktion klein, sondern fügt auch einen dokumentarischen Wert hinzu, da die im Block aufgerufene Funktion einen aussagekräftigen Namen haben kann.
Ich stimme vollkommen zu, das macht sehr viel Sinn.
Später, auf Seite 40, werden Funktionsargumente beschrieben
Die ideale Anzahl von Argumenten für eine Funktion ist Null (Null). Als nächstes kommt eins (monadisch), dicht gefolgt von zwei (dyadisch). Drei Argumente (Triade) sollten nach Möglichkeit vermieden werden. Mehr als drei (polyadisch) erfordern eine besondere Begründung - und sollten dann sowieso nicht verwendet werden. Argumente sind schwer. Sie verbrauchen viel konzeptionelle Kraft.
Ich stimme vollkommen zu, das macht sehr viel Sinn.
Problem
Ich stelle jedoch ziemlich oft fest, dass ich eine Liste aus einer anderen Liste erstelle und mit einem von zwei Übeln leben muss.
Entweder verwende ich zwei Zeilen im Block , eine zum Erstellen des Objekts, eine zum Hinzufügen zum Ergebnis:
public List<Flurp> CreateFlurps(List<BadaBoom> badaBooms)
{
List<Flurp> flurps = new List<Flurp>();
foreach (BadaBoom badaBoom in badaBooms)
{
Flurp flurp = CreateFlurp(badaBoom);
flurps.Add(flurp);
}
return flurps;
}
Oder ich füge der Funktion ein Argument für die Liste hinzu, in die das Objekt eingefügt wird, wodurch es "ein Argument schlechter" wird.
public List<Flurp> CreateFlurps(List<BadaBoom> badaBooms)
{
List<Flurp> flurps = new List<Flurp>();
foreach (BadaBoom badaBoom in badaBooms)
{
CreateFlurpInList(badaBoom, flurps);
}
return flurps;
}
Frage
Gibt es (Un-) Vorteile, die ich nicht sehe und die einen generell vorziehen? Oder gibt es solche Vorteile in bestimmten Situationen; Auf was muss ich in diesem Fall achten, wenn ich eine Entscheidung treffe?
quelle
flurps.Add(CreateFlurp(badaBoom));
?f(g(x))
gegen deinen Style-Guide ist, kann ich deinen Style-Guide nicht reparieren. Ich meine, du bist auch nichtsqrt(x*x + y*y)
in vier Zeilen aufgeteilt, oder? Und das sind drei (!) Verschachtelte Unterausdrücke auf zwei (!) Inneren Verschachtelungsebenen (keuchen!). Ihr Ziel sollte die Lesbarkeit sein , nicht Aussagen einzelner Bediener. Wenn Sie das später wollen, dann habe ich die perfekte Sprache für Sie: Assembler.mov
Anweisungen und einer einzigenjmp toStart
am Ende erstellen . Jemand hat tatsächlich einen Compiler erstellt, der genau das tut: Drlwimi
Anweisung zum PPC. (Dies steht für "Direktes Einfügen einer Maske für das linke Wort drehen".) Dieser Befehl benötigte nicht weniger als fünf Operanden (zwei Register und drei Direktwerte) und führte die folgenden Vorgänge aus: Ein Registerinhalt wurde durch eine Sofortverschiebung gedreht, eine Maske wurde erstellt mit einem einzigen Lauf von 1 Bits, der von den beiden anderen unmittelbaren Operanden gesteuert wurde, und die Bits, die 1 Bits in dieser Maske im anderen Registeroperanden entsprachen, wurden durch die entsprechenden Bits des gedrehten Registers ersetzt. Sehr coole Anleitung :-)Antworten:
Diese Richtlinien sind ein Kompass, keine Karte. Sie weisen Sie in eine vernünftige Richtung . Aber sie können nicht wirklich absolut sagen, welche Lösung „am besten“ ist. Irgendwann müssen Sie nicht mehr in die Richtung gehen, in die Ihr Kompass zeigt, weil Sie an Ihrem Ziel angekommen sind.
Clean Code fordert Sie auf, Ihren Code in sehr kleine, offensichtliche Blöcke zu unterteilen. Das ist eine allgemein gute Richtung. Im Extremfall (wie eine wörtliche Interpretation des zitierten Hinweises nahelegt) haben Sie Ihren Code jedoch in unnötig kleine Teile unterteilt. Nichts tut wirklich etwas, alles delegiert nur. Dies ist im Wesentlichen eine andere Art der Code-Verschleierung.
Es ist Ihre Aufgabe, „kleiner ist besser“ gegen „zu klein ist nutzlos“ abzuwägen. Fragen Sie sich, welche Lösung einfacher ist. Für mich ist das eindeutig die erste Lösung, da sie offensichtlich eine Liste zusammenstellt. Dies ist eine gut verstandene Redewendung. Es ist möglich, diesen Code zu verstehen, ohne eine weitere Funktion anschauen zu müssen.
Wenn es möglich ist, bessere Ergebnisse zu erzielen, können Sie feststellen, dass „alle Elemente von einer Liste in eine andere Liste umwandeln“ ein häufig vorkommendes Muster ist, das häufig mithilfe einer funktionalen
map()
Operation abstrahiert werden kann . In C #, denke ich, heißt esSelect
. Etwas wie das:quelle
CreateFlurps(someList)
wenn die BCL bereits bietetsomeList.ConvertAll(CreateFlurp)
?BadaBoom => CreateFlurp(badaBoom)
ist überflüssig; Sie könnenCreateFlurp
als Funktion direkt übergeben (Select(CreateFlurp)
). (Soweit ich weiß, war dies immer der Fall.)CreateFlurps
ist irreführender und schwerer zu verstehen als nur zu sehenbadaBooms.Select(CreateFlurp)
. Letzteres ist vollständig deklarativ - es gibt kein Problem bei der Zerlegung und daher überhaupt keine Notwendigkeit für eine Methode.badaBooms.Select(CreateFlurp)
. Sie erstellen eine Methode so, dass ihr Name (hohe Ebene) für ihre Implementierung (niedrige Ebene) steht. In diesem Fall sind sie auf dem gleichen Niveau. Um herauszufinden, was genau passiert, muss ich mir die Methode ansehen (anstatt sie inline zu sehen).CreateFlurps(badaBooms)
könnte überraschen,badaBooms.Select(CreateFlurp)
kann aber nicht. Es ist auch irreführend, weil fälschlicherweise nach einemList
statt nach einem gefragt wirdIEnumerable
.Nein! Die ideale Anzahl von Argumenten für eine Funktion ist eins. Wenn der Wert Null ist, müssen Sie sicherstellen, dass die Funktion auf externe Informationen zugreifen muss, um eine Aktion ausführen zu können. "Onkel" Bob hat das sehr falsch verstanden.
In Bezug auf Ihren Code enthält Ihr erstes Beispiel nur zwei Zeilen im Block, da Sie in der ersten Zeile eine lokale Variable erstellen. Entfernen Sie diese Zuordnung, und halten Sie sich an die folgenden Richtlinien für sauberen Code:
Aber das ist sehr langwieriger (C #) Code. Mach es einfach so:
quelle
Der Hinweis 'Clean Code' ist völlig falsch.
Verwenden Sie zwei oder mehr Zeilen in Ihrer Schleife. Das Ausblenden derselben zwei Zeilen in einer Funktion ist sinnvoll, wenn es sich um zufällige mathematische Berechnungen handelt, für die eine Beschreibung erforderlich ist. Wenn die Zeilen bereits beschreibend sind, hat dies jedoch keine Auswirkungen. 'Erstellen' und 'Hinzufügen'
Die zweite Methode, die Sie in erwähnen, ist nicht wirklich sinnvoll, da Sie nicht gezwungen sind, ein zweites Argument hinzuzufügen, um die beiden Zeilen zu vermeiden.
oder
Wie von anderen angemerkt, wird der Rat, dass die beste Funktion eine Funktion ohne Argumente ist, bestenfalls auf OOP und im schlimmsten Fall auf schlechten Rat verzerrt
quelle
Zweitens ist es definitiv schlimmer, da es
CreateFlurpInList
eine Liste akzeptiert und diese modifiziert, wodurch die Funktion nicht rein und schwieriger zu durchdenken ist. Nichts im Methodennamen deutet darauf hin, dass die Methode nur zur Liste hinzugefügt wird.Und ich biete die dritte, beste Option an:
Und zum Teufel, Sie können diese Methode sofort einbinden, wenn es nur einen Ort gibt, an dem sie verwendet wird, da der Einzeiler für sich selbst klar ist, sodass er nicht durch eine Methode eingekapselt werden muss, um ihm eine Bedeutung zu verleihen.
quelle
Die Version mit einem Argument ist besser, aber nicht in erster Linie wegen der Anzahl der Argumente.
Der wichtigste Grund, warum es besser ist, ist die geringere Kopplung , die es nützlicher macht, einfacher zu überlegen, einfacher zu testen und weniger wahrscheinlich in kopierte + eingefügte Klone umzuwandeln.
Wenn Sie mich mit einem zur Verfügung stellen
CreateFlurp(BadaBoom)
, das kann ich mit jeder Art von Sammelbehälters verwenden: EinfacheFlurp[]
,List<Flurp>
,LinkedList<Flurp>
,Dictionary<Key, Flurp>
, und so weiter. Aber mit aCreateFlurpInList(BadaBoom, List<Flurp>)
komme ich morgen auf Sie zurück undCreateFlurpInBindingList(BadaBoom, BindingList<Flurp>)
frage, damit mein Ansichtsmodell die Benachrichtigung erhält, dass sich die Liste geändert hat. Yuck!Als zusätzlicher Vorteil passt die einfachere Signatur eher zu vorhandenen APIs. Sie sagen, Sie haben ein wiederkehrendes Problem
Es ist nur eine Frage der Verwendung der verfügbaren Tools. Die kürzeste, effizienteste und beste Version ist:
Damit können Sie nicht nur weniger Code schreiben und testen, sondern auch schneller, da Sie
List<T>.ConvertAll()
genau wissen, dass das Ergebnis die gleiche Anzahl von Elementen enthält wie die Eingabe, und die Ergebnisliste vorab der richtigen Größe zuweisen. Während Ihr Code (beide Versionen) das Erweitern der Liste erforderte.quelle
List.ConvertAll
. Die idiomatische Methode zum Zuordnen einer Anzahl von Objekten zu verschiedenen Objekten in C # wird aufgerufenSelect
. Der einzige Grund, derConvertAll
hier überhaupt zur Verfügung steht, ist, dass das OP fälschlicherweise nach einerList
in der Methode fragt - es sollte eine seinIEnumerable
.Behalten Sie das übergeordnete Ziel im Hinterkopf: Einfacheres Lesen und Verwalten des Codes.
Oft ist es möglich, mehrere Zeilen zu einer einzigen sinnvollen Funktion zusammenzufassen. Tun Sie dies in diesen Fällen. Gelegentlich müssen Sie Ihren allgemeinen Ansatz überdenken.
Ersetzen Sie in Ihrem Fall beispielsweise die gesamte Implementierung durch var
könnte eine Möglichkeit sein. Oder Sie könnten so etwas tun
Manchmal passt die sauberste und am besten lesbare Lösung einfach nicht in eine Zeile. Sie haben also zwei Zeilen. Machen Sie den Code nicht schwerer zu verstehen, nur um eine willkürliche Regel zu erfüllen.
Ihr zweites Beispiel ist meiner Meinung nach wesentlich schwerer zu verstehen als das erste. Es ist nicht nur so, dass Sie einen zweiten Parameter haben, sondern dass der Parameter durch die Funktion geändert wird. Schauen Sie nach, was Clean Code dazu zu sagen hat. (Ich habe das Buch gerade nicht zur Hand, aber ich bin mir ziemlich sicher, dass es im Grunde genommen "Mach das nicht, wenn du es vermeiden kannst").
quelle