Option(ale) jako pola zamiast nulli?

0

Załóżmy że mamy klasę (dataclass Kotlinowa) np. Issue które ma opcjonalne pole assignedTo typu UserId które może być puste - czy najlepszym sposobem nie jest po prostu stosowanie Option/Optionala jako typu tego opcjonalnego pola ?. W czystym Kotlinie nie mamy co prawda Optiona,ale są biblioteki


data class Issue(val assignedTo: Option<UserId> = Option.none()) {} 

5

Nie lepiej zrobić AssignedIssue i UnassignedIssue? Opcjonalne parametry zalatują troche antypatternem imo :)

0

Też wydaje mi się że lepiej zrobić tutaj jakieś sealed Class z tymi 2 opcjami zamiast takiego opcjonalnego parametru, szczególnie że kotlin ma sensowny pattern matching do tego.

1

Dla pół nie powinno się używać Optional. Jest na ten temat trochę w sieci. Itp
https://dzone.com/articles/optional-anti-patterns

BTW, null jest zły? Załóżmy, że customer_mail = null zastąpimy optionalem, np
https://www.baeldung.com/java-optional-or-else-vs-or-else-get
Niech zwraca [email protected]
albo coś innego w stylu 'purge twoja-stara' - było coś podobnego na 4p w jokes

nulla przynajmniej wychwycisz, radośnie wstawionego dziwnego default z optional nie zlokalizujesz przez 5 lat.

IMHO optional np. dla reduce sum/concatenate dla pustej kolekcji jest ok, ale dla user_ID ja bym go nie stosował, tylko po ludzku przemyślał co, dlaczego i po co dzieje się kiedy user_ID jest nieokreślony (piszę nieokreślony żeby się zastanowić zamiast na szybko dać null). Np. domyślnie jakiś typ/wartość undefinedYet zamiast null, wartość undefinedYet będzie ustawiana na sensowną wartość dopiero kiedy będzie ona znana albo wcale kiedy nie będzie takiej informacji dostarczonej.

3

nulla przynajmniej wychwycisz, radośnie wstawionego dziwnego default z optional nie zlokalizujesz przez 5 lat.

Nie nie nie. Nie wychwycisz default value! To nie optional w twoim przykładzie jest błędem, tylko użycie domyślnej wartości. W praktyce to nulle są właśnie niewidzialne bo typesystem ich nie wychwyci. Optional widać gołym okiem i wiesz że to pole może mieć wartość albo nie i musisz explicite coś z tym zrobić.

1

To że jakiś baeldung czy dzone coś pisze to nie znaczy że trzeba tak robić. Anyway, +1 dla sealed class, tak by było najbardziej 'funkcyjnie'

0

Optional może siedzieć ukryty w implementacji z której tylko korzystasz, może nie być wzmianki o tym, że jest użyty.
Na przykładzie 'opcjonalnego' maila już wolałbym nula niż ukrytego w implementacji opcjonalnego maila do programisty albo admina. I javadoc z info, że może dać null.
Póki klient nie zgłosi bugów to optional przykryje ewentualny problem.

1

@scibi92, a co jest nie tak z @Nullable?
Option(al) jest niezalecany jako pole AFAIK.

2

@Shalom: faktycznie, dawno nie korzystałem z Kotlina i zapomniałem że przeciez są sealed classy...
@vpiotr - chodziło o Kotlina nie Jave. A poza tym ten Optional nie jest zalecany w Javie bo nie jest serializowany, za to taki Option w Vavrze jest ;)

EDIT:
no ale w sumie co jesli mamy klasę NonAssignedIssue i chcemy zrobić kopię która będzie AssignedIssue albo odwrotnie i użyemy sealed class?

0

co jesli mamy klase NonAssignedIssue i chcemy zrobic kopie ktora bedzie AssignedIssue

NajDDDowniej bedzie pewnie miec metode assign(..) w UnassignedIssue, która zwróci AssignedIssue :)

4

ten UnassignedIssue i AssignedIssue to dobre rozwiązanie.
Pola Option tez nie są złe.
Ten artykuł o antypatternie Optional z dzone to w połowie bdzura, a w połowie dotyczy bieda Optionala z javy - Option to co innego (bo jest Serializowalny na przykład).

Mnie w kontekście kotlina zawsze zastanawia korzystanie z nullable (?) i generalnie ciągle nie korzystam... (tylko przy odpakowywaniu czegoś z javy na chwilkę).

0

"UnassignedIssue i AssignedIssue" - Assigned/Unassigned jak dla mnie stan Issue (z jakiegoś mniej lub bardziej wydumanego lifecycle encji), zaś fakt do kogo jest Issue przypisane, to informacja dodatkowa. Później ktoś może pójść za ciosem i nastąpi eksplozja ResolvedIssue/RejectedIssue/... ;)

1
yarel napisał(a):

Później ktoś może pójść za ciosem i nastąpi eksplozja ResolvedIssue/RejectedIssue/... ;)

A to aż tak złe? ;)

1
yarel napisał(a):

"UnassignedIssue i AssignedIssue" - Assigned/Unassigned jak dla mnie stan Issue (z jakiegoś mniej lub bardziej wydumanego lifecycle encji), zaś fakt do kogo jest Issue przypisane, to informacja dodatkowa. Później ktoś może pójść za ciosem i nastąpi eksplozja ResolvedIssue/RejectedIssue/... ;)

To teraz pójdźmy w drugą skrajność, zostawmy ten userId czy tam email jako Option / Optional / @Nullable bo przecież mamy tylko stany assigned / unassigned, co może pójść źle? ;)

