Inicjalizacja listy - czy zawsze konieczna

Inicjalizacja listy - czy zawsze konieczna
T3
  • Rejestracja: dni
  • Ostatnio: dni
0

Jeżeli metoda zwraca obiekt, to o ile on nie jest listą, to przyjmuje się, że może zwrócić NULL (chyba, że stosujemy jakiś wzorzec typu Null Object pattern czy inne mniej czy bardziej wyszukane rozwiązanie).
Jeżeli metoda zwraca listę, to przyjmuje się, że nie będzie NULL, tylko co najwyżej pusta lista.
Zastanawia mnie jednak czy zawsze. Np. w komunikacji przez web service, często są przesyłane dodatkowe pola, które określają status odpowiedzi. Dla uproszczenia przyjmijmy poniższą klasę. Pytanie czy w przypadku braku sukcesu, lista powinna być wstępnie zainicjalizowana czy powinna być NULLem? Moim zdaniem raczej NULLem, ponieważ brak sukcesu pewnie oznaczać, że nie należy dotykać zwracanej listy, i w jakiś sposób należy obsłużyć zaistniałą sytuację. Jeśli zostanie ona użyta to moim zdaniem najlepiej jakby wywaliła klienta, ponieważ istnieje duże prawdopodobieństwo, że programista który go pisał nie przeczytał, że w odpowiedzi jest coś poza listą.

Kopiuj
public class ApiResponse
{
    public List<string>? DataList { get; set; } //?? = new List<string>()
    public bool Success { get; set; }
}
somekind
  • Rejestracja: dni
  • Ostatnio: dni
  • Lokalizacja: Wrocław
4
tryhp3 napisał(a):

Moim zdaniem raczej NULLem, ponieważ brak sukcesu pewnie oznaczać, że nie należy dotykać zwracanej listy, i w jakiś sposób należy obsłużyć zaistniałą sytuację.

Skoro nie należy dotykać tej listy, to w ogóle nie powinna ona być dostępna w tym miejscu.

Jeśli zostanie ona użyta to moim zdaniem najlepiej jakby wywaliła klienta, ponieważ istnieje duże prawdopodobieństwo, że programista który go pisał nie przeczytał, że w odpowiedzi jest coś poza listą.

Czyli konsekwencje błędu programisty ma ponieść użytkownik? Nie brzmi to fair.

Moim zdaniem nic nigdy nie powinno być nullem.

markone_dev
  • Rejestracja: dni
  • Ostatnio: dni
  • Postów: 833
0

Zawsze.

somekind napisał(a):

Moim zdaniem nic nigdy nie powinno być nullem.

Ament

T3
  • Rejestracja: dni
  • Ostatnio: dni
1
somekind napisał(a):

Czyli konsekwencje błędu programisty ma ponieść użytkownik? Nie brzmi to fair.

Załóżmy, że mamy WEB API które generuje alerty o trzęsieniach ziemi, które zdarzają się co kilka lat. Klient zapomniał zaimplementować wysyłania kodu regionu (albo np. nie wdrożył na PROD słownika tych regionów, albo...) który jest parametrem wymaganym. W efekcie dostanie kod błędu ale i pustą listę. Klient zadowolony, że dostał w odpowiedzi pustą listę i nie zauważył, że jest informacja o błędzie, którą powinien obsłużyć. Cała sytuacja wyjaśnia się po kilku latach po trzęsieniu ziemi.
Jakby dostał NULL zamiast pustej listy, to by zapewne to jakoś obsłużył na etapie DEV. Pytanie co jest fair dla użytkownika? Moim zdaniem w komunikacji sieciowej nie należy mieć żadnego zaufania.

Riddle
  • Rejestracja: dni
  • Ostatnio: dni
  • Postów: 10230
3
tryhp3 napisał(a):

