Ist es eine schlechte Idee, verschiedene Datentypen von einer einzelnen Funktion in einer dynamisch typisierten Sprache zurückzugeben?

65

Meine Hauptsprache ist statisch (Java). In Java müssen Sie von jeder Methode einen einzelnen Typ zurückgeben. Sie können beispielsweise keine Methode haben, die bedingt a Stringoder bedingt a zurückgibt Integer. In JavaScript ist dies beispielsweise sehr gut möglich.

In einer statisch getippten Sprache verstehe ich, warum dies eine schlechte Idee ist. Wenn jede Methode zurückgegeben wird Object(das gemeinsame übergeordnete Element, von dem alle Klassen erben), wissen Sie und der Compiler nicht, womit Sie es zu tun haben. Sie müssen alle Ihre Fehler zur Laufzeit entdecken.

In einer dynamisch getippten Sprache gibt es möglicherweise nicht einmal einen Compiler. In einer dynamisch getippten Sprache ist mir nicht klar, warum eine Funktion, die mehrere Typen zurückgibt, eine schlechte Idee ist. Aufgrund meines Hintergrunds in statischen Sprachen vermeide ich es, solche Funktionen zu schreiben, aber ich fürchte, ich bin sehr gespannt auf eine Funktion, die meinen Code auf eine Weise bereinigen könnte, die ich nicht sehen kann.


Bearbeiten : Ich werde mein Beispiel entfernen (bis ich mir ein besseres vorstellen kann). Ich denke, es lenkt die Leute, auf einen Punkt zu antworten, den ich nicht anstrebe.

Daniel Kaplan
quelle
Warum nicht eine Ausnahme im Fehlerfall auslösen?
TrueWill
1
@ TrueWill Ich adressiere das im nächsten Satz.
Daniel Kaplan
Ist Ihnen aufgefallen, dass dies in dynamischeren Sprachen üblich ist? Es ist in funktionalen Sprachen üblich, aber dort sind die Regeln sehr explizit. Und ich weiß, dass es insbesondere in JavaScript üblich ist, Argumente in unterschiedlichen Typen zuzulassen (da dies im Wesentlichen die einzige Möglichkeit ist, Funktionsüberladungen durchzuführen), aber ich habe selten gesehen, dass dies auf Rückgabewerte angewendet wird. Das einzige Beispiel, an das ich denken kann, ist PowerShell mit seinem weitgehend automatischen Ein- und Auspacken von Arrays, und Skriptsprachen sind ein Ausnahmefall.
Aaronaught
3
Nicht erwähnt, aber es gibt viele Beispiele (auch in Java Generics) für Funktionen, die einen Rückgabetyp als Parameter verwenden. zB in Common Lisp haben wir (coerce var 'string)Ausbeuten a stringoder (concatenate 'string this that the-other-thing)ähnlich. Ich habe auch Dinge wie geschrieben ThingLoader.getThingById (Class<extends FindableThing> klass, long id). Und dort könnte ich nur etwas zurückgeben, das loader.getThingById (SubclassA.class, 14)SubclassBSubclassA
untergeordnet ist
1
Dynamisch getippte Sprachen sind wie der Löffel im Film Die Matrix . Versuchen Sie nicht, den Löffel als Zeichenfolge oder Zahl zu definieren . Das wäre unmöglich. Stattdessen ... versuche nur die Wahrheit zu verstehen. Dass es keinen Löffel gibt .
Reactgular

Antworten:

42

Im Gegensatz zu anderen Antworten gibt es Fälle, in denen die Rückgabe unterschiedlicher Typen akzeptabel ist.

Beispiel 1

sum(2, 3)  int
sum(2.1, 3.7)  float

In einigen statisch typisierten Sprachen ist dies mit Überladungen verbunden, sodass wir davon ausgehen können, dass es mehrere Methoden gibt, von denen jede den vordefinierten festen Typ zurückgibt. In dynamischen Sprachen kann dies dieselbe Funktion sein, die implementiert ist als:

var sum = function (a, b) {
    return a + b;
};

Gleiche Funktion, verschiedene Arten von Rückgabewerten.

Beispiel 2

Stellen Sie sich vor, Sie erhalten eine Antwort von einer OpenID / OAuth-Komponente. Einige OpenID / OAuth-Anbieter enthalten möglicherweise weitere Informationen, z. B. das Alter der Person.

var user = authProvider.findCurrent();
// user is now:
// {
//     provider: 'Facebook',
//     name: {
//         firstName: 'Hello',
//         secondName: 'World',
//     },
//     email: '[email protected]',
//     age: 27
// }

Andere hätten das Minimum, wäre es eine E-Mail-Adresse oder das Pseudonym.

var user = authProvider.findCurrent();
// user is now:
// {
//     provider: 'Google',
//     email: '[email protected]'
// }

Wieder gleiche Funktion, unterschiedliche Ergebnisse.

