Sollte man immer wissen, was eine API macht, wenn man sich nur den Code ansieht?

20

Kürzlich habe ich meine eigene API entwickelt und mit dem investierten Interesse an API-Design war ich sehr interessiert, wie ich mein API-Design verbessern kann.

Ein Aspekt, der einige Male auftauchte, ist (nicht von Benutzern meiner API, sondern in meiner beobachtenden Diskussion zum Thema): Man sollte nur anhand des Codes, der die API aufruft, wissen, was sie tut .

Sehen Sie sich zum Beispiel diese Diskussion auf GitHub für das Diskurs- Repo an. Es geht ungefähr so:

foo.update_pinned(true, true);

Wenn man sich nur den Code ansieht (ohne die Parameternamen, die Dokumentation usw. zu kennen), kann man nicht erraten, was er tun wird - was bedeutet das zweite Argument? Die vorgeschlagene Verbesserung ist, etwas zu haben wie:

foo.pin()
foo.unpin()
foo.pin_globally()

Und das klärt die Dinge auf (das zweite Argument war wohl, ob man foo global pinnen sollte), und ich stimme in diesem Fall zu, dass das letztere sicherlich eine Verbesserung wäre.

Ich glaube jedoch, dass es Fälle geben kann, in denen Methoden zum Festlegen eines anderen, aber logisch zusammenhängenden Zustands besser als ein Methodenaufruf als als separate dargestellt werden, obwohl Sie nicht wissen, was sie tun , wenn Sie sich nur den Code ansehen . (Sie müssten sich also die Parameternamen und die Dokumentation ansehen, um herauszufinden, was ich persönlich immer tun würde, egal, wenn ich mit einer API nicht vertraut bin.)

Zum Beispiel stelle ich eine Methode SetVisibility(bool, string, bool)auf einem FalconPeer zur Verfügung und bestätige, dass ich nur die Zeile betrachte:

falconPeer.SetVisibility(true, "aerw3", true);

Sie hätten keine Ahnung, was es tut. Es werden 3 verschiedene Werte festgelegt, die die "Sichtbarkeit" von falconPeerim logischen Sinne steuern : Join-Anforderungen akzeptieren, nur mit Kennwort und Antworten auf Discovery-Anforderungen. Das Aufteilen in 3 Methodenaufrufe könnte dazu führen, dass ein Benutzer der API einen Aspekt der "Sichtbarkeit" festlegt und vergisst, andere festzulegen, über die er nachdenken muss, indem nur die eine Methode verfügbar gemacht wird, mit der alle Aspekte der "Sichtbarkeit" festgelegt werden . Wenn der Benutzer einen Aspekt ändern möchte, möchte er außerdem fast immer einen anderen Aspekt ändern und kann dies jetzt in einem Aufruf tun.

markmnl
quelle
13
Genau aus diesem Grund haben einige Sprachen benannte Parameter (manchmal sogar erzwungene benannte Parameter). Zum Beispiel könnten Sie Gruppe eine Menge von Einstellungen in einem einfachen updateVerfahren: foo.update(pinned=true, globally=true). Oder: foo.update_pinned(true, globally=true). Die Antwort auf Ihre Frage sollte daher auch die Sprachfunktionen berücksichtigen, da eine gute API für Sprache X möglicherweise nicht für Sprache Y und umgekehrt geeignet ist.
Bakuriu
Vereinbarte - lassen Sie uns aufhören mit booleans :)
Ven
2
Es ist bekannt als "Boolesche Falle"
user11153
@ Bakuriu Sogar C hat Aufzählungen, auch wenn es sich um getarnte ganze Zahlen handelt. Ich glaube nicht, dass es eine reale Sprache gibt, in der Booleaner ein gutes API-Design sind.
Doval
1
@Doval Ich verstehe nicht, was Sie sagen wollen. Ich habe in dieser Situation Boolesche Werte verwendet, nur weil das OP sie verwendet hat, aber mein Punkt ist völlig unabhängig vom übergebenen Wert. Zum Beispiel: setSize(10, 20)ist nicht so lesbar wie setSize(width=10, height=20)oder random(distribution='gaussian', mean=0.5, deviation=1). In Sprachen mit erzwungenen benannten Parametern können Boolesche Werte genau so viele Informationen übermitteln wie Enums / benannte Konstanten, sodass sie in APIs nützlich sein können .
Bakuriu

