Programando en Ruby

La Guía de los "Programadores Pragmáticos"

Cuando los problemas atacan



Es triste decirlo pero, es posible hacer programas con bugs usando Ruby. Perdona por esto.

¡Pero no hay problema! Ruby dispone de varias características que te ayudarán a depurar tus programas. Miraremos estas funcionalidades, y luego mostraremos algunos errores típicos que se pueden cometer al programar en Ruby, y como arreglarlos.

Depurador de Ruby

Ruby viene con un depurador, que está integrado convenientemente dentro del sistema base. Puedes ejecutarlo invocando al intérprete con la opción -r debug, además de otras opciones y el nombre de tu script:

ruby -r debug [
            opciones
            ] [
            nombreprograma
            ] [
            argumentos
            ]

El depurador dispone de las características típicas que se podrían esperar de él, incluyendo la capacidad para establecer puntos de interrupción, ir paso a paso a través de las llamadas a los métodos, y mostrar los marcos de pila y las variables.

También puede listar los métodos de instancia definidos para un objeto o clase particular, y también permite ver y controlar hilos separados dentro de Ruby. La tabla 12.1 en la página 131 lista todos los comandos disponibles en el depurador.

Si dispones de soporte de readline en Ruby, puedes usar las teclas cursor para moverte adelate y atrás en el historial de comandos y usar comandos de la línea de edición para corregir lo introducido anteriormente.

Para que te hagas una idea del aspecto que tiene el depurador de Ruby, aquí está una sesión de muestra.

% 
              ruby -rdebug t.rb
            
Debug.rb
Emacs support available.
t.rb:1:def fact(n)
(rdb:1) 
              list 1-9
            
[1, 10] in t.rb
=> 1  def fact(n)
   2    if n <= 0
   3      1
   4    else
   5      n * fact(n-1)
   6    end
   7  end
   8
   9  p fact(5)
(rdb:1) 
              b 2
            
Set breakpoint 1 at t.rb:2
(rdb:1) 
              c
            
breakpoint 1, fact at t.rb:2
t.rb:2:  if n <= 0
(rdb:1) 
              disp n
            
  1: n = 5
(rdb:1) 
              del 1
            
(rdb:1) 
              watch n==1
            
Set watchpoint 2
(rdb:1) 
              c
            
watchpoint 2, fact at t.rb:fact
t.rb:1:def fact(n)
1: n = 1
(rdb:1) 
              where
            
--> #1  t.rb:1:in `fact'
    #2  t.rb:5:in `fact'
    #3  t.rb:5:in `fact'
    #4  t.rb:5:in `fact'
    #5  t.rb:5:in `fact'
    #6  t.rb:9
(rdb:1) 
              del 2
            
(rdb:1) 
              c
            
120

Interactive Ruby

Si quieres jugar con Ruby, existe una aplicación llamada Interactive Ruby---irb, para abreviar. irb es básicamente una ``shell'' Ruby similar conceptualmente a una shell de un sistema operativo (complete with job control). Proporciona un entorno en el que puedes ``jugar'' con el lenguaje en tiempo real. irb se ejecuta en la línea de comandos:

irb [
            opciones-irb
            ] [
            script_ruby
            ] [
            opciones
            ]

irb irá mostrando el valor de cada expresión a medida que las vas introduciendo. Por ejemplo:

% irb
irb(main):001:0> 
              a = 1 +
            
irb(main):002:0* 
              2 * 3 /
            
irb(main):003:0* 
              4 % 5
            
2
irb(main):004:0> 
              2+2
            
4
irb(main):005:0> 
              def test
            
irb(main):006:1> 
              puts "Hello, world!"
            
irb(main):007:1> 
              end
            
nil
irb(main):008:0> 
              test
            
Hello, world!
nil
irb(main):009:0> 

irb también te permite crear subsesiones, cada una de las cuales puede tener su propio contexto. Por ejemplo, puedes crear una subsesión con el mismo contexto (top-level) que la sesión original, o crear una subsesión en el contexto de una clase o instancia particular. La sesión de muestra que aparece en la Figura 12.1 en la página 126 es un poco más larga, pero te indica cómo puedes crear subsesiones y cambiar entre ellas.

Figura no disponible..