Hier ist der Vorteil der Rückgabe verschiedener Typen besonders wichtig, wenn Sie sich nicht für Typen und Schnittstellen interessieren, sondern für welche Objekte sie tatsächlich enthalten. Stellen wir uns zum Beispiel vor, eine Website enthält eine ausgereifte Sprache. Dann findCurrent()kann das so verwendet werden:

var user = authProvider.findCurrent();
if (user.age || 0 >= 16) {
    // The person can stand mature language.
    allowShowingContent();
} else if (user.age) {
    // OpenID/OAuth gave the age, but the person appears too young to see the content.
    showParentalAdvisoryRequestedMessage();
} else {
    // OpenID/OAuth won't tell the age of the person. Ask the user himself.
    askForAge();
}

Das Umgestalten in Code, bei dem jeder Anbieter seine eigene Funktion hat, die einen genau definierten, festen Typ zurückgibt, würde nicht nur die Codebasis verschlechtern und eine Code-Duplizierung verursachen, sondern auch keinen Nutzen bringen. Man kann am Ende Horror machen wie:

var age;
if (['Facebook', 'Yahoo', 'Blogger', 'LiveJournal'].contains(user.provider)) {
    age = user.age;
}
Arseni Mourzenko
quelle
5
"In statisch typisierten Sprachen bedeutet dies Überladungen" Ich denke, Sie meinten "in einigen statisch typisierten Sprachen" :) Gute statisch typisierte Sprachen erfordern für etwas wie Ihr sumBeispiel keine Überladung .
Andres F.
17
Für Ihr spezifisches Beispiel betrachtet Haskell: sum :: Num a => [a] -> a. Sie können eine Liste von allem zusammenfassen, das eine Zahl ist. Wenn Sie im Gegensatz zu Javascript versuchen, etwas zu summieren, das keine Zahl ist, wird der Fehler beim Kompilieren abgefangen.
Andres F.
3
@MainMa In Scala gibt es Iterator[A]eine Methode def sum[B >: A](implicit num: Numeric[B]): B, die es erlaubt, beliebige Zahlen zu addieren. Sie wird beim Kompilieren überprüft.
Petr Pudlák
3
@Bakuriu Natürlich können Sie eine solche Funktion schreiben, obwohl Sie zuerst entweder eine Überladung +für Zeichenfolgen (durch Implementierung Num, was eine schreckliche Idee ist, die aber rechtmäßig ist) oder einen anderen Operator / eine andere Funktion erfinden müssen, die durch Ganzzahlen und Zeichenfolgen überladen ist beziehungsweise. Haskell hat einen Ad-hoc-Polymorphismus über Typklassen. Eine Liste, die sowohl Ganzzahlen als auch Zeichenfolgen enthält, ist viel schwieriger (möglicherweise ohne Spracherweiterungen unmöglich), aber eine ganz andere Angelegenheit.
2
Ihr zweites Beispiel beeindruckt mich nicht besonders. Diese beiden Objekte haben den gleichen konzeptionellen "Typ", es ist nur so, dass in einigen Fällen bestimmte Felder nicht definiert oder nicht ausgefüllt sind. Sie könnten , dass nur gut vertreten , auch in einer statisch typisierte Sprache mit nullWerten.
Aaronaught
31

Im Allgemeinen ist es aus den gleichen Gründen eine schlechte Idee, dass das moralische Äquivalent in einer statisch typisierten Sprache eine schlechte Idee ist: Sie haben keine Ahnung, welcher konkrete Typ zurückgegeben wird, Sie haben also keine Ahnung, was Sie mit dem Ergebnis tun können (abgesehen von einige Dinge, die mit jedem Wert getan werden können). In einem statischen Typsystem verfügen Sie über vom Compiler überprüfte Annotationen von Rückgabetypen und dergleichen, aber in einer dynamischen Sprache ist das gleiche Wissen immer noch vorhanden - es ist nur informell und wird in den Köpfen und in der Dokumentation gespeichert, nicht im Quellcode.

In vielen Fällen gibt es jedoch einen Reim und einen Grund, zu dem der Typ zurückgegeben wird, und der Effekt ähnelt der Überladung oder dem parametrischen Polymorphismus in statischen Typsystemen. Mit anderen Worten, der Ergebnistyp ist vorhersehbar und nicht ganz so einfach auszudrücken.

Beachten Sie jedoch, dass eine bestimmte Funktion möglicherweise aus anderen Gründen nicht richtig entworfen wurde: Eine sumFunktion, die bei ungültigen Eingaben false zurückgibt, ist in erster Linie deshalb eine schlechte Idee, weil dieser Rückgabewert unbrauchbar und fehleranfällig ist (0 <-> false confusion).


