Etiqueta: modularidad

  • Comparando el estilo “clásico orientado a clases” con el estilo Ruby-idiomático, funcional y modular

    Vamos a aterrizar la idea de Dave Thomas con código Ruby real, comparando el estilo “clásico orientado a clases” con el estilo Ruby-idiomático, funcional y modular, y viendo por qué el segundo suele envejecer mejor.

    No es una religión. Es ingeniería pragmática.


    1. El punto de partida clásico (el reflejo Java)

    Imagina un caso típico: procesar un pedido.

    Enfoque habitual con clases

    class Order
      attr_reader :items, :customer
    
      def initialize(items:, customer:)
        @items = items
        @customer = customer
      end
    
      def total_price
        items.sum(&:price)
      end
    
      def valid?
        items.any? && customer.active?
      end
    end
    
    class OrderProcessor
      def initialize(order)
        @order = order
      end
    
      def process
        raise "Invalid order" unless @order.valid?
    
        charge_customer
        send_confirmation
      end
    
      private
    
      def charge_customer
        PaymentGateway.charge(@order.customer, @order.total_price)
      end
    
      def send_confirmation
        Mailer.order_confirmation(@order)
      end
    end
    

    Esto es correcto. También es más estructura de la necesaria.

    Problemas sutiles:

    • Las clases no modelan cosas del mundo real, sino pasos de un flujo
    • La lógica está dispersa
    • Probar OrderProcessor implica instanciar Order
    • La clase existe solo para agrupar métodos

    2. La propuesta de Dave Thomas: empieza por acciones

    Ruby no te obliga a empezar pensando en “objetos”.
    Puedes empezar pensando en verbos.

    Enfoque funcional y plano

    def total_price(items)
      items.sum(&:price)
    end
    
    def valid_order?(items, customer)
      items.any? && customer.active?
    end
    
    def process_order(items:, customer:)
      raise "Invalid order" unless valid_order?(items, customer)
    
      PaymentGateway.charge(customer, total_price(items))
      Mailer.order_confirmation(items, customer)
    end
    

    Observa algo importante:

    • No hay estado implícito
    • Cada función hace una cosa
    • Las dependencias son explícitas
    • Es trivial testear cada función

    Esto ya es Ruby de primera clase, no un atajo.


    3. “Pero esto queda desordenado”: módulos al rescate

    Aquí es donde mucha gente se pone nerviosa.
    Dave Thomas dice: no saltes a clases, usa módulos.

    module Orders
      module Pricing
        def self.total(items)
          items.sum(&:price)
        end
      end
    
      module Validation
        def self.valid?(items, customer)
          items.any? && customer.active?
        end
      end
    
      module Processing
        def self.process(items:, customer:)
          raise "Invalid order" unless Validation.valid?(items, customer)
    
          PaymentGateway.charge(customer, Pricing.total(items))
          Mailer.order_confirmation(items, customer)
        end
      end
    end
    

    Esto aporta:

    • Namespacing claro
    • Ningún estado oculto
    • Ninguna jerarquía artificial
    • Código legible como un mapa mental del dominio

    4. ¿Cuándo sí aparece una clase?

    Dave Thomas no es anti-clases.
    Las clases aparecen cuando hay identidad y estado duradero.

    Ejemplo: un Money, un User, un Session.

    class Money
      attr_reader :amount, :currency
    
      def initialize(amount, currency)
        @amount = amount
        @currency = currency
      end
    
      def +(other)
        raise "Currency mismatch" unless currency == other.currency
        Money.new(amount + other.amount, currency)
      end
    end
    

    Aquí la clase tiene sentido porque:

    • Tiene identidad
    • Encapsula invariantes
    • Protege reglas internas

    Lo que Dave Thomas critica es crear clases solo para colgar métodos.


    5. Un ejemplo muy Rails-real (Service Objects)

    El patrón clásico Rails:

    class CreateUser
      def initialize(params)
        @params = params
      end
    
      def call
        user = User.new(@params)
        user.save!
        Mailer.welcome(user)
        user
      end
    end
    

    La versión “Ruby puro”:

    module Users
      def self.create(params)
        user = User.create!(params)
        Mailer.welcome(user)
        user
      end
    end
    

    Pregunta incómoda:
    👉 ¿qué aporta realmente la clase CreateUser?

    Respuesta honesta: nada, salvo ceremonia.


    6. Beneficios reales (no filosóficos)

    Después de años, este estilo suele ganar porque:

    • El código crece horizontalmente, no en jerarquías
    • Refactorizar es más fácil
    • Las dependencias están a la vista
    • Los tests no requieren dobles complejos
    • El dominio se expresa como lenguaje, no como UML

    Esto es muy Ruby y muy Pragmatic Programmer.


    7. La idea profunda del vídeo (la que no sale en el código)

    La charla no va de clases.
    Va de esto:

    No diseñes por anticipación la forma final del sistema.
    Deja que la estructura emerja del uso real.

    Las clases son una decisión tardía, no el punto de partida.

  • Resumen charla Dave Thomas


    Contexto y quién habla
    Dave Thomas es una figura legendaria en la comunidad Ruby: coautor de The Pragmatic Programmer, uno de los firmantes iniciales del Manifiesto Ágil, y autor de varios libros influyentes sobre Ruby y desarrollo de software. Su experiencia de décadas hace que lo que propone esté menos en plan “dogma” y más como una invitación a replantear nuestras suposiciones. (RubyEvents.org)


    La tesis central: recalibrar cómo estructuramos código en Ruby

    Thomas parte de una observación crítica: “Estamos escribiendo nuestro código Ruby de forma equivocada”. Esa frase es más que provocadora: es una invitación a desafiar un hábito que se ha vuelto casi automático. (sfruby.com)

    Su propuesta principal es:

    Dejar de usar clases como la unidad fundamental de diseño en Ruby cuando no es necesario.
    Esto no significa abolir clases—sino reconsiderar su papel —especialmente cuando alternativas más simples pueden hacer el código más claro, mantenible y flexibles a cambios futuros. (sfruby.com)


    ¿Por qué parar con clases?

    Ruby es un lenguaje extremadamente expresivo y flexible: las clases son solo una de muchas herramientas para estructurar código. Dave Thomas argumenta que:

    • Hemos desarrollado una “dependencia mental” en clases por tradición más que por necesidad real.
    • Diseñar todo alrededor de clases a menudo nos lleva a patrones complejos (“design patterns”) que en muchos casos son artefactos culturales de lenguajes más rígidos (como Java o C++), no de Ruby. (sfruby.com)

    En otras palabras: las clases no son la unidad más natural de pensamiento en Ruby.


    Un enfoque alternativo: estructura desde el crecimiento real

    Cuando diseñamos software tradicionalmente:

    1. Pensamos en la estructura primero.
    2. Escribimos clases y jerarquías.
    3. Luego codificamos.

    Thomas propone algo más parecido a trabajar desde exploración y crecimiento orgánico:

    1. Comienza con pequeñas funciones, módulos y bloques que reflejen exactamente lo que necesitas ahora.
    2. Permite que la estructura emerja conforme el problema y su complejidad crecen.
    3. Solo introduce clases o abstracciones más grandes si se vuelve claro que aportan valor. (sfruby.com)

    Esto es similar al enfoque evolutivo propio del desarrollo ágil, aplicado a la forma de escribir código mismo.


    Ventajas de este enfoque

    Dave Thomas señala beneficios prácticos:

    Simplicidad
    Menos artefactos ceremoniales (clases complejas, jerarquías rígidas) significan menos sobrecarga mental, menos convenciones que memorizar, y código que “dice lo que hace”. (sfruby.com)

    Mantenibilidad real
    Cuando la lógica está en funciones o módulos centrados en acciones claras, es más fácil modificar y razonar sobre ellos. Esto encaja con la filosofía de mantener el código flexible y adaptativo (un principio clave del desarrollo ágil). (sfruby.com)

    Menos dependencias innecesarias
    Menos clases puede significar menos dependencias entre partes del sistema, lo que hace más fácil reusar, testear y reemplazar componentes. (sfruby.com)


    ¿Qué pasa con patrones clásicos de diseño y metodologías?

    Otra parte de la charla—y probablemente la más filosófica—es que muchos de los patrones de diseño que aprendemos y aplicamos vienen del mundo de lenguajes más estáticos. En Ruby, muchas veces:

    • Objetos no son lo que pensamos.
    • Clases no siempre son la forma más natural de expresar una abstracción.
    • DSLs (Domain Specific Languages), bloques y módulos pueden hacer el trabajo de formas más expresivas. (sfruby.com)

    Esto no implica abandonar conceptos, sino reapropiarlos de forma más idiomática para Ruby.


    Ejemplos implícitos en la charla

    Aunque la charla en sí usa ejemplos concretos (código en vivo), el patrón general que Thomas muestra es:

    • Escribir funciones pequeñas y enfocadas.
    • Encapsular comportamiento relevante en módulos en vez de clases generales.
    • Evitar jerarquías profundas y rígidas que enmascaran la intención real del código. (sfruby.com)

    Este estilo se alinea con prácticas contemporáneas que favorecen la composición y la inmutabilidad funcional cuando tiene sentido.


    Un empujón hacia pragmatismo

    Este mensaje no tiene truco: no dice “deja de usar clases por decreto”, sino:

    Piensa críticamente sobre cuando realmente aportan valor y cuando simplemente seguimos patrones por costumbre.

    Ruby es un lenguaje expresivo; abrazar ese poder puede permitir código más simple y sostenible con menos artefactos sintácticos. (sfruby.com)