Dla jakich wartości parametrów funkcja powinna kończyć się sukcesem, a dla jakich nie (np. wyrzucać wyjątek)?

0

Mój problem opiera się na przypadku użycia funkcji w JavaScripcie, ale sądzę, że jego rozwiązanie (o ile jakieś istnieje) jest cross-language.

Najlepiej wyjaśnię na przykładzie, o co mi chodzi. Mam w projekcie JavaScriptowym funkcję, która przyjmuje jako parametr ciąg znaków i konfigurację, i na tej podstawie tworzy drugi ciąg znaków. (Dla formalności: pisałem o niej już tutaj). Wynik funkcji jest nazywany w domenie programu "pudełkiem". Jej sygnatura wygląda tak:

function createBox(config, content)

Przykład działania: dla wywołania

createBox({ width: 5 }, "to-jest-ciąg-znaków"}

funkcja zwraca

to-je
st-ci
ąg-zn
aków

(Na wszelki wypadek napiszę: to nie są 4 oddzielne ciągi znaków, tylko jeden ciąg znaków zawierający znaki nowej linii).

I teraz dwa pytania z nią powiązane:

  1. W jakich sytuacjach ta funkcja mogłaby, a w jakich powinna mieć domyślne parametry?
  2. Według jakich kryteriów w tych sytuacjach należałoby to oceniać?

Mnie przychodzą do głowy dwa kryteria (dla wszystkich funkcji, nie tylko dla tej):

  • założenia domenowe (= co funkcja powinna robić według projektu);
  • rzeczywiste wywołanie (= co funkcja powinna robić przy konkretnym wywołaniu); nie chodzi mi tutaj o to, że np. w programie (tym samym) funkcja zwróci pusty ciąg znaków w miejscu, w którym go być nie może – wtedy będzie rzeczywiście błąd w projekcie, ale to nie jest zmartwienie tej funkcji, tylko innej.

Te kryteria są (albo jedynie: mogą być?) w pewnej mierze ze sobą sprzeczne. Mam na przykład następującą dychotomię w sobie. Ta funkcja ma w założeniu zwracać ciąg znaków. To założenie nie precyzuje, czy domenowo ma sens zwrócenie pustego ciągu znaków, czy nie. Więc, jeśli jej implementacja odpowiada założeniu (nie jest ani mniej, ani bardziej ścisła), to dla braku parametru reprezentującego ciąg znaków (lub posiadającego wartość typu undefined oznaczającą, że wywołujący intencjonalnie "nie podał" wartości parametru) (UPDATE:) nie wiadomo, czy ona zwróci pusty ciąg znaków (nie wyrzuci wyjątku ani nie poda żadnego domyślnego tekstu). I dychotomia jest taka: Z jednej strony w domenie programu może to nie mieć sensu (z jakiegokolwiek powodu) – czyli należałoby wyrzucić ten wyjątek w tej sytuacji; z drugiej strony dla użytkownika może to mieć sens – czyli należałoby nie wyrzucać wyjątku, tylko powiedzieć: "OK, argument niepodany, ale musimy coś zwrócić; dajmy pusty ciąg znaków".

Czyli jeszcze raz moje wątpliwości w tym wątku:

  1. W jakich sytuacjach funkcja (jakakolwiek) mogłaby, a w jakich powinna mieć domyślne parametry? W jakich sytuacjach funkcja może, a w jakich powinna wyrzucać wyjątek w przypadku podania mniejszej/większej liczby argumentów niż zostało to określone w projekcie? W jakich sytuacjach mają sens wartości domyślne typu "pusty ciąg znaków" dla parametru o typie "ciąg znaków", lub "zero" dla parametru o typie "liczba całkowita dodatnia z zerem", lub undefined/null (dla jakiegokolwiek typu)?
  2. Według jakich kryteriów w tych sytuacjach należałoby to oceniać? Czy powyższe dwa (przykładowe) kryteria brzmią sensownie?

Jeśli powyższy przykład jest zbyt prosty, by zrozumieć, o co mi chodzi, podawać inne przykłady; postaram się na ich podstawie to wyjaśnić.

Jeśli trudno zrozumieć, o co mi chodzi, pytać. Idzie mi o to, bym był dobrze zrozumiany. :)

Jeśli w ogóle moje pytanie nie ma sensu, pisać, dlaczego.


UPDATE:

Doszedłem do wniosku, że mój problem jest trochę inny i zawiera się w pytaniu:

Kiedy funkcja powinna kończyć się sukcesem, a kiedy nie (np. wyrzucać wyjątek)?

Względem tego zmieniłem tytuł wątku. Nie zmieniam jednak jego treści, gdyż oceniam, że w dużej mierze jednak ma ona sens nawet przy zmienionym tytule. Jeśli ktoś będzie chcieć odpowiedzieć od strony parametrów, niech tak odpowie; jeśli od strony wyrzucania wyjątków, też będzie dobrze. Poprzedni tytuł: Kiedy parametry funkcji powinny być opcjonalne?

