Rompiendo dependencias contenidas en ensamblados .NET mediante la refactorización de su código...

Post on 27-Jun-2015

1.335 views 0 download

Transcript of Rompiendo dependencias contenidas en ensamblados .NET mediante la refactorización de su código...

Herramienta para romper dependencias en ensamblados .NET

mediante la refactorización de su código intermedio.

Presentado por:Jair Cazarin Villanueva

Bajo la supervisión de:Dr. Mauricio Osorio.

Dr. Mircea Trofin.

Universidad de las Américas, Puebla

Agenda.

• Contexto del Problema.– Programación Orientada a Objetos.– Dependencias.

• Objetivos.• Alcances y Limitaciones.• Análisis del Problema.• Diseño e Implementación.• Resultado y Pruebas.• Conclusiones.

Programación Orientada a Objetos

“El paradigma de la programación orientada a objetos, también conocida como POO, es la que usa objetos y sus interacciones para diseñar aplicaciones.”

Características Principales

Flexible

Fácil de mantener.

Dependencias.

Tipos de dependencias.

Solución: Refactorizar.

“Refactorizar es el proceso de cambiar un software de tal forma que el comportamiento externo no cambia, más bien, se mejora su estructura interna.”

• Desafortunadamente, la refactorización es un proceso que se aplica cuando se tiene acceso al código fuente.

• Ignorando las veces cuando:

Objetivo General.

• Investigar la refactorización de ensamblados binarios con el objetivo de mejorar la reusabilidad y capacidad de resolución de frameworks

Objetivo General.

• Para lograr lo anterior, se desarrolló una herramienta que refactoriza ensamblados existentes, con el objetivo de romper las dependencias contenidas entre distintas clases, y de esta forma hacer posible satisfacer estas dependencias con otros tipos, mediante la mejora de la modularización y extensibilidad de los componentes.

Afterex = Extensibility after-the-fact.

Alcances y Limitaciones.

• .NET Framework y ensamblados .NET.• Dependencias contenidas y directas.• Solución completa para el escenario de

dependencias contenidas.• En el caso de las dependencias directas solo se

abordó el tema y se hicieron los primeros experimentos con los casos de parámetros.

• Aplicación basada en consola.

Análisis del problema.

• Metodología ágil.• Definición de enfoques y tecnologías a usar.

Dependencia Contenida.

• Si tenemos un DLL D con una clase d, y un DLL C con una clase c, una dependencia contenida de D a C, sería si encontráramos instrucciones como al siguiente en clases de d:

• c someVariable = new c(…Parameters…);• ic someVariable = new c(…Parameters…);

Dependencia Directa.

• C someMethod(…);• SomeType someOtherMethod(…,C parameter,

…);• C.someStaticMember(…);• class Cls:C (<-if C wasn’t sealed)• class Cls:SomeGeneric<C>

.NET Framework.

CLR

Ejecución de código administrado.

CIL.

.method static void main(){ .entrypoint .maxstack 1 ldstr "Hello world!" call void [mscorlib]System.Console::WriteLine(string) ret}

Ensamblados.

CECIL.

Dependency Injection.

• Dependency Injection es un patrón de diseño de objetos en los cuales estos son colocados por entidades externas.

• No se usa el operador new para construir objetos.

• Una forma de implementarlo es usando factories.• Esto nos da la flexibilidad de crear

implementaciones alternas especificándola usando un archivo de configuración.

Implementación

Capa de Framework.

Capa de Framework.

• El framework fue desarrollado siguiendo las mejores prácticas dictadas por el Framework Design Guidelines.

Capa de implementación.

Dependency Injection Container.

Rompiendo dependencias contenidas.

Tipo abstracto.

