Los modelos a seguir son importantes.
-- Officer Alex J. Murphy / RoboCop
Una cosa siempre me ha molestado como desarrollador Ruby - los desarrolladores Python tienen una gran referencia de estilo de programación (PEP-8) y nosotros nunca tuvimos una guía oficial, documentando estilos de codificación y mejores prácticas de Ruby. Y yo creo que los estilos importan. También creo que una gran comunidad de hackers, tales como tiene Ruby, debería ser muy capaz de producir este codiciado documento.
Esta guía comenzó su vida como nuestra guía interna de codificación de Ruby para nuestra compañía (escrito por su servidor). En algún momento decidí que el trabajo que estaba haciendo podría ser interesante para los miembros de la comunidad Ruby en general y que el mundo no tenía la necesidad de otra guía interna de una empresa. Pero sin duda el mundo podría beneficiarse de un conjunto de prácticas, lenguajes y estilos para la programación de Ruby dirigido por la comunidad y sancionado por la comunidad.
Desde el inicio de la guía he recibido un montón de comentarios de los miembros de la excepcional comunidad Ruby alrededor de todo el mundo. ¡Gracias por todas las sugerencias y el apoyo! Juntos podemos hacer un recurso beneficioso para todos y cada uno de los desarrolladores Ruby.
Por cierto, si estás interesado en Rails es posible que desees ver la Guía de Estilos de Ruby on Rails 3 complementaria.
Esta guía de estilo de Ruby recomienda las mejores prácticas a fin de que los programadores Ruby del mundo real pueden escribir código que pueda ser mantenido por otros programadores Ruby del mundo real. Una guía de estilo que refleje los usos del mundo real es utilizada, y una guía de estilo que se aferra a un ideal que ha sido rechazado por las personas supone que lo mejor es no utilizarla para nada – no importa lo bueno que sea.
La guía está dividida en varias secciones de de reglas relacionadas. Intenté agregar la racionalización de las normas (si está omitido asumí que es bastante obvio).
No inventé todas las reglas desde la nada misma - ellas se basan sobre todo en mi extensa carrera como ingeniero de software profesional, junto con los comentarios y sugerencias de los miembros de la comunidad Ruby y diversos recursos conocidos de programación de Ruby, como "Programming Ruby 1.9" y "The Ruby Programming Language".
La guía todavía es un trabajo en progreso - a algunas reglas le faltan ejemplos, y algunas reglas no tienen ejemplos que ilustren con la suficiente claridad. En su debido tiempo se abordarán estos temas - sólo ténganlos en cuenta por ahora.
Podés generar un PDF o una versión HTML de esta guía usando Transmuter.
RuboCop es un validador de código, basado en esta guía de estilo.
Traducciones de esta guía están disponibles en los siguientes idiomas:
- Inglés (versión original)
- Chino Simplificado
- Chino Tradicional
- Francés
- Estructura del Código Fuente
- Sintaxis
- Nombres
- Comentarios
- Clases
- Excepciones
- Colecciones
- Strings
- Expresiones Regulares
- Porcentajes Literales
- Metaprogramación
- Varios
- Herramientas
Casi todo el mundo está convencido de que todos los estilos excepto los propios son feos e ilegibles. Deja de lado el "excepto los propios", y probablemente tengan razón...
-- Jerry Coffin (sobre indentación)
-
Usá
UTF-8
como la codificación del archivo fuente. -
Usá dos espacios por cada nivel de identación. No tabs.
# mal - cuatro espacios def some_method do_something end # bien def some_method do_something end
-
Usá finales de línea estilo Unix. (*por defecto los usuarios BSD/Solaris/Linux/OSX están protegidos, los usuarios de Windows tienen que tener mucho cuidado.)
- Si estás usando Git es posible que quieras agregar la siguiente configuración para proteger tu proyecto de los finales de línea de Windows para que no aparezcan solos:
$ git config --global core.autocrlf true
-
No uses
;
para separar declaraciones y expresiones. Por convención - usá una expresión por línea.# mal puts 'foobar'; # superfluous semicolon puts 'foo'; puts 'bar' # dos expresiones en la misma línea # bien puts 'foobar' puts 'foo' puts 'bar' puts 'foo', 'bar' # esto aplica a puts en particular
-
Preferí un formato de única línea por cada definición de clase sin cuerpo.
# mal class FooError < StandardError end # casi bien class FooError < StandardError; end # bien FooError = Class.new(StandardError)
-
Evitá métodos de una sola línea. Aunque son algo popular, hay algunas particularidades sobre su sintaxis para definirlos que hacen que su uso indeseable. En cualquier caso - no debe existir más de una expresión en un método de una sola línea.
# mal def too_much; something; something_else; end # casi bien - el primer ; es necesario def no_braces_method; body end # casi bien - el segundo ; es opcional def no_braces_method; body; end # casi bien - la sintaxis es válida, pero al no usar ; hace que sea un poco difícil de leer def some_method() body end # bien def some_method body end
Una excepción a la regla son los métodos vacíos.
# bien def no_op; end
-
Usá espacios entre operadores, después de las comas, dos puntos y puntos y comas, luego de
{
y antes de}
. Los espacios en blanco pueden (en su mayoría) irrelevantes para el intérprete de Ruby, pero su uso adecuado es la clave para escribir código fácil de leer.sum = 1 + 2 a, b = 1, 2 1 > 2 ? true : false; puts 'Hi' [1, 2, 3].each { |e| puts e }
La única excepción, con respecto a los operadores, es el operador exponente:
# mal e = M * c ** 2 # bien e = M * c**2
{
y}
merecen una aclaración especial, ya que se utilizan para bloques y hash literales, así como las expresiones embebidas en strings. Dos estilos se consideran aceptables para los hash literales.# bien - espacio luego de { y antes de } { one: 1, two: 2 } # bien - sin espacio luego de { y antes de } {one: 1, two: 2}
La primera variante es un poco más fácil de leer (y posiblemente más popular en la comunidad de Ruby en general). La segunda variante tiene la ventaja de tener diferenciación visual entre los bloques y los hash literales. Cualquiera que elijas - usalo de forma consistente.
En cuanto a las expresiones embebidas, también hay dos formas aceptables:
# bien - sin espacios "string#{expr}" # ok - podría decirse que es más legible "string#{ expr }"
El primer estilo es extremadamente más popular y generalmente se aconseja que lo elijas. Por otro lado, el segundo es (posiblemente) un poco más legible. Al igual que con los hashes
- escogé un estilo y usalo de forma consistente.
-
Sin espacios luego de
(
,[
o antes de]
,)
.some(arg).other [1, 2, 3].length
-
Sin espacios luego de
!
.# mal ! something # bien !something
-
Indentá
when
tan profundo comocase
. Sé que muchos no estarán de acuerdo con esto, pero es el estilo establecido tanto en "The Ruby Programming Language" y "Programming Ruby".# mal case when song.name == 'Misty' puts 'Not again!' when song.duration > 120 puts 'Too long!' when Time.now.hour > 21 puts "It's too late" else song.play end # bien case when song.name == 'Misty' puts 'Not again!' when song.duration > 120 puts 'Too long!' when Time.now.hour > 21 puts "It's too late" else song.play end
-
Al asignar el resultado de una expresión condicional a una variable, conservá la alineación de su ramificación.
# mal - bastante complejo kind = case year when 1850..1889 then 'Blues' when 1890..1909 then 'Ragtime' when 1910..1929 then 'New Orleans Jazz' when 1930..1939 then 'Swing' when 1940..1950 then 'Bebop' else 'Jazz' end result = if some_cond calc_something else calc_something_else end # bien - es aparente qué está pasando kind = case year when 1850..1889 then 'Blues' when 1890..1909 then 'Ragtime' when 1910..1929 then 'New Orleans Jazz' when 1930..1939 then 'Swing' when 1940..1950 then 'Bebop' else 'Jazz' end result = if some_cond calc_something else calc_something_else end # bien (y con un espacio más eficiente) kind = case year when 1850..1889 then 'Blues' when 1890..1909 then 'Ragtime' when 1910..1929 then 'New Orleans Jazz' when 1930..1939 then 'Swing' when 1940..1950 then 'Bebop' else 'Jazz' end result = if some_cond calc_something else calc_something_else end
-
Usá líneas vacías entre definiciones de métodos y también para romper un método en párrafos lógicos internos.
def some_method data = initialize(options) data.manipulate! data.result end def some_method result end
-
Usá espacios alrededor del operador
=
cuando asignes valores predeterminados a los parámetros del método:# mal def some_method(arg1=:default, arg2=nil, arg3=[]) # do something... end # bien def some_method(arg1 = :default, arg2 = nil, arg3 = []) # do something... end
Mientras que varios libros de Ruby sugieren el primer estilo, el segundo es mucho más utilizado en la práctica (y hasta se podría decirse que es un poco más fácil de leer).
-
Evitá usar la contínuación de línea con '' cuando no sea necesario. En la práctica, evitá el uso de continuación de línea en cualquier caso, excepto para la concatenación de strings.
# mal result = 1 - \ 2 # bien (pero todavía se ve horrible) result = 1 \ - 2 long_string = 'First part of the long string' \ ' and second part of the long string'
-
Al continuar una invocación de método encadenado en otra línea mantener el
.
en la segunda línea.# mal - es necesario leer la primer línea para entender la segunda línea one.two.three. four # bien - inmediatamente se ve qué está pasando en la segunda línea one.two.three .four
-
Alineá los parámetros de una llamada a un método si ocupan más de una línea. Cuando se alinean los parámetros no es apropiado que sea con más indentación de lo debido, y utilizar un indentado único para las líneas luego del primer parámetro también es aceptable.
# punto de partida (la línea es muy larga) def send_mail(source) Mailer.deliver(to: '[email protected]', from: '[email protected]', subject: 'Important message', body: source.text) end # mal (doble indentado) def send_mail(source) Mailer.deliver( to: '[email protected]', from: '[email protected]', subject: 'Important message', body: source.text) end # bien def send_mail(source) Mailer.deliver(to: '[email protected]', from: '[email protected]', subject: 'Important message', body: source.text) end # bien (indentado normal) def send_mail(source) Mailer.deliver( to: '[email protected]', from: '[email protected]', subject: 'Important message', body: source.text ) end
-
Alineá los elementos de arrays literales que ocupen múltiples líneas.
# mal - indentado simple menu_item = ["Spam", "Spam", "Spam", "Spam", "Spam", "Spam", "Spam", "Spam", "Baked beans", "Spam", "Spam", "Spam", "Spam", "Spam"] # bien menu_item = [ "Spam", "Spam", "Spam", "Spam", "Spam", "Spam", "Spam", "Spam", "Baked beans", "Spam", "Spam", "Spam", "Spam", "Spam" ] # bien menu_item = ["Spam", "Spam", "Spam", "Spam", "Spam", "Spam", "Spam", "Spam", "Baked beans", "Spam", "Spam", "Spam", "Spam", "Spam"]
-
Agregá guiones bajos para números literales grandes para mejorar su lectura.
# mal - cuantos 0s hay ahi? num = 1000000 # bien - mucho más fácil de leer por el cerebro humano num = 1_000_000
-
Usá RDoc y sus convenciones para la documentación de APIs. No dejes una línea en blanco entre el bloque de comentario y el
def
. -
Limitá las líneas a 80 caracteres.
-
Evitá los espacios en blanco.
-
No uses los comentarios de bloque. Ellos no pueden tener espacios en blanco antes y no son tan fáciles de entenderlos como comentarios.
# mal == begin comment line another comment line == end # bien # comment line # another comment line
-
Usá
::
sólo para referenciar constantes (esto incluye clases y módulos) y construcciones (comoArray()
oNokogiri::HTML()
). Nunca uses::
para la invocación de métodos.# mal SomeClass::some_method some_object::some_method # bien SomeClass.some_method some_object.some_method SomeModule::SomeClass::SOME_CONST SomeModule::SomeClass()
-
Usá
def
con paréntesis cuando tengas argumentos. Omití los paréntesis cuando el método no acepta ningún argumento.# mal def some_method() # body omitted end # bien def some_method # body omitted end # mal def some_method_with_arguments arg1, arg2 # body omitted end # bien def some_method_with_arguments(arg1, arg2) # body omitted end
-
Nunca uses
for
, a menos que sepas exactamente para qué lo usás. En su lugar deberías usar los iteradores la mayor parte del tiempo.for
se debe implementar en forma deeach
(asi estás agregando un nivel de indirección), pero con una peculiaridad -for
no introduce un nuevo scope (a diferencia deeach
) y las variables que se definen dentro de su bloque son visibles fuera del mismo.arr = [1, 2, 3] # mal for elem in arr do puts elem end # elem puede ser leída fuera del loop for elem #=> 3 # bien arr.each { |elem| puts elem } # elem no puede ser leída fuera del bloque each elem #=> NameError: undefined local variable or method `elem'
-
Nunca uses
then
paraif/unless
con multilíneas.# mal if some_condition then # body omitted end # bien if some_condition # body omitted end
-
Siempre escribí la condición en la misma línea para los condicionales
if
/unless
con multilíneas.# mal if some_condition do_something do_something_else end # bien if some_condition do_something do_something_else end
-
Preferí el operador ternario (
?:
) en lugar de las construccionesif/then/else/end
. Es más común y obviamente más conciso.# mal result = if some_condition then something else something_else end # bien result = some_condition ? something : something_else
-
Usá una expresión por fila por operador ternario. Esto también significa que los operadores ternarios no deben anidarse. Es preferible utilizar construcciones
if/else
en estos casos.# mal some_condition ? (nested_condition ? nested_something : nested_something_else) : something_else # bien if some_condition nested_condition ? nested_something : nested_something_else else something_else end
-
Nunca uses
if x: ...
- fue eliminado en Ruby 1.9. Usá el operador ternario en su lugar.# mal result = if some_condition: something else something_else end # bien result = some_condition ? something : something_else
-
Nunca uses
if x; ...
. Usá el operador ternario en su lugar. -
Usá
when x then ...
para condicionales de una línea. La sintaxis alternativawhen x: ...
fue eliminada en Ruby 1.9. -
Nunca uses
when x; ...
. Mirá la regla anterior. -
Usá
!
en lugar denot
.# mal - los paréntesis son necesarios por la precedencia de operador x = (not something) # bien x = !something
-
Evitá el uso de
!!
.# mal x = 'test' # obscure nil check if !!x # body omitted end x = false # double negation is useless on booleans !!x # => false # bien x = 'test' if !x.nil? # body omitted end
-
Las palabras
and
yor
están prohibidas. Simplemente no valen la pena. En su lugar, siempre usá&&
y||
.# mal # boolean expression if some_condition and some_other_condition do_something end # control flow document.saved? or document.save! # bien # boolean expression if some_condition && some_other_condition do_something end # control flow document.saved? || document.save!
-
Evitá usar
?:
(operador ternario) en multilíneas; en su lugar usáif/unless
. -
Favorecé al uso del modificador
if/unless
cuando tengas que escribir en una línea. Otra buena alternativa es el uso del control de flujo con&&/||
.# mal if some_condition do_something end # bien do_something if some_condition # otra buena opción some_condition && do_something
-
Favorecé
unless
por sobreif
para condiciones negativas (o control de flujo con||
).# mal do_something if !some_condition # mal do_something if not some_condition # bien do_something unless some_condition # otra buena opción some_condition || do_something
-
Nunca uses
unless
conelse
. Reescribí para que el caso positivo esté primero.# mal unless success? puts 'failure' else puts 'success' end # bien if success? puts 'success' else puts 'failure' end
-
No uses paréntesis alrededor de la condición de
if/unless/while/until
.# mal if (x > 10) # body omitted end # bien if x > 10 # body omitted end
-
Nunca uses
while/until condition do
para unwhile/until
multilínea.# mal while x > 5 do # body omitted end until x > 5 do # body omitted end # bien while x > 5 # body omitted end until x > 5 # body omitted end
-
Favorecé el uso del modificador
while/until
cuando puedas escribir la comparación en una línea.# mal while some_condition do_something end # bien do_something while some_condition
-
Favorecé
until
por sobrewhile
para condiciones negativas.# mal do_something while !some_condition # bien do_something until some_condition
-
Usá
Kernel#loop
con break en lugar debegin/end/until
obegin/end/while
para validar al final de cada loop.# mal begin puts val val += 1 end while val < 0 # bien loop do puts val val += 1 break unless val < 0 end
-
Omití los paréntesis alrededor de los parámetros para los métodos que forman parte de un DSL interno (ejemplo: Rake, Rails, RSpec), métodos que tengan "keyword" status en Ruby (ejemplo:
attr_reader
,puts
) y métodos accesores de atributos. Usá paréntesis alrededor de los argumentos de todas las otras llamadas a métodos.class Person attr_reader :name, :age # omitted end temperance = Person.new('Temperance', 30) temperance.name puts temperance.age x = Math.sin(y) array.delete(e) bowling.score.should == 0
-
Omití las llaves externas alrededor de las opciones implícitas de un hash.
# mal user.set({ name: 'John', age: 45, permissions: { read: true } }) # bien user.set(name: 'John', age: 45, permissions: { read: true })
-
Omití tanto las llaves externas como los paréntesis para métodos que formen parte de un DSL interno.
class Person < ActiveRecord::Base # mal validates(:name, { presence: true, length: { within: 1..10 } }) # bien validates :name, presence: true, length: { within: 1..10 } end
-
Omití los paréntesis para llamadas a métodos sin argumentos.
# mal Kernel.exit!() 2.even?() fork() 'test'.upcase() # bien Kernel.exit! 2.even? fork 'test'.upcase
-
Elegí
{...}
por sobredo...end
para bloques de una línea. Evitá el uso de{...}
para bloques multilíneas (encadenamiento de multilínea siempre es horrible). Siempre usádo...end
para "contorl de flujo" y "definiciones de métodos" (e.g. en Rakefiles y algunos DSLs). Evitá usardo...end
cuando estés encadenando métodos.names = ['Bozhidar', 'Steve', 'Sarah'] # mal names.each do |name| puts name end # bien names.each { |name| puts name } # mal names.select do |name| name.start_with?('S') end.map { |name| name.upcase } # bien names.select { |name| name.start_with?('S') }.map { |name| name.upcase }
Puede ser que algunas personas piensen que el encadenamiento en multilínea se vería bien con el uso de {...}, pero en realidad deberían preguntarse a sí mismos - es el código realmente legible y los contenidos de los bloques pueden ser extraídos con métodos elegantes?
-
Evitá usar
return
cuando no se requiera realizar control de flujo.# mal def some_method(some_arr) return some_arr.size end # bien def some_method(some_arr) some_arr.size end
-
Evitá usar
self
cuando no es necesario. (Solo se necesita cuando se llama a un accesor de escritura propio.)# mal def ready? if self.last_reviewed_at > self.last_updated_at self.worker.update(self.content, self.options) self.status = :in_progress end self.status == :verified end # bien def ready? if last_reviewed_at > last_updated_at worker.update(content, options) self.status = :in_progress end status == :verified end
-
Como convención, evitá oscurecer métodos con variables locales, excepto que ambos sean lo mismo.
class Foo attr_accessor :options # ok def initialize(options) self.options = options # both options and self.options are equivalent here end # mal def do_something(options = {}) unless options[:when] == :later output(self.options[:message]) end end # bien def do_something(params = {}) unless params[:when] == :later output(options[:message]) end end end
-
No uses el valor de retorno de
=
(asignación) en expresiones condicionales a menos que la asignación se encuentre entre paréntesis. Esta es una expresión bastante popular entre los Rubyistas que se refiere a veces como asignación segura en condiciones.# mal (+ una advertencia) if v = array.grep(/foo/) do_something(v) ... end # bien (MRI todavía se quejaría, per RuboCop no) if (v = array.grep(/foo/)) do_something(v) ... end # bien v = array.grep(/foo/) if v do_something(v) ... end
-
Usá
||=
libremente para inicializar variables.# set name to Bozhidar, only if it's nil or false name ||= 'Bozhidar'
-
No uses
||=
para inicializar variables booleanas. (Considerá qué pasaría si el valor actual fuesefalse
.)# mal - asignaría true a enabled aunque ya era false enabled ||= true # bien enabled = true if enabled.nil?
-
Evitá el uso explícito del operador de igualdad idéntica
===
. Como su nombre indica, está destinado a ser utilizado implícitamente por expresionescase
y fuera de ellas provee código bastante confuso.# mal Array === something (1..100) === 7 /something/ === some_string # bien something.is_a?(Array) (1..100).include?(7) some_string =~ /something/
-
Evitá usar variables especiales de tipo Perl (como
$:
,$;
, etc.). Son bastante crípticas y su uso en cualquier lugar excepto scripts de una línea está desalentado. Usá los alias amigables para humanos proporcionados por la libreríaEnglish
.# mal $:.unshift File.dirname(__FILE__) # bien require 'English' $LOAD_PATH.unshift File.dirname(__FILE__)
-
Nunca uses un espacio entre el nombre de un método y la apertura de un paréntesis.
# mal f (3 + 2) + 1 # bien f(3 + 2) + 1
-
Si el primer argumento de un método comienza con un paréntesis abierto, siempre utilizá paréntesis en la invocación del método. Por ejemplo, escribí
f ((3 + 2) + 1)
. -
Siempre ejecutá el intérprete de Ruby con la opción
-w
, para que avise si se olvida alguna de las reglas anteriores! -
Usá la nueva sintaxis de lambda literal para bloques de una sola línea. Usá el método
lambda
para bloques multilínea.# mal l = lambda { |a, b| a + b } l.call(1, 2) # correcto, pero se ve extremadamente incómodo l = ->(a, b) do tmp = a * 7 tmp * b / 50 end # bien l = ->(a, b) { a + b } l.call(1, 2) l = lambda do |a, b| tmp = a * 7 tmp * b / 50 end
-
Elegí
proc
por sobreProc.new
.# mal p = Proc.new { |n| puts n } # bien p = proc { |n| puts n }
-
Elegí
proc.call()
por sobreproc[]
oproc.()
tanto para lambdas y procs.# mal - se ve similar a un acceso de Enumeración l = ->(v) { puts v } l[1] # también mal - sintaxis no común l = ->(v) { puts v } l.(1) # bien l = ->(v) { puts v } l.call(1)
-
Usá
_
para los parámetros sin usar de bloques.# mal result = hash.map { |k, v| v + 1 } # bien result = hash.map { |_, v| v + 1 }
-
Usea
$stdout/$stderr/$stdin
en lugar deSTDOUT/STDERR/STDIN
.STDOUT/STDERR/STDIN
son constantes, y mientras que podés reasignar constantes (posiblemente para redirigr un proceso) en Ruby, vas a tener una advertencia del intérprete si hacés eso. -
Usá
warn
en lugar de$stderr.puts
. Aparte de ser más conciso y claro,warn
te permite suprimir advertencias si lo necesitás (seteando el nivel de advertencia a 0 via-W0
). -
Elegí usar
sprintf
y su aliasformat
por sobre el método crípticoString#%
.# mal '%d %d' % [20, 10] # => '20 10' # bien sprintf('%d %d', 20, 10) # => '20 10' # bien sprintf('%{first} %{second}', first: 20, second: 10) # => '20 10' format('%d %d', 20, 10) # => '20 10' # bien format('%{first} %{second}', first: 20, second: 10) # => '20 10'
-
Elegí el uso de
Array#join
por sobre el crípticoArray#*
con un argumento string.# mal %w(one two three) * ', ' # => 'one, two, three' # bien %w(one two three).join(', ') # => 'one, two, three'
-
Usá
[*var]
oArray()
en lugar de la verificación explícitaArray
, cuando trabajes con una variable que quieras tratar como un Array, pero no estás seguro que sea un array.# mal paths = [paths] unless paths.is_a? Array paths.each { |path| do_something(path) } # bien [*paths].each { |path| do_something(path) } # bien (y un poquito más legible) Array(paths).each { |path| do_something(path) }
-
Usá rangos o
Comparable#between?
en lugar de una comparación lógica compleja cuando sea posible.# mal do_something if x >= 1000 && x <= 2000 # bien do_something if (1000..2000).include?(x) # bien do_something if x.between?(1000, 2000)
-
Elegí el uso de métodos subyacentes en lugar de las comparaciones explícitas con
==
. Comparaciones numéricas están OK.# mal if x % 2 == 0 end if x % 2 == 1 end if x == nil end # bien if x.even? end if x.odd? end if x.nil? end if x.zero? end if x == 0 end
-
Evitá el uso de bloques
BEGIN
. -
Nunca uses bloques
END
. En su lugar usáKernel#at_exit
.# mal END { puts 'Goodbye!' } # bien at_exit { puts 'Goodbye!' }
-
Evitá el uso de flip-flops.
-
Evitá el uso de condicionales anidados para control de flujo. Elegí una cláusula de guardia (guard clause) cuando puedas afirmar datos inválidos. Una cláusula de guardia es un condicional al principio de una función que trata de salir de ella tan pronto como pueda.
# mal def compute_thing(thing) if thing[:foo] update_with_bar(thing) if thing[:foo][:bar] partial_compute(thing) else re_compute(thing) end end end # bien def compute_thing(thing) return unless thing[:foo] update_with_bar(thing[:foo]) return re_compute(thing) unless thing[:foo][:bar] partial_compute(thing) end
Las únicas dificultades reales en programación son invalidación de caché y nombrar cosas.
-- Phil Karlton
-
Nombres de referencia en Inglés.
# mal - referencia no está utilizando caracteres ascii заплата = 1_000 # mal - la referencia es una palabra búlgara, escrita con caracteres latinos (en lugar de Cirílico) zaplata = 1_000 # bien salary = 1_000
-
Usá
snake_case
para los símbolos, métodos y variables.# mal :'some symbol' :SomeSymbol :someSymbol someVar = 5 def someMethod ... end def SomeMethod ... end # bien :some_symbol def some_method ... end
-
Usá
CamelCase
para clases y módulos. (Mantené en mayúsculas los acrónimos como HTTP, RFC, XML.)# mal class Someclass ... end class Some_Class ... end class SomeXml ... end # bien class SomeClass ... end class SomeXML ... end
-
Usá
SCREAMING_SNAKE_CASE
para las constantes.# mal SomeConst = 5 # bien SOME_CONST = 5
-
Los nombres de los métodos que afirman (métodos que devuelven un valor booleano) deben terminar con un signo de pregunta. (ejemplo:
Array#empty?
). Los métodos que no devuelvan un booleano, no deben terminar con un signo de pregunta. -
Los nombres de métodos potencialmente peligrosos (ejemplo: métodos que modifican
self
o los argumentos,exit!
- no ejecuta finalizadores como lo haceexit
, etc.) deben terminar con un signo de exclamación solo si existe una versión segura de ese método peligroso.# mal - no hay ningún método 'seguro' que se llame igual class Person def update! end end # bien class Person def update end end # bien class Person def update! end def update end end
-
Definí un método non-bang (seguro) en relación al método bang (peligroso) si es posible.
class Array def flatten_once! res = [] each do |e| [*e].each { |f| res << f } end replace(res) end def flatten_once dup.flatten_once! end end
-
Cuando se usa
reduce
con bloques chicos, nombrá los argumentos|a, e|
(accumulator, element). -
Cuando definas operadores binarios, nombrá el argumento como
other
(<<
y[]
son excepciones a la regla, ya que su semántica es diferente).def +(other) # body omitted end
-
Elegí
map
por sobrecollect
,find
por sobredetecet
,select
por sobrefind_all
,reduce
por sobreinject
ysize
por sobrelength
. No es un requerimiento difícil; si el uso de alias realza la legibilidad, está bien usarlos. Los métodos de rima son heredados de Smalltalk y no son comunes en otros lenguajes de programación. La razón para usarselect
por sobrefind_all
es porque va muy bien junto conreject
y su nombre es bastante auto-explicativo. -
Usá
flat_map
en lugar demap
+flatten
. Esto no se aplica a los arrays con profundidad mayor a 2, ejemplo: si tenemosusers.first.songs == ['a', ['b','c']]
, entonces hay que usarmap + flatten
en lugar deflat_map
.flat_map
achata el array a 1 nivel, mientras queflatten
achata el array del todo.# mal all_songs = users.map(&:songs).flatten.uniq # bien all_songs = users.flat_map(&:songs).uniq
-
Usá
reverse_each
en lugar dereverse.each
.reverse_each
no realiza una asignación de array y eso es algo bueno.# mal array.reverse.each { ... } # bien array.reverse_each { ... }
El buen código es su mejor documentación. Cuando estés a punto de agregar un comentario, preguntate: "¿Cómo puedo mejorar el código para que no sea necesario este comentario?" Mejorá el código y luego documentalo para que hacerlo aún más claro.
-- Steve McConnell
-
Escribí código autodocumentado e ignorá el resto de esta sección. En serio!
-
Escribí tus comentarios en Inglés para evitar problemas con los caracteres especiales.
-
Usá un espacio entre el primer caracter
#
del comentario y el texto propio del comentario. -
Los comentarios que son más largos que una palabra son capitalizados y usan puntuación. Usá un espacio luego de los puntos.
-
Evitá comentarios supérfluos.
# mal counter += 1 # Increments counter by one.
-
Mantené los comentarios existentes actualizados. Un comentario viejo es peor que no utilizar comentarios.
El buen código es como un buen chiste - no necesita explicación
-- Russ Olsen
- Evitá escribir comentarios para explicar código malo. Refactorizá el código para hacerlo más auto-explicativo. ()
- Avoid writing comments to explain bad code. Refactor the code to make it self-explanatory. (Hacé o no hacé - no hay intentar. --Yoda)
-
Generalmente los apuntes se tienen que escribir inmediatamente en la primer línea encima del código a comentar.
-
La palabra clave del apunte es seguida de dos puntos y un espacio, y luego una nota que describe el problema.
-
Si se son necesarias varias líneas para describir el problema, las líneas subsiguientes tienen que tener una sangría de dos espacios después del
#
.def bar # FIXME: This has crashed occasionally since v3.2.1. It may # be related to the BarBazUtil upgrade. baz(:quux) end
-
En los casos donde el problema es tan obvio que cualquier documentación fuese redundante, los apuntes se pueden dejar al final de esa línea, sin ninguna nota. Su uso debe ser la excepción y no la regla.
def bar sleep 100 # OPTIMIZE end
-
Usá
TODO
para resaltar funcionalidades faltantes o funcionalidades que deben agregarse más adelante. -
Usá
FIXME
para resaltar código roto que necesita ser arreglado. -
Usá
OPTIMIZE
para resaltar código lento o ineficiente que pueda causar problemas de performance. -
Usá
HACK
para resaltar código que utilice prácticas cuestionables que no se vean bien y que debería ser refactorizado lo antes posible. -
Usá
REVIEW
para resaltar cualquier cosa que debe ser revisada para confirmar que está funcionando como debería. Por ejemplo:REVIEW: Are we sure this is how the client does X currently?
-
Usá otra palabra como apunte si sentís que sea apropiado, pero asegurate de documentarlas en el
README
de tu proyecto o similar.
-
Usá una estructura coherente para definicior tu clase.
class Person # extend and include go first extend SomeModule include AnotherModule # constants are next SOME_CONSTANT = 20 # afterwards we have attribute macros attr_reader :name # followed by other macros (if any) validates :name # public class methods are next in line def self.some_method end # followed by public instance methods def some_method end # protected and private methods are grouped near the end protected def some_protected_method end private def some_private_method end end
-
Elegí módules de clases únicamente con métodos de clases. Las clases deben ser utilizadas únicamente cuando tiene sentido crear instancias fuera de ellos.
# mal class SomeClass def self.some_method # body omitted end def self.some_other_method end end # bien module SomeClass module_function def some_method # body omitted end def some_other_method end end
-
Elegí el uso de
module_function
por sobreextend self
cuando quieras cambiar los métodos de instancia de un módulo en un método de clase.# mal module Utilities extend self def parse_something(string) # do stuff here end def other_utility_method(number, string) # do some more stuff end end # bien module Utilities module_function def parse_something(string) # do stuff here end def other_utility_method(number, string) # do some more stuff end end
-
Cuando diseñes jerarquías de clases, asegurate de que se ajuseten al Principio de Sustitución de Liskov.
-
Tratá de hacer tus clases tan SOLIDas como sea posible.
-
Siempre proporcioná un método
to_s
para clases que representen objetos de dominio.class Person attr_reader :first_name, :last_name def initialize(first_name, last_name) @first_name = first_name @last_name = last_name end def to_s "#{@first_name} #{@last_name}" end end
-
Usá la familia de funciones
attr
para definir accesores triviales o mutators.# mal class Person def initialize(first_name, last_name) @first_name = first_name @last_name = last_name end def first_name @first_name end def last_name @last_name end end # bien class Person attr_reader :first_name, :last_name def initialize(first_name, last_name) @first_name = first_name @last_name = last_name end end
-
Evitá el uso de
attr
. En su lugar usáattr_reader
yattr_accessor
.# mal - crea un único accesor de atributo (deprecado en 1.9) attr :something, true attr :one, :two, :three # behaves as attr_reader # bien attr_accessor :something attr_reader :one, :two, :three
-
Considerá usar
Struct.new
, el cual define por vos los accesores triviales, constructor y operadores de comparación.# bien class Person attr_reader :first_name, :last_name def initialize(first_name, last_name) @first_name = first_name @last_name = last_name end end # mejor Person = Struct.new(:first_name, :last_name) do end
-
No extiendas un
Struct.new
- ya de por si es una clase nueva. Extendiéndolo introduce un nivel de clase superfluo y también puede introducir errores extraños si el archivo es requerido múltiples veces. -
Considerá agregar un método factory para proveer más formas sensibles de crear instancias de una clase en particular.
class Person def self.create(options_hash) # body omitted end end
-
Preferí duck-typing en lugar de herencia.
# mal class Animal # abstract method def speak end end # extend superclass class Duck < Animal def speak puts 'Quack! Quack' end end # extend superclass class Dog < Animal def speak puts 'Bau! Bau!' end end # bien class Duck def speak puts 'Quack! Quack' end end class Dog def speak puts 'Bau! Bau!' end end
-
Evitá el uso de variables de clas (
@@
) debido a sus comportamientos "sucios" en la herencia.class Parent @@class_var = 'parent' def self.print_class_var puts @@class_var end end class Child < Parent @@class_var = 'child' end Parent.print_class_var # => will print "child"
Como podés ver todas las clases en una jerarquía de clases en realidad comparten una variable de clase. Por lo general las variables de instancia de clase deben ser preferidas a las variables de clase.
-
Asigná niveles de visibilidad adecuados para los métodos (
private
,protected
) de acuerdo con su correcto uso. No vayas por ahi dejando todopublic
(que es el estado predeterminado). Después de todo ahora estamos programando en Ruby, no en Python. -
Indentá las palabas
public
,protected
, yprivate
tanto como los métodos a los que se aplican. Dejá una línea en blanco antes y después del modificador de visibilidad, en orden de enfatizar que eso aplica a todos los métodos que se encuentran debajo.class SomeClass def public_method # ... end private def private_method # ... end def another_private_method # ... end end
-
Usá
def self.method
para definir métodos singleton. Eso hace el código más fácil de refactorizar, debido a que el nombre de la clase no está repetido.class TestClass # mal def TestClass.some_method # body omitted end # bien def self.some_other_method # body omitted end # También es posible y conveniente cuando # quieras definir muchos métodos singleton. class << self def first_method # body omitted end def second_method_etc # body omitted end end end
-
Señalizá las excepciones utilizando el método
fail
. Usáraise
solo cuando quieras atrapar una excepción y quieras volver a llamarlo (porque no está fallando, sino que está lanzando una excepción de forma explícita y a propósito).begin fail 'Oops' rescue => error raise if error.message != 'Oops' end
-
No especifiques explícitamente
RuntimeError
en la versión de dos argumentos defail/raise
.# mal fail RuntimeError, 'message' # bien - señaliza un RuntimeError por defecto fail 'message'
-
Elegí suministrar una clase de excepción y un mensaje como dos argumentos separados para
fail/raise
, en lugar de una instancia de excepción.# mal fail SomeException.new('message') # No hay una forma de hacer `fail SomeException.new('message'), backtrace`. # bien fail SomeException, 'message' # Consistente con `fail SomeException, 'message', backtrace`.
-
Nunca retornes desde un bloque
ensure
. Si retornás de forma explícita desde un método dentro de un bloqueensure
, el retorno va a tomar precedente sobre cualquier excepción que sea llamada, y el método va a retornar como si ninguna excepción hubiera sido llamada. De hecho, la excepción va a ser desestimada en silencio.def foo begin fail ensure return 'very bad idea' end end
-
Usá bloques implícitos de begin siempre que sea posible.
# mal def foo begin # main logic goes here rescue # failure handling goes here end end # bien def foo # main logic goes here rescue # failure handling goes here end
-
Mitigá la proliferación de bloques
begin
utilizando métodos de contingencia (un término elegido por Avdi Grimm).# mal begin something_that_might_fail rescue IOError # handle IOError end begin something_else_that_might_fail rescue IOError # handle IOError end # bien def with_io_error_handling yield rescue IOError # handle IOError end with_io_error_handling { something_that_might_fail } with_io_error_handling { something_else_that_might_fail }
-
No suprimas las excepciones.
# mal begin # an exception occurs here rescue SomeError # the rescue clause does absolutely nothing end # mal do_something rescue nil
-
Evitá usar
rescue
en su forma de modificador.# mal - esto atrapa una excepción de la clase StandardError y sus clases hijas read_file rescue handle_error($!) # bien - esto atrapa solo las excepciones de la clase Errno::ENOENT y sus clases hijas def foo read_file rescue Errno::ENOENT => ex handle_error(ex) end
-
No uses excepciones para control de flujo.
# mal begin n / d rescue ZeroDivisionError puts 'Cannot divide by 0!' end # bien if d.zero? puts 'Cannot divide by 0!' else n / d end
-
Evitá rescatar la clase
Exception
. Esto va a atrapar la señal y va a llamar aexit
, siendo necesario que paseskill -9
al proceso.# mal begin # calls to exit and kill signals will be caught (except kill -9) exit rescue Exception puts "you didn't really want to exit, right?" # exception handling end # bien begin # a blind rescue rescues from StandardError, not Exception as many # programmers assume. rescue => e # exception handling end # también está bien begin # an exception occurs here rescue StandardError => e # exception handling end
-
Escribí excepciones más específicas primero, de otra forma nunca van a poder ser atrapadas.
# mal begin # some code rescue Exception => e # some handling rescue StandardError => e # some handling end # bien begin # some code rescue StandardError => e # some handling rescue Exception => e # some handling end
-
Cerrá recursos externos abiertos por tu programa en un bloque ensure.
f = File.open('testfile') begin # .. process rescue # .. handle error ensure f.close unless f.nil? end
-
Preferí el uso de excepciones de la standard library en lugar de crear nuevas clases de excepciones.
-
Preferí el uso de la notación para arrays literales y creación de hashes (excepto que necesites pasar parámetros a sus constructores).
# mal arr = Array.new hash = Hash.new # bien arr = [] hash = {}
-
Preferí usar
%w
en lugar de la sintaxis array literal cuando necesites un array de palabras (strings no-vacías sin espacios ni caracteres espaciles en cada uno). Aplicá esta regla solo en los arrays de dos o más elementos.# mal STATES = ['draft', 'open', 'closed'] # bien STATES = %w(draft open closed)
-
Preferí
%i
en lugar de la sintaxis de array literal cuando necesites un array de símbolos (y no necesitás mantener compatibilidad con Ruby 1.9). Aplicá esta regla sólo para arrays con dos o más elementos.# mal STATES = [:draft, :open, :closed] # bien STATES = %i(draft open closed)
-
Evitá la creación de grandes espacios en arrays.
arr = [] arr[100] = 1 # y así tenés un array con un montón de nils
-
Cuando estés accediendo al primer o último elmento de un array, preferí usar
first
olast
en lugar de[0]
o[-1]
. -
Usá
Set
en lugar deArray
cuando estés tratando con elementos únicos.Set
implementa una colección de valores desordenados sin duplicados. Esto es un híbrido de la vacilidad interoperacional intuitiva deArray
, y velocidad de lectura deHash
. -
Preferí símbolos en lugar de strings y hash keys.
# mal hash = { 'one' => 1, 'two' => 2, 'three' => 3 } # bien hash = { one: 1, two: 2, three: 3 }
-
Evitá el uso de objetos mutables como hash keys.
-
Usá la sintaxis de hash literal cuando tus hash keys sean símbolos.
# mal hash = { :one => 1, :two => 2, :three => 3 } # bien hash = { one: 1, two: 2, three: 3 }
-
Usá
Hash#key?
en lugar deHash#has_key?
yHash#value?
en lugar deHash#has_value?
. Como dice Matz aquí, Las funciones largas están consideradas deprecadas.# mal hash.has_key?(:test) hash.has_value?(value) # bien hash.key?(:test) hash.value?(value)
-
Usá
Hash#fetch
cuando estés tratando con hash keys que deben estar presentes.heroes = { batman: 'Bruce Wayne', superman: 'Clark Kent' } # mal - si generamos un error, puede que no lo veamos rápido heroes[:batman] # => "Bruce Wayne" heroes[:supermann] # => nil # bien - fetch genera un KeyError, haciendo obvios los problemas heroes.fetch(:supermann)
-
Utilizá valores por defecto para hash keys usando
Hash#fetch
, opuesto a generar tu propia lógica.batman = { name: 'Bruce Wayne', is_evil: false } # mal - si solo usamos el operador || y tenemos un error medio falso, no vamos a tener el resultado esperado batman[:is_evil] || true # => true # bien - fetch trabaja mejor con valores medio falsos batman.fetch(:is_evil, true) # => false
-
Preferí el uso de bloques en lugar del formato por defecto de
Hash#fetch
.batman = { name: 'Bruce Wayne' } # mal - si usamos el valor por defecto, se va a evaluar en el momento # por lo que vamos a relantizar el programa, si se utiliza múltiples veces batman.fetch(:powers, get_batman_powers) # get_batman_powers is an expensive call # bien - los bloques se evalúan sin bloquer el proceso, asi solo se llama en caso de una excepción KeyError batman.fetch(:powers) { get_batman_powers }
-
Confiá en el hecho de que desde Ruby 1.9 los hashes están ordenados.
-
Nunca modifiques una colección mientras la estés recorriendo.
-
Preferí interpolación de strings en lugar de concatenación de strings:
# mal email_with_name = user.name + ' <' + user.email + '>' # bien email_with_name = "#{user.name} <#{user.email}>"
-
Considerá el uso de interpolación de string con espacio. Hace que sea más claro para separar el código del string.
"#{ user.last_name }, #{ user.first_name }"
-
Preferí escribir los con una sola comilla cuando no tengás la necesidad de realizar interpolación o usar símbolos especiales como
\t
,\n
,'
, etc.# mal name = "Bozhidar" # bien name = 'Bozhidar'
-
No uses el caracter literal de sintaxis
?x
. Desde Ruby 1.0 esto se hizo redundante -?x
se interpreta como'x'
(un string con solo un caracter dentro).# mal char = ?c # bien char = 'c'
-
No dejes de usar
{}
alrededor de las variables de instancia o globales, siendo interpolados dentro de un string.class Person attr_reader :first_name, :last_name def initialize(first_name, last_name) @first_name = first_name @last_name = last_name end # mal - válido, pero raro def to_s "#@first_name #@last_name" end # bien def to_s "#{@first_name} #{@last_name}" end end $global = 0 # mal puts "$global = #$global" # bien puts "$global = #{$global}"
-
Evitá usar
String#+
cuando necesites construir un pedazo grande de datos. En su lugar usáString#<<
. Concatenación muta la instancia del string en el lugar y siempre es más rápido queString#+
, el cual crea un grupo de nuevos objetos de strings.# bien y rápido además html = '' html << '<h1>Page title</h1>' paragraphs.each do |paragraph| html << "<p>#{paragraph}</p>" end
-
Cuando estés usando heredocs para strings multi-línea no te olvides del hecho de que ellos necesitan espacios en blanco. Es una buena práctica utilizar algo de margen basado en cómo hay que recortar el espacio en blanco excesivo.
code = <<-END.gsub(/^\s+\|/, '') |def test | some_method | other_method |end END #=> "def test\n some_method\n other_method\nend\n"
Algunas personas, cuando se encuentran un problema, piensan "Ya se, voy a usar expresiones regulares." Ahora tienen dos problemas.
-- Jamie Zawinski
-
No uses expresiones regulares si solo necesitás buscar texto plano en un string:
string['text']
-
Para construcciones simples podés usar regexp directamente a través de un índice de string.
match = string[/regexp/] # get content of matched regexp first_group = string[/text(grp)/, 1] # get content of captured group string[/text (grp)/, 1] = 'replace' # string => 'text replace'
-
Usá grupos que no capturen código cuando no uses resultados capturados con paréntesis.
/(first|second)/ # mal /(?:first|second)/ # bien
-
No uses las variables crípticas de Perl, que denoten las pocisiones de los resultados de regex (
$1
,$2
, etc). En su lugar usáRegexp.last_match[n]
./(regexp)/ =~ string ... # mal process $1 # bien process Regexp.last_match[1]
-
Evitá usar grupos numerados, ya que puede ser difícil de decir qué contienen. En su lugar deben usarse grupos con nombre.
# mal /(regexp)/ =~ string ... process Regexp.last_match[1] # bien /(?<meaningful_var>regexp)/ =~ string ... process meaningful_var
-
Clases de caracteres únicamente tienen caracteres especiales que te deberían importar:
^
,-
,\
,]
, por lo que no debes escapar.
o llaves en[]
. -
Tené cuidado con
^
y$
, ya que ellos se igualan con el inicio/final de la línea, no el final del string. Si querés igualar el string completo usá:\A
y\z
(no confundir con\Z
el cual es el equivalente de/\n?\z/
).string = "some injection\nusername" string[/^username$/] # matches string[/\Ausername\z/] # don't match
-
Usá el modificador
x
para regexps complejos. Esto los hace más legibles y vas a poder agregar mejores comentarios. Pero tené cuidado, que los espacios son ignorados.regexp = %r{ start # some text \s # white space char (group) # first group (?:alt1|alt2) # some alternation end }x
-
Para cambios complejos se pueden usar
sub
/gsub
con un bloque o un hash.
-
Usá
%()
(es un alias para%Q
) para un string de una línea, el cual requiere tanto interpolación y uso de comillas dobles. Para strings multi-línea, es preferible usar heredocs.# mal (no necesita interpolación) %(<div class="text">Some text</div>) # should be '<div class="text">Some text</div>' # mal (no tiene comillas dobles) %(This is #{quality} style) # Debería ser "This is #{quality} style" # mal (múltiples líneas) %(<div>\n<span class="big">#{exclamation}</span>\n</div>) # debería ser un heredoc. # bien (requiere interpolación, tiene comillas, y es una sola línea) %(<tr><td class="name">#{name}</td>)
-
Evitá
%q
, excepto que tengas un string con'
y"
dentro. Los strings literales son más legibles y deberían ser elegidos, excepto que tengamos que escapar un montón de caracteres internos.# mal name = %q(Bruce Wayne) time = %q(8 o'clock) question = %q("What did you say?") # bien name = 'Bruce Wayne' time = "8 o'clock" question = '"What did you say?"'
-
Usá
%r
solo para expresiones regulares que igualen a más de un caracter/
.# mal %r(\s+) # todavía está mal %r(^/(.*)$) # debería ser /^\/(.*)$/ # bien %r(^/blog/2011/(.*)$)
-
Evitá el uso de
%x
, excepto que estés invocando un comando con comillas contrarias (que es bastante inusual).# mal date = %x(date) # bien date = `date` echo = %x(echo `date`)
-
Evitá el uso de
%s
. Parece que la comunidad decidió que:"some string"
es la forma preferida para crear un símbolo con espacios dentro. -
Preferí
()
como delimitadores para todos los literales%
, excepto para%r
. Ya que las llaves aparecen seguido dentro de las expresiones regulares en varios escenarios, va a ser menos común que aparezca el caracter{
y va a ser una mejor elección para usar como delimitador, dependiendo en el contenido de la regexp.# mal %w[one two three] %q{"Test's king!", John said.} # bien %w(one two three) %q("Test's king!", John said.)
-
Evitá metaprogramación innecesaria.
-
No hagas lío con las clases core cuando estés escribiendo librerías. (No parchees como un mono.)
-
La forma de bloque de
class_eval
es preferible en forma de interpolación de string.-
cuando uses la forma de interpolación de string, siempre usá
__FILE__
y__LINE__
, asi la búsqueda de código tiene sentido:class_eval 'def use_relative_model_naming?; true; end', __FILE__, __LINE__
-
define_method
es mejor queclass_eval{ def ... }
-
-
Cuando uses
class_eval
(u otroeval
) con interpolación de string, agregá un bloque de comentario que muestra su apariencia si está interpolada (una práctica que aprendí con el código de Rails):# from activesupport/lib/active_support/core_ext/string/output_safety.rb UNSAFE_STRING_METHODS.each do |unsafe_method| if 'String'.respond_to?(unsafe_method) class_eval <<-EOT, __FILE__, __LINE__ + 1 def #{unsafe_method}(*args, &block) # def capitalize(*args, &block) to_str.#{unsafe_method}(*args, &block) # to_str.capitalize(*args, &block) end # end def #{unsafe_method}!(*args) # def capitalize!(*args) @dirty = true # @dirty = true super # super end # end EOT end end
-
Evitá usar
method_missing
para metaprogramación, ya que hace dificil leer el código, el comportamiento no está listado en#methods
, y las llamadas a métodos mal escritas pueden funcionar silienciosamente, ejemplo:nukes.launch_state = false
. En su lugar, considerá usar delegation, proxy odefine_method
. Si es necesario, usámethod_missing
:-
Está seguro de también definir
respond_to_missing?
-
Solo atrapá métodos con un prefix bien definido, como
find_by_*
-- hacé tu código lo más asertivo posible. -
Llamá a
super
al final de tu definición -
Delegá en métodos asertivos, no mágicos:
# mal def method_missing?(meth, *args, &block) if /^find_by_(?<prop>.*)/ =~ meth # ... lots of code to do a find_by else super end end # bien def method_missing?(meth, *args, &block) if /^find_by_(?<prop>.*)/ =~ meth find_by(prop, *args, &block) else super end end # best of all, though, would to define_method as each findable attribute is declared
-
-
Escribí código seguro con
ruby -w
. -
Evitá los hashes como un parámetro opcional. Acaso el método hace demasiado? (Los inicializadores de objetos son excepciones a esta regla).
-
Evitá métodos mayores que 10 LOC (lines of code). Idealmente, la mayoría de los métodos van a ser menores que 5 LOC. Líneas vacías no cuentan como LOC relevantes.
-
Evitá listas de parámetros mayores que tres o cuatro parámetros.
-
Si realmente necesitás métodos "globales", agregalos a tu Kernel y convertilos en
private
. -
Usá variables de instancia en módulos en lugar de variables globales.
# mal $foo_bar = 1 # bien module Foo class << self attr_accessor :bar end end Foo.bar = 1
-
Evitá
alias
cuandoalias_method
hace mejor el trabajo. -
Usá
OptionParser
para parsear líneas de opciones de comando complejas yruby -s
para líneas de opciones de comando triviales. -
Preferí
Time.now
por sobreTime.new
cuando estés leyendo la hora del sistema. -
Escribí código en forma funcional, evitando mutación cuando eso tenga sentido.
-
No mutes argumentos excepto que ese sea el propósito del método.
-
Evitá más de tres niveles de anidación de bloques.
-
Se consistente. En un mundo ideal, se consistente con estas guías.
-
Usá el sentido común.
Aquí hay algunas herramientas que pueden ayudarte a validar el código Ruby de forma automática con esta guía.
RuboCop es un validador de código Ruby basado en esta guía. RuboCop actualmente cubre una significante parte de esta Guía, soportando tanto MRI 1.9 y MRI 2.0, y ya tiene integración con Emacs.
El código de inspección de RubyMine está parcialmente basado en esta guía.
Nada de lo que está en esta guía está escrito en piedra. Es mi deseo que trabajemos juntos con todos los que estén interesados con el estilo de código en Ruby, para que podamos crear un recurso que pueda beneficiar a toda la comunidad de Ruby.
Siéntanse libres de abrir tickets o enviar pull requests con mejoras. ¡Desde ya muchas gracias por su ayuda!
Este trabajo está licenciado bajo Creative Commons Attribution 3.0 Unported License
Una guía de estilos pensada para la comunidad es de poca ayuda para esa comunidad si no conoce su existencia. Escribí tweets sobre la guía, compartila con tus amigos y compañeros de trabajo. Cada comentario, sugerencia u opinión hace que la guía sea un poco mejor. Y lo que más queremos que la mejor guía posible, ¿verdad?
Saludos,
Bozhidar