Skip to content

Either or throw? #13

@SimoneBressan

Description

@SimoneBressan

Il problema secondario ma non risolto perchè è un BrakingChange è in produzione, questo approccio alle use case basato sugli Either porta ad alcuni problemi:

  • Sulla Failure bisogna mantenere lo StackTrace del punto di creazione della failure che se non è necessaria non serve e questo è molto pesante in termini di performance
  • La gestione dell'Either e' complicata e prolissa anche quando dovrebbe essere molto semplice

Lanciare le Failure con throw porterebbe a notevoli vantaggi, le failure non gestite andrebbero direttamente sul logger e verebbe creato lo stackTrace solo quando il logger riceve l'errore inoltre i tipi sarabbero sicuri e la scrittura del codice verebbe piu breve e leggibile.

Either:

  1. 🔴 Più posti dove viene gestita la failure con lo stesso codice, si puo ovviare scrivendo estensioni apposta per i futuri
  2. 🔴 E' necessario controllare il tipo della failure con il costrutto if ( is ) per ogni failure
  3. 🔴 Se non si vuole gestire la failure bisogna inviarla al logger ottenendo cosi uno stackTrace astruso
  4. 🟢 E' possibile utilizzare "facilmente" la use case con il pacchetto provider ma richiede una scrittura prolissa per il recupero del valore final res = context.read<Either<Failure, Success>>() e poco usabile/usato lato presentation
    ThrowCatch:
  5. 🟢 Un unico posto dove gestire la failure
  6. 🟢 Non è necessario controllare il tipo della failure dato che è presente la keywork on
  7. 🟢 Se non si vuole gestire la failure non serve far nulla, viene catturata dal logger con lo stackTrace corretto e chiaro
  8. 🟢 Non si ottiene codice anidadato o codice prolisso con molti controlli di tipo
  9. 🔴 Non sempre è possibile passare avanti nella catena l'errore, l'utilizzo diretto con il pacchetto provider non è possibile. E' veramente necessario? No, dovresti passare sempre per il bloc appena la logica diventa complicata la quale necessita l'utilizzo di provider

Sono mostrati solo esempi sicuri da scrivere, che non possono presentare errori di tipo
Con Either:
Esempio 1:

final result = await myUseCase.call(MyUseCaseParams());

result.fold((failure) {
  if (failure is SpecificFailure) {
    // Handle my specific failure
    return;
  } 
  // Handle a failure
}, (myRes) async {
  final result = await myUseCase.call(MyUseCaseParams());

  result.fold((failure) {
    // Stesso codice della precedente gestione della failure
  }, (myRes) {
    // Hendle result
  })
})

Esempio 2:

final result = await myUseCase.call(MyUseCaseParams());

if (result is Left<Failure, MyResultType>) {
  final failure = result.value;
  if (failure is SpecificFailure) {
    // Handle my specific failure
    return;
  } 
  // Handle a failure
  return;
}
final result = await myUseCase.call(MyUseCaseParams());

if (result is Left<Failure, MyResultType>) {
  // Stesso codice della precedente gestione della failure
  return;
}

// Handle all results

Con throw e catch
Esempio 3:

try {
  final result = await myUseCase.call(MyUseCaseParams());
  final result = await myUseCase.call(MyUseCaseParams());
} on SpecificFailure {
  // Handle my specific failure
} on Failure {
  // Handle failure
  // Un unico posto dove gestire la failure
}
  • Ci sono altri vantaggi o svantaggi nell'uso di uno di questi due approcci?
  • Per esperienza personale quale di questi approcci è stato più corretto e con meno bug?

Link esterni:
provider
bloc

Altri problemi conosciuti sono stati presentati in parte in questa PR #12.
Come dovresti far per recuperare la UI da un errore in aspettato con i diversi casi di utilizzo?
Con Either: Esempio 1:

try {
  final result = await myUseCase.call(MyUseCaseParams());
  
  result.fold((failure) {
    if (failure is SpecificFailure) {
      // Handle my specific failure
      return;
    } 
    // Handle a failure
  }, (myRes) async {
      // Hendle result
  })
} catch (error) {
   // Recupera la UI in caso fosse necessario
  retrhow; // Invia l'errore al logger perchè non gestito
}

Con throw e catch: Esempio 2:

try {
  final result = await myUseCase.call(MyUseCaseParams());
} on Failure {
  // Handle failure
} catch (error) {
  // Recupera la UI in caso fosse necessario
  retrhow; // Invia l'errore al logger perchè non gestito
}

Non è molto chiaro mischiare i due casi di gestione dell'errore per quanto riguarda l'esempio 1 invece nell'esempio 2 rimane tutto molto chiaro

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions