=> „Mission Octopussy wurde James Bond mit dem Ziel zugewiesen, das Nukleargerät zu finden. Die Lizenz zum Töten wurde erteilt. “

Dies ist Teil 2 einer kleinen Serie über Codegerüche und mögliche Refactorings. Die Zielgruppe, an die ich dachte, sind Neulinge, die von diesem Thema erfahren haben und vielleicht etwas warten wollten, bevor sie in diese fortgeschrittenen Gewässer eintauchen. Der folgende Artikel behandelt "Feature Envy", "Shotgun Surgery" und "Divergent Change"..

Themen

  • Feature Neid
  • Schrotflinten-Chirurgie
  • Abweichende Änderung

Mit Codegerüchen werden Sie schnell feststellen, dass einige von ihnen sehr nahe Verwandte sind. Sogar ihre Refactorings sind zum Beispiel verwandt, Inline-Klasse und Klasse extrahieren sind nicht so verschieden.

Beim Inlining einer Klasse extrahieren Sie beispielsweise die gesamte Klasse, während Sie die ursprüngliche Klasse loswerden. Also ein bisschen Extraklasse mit einer kleinen Drehung. Der Punkt, den ich zu sagen versuche, ist, dass Sie sich nicht von der Anzahl der Gerüche und Refactorings überfordert fühlen und sich auf jeden Fall nicht von ihren klugen Namen entmutigen lassen sollten. Dinge wie die Shotgun-Chirurgie, Feature Envy und Divergent Change mögen für Leute, die gerade erst angefangen haben, einfallsreich und einschüchternd wirken. Vielleicht irre ich mich natürlich.

Wenn Sie ein wenig in dieses ganze Thema eintauchen und selbst mit ein paar Refactorings für Code-Gerüche spielen, werden Sie schnell feststellen, dass sie oft im selben Stadion landen. Viele Refactorings sind einfach verschiedene Strategien, um zu einem Punkt zu gelangen, an dem Sie kurze, gut organisierte Klassen haben und sich auf wenige Verantwortlichkeiten konzentrieren. Ich denke, es ist fair zu sagen, dass Sie, wenn Sie dies erreichen können, die meiste Zeit voraus sind - nicht, dass es so wichtig ist, anderen voraus zu sein, aber solche Klassendesigns fehlen im Code von den Leuten vor ihnen oft werden als "Experten" betrachtet.

Warum also nicht frühzeitig mit dem Spiel beginnen und eine konkrete Grundlage für die Gestaltung Ihres Codes schaffen. Glauben Sie nicht, was Ihre eigene Erzählung sein könnte, dass dies ein fortgeschrittenes Thema ist, das Sie für eine Weile verschieben sollten, bis Sie bereit sind. Selbst wenn Sie ein Neuling sind, wenn Sie kleine Schritte unternehmen, können Sie Ihren Kopf viel früher mit Gerüchen und deren Umgestaltung umwickeln, als Sie denken.

Bevor wir in die Mechanik eintauchen, möchte ich einen wichtigen Punkt aus dem ersten Artikel wiederholen. Nicht jeder Geruch ist von Natur aus schlecht und nicht jedes Refactoring lohnt sich immer. Sie müssen sofort entscheiden, wann Sie alle relevanten Informationen zur Verfügung haben, wenn Ihr Code nach einem Refactoring stabiler ist und ob es sich lohnt, den Geruch zu beseitigen.

Feature Neid

Lassen Sie uns ein Beispiel aus dem vorherigen Artikel erneut betrachten. Wir haben eine lange Liste von Parametern für extrahiert #assign_new_mission in ein Parameterobjekt über die Mission Klasse. So weit so cool.

M mit Feature Neid

"Ruby-Klasse M def assign_new_mission (mission) print" Mission # mission.mission_name wurde # mission.agent_name mit dem Ziel # # mission.objective zugewiesen. "if mission.licence_to_kill print" Die Lizenz zum Töten. " wurde erteilt. ”else print“ Die Lizenz zum Töten wurde nicht erteilt. ”Ende Ende Ende

Klasse Mission attr_reader: mission_name,: agent_name,: objektiv,: licence_to_kill

def initialize (mission_name: mission_name, agent_name: agent_name, Ziel: Ziel, Lizenz_zu_kill: licence_to_kill) @mission_name = missionsname @agent_name = agent_name @objective = objektiv @licence_to_kill = Ende der Lizenz_to_kill =

m = M.neu

mission = Mission.new (mission_name: 'Octopussy', Agent_name: 'James Bond', Ziel: 'Finde das Nukleargerät', licence_to_kill: true)

m.assign_new_mission (mission)

=> „Mission Octopussy wurde James Bond mit dem Ziel zugewiesen, das Nukleargerät zu finden. Die Lizenz zum Töten wurde erteilt. “

"

Ich habe kurz erwähnt, wie wir das vereinfachen können M noch mehr Klasse durch Verschieben der Methode #assign_new_mission auf die neue Klasse für das Parameterobjekt. Was ich nicht ansprach, war die Tatsache M hatte eine leicht heilbare Form von Feature Neid auch. M war viel zu neugierig über Attribute von Mission. Anders ausgedrückt, stellte sie viel zu viele „Fragen“ zum Missionsobjekt. Es ist nicht nur ein schlimmer Fall von Mikromanagement, sondern auch ein sehr verbreiteter Codegeruch.

Lass mich dir zeigen, was ich meine. Im M # assign_new_mission, M ist "neidisch" auf die Daten im neuen Parameterobjekt und möchte überall darauf zugreifen.

  • mission.mission_name
  • mission.agent_name
  • Missionsziel
  • mission.licence_to_kill

Zusätzlich haben Sie noch ein Parameterobjekt Mission das ist jetzt nur für Daten verantwortlich - was ein anderer Geruch ist, a Datenklasse.

Diese ganze Situation sagt Ihnen das im Grunde #assign_new_mission will woanders sein und M muss nicht genau wissen, wie Missionen zugewiesen werden. Warum sollte es nicht Aufgabe einer Mission sein, neue Missionen zu vergeben? Denken Sie daran, immer Dinge zusammenzustellen, die sich auch ändern.

M ohne Feature Neid

msgstr "Ruby - Klasse M def assign_new_mission (mission) mission.assign end end

Klasse Mission attr_reader: mission_name,: agent_name,: objektiv,: licence_to_kill

def initialize (mission_name: mission_name, agent_name: agent_name, Ziel: Ziel, Lizenz_zu_kill: licence_to_kill) @mission_name = missionsname @agent_name = agent_name @objective = objektiv @licence_to_kill = Ende der Lizenz_to_kill

def vergeben print "Mission # mission_name wurde # agent_name mit dem Ziel # Ziel zugewiesen." if licence_to_kill print "Die Lizenz zum Töten wurde erteilt." else print "Die Lizenz zum Töten ist nicht vorhanden gewährt. “Ende Ende Ende

m = M.new mission = Mission.new (mission_name: 'Octopussy', Agent_name: 'James Bond', Ziel: 'Das Atomgerät finden', licence_to_kill: true) m.assign_new_mission (mission) "

Wie Sie sehen, haben wir die Dinge ein bisschen vereinfacht. Die Methode wurde erheblich reduziert und das Verhalten an das verantwortliche Objekt delegiert. M fordert keine spezifischen Missionsdaten mehr an und bleibt gewiss davon fern, sich damit zu beschäftigen, wie Aufträge gedruckt werden. Jetzt kann sie sich auf ihren eigentlichen Job konzentrieren und muss nicht gestört werden, wenn sich Details zu den Missionsaufgaben ändern. Mehr Zeit für Gedankenspiele und Jagd auf Schurkenagenten. Win-Win!

Mit Neid verzaubern sich die Rassen - damit meine ich nicht die gute Art - die, mit der Informationen schneller als Licht spekuliert werden können -, ich spreche von der, die im Laufe der Zeit dazu führen könnte, dass Ihr Entwicklungsimpuls immer näher rückt. Nicht gut! Warum so Ripple-Effekte im gesamten Code erzeugen Widerstand! Eine Veränderung an einem Ort Schmetterlinge durch alle möglichen Sachen und Sie enden als Drachen in einem Hurrikan. (Ok, etwas übertrieben dramatisch, aber ich gebe mir ein B + für die Bond-Referenz dort.)

