Mejora del proceso de programación personal mediante Sensei

Publicado el 07 de diciembre de 2020
por Alan Richardson
ESTUDIO DE CASO

Mejora del proceso de programación personal mediante Sensei

Publicado el 07 de diciembre de 2020
por Alan Richardson
Ver recurso
Ver recurso

Para este post, he recreado un enfoque de codificación "malo" que utilicé cuando estaba aprendiendo JUnit, y demostraré cómo convertir el patrón "malo" en un patrón de codificación acordado, y "mejor", utilizando Sensei.

Cuando estaba aprendiendo JUnit, sólo podía retener una cantidad de cosas en mi cabeza en un momento dado. Constantemente olvidaba cómo omitir las pruebas cuando no funcionaban.

Si trabajamos en equipo, podemos utilizar las revisiones de código en los pull requests para ayudar a reforzar los estilos de codificación. Y podemos acortar el ciclo de retroalimentación cuando programamos en pareja con un programador más experimentado.

También podemos aumentar nuestro proceso con herramientas y hacer que éstas nos indiquen que hagamos lo correcto. Thoughtworks lo describió como "herramientas en lugar de reglas", en su lista de Technology Radar para Sensei, para: "facilitar que se haga lo correcto en lugar de aplicar normas y procedimientos de gobernanza tipo lista de control"


Desactivación de una prueba JUnit


Lo ideal sería, como todos sabemos, utilizar la anotación @Disabled y escribir:

  @Disabled
  @Test
  void canWeAddTwoNumbers(){
        Assertions.fail("this test was skipped and should not run");
   }

Pero, al aprender, tuve que entrenarme para usar @Disabled.

Cuando me olvidaba de cómo desactivar un método de prueba, eliminaba la anotación @Test y cambiaba el nombre de la prueba:

class SkipThisTest {
    void SKIPTHIScanWeAddTwoNumbers(){
        Assertions.fail("this test was skipped and should not run");
    }   
}


No era bueno, pero hacía el trabajo. No tenía algo como Sensei para ayudarme a recordar y entonces caí en el uso de patrones de codificación pobres.

Las tareas que he asumido para este puesto son:

  • Cree una regla que encuentre los métodos que han sido "omitidos" o "deshabilitados" cambiando el nombre del método.
  • Crea un QuickFix para renombrar el método y añadir una anotación @Test y @Disabled.


Ajustes de la receta

El primer paso que doy con Sensei es "añadir nueva receta" y buscar el patrón de codificación sobre el que quiero que actúe la receta.

Nombre: JUnit: Hacer @Disabled @Test de SKIPTHIS

Descripción breve: Deje de nombrar los métodos SKIPTHIS, utilice en su lugar @Disabled @Test


Y mi búsqueda es muy sencilla. Utilizo una regex básica para que coincida con el nombre del método.

búsqueda:
método:
nombre:
coincide: "SKIPTHIS.*"


Ajustes de la receta para omitir una prueba


Ajustes de QuickFix

El QuickFix es un poco más complicado porque va a reescribir el código, y voy a utilizar algunos pasos para lograr mi código final.

Quiero hacerlo:

  • añadir una anotación @Test al método
  • añadir una anotación @Disabled al método
  • modificar el nombre del método

Añadir las anotaciones es bastante simple usando el arreglo addAnnotation. Si utilizo un nombre completo para la anotación, Sensei añadirá automáticamente las importaciones por mí.

availableFixes:
- name: "Add @Disabled and @Test Annotation"
  acciones:
- addAnnotation:
annotation: "@org.junit.jupiter.api.Test"
- addAnnotation:
annotation: "@org.junit.jupiter.api.Disabled"


El renombramiento real parece un poco más complicado, pero sólo estoy usando un reemplazo regex, y la forma genérica de hacer esto con Sensei es usar sed en una acción de reescritura.

