en cuanto aparece la palabra Option el if te lo comes sí o sí siempre, pero hay que asumirlo, al menos el manejo de errores es explícito y te ahorras los errores más comunes en estos casos. Lo veo mejor que lanzar una excepción
#31 el error no es una excepción, es un tipo más. Es más explícito el try que puso desu que un option porque el try tiene información del error y el option no
#32 pero cuando tienes un option eres más consciente del posible error que puedes tener, una exception puede ser cualquier cosa. Con el option añades complejidad, pero me parece mucho más fácil de leer y entender que una exception.
Tampoco sé lo que habréis visto de funcional, pero se usa muchísimo más option
#33 Peor que te está devolviendo un Try, no está hablando de excepciones (y yo devolvería una excepción directamente). Aún así, algo mucho mejor es hacer TDD:
class Name(name: String) -> Checkeo del nombre
class Age(age: Int) -> Checkeo de la edad
class Person(name: Name, age: Age)
Ya tienes una clase Person que no devuelve errores cuando se crea.
Es que un option es algo que puede ser o no ser. Si creo un usuario, semanticamente está mal devolver un Option
#34 y si checkeas la edad y no está bien, ¿qué pasa cuando instancias Person?
EDIT: volviendo al tema del smart constructor, no nos gustan los option, ok, ahora usemos either como ha dicho desu o me han dicho por privado
case class Name(value: String)
case class Age(value: Int)
case class Person(name: Name, age: Age)
object Person {
def create(name: String, age: Int): Either[String, Person] = {
if (name.isEmpty) {
Left("Error: Nombre inválido")
} else if (age < 0) {
Left("Error: Edad inválida")
} else {
Right(Person(Name(name), Age(age)))
}
}
}
val person = Person.create("Desu", -1)
person match {
case Right(p) => println(s"Ok)
case Left(err) => println(s"Ko")
}
qué hay de malo en esto? Pregunto porque quiero aprender bien, no por llevarle la contra a nadie
#35 What? Si voy a crear un Age con una edad inválida lanzo un error. Lo mismo con el nombre.
Al final trato Name y Age como datos primitivos fuera de lo que es mi dominio (aunque se tengan que construir en algún sitio, su construcción está mucho más controlada en los datos de entradada de la app y listo, fuera de ahí va a ser correcto).
Porque vamos ya esto viendo vuestro código por todos lados muy fluent api todo
Person.create(asdfas, 123)
.map(...)
.getOrElse(throw new Exception(asdfasdf))
O sin el throw y al final del endpoint o lo que vayais a hacer:
result match {
case Some(x) => x
case None => Error("ha habido un error")
}
EIDT: Para mi el principal problema de hacer eso es precisamente la posibilidad de crear cosas incorrectas en mitad de tu código. Si he llegado al punto en mi código de llamar a Person.create y ese create puede no crear una persona, es que hay algo que está mal.
#36MTX_Anubis:Si he llegado al punto en mi código de llamar a Person.create y ese create puede no crear una persona, es que hay algo que está mal.
Pero estás haciendo lo mismo, aunque trasladándolo un paso antes, podrías decir: si he llagado al punto de Llamar a Age.create y puedo no crear una edad, es que hay algo que está mal
#34 Yo en general estoy en contra de TDD para estas cosas. Porque si me entra data a parsear seguramente la parseare nada mas recibirla... Y quiero fallar lo antes posible. Y si se que la voy a usar en una Person pues prefiero parsearla directamente todo en el constructor que para eso lo escribo...
Estoy mas a favor de usar TDD si tengo que comunicar modulos internos por ejemplo... Y quiero dejar claro que ahora estoy usando un int32
por ejemplo para Age
, pero en el futuro puede ser un int64
, asi que le meto el nuevo type Age = int32
para que el consumer solo tenga que cambiar el codigo en 1 sitio.
Usar TDD en las capas de externas de parseo, entrada de un server http o comunicandote con una db por ejemplo. Es perder el tiempo. Es como devolver un Option que deciamos... Si ya sabes que tienes un error y lo vas a tener que devolver pues tira el error.
Usar TDD es mas para hacer una API mas resiliente a cambios.
#35vago_21:qué hay de malo en esto?
En que? Yo me he perdido ahora. A parte que en tu codigo no usas los new types de Name, Age XD
Primero deberiamos enteder si estas Parseando data en una capa externa. Input network externo o hablando con una DB o una API externa....
O si estas diseñando una libreria interna que usan otros componentes internos.
Que caso de uso no te queda del todo claro?
#38 sí, he copiado y pegado lo de Anubis.
Pero antes has dicho de usar un either antes que un option, lo he modificado para que no devuelva option y usando smart constructor ¿lo ves mejor así o se puede mejorar?
Edit: digamos que estoy parseando datos que me vienen de una api, y puede que algunos datos de person vengan no no, imagina que algunas veces hay age y en otras no, no quiero crashear el programa por eso y una forma limpia de manejar los errores me ha parecido un smart constructor
#39 Yo en JAVA (JVM) tiraria excepcion siempre. Esto en java en su dia se hizo una chapuza con las excepciones y no existia una manera unificada de devolver errores. Hay librerias que devuelven un Optional, otras un Either y otras una Checked Exception.
Mientras haya 1 sola libreria o framework que tire una excepcion como mecanismo de errores... Que seguro que la tienes. Porque Spring todo tira excepciones... Yo tiraria excepciones en todos lados.
Para que quieres devolver una string con el error? Mejor tira la excepcion y cacheala donde toque y ya tienes el error formateado.
Either o Try, pues prefiero el Try... Aunque como digo es un poco bobada en JVM hacer esto si despues tiraras la excepcion y alguien la recojera... Pero lo puedes usar a nivel interno para que tu codigo sea mas facil de leer y seguir y tener mejor control de los errores. No esta mal que sea mas comprensible.
Si estas parseando datos de algo externo, siempre parsea en el constructor, si el valor de age es nullable, entonces tu Person deberia considerar que age es Option y ya no es un error no? El error significa que tus datos estan en unestado incosisntente o no representable o no tiene ningun sentido... Y entonces va a devolver un error.
Si el estado del programa es valido, en este caso age es nullable, pues usa un Option... porque no es un error.
Te has inventado un caso de uso que no tiene que ver con lo que nos has mostrado al principio. Nosotros hablabamos de cuando Age y Name son obligatorios. Si no estan, es un error.
Lo de crashear el programa no lo entiendo, si es un server devolveras un status code y response de error, si es una db devolveras un error, si es un programa pues tiraras un log de error y descartaras el subproceso... En ningun caso hablamos de crashear en errores ni una excepcion significa que vamos a crashear.
#37 Es que el Age.create se llamará en el parseo de la entrada de la app (y siempre va a haber parseos en tu app)
Por eso su existencia, estás elevanto Age y Name a tipos primitivos en tu domino y los errores los identificas en el parseo y no van más allá
#38 El caso de usar Age y Name por ejemplo es evitar futuros errores y comprobaciones por doquier en tu código cada vez que quiera llamar a Person.create. Que ahora es sólo Person.create pero imaginate todo tu código de cientos de miles de lineas así.
Además como digo, tu error lo identificas justo cuando parseas y lo lanzas, no llega a tocar la capa de negocio.
#41 Si siempre que recibo un Age y un Name voy a crear una Person. Tener el codigo dentro de Person.create es lo mismo que tener un Age.create y un Name.create y luego llamar Person.create... Yo prefiero tenerlo en Person.create.
#41MTX_Anubis:Además como digo, tu error lo identificas justo cuando parseas y lo lanzas, no llega a tocar la capa de negocio.
Yo igual.
#42desu:Si siempre que recibo un Age y un Name voy a crear una Person
Si claro, en esta suposición sí
Buena liada del sbt community repository
https://www.scala-lang.org/blog/2023/04/20/sbt-plugins-community-repository.html