AUTONOMATIZACIÓN CON POKA-YOKES ORIENTADOS A ASPECTOS


Rafael Luque

Madrid GUG, Feb 2014

HISTORIA DE UN BUG real



class BuggyTagLib {    def applicationData = { attrs ->        ...        out << "<div class='u'>${campaign.betaTesters.size()}</div>"        ...    }}

LAS COLECCIONES GORM SON ESPADAS DE DOBLE FILO


  • Mejoran el rendimiento cargándose perezosamente sin acceder a la base de datos.
  • Pero una vez son accedidas se cargan completamente.
  • N+1 queries.

SOLUCIón 1


class Campaign {

static hasMany = [ betaTesters: CampaignUser ]

int betaTestersCount

static mapping = {
betaTestersCount formula: '(select count(*) from campaign_betatesters b where b.campaign_id = id)'
}

}


SOLUCIón 2


class Campaign {

static hasMany = [ betaTesters: CampaignUser ]

int getBetaTestersCount() {
betaTesters == null ? 0 : withSession {
it.createFilter(betaTesters, 'select count(*)').uniqueResult()
}
}

}


SOLUCIÓN 3

class BootStrap {
def init = { injectMappedCollectionCountMethod() }
private void injectMappedCollectionCountMethod() {
def classesWithCollections = grailsApplication.domainClasses.findAll { it.hasProperty('hasMany') }.collect { it.clazz }
classesWithCollections.each {
it.hasMany.each { colName, colClass ->
String countMethodName = 'get' + colClass.subString(0,1).toUppercase() + colClass.subString(1) + 'Count'
it.metaClass."$countMethodName" << { ->
withSession { session ->
delegate."$colName" == null ? 0 :
session.createFilter(delegate."$colName", 'select count(*)').uniqueResult()
}
}
}
}


SOMOS LO QUE LEEMOS



¿SOLUCIONAR EL PROBLEMA ES SUFICIENTE PARA SOLUCIONAR EL PROBLEMA?

  • ¿Qué ocurre con los nuevos miembros del equipo?
  • ¿Y con el resto de equipos de la organización?
  • ¿Y con otros miembros de la comunidad Grails?

CÓMO GESTIONAMOS LOS ERRORES EN EL DESARROLLO DE SOFTWARE

  • Buscando los culpables ?
  • Emails al equipo de desarrollo ?
  • Wikis/Blogs internos ?
  • Reuniones con el equipo ?
  • Catálogos de gotchas/pitfalls ?

EL ROL ACTUAL DE LOS ERRORES DE SOFTWARE


  • Es algo embarazoso de lo que no se habla mucho.
  • Por lo tanto no aprendemos de ello.
  • A lo sumo a nivel individual, pero rara vez como organización o como profesión.
  • En el mejor de los casos se añaden algunos tests y se documenta el problema: wikis, blogs, etc.

APRENDAMOS DE LAS ingenierías más maduras


  • Culturas de aprendizaje del fracaso (H. Petroski):
    • Catástrofes aéreas.
    • Fallos de ingeniería civil.
  • Se puede aprender más de los fracasos que de los éxitos, pero hay que reflexionar sobre la causa raíz y sobre el proceso de desarrollo.

FALLO VS DEFECTO


  • El fallo es humano e inevitable.
  • Los defectos ocurren cuando esos fallos llegan al cliente (producción).
  • Es posible mejorar el diseño de nuestros productos hasta conseguir procesos cero defectos.
  • Es el deber de todo ingeniero.
  • Dejemos de justificar nuestros defectos porque somos "arquitectos", "artesanos", "jardineros", etc. 
  • ¡¡Convirtámonos en ingenieros de software!!

DEL DRY AL DRYF


DON'T REPEAT YOUR FAILURES



responde a LAS PREGUNTAS CORRECTAS


  • ¿Cómo podría haber detectado el fallo más rápido?
  • ¿Cómo puede el sistema ser más resistente a este tipo de fallos?  ("Resiliencia")
  • ¿Puedo mejorar el proceso de desarrollo para que este error  no vuelva a producirse?

UN POKA-YOKE NO ES UN POKEMON



POKA-YOKE


  • Término japonés que significa a prueba de fallos.
  • Introducido por Shigeo Shingo en los 60s como parte del TPS.
 

POKA-YOKE


Puede tratarse de cualquier restricción (por simple que sea) incorporada al diseño de un proceso para evitar errores o detectarlos lo antes posible.


POKA-YOKE



POKA-YOKE


POKA-YOKE


POKA-YOKE


JIDOKA

  • Lean management = Muda + Jidoka.
  • Muda es la eliminiación de desperdicio en el proceso de producción..
  • Jidoka es la introducción de la calidad en el proceso de producción.
  • Los principios ágiles han cubierto Muda:
    • Refactoring
    • YAGNI
    • DRY
    • Simplest that works.

JIDOKA EN EL DESARROLLO DE SOFTWARE


  • Jidoka no es sólo detectar el fallo y corregirlo. 
  • Consiste en corregir el fallo, investigar la causa raíz y eliminarla para siempre del proceso.
  • Las herramientas habituales no son suficientes:
    • Pruebas automáticas.
    • Integración continua.




AUTONOMATIZACIÓN


  • Automatización con un toque humano.
  • Característica incorporada en el diseño de un máquina/proceso para aplicar el principio Jidoka.


PROPUESTA


  • Herramienta para facilitar la aplicación de Jidoka en el desarrollo de las aplicaciones Grails mediante la creación de un catálogo de Poka-Yokes por parte de la comunidad.
  • Cada error se convierte en una oportunidad para mejorar, tanto la aplicación actual, como las futuras.
  • Tanto nuestros equipos, como toda la comunidad Grails.

ANÁLISIS DINÁMICO VS ANÁLISIS ESTÁTICO

  • Herramientas similares:
    • Lint
    • JSLint
    • PMD
    • FindBugs
    • Codenarc
  • Útiles para comprobaciones estilísticas y sintácticas.
  • Herramientas de análisis estático limitadas al no disponer de información sobre el contexto de ejecución.

POKA-YOKES como ASPECTOS


  • Cross-cutting concerns:
    • SRP
    • OCP
  • AOP permite implementar análisis dinámico.
  • Distintos modelos de weaving:
    • Source-code weaving.
    • Byte-code weaving.
    • Load-time weaving.

AOP VS METAPROGRAMMING


 "AOP was invented to be a move away from explicit metaprogramming to a direct semantics for some of the kinds of problems metaprogramming could be used for. It helps to raise abstraction level."
"It's Not Metaprogramming" --- Gregor Kiczales

AOP VS METAPROGRAMMING


Y Gregor Kiczales no es sospechoso ;-)


EJEMPLO 1

GormCollectionSize.aj
import org.codehaus.groovy.runtime.callsite.Callsite;import org.hibernate.collection.PersistentCollection;
public aspect GormCollectionSize extends AbstractPokaYoke { public pointcut violation(): sizeOnPersistentCollection(Object, Callsite);
public sizeOnPersistentCollection(Object receiver, Callsite callsite): GroovyPointCuts.groovyCall(receiver, callsite) && if ((receiver instanceof PersistentCollection) && ("size".equals(callsite.getName())));}

xpi = ASPECT PROGRAMMING INTERFACE


GroovyPointCuts.aj

public aspect GroovyPointCuts {
public pointcut groovyCall(Object receiver, CallSite callsite): call(public Object CallSite.call*(..)) && target(callsite) && args(receiver);
}

ABSTRACTPOKAYOKE


Advice implementation & Method Factory pattern:

public abstract aspect AbstractPokaYoke implements PokaYoke {
public abstract pointcut violation(); public abstract String getDescription();
before(): violation() { warn(thisJoinPoint); }
...}

EJEMPLO 2

Unintentionally bypassing the bean proxy
Be careful when calling an annotated method within a service when the annotation settings are different. 
Because you're underneath the proxy it's a direct method call, and any checks that the proxy would have done will be bypassed.
@Trasactionalvoid someMethod() {  anotherMethod()}
@Transactional(propagation=Propagation.REQUIRES_NEW)void anotherMethod() { ...}

EJEMPLO 2

ServiceTransactionalServiceBypassed.aj
public aspect ServiceTransactionalServiceBypassed extends AbstractPokaYoke {  public pointcut violation():     proxyBypassed(Object, Transactional, Object, Transactional);
public pointcut proxyByPassed(Object caller, Transactional callerAnnotation, Object callee, Transactional calleeAnnotation): cflowbelow(annotatedPublicServiceMethod(caller, callerAnnotation)) && annotatedPublicServiceMethod(callee, calleeAnnotation) && if ((caller.getClass() == callee.getClass()) && (!callerAnnotation.equals(calleeAnnotation)) && (!isCalledThroughProxy())); ...}

EJEMPLO 2

ServiceTransactionalProxyBypassed.aj (cont.)




public boolean isCalledThroughProxy() {  StackTraceElement[] elements = Thread.currentThread().getStackTrace();  StackTraceElement callerElement = elements[3];  return ClassUtils.isCglibProxyClassName(callerElement.getClassName());}
public pointcut annotatedPublicServiceMethod(    Object receiver, Transactional ann):  GrailsPointCuts.publicServiceMethod() && this(receiver) && @annotation(ann);

GRAILS XPI

GrailsPointCuts.aj
public aspect GrailsPointCuts {
public pointcut inService(): within(..*Service);
public pointcut publicServiceMethod():  inService() && execution(public * *.*(..));
...
}

EJEMPLO 3

Policy enforcement

GormLayeringViolation.aj
public aspect GormLayeringViolation extends AbstractPokaYoke {  public pointcut violation():    GrailsPointCuts.isDomain() &&    GormPointCuts.gormMethod() &&    !cflowbelow(GrailsPointCuts.isService());}

XPI

GrailsPointCuts.aj



GormPointCuts.aj
 public pointcut gormMethod():   saveMethod() || ...;
public pointcut saveMethod(): execution(public Object save()) || execution(public Object save(java.util.Map)) || execution(public Object save(boolean));
 public pointcut isDomain():   @within(grails.persistence.Entity);

POKA-YOKE ADVICES


  • Configurable:
    • Logging.
    • Throw RuntimeException.
    • JMX MBean -> Collectd -> Graphite dashboard.
    • Sugerencias?

GRAILS POKA-YOKES PLUGIN


  • Sólo es un experimento
  • No-tests-no-docs mode. Usa bajo tu responsabilidad.

 https://github.com/osoco/grails-poka-yokes-plugin