Etiqueta: abstracción

  • 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.