Jeżeli metoda zwraca obiekt, to o ile on nie jest listą, to przyjmuje się, że może zwrócić NULL (chyba, że stosujemy jakiś wzorzec typu Null Object pattern czy inne mniej czy bardziej wyszukane rozwiązanie).
Jeżeli metoda zwraca listę, to przyjmuje się, że nie będzie NULL, tylko co najwyżej pusta lista.
Zastanawia mnie jednak czy zawsze. Np. w komunikacji przez web service, często są przesyłane dodatkowe pola, które określają status odpowiedzi. Dla uproszczenia przyjmijmy poniższą klasę. Pytanie czy w przypadku braku sukcesu, lista powinna być wstępnie zainicjalizowana czy powinna być NULLem? Moim zdaniem raczej NULLem, ponieważ brak sukcesu pewnie oznaczać, że nie należy dotykać zwracanej listy, i w jakiś sposób należy obsłużyć zaistniałą sytuację. Jeśli zostanie ona użyta to moim zdaniem najlepiej jakby wywaliła klienta, ponieważ istnieje duże prawdopodobieństwo, że programista który go pisał nie przeczytał, że w odpowiedzi jest coś poza listą.

Bardzo istotne pytanie z punktu widzenia designu aplikacji i serwisów. Prawdopodobnie trudno Ci samemu znaleźć na to odpowiedź, bo myślę że patrzysz na to z perspektywy osoby piszącej kod - jesteś w miejscu w kodzie gdzie obsługujesz błąd komunikacji i myślisz sobie... co ja mam teraz pisać - bardzo częsty use-case.

Dobrą metodyką jest spojrzenie na swoje rozwiązanie od drugej strony - użytkownika tej aplikacji. Napisanie testów najpierw stawia Cię w perspektywie osoby używającej jakiegoś kodu (a nie piszącej, jeszcze). Dzięki temu dosyć łatwo można wyłapać błędy projektowe i podjąć dobrą decyzję nt interfejsu Twojej klasy - w Twoim przypadku - co się ma stać w przypadku nieudanej odpowiedzi.

Pamiętaj też - raczej nie piszesz biblioteki, Twoja klasa ApiResponse nie powinna być aż tak generyczna, żeby spełniała wszystkie standardy światowe. Najpewniej używasz tej klasy w swoim kodzie w kilku strategicznych miejscach. Zwróć uwagę na to w jaki sposob używasz klasy ApiResponse i staraj się dopasować interfejs do Twojego przypadku użycia - nie próbuj się wpisać w jakieś ogólnie przyjęte narzucone konwencje.

tryhp3 napisał(a):

Załóżmy, że mamy WEB API które generuje alerty o trzęsieniach ziemi, które zdarzają się co kilka lat. Klient zapomniał zaimplementować wysyłania kodu regionu (albo np. nie wdrożył na PROD słownika tych regionów, albo...) który jest parametrem wymaganym. W efekcie dostanie kod błędu ale i pustą listę. Klient zadowolony, że dostał w odpowiedzi pustą listę i nie zauważył, że jest informacja o błędzie, którą powinien obsłużyć. Cała sytuacja wyjaśnia się po kilku latach po trzęsieniu ziemi.
Jakby dostał NULL zamiast pustej listy, to by zapewne to jakoś obsłużył na etapie DEV. Pytanie co jest fair dla użytkownika? Moim zdaniem w komunikacji sieciowej nie należy mieć żadnego zaufania.

Znowu - nie wiem dokładnie w jaki sposób używasz tej klasy. Ja prawdopodobnie zwróciłbym status błędu (jako enum, int, string, etc.), może to być "success", "network-failure", "invalid-response", etc. i dodałbym funkcję readAlerts(), która rzuci wyjątek jeśli status jest inny niż "success". Ale to nie jest jedyne dobre wyjście - postaraj zaprojektować interfejs klasy tak żeby pasowała do Twojego przypadku jej użycia, a do tego najlepiej dojść testami automatycznymi.

T3
  • Rejestracja: dni
  • Ostatnio: dni
0
Riddle napisał(a):

Znowu - nie wiem dokładnie w jaki sposób używasz tej klasy. Ja prawdopodobnie zwróciłbym status błędu (jako enum, int, string, etc.), może to być "success", "network-failure", "invalid-response", etc. i dodałbym funkcję readAlerts(), która rzuci wyjątek jeśli status jest inny niż "success".

Tak mam kod błędu plus jego opis. Robię Web Api które zwraca json. Więc funkcję "readAlerts" musiałby napisać klient - generalnie o to mi chodziło.
Moja usługa będzie działać na szynie ESB, która już pewne kwestie ustandaryzuje na swój sposób, ale w pytaniu chodziło mi właśnie o takie porady/dobre praktyki.

Riddle
  • Rejestracja: dni
  • Ostatnio: dni
  • Postów: 10230
1
tryhp3 napisał(a):
Riddle napisał(a):

Znowu - nie wiem dokładnie w jaki sposób używasz tej klasy. Ja prawdopodobnie zwróciłbym status błędu (jako enum, int, string, etc.), może to być "success", "network-failure", "invalid-response", etc. i dodałbym funkcję readAlerts(), która rzuci wyjątek jeśli status jest inny niż "success".

Tak mam kod błędu plus jego opis. Robię Web Api które zwraca json. Więc funkcję "readAlerts" musiałby napisać klient - generalnie o to mi chodziło.
Moja usługa będzie działać na szynie ESB, która już pewne kwestie ustandaryzuje na swój sposób, ale w pytaniu chodziło mi właśnie o takie porady/dobre praktyki.

Okej, czyli projektujesz nie interfejs klasy, tylko interfejs Twojej aplikacji, tak?

No to moja rada z testami automatycznymi nadal jest aktulna. Najlpiej byłoby napisać test post jeszcze nieistniejąca funkcjonalność, np. tak:

(kod poglądowy, zależnie od biblioteki do testów notacja i funkcje testowe mogą być inne)

Kopiuj
void testAlertersAreReturned() {
 response = readAlerts()
 assertEquals("success", response.status);
 assertEquals(1, response.alerts.size());
 assertEquals("Burza", response.alerts[0].title);
}

Zauważ, że już podczas pisania testu, zanim dodasz implementację możesz postawić się w perspektywie osoby która korzysta z takiego API, możesz podjąć lepsze małe decyzje projektowe.

Masz kilka opcji do wyboru. Jeśli Twoja usługa nie może dostarczyć listy alertów, to pole może mieć null, może być tam pusta lista, może tego pola w ogóle nie być. To co powinieneś zrobić, to pogadać z użytkownikami Twojej aplikacji i zebrać informacje o tym jak oni z reguły korzystają z czegoś takiego; bo rozumiem że robisz aplikację pod kogoś, tak?

Myślę że ja nie wybrałbym opcji pustej tablicy, dlatego że pusta tablica sugerowałaby (mi, osobiście) że raporty zostały wczytane, i zostały ich wczytanie 0 - po prostu nie ma raportów na dzisiaj. Nie skorzystałbym z opcji że pola ma nie być, bo to mogłoby zmylić użytkowników API. Myślę że ja, w początkowej wersji ustawiłbym pole na null, jeśli raportów nie można wczytać, i napisałbym w dokumentacji ze to pole zawiera raporty, tylko jeśli status jest "success".

T3
  • Rejestracja: dni
  • Ostatnio: dni
0

Działam sam, robię wszystko po stronie usługi (niestety bez TDD). Niby wszystko przetestowane i gotowe do wydania. Ale się jeszcze nad tym zastanawiam.
Generalnie działa to tak jak opisałeś tj. wersja z NULL w przypadku niepowodzenia.
Pogadanie z klientem było by słuszne, ale w tym przypadku i tak dostanie odpowiedź z ESB do której jest przyzwyczajony. Natomiast ESB jest przyzwyczajone, że może dostać wszystko i po swojemu sobie poradzi...

Riddle
  • Rejestracja: dni
  • Ostatnio: dni
  • Postów: 10230
0
tryhp3 napisał(a):

Działam sam, robię wszystko po stronie usługi (niestety bez TDD). Niby wszystko przetestowane i gotowe do wydania. Ale się jeszcze nad tym zastanawiam.
Generalnie działa to tak jak opisałeś tj. wersja z NULL w przypadku niepowodzenia.
Pogadanie z klientem było by słuszne, ale w tym przypadku i tak dostanie odpowiedź z ESB do której jest przyzwyczajony. Natomiast ESB jest przyzwyczajone, że może dostać wszystko i po swojemu sobie poradzi...

