técnicas y herramientas para que la computadora haga más y el programador menos
TRANSCRIPT
Técnicas y Herramientas para que la Computadora haga más y el Programador menos
Lic. Hernán A. Wilkinson
En Mercap desarrollamos Productos (dominio financiero)
Tiempo de vida > 15 años Muy importante la calidad Muy importante la funcionalidad diferencial
Nuevos Requerimientos Funcionales Mejoras en el Diseño/Implementación Actual Rotación de Personal
Peter Naur Programming as a Theory Building NO QUEREMOS QUE SEA UN PROBLEMA
Evolución de Código
¡Lograr que la computadora trabaje por nosotros!
Para eso están… para hacer el trabajo “automatizable”
26
En su libro “Los ocho pecados mortales de la humanidad
civilizada”
“si no reflexinamos…”
“viviremos una eterna niñez…”
29
¡Debemos hacerlo conociendo nuestras raíces, nuestra historia!
LISP – ¡50 años!
Para no cometer los mismos errores, para estar por arriba de la media
Paremos la pelota
¿Qué tareas puedo automatizar?
¿Qué tareas son repetitivas, propensas a error, etc?
Qué vamos a ver
Testing Automatizado Objetos Auto-Defendibles Automatización de Inspección de Código Automatización de Excepciones a la regla Asegurar Calidad en Puntos Críticos Integrar Código Automáticamente Migrar Datos (objetos) Automáticamente
¿Para qué sirve?
Ayuda a tener confianza sobre los cambios a realizar
Delega en la computadora la verificación de lo que estamos haciendo
Los test son especificaciones de cómo debe funcionar el código
Ayuda a…
Entender el problema a partir de ejemplos concretos
Concentrarse en el problema real Minimizar Acoplamiento
¿Como sabemos el “tipo”?
aTransac
aOwnAcc
aThirdAcc
10 pesos
from
to
amount
aTransacaOwnAcc
aThirdAcc
10 pesos
from
to
amount
Compra Venta
typetype
Típica implementación usando If
Transaction>>type
(fromAccount isThirdAccount and: [toAccount isOwnAccount ]) ifTrue: [^’Sell'].(fromAccount isOwnAccount and: [toAccount isThirdAccount]) ifTrue: [^’Buy'].
47
journalEntry
transaction type = ‘Buy’ ifTrue: [ ^self buyJournalEntry ]. transaction type = ‘Sell’ ifTrue: [ ^self sellJournalEntry ].
voucher
transaction type = ‘Buy’ ifTrue: [ ^self buyVoucher]. transaction type = ‘Sell’ ifTrue: [ ^self sellVoucher].
Típico uso del “tipo” con if
type
(fromAccount isThirdAccount and: [toAccount isOwnAccount ]) ifTrue: [^’Sell'].(fromAccount isOwnAccount and: [toAccount isThirdAccount]) ifTrue: [^’Buy'].(fromAccount isOwnAccount and: [toAccount isOwnAccount ]) ifTrue: [^‘Transfer'].
Agrego el nuevo caso
Típica implementación usando If
voucher
transaction type = ‘Buy’ ifTrue: [ ^self buyVoucher]. transaction type = ‘Sell’ ifTrue: [ ^self sellVoucher].
50
journalEntry
transaction type = ‘Buy’ ifTrue: [ ^self buyJournalEntry ]. transaction type = ‘Sell’ ifTrue: [ ^self sellJournalEntry ]. transaction type = ‘Transfer’ ifTrue: [ ^self transferJournalEntry ].
¡En algún lugar nos vamos a olvidar de agregar el if!
Típico uso del “nuevo tipo” con if
¿Cuál es el Problema del if?
La computadora no decide ¡Lo hacemos nosotros! No hay Meta-Información
(información sobre las decisiones de diseño)
¿Qué tal si hacemos que los objetos lo resuelvan?
que la computadora haga más…y el programador menos
Modelemos los Tipos de Transacciones
TransactionType-accepts: aTransaction -canHandle: aTransaction-typeOf: aTransaction
Buy-accepts: aTransaction -canHandle: aTransaction
Sell-accepts: aTransaction -canHandle: aTransaction
Implementación usando Objetos
Transaction>>type
^TransactionType typeOf: self
Transaction>>type
(fromAccount isThirdAccount and: [toAccount isOwnAccount ]) ifTrue: [^’Sell'].(fromAccount isOwnAccount and: [toAccount isThirdAccount]) ifTrue: [^’Buy'].
Sell>>canHandle: aTrx
^aTrx fromAccount isThirdAccount and: [aTrx toAccount isOwnAccount ]
Buy>>canHandle: aTrx
^aTrx fromAccount isOwnAccount and: [aTrx toAccount isThirdAccount ]
Usando el “tipo” con Objetos
Buy-accepts: aTransaction -canHandle: aTransaction
Sell-accepts: aTransaction -canHandle: aTransaction
canHandle: aTransaction canHandle: aTransaction
TransactionType-accepts: aTransaction -canHandle: aTransaction-typeOf: aTransaction
typeOf: aTransaction^aSell
^false ^true
56
journalEntry
^transaction type accept: self
Usando el “tipo” con ObjetosjournalEntry
transaction type = ‘Buy’ ifTrue: [ ^self buyJournalEntry ]. transaction type = ‘Sell’ ifTrue: [ ^self sellJournalEntry ].
visitBuy
^self buyJournalEntry
visitSell
^self sellJournalEntry
57
Usando el “tipo” con Objetos
journalEntry
aTrx
type^aSell
aSell
accept: selfvisitSell
^aSellJournalEntry voucher
aTrx
type^aSell
aBuy
accept: selfvisitBuy
^aBuyVoucher¡Son Visitors!
¿Qué pasa ahora si agregamos Transferencia?
TransactionType-accepts: aTransaction -canHandle: aTransaction-typeOf: aTransaction
Buy-accepts: aTransaction -canHandle: aTransaction
Sell-accepts: aTransaction -canHandle: aTransaction
Transfer-accepts: aTransaction -canHandle: aTransaction
61
Agregemos Transferencia
journalEntry
aTrx
type^aTransfer
aTransfer
accept: selfvisitTransfer
^aTransferJournalEntry
journalEntry
aTrx
type^aSell
aTransf
accept: selfvisitTransfer
^aTransferVoucher
¿Nos podemos olvidar de implementar visitTransfer?
Inspecciones Automáticas de Código
Naming conventions: Ortografía, Nombre de módulos, Nombre de Clases, etc.
Diseño: Clases abstractas sin variables de instancia
Arquitectura: Clases de test en aplicaciones de test, mock objects en
aplicaciones de test, inexistencia de referencias desde aplicaciones del modelo a aplicaciones de test, etc.
Implementación: Comentarios de código según convención, categorización de
mensajes, dónde debe estar implementado un mensaje, de dónde se debe enviar un mensaje, etc.
Inspecciones Automáticas de Código
Implementación de Patrones Correcta implementación de Singleton y Visitor
Ambiente de Desarrollo Que todos estén usando los mismos directorios, los mismos
settings del ambiente, etc. Uso correcto de Frameworks
SUnit: Recursos que no referencien a tests, que los tests hagan assert, recursos no cíclicos, etc.
Dependecy Injection: Que todas las dependencias sean iniciliazadas correctamente
Code Coverage: Definición correcta de los métodos a excluir ETC…
Inspecciónes automática de Código
Evita errores comúnes Ayuda a mantener consistente el modelo Controla los errores de programadores
nuevos Asegura calidad de
código
Lo importante
Automatizar la auditoría de todas las desiciones que se tomenDiseño ImplementaciónArquitecturaNomenclaturaUso de otros modelosEtc…
340 chequeos
Implementación de typeOf:Transaction>>type
^TransactionType typeOf: self
TransactionType >>typeOf: aTransaction
^self subclasses detect: [ :aClass | aClass canHandle: aTransaction ]
Reflexionemos un poco…
Reflexionemos un poco…typeOf: aTransaction
^self subclasses detect: [ :aClass | aClass canHandle: aTransaction ]
typeOf: aTransaction | posibleTypes | posibleTypes := self subclasses select: [ :aClass | aClass canHandle: aTransaction ]. posibleTypes isEmpty ifTrue: [ self error: ‘Error de programación’ ].
^posibleTypes first
¿Qué pasa si más de un tipo corresponde a la transacción?
¿Qué pasa si ningún tipo corresponde a la transacción?
posibleTypes size >1 ifTrue: [ self error: ‘Error de programación ].
Reflexionemos un poco más…
¿Hay que escribir siempre lo mismo?¿No es el mismo problema?
¿Necesitaremos hacer esto para otros casos?
typeOf: aTransaction | posibleTypes | posibleTypes := self subclasses select: [ :aClass | aClass canHandle: aTransaction ]. posibleTypes isEmpty ifTrue: [ self error: ‘Error de programación’ ]. posibleTypes size >1 ifTrue: [ self error: ‘Error de programación ].
^posibleTypes first for: aTransaction
¡Reificar el código!typeOf: aTransaction | posibleTypes | posibleTypes := self subclasses select: [ :aClass | aClass canHandle: aTransaction ]. posibleTypes isEmpty ifTrue: [ self error: ‘Error de programación’ ]. posibleTypes size >1 ifTrue: [ self error: ‘Error de programación ].
^posibleTypes first for: aTransaction
SuitableClassSelector-value -for: aSuperClass
Reflexionemos un poco más…
typeOf: aTransaction ^(SuitableClassSelector for: self) value for: aTransaction
typeOf: aMovie ^(SuitableClassSelector for: self) value for: aMovie
typeOf: aCar
^(SuitableClassSelector for: self) value for: aCar
TransactionType:
MovieType:
CarType:
etc…
Usando el “tipo” con ObjetosBuy
-accepts: aTransaction -canHandle: aTransaction Sell
-accepts: aTransaction -canHandle: aTransaction
aSuitableClassFinder
value
aTransaction(sell)
canHandle: aTransaction canHandle: aTransaction
^Sell
TransactionType-accepts: aTransaction -canHandle: aTransaction-typeOf: aTransaction
typeOf: aTransaction^aSell
Transfer-accepts: aTransaction -canHandle: aTransaction
canHandle: aTransaction
^false ^true
^false
¿Qué logramos?
Creamos una nueva construcción “sintáctica” No puede haber “errores” al agregar nuevos
casos puesto que la computadora se encarga de verificarlo
Son objetos (meta), no texto. Podemos ver: Donde se usa Cuanto casos hay Testearlo una sola vez Etc.
Recapitulemos
TDD Self Defense Objects Reificación de
Construciones Sintácticas Inspección Automática de
Código
Falsos Positivos
isEmpty
^self size = 0
Warning: No se debe usar “size = 0” sino “isEmpty”
¡Pero se está implementando “isEmpty”!
Se marca como “error esperado” y se puede seguir usando la herramienta
Ejemplo (modelo open source)
expectedSmallLintFailures ^Array with: ( MCPExpectedSmallLintFailure unoptimizedToDoFailedClass: self failedMethod: 'initializeConnectors' asSymbol reasonDescription: 'No funciona bien con el #to:do: optimizado, ver comentarios en el metodo' definedBy: 'diegof')
expectedSmallLintFailures ^Array with: ( MCPExpectedSmallLintFailure utilityMethodsFailedClass: self failedMethod: #factorForSameYearFrom:to: reasonDescription: 'Por factorizacion‘ definedBy: 'hernan')
Hay un test que verifica que el usuario es válido!
¿Qué ganamos?
Evitar Falsos Positivos constantes que desalientan el uso de la automatización de inspección de código
Se debe asegurar que no haya “errores esperados” que no aplican
Módulo Estable
Módulo Estable
Depende de Depende de
Módulo Estable Módulo Estable
¡100 % de Cobertura!¡No hay errores!
Módulo No Estable Módulo No Estable
¿Qué ganamos?
Test que aseguran que los modulos estables tienen un 100 % de cobertura
No hay errores (detectables automáticamente) Aseguramos calidad para los módulos core Delegar en la computadora dicha verificación
¿Qué ganamos?
Proceso automático No hay posibilidad de errorEs más rápido que hacerlo manualmente
Pasamos de estas varios días integrando a hacerlo en menos de media hora
¿Qué pasa con los refactorings?
ClassA ClassB
m1^ClassA new
Renombra
Error de Integración
Tesis de Diego Tubello para integrar refactorings
automáticamente
Linea Base
Linea A
Linea B
Segunda Versión de Stock
Stock (versión 1)
acindar ‘acindar’name
Stock (versión 2)
acindar ‘acindar’name
dollar
issueCurrency
¿Por qué hay que migrar datos?
¿Por qué cambia el tipo de un campo? ¿Por qué cambia la estructura de una
tabla? ¿Por qué se agregan nuevas tablas?
¡¡POR QUE EVOLUCIONA EL CÓDIGO!! Migrar código y datos!
No sería bueno una herramienta que…
Nos avise cuando agregamos/sacamos una variable de instancia?
Nos diga cuando cambia una jerarquía? Modifique el código de una nueva
versión? ¿No sería bueno que la computadora se
encarge de resolver este problema?
¿Qúe ganamos?
Cambio automatizado de versiones Nos permite tener una única línea de
productos Permite que los clientes tengan la última
versión
99
Camino recorrido Testing automatizado Código auto defendible Inspecciones automátizadas Reificación de Código Excepciones a la regla Asegurar la calidad de puntos críticos Integración de Código Migración de Objetos
103
Porque usamos Herramientas
estables, maduras
que permiten hacer Meta-programación(como propone Alan
Kay)
Ideas de Tesis… Change Model para Objetos Mejor modelo de Migración de Objetos Interfaz de Usuario declarativa …además de las que ya se están haciendo
cómoRefactorings de TraitsSmallLint para TraitsReificación de Meta-Ejecución