Resumen Lecturas

Resumen lecturas: PRINCIPIOS DE DISEÑO EN SMALLTALK (DANIEL HH INGALLS) Algunos de los principios generales observados

Views 121 Downloads 5 File size 636KB

Report DMCA / Copyright

DOWNLOAD FILE

Recommend stories

Citation preview

Resumen lecturas:

PRINCIPIOS DE DISEÑO EN SMALLTALK (DANIEL HH INGALLS) Algunos de los principios generales observados durante el trabajo en Smalltalk 1) Dominio Personal: para que un sistema ayude al espíritu creativo, deberá ser completamente entendible para un individuo. Es decir: se deberá proveer un medio que pueda ser dominado completamente por el individuo ya que si alguna parte del sistema no es entendida por el usuario esto ocasionara una barrera a la expresión creativa. Cualquier parte del sistema que no pueda ser cambiada o que no es lo suficientemente general es probable que origine problemas. 2) Buen diseño: El sistema debe estar construido con un mínimo conjunto de partes no modificables, dichas partes ser lo más general posible y todas las partes del sistema ser mantenidos dentro de un esquema uniforme. LENGUAJE. 1) Propósito del lenguaje: proveer un esquema para la comunicación. Una computadora podría caracterizarse con dos elementos, “cuerpo” que sería una pantalla visual de información que recibe datos del usuario, y una “mente” que incluiría los elementos de memoria y procesamiento interno. 2) Alcance: el diseño de un lenguaje para usar computadoras debe tratar con modelos internos, medios externos y la interacción entre ellos. OBJETOS QUE SE COMUNICAN. 1) Objetos: Un lenguaje de computación debe soportar el concepto de “objeto” y proveer una manera uniforme de referirse a ellos. Esto es debido a que cuando queremos interactuar con un objeto, debemos diferenciarlo del universo, por ejemplo: “Esa silla que está ahí” Smalltalk provee un modelo orientado a objetos de la memoria de todo el sistema. La referenciación uniforme se obtiene asociando un entero no repetido a cada objeto del sistema. Esto es lo que nos permite crear diversos objetos y pasarlos de un lado al otro, cuando la referencia a un objeto desaparece del sistema, el mismo objeto desaparece y su espacio de almacenamiento es recuperado. 2) Administración del almacenamiento: Para que un sistema sea “Orientado a Objetos” debe proveer administración automática del almacenamiento. (no es tan asi) Una manera de ver si un lenguaje no está funcionando bien viendo si los programas que hacen tienen instrucciones de administración de almacenamiento, esto no sería orientado a objetos. Cada objeto tiene vida propia. 3) Mensajes: La computación debería ser vista como una capacidad de que los objetos puedan ser invocados enviándoles mensajes.

Smalltalk envía el nombre de la operación deseada, junto con cualquier parámetro, como un mensaje al número, entendiendo que el receptor es el que mejor sabe cómo realizar la operación deseada. La transmisión de mensajes es el único proceso que se hace afuera de los objetos, debido a que estos viajan entre objetos. 4) Metáfora Uniforme: Un lenguaje debe ser diseñado alrededor de una metáfora poderosa. ORGANIZACIÓN. Al incrementar la cantidad de componentes de un sistema, la probabilidad de interacción no deseada crece. 1) Encapsulamiento: Ningún componente en un sistema complejo debería depender de los detalles internos de ningún otro componente. La complejidad de un sistema puede ser reducida agrupando componentes similares. Este agrupamiento es conseguido mediante el tipado de datos y a través de clases en Smalltalk. Una clase describe otros objetos – su estado interno, el protocolo de mensajes que reconocen y métodos internos. Los objetos de una clase se llaman instancias de la clase. Las propias clases son instancias de la clase Class que describe el protocolo y la implementación. 2) Clasificación: Un lenguaje debe proveer un medio para clasificar objetos similares, y para agregar nuevas clases de objetos en pie de igualdad con las clases centrales del sistema. 3) Polimorfismo: Un programa solo debería especificar el comportamiento esperado de los objetos, no su representación. (Esto es encapsulamiento, nada que ver) Un ejemplo es que un programa nunca debería declarar que cierto objeto es un Smallinteger o un LargeInteger, sino que solo responde al protocolo de los enteros. 4) Factorización: Cada componente independiente de un sistema solo debería aparecer en un solo lugar. Hay muchas razones para este principio. Primero, ahorra tiempo, esfuerzo y espacio. Segundo, en la ausencia de una factorización apropiada, aparecen problemas para sincronizar cambios. La falla de factorización implica una violación a la modularidad. Smalltalk promueve diseños bien factorizados a través de la herencia. Todas las clases heredan comportamiento de su superclase. Todas las clases terminan heredando de la clase Object que describe el comportamiento mínimo de todos los objetos del sistema. 5) Reaprovechamiento: Cuando un sistema está bien factorizado, un gran aprovechamiento está disponible para los usuarios como para los implementadores. Smalltalk tiene una máquina virtual que establece un modelo orientado a objetos para el almacenamiento, un modelo orientado a mensajes para el procesamiento y un modelo de bitmap para el despliegue visual de información.

INTERFAZ AL USUARIO. Una interfaz al usuario es simplemente un lenguaje en el que la mayor parte de la comunicación es visual. 1) Principio reactivo: Cada componente accesible al usuario debería ser capaz de presentarse de una manera entendible para ser observado y manipulado. 2) Sistema operativo: es una colección de cosas que no encajan dentro de un lenguaje. Sistemas operativos incorporados al lenguaje Smalltalk:      

Administración del almacenamiento – Enteramente automático. Los objetos son creados por un mensaje a su clase y destruidos cuando nadie los referencia. Sistema de archivos – Incorporado al esquema usual a través de objetos como Files, Directories con protocolos de mensajes que soportan el acceso a archivos. Manejo de la pantalla – la pantalla es simplemente una instancia de la clase Form. Entrada del teclado Acceso a subsistemas Debugger

Selección natural: Los lenguajes y sistemas que son de buen diseño persistirá, solo para ser reemplazados por otros mejores.

PAUTAS DE LAS PRUEBAS UNITARIAS 1) Mantener las pruebas unitarias chicas y rápidas (probar cosas chicas) Lo ideal es que el conjunto de pruebas se ejecute para verificar el código, es por eso que debe ser rápido 2) Las pruebas unitarias deberían ser completamente automáticas y no interactivas Si las pruebas requieren inspección manual, no son buenas pruebas unitarias 3) Hacer pruebas unitarias simples de ejecutar Las pruebas se tienen que ejecutar con un solo comando o un solo click. 4) Mida las pruebas Aplicar el análisis de cobertura a las pruebas para saber que partes del código se ejecutan y cuales no (Es una herramienta para ver cuánto código está cubierto) 5) Arregle las pruebas que fallan inmediatamente El desarrollador debe asegurarse de que una nueva prueba se ejecute correctamente al momento de verificar el código, si una prueba falla durante la ejecución, el problema deberá ser solucionado. 6) Mantener el nivel de testeo unitario La prueba unitaria trata de evaluar las clases. Debe haber una clase de prueba por clase y el comportamiento de dicha clase debe ser testeado de forma aislada. Evitar probar todo un flujo de trabajo. Este tipo de pruebas pueden darse, pero no son unitarias.