No to wychodzi że nie ma znaczenia czy zwrócisz null czy pustą listę, nie?

T3
  • Rejestracja: dni
  • Ostatnio: dni
0
Riddle napisał(a):

No to wychodzi że nie ma znaczenia czy zwrócisz null czy pustą listę, nie?

W tym przypadku nie bardzo. Chciałem się po prostu dowiedzieć jak to powinno być zrobione zgodnie ze sztuką, a ogólnie się przyjmuje, że lista nie powinna być NULLem, natomiast tutaj mi to nie pasowało.
Dwa, że ja tam mam generyka jak niżej który w założeniu będzie zwracany przez kolejne operacje. Zostawię go jak jest.

Kopiuj
public class ApiResponse<T>
{
    public <T>? Data { get; set; }
Riddle
  • Rejestracja: dni
  • Ostatnio: dni
  • Postów: 10230
1
tryhp3 napisał(a):

W tym przypadku nie bardzo. Chciałem się po prostu dowiedzieć jak to powinno być zrobione zgodnie ze sztuką, a ogólnie się przyjmuje, że lista nie powinna być NULLem, natomiast tutaj mi to nie pasowało.

Nie sądzę że istnieje coś takiego jak jedna obiektywnie poprawna odpowiedź w tym wypadku. Design to są targi, coś za, coś przeciw.

Zrób interfejs który będzie najlepszy do tego co konsumuje Twój interfejs. Jeśli to co będzie gadać z Twoim API jest ten ESB, to zrób interfejs który będzie odpowiedni dla ESB własnie.

tryhp3 napisał(a):

Dwa, że ja tam mam generyka jak niżej który w założeniu będzie zwracany przez kolejne operacje. Zostawię go jak jest.

Kopiuj
public class ApiResponse<T>
{
    public <T>? Data { get; set; }

A to z kolei się wydaje szczegółem implementacyjnym, nie kierowałbym się tym zbytnio.

somekind
  • Rejestracja: dni
  • Ostatnio: dni
  • Lokalizacja: Wrocław
4
tryhp3 napisał(a):

Załóżmy, że mamy WEB API które generuje alerty o trzęsieniach ziemi, które zdarzają się co kilka lat. Klient zapomniał zaimplementować wysyłania kodu regionu (albo np. nie wdrożył na PROD słownika tych regionów, albo...) który jest parametrem wymaganym. W efekcie dostanie kod błędu ale i pustą listę.

Jakim cudem dostaje jednocześnie kod błędu i pustą listę? 😮

Jak zwracasz kod błędu (zakładam, że mowa o kodach HTTP, dokumentacja tutaj: https://http.cat/), to nie zwracasz żadnej pustej listy, ani nawet żadnej struktury przypominającej tą prawidłową. Od tego jest problem details, dokumentacja tutaj: https://www.rfc-editor.org/rfc/rfc9457.html

Jeśli zwracasz takie coś:

Kopiuj
HTTP/1.1 422 Unprocessable Content
Content-Type: application/problem+json
Content-Language: en

{
 "type": "https://example.net/validation-error",
 "title": "Your request is not valid.",
 "errors": [
             {
               "detail": "must be a positive integer",
               "pointer": "#/age"
             },
             {
               "detail": "must be 'green', 'red' or 'blue'",
               "pointer": "#/profile/color"
             }
          ]
}

i autor kodu klienta mimo wszystko uzna to prawidłową listę miejsc zagrożonych trzęsieniami ziemi, to nic na to nie poradzisz.

Moim zdaniem w komunikacji sieciowej nie należy mieć żadnego zaufania.

No oczywiście, że nie. Dlatego m.in. nie zwraca się poprawnych struktury dla niepoprawnych odpowiedzi.

Zarejestruj się i dołącz do największej społeczności programistów w Polsce.

Otrzymaj wsparcie, dziel się wiedzą i rozwijaj swoje umiejętności z najlepszymi.