quelle
40
Meine zwei Cent: Ich hasse es, wenn das passiert. Ich habe JS-Bibliotheken gesehen, die null zurückgeben, wenn es keine Ergebnisse gibt, ein Objekt für ein Ergebnis und ein Array für zwei oder mehr Ergebnisse. Anstatt nur eine Schleife durch ein Array zu führen, müssen Sie feststellen, ob es null ist und ob es nicht, ob es ein Array ist und ob es eine Sache ist, oder ob Sie etwas anderes tun müssen. Insbesondere, da jeder vernünftige Entwickler im normalen Sinne einfach das Objekt zu einem Array hinzufügt und die Programmlogik von der Verarbeitung eines Arrays wiederverwendet.
Phyrfox
10
@ Izkata: Ich denke nicht, dass NaNes überhaupt "Kontrapunkt" ist. NaN ist tatsächlich eine gültige Eingabe für jede Gleitkommaberechnung. Sie können alles damit machen NaN, was Sie mit einer tatsächlichen Nummer machen könnten. Zwar ist das Endergebnis dieser Berechnung für Sie nicht sehr nützlich, es führt jedoch nicht zu merkwürdigen Laufzeitfehlern, und Sie müssen es nicht ständig überprüfen - Sie können es nur einmal am Ende von a überprüfen Reihe von Berechnungen. NaN ist kein anderer Typ , es ist nur ein spezieller Wert , ähnlich einem Null-Objekt .
Aaronaught
5
@Aaronaught Auf der anderen Seite NaNneigt das Objekt, wie das Nullobjekt, dazu, sich zu verstecken, wenn Fehler auftreten, nur damit sie später angezeigt werden . Zum Beispiel wird Ihr Programm wahrscheinlich explodieren, wenn es NaNin eine Schleifenbedingung gerät.
Izkata
2
@ Izkata: NaN und Null haben völlig unterschiedliche Verhaltensweisen. Eine Nullreferenz explodiert sofort nach dem Zugriff, ein NaN breitet sich aus. Warum letzteres passiert, liegt auf der Hand: Sie können mathematische Ausdrücke schreiben, ohne das Ergebnis jedes Unterausdrucks überprüfen zu müssen. Persönlich sehe ich null als weitaus schädlicher an, da NaN ein geeignetes numerisches Konzept darstellt, ohne das man nicht durchkommt, und mathematisch gesehen ist Propagierung das richtige Verhalten.
Phoshi
4
Ich weiß nicht, warum dies zu einem Argument "Null gegen alles andere" wurde. Ich habe lediglich darauf hingewiesen, dass der NaNWert (a) eigentlich kein anderer Rückgabetyp ist und (b) eine gut definierte Gleitkommasemantik hat und sich daher nicht als Analogon zu einem variabel typisierten Rückgabewert qualifiziert. Eigentlich ist es in der Ganzzahl-Arithmetik gar nicht so anders als Null . Wenn eine Null "versehentlich" in Ihre Berechnungen einfließt, erhalten Sie häufig entweder eine Null als Ergebnis oder einen Fehler beim Teilen durch Null. Sind die durch IEEE-Gleitkomma definierten "Unendlich" -Werte auch böse?
Aaronaught
26

In dynamischen Sprachen sollten Sie nicht fragen, ob unterschiedliche Typen, sondern Objekte mit unterschiedlichen APIs zurückgegeben werden sollen . Da sich die meisten dynamischen Sprachen nicht wirklich für Typen interessieren, sondern verschiedene Versionen von Duck Typing verwenden .

Bei Rücksendung sind unterschiedliche Typen sinnvoll

Zum Beispiel ist diese Methode sinnvoll:

def load_file(file): 
    if something: 
       return ['a ', 'list', 'of', 'strings'] 
    return open(file, 'r')

Da sowohl file als auch eine Liste von Strings (in Python) iterable sind, wird der String zurückgegeben. Sehr unterschiedliche Typen, dieselbe API (es sei denn, jemand versucht, Dateimethoden in einer Liste aufzurufen, dies ist jedoch eine andere Geschichte).

Sie können bedingt listoder zurückgeben tuple( tupleist eine unveränderliche Liste in Python).

Formal sogar dabei:

def do_something():
    if ...: 
        return None
    return something_else

oder:

function do_something(){
   if (...) return null; 
   return sth;
}

gibt verschiedene Typen zurück, da sowohl Python Noneals auch Javascript nulleigenständige Typen sind.

Alle diese Anwendungsfälle hätten ihre Entsprechung in statischen Sprachen, die Funktion würde nur eine richtige Schnittstelle zurückgeben.

Bei der bedingten Rückgabe von Objekten mit unterschiedlicher API ist eine gute Idee

In den meisten Fällen ist es nicht sinnvoll, verschiedene APIs zurückzugeben. Nur ein sinnvolles Beispiel, das mir in den Sinn kommt, kommt dem, was @MainMa gesagt hat, sehr nahe : Wenn Ihre API unterschiedliche Detaillierungsgrade liefern kann, ist es möglicherweise sinnvoll, mehr Details zurückzugeben, wenn diese verfügbar sind.

