Kotlin i niejednoznaczny null

Kotlin i niejednoznaczny null
AP
  • Rejestracja:około 6 lat
  • Ostatnio:3 miesiące
  • Postów:164
0

Zainspirowany wątkiem o wyższości innych języków nad Javą, postanowiłęm sprawdzić Kotlina.
Zacząłem przepisywać projekt nad którym obecnie pracuje, póki co muszę przyznać, całkiem fajnie to wygląda, dużo mniej kodu, niektóre rzeczy bardzo ułatwiają pracę, ale chyba czegoś nie łapie...

Mam taką metodę w repozytorium

Kopiuj
    fun findById(userId: String): UserProfile? {
        return mongoTemplate.findOne(userIdQuery(userId), UserProfile::class.java)
    }

I taki serwis do logowania użytkownika

Kopiuj
@Service
class AuthenticationService(private val userRepository: UserRepository,
                            private val tokenProvider: JwtTokenProvider,
                            private val passwordEncoder: PasswordEncoder) {

    fun authenticate(input: AuthenticationInput): String? {
        val user = userRepository.findByEmail(input.email)
        require(user != null ) {"User ${input.email} was not found"}
        require(validPassword(input.password, user.password)) {"Password is incorrect"}
        return tokenProvider.createToken(user.id);
    }

    fun validPassword(providedPassword: String, actualPassword: String): Boolean {
        return passwordEncoder.matches(providedPassword, actualPassword);
    }
}

No i wywala to błąd, który wiem jak poprawić ale nie do końca rozumiem czemu on tam jest?
screenshot-20201127212707.png

  • findByEmail() może zwrócić nulla, ok, w następnej linijce jednak to sprawdzam
  • metoda validPassword nie akceptuje nullowych wartości, ale mimo wszystko IDE nie krzyczy, że nie wolno - czy to dlatego, że wcześniej jest require != null?
  • jeżeli tak, to dlaczego createToken(), który równeż wymaga nie nullowej wartości, nagle ma z tym problem?

No i dwa pytania poboczne

  • wyczytałem, że require jest ok do takich powiedzmy biznesowych walidacji, prawda to?
  • nie widzę w Kotlinie Optional, poza tym z java.util, jak obsługiwać takie przypadki jak ten z repo? w Javie zwracanym typem był po prostu Optional<UserProfile>, czy w Kotlinie też korzystać z Javowego Optional czy potrzebuje Option z Vavra, czy zwracanie nulla jest ok bo i tak muszę go obsłużyć?
edytowany 2x, ostatnio: AngryProgrammer
stivens
createToken zwraca stringa lub nie? O.o
S9
  • Rejestracja:ponad 10 lat
  • Ostatnio:6 miesięcy
  • Lokalizacja:Warszawa
  • Postów:3573
2

1.W Kotlinie null jest ok
2.A co przymuje metoda createToken? Bo zdaje sę że przekazujesz tam user.id które może być nullem


"w haśle <młody dynamiczny zespół> nie chodzi o to ile masz lat tylko jak często zmienia się skład"
AP
  • Rejestracja:około 6 lat
  • Ostatnio:3 miesiące
  • Postów:164
0

@scibi92:

Właśnie miałem edytować posta, znalazłem swój błąd, tak jak mówisz... z wiadomych przyczyn id musi być tutaj nullable

Kopiuj
@Document
data class UserProfile(
        @Id
        val id: String? = null,
        val email: String,
        val password: String,
}

No a createToken() oczekuje na non-nullable Stringa

No to w takim razie, co z required? Mogę stosować do obsługi logiki biznesowej? Czy to antypatern jak rzucanie wyjątków w Javie przy każdej możliwej okazji?

edytowany 1x, ostatnio: AngryProgrammer
stivens
  • Rejestracja:ponad 8 lat
  • Ostatnio:około 8 godzin
1

Zalezy czy chcesz IllegalArgumentException czy bawic sie w jakies monady lub T?.

Jesli haslo sie nie zgadza no to generalnie da sie to naprawic proszac uzytkownika o ponowna probe. Tym zreszta moze sie pomylka zdarzyc, wiec moze to nie taka sytuacja wyjatkowa?

PS: Takie jednolinijkowe wyrazenia mozesz zapisac krocej jako

Kopiuj
    fun validPassword(providedPassword: String, actualPassword: String): Boolean =
        passwordEncoder.matches(providedPassword, actualPassword)
    

    // a nawet 
    fun validPassword(providedPassword: String, actualPassword: String) =
        passwordEncoder.matches(providedPassword, actualPassword) // ale niekoniecznie najlepsze w publicznych metodach