7) Comenzar simple Una prueba simple es infinitamente mejor que ninguna prueba en absoluto. Dicha prueba establece el marco de prueba de la clase objetivo, verificando la presencia y corrección del entorno de compilación, prueba de la unidad y ejecución. 8) Mantener las pruebas independientes Para asegurar la robustez de las pruebas y simplificar el mantenimiento, las pruebas no deben depender de otras pruebas ni de su orden de ejecución. 9) Mantener las pruebas cerca de la clase que se está probando Si la clase a evaluar es Foo, la clase de prueba debería llamarse (FooTest) y mantenerse en el mismo paquete que Foo. Mantener las pruebas en otros paquetes hace que sea más difícil de acceder y de mantener. 10) Nombre apropiadamente a las pruebas Cada método de una prueba tiene que probar una característica distinta de la clase que se prueba. La convención de nomenclatura es test[what] como: testSaveAs(), testAddListener(), etc 11) Pruebe la API (interfaz de la clase) publica (las pruebas prueban comportamiento, no como esta implementado) Las pruebas unitarias se pueden definir como pruebas de clases a través de su API publica, es decir, debe evitarse probar el contenido privado de una clase, ya que hace que la prueba sea más detallada y más difícil de mantener. Si hay contenido privado que necesita probarse, considere la posibilidad de re factorizarlo en métodos. 12) Pensar en una caja negra Actúa como un consumidor de la clase y prueba si la clase cumple con sus requisitos. 13) Pensar en una caja blanca (Es en caso de que al hacer la cobertura, me da que algo esta sin probar) Se debe hacer un esfuerzo adicional para probar la lógica más compleja. 14) Probar los casos triviales también A veces se recomienda no testear los casos triviales y omitirlos como setters y getters. Sin embargo, hay varias razones por las cuales los casos triviales deberían ser probados también. Desde el punto de una caja negra no se sabe que parte del código es trivial. También se pueden cometer errores a menudo como resultado del copiar y pegar. La recomendación es probar todo. 15) Primero hacer foco en la cobertura de ejecución Diferenciar entre la cobertura de ejecución y la cobertura de prueba real. El objetivo inicial de una prueba debe ser garantizar una alta cobertura de ejecución. Esto asegura que el código se ejecute realmente. 16) Cubrir los casos limites Asegúrese de que los casos límites de los parámetros estén cubiertos. El objetivo es asegurarse de que la mayor cantidad posible de estos pasen correctamente ya que estos errores son los principales a fallar. 17) Proporcionar un generador aleatorio Esto no porque nose si el random prueba todos los tipos de entrada y si falla no se bien porque falla a menos que guarde los valores ejecutados

18)

19)

20)

21)

22)

23)

24)

25) 26) 27)

Cuando se cubren casos límites, una forma simple de mejorar aún más la cobertura de la prueba es generar parámetros aleatorios para que las pruebas se puedan ejecutar con diferentes entradas. El generador debe producir valores de todo el dominio de cada tipo ya sea int, doubles, strings, etc. Si las pruebas son rápidas considere ejecutarlas dentro de los bucles para cubrir mayor cantidad posible de combinaciones de entrada. Prueba cada característica una vez Solo se debe probar exactamente la función indiciada por el nombre del método de prueba, manteniendo la cantidad del código de la prueba lo más bajo posible. Usar afirmaciones explicitas Siempre es preferible “assertEquals(a, b)” que “assertTrue(a == b)” ya que el primero dará mas información útil de que es exactamente lo que está mal en caso de que la prueba falle. Proporcione pruebas negativas Las pruebas negativas prueban el uso indebido intencional del código y verifican su solidez y el manejo adecuado de errores Diseñar el código con las pruebas en la mente  Haga que los miembros de la clase sean inmutables al establecer el estado en el momento de la construcción, reduciendo el uso de setters.  Restringir el uso de herencia excesiva y métodos públicos virtuales  Evitar ramificaciones innecesarias  Mantener el menor código posible dentro de las ramas  Hacer uso intensivo de excepciones y aserciones para validar argumentos en API públicas y privadas respectivamente  Restrinja el uso de métodos de conveniencia. No conectarse a recursos externos predefinidos (Puede haber otra cosa que afecte a mi prueba que no tiene nada que ver con mi codigo) Las pruebas unitarias deben escribirse sin conocimiento explícito de cómo se ejecutan, de modo que se pueden ejecutar en cualquier momento y lugar, teniendo en cuenta que el lugar debe disponer de los recursos necesarios para la prueba. Conocer el costo de las pruebas No escribir pruebas unitarias y escribirlas tienen un costo, pero se compensan con un 80% de la cobertura de ejecución. Las áreas típicas en las que es difícil obtener una cobertura de ejecución completa es en el manejo de errores y excepciones debido a que tratan con recursos externos. Priorizar las pruebas Las pruebas unitarias son un proceso ascendente, si no hay los suficientes recursos para probar todas las partes de un sistema, la prioridad debe colocarse primero en los niveles más bajos. Prepare el código de prueba para las fallas Si el código falla en alguna parte, no se interrumpan las demás pruebas. Escribir pruebas para reproducir errores Cuando se informa un error, escriba una prueba para reproducir el error. Conozca las limitaciones

