Criterios de claridad en Ruby
El código limpio no es “código corto”: es código fácil de leer, cambiar y probar. En Ruby, la legibilidad suele mejorar cuando el código se acerca a un estilo declarativo (decir qué queremos) y reduce el ruido (decir cómo en exceso). A continuación tienes criterios prácticos para evaluar y mejorar claridad.
Nombres expresivos (intención primero)
Un buen nombre reduce la necesidad de comentarios y evita malentendidos. En Ruby, aprovecha nombres de métodos que suenen a pregunta para booleanos y nombres de variables que describan el rol, no el tipo.
- Booleanos:
active?,expired?,valid? - Métodos con efecto: verbos claros:
calculate_total,apply_discount,normalize_email - Evita abreviaturas ambiguas:
cfg,tmp,arr(salvo convenciones muy conocidas)
# Menos claro
u = users.select { |x| x[:a] }
# Más claro
active_users = users.select { |user| user[:active] }Funciones pequeñas y con una sola responsabilidad
Un método pequeño facilita pruebas, reutilización y cambios. Señales de alerta: demasiados niveles de indentación, mezcla de validación + cálculo + formateo, o variables temporales que “transportan” estado entre bloques.
- Un método debería poder describirse en una frase.
- Si necesitas comentarios tipo “Paso 1/2/3”, probablemente son métodos por extraer.
- Prefiere devolver valores en lugar de mutar estructuras externas.
Evitar duplicación (DRY con criterio)
Duplicación no es solo copiar/pegar: también es repetir la misma idea con pequeñas variaciones. En Ruby, suele resolverse extrayendo métodos, usando enumerables o encapsulando reglas en objetos.
# Duplicación de regla de negocio
if user[:country] == "ES"
tax = subtotal * 0.21
else
tax = subtotal * 0.10
end
# Extraer regla
rate = tax_rate_for(user)
tax = subtotal * rateCoherencia en estructuras de control
La coherencia reduce la carga cognitiva. Decide un estilo y aplícalo de forma consistente:
- Escuche el audio con la pantalla apagada.
- Obtenga un certificado al finalizar.
- ¡Más de 5000 cursos para que explores!
Descargar la aplicación
- Preferir guard clauses (retornos tempranos) para evitar anidación profunda.
- Evitar
elsecuando un retorno temprano lo hace innecesario. - Usar
casecuando hay múltiples ramas por el mismo criterio.
# Anidación innecesaria
if user
if user[:active]
send_email(user)
end
end
# Guard clauses
return unless user
return unless user[:active]
send_email(user)Uso idiomático de enumerables
Ruby brilla cuando transformas colecciones con map, filtras con select/reject, agregas con reduce y consultas con any?/all?/none?. Esto suele reemplazar bucles manuales, variables acumuladoras y condicionales repetidos.
| Objetivo | Preferir | Evitar |
|---|---|---|
| Transformar | map | Construir arrays con << en bucles |
| Filtrar | select, reject | if dentro de bucles para decidir si agregar |
| Sumar/combinar | reduce, sum | Acumuladores manuales |
| Buscar | find | Recorrer todo y guardar “el último encontrado” |
# Imperativo
sum = 0
items.each do |item|
if item[:active]
sum += item[:price]
end
end
# Declarativo
sum = items.select { |i| i[:active] }.sum { |i| i[:price] }Refactorizaciones típicas (con ejemplos)
1) Reemplazar condicionales anidados por guard clauses
Cuando hay validaciones previas, los retornos tempranos hacen el “camino feliz” más visible.
# Antes
def process(order)
if order
if order[:items] && !order[:items].empty?
if order[:paid]
"OK"
else
"UNPAID"
end
else
"EMPTY"
end
else
"NO_ORDER"
end
end
# Después
def process(order)
return "NO_ORDER" unless order
return "EMPTY" if order[:items].nil? || order[:items].empty?
return "UNPAID" unless order[:paid]
"OK"
end2) Extraer métodos para nombrar decisiones
Extraer métodos no solo reduce tamaño: convierte lógica en vocabulario del dominio.
# Antes
if user[:active] && !user[:banned] && user[:email].to_s.include?("@")
notify(user)
end
# Después
def notifiable?(user)
user[:active] && !user[:banned] && user[:email].to_s.include?("@")
end
notify(user) if notifiable?(user)3) Reducir variables temporales
Las variables temporales encadenadas suelen indicar que puedes componer expresiones o extraer un método. Mantén variables solo si aportan significado (nombre) o evitan repetir un cálculo costoso.
# Antes
subtotal = items.sum { |i| i[:price] * i[:qty] }
discount = subtotal * discount_rate
final = subtotal - discount
# Después (si el significado es obvio)
final = items.sum { |i| i[:price] * i[:qty] } * (1 - discount_rate)Si el cálculo necesita nombre, extrae:
def subtotal_for(items)
items.sum { |i| i[:price] * i[:qty] }
end
final = subtotal_for(items) * (1 - discount_rate)4) Simplificar colecciones con map/select/reduce
Busca patrones: “recorrer y construir”, “recorrer y filtrar”, “recorrer y acumular”. Cambiarlos a enumerables reduce ruido y errores.
# Antes
emails = []
users.each do |u|
if u[:active]
emails << u[:email].to_s.strip.downcase
end
end
# Después
emails = users.select { |u| u[:active] }
.map { |u| u[:email].to_s.strip.downcase }Sesión guiada de refactorización: de script “desordenado” a código claro
En esta práctica vas a refactorizar un script que calcula un reporte de pedidos. El objetivo no es cambiar el comportamiento, sino mejorar legibilidad, reducir duplicación y usar enumerables de forma idiomática.
Script provisto (antes)
# Script desordenado: reporte de pedidos
# Entrada: orders (Array de Hashes)
# Cada order: { id:, customer:, status:, items: [ { name:, price:, qty: } ] }
def report(orders)
out = ""
total = 0
i = 0
while i < orders.length
o = orders[i]
if o != nil
if o[:status] == "paid"
s = 0
j = 0
while j < o[:items].length
it = o[:items][j]
if it != nil
s = s + (it[:price] * it[:qty])
end
j = j + 1
end
total = total + s
out = out + "Order #" + o[:id].to_s + " - " + o[:customer].to_s + " - " + s.to_s + "\n"
else
out = out + "Order #" + o[:id].to_s + " skipped\n"
end
end
i = i + 1
end
out = out + "TOTAL=" + total.to_s
out
endChecklist de mejoras (úsala como guía)
- Nombres: ¿
o,s,itexpresan intención? Renombrar aorder,subtotal,item. - Anidación: ¿hay
ifdentro deifdentro de bucles? Aplicar guard clauses o separar caminos. - Duplicación: ¿se repite lógica de formateo o cálculo? Extraer métodos.
- Enumerables: reemplazar
while+ índices poreach,map,sum,select. - Nil-checks: si el contrato de datos garantiza arrays/hashes válidos, elimina checks redundantes; si no, encapsula la tolerancia a
nilen un lugar. - Construcción de strings: evitar concatenación repetida; preferir
lines = []y luegolines.joinojoin("\n"). - Separación de responsabilidades: cálculo de subtotal, selección de pedidos y formateo en métodos distintos.
Paso a paso de refactorización
Paso 1: Cambiar índices por enumerables y mejorar nombres
def report(orders)
out = ""
total = 0
orders.each do |order|
next if order.nil?
if order[:status] == "paid"
subtotal = 0
order[:items].each do |item|
next if item.nil?
subtotal += item[:price] * item[:qty]
end
total += subtotal
out << "Order ##{order[:id]} - #{order[:customer]} - #{subtotal}\n"
else
out << "Order ##{order[:id]} skipped\n"
end
end
out << "TOTAL=#{total}"
endPaso 2: Extraer el cálculo de subtotal
Nombrar el cálculo reduce ruido en el reporte.
def order_subtotal(order)
items = order[:items] || []
items.compact.sum { |item| item[:price] * item[:qty] }
endAhora el reporte se enfoca en el flujo principal:
def report(orders)
out = ""
total = 0
orders.compact.each do |order|
if order[:status] == "paid"
subtotal = order_subtotal(order)
total += subtotal
out << "Order ##{order[:id]} - #{order[:customer]} - #{subtotal}\n"
else
out << "Order ##{order[:id]} skipped\n"
end
end
out << "TOTAL=#{total}"
endPaso 3: Reemplazar condicionales por guard clauses y separar “líneas”
Construir un array de líneas suele ser más claro que concatenar en cada iteración.
def paid?(order)
order[:status] == "paid"
end
def order_line(order, subtotal)
"Order ##{order[:id]} - #{order[:customer]} - #{subtotal}"
end
def skipped_line(order)
"Order ##{order[:id]} skipped"
end
def report(orders)
lines = []
total = 0
orders.compact.each do |order|
unless paid?(order)
lines << skipped_line(order)
next
end
subtotal = order_subtotal(order)
total += subtotal
lines << order_line(order, subtotal)
end
lines << "TOTAL=#{total}"
lines.join("\n")
endPaso 4: Simplificar aún más con map/select/sum (opcional)
Si quieres un estilo más declarativo, puedes construir líneas y total con enumerables. Úsalo si tu equipo lo encuentra más legible.
def report(orders)
safe_orders = orders.compact
lines = safe_orders.map do |order|
if paid?(order)
subtotal = order_subtotal(order)
order_line(order, subtotal)
else
skipped_line(order)
end
end
total = safe_orders.select { |o| paid?(o) }
.sum { |o| order_subtotal(o) }
(lines + ["TOTAL=#{total}"]).join("\n")
endComparación antes/después (qué mejoró y por qué)
| Aspecto | Antes | Después |
|---|---|---|
| Legibilidad | Variables crípticas (o, s, it) y bucles con índices | Nombres con intención y enumerables |
| Anidación | Múltiples if anidados dentro de while | Guard clauses y rutas claras |
| Responsabilidades | Cálculo + formateo + acumulación mezclados | Métodos extraídos: order_subtotal, order_line, paid? |
| Duplicación/ruido | Concatenación repetida y checks dispersos | Construcción de líneas y compactación centralizada |
| Estilo Ruby | Imperativo (índices, acumuladores manuales) | Idiomas Ruby: each, sum, map, select, compact |