EDIT: Jednolinijkowe to byl zbyt duzy skrot myslowy. Generalnie wyrazenia.


λλλ
edytowany 3x, ostatnio: stivens
jarekr000000
U mnie w kodzie w zasadzie nigdy nie używam return. W scali to jest łapane przez lintery. W kotlinie też przyjmuje, że się w to nie bawię.
stivens
To ze w Scali nie padnie return nie znaczy jeszcze ze to nie Scava. Tzn. return jest zbedny nawet jak masz blok kodu ze statementami.
jarekr000000
@stivens: to fakt, ale to jest zwykle dobry znak.
stivens
W Kotlinie byc moze taki constraint by byl silniejszy. Bo o ile wewnatrz lambdy dalej mozna walic imperatywnie, to chociaz widac ze najbardziej zewnetrzna czesc funkcji jest wyrazeniem
AP
  • Rejestracja:około 6 lat
  • Ostatnio:3 miesiące
  • Postów:164
0

Właśnie zauważyłem, że required rzuca IllegalArgumentException, a we wcześniejszym podejściu w Javie chciałem uniknąć rzucania wyjątków w takiej sytuacji. Tak to wyglądało w Javie

Kopiuj
public class OperationResult<T> {
    private final Either<List<Error>, T> result;

    private OperationResult(Either<List<Error>, T> result) {
        this.result = result;
    }

    public static OperationResult<Success> success(String message) {
        return new OperationResult<>(Either.right(new Success(message)));
    }

    public static <T> OperationResult<T> success(T object) {
        return new OperationResult<>(Either.right(object));
    }

    public static <T> OperationResult<T> failure(Seq<Error> errors) {
        return new OperationResult<>(Either.left(errors.toJavaList()));
    }

    public static <T> OperationResult<T> failure(Error... errors) {
        return new OperationResult<>(Either.left(Arrays.asList(errors)));
    }

    public Either<List<Error>, T> getResult() {
        return result;
    }    
}

No i sam AuthenticationService wyglądał tak

Kopiuj
    public OperationResult<AuthenticationResult> authenticate(AuthenticationInput command) {
        return userRepository.findByEmail(command.getEmail())
                .map(user -> validatePassword(user, command))
                .orElseGet(() -> failure(Error.USER_NOT_FOUND));
    }

    private OperationResult<AuthenticationResult> validatePassword(User user, AuthenticationInput command) {
        return passwordEncoder.matches(command.getPassword(), user.getPassword()) ?
                success(authenticationToken(user)) :
                failure(Error.INCORRECT_PASSWORD);
    }

    private AuthenticationResult authenticationToken(User user) {
        return new AuthenticationResult(tokenProvider.createToken(user.getId()));
    }

Error to po prostu enum.
Jest podobny mechanizm w Kotlinie czy tak jak w Javie - Vavr i piszę to samemu?

edytowany 2x, ostatnio: AngryProgrammer
Zobacz pozostałe 4 komentarze
stivens
W sumie Vavra najlepiej do Kotlina tez podlaczyc bo te kolekcje z Kotlina to java.util w masce i z duzo bogatszym interfejsem.
stivens
Albo nie tyle "najlepiej" co mozna
AP
Czyli potrzebuje zarówno ArrowKt i Vavra?...
stivens
Nie. Wystarczy jedno. Monad niestety nie ma w bibliotece standardowej ale biblioteka to raczej nie jest duza niedogodnosc
jarekr000000
pamiętaj, że jest https://github.com/vavr-io/vavr-kotlin - kilka goodies do kotlina, żeby z vavra sie lepiej korzystało (nic specjalnego, ale przyjazne)
damianem
  • Rejestracja:prawie 8 lat
  • Ostatnio:4 miesiące
  • Postów:205
1

Polecam użyć sealed class:

Kopiuj
class AuthenticationService(
    private val userRepository: UserRepository
) {
    fun authenticate(input: AuthenticationInput): AuthenticationResult =
        userRepository.findByEmail(input.email)
            ?.let { user -> if (passwordValid(input.password, user.password)) AuthenticationResult.Ok else AuthenticationResult.IncorrectPassword }
            ?: AuthenticationResult.UserNotFound

    private fun passwordValid(givenPassword: String, userPassword: Any): Boolean {
        TODO("Not yet implemented")
    }
}

sealed class AuthenticationResult {
    object Ok : AuthenticationResult()
    object UserNotFound : AuthenticationResult()
    object IncorrectPassword : AuthenticationResult()
}
edytowany 1x, ostatnio: damianem

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.