public Interface ISort{ void Sort(int[] list();}

Rompiendo dependencias contenidas.

Implementación concreta.

public class BubbleSort : ISort{ public void Sort(int[] list) { …

Implementation goes here…}}

Rompiendo dependencias contenidas.

Tipo dependendiente.

public class DependentType { void m1() { ISort sorter = new BubbleSort(); … sorter.Sort(numbers) …. }}

• Propiedad

private static Func<ISort> sortBaseTypeFactory = null;

Implementación del Factorypublic static Func<ISort> SortBaseTypeFactory{ get { if (null == SortBaseTypeFactory) { SettingsReader settingsReader = new SettingsReader(); string assemblyName = settingsReader.GetValue("Assembly"); string typeName = settingsReader.GetValue("Type"); string methodName = settingsReader.GetValue("Method"); string assemblyFullName =Path.Combine(Directory.GetCurrentDirectory(), assemblyName); MethodInfo method =

Assembly.LoadFile(assemblyFullName).GetType(typeName).GetMethod(methodName); sortBaseTypeFactory = () => (String)(method.Invoke(null, null)); } return baseTypeFactory; } set { sortBaseTypeFactory = value; }}

Factory Method:

public static ISort GetBubbleSortInstance()

{ return new BubbleSort();}

XML de configuración.

<Settings> <Assembly>SortFactory.dll</Assembly>

<Type>SortFactory.Factory</Type><Method>SomeMethod</Method></Settings>

Ahora instanciamos así:

ISort sorter = Factory.SortBaseTypeFactory();

En lugar de:

ISort sorter = new BubbleSort();

Nuevo componente.

public class QuickSort : ISort{ public void Sort(int[] list) { …Implementation goes here… }}

Factory Method

public static ISort GetQuickSortInstance(){ return new QuickSort();}

XML de configuración.

<Settings> <Assembly>newComponent.dll</Assembly>

<Type>QuickSortComponent</Type><Method>GetQuickSortInstance</Method></Settings>

Refactorizando instanciaciones.RefactorInstantiations(baseType, concreteType, targetAssembly)1 Foreach Type t in targetAssembly.Types2 Foreach Method m in t.Methods3 If method doesn’t has a body4 Continue5 If method doesn’t contain a variable of type6 baseType7 Continue8 Foreach Instruction i in m.Body9 If perform a new instantiation of10 concreteType11 Replace the instruction to call12 the property instead of new.

public class DependentType { void m1() { BubbleSort sorter = new

BubbleSort(); … sorter.Sort(numbers) …. }}

Refactorizando instanciaciones concretas.

RefactorInstantiations(concreteType, targetAssembly)1 newInterface ← new Interface2 Declare the operations of concreteType in

newInterface3 Change concreteType to implemente newInterface4 Perform RefactorConcreteImplementations5 the property instead of new

AND6 change the variable to be

baseType.

Dependencias Directas.

• Se logró crear una capa más abstracta para el API que define.

• Sin embargo, aún no sabemos cómo romper la dependencia completamente.

• Tampoco sabemos cómo desarrolladores terceros puedan utilizar esta capa más abstracta o tomar ventaja de ella.

Refactorizando Parámetros.RefactorParameters(baseType, concreteType, targetAssembly)1 Foreach Type t in target.AssemblyTypes2 Foreach Method m in t.Methods3 If m contain parameter of type concreteType4 Change the parameter to be baseType6 mbody ← m.MethodBody5 Make the method protected and abstract.6 Make the type to be abstract.7 Create a new type tAbstract8 Create a new assemblyAbstract9 Make tAbstract inherits from t10 Implement abstract methods of t with

mbody.11 Add tAbstract to assemblyAbstract

Renombramiento.

AssemblyRenameApproach(originalAssembly, newAssembly, originalType, Type newType)

1 oname ← originalAssembly.Name2 originalAssembly.name ← newAssembly.Name3 newAssembly.name ← oname4 otype ← originalType.name5 originalType ← newType.Name6 newType ← otype

Refactorizando Parámetros de retorno.

RefactorReturnParameters(baseType, concreteType, targetAssembly)

1 Foreach Type t in target.AssemblyTypes2 Foreach Method m in t.Methods3 If return type of m is of type concreteType4 Find which methods calls m5 Move m to the new assembly and

type.6 If the list of method that calls m > 17 Move all methods to the new

assembly8 Update calls to m.

Capa de aplicación.

Capa de aplicación.

• Extensible. Se pueden cargar nuevas reglas de refactorización.

• Fácil de cambiar a otra implementación. Ejemplo: Una interfaz gráfica.

• Genera un archivo XML con un resumen de las reglas aplicadas.

Pruebas y Resultados.

Escenario 2.

Resumiendo

• La aplicación ya no es responsable de encontrar sus dependencias.

• El contenedor se encarga de encontrar esas dependencias.

• Añadimos flexibilidad a la aplicación para futuros cambios.

• Promovimos la disminución del acoplamiento entre componentes, por lo cual facilitamos las pruebas de unidad.

Conclusiones.

Trabajo a Futuro.

• Terminar un escenario completo de las dependencias directas.

• Estudiar las dependencias indirectas y ocultas.• Crear un contenedor de dependencias para

aplicaciones existentes más robusto.• Mejorar la complejidad de los algoritmos.• Extender el API y fusionarlo con CECIL.

?