Para una descripción total de todos los comandos que irb soporta, mira en la referencia que empieza en la página 517.

Del mismo modo que con el depurador, si tu versión de Ruby está compilada con soporte para GNU Readline , puedes usar las teclas cursor (como en Emacs) o accesos rápidos de teclado al estilo de vi para editar líneas individuales o volver a una instrucción anterior y reejecutarla o editarla---igual que en una shell.

irb es una gran herramienta de aprendizaje: es muy útil por ejemplo, si quieres probar rápidamente una idea y mirar si funciona.

Soporte de editor

Ruby está diseñado para leer el código de un programa en un solo paso; ésto implica que puedes introducir un programa completo por la entrada estándar de Ruby y hacer que se ejecute.

Podemos aprovecharnos de esta característica para ejecutar código Ruby desde un editor. En Emacs, por ejemplo, puedes seleccionar una región de texto Ruby y usar el comando Meta-| para ejecutarlo. El intérprete de Ruby usará la región seleccionada como entrada estándar y la salida irá a un buffer llamado ``*Shell Command Output*.'' Ésta funcionalidad nos ha sido de mucha ayuda durante la redacción de este libro---simplemente con seleccionar unas cuantas líneas en el medio de un párrafo, ya lo podíamos probar.

También se puede hacer algo parecido en el editor vi usando ``:!ruby'', que reemplaza el texto del programa con su salida, o ``:w[visible space]!ruby'', que muestra la salida sin afectar al código. Existen más editores que tienen características parecidas.

Y ya que estamos hablando del tema, quizá sea un buen momento para mencionar que existe un modo Ruby en Emacs, incluído en la distribución como misc/ruby-mode.el. Existen también varios módulos de resaltado de sintaxis para vim (una versión mejorada del editor vi), jed, y otros editores diponibles en internet. Mira en la FAQ de Ruby para conocer sus localizaciones y disponibilidad.

¡Pero ésto no funciona!

Ahora que ya has leído suficiente de éste libro, supongamos que empiezas a escribir tu propio programa Ruby, pero no funciona. Aquí te mostramos una lista de errores típicos y algunos consejos.

Existe una técnica importante que hace que programar en Ruby sea a la vez más fácil y divertido. Desarrollar tus aplicaciones incrementalmente. Escribe unas pocas líneas de código, y ejecútalo. Programa un poco más, y ejecútalo de nuevo. Uno de los principales beneficios de un lenguage sin tipos, es que no es necesario completar las cosas antes de que se puedan usar.

¡Pero es demasiado lento!

Ruby es un lenguaje interpretado de alto nivel, y como tal, su rendimiento no es tan alto como en lenguajes de más bajo nivel como C. En esta sección, listaremos algunos conceptos básicos que puedes aplicar para mejorar el rendimiento; también puedes echarle un vistazo al índice Rendimiento para ver otras puntualizaciones.

Crear variables locales fuera de bloques

Intenta definir las variables usadas dentro de un bloque antes de que el bloque se ejecute. Cuando se itera sobre un conjunto grande de elementos, puedes mejorar la velocidad declarando antes calquier variable del iterador. En el primer ejemplo a continuación, Ruby crea variables x e y nuevas en cada iteración, pero en el segundo no lo hace. Usaremos el paquete benchmark del Ruby Application Archive para comparar los bucles:

require "benchmark"
include Benchmark
n = 1000000
bm(12) do |test|
  test.report("normal:")    do
    n.times do |x|
      y = x + 1
    end
  end
  test.report("predefine:") do
    x = y = 0
    n.times do |x|
      y = x + 1
    end
  end
end
produces:
                  user     system      total        real
normal:       2.450000   0.020000   2.470000 (  2.468109)
predefine:    2.140000   0.020000   2.160000 (  2.155307)

Usa el profiler

Ruby viene con un profiler de código (la documentación empieza en la página 454). Ésto en si mismo no es muy sorprendente, pero el hecho de que esté escrito en tan solo 50 líneas de Ruby, demuestra lo impresionante que es este lenguaje.

Puedes ejecutar el profiler en tu código usando la opción de la línea de comandos -r  profile, o desde el propio código con require "profile". Por ejemplo:

require "profile"
class Peter
  def initialize(amt)
    @value = amt
  end

  def rob(amt)     @value -= amt     amt   end end

