Si dovrebbe sempre sapere cosa sta facendo un'API semplicemente guardando il codice?


20

Recentemente ho sviluppato la mia API e con quell'interesse investito nella progettazione dell'API sono stato fortemente interessato a come posso migliorare la mia progettazione dell'API.

Un aspetto che è emerso un paio di volte è (non dagli utenti della mia API ma nella mia osservazione osservativa sull'argomento): si dovrebbe sapere solo guardando il codice che chiama l'API quello che sta facendo .

Ad esempio, vedi questa discussione su GitHub per il repository discorso , va qualcosa del tipo:

foo.update_pinned(true, true);

Solo guardando il codice (senza conoscere i nomi dei parametri, la documentazione ecc.) Non si può indovinare cosa farà: cosa significa il secondo argomento? Il miglioramento suggerito è di avere qualcosa di simile:

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

E questo chiarisce le cose (il secondo argomento era se fissare il pippo a livello globale, immagino), e sono d'accordo in questo caso che il successivo sarebbe sicuramente un miglioramento.

Tuttavia, credo che possano esserci casi in cui i metodi per impostare uno stato diverso ma logicamente correlato sarebbero meglio esposti come una chiamata di metodo piuttosto che separati, anche se non si saprebbe cosa sta facendo semplicemente guardando il codice . (Quindi dovresti ricorrere a esaminare i nomi dei parametri e la documentazione per scoprirlo - cosa che personalmente farei sempre, non importa se non avessi familiarità con un'API).

Ad esempio espongoSetVisibility(bool, string, bool) un metodo su un FalconPeer e riconosco solo guardando la linea:

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

Non avresti idea di cosa stia facendo. Sta impostando 3 diversi valori che controllano la "visibilità" falconPeerin senso logico: accetta richieste di join, solo con password e rispondi a richieste di rilevamento. Dividere questo in 3 chiamate di metodo potrebbe portare un utente dell'API a impostare un aspetto della "visibilità" dimenticando di impostare gli altri a cui li costringo a pensare esponendo l'unico metodo per impostare tutti gli aspetti della "visibilità" . Inoltre, quando l'utente vuole cambiare un aspetto, quasi sempre vorrà cambiare un altro aspetto e ora può farlo in una chiamata.


13
Questo è precisamente il motivo per cui alcune lingue hanno parametri nominati (a volte persino imposti parametri nominati). Ad esempio si potrebbe gruppo un sacco di impostazioni in un semplice updatemetodo: foo.update(pinned=true, globally=true). Oppure: foo.update_pinned(true, globally=true). Pertanto, la risposta alla tua domanda dovrebbe tenere conto anche delle funzionalità della lingua, perché una buona API per la lingua X potrebbe non essere buona per la lingua Y e viceversa.
Bakuriu,

D'accordo - smettiamo di usare i booleani :)
Ven


@Bakuriu Anche C ha enumerazioni, anche se sono numeri interi mascherati. Non credo che esista un linguaggio del mondo reale in cui i booleani siano una buona progettazione API.
Doval,

1
@Doval Non capisco cosa stai cercando di dire. Ho usato i booleani in quella situazione semplicemente perché l'OP li ha usati, ma il mio punto non è completamente correlato al valore passato. Ad esempio: setSize(10, 20)non è leggibile come setSize(width=10, height=20)o random(distribution='gaussian', mean=0.5, deviation=1). Nelle lingue con parametri nominali forzati, i booleani possono trasmettere esattamente la stessa quantità di informazioni dell'uso di enumerazioni / costanti nominate, quindi possono essere utili nelle API.
Bakuriu,

Risposte:


27

Il tuo desiderio di non dividerlo in tre chiamate di metodo è completamente comprensibile, ma hai altre opzioni oltre ai parametri booleani.

Puoi usare enum:

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

O anche un enum di bandiere (se la tua lingua lo supporta):

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

Oppure potresti usare un oggetto parametro :

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

Il modello Oggetto parametro offre alcuni altri vantaggi che potresti trovare utili. Semplifica il passaggio e la serializzazione di un set di parametri e puoi facilmente fornire valori "predefiniti" di parametri non specificati.

Oppure potresti semplicemente usare parametri booleani. Microsoft sembra farlo tutto il tempo con l'API .NET Framework, quindi potresti semplicemente scrollare le spalle e dire "se è abbastanza buono per loro, è abbastanza buono per me".


Il modello di oggetto parametro presenta ancora un problema dichiarato dall'OP: " Dividere questo in 3 chiamate di metodo potrebbe portare un utente dell'API a impostare un aspetto della" visibilità "dimenticando di impostarne altri ".
Lode

Vero, ma penso che renda la situazione migliore (se non perfetta). Se l'utente è costretto a creare un'istanza e passare un oggetto VisibilityOptions, può (con un po 'di fortuna) ricordare loro che ci sono altre proprietà sull'oggetto VisibilityOptions che potrebbero voler impostare. Con l'approccio delle tre chiamate ai metodi, tutto ciò che devono ricordare loro sono commenti sui metodi che stanno chiamando.
BenM,

6

Ovviamente, ci sono sempre eccezioni alla regola, ma come hai spiegato bene da solo, ci sono alcuni vantaggi nell'avere un'API leggibile. Gli argomenti booleani sono particolarmente fastidiosi, in quanto 1) non rivelano un intento e 2) implicano che tu chiami una funzione, dove in realtà dovresti averne due, perché accadranno cose diverse a seconda della bandiera booleana.

La domanda principale è piuttosto: quanti sforzi vuoi investire per rendere più leggibile la tua API? Più è rivolto verso l'esterno, più sforzo può essere facilmente giustificato. Se si tratta di un'API utilizzata solo da un'altra unità, non è un grosso problema. Se stai parlando di un'API REST in cui prevedi di far perdere tutto il mondo, allora puoi anche investire qualche sforzo in più per renderlo più comprensibile.

Per quanto riguarda il tuo esempio, esiste una soluzione semplice: a quanto pare, nel tuo caso la visibilità non è solo una cosa vera o falsa. Invece, hai un intero insieme di cose che consideri "visibilità". Una soluzione potrebbe essere quella di introdurre qualcosa come una Visibilityclasse, che copre tutti questi diversi tipi di visibilità. Se applichi ulteriormente il modello Builder per crearli, potresti finire con un codice simile al seguente:

Visibility visibility = Visibility.builder()
  .acceptJoinRequests()
  .withPassword("aerw3")
  .replyToDiscoveryRequests()
  .build();
falconPeer.setVisibility(visibility);
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.