jb.
quelle
4
Gute Antwort. Es ist zu beachten, dass das Java- / statisch typisierte Äquivalent eine Funktion haben soll, die eine Schnittstelle anstelle eines bestimmten konkreten Typs zurückgibt, wobei die Schnittstelle die API / Abstraktion darstellt.
Mikera
1
Ich denke, Sie sind ein Trottel, in Python können eine Liste und ein Tupel von verschiedenen "Typen" sein, aber sie sind vom gleichen "Ententyp", dh sie unterstützen die gleichen Operationen. Und genau in solchen Fällen wurde das Duck Typing eingeführt. Sie können auch in Java long x = getInt () ausführen.
Kaerber
"Verschiedene Typen zurückgeben" bedeutet "Objekte mit verschiedenen APIs zurückgeben". (Einige Sprachen machen die Art und Weise, wie das Objekt erstellt wird, zu einem grundlegenden Teil der API, andere nicht. Dies hängt davon ab, wie aussagekräftig das Typsystem ist.) In Ihrem Listen- / do_fileDateibeispiel wird in beiden Fällen eine Iteration von Zeichenfolgen zurückgegeben.
Gilles
1
In diesem Python-Beispiel können Sie auch iter()sowohl die Liste als auch die Datei aufrufen , um sicherzustellen, dass das Ergebnis nur in beiden Fällen als Iterator verwendet werden kann.
RemcoGerlich
1
returning None or somethingist ein Killer für die Leistungsoptimierung, die Tools wie PyPy, Numba, Pyston und andere leisten. Dies ist einer der Python-Ismen, die Python nicht so schnell machen.
Matt
9

Ihre Frage bringt mich dazu, ein wenig zu weinen. Nicht für die von Ihnen angegebene Beispielnutzung, sondern weil jemand diesen Ansatz unabsichtlich zu weit gehen wird. Es ist nur ein kurzer Schritt von lächerlich nicht zu wartendem Code entfernt.

Der Anwendungsfall der Fehlerbedingung macht Sinn, und das Nullmuster (alles muss ein Muster sein) in statisch typisierten Sprachen macht das Gleiche. Ihr Funktionsaufruf gibt ein objectoder zurück null.

Aber es ist nur ein kurzer Schritt, um zu sagen, "Ich werde dies verwenden, um ein Fabrikmuster zu erstellen " und entweder foooder baroder bazje nach Stimmung der Funktion zurückzukehren. Das Debuggen wird zu einem Albtraum, wenn der Anrufer dies erwartet, es fooaber erhalten hat bar.

Ich glaube also nicht, dass Sie aufgeschlossen sind. Sie sind in Bezug auf die Verwendung der Sprachfunktionen entsprechend vorsichtig.

Offenlegung: Mein Hintergrund liegt in statisch typisierten Sprachen vor, und ich habe im Allgemeinen in größeren, unterschiedlichen Teams gearbeitet, in denen ein relativ hoher Bedarf an wartbarem Code bestand. Meine Perspektive ist also wahrscheinlich auch verzerrt.

doppelgreener
quelle
6

Wenn Sie Generics in Java verwenden, können Sie einen anderen Typ zurückgeben, ohne die statische Typensicherheit zu beeinträchtigen. Sie geben einfach den Typ an, den Sie im generischen Typparameter des Funktionsaufrufs zurückgeben möchten.

Ob Sie einen ähnlichen Ansatz in Javascript verwenden könnten, ist natürlich eine offene Frage. Da Javascript eine dynamisch typisierte Sprache ist, objecterscheint es naheliegend, eine zurückzugeben.

Wenn Sie wissen möchten, wo ein dynamisches Rückgabeszenario funktionieren kann, wenn Sie an die Arbeit in statisch typisierter Sprache gewöhnt sind, sollten Sie sich das dynamicSchlüsselwort in C # ansehen. Rob Conery konnte mit dem Schlüsselwort erfolgreich einen objektrelationalen Mapper in 400 Codezeilen schreibendynamic .

Natürlich ist alles, was dynamicwirklich getan wird, eine objectVariable mit einer gewissen Laufzeitsicherheit zu verpacken .

Robert Harvey
quelle
4

Ich denke, es ist eine schlechte Idee, verschiedene Typen unter bestimmten Bedingungen zurückzugeben. Eine der für mich häufig vorkommenden Möglichkeiten ist, ob die Funktion einen oder mehrere Werte zurückgeben kann. Wenn nur ein Wert zurückgegeben werden muss, kann es sinnvoll sein, den Wert nur zurückzugeben, anstatt ihn in ein Array zu packen, um zu vermeiden, dass er in der aufrufenden Funktion entpackt werden muss. In diesem (und den meisten anderen Fällen) ist der Anrufer jedoch verpflichtet, beide Typen zu unterscheiden und zu behandeln. Die Funktion ist einfacher zu überlegen, wenn immer derselbe Typ zurückgegeben wird.

user116240
quelle
4

Die "schlechte Praxis" besteht unabhängig davon, ob Ihre Sprache statisch geschrieben ist. Die statische Sprache lenkt Sie eher von diesen Praktiken ab, und möglicherweise finden Sie mehr Benutzer, die sich über "schlechte Praktiken" in einer statischen Sprache beschweren, da es sich um eine formalere Sprache handelt. Die zugrunde liegenden Probleme liegen jedoch in einer dynamischen Sprache vor, und Sie können bestimmen, ob sie gerechtfertigt sind.

Hier ist der unangenehme Teil dessen, was Sie vorschlagen. Wenn ich nicht weiß, welcher Typ zurückgegeben wird, kann ich den Rückgabewert nicht sofort verwenden. Ich muss etwas "entdecken".

total = sum_of_array([20, 30, 'q', 50])
if (type_of(total) == Boolean) {
  display_error(...)
} else {
  record_number(total)
}

Oft ist diese Art der Codeumschaltung einfach eine schlechte Übung. Dies erschwert das Lesen des Codes. In diesem Beispiel sehen Sie, warum das Auslösen und Abfangen von Ausnahmefällen beliebt ist. Anders ausgedrückt: Wenn Ihre Funktion nicht das tun kann, was sie sagt, sollte sie nicht erfolgreich zurückkehren . Wenn ich Ihre Funktion aufrufe, möchte ich Folgendes tun:

total = sum_of_array([20, 30, 'q', 50])
display_number(total)

Da die erste Zeile erfolgreich zurückgegeben wird, gehe ich davon aus, dass sie totaltatsächlich die Summe des Arrays enthält. Wenn es nicht erfolgreich zurückkehrt, springen wir zu einer anderen Seite meines Programms.

Lassen Sie uns ein anderes Beispiel verwenden, das sich nicht nur mit der Verbreitung von Fehlern befasst. Vielleicht versucht sum_of_array, schlau zu sein und in einigen Fällen eine von Menschen lesbare Zeichenfolge zurückzugeben, wie "Das ist meine Schließfachkombination!" genau dann, wenn das Array [11,7,19] ist. Mir fällt es schwer, ein gutes Beispiel zu finden. Auf jeden Fall gilt das gleiche Problem. Sie müssen den Rückgabewert überprüfen, bevor Sie etwas damit anfangen können:

total = sum_of_array([20, 30, 40, 50])
if (type_of(total) == String) {
  write_message(total)
} else {
  record_number(total)
}

Sie könnten argumentieren, dass es nützlich wäre, wenn die Funktion eine Ganzzahl oder einen Gleitkomma zurückgibt, zB:

sum_of_array(20, 30, 40) -> int
sum_of_array(23.45, 45.67, 67.789044) -> float

Für Sie sind diese Ergebnisse jedoch keine unterschiedlichen Typen. Sie werden beide als Zahlen behandeln, und das ist alles, was Sie interessiert. Sum_of_array gibt also den Nummerntyp zurück. Darum geht es beim Polymorphismus.

Es gibt also einige Vorgehensweisen, gegen die Sie möglicherweise verstoßen, wenn Ihre Funktion mehrere Typen zurückgeben kann. Wenn Sie diese kennen, können Sie feststellen, ob Ihre bestimmte Funktion ohnehin mehrere Typen zurückgeben soll.

Travis Wilson
quelle
Tut mir leid, ich habe dich mit meinem armen Beispiel in die Irre geführt. Vergiss, ich habe es an erster Stelle erwähnt. Ihr erster Absatz ist auf den Punkt. Ich mag auch dein zweites Beispiel.
Daniel Kaplan
4

Tatsächlich ist es nicht sehr ungewöhnlich, unterschiedliche Typen auch in einer statisch typisierten Sprache zurückzugeben. Deshalb haben wir zum Beispiel Gewerkschaftstypen.

Tatsächlich geben Methoden in Java fast immer einen von vier Typen zurück: eine Art Objekt nulloder eine Ausnahme oder sie geben überhaupt nichts zurück.

In vielen Sprachen werden Fehlerzustände als Unterprogramme modelliert, die entweder einen Ergebnistyp oder einen Fehlertyp zurückgeben. Zum Beispiel in Scala:

def transferMoney(amount: Decimal): Either[String, Decimal]

Das ist natürlich ein dummes Beispiel. Der Rückgabetyp bedeutet "entweder eine Zeichenfolge oder eine Dezimalzahl zurückgeben". Konventionell ist der linke Typ der Fehlertyp (in diesem Fall eine Zeichenfolge mit der Fehlermeldung) und der rechte Typ der Ergebnistyp.

Dies ähnelt Ausnahmen, mit der Ausnahme, dass Ausnahmen auch Kontrollflusskonstrukte sind. Sie sind in der Tat gleichbedeutend mit GOTO.