Las pruebas unitarias nunca pueden probar la exactitud del código. Lo más útil de las pruebas unitarias es la verificación y documentación de los requisitos en un nivel bajo, y las pruebas de regresión: verificando que las invariantes del código permanecen estables durante la evolución del código y la refactorización.

LOS 8 PRINCIPIOS DE UNA MEJOR PRUEBA UNITARIA (Dror Helper) Escribir pruebas unitarias debería ser fácil para los desarrolladores de software, después de todo escribir las pruebas es como escribir el codigo de producción. ¿Qué hace que una prueba unitaria sea buena? La prueba unitaria tiene que ser corta, rápida y automatizada asegurando como fuinciona una parte especifica del programa. Las pruebas testean una funcionalidad especifica de un método o de una clase con una condición clase de aprobado y no aprobado. Al escribir pruebas unitarias, los desarrolladores pueden asegurarse de que su codigo funcione. Una prueba unitaria buena sigue estas dos reglas:  

La prueba solo falla cuando se introduce un nuevo error en el sistema o cambian los requisitos. Cuando falla la prueba es fácil saber el motivo

Principios para desarrollar buenas pruebas unitarias: 1) Saber lo que estas testeando: una prueba escrita sin un objetivo claro en mente es fácil de detectar, generalmente son largas, difíciles de entender y prueban más de una cosa. Probar solo una cosa crea una prueba más legible. Cuando una prueba simple falla es más fácil encontrar la causa y corregirla que hacerlo con una prueba larga y compleja. 2) Las pruebas unitarias deberían ser auto-suficientes: Una buena prueba unitaria debe ser aislada. Evitar la dependencia con la configuración del entorno. Las pruebas no deben depender una de la otra ni debe verse afectada por el orden en el que se ejecute. Ejecutar la misma prueba 1000 veces debería devolver el mismo resultado. Evite usar estados globales como variables estáticas. 3) Las pruebas deben ser deterministas: Una prueba unitaria no puede pasar o fallar una parte de tiempo, tiene que pasar o fallar todo el tiempo. Una prueba que depende de un delay puede que en una computadora rápida pase y en una lenta falle, en este caso la prueba no daría una indicación definitiva de que haya un error en el código. También es buena práctica evitar escribir pruebas con entrada aleatoria que hagan que todo el tiempo cambien, debido a que si falla no se sabe porque falla. 4) Convenciones de nomenclatura: Para saber porque falla una prueba debemos ser capaces de entenderla de un vistazo, notando a primera vista el nombre de la misma. Cuando falla una prueba bien nombrada es fácil entender que se probó y porque fallo.

5) Que se repita: (Repetir para que se lea mejor) La duplicación de código debe evitarse debido a que causa problemas de mantenimiento. Pero la legibilidad es muy importante en las pruebas unitarias por lo que es aceptable tener un código duplicado sino esto podría llevar a crear pruebas que son difíciles de leer y comprender. La eliminación de la duplicación es algo bueno siempre y cuando no oscurezca nada. 6) Resultados de la prueba, no implementación: No se deben probar métodos internos/privados, debido a que estos no están destinados a ser vistos fuera de la clase y son parte de la mecánica interna de la clase, se los prueba solo si es importante testearlos. Se debe testear los métodos públicos. 7) Evitar la sobre escritura: solo escribir desde el punto del comportamiento y no desde la implementacion 8) Usar el marco de aislamiento crea los objetos para realizar la prueba

REEMPLACE EL CONDICIONAL CON EL POLIMORFISMO. La técnica de refactorización puede ayudar si el código contiene operadores que realizan varias tareas:  

