Mostrando entradas con la etiqueta Estándares de programación. Mostrar todas las entradas
Mostrando entradas con la etiqueta Estándares de programación. Mostrar todas las entradas

jueves, 18 de agosto de 2022

¿Por qué la clase abstracta no puede ser instanciada?

Este post es una copia de una respuesta que di en Quora a la pregunta ¿Por qué la clase abstracta no puede ser instanciada?



Porque es una clase cuya información y características para que sea funcional está incompleta, con lo que no tendría sentido que fuera instanciada.

¿Qué es la abstracción?


La abstracción es un proceso del pensamiento en el que podemos distinguir las características de un objeto que lo hacen ser ese tipo de objeto en concreto.

Por ejemplo todos tenemos claro cómo debe ser un árbol; debe tener raíces, tronco, ramas y hojas pero esto no define ningún árbol en concreto, ni siquiera es un árbol, es la “idea que tenemos de un árbol”. Un árbol es un manzano, un roble, un nogal o un pino, todos de ellos bastantes diferentes entre sí e inconfundibles pero cuando los vemos los reconocemos como un árbol inmediatamente y sin dudar.

Scott McCloud en su libro “Entender el comic” lo explica muy bien:


Todas las imágenes anteriores son caras pero si nos vamos a la izquierda tenemos una cara en particular una “instancia” de una cara, según avanzamos a la derecha tenemos menos características de una cara, nos quedamos con la esencia de lo que es una cara. Eso es la “abstracción”. Al final tenemos algo que estrictamente no es una cara (y que no podría funcionar como tal) pero al verlo tenemos la idea de que si.

En la programación es parecido; extraemos ideas, procesos y características para convertirlos en clases. Las clases como tal son una abstracción del objeto que queremos construir, pero no son el objeto.

La clase define el objeto, define su comportamiento y los atributos que contiene pero no el valor de estos. La clase solo es funcional cuando la instanciamos y la convertimos en un objeto.

Por ejemplo podemos definir un cuadrado como una figura geométrica de 4 lados iguales. Pero esto no sería un cuadrado, seria la clase de un cuadrado. Para que fuera un cuadrado como tal debemos instancia la clase y crear un objeto que tenga un número de determinado de unidades concreto por ejemplo un cuadrado de lado 10.


Si profundizáramos mas en la esencia de un cuadrado veríamos que este es una peculiaridad de un rectángulo, una figura geométrica de 4 lados iguales dos a dos. Por ejemplo un rectángulo de 2x2

Así funciona la herencia, con mecanismos de abstracción cada vez mayores y menos concretos. Un rectángulo es una abstracción superior de un cuadrado o un cuadrado es una peculiaridad de un rectángulo.


Pero en este caso tanto las clases cuadradas y rectángulas pueden instanciarse y tener sentido por ellas mismas; existen los cuadrados y existen los rectángulos, son tangibles y se pueden medir, son objetos concretos

Si profundizamos más el concepto de rectángulo descubriremos que es un área cerrada de N lados, es decir un polígono.


Y aquí es donde llegamos al concepto de clase abstracta. Si la clase es una abstracción del objeto, la clase abstracta es una “abstracción” de la clase

La existencia del polígono no tiene sentido sin que tenga una forma particular, es decir no existe un polígono que no sea un cuadrado, triangulo, trapezoide o cualquier otra forma cerrada. No hay manera de instanciar solo el de polígono sin que sea además otra cosa más concreta.

Si definimos nuestro concepto de polígono como una clase que tiene n lados y además tiene un área, tendremos un idea aproximada de cómo es el polígono, pero no sabremos cuánto mide cada lado, ni si es regular o irregular y mucho menos como se calcula el área.

No tiene sentido instanciar las clase abstracta polígono, porque no es algo “real” y concreto con lo que podamos trabajar, necesitamos completar la clase con algo más particular para que tenga sentido, es decir un tipo particular de polígono.

Si vemos la clase abstracta “polígono” el número de lados es una atributo, y es una característica común de los polígonos, sin embargo como calcular el área es una particularidad de cada polígono.

Por ejemplo para calcular el área de los siguientes polígonos se calcula así:

  • Cuadrado: lado al cuadrado
  • Rectángulo: base por altura
  • Triangulo: Alto x Ancho /2
  • Pentágono: ((5 * longitud) * apotema) / 2;


Además tenemos un caso especial, una circunferencia que tiene área pero no es un polígono (por lo menos no como lo hemos definido) ya que no tiene lados. El hecho que se le pueda calcular el área se puede definir mediante una interfaz, así pues:

  • Una clase abstracta es una clase implementa características, como por ejemplo el hecho de tener numero de lados para nuestro polígono, pero no implementa otras como calcular un área (pero si las define). Las características no implementadas deben estar dentro de su jerarquía de herencia.
  • Las interfaces nos definen que características y funcionalidades debe tener una clase, pero en ningún caso nos indica como implementarlas, y no tiene que existir ninguna relación de herencia. Simplemente indicamos que una clase en particular debe poder resolver unas necesidades, pero no como hacerlo.