Jörg W. Mittag
quelle
Methode in Java, die Ausnahme zurückgibt, klingt seltsam. " Rückgabe einer Ausnahme " ... hmm
gnat
3
Eine Ausnahme ist ein mögliches Ergebnis einer Methode in Java. Eigentlich habe ich einen vierten Typ vergessen: Unit(eine Methode ist überhaupt nicht erforderlich, um zurückzukehren). Sie verwenden das returnSchlüsselwort zwar nicht wirklich , aber es ist ein Ergebnis, das dennoch von der Methode stammt. Mit überprüften Ausnahmen wird dies sogar explizit in der Typensignatur erwähnt.
Jörg W Mittag
Aha. Ihr Punkt scheint einige Verdienste zu haben, aber die Art , wie es dargestellt ist es nicht überzeugend aussehen ... eher Gegenteil
gnat
4
In vielen Sprachen werden Fehlerbedingungen als Subroutine modelliert, die entweder einen Ergebnistyp oder einen Fehlertyp zurückgibt. Ausnahmen sind eine ähnliche Idee, mit der Ausnahme, dass sie auch Kontrollflusskonstrukte sind ( GOTOtatsächlich gleich in der Ausdruckskraft zu ).
Jörg W Mittag
4

Es gibt noch keine Antwort auf die SOLID-Prinzipien. Insbesondere sollten Sie dem Liskov-Substitutionsprinzip folgen, dass jede Klasse, die einen anderen als den erwarteten Typ empfängt, mit allem, was sie erhalten, arbeiten kann, ohne zu testen, welcher Typ zurückgegeben wird.

Wenn Sie also ein paar zusätzliche Eigenschaften auf ein Objekt werfen oder eine zurückgegebene Funktion mit einer Art Dekorator umschließen, der immer noch das leistet, was die ursprüngliche Funktion erreichen soll, sind Sie gut, solange sich kein Code, der Ihre Funktion aufruft, jemals darauf stützt Verhalten in einem beliebigen Codepfad.

Anstatt einen String oder eine Ganzzahl zurückzugeben, könnte ein besseres Beispiel darin bestehen, ein Sprinklersystem oder eine Katze zurückzugeben. Dies ist in Ordnung, wenn der aufrufende Code nur functionInQuestion.hiss () aufruft. Tatsächlich haben Sie eine implizite Schnittstelle, die der aufrufende Code erwartet, und eine dynamisch typisierte Sprache wird Sie nicht zwingen, die Schnittstelle explizit zu machen.

Leider werden Ihre Mitarbeiter dies wahrscheinlich tun, sodass Sie wahrscheinlich in Ihrer Dokumentation die gleiche Arbeit ausführen müssen, es sei denn, es gibt keine allgemein akzeptierte, knappe und maschinenanalysierbare Methode, wie dies bei der Definition einer Schnittstelle der Fall ist in einer Sprache, die sie hat.

bA
quelle
3

Der eine Ort, an dem ich sehe, dass ich verschiedene Typen sende, ist für ungültige Eingaben oder "Ausnahmen für schlechte Leute", bei denen die "außergewöhnliche" Bedingung nicht sehr außergewöhnlich ist. Zum Beispiel aus meinem Repository von PHP-Utility-Funktionen dieses verkürzte Beispiel:

function ensure_fields($consideration)
{
        $args = func_get_args();
        foreach ( $args as $a ) {
                if ( !is_string($a) ) {
                        return NULL;
                }
                if ( !isset($consideration[$a]) || $consideration[$a]=='' ) {
                        return FALSE;
                }
        }

        return TRUE;
}

Die Funktion gibt nominal ein BOOLEAN zurück, bei ungültiger Eingabe jedoch NULL. Beachten Sie, dass sich seit PHP 5.3 alle internen PHP-Funktionen auch so verhalten . Zusätzlich geben einige interne PHP-Funktionen bei nominaler Eingabe FALSE oder INT zurück, siehe:

strpos('Hello', 'e');  // Returns INT(1)
strpos('Hello', 'q');  // Returns BOOL(FALSE)
dotancohen
quelle
4
Und deshalb hasse ich PHP. Na ja, einer der Gründe jedenfalls.
Aaronaught
1
Eine Ablehnung, weil jemand die Sprache, in der ich mich beruflich entwickle, nicht mag?!? Warte, bis sie herausfinden, dass ich Jude bin! :)
Dotancohen
3
Gehen Sie nicht immer davon aus, dass die kommentierenden und die abstimmenden Personen dieselben Personen sind.
Aaronaught
1
Ich bevorzuge es, eine Ausnahme zu haben, anstatt null(a) dass sie laut ausfällt , sodass sie eher in der Entwicklungs- / Testphase behoben wird, (b) dass sie einfach zu handhaben ist, da ich alle seltenen / unerwarteten Ausnahmen ein einziges Mal abfangen kann meine gesamte Anwendung (in meinem Front-Controller) und logge sie einfach ein (normalerweise sende ich E-Mails an das Entwicklerteam), damit sie später behoben werden kann. Und ich hasse es tatsächlich, dass die Standard-PHP-Bibliothek meistens den "return null" -Ansatz verwendet - das macht PHP-Code wirklich fehleranfälliger, es sei denn, Sie überprüfen alles mit isset(), was einfach zu viel Belastung ist.
Skript am
1
Wenn Sie nullauf einen Fehler zurückkommen , machen Sie dies bitte in einem Dokumentblock (z. B. @return boolean|null) deutlich. Wenn ich also eines Tages auf Ihren Code stoße , müsste ich den Funktions- / Methodenkörper nicht überprüfen.
Skript am
3