Antworten:

27

Ihr Wunsch, es nicht in drei Methodenaufrufe aufzuteilen, ist völlig verständlich, aber Sie haben neben booleschen Parametern noch andere Optionen.

Sie könnten Aufzählungen verwenden:

falconPeer.SetVisibility(JoinRequestOptions.Accept, "aerw3", DiscoveryRequestOptions.Reply);

Oder sogar eine Flaggen-Aufzählung (wenn Ihre Sprache dies unterstützt):

falconPeer.SetVisibility(VisibilityOptions.AcceptJoinRequests | VisibilityOptions.ReplyToDiscoveryRequests, "aerw3");

Oder Sie könnten ein Parameter-Objekt verwenden :

var options = new VisibilityOptions();
options.AcceptJoinRequests = true;
options.ReplyToDiscoveryRequest = true;
options.Password="aerw3";
falconPeer.SetVisibility(options);

Das Parameter-Objektmuster bietet Ihnen einige weitere Vorteile, die Sie möglicherweise hilfreich finden. Es erleichtert das Weitergeben und Serialisieren eines Parametersatzes, und Sie können nicht spezifizierte Parameter einfach als "Standard" -Werte festlegen.

Oder Sie könnten einfach boolesche Parameter verwenden. Microsoft scheint dies die ganze Zeit mit der .NET Framework-API zu tun, sodass Sie nur mit den Schultern zucken und sagen können: "Wenn es gut genug für sie ist, ist es gut genug für mich."

BenM
quelle
Das Parameter-Objektmuster hat immer noch ein vom OP angegebenes Problem: "Das Aufteilen in drei Methodenaufrufe kann dazu führen, dass ein Benutzer der API einen Aspekt der" Sichtbarkeit "festlegt, ohne andere festzulegen."
Lode
Stimmt, aber ich denke, es macht die Situation besser (wenn nicht perfekt). Wenn der Benutzer gezwungen ist, ein VisibilityOptions-Objekt zu instanziieren und weiterzugeben, wird er möglicherweise (mit etwas Glück) daran erinnert, dass das VisibilityOptions-Objekt andere Eigenschaften aufweist, die er möglicherweise festlegen möchte. Bei der Methode mit drei Methodenaufrufen müssen sie lediglich Kommentare zu den von ihnen aufgerufenen Methoden eingeben.
BenM
6

Natürlich gibt es immer Ausnahmen von der Regel, aber wie Sie selbst gut erklärt haben, hat eine lesbare API gewisse Vorteile. Boolesche Argumente sind besonders störend, da 1) sie keine Absicht offenbaren und 2) implizieren, dass Sie eine Funktion aufrufen, bei der Sie eigentlich zwei haben sollten, da abhängig von der Booleschen Flagge unterschiedliche Dinge passieren werden.

Die Hauptfrage lautet vielmehr: Wie viel Aufwand möchten Sie betreiben, um Ihre API besser lesbar zu machen? Je nach außen gerichteter es ist, desto mehr Aufwand ist leicht zu rechtfertigen. Wenn es sich um eine API handelt, die nur von einer anderen Einheit verwendet wird, ist dies keine große Sache. Wenn Sie von einer REST-API sprechen, bei der Sie die ganze Welt verlieren lassen möchten, können Sie auch größere Anstrengungen unternehmen, um sie verständlicher zu machen.

Für Ihr Beispiel gibt es eine einfache Lösung: Anscheinend ist Sichtbarkeit in Ihrem Fall nicht nur eine wahre oder falsche Sache. Stattdessen haben Sie eine ganze Reihe von Dingen, die Sie als "Sichtbarkeit" betrachten. Eine Lösung könnte darin bestehen, so etwas wie eine VisibilityKlasse einzuführen , die all diese verschiedenen Arten der Sichtbarkeit abdeckt. Wenn Sie das Builder-Muster zum Erstellen dieser weiter anwenden, erhalten Sie möglicherweise folgenden Code:

Visibility visibility = Visibility.builder()
  .acceptJoinRequests()
  .withPassword("aerw3")
  .replyToDiscoveryRequests()
  .build();
falconPeer.setVisibility(visibility);
Frank
quelle