Übergeben von Hashes anstelle von Methodenparametern [geschlossen]


78

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?


11
Ein kleiner Hinweis: {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)
sarahhodne

Antworten:


28

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.


1
Wenn ich nun einen Options-Hash hätte, könnte ich dann einfach den gesamten Hash eingeben oder muss ich die Schlüssel => -Werte einzeln übergeben?
FilBot3

1
Es ist wahr, dass beide Vor- und Nachteile haben, aber ich bin mit der allgemeinen Regel nicht einverstanden. Ich glaube, Optionen sind im Allgemeinen eine schlechte Praxis und sollten nur verwendet werden, wenn dies der einzige Weg ist, ein Problem zu lösen.
Dragan Nikolic

42

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.


Ab Ruby 2.7 sind implizite Hash-Parameter veraltet und werden in Ruby 3.0 entfernt. Dies bedeutet, dass wir erneut {…} 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 .
Tanius

8

Ich würde das sagen, wenn Sie entweder sind:

  1. Mit mehr als 6 Methodenparametern
  2. Übergeben von Optionen , für die einige erforderlich, einige optional und einige mit Standardwerten 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 @optionTag 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

3

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.


3

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.


1
Wenn Sie eine variable Anzahl von Argumenten benötigen, die nur verwendet werden def method(*args), lösen Hashes dieses spezielle Problem nicht
MBO

1
Vielen Dank für den Hinweis auf die mögliche Verwirrung. Ich habe wirklich an den Fall gedacht, in dem das Originalposter angehoben wurde, bei dem die Reihenfolge und Anzahl der Parameter variabel ist.
Chris McCauley

1
Um diesem Punkt hinzuzufügen, ist dieses Anti-Muster auch als Magic Container bekannt
jtzero

3

Der Vorteil der Verwendung eines Hashas-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.)


Obwohl Ihre Aussage wahr ist, denke ich nicht, dass dies der beste Weg ist, um eine Bibliothek kompatibel zu halten. Sie können dasselbe erreichen, indem Sie einfach optional neue Parameter hinzufügen. Sie beeinträchtigen die Kompatibilität nicht und bieten dennoch Klarheit und Selbstdokumentation.
Dragan Nikolic

@DraganNikolic Ich bin auch mit dem Sany Metz-Zeug an Bord - die Vorteile, keine Liste manueller Parameter erstellen zu müssen, sind riesig und es gibt andere Möglichkeiten, Code selbst zu dokumentieren, die im Buch
Mirv - Matt

Eine andere Sache, die viele (einschließlich mir!) Oft vergessen, ist, dass die Reihenfolge der Parameter auch eine Abhängigkeit ist! Dies bedeutet, dass Sie nur optionale Parameter verwenden können, um die Reihenfolge der Parameter aus irgendeinem Grund nicht zu ändern (aber normalerweise möchten Sie sowieso nicht viele Argumente!)
Aldo 'xoen' Giambelluca

2

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.


1

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.


1

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.


0

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.


0

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
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.