Exception é um vilão?
Exception é um vilão? Tudo bem, não vi ninguém falar que era, mas vi muitos influenciadores, dizendo para parar de utilizar Exceptions, em detrimento de monads (acho válido, mas não em todos os cenários).
Meu intuito aqui não é explicar a melhor forma de lidar com exceptions.
Também não quero tentar diferenciar erros de exceções.
Quero apenas percorrer pelo assunto sobre deexceptions
para identificarmos melhor método!
Ué, tudo em tech não édepende
? Polêmico não é mesmo!?
Esse aqui é só um depende oculto 😎, já vai conseguir identificar.
Esse texto faz parte de um post resumido no meu linkedin, se quiser se conectar Ou& curtir lá, fique avontade: André Luz
Link do post: O Exception nosso de cada dia
Se não quiser ler tudo do início, mas quiser uma respota direta e depois se quiser voltar para entender como cheguei a essa resposta, então pode ir direto pra cá ⇒ Qual melhor método
O que é uma Exception?
Primeiramente é bom darmos uma definição de exceptions. Até para quem não conhece ou ta começando agora.
Uma definição "formal" para exceptions seria:
Exceptions: são condiçōes anormais, eventos fora do fluxo padrão/(fluxo feliz 😊).
Certo, mas e na prática o que é?
É uma estrutura de dados para lidar com erros no fluxo do seu código.
Eu amo exemplos, então vamos ver um exemplo abaixo
Ex:
var on = "Exception".Substring(7, 3);
Console.WriteLine(on);
Unhandled exception. System.ArgumentOutOfRangeException: Index and length must refer to a location within the string. (Parameter 'length')
at System.String.ThrowSubstringArgumentOutOfRange(Int32 startIndex, Int32 length)
at System.String.Substring(Int32 startIndex, Int32 length)
Vê que ele estoura o app instantaneamente, isso porque você não lidou com a exceção.
O que fazemos para lidar com exceção? O básico né...
try
..
catch..
finaly
Isso é suficiente. Vou por um código bem besta abaixo, apenas para quem nunca lidou com isso antes...
try{
var on = "Exception".Substring(7, 3);
Console.WriteLine(on);
} catch (Exception ex) {
Console.WriteLine($"Nesse caso não é exceção é burrice mesmo! | \n {ex}");
}
O que é um monad / optional / nullable / error
Vamos com calma. Coloquei vários termos aqui, e não, eu não estou resumindo todo como a mesma coisa.
Embora que de modo usual todos tentam resolver a mesma coisa, cada um tem sua particularidade.
Vamos para o mais simples: monads.
Monads
É básicamente um objeto (qualquer que seja, até tipo primitivo) encapsulado em um objeto.
Vamos construir um monad agora em javascript, para exemplo:
class Monad {
constructor(value) {
this.value = value;
}
map(fn) {
return this.value !== null ? new Monad(fn(this.value)) : new Monad(null);
}
}
const monadValue = new Monad(Math.random() > 0.5 ? 'Hello' : null);
const result = monadValue.map((val) => val.toUpperCase()).value || 'Valor ausente';
console.log(result);
Levar em consideração que é um cenário irreal.
No caso da imagem acima, o resultado foi menor ou igual a 0.5.
Certo, o que isso quer dizer? Não peguei a ideia até agora 🤔.
Ao invés de retornarmos a string Hello ou null (ou qualquer outra coisa em outro cenário), retornamos um wrapper do dado real que queremos, e dessa forma conseguimos lidar com possíveis falhas.
E o que ganhamos com isso? Não precisamo lidar com exceção, porque ou é o meu dado ou não é.
Nullable/Optional
Nullable é basicamente a mesma coisa do que foi explicado acima. Tem um objeto que encapsula o seu dado real.
Eu vou utilizar o C#. A explicação formal para ele isso é:
Enquanto Nullable<T> é uma maneira de permitir que tipos de valor (int, bool, etc..) sejam null, Optional<T> é uma abordagem mais recente e expressiva para lidar com valores que podem estar presentes ou ausentes, oferecendo uma forma melhor para lidar com esses casos.
Por isso vou utilizar o Nullable.
Optional tem alguns métodos para lidarmos de modo mais direto com problemas (coisas que raramente vamos utilizar no dia-a-dia).
using System;
class Program
{
static void Main(string[] args)
{
int? nullableValue = 42;
Console.WriteLine($"Nullable com valor: {nullableValue.HasValue}");
if (nullableValue.HasValue)
Console.WriteLine($"Valor do Nullable: {nullableValue.Value}");
int? nullableEmpty = null;
Console.WriteLine($"Nullable sem valor: {nullableEmpty.HasValue}");
int defaultValue = 0;
int valueOrDefault = nullableEmpty ?? defaultValue;
Console.WriteLine($"Valor padrão: {valueOrDefault}");
}
}
Nullable com valor: True
Valor do Nullable: 42
Nullable sem valor: False
Valor padrao: 0
Veja que antes de acessar o valor, podemos verificar se existe valor com a propriedade .HasValue
, isso nos dá a flexibilidade com NullPointerException
.
E mais uma vez não foi preciso lidar com exceção.
Error
No C# em especifico não é algo suportado nativamente, temos essa ótima biblioteca para lidar com isso: ErrorOr
Mas em Go/Rust temos isso suportado e estimulado à utilização.
Em Go seria um error
Em Rust seria um Result<T, E>
Vou dá um exemplo em Go, porque conheço praticamente nada de Rust, então tenho pouca propriedade.
package main
import (
"fmt"
"errors"
)
func divide(a, b float64) (float64, error) {
if b == 0 {
return 0, errors.New("divisão por zero não é permitida")
}
return a / b, nil
}
func main() {
result, err := divide(10, 0)
if err != nil {
fmt.Println("Erro:", err)
return
}
fmt.Println("Resultado:", result)
}
Veja que go retorna dois valores para desconstrução, um float64
e um error
.
Veja que posteriormente podemos verificar se o retorno de error
é diferente de nil
(nulo), caso não seja igual a nulo, significa que houve um erro.
Então vamos tratar disso, e não precisou lidar com exceções.
No caso de Go e Rust não possuem exceções.
Mas em C#, utilizando a biblioteca ErrorOr, seria algo assim:
public ErrorOr<float> Divide(int a, int b)
{
if (b == 0)
{
return Error.Unexpected(description: "Cannot divide by zero");
}
return a / b;
}
var result = Divide(4, 2);
if (result.IsError)
{
Console.WriteLine(result.FirstError.Description);
return;
}
Console.WriteLine(result.Value * 2); // 4
Veja que é fundamentalmente a mesma coisa.
Qual melhor método
Ceto, e qual a respota para isso? Depende 🤣🤣
Deixando a brincadeira de lado e sendo mais rígido, é o seguite:
Você pode utilizar o que você se sentir melhor ❤️, fato!
E quando se está em uma equipe, ou fazendo algo com uma comunidade. O melhor sempre é trabalhar em conjunto com a comunidade, e o que isso quer dizer?
As especificações do C#, Java, Js, Ts, Go, Rust, V, Nim, Zig etc.. é mais importante!
Estamos sempre trabalhando com padrões. Como isso é verdade, então as linguagens também fazem isso, não é pra dizer que é a diferentona, mas para manter consistência em toda base de código que encontrar pela internet.
Se você estava travalhando com C# em 2018, utilize Exceptions para lidar até mesmo com o mais básico, ex:
Saber se o retorno de GetFirstUser()
Retorna um User/uma referência nula.
Mas de 2019 para cá, com C#, você já tem Nullable<T>, então utilize disso.
Você tem suporte completo do C#, eles não só lançara uma classe que encapsula o resultado, e dane-se...
Foi dado operadores novos para lidar com isso de forma mais fácil e intuitiva.
Eu sou time C#, stack que mais utilizo, ela que compra as fraldas 🚼 do meu filho.
Embora o hype enorme com Rust (Eu sei e concordo que é uma ótima linguagem), eu prefiro Go para lidar com erros, rust acaba tornando complicado algo bonito e simples que Go fez para lidar com erros.
E seria altamente esquisito e fora de contexto criar uma biblioteca para ter exceções em Go ou Rust.
E por quê? Porque a linguagem já nasceu lidando com erros, quase como um paradigma de programação.
Bem pessoal, o que gostaria de explicitar o que expliquei acima é. Lide com erros na sua linguagem que está utilizando de modo apropriado ao que já é suportado, para não precisar criar wrappers de exceções.