2010-08-12 2 views
9

Ok, ich versuche hier nicht, einen Flammenkrieg zu starten, und ich weiß, dass der Streit zwischen statischen und dynamischen Sprachen viele Male, einschließlich hier, ausgehebelt wurde. Aber ich habe eine sehr praktische Frage, die hoffentlich jemand hier beleuchten kann. Sorry für die Länge, aber das ist keine einfache Frage mit wahrscheinlich keine einfache Antwort.Refactoring mit dynamisch typisierter Sprache

Ruby, PHP und Javascript sind heutzutage ziemlich populäre Sprachen, und sie haben eine Menge Leute, die sie verteidigen und argumentieren, dass die dynamische Eingabe den Entwickler nicht zurückhält. Ich bin neu in diesen Sprachen und möchte sie für größere Projekte verwenden, aber hier ist ein grundlegendes Refactoring-Szenario, das die ganze Zeit bei der Arbeit auftaucht (work == C#), und ich frage mich, wie der Ansatz aussehen würde Ruby - Ich habe Ruby gewählt, weil es OO ist.

Ok, ich benutze Ruby, und ich baue ein Kundenobjekt. Es hat Methoden zum Laden/Speichern/Löschen von der Datenbank. Es ist gut und die Leute benutzen es. Ich füge mehr Methoden für andere Dinge hinzu und die Leute benutzen es mehr. Ich füge eine Methode zur Berechnung der Bestellhistorie basierend auf einigen Parametern hinzu. Mittlerweile wird diese Klasse im gesamten System verwendet. Dann entscheide ich eines Tages, die Parameter der GetOrderHistory-Methode zu ändern. So I:

  • fügen Sie die neuen Parameter an die Methode
  • den Code der Methode umschreiben die neuen Parameter verwenden
  • Änderung der Client-Code, den ich im Sinn hatte, um die neuen Parameter zu übergeben und verwenden diese modifizierte Methode

Aber was nun? Ich habe dutzende/hunderte/whoknows, wie viele andere Stellen im System geändert werden müssen. Wie würde ich in einer dynamischen OO-Sprache wie Ruby oder Javascript vorgehen?

Aus der Spitze von meinem Kopf, nicht sehr gut Rubin zu kennen, kann ich von zwei dummen Antworten denkt:

  1. 100% Deckung-Code. Ich teste die gesamte App und jedes Mal, wenn es bricht, sehe ich, ob es diese Methode ist und es beheben
  2. Suchen und Ersetzen. Ich benutze die Textsuche, um nach dieser Methode zu suchen. Aber ich könnte andere Objekte mit denselben Methodennamen haben.

Also gibt es eine gute Antwort darauf? Es scheint, dass die IDE es schwer haben würde. Wenn ich als

solchen Code hatte
c = Customer.new 

es wäre in der Lage sein, es herauszufinden, aber was, wenn es

c= SomeFunctionThatProbablyReturnsACustomerButMightReturnOtherThings() 

So wie würden Sie Ruby-Experten nehmen in diesem Fall?

+0

Ich bin ziemlich sicher, dass Refactoring bestehende Verträge nicht brechen sollte ... – CurtainDog

+0

@CurtainDog was ist mit dem Umbenennen einer privaten Funktion in einer Bibliothek? – Toskan

+0

ein anderes gutes Beispiel ist Ruby und Python. Darf ich Sie freundlich daran erinnern, dass weder Ruby noch Python bei dem, was Sie gerade gesagt haben, bei der Veröffentlichung neuer Versionen bleiben? – Toskan

Antwort

3

Einer der starken Argumente, die Sie hören werden, ist, dass Sie Tests vorher schreiben soll. Dies würde Ihnen theoretisch genau zeigen, wo sich die App ändern muss, falls sich etwas anderes ändert.

Aber das ist nur die Spitze des Eisbergs. Ruby ist mit bestimmten Richtlinien konzipiert, wie kurze, ausdrucksstarke Funktionen, Aufteilung der Verantwortlichkeiten in Modulen, Nichtwiederholung des Codes (DRY), das Prinzip der geringsten Überraschung, etc .; plus eine Reihe von empfohlenen Praktiken, wie Testen zuerst, Übergabe von Parametern als Hash-Optionen, mit Metaprogrammierung weise usw. Ich bin sicher, dass andere dynamische Sprachen dies auch tun.

Wenn c kein Kunde ist, dann würde ich zumindest erwarten, act wie eins. IDEs können nach Duck-Typing suchen, was flexibler ist, als nach einer Instanz einer bestimmten Klasse zu suchen.

Einige IDEs (zumindest Rubymine) schauen sich auch Konventionen an. Beispielsweise geht Rubymine in Rails-Anwendungen in die Schemadatei und fügt die Modelleigenschaften in der Datenbank als Methoden hinzu. Es erkennt auch die Assoziationen (has_many, gehört_to, etc.) und fügt dynamisch die entsprechenden Methoden hinzu, die Rails unter der Haube generiert.

Nun reduziert dies die Notwendigkeit eines Refactorings, zumindest auf ein Minimum. Aber sicherlich löst es nicht. Und ich denke nicht, dass es gelöst werden kann.

+0

gute einblicke, danke. Die Ente-Tipp-Idee ist besonders interessant, ich werde mehr darüber nachdenken. In einem anderen Post hier erwähnte jemand auch Flow Analysis, was meiner Meinung nach eine intelligentere Vorkompilierungs-Sache ist, die herausfinden kann, was der Runtime-Typ sein wird. Klingt kompliziert, aber ... – LoveMeSomeCode

+1

auch nicht argumentativ, aber die Idee von Tests stört mich. Nicht die Tests selbst, weil ich glaube, dass jeder gute Code gründliche Komponententests haben sollte, aber die Idee der Ausführlichkeit. Viele Leute werden argumentieren, dass dynamische Sprachen weniger ausführlich sind, weil Sie keine Typdeskriptoren benötigen, aber wenn dann über Typensicherheit gesprochen wird, werden sie die Notwendigkeit von zusätzlichen Tests erwähnen, die den prägnanten Code, den sie geschrieben haben, aufheben . nur eine allgemeine Beobachtung ... – LoveMeSomeCode

+0

Nun es tut mir leid ... aber konnten Sie nicht, und ich meine es ernst, argumentieren, dass diese Konzepte in allen Programmiersprachen gelten sollten? Nicht in der Lage zu sein, tatsächlich zu refaktorieren, ist ein großes Problem. – Toskan

1

Dies wird wahrscheinlich nicht die beste Antwort auf Ihre Frage sein, aber ich neige dazu, Methoden zu entwerfen, um einen Hash zu akzeptieren, um zukünftige Änderungen abzudecken.

Beispiel:

def my_method(options = {}) 
    if options[:name] 
    ... 
    end 
end 

Ich glaube, dass viele der fortgeschritteneren Ruby-Leute zur Umsetzung eine Art Metaprogrammierung Muster aussehen würde.

Andere Optionen können das Überfahren der Methode in einer Unterklasse umfassen, um die von Ihnen gewünschte Funktionalität auszuführen.Oder

, wie etwa ...

def get_order_history(required_param, options = []) 

    @required_param = required_param 

    if options[:do_something_else] 
    result = other_method(options[:do_something_else]) 
    else 
    result = ... 
    end 

    result  

end 

def other_method(something_else) 
    ... 
end 
+0

Subclassing ist ein guter Punkt. Das ist eine der Optionen, die ich in Betracht gezogen habe, nachdem ich gepostet habe, aber dann dachte ich: "Was ist, wenn sich die Methode wirklich ändert, etwa aufgrund einer Änderung des Datenbankschemas, und Sie den alten Code überhaupt nicht brauchen und jeder muss Verwenden Sie die neue Methode? – LoveMeSomeCode