Als allgemeines Gegenmittel gegen Feature Neid möchten Sie das Entwerfen von Klassen anstreben, die sich hauptsächlich um ihre eigenen Sachen kümmern und - wenn möglich - einzelne Verantwortlichkeiten haben. Kurz gesagt, sollte der Unterricht so etwas wie ein freundlicher Otakus sein. In sozialer Hinsicht ist dies möglicherweise nicht das gesündeste Verhalten, aber für die Gestaltung von Klassen ist es oft eine vernünftige Richtlinie, den Schwung dahin zu halten, wo er sich entwickeln sollte!

Schrotflinten-Chirurgie

Der Name ist ein bisschen dumm, nicht wahr? Aber gleichzeitig ist es eine ziemlich genaue Beschreibung. Klingt nach ernstem Geschäft, und das ist es! Zum Glück ist es nicht so schwer zu begreifen, aber trotzdem ist es einer der übelsten Codegerüche. Warum? Denn es führt zu Duplikaten wie kein anderer und es ist leicht, alle Änderungen aus den Augen zu verlieren, die Sie zur Behebung von Problemen vornehmen müssen. Was bei einer Flintenoperation passiert, ist, dass Sie eine Klasse / Datei ändern und viele andere Klassen / Dateien, die aktualisiert werden müssen, berühren müssen. Ich hoffe, das hört sich nicht nach einer guten Zeit an, auf die Sie Lust haben.

Zum Beispiel denken Sie vielleicht, dass Sie mit einer kleinen Änderung an einer Stelle davonkommen können, und dann feststellen, dass Sie eine ganze Reihe von Dateien durchsuchen müssen, um entweder dieselbe Änderung vorzunehmen oder etwas anderes zu reparieren, das daran kaputt geht. Nicht gut, überhaupt nicht! Das klingt eher nach einem guten Grund, warum Leute anfangen, den Code zu hassen, mit dem sie sich befassen.

Wenn Sie ein Spektrum mit DRY-Code auf einer Seite haben, ist der Code, der häufig für eine Flintenoperation erforderlich ist, am anderen Ende. Sei nicht faul und lass dich in dieses Gebiet ein. Ich bin sicher, dass Sie lieber eine Datei öffnen und Ihre Änderungen dort anwenden und damit fertig sind. Das ist die Art von Faulheit, die Sie anstreben sollten!

Um diesen Geruch zu vermeiden, finden Sie hier eine kurze Liste der Symptome, auf die Sie achten können:

  • Feature Neid
  • Enge Kupplung
  • Lange Parameterliste
  • Jede Form der Code-Vervielfältigung

Was meinen wir, wenn wir über gekoppelten Code sprechen? Nehmen wir an, wir haben Objekte EIN und B. Wenn sie nicht gekoppelt sind, können Sie eine davon ändern, ohne die andere zu beeinflussen. Ansonsten müssen Sie sich meist auch mit dem anderen Objekt beschäftigen.

Dies ist ein Problem, und die Schrotflintenoperation ist auch ein Symptom für eine feste Kopplung. Achten Sie also immer darauf, wie einfach Sie Ihren Code ändern können. Wenn es relativ einfach ist, bedeutet das, dass Ihr Kopplungsgrad akzeptabel niedrig ist. Allerdings ist mir klar, dass Ihre Erwartungen unrealistisch sind, wenn Sie die Kopplung um jeden Preis um jeden Preis vermeiden möchten. Das wird nicht passieren! Sie werden gute Gründe finden, sich gegen das drängende Ersetzen von Bedingungen durch zu entscheiden Polymorphismus. In einem solchen Fall lohnt es sich, ein wenig zu koppeln, eine Flintenoperation durchzuführen und die API der Objekte synchron zu halten Nullobjekt (mehr dazu in einem späteren Stück).

Am häufigsten können Sie eine der folgenden Refactorings anwenden, um die Wunden zu heilen:

  • Feld verschieben
  • Inline-Klasse
  • Klasse extrahieren
  • Methode verschieben