Resultado de llamar a uno de los métodos de un objeto Si aparece una nueva propiedad o tipo de objeto, deberá buscar y agregar código en todos los condicionales similares.

Los beneficios de esta técnica son debido a que se basa en el principio “Tell don’t ask” en lugar de preguntarle a un objeto sobre su estado y luego realizar acciones basadas en esto, es mucho más fácil decirle al objeto lo que tiene que hacer y dejar que decida por sí mismo como hacerlo. Esto elimina código duplicado, eliminación de condicionales casi idénticos, si se necesita agregar una nueva variante de ejecución todo lo que necesita hacer es agregar una nueva subclase sin tocar el código existente. Para re factorizar debe tener una jerarquía de clases que contendrá comportamientos alternativos. Otras técnicas usadas son: 

Reemplazar el código con las subclases

Las subclases se crearán para todos los valores de una propiedad de un objeto en particular.



Reemplazar el código con estado/estrategia

Una clase se dedicará a una propiedad del objeto en particular y se crearan subclases para cada valor de la propiedad. Si el condicional está en un método que realiza otras acciones, realice el método de extracción. Para cada subclase de la jerarquía redefina el método que contiene el condicional y copie el código de la rama condicional correspondiente. Eliminar la rama del condicional. Una vez que el condicional este vacío borre el condicional y declare el método abstracto.

INTEGRACION CONTINUA (Martin Fowler) Practica de desarrollo de software en la que se va integrando con frecuencia el trabajo particular de cada integrante del equipo de desarrollo. Todos los días un poco, con este enfoque progresivo se reduce de manera significativa los problemas de integración y el resultado final es un software más coherente, mejor calidad. Cada integración se realiza a través de una automatización, incluyendo los tests. Tras una integración nueva, si surgen errores y se vino aplicando esta práctica de desarrollo, es muy probable que el error sea fácil de hallar, por lo tanto, rápida solución al “pequeño problema “, lo defino así porque la integración continua, por lo general, evita fallas de gran tamaño gracias a la frecuencia de revisión. Prácticas de integración continua 

Mantener un único repositorio: los proyectos de software implican muchos archivos que deben organizarse juntos, si estos no están todos en un mismo lugar es difícil hacer un



 





 

 

seguimiento en especial si hay muchas personas involucradas. Nadie debería preguntar dónde se encuentra tal archivo Automatizar la compilación: cualquier persona debería poder traer una maquina virgen, verificar las fuentes del repositorio, emitir un solo comando y tener el sistema ejecutándose en su máquina. Hacer tus propias pruebas de compilación: hacer uso de la técnica de TDD Todos se comprometen con la línea principal todos los días: la integración permite a los desarrolladores informar a otros sobre los cambios que han realizado, la comunicación frecuente permite a las personas saber rápidamente los cambios en la línea principal. Al hacerlo con frecuencia los desarrolladores descubren rápidamente si hay conflicto entre dos desarrolladores. La clave para solucionar problemas rápidamente es encontrarlos rápidamente. Los problemas que se encuentran cada pocas horas son fáciles de solucionar, en cambio errores de semanas se vuelven sumamente difíciles. Comprometerse con frecuencia tiende a que los desarrolladores dividan su trabajo en pequeños trozos de pocas horas. Arreglar los errores de compilación inmediatamente: no es malo que se rompa la compilación en la línea principal, pero si pasa todo el tiempo sugiere que las personas no tienen cuidado al realizar la actualización antes de una confirmación. Cuando se rompe la compilación principal es importante solucionarla rápidamente, nadie tiene una tarea de mayor prioridad que arreglar la compilación principal. Mantener la compilación rápida: Intentar siempre reducir el tiempo de compilación, aunque sea un minuto. Una compilación de una hora es irracional. Por medio de las prácticas de XP la compilación tiene que ser menor a diez minutos. La idea es hacer una compilación por etapas. Primero está la compilación de compromiso que es la compilación de la línea principal, esta debe hacerse rápidamente. Luego están las compilaciones secundarias que se pueden ejecutar en paralelo reduciendo los tiempos, la idea es que, si fallan este tipo de compilaciones, la de compromiso se mantenga en ejecución. Testear en un clon del entorno de producción Facilitar el acceso de cualquier persona al último ejecutable: cualquier persona involucrada en un proyecto de software debería poder obtener el ultimo ejecutable y poder ejecutarlo para demostraciones, pruebas exploratorias o simplemente para ver qué cambio esta semana. Todos pueden ver lo que está pasando Automatizar la implementación

