Tipi di reso dettagliati

5

Recentemente ho scritto un codice che tratta di terze parti - ovviamente si verificheranno degli errori, quindi utilizzerò le / o forse le monade se appropriato. Poiché si tratta di C #, sto anche utilizzando attività asincrone.

Il mio problema è dopo che è stato detto tutto & fatto i miei tipi di ritorno sono piuttosto prolissi. per es.

Task<Either<ApiErrorEnum, ActualApiResultDto>> GetSomeFoo(int fooId);

Ma aspetta che possa migliorare ancora

Task<Either<ApiErrorEnum, IEnumerable<ActualApiResultDto>> GetSeveralFoos()

È un po 'troppo, ma mi sembra tutto necessario. Ovviamente l'attività < .. > è perché si tratta di un'operazione legata all'IO. l'Either è necessario perché questo può andare storto in molti modi diversi, e il DTO del risultato reale è necessario perché è quello che cerco davvero.

Ho scritto del codice per fondamentalmente typedef Either<ApiErrorEnum,T> ma è un po 'carente in quanto il codice dell'infrastruttura per O ho scritto non può gestire abbastanza i tipi derivati (ho provato, c'è sempre un caso limite o due ).

Domanda TLDR: come posso ridurre la verbosità dei miei tipi di ritorno?

    
posta mamidon 09.06.2016 - 19:38
fonte

3 risposte

3

Non mi piace usare l'ereditarietà per questo, perché l'API sarà goffo. Perdi il bel concatenamento funzionale quando usi Either se lo sposti in Task, perché in quel caso dovrai attendere dopo ogni chiamata.

Invece, opterei per creare EitherTask , cioè Either che è asincrono dentro o anche andare oltre e creare qualcosa come ApiResult che include anche il codice di errore.

Qualcosa come:

public class ApiResult<TResult>
{
    Task<Either<ApiErrorEnum, TResult>> _task;

    public ApiResult(Task<Either<ApiErrorEnum, TResult>> task)
    {
        _task = task;
    }

    public ApiResult<TOther> Select<TOther>(Func<TResult, TOther> selector)
    {
        var task = _task.ContinueWith(x => x.Result.Select(selector));
        return new ApiResult<TOther>(task);
    }

    public async Task<TOther> Final<TOther>(Func<TResult, TOther> valid, Func<ApiErrorEnum, TOther> errored)
    {
        var eitherVal = await _task;
        return eitherVal.Match(valid, errored)();
    }
}

Che può quindi essere usato come:

public ApiResult<IEnumerable<ActualApiResultDto>> GetSeveralFoos()
{
    // do whatever
}

var result =
    await
    GetSeveralFoos()
    .Select(x => x.Count())
    .Final(x => x.ToString(), x => "Error :" + x.ToString());

Ovviamente, prendere tutti i casi limite (come eccezioni quando si usa ContinueWith ) richiederà del tempo, ma è fattibile.

Un problema che posso vedere con questo è che GetSeveralFoos() non può essere implementato come async , perché non restituisce Task . E tutte le soluzioni che posso pensare richiedono l'ortografia di Task<Either<ApiErrorEnum, IEnumerable<ActualApiResultDto>> da qualche parte all'interno dell'API. Forse qualcosa come Task.Then potrebbe fare è più leggibile senza usare await (potresti addirittura sostituire ContinueWith con esso).

    
risposta data 09.06.2016 - 21:15
fonte
2

Una possibile opzione

public class ActualApiResultWrapper : Either<IApiErrorEnum, IEnumerable<ActualApiResultDto>>
{
}

Task<ActualApiResultWrapper> GetSeveral();

Quindi il risultato successivo, OtherApiResult ...

public class OtherApiResultWrapper : Either<IApiErrorEnum, IEnumerable<OtherApiResultDto>>
{
}
    
risposta data 09.06.2016 - 19:47
fonte
1

Puoi ridurre la verbosità usando le eccezioni, che è il modo idiomatico e stabilito di gestire le condizioni di errore in C #.

In pratica stai introducendo una forma di eccezioni tipizzate (come Java) o codici di errore digitati su una lingua con eccezioni non controllate. Questo è destinato ad essere più dettagliato e, in particolare, non è possibile utilizzare i costrutti try / catch per gestire i propri codici di errore, ma è comunque necessario provare / catturare per gestire le eccezioni built-in. In generale "combattere contro la lingua" sarà più prolisso. Dovresti prendere in considerazione l'utilizzo di un linguaggio più a tuo piacimento come Java (per le eccezioni controllate) o F # (per il supporto integrato per la corrispondenza dei modelli, che renderà la gestione di questo tipo di errori meno prolisso).

Se il tuo requisito è di avere codici di errore tipizzati esplicitamente come tipi di firma, non vedo davvero come i valori di ritorno potrebbero essere più semplici.

    
risposta data 09.06.2016 - 19:46
fonte

Leggi altre domande sui tag