Ich halte das nicht für eine schlechte Idee! Im Gegensatz zu dieser gängigsten Meinung und wie bereits von Robert Harvey hervorgehoben, haben statisch getippte Sprachen wie Java Generics genau für Situationen wie die von Ihnen angeforderte eingeführt. Tatsächlich versucht Java (wo immer möglich), die Typensicherheit während der Kompilierung beizubehalten, und manchmal vermeiden Generics die Vervielfältigung von Code. Warum? Weil Sie dieselbe Methode oder dieselbe Klasse schreiben können, die unterschiedliche Typen behandelt / zurückgibt . Ich mache nur ein sehr kurzes Beispiel, um diese Idee zu zeigen:

Java 1.4

public static Boolean getBoolean(String property){
    return (Boolean) properties.getProperty(property);
}
public static Integer getInt(String property){
    return (Integer) properties.getProperty(property);
}

Java 1.5+

public static <T> getValue(String property, Class<T> clazz) throws WhateverCheckedException{
    return clazz.getConstructor(String.class).newInstance(properties.getProperty(property));
}
//the call will be
Boolean b = getValue("useProxy",Boolean.class);
Integer b = getValue("proxyPort",Integer.class);

In einer dynamischen typisierten Sprache können Sie, da Sie zur Kompilierungszeit keine Typensicherheit haben, den Code schreiben, der für viele Typen funktioniert. Da auch in einer statisch typisierten Sprache Generics eingeführt wurden, um dieses Problem zu lösen, ist es eindeutig ein Hinweis darauf, dass das Schreiben einer Funktion, die verschiedene Typen in einer dynamischen Sprache zurückgibt, keine schlechte Idee ist.

thermz
quelle
Noch eine Gegenstimme ohne Erklärung! Danke SE Community!
Thermz
2
Habe ein Mitgefühl +1. Sie sollten versuchen, Ihre Antwort mit einer klareren und direkteren Antwort zu öffnen und keine anderen Antworten zu kommentieren. (Ich würde dir eine weitere +1 geben, da ich dir zustimme, aber ich habe nur eine zu geben.)
DougM
3
Generika existieren nicht in dynamisch typisierten Sprachen (von denen ich weiß). Es würde keinen Grund für sie geben. Bei den meisten Generika handelt es sich um Sonderfälle, bei denen der "Container" interessanter ist als der Inhalt (weshalb manche Sprachen, wie Java, tatsächlich die Typlöschung verwenden). Der generische Typ ist eigentlich ein eigener Typ. Generisches Methoden , ohne einen tatsächlichen generischen Typen, wie in Ihrem Beispiel hier sind fast immer nur Syntax Zucker für ein assign-und-Druckguss semantisch. Kein besonders überzeugendes Beispiel dafür, worum es in der Frage wirklich geht.
Aaronaught
1
Ich versuche es ein bisschen syntaktischer: "Da auch in einer statisch typisierten Sprache Generika eingeführt wurden, um dieses Problem zu lösen, ist es eindeutig ein Hinweis darauf, dass das Schreiben einer Funktion, die verschiedene Typen in einer dynamischen Sprache zurückgibt, keine schlechte Idee ist." Besser jetzt? Überprüfen Sie die Antwort von Robert Harvey oder den Kommentar von BRPocock und Sie werden feststellen, dass das Beispiel von Generics mit dieser Frage zusammenhängt
Thermz
1
Fühle dich nicht schlecht, @thermz. Downvotes sagen oft mehr über den Leser als über den Schreiber aus.
Karl Bielefeldt
2

Die Entwicklung von Software ist im Grunde eine Kunst und ein Handwerk, um mit Komplexität umzugehen. Sie versuchen, das System an den Punkten einzugrenzen, die Sie sich leisten können, und die Optionen an anderen Punkten einzuschränken. Die Schnittstelle der Funktion ist ein Vertrag, mit dessen Hilfe die Komplexität des Codes verwaltet werden kann, indem das für die Arbeit mit einem Code erforderliche Wissen begrenzt wird. Durch die Rückgabe verschiedener Typen erweitern Sie die Schnittstelle der Funktion erheblich, indem Sie alle von Ihnen zurückgegebenen Schnittstellen verschiedener Typen hinzufügen UND nicht offensichtliche Regeln hinzufügen, welche Schnittstelle zurückgegeben wird.