Ventajas:    

Se sabe dónde se está parado siempre, en que parte del proyecto se encuentra. Reducción de riesgos. Facilidad para encontrar bugs y errores. Mejor calidad de código (generalmente).

¿PARA QUE SIRVE UN MODELO? (Martin Fowler) El valor que proporciona un modelo se basa fundamentalmente en darle al usuario una mayor comprensión del software o del dominio que admite dicho software. Algunas personas entienden mejor las cosas por medio de una forma textual y otras por una forma visual. Una parte importante de un modelo es que no hay que sumergirse en los detalles, sino resaltar las partes más importantes del sistema, esto se puede hacer antes o después del desarrollo del software. El punto es entender las estructuras clave que hacen que el software funcione primero y luego se puede se pueden investigar mejor los detalles. Un modelo esquelético es mejor que uno completamente desarrollado. Cuando se quiere entender un modelo lo primero es ver por dónde empezar, por lo que se buscan las cosas más importantes, pero como no saben que es lo más importante, es responsabilidad de quien realizo el código hacer dicha selección. Los modelos esqueléticos son más fáciles de mantener que uno completo, debido a que los detalles cambian con menor frecuencia. Si la gente quiere detalles puede explorar el código una vez que el esqueleto le dio una descripción general.

EXTREME PROGRAMMING (XP) (Kent Beck) Consta de cinco valores: Simplicidad: es la base de XP, es la simplificación del diseño para agilizar el desarrollo y facilitar el mantenimiento. Para mantener la simplicidad es necesaria la refactorización del código. Intentar que el código este autodocumentado para eso deben usarse nombres adecuado para las variables, métodos y clases; hacer uso de nombres largos. Comunicación: un código autodocumentado es mejor que los comentarios, dado que estos están desfasados con el código, se los utiliza para cosas que no vayan a cambiar por ejemplo el objetivo de una clase o funcionalidad de un método. Las pruebas unitarias son otra forma de comunicación. Los programadores se comunican constantemente gracias a la programación por parejas. Retroalimentación (feedback): al estar el cliente integrado en el proyecto, su opinión sobre el estado del proyecto se conoce en tiempo real, ante cualquier cambio necesario para él, el grupo de programadores no pierde tiempo en lo que antes era requerido. Las pruebas unitarias también informan sobre el estado del código. Valentía: un ejemplo de Valente es la idea es programar para hoy y no para mañana, otro ejemplo es quitar código fuente obsoleto sin importar el esfuerzo y tiempo invertido en dicho código.

Respeto: los miembros del equipo se respetan el trabajo del resto no haciendo menos a otros, una mejor autoestima en el equipo eleva su ritmo de producción. Refactorización del código: es decir, reescribir ciertas partes del código para aumentar su legibilidad y mantenibilidad, pero sin modificar su comportamiento. Las pruebas han de garantizar que en la refactorización no se ha introducido ningún fallo. Programación en parejas: se recomienda que las tareas de desarrollo se lleven a cabo por dos personas en un mismo puesto. La mayor calidad del código escrito de esta manera -el código es revisado y discutido mientras se escribe- es más importante que la posible pérdida de productividad inmediata.

