Exceptions são muito intrusivas

No post anterior, compartilhei um exemplo de como containers podem nos ajudar a deixar o código mais claro sobre os resultados de um método.

public interface IEmployeeRepository
{
  Option<Employee> GeyById(string id);
}
 
class EmployeeRepository : IEmployeeRepository
{
  public Option<Employee> GeyById(string id)
    =>; new DbContext().Find(id);
}

E o que temos sobre Exceptions?! O método GetById irá lançar uma exception se alguma coisa der errado (se não for possível conectar com o banco, por exemplo), mas não há nada sobre isso na assinatura do método.

Either

Uma abordagem superior seria:

public interface IEmployeeRepository
{
    Either<Exception, Option<Employee>> GeyById(string id);
}

class EmployeeRepository : IEmployeeRepository
{
    public Either<Exception, Option<Employee>> GeyById(string id)
    {
        try
        {
            return new DbContext().Find(id);
        }
        catch (Exception e)
        {
            return e;
        }
    }
}

O tipo Either é apenas uma struct comum com conversões implícitas e algumas operações funcionais. Ele permite diferentes tipos de retorno. Neste exemplo, estamos usando o container para tornar explícito que o método pode resultar tanto uma instância de Employee quanto uma exceção.

Try

Eu amo o Either, mas ele não é bom o suficiente! Eu tenho trabalhando bastante para tornar o meu código ainda mais claro.

public interface IEmployeeRepository
{
    Try<Exception, Option<Employee>> GeyById(string id);
}

class EmployeeRepository : IEmployeeRepository
{
    public Try<Exception, Option<Employee>> GeyById(string id)
        => Try.Run(return new DbContext().Find(id));
}

O tipo Try, assim como Either, é uma struct que provê conversões implícitas e algumas operações muito básicas. Ela permite que diferentes tipos de retorno para falha e para sucesso.

A coisa mais interessante sobre esta abordagem é que os programadores que consomem EmployeeRepository são agora forçados a tratar exceções.

public IActionResult Get(string id) => _repository.GetById(id).Match<IActionResult>(
  failure: _ => DatabaseError(),
  success: e => e .Match<IActionResult>(
    some: employee => Ok(employee),
    none: () => NotFound()
  ));

Implementações de Either e Try estão disponíveis no meu github.

3 Comentários
  1. Rafael Oliveira

    Achei fantástica sua abordagem. Só não entendi como o programador pode ser forçado a tratar a exceção se ele ainda pode simplesmente chamar _repository.GetById(id) sem o Match()

    1. elemarjr

      Sem o match ele não chega no valor.

  2. Tiago

    Elemar, lembro de um poste seu aonde você encadeava chamadas com o Try, porém não estou conseguindo achar no seu blog, parece que ele foi resetado rs, você teria ele ai ?

Deixe uma resposta

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *