В программировании часто бывает так: что-то очень простое называют так сложно, что хочется плакать. Так произошло для меня с Discriminated Unions. Когда кто-то у нас на встречах рассказывал про эту штуку, упоминая при этом F#, я плакал.🔮
Было ничего не понятно. Ни зачем, ни что, ни как.
А все из-за названия, которое непонятно как перевести на русский. Вон даже гугл не справляется
Короче, на деле это оказалась очень простая и практичная штука. И её пока официально нет в шарпе, но её все ждут, а для тех, кто не дождался есть библиотечка (и я снова призываю вас въебать ей звезду как можно оперативнее), которая имплементит эту фичу.
Что эта штука помогает делать. Предположим у вас есть метод:
public Task<User> CreateUser(string userName, string email);
Что возвращает этот метод? Ну понятное дело юзера. Но это в лучшем случае. А что если такой пользователь уже создан? А если email не по формату? Что вернуть тогда?
Есть несколько вариантов.
1️⃣ Можно сделать так (вариант для джуниОров):
<exception cref="UserExists"></exception>
<exception cref="EmailValidationException"></exception>
public Task<User> CreateUser(string userName, string email);
Это, конечно, мде. За логику на эксепшонах будут ругать. Но если никто не видит, то по-честному это самый удобный способ.
2️⃣ Второй способ заключается в том, чтобы возвращать не юзера, а некий UserCreationResult, в котором будет что-то типа такого (вариант для сениОров):
public class UserCreatedResult
{
public bool IsCreationSuccess { get; set; }
public User? User { get; set; }
}
Понятно, что IsCreationSuccess можно превратить в enum и так далее. Но этот способ неудобен, так как мы возвращаем в случае неудачи избыточное поле User. Оно, конечно, нулловое, но согласитесь, это-то и плохо.
Было бы удобно что-то типа такого:
public Task<User || UserAlreadyExists || EmailValidationFailed> CreateUser(string userName, string email);
Это удобный вариант, так как под каждый случай, мы вернем ровно те данные которые нужны для обработки этого случая. Так можно делать в F#, в TypeScript (ну не прям так, но типа). За что многие и любят эти язычки.
3️⃣ Так вот в по сути библиотечка OneOf и позволяет делать то же самое! И вот как это выглядит (вариант для чемпиОнов):
public Task<OneOf<User, UserAlreadyExists, EmailValidationFailed>> CreateUser(string userName, string email);
🅰️ Итого: Если метод может возвращать разные типы в результате своей деятельности – не делай енумы со статусами успешности или не делай булеановские поля типа IsSuccess, а поработай с библиотечкой OneOf. Правда пока я бы только для пет-проектов это юзал, а для рабочего прода дождался бы официальной реализации в языке. Пропозал есть, значит все сокро будет – надо только подождать.
Я не буду тут приводить примеры кода, а напишу, что попробовал на своих пет-проектах все это дело и скажу, что смог неплохо так упростить пару свитчей. Причем, что мне нравится. Код упростился как в методе, так и в обработчиках результатов.
Короче, супер-рекомендую обратить внимание на библиотечку OneOf, втрепать ей звезду на гитхабе, и посмотреть видос от Ника Чапсаса. Я-то думаю, что все заебись объяснил, но он там еще и по коду все это дело раскидывает.