¿A qué se refiere el artículo “Unit Testing Guidelines” con la frase Cover Boundary Cases? “Cubrir los casos de frontera”, implica asegurarse de que las situaciones límites estén verificadas, lograr que el mayor número posible de ellas estén probados adecuadamente ya que son estos casos los candidatos principales a generar errores. Por ejemplo el registro de fechas, ya sea el 1 de enero como el 31 de diciembre o el 29 de febrero. ¿A qué se refiere el artículo “Unit Testing Guidelines” con la frase Keep Test Independent? Para poder mantener la robustez y la simpleza en el mantenimiento, los tests nunca deben depender de otros tests o del ingreso externo de información para que puedan llevarse a cabo, tampoco de el orden en que son ejecutados. ¿A qué se refiere el artículo “Unit Testing Guidelines” con la frase Provide Negative Tests? Para poder tender al 100% en la cobertura del test, se deben probar la mayor cantidad de situaciones posibles entre las cuales hay casos de respuestas no esperadas que deben ser generadas intencionalmente, esto nos permitirá medir la calidad de código y verificar la robustez. También para chequear manejo de errores. ¿A qué se refiere Ingalls con la frase “Ningún componente en un sistema complejo debería depender de los detalles internos de ningún otro componente”? Esto se basa en el principio del encapsulamiento, debido a que se trabaja bajo el lema “Tell don’t ask” su significado sería: decile al objeto que hacer no le preguntes cosas para hacerlo. ¿Por qué Ingalls afirma que “Un programa nunca debería declarar que cierto objeto es SmallInteger o un LargeInteger? Ingalls se basa en el uso del polimorfismo por lo que dicho objeto debería responder al protocolo de un Integer sin depender el tipo de integer que sea. Segun Fowler, ¿Cuáles son los principales beneficios de la integración continua? Los principales beneficios de la CI son:   

Todos los integrantes del grupo saben la situación actual del proyecto Se minimizan los errores, en caso de aparecer son pequeños Mejor calidad del código (generalmente)

¿Por qué Fowler sostiene que en el ámbito donde se realicen las pruebas debe ser una clonación del ambiente real de producción? ¿Esto es siempre posible? Cuanto más cerca del ámbito real de producción esté el testeo es mejor porque se aproxima a una simulación del uso final del proyecto, los resultados son más relevantes. Simular la realidad, a la que se va a enfrentar el proyecto una vez terminado, para el desarrollo de tests no es fácil ni siempre es posible. Por ejemplo, supongamos que la respuesta de algún método es la impresión de un documento, esto es imposible de testear con código. Explique qué ventajas ofrece el tipeo estático sobre el dinámico. Hay autores que dicen que la falta de genericidad era un déficit en los lenguajes de tipeo estático. ¿Por qué? Los distintos lenguajes de desarrollo de software varían en la forma en que hacen la comprobación de tipos de datos, algunos tipeo estático, otros dinámico y los más modernos aceptan el uso de ambos, pero con ciertas restricciones o condiciones de uso. El chequeo estático se realiza en tiempos de compilación, si no es aceptado el software no podrá ejecutarse a diferencia del chequeo dinámico que surge directamente en tiempos de ejecución. Las ventajas de uno u otro van a depender del contexto de desarrollo. El estático previene posibles errores al momento de ejecución y demanda un código más específico, se deben aclarar todos los tipos de cada variable o tipo de parámetro que va a utilizar ya sea una función, método, etc. En cierto aspecto es bueno pero para muchos programadores es medio molesto tener que aclarar todos los tipos, aquí está una de las ventajas de uso dinámico, por ejemplo si se desea aplicar un método a una colección de distintos tipos de elementos, no es necesario que todos sean del mismo tipo, y se sobre entiende que sea cual sea el tipo deberá poder responder al método que se está invocando, esto último es lo que se conoce como genericidad y en lenguajes de comprobación estática se presenta en déficit. Explique a qué llamamos TDD y cuáles son sus objetivos (media carilla) TDD (Test Driven Development) o en español desarrollo orientado por pruebas, es un tipo de práctica de desarrollo de software, en el cual se comienza desarrollando pruebas, estas van a definir el comportamiento esperado del software, se espera que los primeros test que se escriban sean unitarios y a medida que se va progresando sean mas integradores. Obviamente al ejecutar estos test no van a pasar ya que no hay código escrito previamente que los soporte, es entonces, que a partir de los errores de ejecución se empieza a programar. Hay grandes ventajas en esta práctica: 