Schauen wir uns Code an. In diesem Beispiel wird erläutert, wie eine Spectre-App Zahlungen zwischen ihren Auftragnehmern und bösen Kunden verarbeitet. Ich habe die Zahlungen etwas vereinfacht, indem ich sowohl für Auftragnehmer als auch für Kunden Standardgebühren erhalte. Es ist also egal, ob Specter beauftragt wird, eine Katze zu kidnappen oder ein ganzes Land zu erpressen: Die Gebühr bleibt gleich. Dasselbe gilt für das, was sie ihren Auftragnehmern zahlen. In seltenen Fällen geht eine Operation nach Süden und eine andere Nr. 2 muss buchstäblich den Hai springen, Spectre bietet eine volle Rückerstattung, um böse Kunden glücklich zu machen. Specter verwendet einige proprietäre Zahlungsedel, die im Wesentlichen Platzhalter für alle Arten von Zahlungsprozessoren sind.

Im ersten Beispiel unten wäre es schmerzhaft, wenn Specter sich für eine andere Bibliothek zur Abwicklung von Zahlungen entschieden würde. Es wären mehr bewegliche Teile involviert, aber zur Demonstration der Flintenoperation wird diese Komplexität meiner Meinung nach ausreichen:

Beispiel mit Flintenoperationsgeruch:

"Rubinklasse EvilClient #…

STANDARD_CHARGE = 10000000 BONUS_CHARGE = 10000000

def accept_new_client PaymentGem.create_client (email) end

def charge_for_initializing_operation evil_client_id = PaymentGem.find_client (email) .payments_id PaymentGem.charge (evil_client_id, STANDARD_CHARGE) ende

def charge_for_successful_operation evil_client_id = PaymentGem.find_client (email) .payments_id PaymentGem.charge (evil_client_id, BONUS_CHARGE) Ende

Klasse Operation #…

REFUND_AMOUNT = 10000000

def refund transaction_id = PaymentGem.find_transaction (payment_id) PaymentGem.refund (transaction_id, REFUND_AMOUNT) end end

Klasse Auftragnehmer #…

STANDARD_PAYOUT = 200000 BONUS_PAYOUT = 1000000

def process_payout spectre_agent_id = PaymentGem.find_contractor (email) .payments_id if operation.enemy_agent == 'James Bond' && operation.enemy_agent_status == 'In Aktion getötet' PaymentGem.transfer_funds (spectre_agent_id, BONUS_PAYOUT) else Ende Ende Ende "

Wenn Sie sich diesen Code ansehen, sollten Sie sich fragen: "Soll das EvilClients Seien Sie wirklich besorgt darüber, wie der Zahlungsabwickler neue böse Kunden akzeptiert und wie sie für den Betrieb berechnet werden? “Natürlich nicht! Ist es eine gute Idee, die verschiedenen Beträge zu verteilen, um sie überall zu bezahlen? Sollten die Implementierungsdetails des Zahlungsabwicklers in einer dieser Klassen angezeigt werden? Ganz bestimmt nicht!

Schau es dir so an. Wenn Sie etwas an der Art und Weise ändern möchten, wie Sie mit Zahlungen umgehen, warum müssen Sie die EvilClient Klasse? In anderen Fällen kann es sich um einen Benutzer oder einen Kunden handeln. Wenn Sie darüber nachdenken, macht es keinen Sinn, sie mit diesem Prozess vertraut zu machen.

In diesem Beispiel sollte leicht zu erkennen sein, dass Änderungen an der Art und Weise, wie Sie Zahlungen akzeptieren und übertragen, Auswirkungen auf den gesamten Code haben. Wenn Sie den Betrag, den Sie berechnen oder an Ihren Vertragspartner überwiesen haben, ändern möchten, benötigen Sie überall zusätzliche Änderungen. Paradebeispiele der Flintenoperation. Und in diesem Fall haben wir nur drei Klassen. Stellen Sie sich Ihren Schmerz vor, wenn es um etwas realistischere Komplexität geht. Ja, das ist der Stoff, aus dem Albträume gemacht werden. Schauen wir uns ein Beispiel an, das etwas vernünftiger ist:

Beispiel ohne Schrotflintenoperationsgeruch und extrahierter Klasse:

"Ruby-Klasse PaymentHandler STANDARD_CHARGE = 10000000 BONUS_CHARGE = 10000000 REFUND_AMOUNT = 10000000 STANDARD_CONTRACTOR_PAYOUT = 200000 BONUS_CONTRACTOR_PAYOUT = 1000000

def initialize (payment_handler = PaymentGem) @payment_handler = payment_handler end

def accept_new_client (evil_client) @ payment_handler.create_client (evil_client.email) end

def charge_for_initializing_operation (evil_client) evil_client_id = @ payment_handler.find_client (evil_client.email) .payments_id @ payment_handler.charge (evil_client_id, STANDARD_CHARGE) end

def charge_for_successful_operation (evil_client) evil_client_id = @ payment_handler.find_client (evil_client.email) .payments_id @ payment_handler.charge (evil_client_id, BONUS_CHARGE) end

def refund (operation) transaction_id = @ payment_handler.find_transaction (operation.payments_id) @ payment_handler.refund (transaction_id, REFUND_AMOUNT) end

def contractor_payout (contractor) spectre_agent_id = @ payment_handler.find_contractor (contractor.email) .payments_id if operation.enemy_agent == 'James Bond' && operation.enemy_agent_status == 'In Aktion getötet' @ payment_handler.transfer_funds (spectre_agent_id), payment_handler.transfer_funds (spectre_agent_id, STANDARD_CONTRACTOR_PAYOUT) end end end end

Klasse EvilClient #…

def accept_new_client PaymentHandler.new.accept_new_client (Eigen) Ende

def charge_for_initializing_operation PaymentHandler.new.charge_for_initializing_operation (Selbst-) Ende

def charge_for_erfolgreiche_operation (operation) PaymentHandler.new.charge_for_erfolgreiche_operation (selbst) end end

Klasse Operation #…

def Rückzahlung PaymentHandler.new.refund (selbst) end end

Klasse Auftragnehmer #…

def process_payout PaymentHandler.new.contractor_payout (selbst) end end "

Was wir hier gemacht haben, ist die PaymentGem API in unserer eigenen Klasse. Jetzt haben wir einen zentralen Ort, an dem wir unsere Änderungen anwenden, wenn wir beispielsweise entscheiden, dass a SpectrePaymentGem würde besser für uns arbeiten. Es ist nicht mehr nötig, die internen Daten mehrerer Zahlungen mit mehreren Zahlungsarten zu berühren, die nicht miteinander verbunden sind, wenn wir uns an Änderungen anpassen müssen. In den Klassen, die sich mit Zahlungen beschäftigen, haben wir einfach das instantiiert PaymentHandler und delegieren Sie die erforderliche Funktionalität. Einfach, stabil und kein Grund zum Ändern.

Und wir haben nicht nur alles in einer einzigen Datei gespeichert. Innerhalb des ZahlungenHandler Klasse, es gibt nur einen Ort, an dem wir einen möglichen neuen Zahlungsprozessor eintauschen und darauf verweisen können initialisieren. Das ist rad in meinem Buch. Wenn der neue Zahlungsservice eine völlig andere API hat, müssen Sie die Körper einiger Methoden einschränken PaymentHandler. Dies ist ein winziger Preis, der im Vergleich zu einer vollständigen Schrotflintenoperation zu zahlen ist. Dies ist eher eine Operation für einen kleinen Splitter im Finger. Gutes Geschäft!

Wenn Sie beim Schreiben von Tests für einen Zahlungsprozessor wie diesen oder einen externen Dienst, auf den Sie sich verlassen müssen, nicht vorsichtig sind, haben Sie möglicherweise ernsthafte Probleme, wenn sie ihre API ändern. Sie „leiden natürlich auch unter Veränderung“. Und die Frage ist nicht, dass sie ihre API ändern, nur wenn.

Durch unsere Kapselung sind wir viel besser in der Lage, unsere Methoden für den Zahlungsabwickler zu verkürzen. Warum? Denn die Methoden, die wir verwenden, sind unsere eigenen und ändern sich nur dann, wenn wir sie wollen. Das ist ein großer Gewinn. Wenn Sie noch keine Erfahrung mit dem Testen haben und Ihnen dies nicht völlig klar ist, machen Sie sich keine Sorgen. Nimm dir Zeit; dieses thema kann zunächst schwierig sein. Weil es ein solcher Vorteil ist, wollte ich es nur der Vollständigkeit halber erwähnen.

Wie Sie sehen, habe ich die Zahlungsabwicklung in diesem dummen Beispiel ziemlich vereinfacht. Ich hätte auch das Endergebnis noch ein bisschen bereinigen können, aber es ging darum, den Geruch deutlich zu machen und wie man ihn durch Abstraktion loswerden kann.

Wenn Sie mit diesem Kurs nicht ganz zufrieden sind und Möglichkeiten für das Refactoring sehen, dann begrüße ich Sie - und freue mich, dass Sie dafür einen Kredit erhalten. Ich empfehle dir, dich selbst rauszuwerfen! Ein guter Anfang könnte sich mit der Art und Weise beschäftigen, wie Sie finden payments_ids. Die Klasse selbst war auch schon etwas überfüllt…

Abweichende Änderung

Eine abweichende Veränderung ist gewissermaßen das Gegenteil einer Flintenoperation, bei der Sie etwas ändern wollen und diese Veränderung durch eine Reihe verschiedener Dateien sprengen müssen. Hier wird eine einzelne Klasse häufig aus verschiedenen Gründen und auf unterschiedliche Weise geändert. Meine Empfehlung ist, Teile zu identifizieren, die sich gemeinsam verändern, und sie in einer separaten Klasse zu extrahieren, die sich auf diese einzelne Verantwortung konzentrieren kann. Diese Klassen sollten auch nur einen Grund für die Änderung haben. Andernfalls wartet ein anderer abweichender Änderungsgeruch darauf, Sie zu beißen.

Klassen, die unter abweichenden Veränderungen leiden, werden stark verändert. Mit Tools wie Churn können Sie messen, wie oft bestimmte Teile Ihres Codes in der Vergangenheit geändert wurden. Je mehr Punkte Sie in einer Klasse finden, desto höher ist die Wahrscheinlichkeit, dass abweichende Änderungen wirksam werden. Es würde mich auch nicht wundern, wenn genau diese Klassen die meisten Fehler insgesamt verursachen.

Versteht mich nicht falsch: Sich oft zu verändern, ist nicht direkt der Geruch. Es ist jedoch ein nützliches Symptom. Ein anderes sehr häufiges und expliziteres Symptom ist, dass dieses Objekt mehr als eine Verantwortung jonglieren muss. Das Grundsatz der Einzelverantwortung SRP ist eine hervorragende Richtlinie, um diesen Codegeruch zu verhindern und generell stabileren Code zu schreiben. Es kann schwierig sein zu folgen, aber es lohnt sich trotzdem.

Schauen wir uns dieses unangenehme Beispiel unten an. Ich habe das Beispiel der Flintenoperation etwas geändert. Blofeld, Kopf von Spectre, ist vielleicht bekannt für das Mikromanagement von Dingen, aber ich bezweifle, dass er sich für die Hälfte der Dinge interessieren würde, mit denen diese Klasse zu tun hat.

"Ruby Klasse Specter

STANDARD_CHARGE = 10000000 STANDARD_PAYOUT = 200000

def charge_for_initializing_operation (client) evil_client_id = PaymentGem.find_client (client.email) .payments_id PaymentGem.charge (evil_client_id, STANDARD_CHARGE) beendet

def contractor_payout (contractor) spectre_agent_id = PaymentGem.find_contractor (contractor.email) .payments_id PaymentGem.transfer_funds (spectre_agent_id, STANDARD_PAYOUT) beendet

def assign_new_operation (operation) operation.contractor = 'Irgendein böser Kerl' operation.objective = 'Eine Bootladung wertvoller Dinge stehlen' operation.deadline = 'Mitternacht, 18. November' Ende

def print_operation_assignment (operation) print "# operation.contractor wird # operation.objective zugewiesen. Die Missionsfrist endet um # operation.deadline. ”End

