ruby - Tamaño de bloque de línea Rubocop 25 y pruebas RSpec
(2)
Una actualización reciente de Rubocop puso en juego una nueva regla, que los bloques no deberían tener más de 25 líneas. No estoy seguro de la razón para ello, porque no figura en la guía de estilo Ruby.
Solía ser que todos los policías se basaban en The Ruby Style Guide, y RuboCop era una forma de adherirse a las prácticas establecidas por la comunidad.
La dirección ha cambiado desde entonces, y el alcance de RuboCop se ha ampliado para ayudar a los desarrolladores a garantizar la coherencia en sus bases de código en general. Esto ha llevado a dos cosas:
- Los policías (incluso aquellos basados en The Ruby Style Guide) ahora son en su mayoría configurables.
- Hay policías para cosas que no se mencionan en la Guía de estilo Ruby, pero que aún son útiles para garantizar la coherencia en un proyecto.
Este policía cae en la segunda categoría.
¿RSpec está utilizando un enfoque ahora desacreditado para el diseño del código, y qué opciones razonables tengo para reducir los tamaños de bloque en nuestras pruebas de RSpec?
La respuesta corta es no. Las DSL siguen siendo geniales. :-)
Este policía está dirigido a un bloque grande en el sentido de programación imperativo.
Como guía general, no se aplica a las DSL, que a menudo son declarativas.
Por ejemplo, tener un archivo
routes.rb
largo en Rails es perfectamente benigno.
Es solo el resultado natural de una aplicación grande, en lugar de una violación de estilo.
(Y tener muchas pruebas es puramente increíble).
Ahora, RuboCop es bastante inteligente, pero no sabe qué es un DSL y no, por lo que no podemos ignorarlos automáticamente. Se podría argumentar que podríamos excluir los métodos de entrada DSL de marcos populares, como las rutas Rails y las especificaciones RSpec. Las razones para no hacerlo son principalmente:
- Falsos negativos. Cualquier clase puede implementar un método, tomando un bloque, con el mismo nombre.
-
RuboCop es una herramienta de análisis de Ruby, y realmente no debería saber sobre bibliotecas externas.
(Excluir el directorio
/spec
es una cortesía hasta que tengamos un sistema de extensión adecuado, y esto puede ser manejado por la gemarubocop-rspec
).
Quiero decir, eso es factible, pero convertir grupos de ejemplos de especificaciones en funciones auxiliares de esta manera parece ser lo opuesto al enfoque legible fomentado por el diseño de RSpec.
La conclusión es: RuboCop está ahí para ayudarnos a escribir un mejor código. Si el diseño de nuestra aplicación es sólido y nos encontramos haciendo cosas menos legibles simplemente para complacer a RuboCop, entonces debemos filtrar, configurar o deshabilitar al policía. :-)
En respuesta, simplemente hemos establecido la regla de tamaño de bloque de Rubocop en un umbral alto. Pero eso me hace preguntarme: ¿qué me estoy perdiendo?
Esta es una herramienta bastante contundente y, como estás insinuando, probablemente tendrás algunos falsos negativos debido a eso. Hay dos tipos de falsos positivos para este policía:
- Archivos que contienen DSL puramente declarativos, por ejemplo, rutas Rails, especificaciones RSpec.
-
Los archivos que tienen un DSL declarativo mezclado en su mayoría código imperativo, por ejemplo, una declaración de máquina de estado de
aasm
en un modelo Rails.
En el primer caso, la mejor solución es excluir el archivo o directorio, y en el segundo usar una desactivación en línea.
En su caso, debe actualizar su
.rubocop.yml
con:
Metrics/BlockLength:
Exclude:
- ''Rakefile''
- ''**/*.rake''
- ''test/**/*.rb''
(Tenga en cuenta que debe repetir las exclusiones básicas de la configuración predeterminada, ya que la lista se sobrescribirá).
Una prueba de unidad RSpec típica hace un uso extensivo de bloques Ruby anidados para estructurar el código y hacer uso de "magia" DSL para que las especificaciones se lean como declaraciones BDD:
describe Foo do
context "with a bar" do
before :each do
subject { Foo.new().add_bar }
end
it "looks like a baz" do
expect # etc
En una especificación ideal, cada ejemplo puede ser relativamente corto y preciso.
Sin embargo, parece habitual que los bloques externos crezcan hasta 100 líneas más, porque la estructura RSpec funciona de esta manera y no toma muchos ejemplos de especificaciones, cada uno de los cuales puede tener algunas líneas de configuración específica, para poder
describe
bloques que son del mismo tamaño o mayor que el código para el tema que se describe.
Una actualización reciente de Rubocop puso en juego una nueva regla, que los bloques no deberían tener más de 25 líneas.
No estoy seguro de la razón para ello, porque no figura en la
guía de estilo Ruby
.
Puedo ver por qué podría ser algo bueno y agregarlo al conjunto de reglas predeterminado.
Sin embargo, después de la actualización, nuestra prueba de Rubocop falla varias veces con mensajes como
tests/component_spec.rb:151:3: C: Block has too many lines. [68/25]
tests/component_spec.rb:151:3: C: Block has too many lines. [68/25]
Con herramientas de métricas de código como Rubocop, me gusta tener una política de "Usar los valores predeterminados, vincular a la guía de estilo, hacer el trabajo". (principalmente porque debatir pestañas frente a espacios y otras minucias está perdiendo el tiempo, y el IME nunca se resuelve) Aquí, eso claramente no es posible, dos de nuestras herramientas centrales de calidad de datos no están de acuerdo con el enfoque de diseño de código, o al menos así es como interpreto los resultados , No veo nada intrínsecamente incorrecto en cómo hemos escrito las especificaciones.
En respuesta, simplemente hemos establecido la regla de tamaño de bloque de Rubocop en un umbral alto. Pero eso me hace preguntarme: ¿qué me estoy perdiendo? ¿RSpec está utilizando un enfoque ahora desacreditado para el diseño del código, y qué opciones razonables tengo para reducir los tamaños de bloque en nuestras pruebas de RSpec? Puedo ver formas de reestructurar el código para evitar grandes bloques, pero son, sin excepción, hacks feos destinados exclusivamente a cumplir con la regla de Rubocop, por ejemplo, dividir todos los bloques en funciones auxiliares:
def looks_like_a_baz
it "looks like a baz" do
expect # etc
end
end
def bar_context
context "with a bar" do
before :each do
subject { Foo.new().add_bar }
end
looks_like_a_baz
end
end
describe Foo do
bar_context
# etc
. . . Quiero decir, eso es factible, pero convertir grupos de ejemplos de especificaciones en funciones auxiliares de esta manera parece ser lo opuesto al enfoque legible fomentado por el diseño de RSpec.
¿Hay algo más que pueda hacer aparte de encontrar formas de ignorarlo?
La pregunta existente más cercana que pude encontrar sobre este tema aquí fue RSpec & Rubocop / Ruby Style Guide y esto parecía solucionable editando plantillas de prueba.
Si un bloque específico suele ser demasiado largo, lo especifico en lugar de los archivos
Metrics/BlockLength:
ExcludedMethods: [''describe'', ''context'']