Czy zawsze należy wiedzieć, co robi interfejs API, patrząc tylko na kod?

20

Ostatnio opracowuję własny interfejs API i dzięki temu zainwestowanemu zainteresowaniu projektowaniem interfejsu API byłem bardzo zainteresowany, jak mogę ulepszyć swój interfejs API.

Jednym z aspektów, który pojawiał się kilka razy jest (nie przez użytkowników mojego API, ale podczas mojej dyskusji na ten temat): należy wiedzieć, patrząc na kod wywołujący API, co robi .

Na przykład zobacz tę dyskusję na GitHub dla repozytorium dyskursu , wygląda to tak:

foo.update_pinned(true, true);

Patrząc na kod (nie znając nazw parametrów, dokumentacji itp.) Nie można zgadnąć, co zamierza zrobić - co oznacza drugi argument? Sugerowana poprawa to mieć coś takiego:

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

I to wyjaśnia wszystko (przypuszczam, że drugi argument dotyczy tego, czy przypiąć foo globalnie), i zgadzam się w tym przypadku, że później z pewnością byłaby poprawa.

Jednak uważam, że mogą istnieć przypadki, w których metody ustawiania innego, ale logicznie powiązanego stanu byłyby lepiej widoczne jako jedno wywołanie metody niż osobne, nawet jeśli nie wiedziałbyś, co robi po prostu patrząc na kod . (Aby dowiedzieć się, musiałbyś sięgnąć do nazw parametrów i dokumentacji - które osobiście zawsze bym zrobił, bez względu na to, czy nie jestem zaznajomiony z API).

Na przykład ujawniam jedną metodę SetVisibility(bool, string, bool)na FalconPeer i potwierdzam, że patrzę na linię:

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

Nie miałbyś pojęcia, co on robi. Ustawia 3 różne wartości, które kontrolują „widoczność” falconPeerw sensie logicznym: akceptuj prośby o dołączenie, tylko z hasłem i odpowiedz na prośby o wykrycie. Podział tego na 3 wywołania metod może spowodować, że użytkownik interfejsu API ustawi jeden aspekt „widoczności”, zapominając o ustawieniu innych, o których zmuszam ich do myślenia, ujawniając tylko jedną metodę ustawiania wszystkich aspektów „widoczności” . Ponadto, gdy użytkownik chce zmienić jeden aspekt, prawie zawsze będzie chciał zmienić inny aspekt i może teraz to zrobić za jednym razem.

markmnl
źródło
13
To jest właśnie powód, dla którego niektóre języki nadały parametry (czasem nawet wymusiły nazwane parametry). Na przykład mógłbyś grupa wiele ustawień w prosty updatesposób: foo.update(pinned=true, globally=true). Lub: foo.update_pinned(true, globally=true). Tak więc odpowiedź na twoje pytanie powinna uwzględniać także funkcje językowe, ponieważ dobre API dla języka X może nie być dobre dla języka Y i odwrotnie.
Bakuriu
Zgoda - przestańmy używać booleanów :)
Ven
2
Jest znany jako „pułapka boolowska”
user11153
@ Bakuriu Nawet C ma wyliczenia, nawet jeśli są liczbami całkowitymi w przebraniu. Nie sądzę, żeby istniał jakiś język świata rzeczywistego, w którym booleany są dobrym projektem API.
Doval
1
@Doval Nie rozumiem tego, co próbujesz powiedzieć. Użyłem booleanów w tej sytuacji po prostu dlatego, że OP ich użył, ale mój punkt widzenia jest całkowicie niezwiązany z przekazaną wartością. Na przykład: setSize(10, 20)nie jest tak czytelny jak setSize(width=10, height=20)lub random(distribution='gaussian', mean=0.5, deviation=1). W językach z wymuszonymi nazwanymi parametrami logiczne mogą przekazywać dokładnie taką samą ilość informacji, jak przy użyciu wyliczeń / nazwanych stałych, więc mogą być dobre w interfejsach API.
Bakuriu

Odpowiedzi:

27

Twoje pragnienie, aby nie dzielić go na trzy wywołania metod, jest całkowicie zrozumiałe, ale masz inne opcje oprócz parametrów boolowskich.

Możesz użyć wyliczeń:

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

Lub nawet wyliczanie flag (jeśli twój język to obsługuje):

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

Lub możesz użyć obiektu parametru :

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

Wzorzec obiektu parametru ma kilka innych zalet, które mogą okazać się pomocne. Ułatwia to przekazywanie i szeregowanie zestawu parametrów oraz łatwe nadawanie nieokreślonym parametrom „domyślnych” wartości.

Lub możesz po prostu użyć parametrów boolowskich. Wydaje się, że Microsoft robi to cały czas z interfejsem API .NET Framework, więc możesz po prostu wzruszyć ramionami i powiedzieć: „jeśli jest dla nich wystarczająco dobry, jest dla mnie wystarczająco dobry”.

BenM
źródło
Wzorzec parametru parametru wciąż ma problem, który stwierdził OP: „ Podział tego na 3 wywołania metod może prowadzić do tego, że użytkownik interfejsu API ustawi jeden aspekt„ widoczności ”, zapominając o ustawieniu innych ”.
Lode,
To prawda, ale myślę, że to poprawia sytuację (jeśli nie idealną). Jeśli użytkownik zostanie zmuszony do utworzenia instancji i przekazania obiektu VisibilityOptions, może (przy odrobinie szczęścia) przypomnieć, że istnieją inne właściwości obiektu VisibilityOptions, które mogą chcieć ustawić. Dzięki podejściu opartemu na trzech metodach muszą im tylko przypomnieć o komentarzach do metod, które wywołują.
BenM,
6

Oczywiście zawsze są wyjątki od reguły, ale jak dobrze to wyjaśniłeś, posiadanie czytelnego API ma pewne zalety. Argumenty boolowskie są szczególnie uciążliwe, ponieważ 1) nie ujawniają zamiaru i 2) sugerują, że wywołujesz funkcję, w której powinieneś mieć dwie, ponieważ różne rzeczy będą się dziać w zależności od flagi boolowskiej.

Główne pytanie brzmi: ile wysiłku chcesz zainwestować, aby interfejs API był bardziej czytelny? Im bardziej jest skierowany na zewnątrz, tym więcej wysiłku można łatwo uzasadnić. Jeśli jest to interfejs API, który jest używany tylko przez jedną inną jednostkę, to nie jest to taka wielka sprawa. Jeśli mówisz o interfejsie API REST, w którym planujesz pozwolić, aby cały świat na nim stracił, równie dobrze możesz zainwestować więcej wysiłku w uczynienie go bardziej zrozumiałym.

Jeśli chodzi o twój przykład, istnieje proste rozwiązanie: najwyraźniej w twoim przypadku widoczność nie jest tylko prawdą czy fałszem. Zamiast tego masz cały zestaw rzeczy, które uważasz za „widoczność”. Jednym z rozwiązań może być wprowadzenie czegoś w rodzaju Visibilityklasy, która obejmuje wszystkie te różne rodzaje widoczności. Jeśli zastosujesz wzorzec Konstruktora do ich tworzenia, możesz otrzymać kod podobny do następującego:

Visibility visibility = Visibility.builder()
  .acceptJoinRequests()
  .withPassword("aerw3")
  .replyToDiscoveryRequests()
  .build();
falconPeer.setVisibility(visibility);
Szczery
źródło