class Paul   def initialize     @value = 0   end

  def pay(amt)     @value += amt     amt   end end

peter = Peter.new(1000) paul = Paul.new 1000.times do   paul.pay(peter.rob(10)) end

Si ejecutas ésto, obtendrás algo como lo que viene a continuación.

 time   seconds   seconds    calls  ms/call  ms/call  name
 32.14     0.27      0.27        1   270.00   840.00  Fixnum#times
 30.95     0.53      0.26     1000     0.26     0.27  Paul#pay
 29.76     0.78      0.25     1000     0.25     0.30  Peter#rob
  5.95     0.83      0.05     1000     0.05     0.05  Fixnum#-
  1.19     0.84      0.01     1000     0.01     0.01  Fixnum#+
  0.00     0.84      0.00        1     0.00     0.00  Paul#initialize
  0.00     0.84      0.00        2     0.00     0.00  Class#inherited
  0.00     0.84      0.00        4     0.00     0.00  Module#method_added
  0.00     0.84      0.00        1     0.00     0.00  Peter#initialize
  0.00     0.84      0.00        1     0.00   840.00  #toplevel
  0.00     0.84      0.00        2     0.00     0.00  Class#new
Con el profiler, puedes identificar y arreglar rápidamente cuellos de botella. Sin embargo, procura comprobar siempre el código sin el profiler---algunas veces, la ralentización que introduce puede enmascarar otros problemas.

Ruby es un lenguaje increíblemente expresivo y transparente, pero ésto no evita que el programador aplique el sentido común: crear objetos innecesarios, realizar trabajo innecesario, y hacer código ineficiente en general es un gasto en cualquier lenguaje.
Comandos del depurador
b[reak] [archivo:]linea Establece un punto de interrupción en la línea indicada de archivo (por defecto es el archivo actual).
b[reak] [archivo:]nombre Establece un punto de interrupción en el metodo de archivo.
b[reak] Muestra los puntos de interrupción y los puntos de vista.
wat[ch] expr Corta cuando la expresión se hace cierta.
del[ete] [nnn] Borra el punto de interrupción nnn (por defecto borra todos).
disp[lay] expr Muestra el valor de nnn cada vez que el depurador toma el control.
disp[lay] Lista lo que se muestra actualmente.
undisp[lay] [nnn] Borra algo de lo que se muestra actualmente (por defecto borra todo).
c[ont] Continua la ejecución.
s[tep] nnn=1 Ejecuta las nnn líneas siguientes, parando en los métodos.
n[ext] nnn=1 Ejecuta las nnn líneas siguientes, stepping over methods.
fi[nish] Finaliza la ejecución de la función actual.
q[uit] Sale del depurador.
w[here] Muestra el marco de pila actual.
f[rame] Sinónimo de where.
l[ist] [comienzo--fin] Lista las líneas del código desde comienzo hasta fin.
up nnn=1 Sube nnn niveles en el marco de pila.
down nnn=1 Baja nnn niveles en el marco de pila.
v[ar] g[lobal] Muestra las variables globales.
v[ar] l[ocal] Muestra las variables locales.
v[ar] i[stance] obj Muestra las variables de la instancia obj.
v[ar] c[onst] Name Muestra las constantes de la clase o módulo de nombre name.
m[ethod] i[nstance] obj Muestra los métodos de la instancia obj.
m[ethod] Name Muestra los métodos de instancia de la clase o módulo de nombre name.
th[read] l[ist] Lista todos los hilos.
th[read] [c[ur[rent]]] Muestra el estado del hilo actual.
th[read] [c[ur[rent]]] nnn Hace que el hilo nnn sea el actual y lo para.
th[read] stop nnn Hace que el hilo nnn sea el actual y lo para.
th[read] resume nnn Continúa el hilo nnn.
[p] expr Evalúa expr en el contexto actual. expr puede incluír asignaciones a variables y llamadas a métodos.
vacio Un comando vacío repite el último comando.


Extraído del libro "Programming Ruby - The Pragmatic Programmer's Guide"
Copyright © 2000 Addison Wesley Longman, Inc. liberado bajo los términos de la Open Publication License V1.0.
La referencia está disponible en: download.