A pół roku później jest już 10 różnych możliwych stanów tego Issue (assigned / unassigned / suspended / blocked / pending / overdue / rejected / cokolwiek) i w zależności od tego który to jest stan 20 różnych property może być puste lub nie, używane lub nie etc. To dopiero będzie eksplozja

0
danek napisał(a):
yarel napisał(a):

Później ktoś może pójść za ciosem i nastąpi eksplozja ResolvedIssue/RejectedIssue/... ;)

A to aż tak złe? ;)

Kotlina nie znam, więc może tam to ma uzasadnienie. Staram się patrzeć z perspektywy modelu, bez wchodzenia w technologię. Co mi wniosą osobne encje? Co jeśli chciałbym dynamicznie definiować cykl życia dla Issue? (stany + przejścia między nimi, np. flow w JIRA).

Nie wiem, może w przypadku konkretnego problemu ma to uzasadnienie i jest bardzo dobrym rozwiązaniem :-)

1

Tak teraz myślę, że daje to jeszcze jedno fajne zabezpieczenie. Porównaj te dwie sygnatury:

fun close(task:Task)
fun close(task:OpenTask)

Aczkolwiek bez jakiegoś konkretnego przypadku to niewiele wymyślimy ;)

0
yarel napisał(a):

"UnassignedIssue i AssignedIssue" - Assigned/Unassigned jak dla mnie stan Issue (z jakiegoś mniej lub bardziej wydumanego lifecycle encji), zaś fakt do kogo jest Issue przypisane, to informacja dodatkowa. Później ktoś może pójść za ciosem i nastąpi eksplozja ResolvedIssue/RejectedIssue/... ;)

To robisz enum IssueStatus i to jest pole :)

@danek @jarekr000000 o takich rzeczach (z typami danych) mówił na swej prezentacji na JDD :P

0
danek napisał(a):

Tak teraz myślę, że daje to jeszcze jedno fajne zabezpieczenie. Porównaj te dwie sygnatury:

fun close(task:Task)
fun close(task:OpenTask)

Aczkolwiek bez jakiegoś konkretnego przypadku to niewiele wymyślimy ;)

Rozumiem ideę. Tam gdzie masz ustalone i niezmienne reguły biznesowe (np. specyfikację protokołu TCP) jest ok, ale tam gdzie reguły biznesowe mogą się zmieniać (wspomniana elastyczność definiowania flow), to takie podejście pewnie będzie wymagało więcej zachodu.

1

Nie no, jak ma być elastycznie, to trzeba zrobić tak:

class Issue
{
    public Map<string, bool> states;
}

Wtedy można zmieniać dynamicznie nie tylko stany, i mieć issue jednocześnie open i closed, ale nawet ich nazwy! I w ogóle jak mamy np. { { "Open": true"}, { "Closed": false } }, to zmienić flow możemy zarówno zmieniając wartości, jak i nazwy kluczy. To jest dopiero elastyczność!

Zresztą, można nawet pójść dalej, bo bool to jednak mocno ograniczający jest, tylko dwie wartości, i zrobić public Map<string, string> states. I wtedy nie ograniczamy się do true i false, ale możemy mieć też "don't care" albo "maybe tommorrow". I oczywiście "null".

UnassignedIssue, AssignedIssue, to złe podejście. Obiektów nie można robić tak dużo, bo zajmują pamięć. No i w ogóle nie mają nic wspólnego z modelem, bo przecież w modelu nie ma czegoś takiego jak przypisane i nieprzypisane zadanie. Absolutnie.

0

Pomysł z UnassignedIssue, AssignedIssue to dobre podejście. Można pójść dalej i wymyślić np. TestedIssue albo ImplementedIssue... W encji TestedIssue będą pola odpowiedzialne za dane testowania (kiedy, kto, gdzie testował, itd.

0
scibi92 napisał(a):

Załóżmy że mamy klasę (dataclass Kotlinowa) np. Issue które ma opcjonalne pole assignedTo typu UserId które może być puste - czy najlepszym sposobem nie jest po prostu stosowanie Option/Optionala jako typu tego opcjonalnego pola ?. W czystym Kotlinie nie mamy co prawda Optiona,ale są biblioteki


data class Issue(val assignedTo: Option<UserId> = Option.none()) {} 

Osobiście widzę zerowy zysk z użycia w Kotlinie takiej konstrukcji. Co ona ma wnieść w porównaniu do:

data class Issue(val assignedTo: UserId? = null) {} 

Podstawowym celem użycia czy to Optional, czy to Option jest zasygnalizowanie, że to pole, bądź zwracana wartość może mieć przypisany null pointer, czyli dokładnie to co robi w Kotlinie ?.

0

Nie znam się specjalnie na praktykach w Kotlinie, ale nie da się zrobić czegoś w stylu:

enum class Issue {
  CREATED,
  ASSIGNED {
    var to: UserId
  },
  CLOSED {
    var by: UserId
  }
}

Bo to chyba byłoby najczytelniejsze rozwiązanie na maszynę stanu skończonego automat skończony jaką są stany w jakich może się znaleźć issue.

0

Po 1. chyba skończoną maszynę stanów, a nie stanu skonczonego (no chyba że o czyms nie wiem? :) ), a po 2. wydaje mi sie ze to rozwiązanie w żaden sposób nie zapewnia odpowiednich niezmienników (przejścia nie powinny być dowolne z każdego stanu w każdy).

0

@hauleth: enumy to takie "singletony" więc nie ;]

2

@hauleth: szukasz czegoś co się nazywa algebraiczny typ danych i w kotlinie jest wspierane przez sealed class

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