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."
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.
@Trasactional
void 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.