Es común que en nuestras aplicaciones aparezcan construcciones del tipo desde-hasta o desde-a. Algunos ejemplos podrían ser un rango de fechas, el principio y fin de un viaje, o un intercambio de una divisa a otra.
Los métodos que ocupan este tipo de rangos, podrían verse así por ejemplo:
def build_trip(from, to, other_options)
do_something
do_something_else(from, to)
end
En un contexto donde este tipo de llamadas abundan, donde dos parámetros solo tienen sentido si van juntos y además suelen delegarse de un lado a otro siempre juntos, vale la pena considerar usar alguna abstracción genérica para simplificar la delegación de argumentos y agregar valor semántico al código.
Esta es mi forma de hacerlo, notar que uso las dependencias sorbet-runtime
y sorbet-struct-comparable
:
class AbstractValue < T::InexactStruct
include T::Struct::ActsAsComparable
end
class FromTo < AbstractValue
extend T::Generic
Elem = type_member
const :from, Elem
const :to, Elem
end
Esto se ocuparía de la siguiente forma:
stops = FromTo[String].new(from: 'santiago', to: 'lima')
build_trip(stops, other_options) # método con nuevo signature más simple
En la que destaca el hecho de que podemos generar instancias tipadas, y de que estas instancias son inmutables y comparables.
Si no estamos usando sorbet podemos hacer una versión no tipada con un comportamiento similar así:
class FromTo
def initialize(from, to)
@from = from
@to = to
end
attr_reader :from, :to
def ==(other)
from == other.from && to == other.to
end
def eql?(other)
from.eql?(other.from) && to.eql?(other.to)
end
end
Que luego se usaría:
FromTo.new('santiago', 'lima')
Todo esto nos permite refactorizar los métodos en cuestión, simplificando sus signatures:
def build_trip(stops, other_options)
do_something
do_something_else(stops)
end
Este es un ejemplo sencillo de como se puede hacer el código más legible agregando pequeñas abstracciones que representan patrones presente en él.