def dispose_of_agent (spectre_agent) schreibt: „Sie haben diese Organisation enttäuscht. Sie wissen, wie Specter mit Fehlern umgeht. Auf Wiedersehen # spectre_agent.code_name! "End end"

Das Gespenst Klasse hat viel zu viele verschiedene Dinge, um die es geht:

  • Neue Operationen zuweisen
  • Aufladung für ihre schmutzige Arbeit
  • Missionszuweisungen drucken
  • Tötung von erfolglosen Agenten
  • Umgang mit den PaymentGem-Internen
  • Ihre Specter-Agenten / Auftragnehmer bezahlen
  • Es kennt auch die Geldbeträge für das Aufladen und die Auszahlung

Sieben verschiedene Verantwortlichkeiten für eine Klasse. Nicht gut! Sie müssen ändern, wie Agenten entsorgt werden? Ein Vektor zum Ändern der Gespenst Klasse. Sie möchten Auszahlungen unterschiedlich behandeln? Ein weiterer Vektor. Sie bekommen den Drift.

Obwohl dieses Beispiel alles andere als realistisch ist, erzählt es immer noch, wie einfach es ist, unnötig Verhalten anzusammeln, das sich häufig an einem einzigen Ort ändern muss. Wir können es besser machen!

"Ruby-Klasse Specter #…

def dispose_of_agent (spectre_agent) schreibt: „Sie haben diese Organisation enttäuscht. Sie wissen, wie Specter mit Fehlern umgeht. Auf Wiedersehen # spectre_agent.code_name! “End end

Klasse PaymentHandler STANDARD_CHARGE = 10000000 STANDARD_CONTRACTOR_PAYOUT = 200000

#…

def initialize (payment_handler = PaymentGem) @payment_handler = payment_handler end

def charge_for_initializing_operation (evil_client) evil_client_id = @ payment_handler.find_client (evil_client.email) .payments_id @ payment_handler.charge (evil_client_id, STANDARD_CHARGE) end

def contractor_payout (contractor) spectre_agent_id = @ payment_handler.find_contractor (contractor.email) .payments_id @ payment_handler.transfer_funds (spectre_agent_id, STANDARD_CONTRACTOR_PAYOUT) end end end

Klasse EvilClient #…

def charge_for_initializing_operation PaymentHandler.new.charge_for_initializing_operation (selbst) end end

Klasse Auftragnehmer #…

def process_payout PaymentHandler.new.contractor_payout (selbst) end end

Klasse Operation attr_accessor: Auftragnehmer,: Ziel,: Termin

def initialize (attrs = ) @contractor = attrs [: Auftragnehmer] @objective = attrs [: Ziel] @deadline = attrs [: Deadline] end

def print_operation_assignment print "# Auftragnehmer wird # Ziel zugewiesen. Die Missionsfrist endet um # Deadline. "End end"

Hier haben wir eine Reihe von Klassen extrahiert und ihnen ihre eigenen, einzigartigen Verantwortlichkeiten gegeben - und damit auch ihren eigenen Grund, sich zu ändern.

Sie möchten mit Zahlungen anders umgehen? Jetzt müssen Sie das nicht mehr berühren Gespenst Klasse. Sie müssen anders berechnen oder bezahlen? Wieder muss die Datei nicht geöffnet werden Gespenst. Das Zuweisen von Druckvorgängen ist jetzt Aufgabe des Betriebs. Das ist es. Nicht zu kompliziert, denke ich, aber definitiv einer der häufigsten Gerüche, mit denen man früh umgehen sollte.

Abschließende Gedanken

Ich hoffe, Sie sind an dem Punkt angelangt, an dem Sie sich bereit fühlen, diese Refactorings in Ihrem eigenen Code zu testen, und Sie können Code-Gerüche in Ihrer Umgebung leichter erkennen. Hüten Sie sich vor uns, dass wir gerade erst angefangen haben, aber Sie haben schon ein paar große angegangen. Ich wette, es war nicht so schwierig, wie Sie vielleicht gedacht hätten!

Sicher, Beispiele aus der realen Welt werden viel schwieriger sein, aber wenn Sie die Mechanismen und Muster zum Erkennen von Gerüchen verstanden haben, können Sie sich schnell an realistische Komplexitäten anpassen.