Ich sehe, dass in Ruby (und in dynamisch typisierten Sprachen im Allgemeinen) eine sehr übliche Praxis darin besteht, einen Hash zu übergeben, anstatt konkrete Methodenparameter zu deklarieren. Anstatt beispielsweise eine Methode mit Parametern zu deklarieren und wie folgt aufzurufen:
def my_method(width, height, show_border)
my_method(400, 50, false)
Sie können es so machen:
def my_method(options)
my_method({"width" => 400, "height" => 50, "show_border" => false})
Ich würde gerne Ihre Meinung dazu erfahren. Ist es eine gute oder eine schlechte Praxis, sollten wir es tun oder nicht? In welcher Situation ist die Anwendung dieser Praxis gültig und in welcher Situation kann sie gefährlich sein?
ruby
oop
coding-style
rmaruszewski
quelle
quelle
{width => 400, height => 50, show_border => false}
ist keine gültige Ruby-Syntax. Ich denke du meinst{:width => 400, :height => 50, :show_border => false}
oder{width: 400, height: 50, show_border: false}
(letzteres gilt nur in Ruby 1.9.1)Antworten:
Beide Ansätze haben ihre eigenen Vor- und Nachteile. Wenn Sie einen Options-Hash verwenden, der Standardargumente ersetzt, verlieren Sie Klarheit im Code, der die Methode definiert, aber Klarheit, wenn Sie die Methode verwenden, aufgrund der pseudo-benannten Parameter, die mithilfe eines Options-Hash erstellt wurden.
Meine allgemeine Regel lautet: Wenn Sie entweder viele Argumente für eine Methode (mehr als 3 oder 4) oder viele optionale Argumente haben, verwenden Sie einen Options-Hash, andernfalls verwenden Sie Standardargumente. Bei Verwendung eines Options-Hashs ist es jedoch wichtig, der Methodendefinition, die die möglichen Argumente beschreibt, immer einen Kommentar beizufügen.
quelle
Ruby hat implizite Hash-Parameter, sodass Sie auch schreiben können
def my_method(options = {}) my_method(:width => 400, :height => 50, :show_border => false)
und mit Ruby 1.9 und neuer Hash-Syntax kann es sein
my_method( width: 400, height: 50, show_border: false )
Wenn eine Funktion mehr als 3-4 Parameter benötigt, ist es viel einfacher zu erkennen, welche was ist, ohne die jeweiligen Positionen zu zählen.
quelle
{…}
explizit den Hash benötigen , um einen Hash zu übergeben ( siehe ). Die hier gezeigte Ruby 1.9-Syntax ist weiterhin zum Aufrufen einer Methode mit Schlüsselwortparametern möglich .Ich würde das sagen, wenn Sie entweder sind:
Sie möchten höchstwahrscheinlich einen Hash verwenden. Es ist viel einfacher zu erkennen, was die Argumente bedeuten, ohne in der Dokumentation nachzuschlagen.
Für diejenigen unter Ihnen, die sagen, es sei schwierig herauszufinden, welche Optionen eine Methode bietet, bedeutet dies nur, dass der Code schlecht dokumentiert ist. Mit YARD können Sie das
@option
Tag verwenden, um Optionen anzugeben:## # Create a box. # # @param [Hash] options The options hash. # @option options [Numeric] :width The width of the box. # @option options [Numeric] :height The height of the box. # @option options [Boolean] :show_border (false) Whether to show a # border or not. def create_box(options={}) options[:show_border] ||= false end
Aber in diesem speziellen Beispiel gibt es so wenige und einfache Parameter, dass ich denke, ich würde damit anfangen:
## # Create a box. # # @param [Numeric] width The width of the box. # @param [Numeric] height The height of the box. # @param [Boolean] show_border Whether to show a border or not. def create_box(width, height, show_border=false) end
quelle
Ich denke, diese Methode der Parameterübergabe ist viel klarer, wenn es mehr als ein paar Parameter gibt oder wenn es eine Reihe optionaler Parameter gibt. Es macht Methodenaufrufe offensichtlich selbstdokumentierend.
quelle
In Ruby ist es nicht üblich, einen Hash anstelle formaler Parameter zu verwenden.
Ich denke, dies wird mit dem üblichen Muster verwechselt , einen Hash als Parameter zu übergeben, wenn der Parameter eine Reihe von Werten annehmen kann, z. B. das Festlegen von Attributen eines Fensters in einem GUI-Toolkit.
Wenn Sie eine Reihe von Argumenten für Ihre Methode oder Funktion haben, deklarieren Sie diese explizit und übergeben Sie sie. Sie erhalten den Vorteil, dass der Interpreter überprüft, ob Sie alle Argumente übergeben haben.
Missbrauche die Sprachfunktion nicht, weiß, wann du sie verwenden sollst und wann du sie nicht verwenden sollst.
quelle
def method(*args)
, lösen Hashes dieses spezielle Problem nichtDer Vorteil der Verwendung eines
Hash
as-Parameters besteht darin, dass Sie die Abhängigkeit von der Anzahl und Reihenfolge der Argumente entfernen.In der Praxis bedeutet dies, dass Sie später die Flexibilität haben, Ihre Methode umzugestalten / zu ändern, ohne die Kompatibilität mit dem Clientcode zu beeinträchtigen (und dies ist sehr gut beim Erstellen von Bibliotheken, da Sie den Clientcode nicht tatsächlich ändern können).
(Sandy Metz ' "Praktisches objektorientiertes Design in Ruby" ist ein großartiges Buch, wenn Sie sich für Software-Design in Ruby interessieren.)
quelle
Es ist eine gute Praxis. Sie müssen nicht über die Methodensignatur und die Reihenfolge der Argumente nachdenken. Ein weiterer Vorteil ist, dass Sie die Argumente, die Sie nicht eingeben möchten, leicht weglassen können. Sie können sich das ExtJS-Framework ansehen, da es diese Art von Argumenten verwendet, die häufig übergeben werden.
quelle
Es ist ein Kompromiss. Sie verlieren an Klarheit (woher weiß ich, welche Parameter übergeben werden müssen) und überprüfen (habe ich die richtige Anzahl von Argumenten übergeben?) Und gewinnen Flexibilität (die Methode kann Standardparameter verwenden, die nicht empfangen werden. Wir können eine neue Version bereitstellen, die erforderlich ist mehr Parameter und brechen keinen vorhandenen Code)
Sie können diese Frage als Teil der größeren Diskussion über starke / schwache Typen sehen. Siehe Steve Yegges Blog hier. Ich habe diesen Stil in C und C ++ in Fällen verwendet, in denen ich eine recht flexible Argumentübergabe unterstützen wollte. Wohl ein Standard-HTTP-GET mit einigen Abfrageparametern ist genau dieser Stil.
Wenn Sie sich für den Hash-Ansatz entscheiden, müssen Sie sicherstellen, dass Ihre Tests wirklich gut sind. Probleme mit falsch geschriebenen Parameternamen werden nur zur Laufzeit angezeigt.
quelle
Ich bin mir sicher, dass es niemanden interessiert, der dynamische Sprachen verwendet, aber denken Sie an die Leistungseinbußen, mit denen Ihr Programm konfrontiert wird, wenn Sie anfangen, Hashes an Funktionen zu übergeben.
Der Interpreter ist möglicherweise intelligent genug, um ein statisches const-Hash-Objekt zu erstellen und es nur mit einem Zeiger zu referenzieren, wenn der Code einen Hash mit allen Elementen verwendet, die Quellcodeliterale sind.
Wenn jedoch eines dieser Elemente Variablen sind, muss der Hash bei jedem Aufruf rekonstruiert werden.
Ich habe einige Perl-Optimierungen vorgenommen, und solche Dinge können sich in inneren Code-Schleifen bemerkbar machen.
Funktionsparameter arbeiten viel besser.
quelle
Im Allgemeinen sollten wir immer Standardargumente verwenden, es sei denn, dies ist nicht möglich. Die Verwendung von Optionen, wenn Sie sie nicht verwenden müssen, ist eine schlechte Praxis. Standardargumente sind klar und selbst dokumentiert (wenn sie richtig benannt sind).
Ein (und möglicherweise der einzige) Grund für die Verwendung von Optionen besteht darin, dass die Funktion Argumente empfängt, die nicht verarbeitet, sondern nur an eine andere Funktion übergeben werden.
Hier ist ein Beispiel, das Folgendes veranschaulicht:
def myfactory(otype, *args) if otype == "obj1" myobj1(*args) elsif otype == "obj2" myobj2(*args) else puts("unknown object") end end def myobj1(arg11) puts("this is myobj1 #{arg11}") end def myobj2(arg21, arg22) puts("this is myobj2 #{arg21} #{arg22}") end
In diesem Fall kennt 'myfactory' nicht einmal die Argumente, die für 'myobj1' oder 'myobj2' erforderlich sind. 'myfactory' übergibt die Argumente nur an 'myobj1' und 'myobj2' und es liegt in ihrer Verantwortung, sie zu überprüfen und zu verarbeiten.
quelle
Hashes sind besonders nützlich, um mehrere optionale Argumente zu übergeben. Ich verwende Hash zum Beispiel, um eine Klasse zu initialisieren, deren Argumente optional sind.
BEISPIEL
class Example def initialize(args = {}) @code code = args[:code] # No error but you have no control of the variable initialization. the default value is supplied by Hash @code = args.fetch(:code) # returns IndexError exception if the argument wasn't passed. And the program stops # Handling the execption begin @code = args.fetch(:code) rescue @code = 0 end end
quelle