Como las acciones de reescritura son plantillas de Mustache, Sensei tiene algunas extensiones funcionales en el mecanismo de plantillas. Una función se representa con {{#...}} así que para sed la función es {{#sed}}. La función toma dos argumentos separados por comas.

El primer argumento es la declaración sed:

  • s/(.*) SKIPTHIS(.*)/$1 $2/

El segundo argumento es el String al que aplicar la sentencia sed, que en este caso es el propio método, y que se representa en las variables Mustache como:

  • {{{.}}}

Dándome la acción de reescribir:

 - Reescritura:

       to: "{{#sed}}s/(.*) SKIPTHIS(.*)/$1 $2/,{{{.}}}{{/sed}}"



La implementación de sed requiere que cuando los argumentos mismos contengan comas, se envuelvan con {{#encodeString}} y {{/encodeString}} - Por ejemplo, {{#encodeString}}{{{.}}}{{/encodeString}}

Receta inversa

Dado que se trata de un ejemplo, y es posible que queramos utilizarlo en demostraciones, quería explorar cómo revertir el cambio anterior utilizando una receta de Sensei .

Pensando bien quiero encontrar un método anotado con @Disabled pero sólo en la clase SkipThisTest donde hago la demostración:

Nombre: JUnit: demo en SkipThisTest eliminar @Disabled y volver a SKIPTHIS

Descripción breve: eliminar @Disabled y volver a SKIPTHIS para fines de demostración en el proyecto

Nivel: advertencia


La búsqueda de configuraciones de recetas es muy simple, ya que coincide con la anotación en una clase específica.

búsqueda:
método:
anotación:
tipo: "Disabled"
in:
class:
name: "SkipThisTest"


Para evitar que el código se vea como un error, definí la configuración general de la receta como una Advertencia. Las advertencias se muestran con resaltados en el código y no hace que el código parezca que tiene un problema mayor.

Para el Quick fix, ya que hemos emparejado el método, utilizo la acción de reescritura y relleno la plantilla utilizando las variables.

availableFixes:
- name: "Eliminar Desactivado y cambiar el nombre a SKIPTHIS..."
  actions:
  - rewrite:
      to: "{{{ returnTypeElement }}} SKIPTHIS{{{ nameIdentifier }}}{{{ parameterList\
        \ }}}{{{ body }}}"


Agrego todas las variables excepto el modificador (ya que quiero deshacerme de las anotaciones) y añado el texto SKIPTHIS en la plantilla.

Este arreglo tiene la debilidad de que al eliminar los modificadores, elimino también cualquier otra anotación.


Añadir otra acción

Puedo añadir otro arreglo con nombre, para que me dé una opción cuando se use el alt+enter para mostrar el QuickFix.

availableFixes:
- name: "Eliminar Desactivado y cambiar el nombre a SKIPTHIS..."
  actions:
  - rewrite:
      to: "{{{ returnTypeElement }}} SKIPTHIS{{{ nameIdentifier }}}{{{ parameterList\
        \ }}}{{{ body }}}"
      target: "self"
- name: "Remove Disabled, keep other annotations, and rename to SKIPTHIS..."
  actions:
  - rewrite:
      to: "{{#sed}}s/(@Disabled\n.*@Test)//,{{{ modifierList }}}{{/sed}}\n\
        {{{ returnTypeElement }}} SKIPTHIS{{{ nameIdentifier }}}{{{ parameterList\
        \ }}}{{{ body }}}"
      target: "self"


Aquí, he añadido una línea adicional en el nuevo Quick Fix.

{{sed}}s/(@Disabled\n.*@Test)//,{{modifierList}} {{sed}}


Esto toma la lista de modificadores, la codifica como una cadena, luego utiliza sed para eliminar la línea con @Disabled de la cadena, pero deja todas las demás líneas del modificador, es decir, deja todas las demás anotaciones.

NOTA: Recuerde añadir el "," en el sed, de lo contrario verá un comentario añadido a su vista previa. Así es como Sensei le avisa de los errores de sintaxis en el comando sed.

/* e.g: {{#sed}}s/all/world/,helloall{{/sed}} */


Llamadas sed anidadas

Tuve la suerte de poder coincidir con @Disabled y @Test en una sola búsqueda y reemplazo.

Si el código es más complicado y quiero tener una secuencia de comandos sed entonces puedo hacerlo anidando:

{{#sed}}s/@Test//,{{#sed}}s/@Disabled\n//,{{{ modifierList }}}{{/sed}}{{/sed}}


En el ejemplo anterior, aplico el reemplazo @Test a los resultados de aplicar el reemplazo @Disabled en el {{{ modifierList }}}.


Resumen

sed es una forma muy flexible de lograr la reescritura de código y es posible anidar las llamadas a la función sed para obtener condiciones de reescritura complicadas.

Este tipo de recetas suelen ser temporales porque las utilizamos para mejorar nuestro proceso de programación, y una vez que hayamos creado la memoria muscular y ya no utilicemos el mal patrón de programación podemos eliminarlo o desactivarlo en el Libro de Cocina.


---

Puedes instalar Sensei desde IntelliJ usando "Preferences \ Plugins" (Mac) o "Settings \ Plugins" (Windows) y luego sólo busca "sensei secure code".
Todo el código de esta entrada del blog se puede encontrar en GitHub en el módulo `junitexamples` de nuestro repositorio de ejemplos del blog https://github.com/SecureCodeWarrior/sensei-blog-examples

Ver recurso
Ver recurso

Autor

Alan Richardson

¿Quieres más?

Sumérjase en nuestras últimas ideas sobre codificación segura en el blog.

Nuestra amplia biblioteca de recursos tiene como objetivo potenciar el enfoque humano de la mejora de la codificación segura.

Ver blog
¿Quieres más?

Obtenga las últimas investigaciones sobre la seguridad impulsada por los desarrolladores

Nuestra amplia biblioteca de recursos está repleta de recursos útiles, desde libros blancos hasta seminarios web, que le ayudarán a iniciarse en la codificación segura orientada a los desarrolladores. Explórela ahora.

Centro de recursos

Mejora del proceso de programación personal mediante Sensei

Publicado el 07 de diciembre de 2020
Por Alan Richardson

Para este post, he recreado un enfoque de codificación "malo" que utilicé cuando estaba aprendiendo JUnit, y demostraré cómo convertir el patrón "malo" en un patrón de codificación acordado, y "mejor", utilizando Sensei.

Cuando estaba aprendiendo JUnit, sólo podía retener una cantidad de cosas en mi cabeza en un momento dado. Constantemente olvidaba cómo omitir las pruebas cuando no funcionaban.

Si trabajamos en equipo, podemos utilizar las revisiones de código en los pull requests para ayudar a reforzar los estilos de codificación. Y podemos acortar el ciclo de retroalimentación cuando programamos en pareja con un programador más experimentado.

También podemos aumentar nuestro proceso con herramientas y hacer que éstas nos indiquen que hagamos lo correcto. Thoughtworks lo describió como "herramientas en lugar de reglas", en su lista de Technology Radar para Sensei, para: "facilitar que se haga lo correcto en lugar de aplicar normas y procedimientos de gobernanza tipo lista de control"


Desactivación de una prueba JUnit


Lo ideal sería, como todos sabemos, utilizar la anotación @Disabled y escribir:

  @Disabled
  @Test
  void canWeAddTwoNumbers(){
        Assertions.fail("this test was skipped and should not run");
   }

Pero, al aprender, tuve que entrenarme para usar @Disabled.

Cuando me olvidaba de cómo desactivar un método de prueba, eliminaba la anotación @Test y cambiaba el nombre de la prueba:

class SkipThisTest {
    void SKIPTHIScanWeAddTwoNumbers(){
        Assertions.fail("this test was skipped and should not run");
    }   
}


No era bueno, pero hacía el trabajo. No tenía algo como Sensei para ayudarme a recordar y entonces caí en el uso de patrones de codificación pobres.

Las tareas que he asumido para este puesto son:

  • Cree una regla que encuentre los métodos que han sido "omitidos" o "deshabilitados" cambiando el nombre del método.
  • Crea un QuickFix para renombrar el método y añadir una anotación @Test y @Disabled.


Ajustes de la receta

El primer paso que doy con Sensei es "añadir nueva receta" y buscar el patrón de codificación sobre el que quiero que actúe la receta.

Nombre: JUnit: Hacer @Disabled @Test de SKIPTHIS

Descripción breve: Deje de nombrar los métodos SKIPTHIS, utilice en su lugar @Disabled @Test


Y mi búsqueda es muy sencilla. Utilizo una regex básica para que coincida con el nombre del método.

búsqueda:
método:
nombre:
coincide: "SKIPTHIS.*"


Ajustes de la receta para omitir una prueba


Ajustes de QuickFix

El QuickFix es un poco más complicado porque va a reescribir el código, y voy a utilizar algunos pasos para lograr mi código final.

Quiero hacerlo:

  • añadir una anotación @Test al método
  • añadir una anotación @Disabled al método
  • modificar el nombre del método

Añadir las anotaciones es bastante simple usando el arreglo addAnnotation. Si utilizo un nombre completo para la anotación, Sensei añadirá automáticamente las importaciones por mí.

availableFixes:
- name: "Add @Disabled and @Test Annotation"
  acciones:
- addAnnotation:
annotation: "@org.junit.jupiter.api.Test"
- addAnnotation:
annotation: "@org.junit.jupiter.api.Disabled"


El renombramiento real parece un poco más complicado, pero sólo estoy usando un reemplazo regex, y la forma genérica de hacer esto con Sensei es usar sed en una acción de reescritura.

Como las acciones de reescritura son plantillas de Mustache, Sensei tiene algunas extensiones funcionales en el mecanismo de plantillas. Una función se representa con {{#...}} así que para sed la función es {{#sed}}. La función toma dos argumentos separados por comas.

El primer argumento es la declaración sed:

  • s/(.*) SKIPTHIS(.*)/$1 $2/

El segundo argumento es el String al que aplicar la sentencia sed, que en este caso es el propio método, y que se representa en las variables Mustache como:

  • {{{.}}}

Dándome la acción de reescribir:

 - Reescritura:

       to: "{{#sed}}s/(.*) SKIPTHIS(.*)/$1 $2/,{{{.}}}{{/sed}}"



La implementación de sed requiere que cuando los argumentos mismos contengan comas, se envuelvan con {{#encodeString}} y {{/encodeString}} - Por ejemplo, {{#encodeString}}{{{.}}}{{/encodeString}}

Receta inversa

Dado que se trata de un ejemplo, y es posible que queramos utilizarlo en demostraciones, quería explorar cómo revertir el cambio anterior utilizando una receta de Sensei .

Pensando bien quiero encontrar un método anotado con @Disabled pero sólo en la clase SkipThisTest donde hago la demostración:

Nombre: JUnit: demo en SkipThisTest eliminar @Disabled y volver a SKIPTHIS

Descripción breve: eliminar @Disabled y volver a SKIPTHIS para fines de demostración en el proyecto

Nivel: advertencia


La búsqueda de configuraciones de recetas es muy simple, ya que coincide con la anotación en una clase específica.

búsqueda:
método:
anotación:
tipo: "Disabled"
in:
class:
name: "SkipThisTest"


Para evitar que el código se vea como un error, definí la configuración general de la receta como una Advertencia. Las advertencias se muestran con resaltados en el código y no hace que el código parezca que tiene un problema mayor.

Para el Quick fix, ya que hemos emparejado el método, utilizo la acción de reescritura y relleno la plantilla utilizando las variables.

availableFixes:
- name: "Eliminar Desactivado y cambiar el nombre a SKIPTHIS..."
  actions:
  - rewrite:
      to: "{{{ returnTypeElement }}} SKIPTHIS{{{ nameIdentifier }}}{{{ parameterList\
        \ }}}{{{ body }}}"


Agrego todas las variables excepto el modificador (ya que quiero deshacerme de las anotaciones) y añado el texto SKIPTHIS en la plantilla.

Este arreglo tiene la debilidad de que al eliminar los modificadores, elimino también cualquier otra anotación.


Añadir otra acción

Puedo añadir otro arreglo con nombre, para que me dé una opción cuando se use el alt+enter para mostrar el QuickFix.

availableFixes:
- name: "Eliminar Desactivado y cambiar el nombre a SKIPTHIS..."
  actions:
  - rewrite:
      to: "{{{ returnTypeElement }}} SKIPTHIS{{{ nameIdentifier }}}{{{ parameterList\
        \ }}}{{{ body }}}"
      target: "self"
- name: "Remove Disabled, keep other annotations, and rename to SKIPTHIS..."
  actions:
  - rewrite:
      to: "{{#sed}}s/(@Disabled\n.*@Test)//,{{{ modifierList }}}{{/sed}}\n\
        {{{ returnTypeElement }}} SKIPTHIS{{{ nameIdentifier }}}{{{ parameterList\
        \ }}}{{{ body }}}"
      target: "self"


Aquí, he añadido una línea adicional en el nuevo Quick Fix.

{{sed}}s/(@Disabled\n.*@Test)//,{{modifierList}} {{sed}}


Esto toma la lista de modificadores, la codifica como una cadena, luego utiliza sed para eliminar la línea con @Disabled de la cadena, pero deja todas las demás líneas del modificador, es decir, deja todas las demás anotaciones.

NOTA: Recuerde añadir el "," en el sed, de lo contrario verá un comentario añadido a su vista previa. Así es como Sensei le avisa de los errores de sintaxis en el comando sed.

/* e.g: {{#sed}}s/all/world/,helloall{{/sed}} */


Llamadas sed anidadas

Tuve la suerte de poder coincidir con @Disabled y @Test en una sola búsqueda y reemplazo.

Si el código es más complicado y quiero tener una secuencia de comandos sed entonces puedo hacerlo anidando:

{{#sed}}s/@Test//,{{#sed}}s/@Disabled\n//,{{{ modifierList }}}{{/sed}}{{/sed}}


En el ejemplo anterior, aplico el reemplazo @Test a los resultados de aplicar el reemplazo @Disabled en el {{{ modifierList }}}.


Resumen

sed es una forma muy flexible de lograr la reescritura de código y es posible anidar las llamadas a la función sed para obtener condiciones de reescritura complicadas.

Este tipo de recetas suelen ser temporales porque las utilizamos para mejorar nuestro proceso de programación, y una vez que hayamos creado la memoria muscular y ya no utilicemos el mal patrón de programación podemos eliminarlo o desactivarlo en el Libro de Cocina.


---

Puedes instalar Sensei desde IntelliJ usando "Preferences \ Plugins" (Mac) o "Settings \ Plugins" (Windows) y luego sólo busca "sensei secure code".
Todo el código de esta entrada del blog se puede encontrar en GitHub en el módulo `junitexamples` de nuestro repositorio de ejemplos del blog https://github.com/SecureCodeWarrior/sensei-blog-examples

Nos gustaría contar con su permiso para enviarle información sobre nuestros productos y/o temas relacionados con la codificación segura. Siempre trataremos sus datos personales con el máximo cuidado y nunca los venderemos a otras empresas con fines de marketing.

Enviar
Para enviar el formulario, habilite las cookies "Analytics". Siéntase libre de desactivarlas de nuevo una vez que haya terminado.