Kaerber
quelle
1

Perl verwendet dies häufig, da die Funktionsweise von seinem Kontext abhängt . Beispielsweise kann eine Funktion ein Array zurückgeben, wenn sie im Listenkontext verwendet wird, oder die Länge des Arrays, wenn sie an einer Stelle verwendet wird, an der ein skalarer Wert erwartet wird. Aus dem Tutorial, das der erste Treffer für "Perl-Kontext" ist , wenn Sie Folgendes tun:

my @now = localtime();

Dann ist @now eine Array-Variable (das bedeutet @) und enthält ein Array wie (40, 51, 20, 9, 0, 109, 5, 8, 0).

Wenn Sie die Funktion stattdessen so aufrufen, dass das Ergebnis ein Skalar sein muss, mit ($ -Variablen sind Skalare):

my $now = localtime();

dann macht es etwas ganz anderes: $ now wird so etwas wie "Fri Jan 9 20:51:40 2009".

Ein weiteres Beispiel ist die Implementierung von REST-APIs, bei denen das Format der zurückgegebenen Daten von den Anforderungen des Clients abhängt. ZB HTML oder JSON oder XML. Obwohl dies technisch gesehen alles Ströme von Bytes sind, ist die Idee ähnlich.

RemcoGerlich
quelle
Vielleicht möchten Sie die genaue Vorgehensweise in Perl - Wantarray erwähnen ... und vielleicht einen Link zu einer Diskussion darüber, ob es gut oder schlecht ist - Verwendung von Wantarray als schädlich bei PerlMonks
Zufällig beschäftige ich mich damit mit einer Java-Rest-API, die ich erstelle. Ich habe es einer Ressource ermöglicht, entweder XML oder JSON zurückzugeben. Der kleinste gemeinsame Nennertyp ist in diesem Fall a String. Jetzt werden Unterlagen zum Rückgabetyp angefordert. Die Java-Tools können es automatisch generieren, wenn ich eine Klasse zurückgebe, aber da ich ausgewählt Stringhabe, kann ich die Dokumentation nicht automatisch generieren. Im Nachhinein / Moral der Geschichte wünschte ich, ich hätte einen Typ pro Methode zurückgegeben.
Daniel Kaplan
Streng genommen handelt es sich um eine Überlastung. Mit anderen Worten, es gibt mehrere Verbundfunktionen, und abhängig von den Einzelheiten des Aufrufs wird die eine oder andere Version ausgewählt.
Ian
1

Im dynamischen Land dreht sich alles um das Tippen von Enten. Am verantwortungsvollsten ist es, potenziell unterschiedliche Typen in einen Wrapper zu packen, der ihnen dieselbe Schnittstelle bietet.

function ThingyWrapper(thingy){ //a function constructor (class-like thingy)

    //thingy is effectively private and persistent for ThingyWrapper instances

    if(typeof thingy === 'array'){
        this.alertItems = function(){
            thingy.forEach(function(el){ alert(el); });
        }
    }
    else {
        this.alertItems = function(){
            for(var x in thingy){ alert(thingy[x]); }
        }
    }
}

function gimmeThingy(){
    var
        coinToss = Math.round( Math.random() ),//gives me 0 or 1
        arrayThingy = [1,2,3],
        objectThingy = { item1:1, item2:2, item3:3 }
    ;

    //0 dynamically evaluates to false in JS
    return new ThingyWrapper( coinToss ? arrayThingy : objectThingy );
}

gimmeThingy().alertItems(); //should be same every time except order of numbers - maybe

Es mag gelegentlich sinnvoll sein, verschiedene Typen ganz ohne generischen Wrapper auszugeben, aber im siebten Jahr des Schreibens von JS habe ich es nicht für sinnvoll oder zweckmäßig befunden, dies sehr oft zu tun. Meistens würde ich das in geschlossenen Umgebungen tun, wie im Inneren eines Objekts, in dem Dinge zusammengefügt werden. Aber es ist nicht etwas, was ich oft genug getan habe, um Beispiele in den Sinn zu bringen.

Meistens würde ich raten, dass Sie aufhören, so viel über Typen nachzudenken. Sie beschäftigen sich mit Typen, wenn Sie eine dynamische Sprache benötigen. Nicht mehr. Es ist der springende Punkt. Überprüfen Sie nicht die Art jedes einzelnen Arguments. Das ist etwas, zu dem ich nur in einer Umgebung versucht wäre, in der die gleiche Methode auf nicht offensichtliche Weise inkonsistente Ergebnisse liefern könnte (also definitiv nicht so etwas tun). Aber es ist nicht der Typ, auf den es ankommt, sondern wie das, was du mir gibst, funktioniert.

Erik Reppen
quelle