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ść” falconPeer
w 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.
źródło
update
sposó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.setSize(10, 20)
nie jest tak czytelny jaksetSize(width=10, height=20)
lubrandom(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.Odpowiedzi:
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ń:
Lub nawet wyliczanie flag (jeśli twój język to obsługuje):
Lub możesz użyć obiektu parametru :
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”.
źródło
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
Visibility
klasy, 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:źródło