Un ejemplo más real


Un ejemplo ya alejado de lo simple de las definiciones teóricas podemos encontrarlo en la implementación de la clase DbConnection de ADO.NET

ADO.NET es la colección de clases de más bajo nivel (y más básicas) que usa .NET para el uso de base de datos. Definen de una forma clara y atómica como conectarse a un motor de base de datos y como ejecutar comandos además de ser un ejemplo excelente para estudiar abstracción.

La clase básica para conectarnos a una base de datos es la clase DbConnection que tiene las siguientes características:


Generalmente las clases abstracta definen e implementan código común a un conjunto de circunstancias pero no ponen atención a los "detalles", que son implementados por las clases hijas

Por ejemplo en la clase DbConnection los elementos marcados en rojo son abstractos, eso quiere decir que la clase no sabe cómo resolver su funcionalidad y al no saberlo la clase no puede ser instanciada, ya que esta “incompleta”.

Cada driver de base de datos debe definir como resolver esta funcionalidad, algunos de estos métodos son:

  • Open, Close, BeginDbTransacion: Estos métodos están extremadamente ligados al motor de base de datos a usar y al driver correspondiente.

Sin embargo otros métodos no dependen del driver en absoluto ya que definen flujos y funciones que son iguales para todos los tipos de base de datos, por ejemplo:

  • OpenAsync: Abre una conexión de forma asíncrona. Este método resuelve todo el flujo de los hilos y finalmente llame al método abstracto Open.
  • StateChange: Se lanza cada vez que se cambia el estado de la conexión.
  • Dispose: Cuando se ejecuta este método el flujo siempre es igual, se liberan los recursos, y siempre se llama al método Close.

Estos métodos realizan una serie de operaciones comunes en la clase base y finalmente “llama” a los métodos abstractos en algún momento.

Cada proveedor de base de datos debe ofrecernos un driver y una clase adecuada que implemente los métodos abstractos y opcionalmente sobrescriba los métodos virtuales (si esto es necesario).

Un diagrama de esto, por ejemplo usando conexiones para SQL, MySQL y Oracle, podría quedar así:



Puedes encontrar el código fuente de todo el artículo en:


Si te ha gustado el artículo, compártelo en tus redes sociales ;-)

domingo, 23 de diciembre de 2018

Regla N°17 de la Ingeniería de Software: Primero el camino principal luego las excepciones


Todo proceso tiene un punto de inicio y un punto de finalización, una serie de pasos que hay que cumplir para llegar del punto A al punto B. Generalmente esto constituye el llamado “camino feliz” (Happy Path). Frecuentemente este camino tiene muchas bifurcaciones y situaciones a considerar para el software funcione de manera adecuada. El cómo estructuramos dichos “caminos” es lo que puede hacer a nuestro código enredoso, poco escalable y difícil de mantener (y crear).




Si es bien es cierto que uno se tiene que prepara para los fallos (para que el software falle) la ruta principal (de ejecución) debe ser un camino limpio y fácilmente trazable. Incluso desde el momento del diseño, no podemos dejarnos abrumar con todas las excepciones y giros posibles de la lógica de negocio, debemos diseñar el camino feliz, y asumir que habrá excepciones en algún momento. Esto es porque si bien el camino principal suele ser sencillo, los recovecos, bifurcaciones y excepciones suelen ser muchas, muy variadas y a veces difíciles de ver, si el cambio principal y los secundarios están entrelazados obtendremos un código sumamente difícil de comprender.




Repasemos un poco de la historia de los controles de errores en los lenguajes, para entender como interfieren en la flujo de un sistemas.

El siguiente ejemplo es de C (Extraído de https://en.wikibooks.org/wiki/C_Programming/Error_handling)

Ejemplo de código en C

  
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include <stdio.h>        /* perror */
#include <errno.h>        /* errno */
#include <stdlib.h>       /* malloc, free, exit */
 
int main(void)
{
 
    /* Pointer to char, requesting dynamic allocation of 2,000,000,000
     * storage elements (declared as an integer constant of type
     * unsigned long int). (If your system has less than 2 GB of memory
     * available, then this call to malloc will fail.)
     */
    char *ptr = malloc(2000000000UL);
 
    if (ptr == NULL) {
        perror("malloc failed");
        /* here you might want to exit the program or compensate
           for that you don't have 2GB available
         */
    } else {
        /* The rest of the code hereafter can assume that 2,000,000,000
         * chars were successfully allocated...
         */
        free(ptr);
    }
 
    exit(EXIT_SUCCESS); /* exiting program */
}
 

  
#include <stdio.h>        /* perror */
#include <errno.h>        /* errno */
#include <stdlib.h>       /* malloc, free, exit */

int main(void)
{

    /* Pointer to char, requesting dynamic allocation of 2,000,000,000
     * storage elements (declared as an integer constant of type
     * unsigned long int). (If your system has less than 2 GB of memory
     * available, then this call to malloc will fail.)
     */
    char *ptr = malloc(2000000000UL);

    if (ptr == NULL) {
        perror("malloc failed");
        /* here you might want to exit the program or compensate
           for that you don't have 2GB available
         */
    } else {
        /* The rest of the code hereafter can assume that 2,000,000,000
         * chars were successfully allocated... 
         */
        free(ptr);
    }

    exit(EXIT_SUCCESS); /* exiting program */
}
Antes de nada recordemos algunas cosas de C.

  • C no tiene un tipo booleano, cualquier cosas diferente de cero es verdadero y cero es falso.
  • C solo permite pasar parámetros por valor, si queremos modificar el valor contenido en una dirección de memoria tenemos que pasarla mediante un puntero (que a su vez se pasa por valor).
  • Las funcionen de C generalmente devuelve un puntero a un resultado, o cero en el caso que no hayan tenido éxito, también pueden devolver un valor numérico y devolver un resultado en un putero pasando como parámetro.

Esto genera un problema puesto que una función tiene que devolver dos cosas, primero indicar si ha tenido éxito o no, y después un valor con el resultado. Una funciona solo y exclusivamente debiera devolver su resultado, que falle por cualquier motivo, es una circunstancia excepcional que no debiera estar involucrado en su resultado.

C no tiene un control de errores establecido como otros lenguajes modernos, esto es que si ocurre un fallo, posiblemente no nos demos cuenta (si no lo revisamos), y el programa siga corriendo hasta que comience a suceder cosas extrañas y falle al querer acceder a una posición de memoria prohibida (lo que se conoce por como segment fault). Esto puede ocurrir bastante lejos, en líneas de código y en tiempo de ejecución, desde donde está realmente el problema, con lo que es terriblemente difícil diagnosticar este tipo de problemas.

Lo anterior provoca que tenga validarse el resultado de cada función antes de poder usarlo, con lo que después de cada llamada, debe hacer una serie de sentencias de verificación. veámoslo en el ejemplo:

Análisis de la preservación de memoria

  
1
2
3
4
5
6
7
8
char *ptr = malloc(2000000000UL);
 
if (ptr == NULL) {
    perror("malloc failed");
    /* here you might want to exit the program or compensate
       for that you don't have 2GB available
     */
}
 

  
char *ptr = malloc(2000000000UL);

if (ptr == NULL) {
    perror("malloc failed");
    /* here you might want to exit the program or compensate
       for that you don't have 2GB available
     */
}
El objetivo es reservar un número determinado de memoria, la función nos devolverá NULL (que es una constante para cero) o una dirección de memoria en caso contrario, aunque el ejemplo es trivial vemos que debemos comparar el resultado de la función siempre que la usemos para estar seguros que se nos regresa una valor apropiado. si usamos la función N veces, debemos comprobar el resultado N veces y prácticamente así con todas las funciones que lleguemos a usar. Esto fue una práctica común de programación durante mucho tiempo.

El problema es que nuestro código se llenaba de condicionales (if) para verificar resultados y no solo eso sino que de alguna forma las funciones que creábamos debieran considerar esas situación, con lo que estamos obligados a implementarlas de igual manera, es decir debíamos devolver de alguna forma, ya sea como resultado de la función o como un parámetro de salida (en forma de puntero), si la función a tenido éxito y por otro lado el resultado.

Una vez que hemos establecido que debemos comprobar si una funciona ha tenido éxito antes de poder usar su resultado, tenemos el problema de que debemos hacer si ha fracaso y como comunicar al usuario el motivo del fracaso. Evidentemente debemos interrumpir el flujo del programa, pudiéramos volver a intentarlo o podríamos retornar a la función llamadora, en cualquier caso debemos usar una estructura if, de forma más o menos complica, y que en un determinado momento, haría que nuestro código se convergiera en código espagueti.

En lenguajes más modernos las situaciones inesperadas o errores se controlan fuera del flujo principal, son las llamadas excepciones, cuya estructura en general es parecida a esta (esta es en c#, sacado de https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/exceptions/)

Ejemplo de código en C#

  
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class ExceptionTest
{
    static double SafeDivision(double x, double y)
    {
        if (y == 0)
            throw new System.DivideByZeroException();
        return x / y;
    }
    static void Main()
    {
        // Input for test purposes. Change the values to see
        // exception handling behavior.
        double a = 98, b = 0;
        double result = 0;
 
        try
        {
            result = SafeDivision(a, b);
            Console.WriteLine("{0} divided by {1} = {2}", a, b, result);
        }
        catch (DivideByZeroException e)
        {
            Console.WriteLine("Attempted divide by zero.");
        }
    }
}
 

  
class ExceptionTest
{
    static double SafeDivision(double x, double y)
    {
        if (y == 0)
            throw new System.DivideByZeroException();
        return x / y;
    }
    static void Main()
    {
        // Input for test purposes. Change the values to see
        // exception handling behavior.
        double a = 98, b = 0;
        double result = 0;

        try
        {
            result = SafeDivision(a, b);
            Console.WriteLine("{0} divided by {1} = {2}", a, b, result);
        }
        catch (DivideByZeroException e)
        {
            Console.WriteLine("Attempted divide by zero.");
        }
    }
}
La estructura es try/catch/finally. lo interesante de esta estructura es que mantiene el flujo principal de la proceso dentro de la instrucción try, mientras que la gestión de errores y situaciones anormales se mantiene dentro de instrucciones catch (una por cada tipo de excepción)


Analicemos la primera función

Función SafeDivision

  
1
2
3
4
5
6
static double SafeDivision(double x, double y)
{
    if (y == 0)
        throw new System.DivideByZeroException();
    return x / y;
}
 

  
static double SafeDivision(double x, double y)
{
    if (y == 0)
        throw new System.DivideByZeroException();
    return x / y;
}
El objetivo de la función es realizar divisiones evitando dividir por cero, para lo cual valida si el segundo parámetro de entrada (el dividendo, la y), es un cero. Si es cero emerge una excepción. Esta es la principal diferencia, no se devuelve un parámetro de salida o un retomo de función indicando si tuvo éxito la ejecución, el único resultado posible de la función (como tal) es la misma división, si ocurriera algo inesperado se lanzaría una excepción que interrumpirá el flujo normal de ejecución.

Analicemos la segunda función

Función Main

  
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
static void Main()
{
    // Input for test purposes. Change the values to see
    // exception handling behavior.
    double a = 98, b = 0;
    double result = 0;
 
    try
    {
        result = SafeDivision(a, b);
        Console.WriteLine("{0} divided by {1} = {2}", a, b, result);
    }
    catch (DivideByZeroException e)
    {
        Console.WriteLine("Attempted divide by zero.");
    }
}
 

  
static void Main()
{
    // Input for test purposes. Change the values to see
    // exception handling behavior.
    double a = 98, b = 0;
    double result = 0;

    try
    {
        result = SafeDivision(a, b);
        Console.WriteLine("{0} divided by {1} = {2}", a, b, result);
    }
    catch (DivideByZeroException e)
    {
        Console.WriteLine("Attempted divide by zero.");
    }
}
El bloque try como tal no tiene ningún control de errores, ni ninguna sentencia condición que altere el flujo de ejecución

Bloque Try

  
1
2
result = SafeDivision(a, b);
Console.WriteLine("{0} divided by {1} = {2}", a, b, result);
 

  
result = SafeDivision(a, b);
Console.WriteLine("{0} divided by {1} = {2}", a, b, result);
los esperado es que la función devuelva la división y que se muestre el mensaje en pantalla.

El catch maneja (de forma aparte), cualquier tipo de error en el proceso.

Bloque Catch

  
1
2
3
4
catch (DivideByZeroException e)
{
    Console.WriteLine("Attempted divide by zero.");
}
 

  
catch (DivideByZeroException e)
{
    Console.WriteLine("Attempted divide by zero.");
}
Como vemos queda perfectamente clara cuál es el proceso principal y cuáles son las excepciones, siendo además extremadamente escalable.

El único problema que pudiéramos es cuantas bloque try/catch podemos usar y donde es idóneo colocarlas. Bueno aquí tener en cuenta un principio básico y es que las funciones deben ser lo más reducidas posibles, y realizar una exclusiva tarea, así que jamás debiéramos tener que anidar dos (o más) bloques try/catch. Sobre donde debemos poner los bloques try/catch, por el mismo motivo, funciones simplificada y únicas, las funciones mas internas (o privadas) realizarían menos actividades, con lo que posiblemente tengan una necesidad menor de controlar excepciones y si de emitirlos, así que se podría decir que las capas más internas de nuestra aplicación (private, internal) emiten excepciones y las mas externas los controlan.

Hasta ahora hemos repasado situación de control de errores o excepciones, situaciones anómalas, ¿Qué pasa cuando no son situaciones anormales, si no que con simplemente caminos a tomar, correctos, pero fuera del flujo principal de nuestra aplicación?

El objetivo primordial a evitar sigue siendo el mismo, evitar las sentencias condicionales y crear un camino claro hacia el flujo principal.

Aquí entra en juego la programación orientada objetos:

  • Mediante la herencia la POO, nos permite personalizar acciones, cada camino puede ser un objeto que sobrescriba las diferencias entre una necesidad (un camino y otro).
  • Mediante El principio de la Inversión de Control, nuestro camino principal está definido claramente, pero delega en objetos (que generalmente se establecer por Inyección de Dependencias), para realizar sus acciones. Es decir si queremos cambiar el comportamiento solo tenemos que cambiar el objeto concreto que realiza las opciones.

Por ejemplo, pensemos que estamos implementando el sistema de una tienda, El flujo principal podría ser, que el cliente selecciona un objeto y lo paga, con el pago pudiera haber una bifurcación según el medio de pago (efectivo, tarjeta, cheque), pero el flujo principal seria siempre el mismo, solo usaríamos un objeto que procesa el pago acorde a la necesidad presentada.

La regla final seria "Primero el camino principal luego las excepciones", esto aplicaría no solo a efectos de código, sino también a efectos de diseño.

domingo, 24 de septiembre de 2017

Roles principales en el desarrollo de software, una comparación tradicional-ágil.


Un “rol”, es una figura que adquiere una persona y que conlleva ciertas responsabilidades o comportamientos asociados. En el desarrollo de software, los integrantes de un equipo adquieren rol o roles según la función que desempeñen. El rol es independiente del puesto (o por lo menos no hay ningún motivo para que dependa de él) y una misma persona puede desempeñar varios roles al mismo tiempo.

En el desarrollo de software tradicional hay una serie de roles bien marcados. los analizaremos uno a uno y compararemos su función en metodologías tradicionales con el desarrollo de software ágil. Los roles a considerar son: Cliente, Líder de proyecto, Analista, Arquitecto, Diseñador, Programador y Tester.




Cliente


Es la entidad (o persona representante) que tiene la necesidad de un sistema o software nuevo (o de la modificación de uno ya existente). Una parte importante relativa al cliente es el usuario operador, que es la persona concreta que va a utilizar el sistema. Puede, y es además bastante probable, que el cliente o dueño final del sistema a construir no sea la misma persona que el usuario.

Líder de proyecto


Es el responsable (directo) de llevar a buen término el proyecto, por lo menos de parte de la empresa constructora del software. Debe coordinar al resto del equipo para que trabajen en armonía y eficientemente, también es el responsable de concretar las fechas de entregar de cada fase y garantizar que son cumplidas. El resto de personas del equipo trabajan bajo su dirección

Analista


Es rol encargado de analizar las necesidades del cliente en cuanto a su futuro sistema. Debe tener la capacidad de concretar todo lo expuesto por dicho cliente en una colección de requisitos a construir, además debe entender el negocio del cliente (que seguramente le es ajeno) y poder hablar su mismo "idioma". Una de las partes mas complicadas del rol de analista, es poder asumir que el mismo cliente no comprende completamente sus necesidades, ni las múltiples soluciones software a este. El cliente generalmente solo va a querer automatizar el trabajo que ha estado haciendo hasta ahora y va a mostrar una gran resistencia al cambio y a su forma de trabajar. Pero el problema es que cambio es realmente necesario, sino no necesitaría un nuevo sistema software. Si el analista no es capaz de comprender esto, el desarrollo se alargara mucho más tiempo del calculado, y creara una sensación de insatisfacción tanto del cliente, como del equipo constructor, que sentirá esta andando en círculos y sin sentido durante todo el proyecto.

Arquitecto


El rol de arquitecto es uno de los mas confusos que existen (asumiendo a veces roles de programador o diseñador), pero en esencia debe encargarse del sistema a un nivel "macro". Debe asegurar que el sistema encaja perfectamente en el resto de sistema de la empresa, que se esta construyen con las patrones, metodólogas y herramientas adecuadas, garantizando que el sistema presenta una continuidad de negocio con el escenario actual de la empresa y que está preparado para adaptarse a los futuros , y posiblemente desconocidos, cambios venideros. Además debe detectar los componentes reutilizables de un sistema (que son mas de los que a priori se cree) y asegurase que son creados, utilizados y conocidos, por el resto del equipo.

Diseñador


Es el responsable de convertir los requisitos obtenidos por el analista, en "algo" que se pueda traducir en software, mediante las guías e indicaciones de la arquitectura de software. Se encarga de definir el sistema en un nivel detallado y concreto. El diseñador indicara que componentes y de qué forma se relacionan dentro del sistema y en la lógica de negocio.

Programador


Es el constructor efectivo del sistema, resolviendo los requisitos mediante código fuente, que a su vez es convertirlo en dicho sistema. Debe ser experto en las tecnologías es las que está trabajando, aunque es frecuente que a veces se auto capacite según las necesidades del sistema, con éxito disparejo. Debe tener capacidad de participar en un escenario colaborativo con el resto de los codificadores

Tester


Es el encargado de validar que el software cumpla con los criterios de calidad establecidos, es decir que el software funcione. Se puede considerar que un software sea de “calidad”, en diversos aspectos incluyendo el aspecto el grafico (interacción con el usuario), el funcional (que cumpla lo requerido), o el operativo (que siga funcionando en determinadas circunstancias como carga excesivas o situaciones de uso “anormales”).

Roles en metodologías de desarrollo de software tradicionales


En las metodologías de software tradiciones, se sigue una secuencia de fases más o menos estricta (que puede repetirse en varias vueltas o ciclos). Cada fase tiene una entrada y una salida, en las primeras fases (análisis y diseño), se convierte las requisitos de usuarios, en documentación, en las fases de construcción, se “convierte” la documentación en software.

Cliente


Curiosamente el cliente no tiene gran relevancia en las metodologías tradicionales, interviene al principio de la fase de desarrollo (en la que es entrevistado), y al final al recibir el sistema construido. Pocas veces se le considera como parte del flujo de desarrollo de software, salvo por reuniones puntuales que no tienen más objetivo que garantizar que el software se sigue construyendo.

Líder de proyecto


Uno de los roles más importantes. De su capacidad de organización y gestión dependerá el éxito del proyecto. Debe coordinar al equipo para llevar a cabo el objetivo, que es la generación del sistemas en tiempo y forma.

Analista


Debido a que es el iniciador del proceso y es el que extraerá los requisitos directamente del cliente, su importancia es alta. Una mala interpretación del analista puede ocasionara una desviación importante en el proyecto. El análisis es una fase que suele alargarse bastante puesto que el cliente no suele estar presente en otras fases del desarrollo.

Arquitecto de software


En las metodologías pesadas el papel del arquitecto de software no es tan relevante, no porque la arquitectura no lo sea sino porque generalmente el sistema está pensando más como un elemento único y no se tiene un enfoque parecido a una fábrica de software. En este escenario el diseñador toma parte de las responsabilidades del arquitecto.

Diseñador


El diseñador es el puente entre el analista y los programadores, traducirá los "requisitos", en definiciones y diagramas, para que posteriormente sean convertidos en código fuente. La coherencia de sus diseños, se trasladara al sistema final, si sus diseños no son coherentes, se acara teniendo un motón de código fuente que tiene sentido como piezas individuales, pero no como un sistema integral.

Programador


Como mencionamos el programador convertirá la documentación obtenida hasta este punto en código fuente, aquí tomara decisiones relativas a la optimización, la estructuración, o a la resolución de problemas concretos relacionados con el lenguaje o la tecnologías seleccionadas.

Tester


Es parte de las secuencias de fases del desarrollo. una vez construido el sistema se pasa a pruebas y se devuelve si no son satisfechas.

Roles en metodologías de desarrollo de software agiles


Para la resolución de metodologías agiles hay varios enfoques, pero todos alzan el valor del software productivo (ya construido), y las iteraciones humanas, frente a los procesos y la documentación.

Cliente


El cliente es el elemento más importante y se considera como parte del equipo del trabajo. Hay que tener en cuenta que todo el significado y motivo de creación del sistema, es la resolución de una necesidad el cliente. El cliente interactúa de diversas formas y frecuentemente en todas las fases del proyecto

Líder de proyecto


Las responsabilidades dentro de un modelo ágil están distribuidas en un modelo mas "plano", dividiendo dichas responsabilidades entre todos los participantes del proyecto. Si bien es necesario alguien que se encargue de llevar el seguimiento y control, no suele tener un puesto diferente al del resto de sus compañeros.

Arquitecto de software


Es el que define las políticas técnicas en cuento a la creación de software, aunque más que políticas son una caja de “herramientas” de soluciones que funcionaron en determinadas circunstancias y son reusables en el futuro, para otros proyectos. Aquí se incluye el uso de patrones de software, librerías y framework. El arquitecto debe vigilas que se apliquen las políticas adecuadas en cada momento, pero con la flexibilidad necesaria para adaptar, crear o derogar políticas según sea necesario.

Analista


El contacto con el cliente es, con diferencia, mucho más frecuente en las metodologías Ágil, lo cual hace que el rol de analista sea menos crítico. A diferencia de las metodologías pesadas, se asumen una incapacidad inicial de comprender la necesidad del cliente, dicha capacidad es suplida con entregas realizadas en corto tiempo, que aportan cada vez más funcionalidad a la solución fácil, hasta que tanto cliente como desarrolladores están conformes con el resultado.

Diseñador


La formalización del diseño (en documentación) no es tan importante, debido a que si hay una buena colección de estándares, guías y arquitecturas, debiera quedar implícitamente la forma de resolver un problema. En las metodologías agiles se asumen que los requisitos van a cambiar con lo que cualquier propuesta de software debe asumir ese cambio y estar preparado. Es decir en lugar de crear un software que solucione un problema, hay que enfocarse en un software cuyo cambio no tengan un impacto excesivo en el sistema.

Programador


Los programadores son la parte más relevante en las metodologías agiles, en la que frecuentemente suele complementar su roles de programador, con los de analista y diseñador. Esto es debido a que como no hay una excesiva documentación, la necesidades de comunicación son más directas e inmediatas. Es esencial la comunicación oral y el trabajo en equipo. De igual forma se necesita que los desarrolladores tenga experiencia, sean respetuoso de los estándares y que conozcan todo las herramientas reutilizables que tienen a su disposición .

Tester


Las pruebas en las metodologías agiles de hacen de forma más frecuente y continuada, no es necesario que esté terminado el sistema para comenzar pruebas. El software está diseñado de forma que se pueda probar unitariamente e incluso podría usarse un desarrollo orientado a pruebas, esto es crear primero las pruebas y dar por satisfecha la construcción del sistema cuando todas las pruebas tengan existo. Como vemos las pruebas pueden comenzar desde muy temprano en un desarrollo ágil.

¿Cual usar?


Frecuente se dice que si tu proyecto es grande uses una metodología pesada, y si es pequeño una ágil. En lo personal no estoy de acuerdo con este razonamiento, porque parece más un temor a una metodología nueva que otra cosa, al considerar que si el proyecto es pequeño se podría corregir cualquier desviación. Yo creo que lo que define cual metodología usar, es la madurez y la capacidad de comunicación y colaboración del equipo, cuanto mayor sea mayor es la posibilidad de éxito con una metodología ágil, cuanto menor sea es mejor el uso de una metodología tradicional que garantiza que cada paso está siendo perfectamente guiado y documentado.

martes, 11 de julio de 2017

Curso Patrones de Software


En las últimas semanas, he estado impartiendo un curso de “Patrones de Software”, en el cual pretendía explicar una colección de patrones, a la vez que se establecían las bases para el desarrollo de un código empresarial sostenible y de alta calidad.

Los materiales del curso están en GitHub. Esperando que pudieran ser de utilidad, lo hago de código libre (tanto los ejemplos como la documentación). Se les puede dar el fin que se desee.



El temario del curso fue el siguiente:



Acerca de este documento


La misión de este documento es exponer los objetivos, mecánica y temerarios planteados para el curso de CreSer “Patrones de Software”.


Objetivos del curso


Los patrones de software son soluciones previamente establecidas (y probadas como optimas) a problemas conocidos y repetitivos dentro del desarrollo de software.

El curso de “Patrones de Software” pretende proporcionar herramientas para crear un escenario en el que se favorezca la creación de software de calidad, escalable y funcional.

A la vez que se revisan conceptos básicos para la ingeniera de software (para establecer un contexto de inicio) se estudiaran una selección de los más útiles patrones de software. Por otro lado y a modo de complemento se revisar una colección de los “peores” patrones de software, o anti-patrones (comportamiento y metodologías perjudiciales para la construcción de sistemas), de forma que sirva como elementos comparativo.


Metodología


Se distribuirá el curso en tres sesiones de tres horas cada una en las cuales, después de una introducción de los temas a plantear, se realizan talleres prácticos de los patrones de software, los cuales serán realizados en equipos de a dos ( dos personas compartiendo una computadora ).

Requisitos


Es necesario una laptop por cada dos personas, y tener conocimientos promedios de programación en C#. La computadora debe tener instalador Visual Studio 2010 o superior.




  • Les proporcionare igualmente un script de Ruby para facilitar la configuración a base de datos, si desean usarlo, deben instalarse Ruby, si no deberán hacer la configuración manualmente.


Temario


  1. Presentación de objetivos

  2. El desarrollo de software en la empresa

  3. En este apartado se trataran temas propios de la ingeniera de software y el desarrollo de software en la empresa con intención de establecer un contexto previo

    a) Acerca de la ingeniería de software


    b) Escenarios dentro del desarrollo de software


    c) Construcción de una fábrica de software


  4. Acerca de los programación orientada a objetos

  5. Se analizara los principios básicos de la programación orientada a objetos, para establecer los fundamentos solos los que se sustentan los patrones de software

    a) Principios generales de la orientación a objetos


    b) Principios SOLID



  6. Introducción a los patrones de software


  7. Breve introducción a los patrones de software, su origen y su utilidad.

    1. Breve historia de los patrones de software.
    2. Tipos de patrones de software.
    3. Anti patrones de software.

  8. Explicación de patrones de software

  9. Hediondez del código


  10. En este apartado se trata la “hediondez del código”, un concepto por el cual un software que aparentemente funciona bien, oculta graves problemas en su interior que pueden emerger en cualquier momento. Se revisaran los siguientes conceptos


  11. Anti-patrones de software

  12. Los anti-patrones de software son la mejor forma de hacer algo mal. Aquí se estudiaran con intención de evitarlos.


    Los anti-patrones para estudiar a:

    • Base de datos como comunicador de procesos.
    • Clase Gorda.
    • Re-dependencia.
    • Acoplamiento secuencial.
    • Modelo de dominio anémico.
    • YAL (Yet Another Layer, y otra capa más).
    • Ancla del barco.
    • Código espagueti.
    • Reinventar la rueda.
    • No inventado aquí.
    • Otra reunión más lo resolverá.
    • Proyecto del día de la marmota.
    • Si funciona, no lo toques.

  13. Conclusiones

jueves, 13 de abril de 2017

Referencias Culturales: Inyección de código en Rick y Morty


La inyección de código es una vulnerabilidad, por la cual un atacante consigue ejecutar un código malicioso en nuestro sistema, introduciéndolo, generalmente, a través de una entrada de datos de dicho sistema.

En el primer capítulo de Rick y Morty de la tercera temporada ( Rick & Morty - S03E01 - Rickson Break ), se ve claramente un ejemplo Inyección de código, con fatales consecuencias (para los enemigos de Rick).



Rick y Morty, es una serie de animación para adultos de Adult Swim, en la que un joven y su abuelo inventor viven aventuras a través de distintos lugares y dimensiones. En este capítulo Rick fue capturado y le están haciendo una exploración dentro de su mente (a través de sus recuerdos), para recuperar cierta información que quieren obtener sobre uno de sus inventos.

En la escena ocurre algo parecido un escalamiento de privilegios, hasta ser root , por no validar los datos de una entrada. Veamos la parte de la inyección de código en cuestión:






Bola extra. Otro ejemplo de inyección de código en Rick y Morty.

En el capítulo cuarto de la primera temporada (Rick & Morty - S01E04 - M. Night Shaym-Aliens ! ), hay una escena parecida de inyección de código, en la esta vez se intenta obtener información sobre un combustible creado por Rick. Rick es puesto en una simulación holográfica, e intentando escapar de ella acaba creando el combustible, los enemigos de Rick intentan recrear la formula sin antes hacer algún tipo de comprobación. La moraleja es “Cualquier entrada es siempre maliciosa, valida tus datos”.




lunes, 3 de abril de 2017

Diferencias entre Ciencias la Computación, Ingeniera y Arquitectura de Software


Para referirnos a nuestra profesión (o a aspectos de ella) de una forma más o menos formal, solemos usar términos como “Ciencias de la computación”, “Ingeniería de Software” o “Arquitectura de Software”, muchas veces de manera intercambiada, y sin distinguir entre cada uno de ellos. En el siguiente artículo voy a establecer puntos que nos ayuden a diferenciarlos entre sí.

Dejemos claro para comenzar que el objetivo de nuestra profesión es hacer sistemas software. Ese es el punto que no debemos perder de vista, y debemos enfocarlo todo a través de él.

¿Qué es un sistema?


Un sistema, para la finalidad que nos ocupa, es un conjunto de elementos, de diversa índole, que trabajan juntos para poder conseguir un objetivo.

¿Qué es un sistema software?


Es un conjunto de elementos (principalmente) software y de otra índole que interactúan entre sí para ofrecer una funcionalidad.

En cuento al software puede incluirse aplicaciones, servicios, portales web, programas propios o desarrollo de terceros.

¿Incluye los sistemas software, la parte hardware? Si. Es imposible ver un sistema software como algo aislado del hardware. El software está diseñado para instalarse en determinado hardware (Teléfonos, PC, MAC, servidores…) y en determinada estructura (como ciertas redes, o arquitecturas cliente servidor, o incluso modelos menos convencionales).

¿Los usuarios son parte del sistema? Si, en el sentido que estos cumplen roles que contempla el software. Es necesario diseñar siempre el software desde el punto de vista de las necesidades de los usuarios.

Ante de nada…


En cuento a las definiciones aquí expuestas, no son tratadas de forma académica, o dicho de otra forma no se busca empatar los conceptos con títulos universitarios ya que estos varían en cada país y muchas veces usan los términos equivalentes o casi equivalentes.

En este artículo se busca obtener en cada definición un fin práctico, casi laboral.

Arquitectura de Software


La arquitectura de software se encarga de la creación de sistemas, enfocándose en los puntos:

  • Formaliza el cómo se debe diseñar un sistema.
  • Define (identifica) los componentes de un sistema, las relaciones entre ellos y su funcionalidad.
  • Las conexiones con otros sistemas.
  • La escalabilidad y la capacidad de mantenimiento de un software.
  • La sencillez en general de un código.
  • Identifica los puntos reutilizables de un código, y crea un escenario donde esto es posible.

La importancia de una arquitectura es que garantiza que el código de un sistema siga siendo compatible “hacia atrás” (lo existen), y “hacia delante”, hacia las necesidades futuras y todavía no determinadas. En otras palabras posibilidad el cambio.

Ingeniera de Software


La Ingeniera del Software abarca más puntos y de índole más diversa que la Arquitectura de Software, o dicho de otra forma la Arquitectura de Software es un punto más, perteneciente a la Ingeniera de Software.

La Ingeniera de Software abarca todos los procesos de la creación de software, desde la identificación apropiada de las necesidades, hasta más allá de la creación de un sistema como producto ("más allá", porque la ingeniera de software no acaba con la entrega del mencionado sistema).

Los puntos en los que se enfoca la Ingeniera de Software son los siguientes:
  • Definir la metodología de trabajo. Dicha metodología puede varias según las necesidades del cliente, equipo (de trabajo), o producto.
  • Gestionar los proyectos, así como de los recursos asignados (que pueden ser humanos, físicos, o económicos)
  • Define apropiadamente todas las fases del proyecto, y las necesidades y objetivos de cada uno.
  • Define los roles a cumplir en el proyecto.
  • También se encarga de la relación (en varios niveles) con el cliente y de detectar sus necesidades.
  • Como ya mencionamos la arquitectura del software es gestionada como un subconjunto.
  • En general, define de manera formal todos los pasos de creación y mantenimiento de un sistema, abarcando no solo aspectos tecnológicos, sino de procesos y de gestión.

Ciencias de la computación


Las ciencias de la computación pertenecen a un rublo diferente a los anteriores, su fin es menos práctico y más teórico, enfocándose más a asuntos académicos y de investigación y menos en conseguir un producto funcional.

Las ciencias de la computación se encarga de descubrir que es lo “que se puede hacer” y “por qué”. La ingeniería se encarga de encontrar un sentido práctico, funcional e incluso laboral, al conociendo obtenido.