Poza tym zauważyłem, że pod mój problem podpada także wątpliwość: co w sytuacji, gdy parametry będą ze sobą sprzeczne? Dla powyższego przykładu: czy zwracać cokolwiek, czy nie, jeśli użytkownik poda jako pierwszy parametr { width: 5 }, a jako drugi undefined? Może to przeoczenie użytkownika – że nie sprawdził swoich wartości?


UPDATE2: Zmieniłem tytuł wątku raz drugi. Poprzedni tytuł: Kiedy funkcja powinna kończyć się sukcesem, a kiedy nie (np. wyrzucać wyjątek)?

2
  1. Nie rozumiem problemu
  2. Funkcja może walidować swoje parametry i rzucać jakimś IllegalArgumentException jeśli podane parametry nie są poprawne
  3. Jeśli masz sytuacje gdzie faktycznie można podać wiele parametrów, ale większość jest opcjonalna, wtedy może warto użyć Buildera?
0
Shalom napisał(a):
  1. Nie rozumiem problemu

Chodzi mi o to:

  1. W przypadku tej funkcji: co ma ona robić w przypadku podania jako parametru content pustego ciągu znaków, albo jako parametru config pustego obiektu, albo podobnie?
  2. W ogólnym przypadku: w jakich sytuacjach i według jakich kryteriów jak należałoby to oceniać, jak dana funkcja ma się zachować w przypadku takich wartości, jak pusty ciąg znaków dla typu "ciąg znaków", czy zero dla typu "liczba całkowita z zerem", czy undefined/null dla jakiegokolwiek typu (który oczywiście może przyjąć wartość undefined/null), czy pusty obiekt dla typu "obiekt", czy pusta tablica dla typu "tablica"?
  1. Funkcja może walidować swoje parametry i rzucać jakimś IllegalArgumentException jeśli podane parametry nie są poprawne

Może, zgadzam się; i właśnie pytam, kiedy powinna, a kiedy nie; kiedy argumenty powinny być uważane przez nią za poprawne, a kiedy nie.

  1. Jeśli masz sytuacje gdzie faktycznie można podać wiele parametrów, ale większość jest opcjonalna, wtedy może warto użyć Buildera?

"Builder" w sensie wzorca projektowego?

1

o_O przecież to są pytania stricte "biznesowe", nie ma na nie ogólnej odpowiedzi. Jeśli funkcja nie ma sensu dla pewnych parametrów to powinna to sygnalizować. Jeśli ktoś robi pop z pustej listy to czy ma to sens? Generalnie to programista określa zachowanie funkcji, także dla przypadków skrajnych. Można co najwyżej dyskutować "jak" sygnalizować problemy. Czy wyjątkami, czy zwracajac jakis Optional, czy może Either czy (nie daj Boże!) nulla.
Tak, Builder pomaga jeśli masz funkcje która może przyjąć wiele parametrów, niektóre opcjonalne. Taki Builder może też od razu walidować czy podany zestaw ma sens i wtedy twoja funkcja nie musi juz tego sama robić.

1

Wydaje mi się, że zależy jakie parametry. Parametr config może być opcjonalny bo jest raczej parametrem pobocznym,
łatwo dać jakiś domyślny, z którym funkcja będzie działać poprawnie i jednocześnie mieć sens, content już raczej nie bardzo bo to on jest głównym
powodem, dla którego ta funkcja w ogóle istnieje.

0
Shalom napisał(a):

o_O przecież to są pytania stricte "biznesowe", nie ma na nie ogólnej odpowiedzi.

Z jednej strony tak… ale z drugiej właśnie, jak piszesz dalej:

Jeśli ktoś robi pop z pustej listy to czy ma to sens?

To nie jest pytanie "biznesowe" o tyle, o ile nie umiem sobie wyobrazić, by jakakolwiek domena (czy po prostu jakikolwiek projekt) opisywała ten przypadek jako sensowny.

Generalnie to programista określa zachowanie funkcji, także dla przypadków skrajnych. Można co najwyżej dyskutować "jak" sygnalizować problemy. Czy wyjątkami, czy zwracajac jakis Optional, czy może Either czy (nie daj Boże!) nulla.

Nie rozumiem. Nie przeczysz sam sobie? Albo programista, albo projektant (= "biznes").

Tak, Builder pomaga jeśli masz funkcje która może przyjąć wiele parametrów, niektóre opcjonalne. Taki Builder może też od razu walidować czy podany zestaw ma sens i wtedy twoja funkcja nie musi juz tego sama robić.