Primero que nada, los tests son muy importantes para cualquier software que se implemente. Al comenzar por ellos nos aseguramos de que comprueben gran parte del código, que generalmente hacerlo al final no se realizan tan detallados o no se hacen. Sirve como documentación, al detallar en una prueba el comportamiento esperado del software estamos contando claramente cómo funciona.

 

Las pruebas resultan objetivas, no están condicionadas a la forma en que se implementó el programa. Colaboran con la refactorización. (mejorar calidad del código sin cambiar comportamientos, control de entropía). Escribir prueba que falle.

Refactorizar.



Hacer que pase la prueba.

CICLO TDD:

Hay un principio de diseño que sugiere depender de interfaces y no de las implementaciones a) ¿Qué significa? b) De un ejemplo concreto y completo de la no aplicación del principio y como solucionarlo, utilizando los diagramas y código que necesite. a) Las interfaces son como clases abstractas, que no pueden ser instanciadas, no tienen estado (no tienen atributos) y tienen todos sus métodos abstractos. La idea de la interfaz, es generar un “comportamiento” básico, del cual extienden varios objetos; el objetivo, es hacer que todos los objetos hijos entiendan los métodos, y estén obligados a redefinirlos. Se utilizan para tener un buen uso de polimorfismo, sin herencia. b) Por ejemplo: tenemos ciertas cosas que se pueden imprimir. Estas son fechas, fracciones e inmuebles (datos). Todos estos objetos, no tienen una clase “madre” en común, por lo que no podemos usar herencia. Pero sí podemos crear la interfaz “imprimible”, cuyo único método (definido abstracto) es imprimirDatos. Hacemos heredar nuestros tres objetos de esa interfaz, y cada uno definirá su propio imprimirDatos, de acuerdo a su formato y necesidad. Lo mismo podríamos hacer entre fecha y fracción; ambas son comparables con objetos de su mismo tipo, por lo que definimos la interfaz “comparable” y las heredamos de ella. Tanto fecha como fracción, redefinirán el método compararCon(otroObjeto) de acuerdo a sus características.

Explique Polimorfismo: El polimorfismo se da en lenguajes con clases, es cuando distintos objetos responden a un mismo mensaje de manera diferente, de acuerdo a como fueron definidos en su clase. El polimorfismo está muy ligado a la herencia, porque en gran parte de los casos en los que se aplica, la herencia, se implementó, lo que no quiere decir que siempre se debe usar herencia para el polimorfismo, existen las interfaces que son como clases abstractas solo que no tienen estado propio, no tienen atributos y todos sus métodos son abstractos, que se usan para casos de polimorfismo sin herencia, casos en los que los tipos de objetos que van a responder a un mismo mensaje no tienen ninguna relación visible.

Refactorización: La refactorización es la parte de todo desarrollo de todo software en la que se revisa el código y se lo mejora. Principio de esta práctica, por lo riesgosa que es, si funciona no se toca, se realizan cambios en las estructuras, pero sin modificar comportamiento, es muy importante contar con test que validen que los cambios realizados no generan nuevos errores, es la forma de corroborar que no se modificó el comportamiento. Por eso es que esta práctica está ligada al TDD, se detalla en el inciso 2. Diseño por contrato: Es una forma de especificar el comportamiento de un objeto. Cuanta con 4 elementos fundamentales: Firmas de métodos: detalla las condiciones para comunicarse con el objeto, nombre del método, tipos de parámetros y lo que se espera que responda. Precondiciones: son las pautas que debe cumplir el medio invocante para que se pueda llevar a cabo con éxito la llamada a este mensaje. Una excepción es un objeto que el receptor de un mensaje envía a su cliente, para avisarle que no está cumpliendo con alguna precondición de ese mensaje. Idealmente, debería definirse una excepción por cada precondición. Postcondiciones: aclaran como queda el contexto tras la ejecución de un método, se relaciona este concepto con los test que son los que van a probar que estas se cumplan correctamente Invariantes: son pautas de cada objeto que no deben cambiar a lo largo de su existencia.