To muszę jeszcze o tym poczytać. (A ledwie wczoraj zacząłem rozumieć wzorzec decorator wspomniany przez @Markuz! A już nowy… :P)


lookacode1 napisał(a):

Wydaje mi się, że zależy jakie parametry. Parametr config może być opcjonalny bo jest raczej parametrem pobocznym,
łatwo dać jakiś domyślny, z którym funkcja będzie działać poprawnie i jednocześnie mieć sens, content już raczej nie bardzo bo to on jest głównym
powodem, dla którego ta funkcja w ogóle istnieje.

Jest tu pewna racja. Ale widzisz, z drugiej strony mnie łatwiej jest wyobrazić sobie działanie tej funkcji dla argumentu content równego undefined, niż dla argumentu config równego undefined.

Ale może to jest też inny problem: że przenoszę znaczenie parametrów dla użytkownika tej funkcji na ich znaczenie w tej funkcji. Może nie przenosząc wszystko byłoby prostsze.


UPDATE:

Skłaniam się ku zdaniu, że końcowa ocena zawsze zależy od projektu aplikacji (= jej domeny = "biznesu"). Natomiast są pewne sytuacje, które należałoby rozpatrzyć szczególnie uważnie podczas dyskusji nad projektem aplikacji; na przykład takie, w których podanie parametru zazwyczaj nie ma sensu w żadnej domenie ("dziedzinie"?) / w danej domenie / w danej sytuacji, jak dzielenie przez zero czy zdjęcie elementu z pustego stosu. To jako programista.

Jako projektant zaś (czy "biznes") jest trudniej, bo muszę "wymyślić program" czasem niemal od zera (chyba nigdy "całkiem od zera"). Dla prostego przykładu właśnie moja funkcja createBox. Cały program w założeniach ma wyświetlać diagram złożony z co najmniej kilku takich "pudełek" (to znaczy, że ta funkcja będzie wywoływana co najmniej kilka razy). Czy, uwzględniając to, ma to sens, by ta funkcja nadawała parametrowi content wartość "" (pusty ciąg znaków) w przypadku, gdy równy jest undefined, czy nie? Jeśli tak, to dla pustego ciągu znaków musiałaby zwracać "puste pudełko" (wtedy jedynie ma sens). Jeśli nie, to musiałaby np. wyrzucać wyjątek (w JavaScripcie najpewniej obiekt Error). Co ma większy sens tutaj?


UPDATE2: A może – odnoszę się do poprzedniego akapitu – przy decydowaniu jednak należy uwzględnić, kto będzie tę funkcję wywoływał (=jaka inna funkcja)? Mam wrażenie, że to byłoby mieszanie odpowiedzialności lub coś podobnego, ale może nie?

1

W ogólnym przypadku: w jakich sytuacjach i według jakich kryteriów jak należałoby to oceniać,

ed. chyba w upade to napisałes.

Wydaje mi się że nie rozumiesz pewnej kwestii. Oceniasz według swojego widzi mi się, zdrowego rozsądku i zasady najmniejszego wysiłku. Jako programista Ty nikt inny, masz zdecydować samodzielnie, jak rozwiązać problem i jak zdecydujesz tak będzie. Ze wszystkimi wadami i zaletami tego stanu rzeczy. Nie ma żadnego zbioru mądrych ludzi którzy wiedzą i powiedzą obrzasku patrz na wschód ani magicznej księgi zawierającej wszystkie odpowiedzi, a jeśli przypadkiem tak się zdarzy że istnieje człowiek który zna rozwiązanie twojego nie trywialnego problemu, i nie jesteś z nim w zespole, to najprawdopodobniej jest zbyt zajęty by Ci odpowiedzieć, lub usłyszysz bardzo głębokie to zależny.
Mozna odpowiedzieć lub napisać ksiązke jak zrobić coś optymalnie, jak zrobić coś szybko, jak zrobić coś żeby blablabla, ale ogólniej odpowiedzi, jak należy postępować, nie było i nigdy nie będzie. Wynika to z istoty rzeczy. Life is brutal, matematycznie udowodnio że się nie jest możliwe stworzenie weryfikowalnych ogólnych odpowiedzi. Stworzenie dowodu zajęło matematyką 100? lat pracy bazującej na osiągnięciach tych przednimi. Nie ma sensu się szarpać i tak nic nie wymyślisz, tylko czas stracisz. Life is brutal. Musisz z tym pogodzić, i nauczyć jak sprowadzić że problem należy sprowadzać do skończonego kontekstu/wersji i z jego punktu oceniać co jest dobre co złe.
Jeśli jest dla Ciebie użyteczne zwracanie pustego strinaga, bo nie dostaniesz runtime exception to bardzo dobrze, jak tak zrobisz. Jeśli użyteczne dla Ciebie jest wyrzucenie wyjątku i zamkniecie aplikacji, bo na tym poziomie abstracji dane powinny być zwalidowane bo martwa aplikacja jest lepsza niz ta co kłamie, to ją ubij, to dobre rozwiązanie. Jeśli masz patern, że na poziomie abstarcji x zwracasz zawsze nulla i sprawdzasz go if'em dodaj ifa, jeśli urzywasz opcjonali to uzyj opcjonala. Mozesz też mieszać wszyskie praktyki naraz, bo czemu nie, ale nie bedzie to ani rozsądne, ani nie bedzie oznaczać najmniejszego wysiłku. Wiec jeśli nie lubisz klnać na komputer, to tak nie rób. Ja sie tym kieruje, jeśli klne w myślach lub pod nosem na kod który czytam, albo sie głowie bóg wie ile czując jak RAM w głowie mi się kończy, to znaczy to co widze to nie było dobre rozwiązanie. (chyba ze robisz coś specyficznego i płacisz złożonością profity w innych dziecinach).

2

Nie rozumiem. Nie przeczysz sam sobie? Albo programista, albo projektant (= "biznes").

Nie przeczę. Jeśli funkcja jest wystawiona do usera, to biznes decyduje co powinna zwracać użytkownikowi, to chyba dość oczywiste. To się nazywa wymagania funkcjonalne ;)
Jeśli funkcja jest gdzieś "niżej" to decyduje programista, jak mu wygodnie (i jak reszta zespołu uzna że ma sens), ale uwzględniając jednocześnie co się z tym potem dzieje!

Jeśli masz pole które pokazuje wysokość raty kredytu, to biznes decyduje co wyświetlić jak użytkownik poda, że chce podzielić kwotę na 0 rat.
Jeśli piszesz jakiegoś BigDecimala i tam zastanawiasz się co zrobić jak ktoś wykona dzielenie przez 0, to jest twoja sprawa jako programisty. Ale jest trochę "podyktowana" tym do czego potem tego użyjesz. Nikt ci nie broni zwrócić 0, ale bardzo możliwe, że jednak będziesz musiał gdzieś w kodzie "wyżej" umieć rozpoznać taką sytuacje, żeby odróżnić takie "lewe 0" od prawdziwego 0. Chociażby do tej raty kredytu, żeby nagle nie zrobić, że milion podzielony na 0 rat to 0 do zapłaty :) W efekcie pewnie nie zdecydujesz się tu zwracać 0 tylko jednak postanowisz zasygnalizować błąd, żeby potem wygodnie z tego korzystać.

2
Shalom napisał(a):

Nie rozumiem. Nie przeczysz sam sobie? Albo programista, albo projektant (= "biznes").
Jeśli funkcja jest gdzieś "niżej" to decyduje programista, jak mu wygodnie (i jak reszta zespołu uzna że ma sens), ale uwzględniając jednocześnie co się z tym potem dzieje!

No właśnie, reszta zespołu. Nie powinno być tak że każdy programista w zespole rozwiązuje se taką sytuację po swojemu a tego bajzlu nikt potem nie ogarnia, niestety zdarza się coś takiego bardzo często, zwłaszcza gdy w zespole nie ma nikogo z doświadczeniem, a za projektanta uważa się użyszkodnika końcowego, bo na projektanta/architekta kasy szkoda

0
Shalom napisał(a):

Jeśli piszesz jakiegoś BigDecimala i tam zastanawiasz się co zrobić jak ktoś wykona dzielenie przez 0, to jest twoja sprawa jako programisty. Ale jest trochę "podyktowana" tym do czego potem tego użyjesz.

Tak sobie myślę, że to, co funkcja robi w przypadkach skrajnych (lub: za co odpowiada w takich przypadkach klasa czy moduł), zależy od projektu – a to, co projekt w takich przypadkach przewiduje, zależy od wymagań. Czyli jakby przypadki skrajne powinny być od początku ściśle opisane. Ale jeszcze nie mam tego przemyślanego.

1

Ja bym napisał testy i wtedy bym się przekonał, w którą stronę pójść, żeby było najwygodniej dla klienta mojego komponentu. Jeśli nie będę miał na to testu, to w zasadzie mogę zrobić dowolnie (albo przyjąć konwencję w projekcie). TDD baby.

0

@Charles_Ray: nie jestem przekonany, czy testy (jednostkowe?) mają związek z wygodą korzystania z oprogramowania. Natomiast jeśli mogę robić dowolnie, to to też byłoby problemem; mógłbym zadać sobie pytanie: "Dowolnie… czyli konkretnie jak?"


UPDATE: Po aktualizacji Twojego postu: konwencję z drugiej strony mógłbym wtedy przyjąć łatwo. Ale ona też musi zostać ustalona na podstawie czegoś.

1 użytkowników online, w tym zalogowanych: 0, gości: 1