Todas las entradas de: laparca

Curso de plantillas en C++: uso en funciones

Antes de continuar con las funciones quedó pendiente la sintaxis de las plantillas:

template < LISTA_DE_LA_PANTILLA > SOBRE_LO_QUE_APLICA_LA_PANTILLA

Bueno, creo que está claro: se usa la palabra reservada template y entre brakets (los simbolitos ‘<‘ y ‘>‘) se pone la lista de cosas que queremos que se puedan hacer plantilla. Finalmente va la parte sobre la que vamos a aplicar la plantilla, que puede ser una estructura o unión, una clase, una función, un renombrado de tipo con using (a partir del estándar de 2011) o una declaración de variable (a partir del estándar de 2014).

Y con esto se termina esta breve explicación. El resto lo iremos viendo con ejemplos.

Ahora sí: Funciones

En el momento en el que podemos tener tipos como plantillas surge el problema de poder usarlos en funciones. En este punto la lógica es sencilla, si tengo que pasar a una función un tipo que es una plantilla, la función también debe ser una plantilla.

template<class T> struct mi_tipo {
   /* Da igual lo que vaya aquí para el ejemplo */
};

template<class T>
void una_funcion(mi_tipo<T> valor) {
   /* Esta funcion aceptará cualquier mi_tipo */
}

De este modo, desde una_funcion tendríamos acceso a todas las funciones miembro y variables miembro de mi_tipo. A partir de aquí lo usaríamos de forma normal como cualquier otra variable.

Hay otra forma de crear la plantilla para que acepte cualquier cosa:

template<class T>
void otra_funcion(T valor) {
   /* Esta funcion aceptara cualquier tipo, incluido mi_tipo */
}

Con este código, otra_funcion acepta cualquier tipo de dato. Si intentamos acceder a alguna variable miembro o función miembro de valor el compilador dará error si no está implementada en el tipo T.

Un ejemplo práctico

Hasta ahora hemos visto un poco por encima cómo hacer plantillas sencillas, pero toca ver algo que tenga alguna utilidad. Por eso vamos hacer una clase que implemente un array que pueda devolver su tamaño con una función (como en java).

Actualmente ya existe una clase que hace eso en el estándar. Es std::array.

/* Este codigo es compatible C++03 */
#include <iostream>
#include <stdexcept>

template<class Tipo, int Tam> class array {
   public:
      Tipo& operator[](int p) {
         if (p >= Tam) throw std::out_of_range(std::string("Te saliste del rango"));
         return valores_[p];
      }
      int tam() const {
         return Tam;
      }
   private:
      Tipo valores_[Tam];
};

int main() {
   array<int, 5> valores;
   for (int i = 0; i < valores.tam(); i++)
      valores[i] = i*2;

   for (int i = 0; i < valores.tam(); i++)
      std::cout << valores[i] << std::endl;

   return 0;
}

En las siguientes entregas profundizaremos más en lo que se puede hacer con las plantillas.

Curso de plantillas en C++: primeros pasos

En la entrada anterior introdujimos por encima las plantillas. Seguro que generó más dudas que otra cosa, así que vamos a empezar a ver chicha código.

¿Qué resuelven las plantillas?

En lenguajes como C o Pascal implementamos algo tan básico como una lista enlazada tal que así:

/* Caso 1: Lista para un tipo concreto */
struct ListaPersona
{
   struct Lista* siguiente;
   /* El dato almacena un elemento de la lista puede ser un puntero (suele serlo */
   Persona dato;
};

/* Caso 2: Lista genérica para cualquier cosa */
struct Lista
{
   struct Lista* siguiente;
   void* dato;
};

En el caso 1, el problema es que hay que hacer una implementación por cada tipo de dato que queramos. Esto es bastante esfuerzo y, sobre todo, mucho trabajo de mantenimiento en caso de problemas.

En el caso 2 podemos hacer cualquier cosa, lo que puede acarrear problemas. Estos problemas vendrían, principalmente, por no poder validar el tipo de los datos que almacenamos. Por ejemplo:

/* No hagáis esto en casa */
typedef void*(*crea_instancia_t)();
int i;
struct Lista* lista;
crea_instancia_t crea_instancia[3] = { crea_persona, crea_pedido, crea_cualquier_cosa_guay };

for (i = 0; i < 100; i++)
{
   lista = lista_agregar(lista, crea_instancia[rand()%3]());
}
/* ¿Qué hay en cada entrada de la lista? */

Vale, es muy cogido por los pelos, pero siempre puede pasarnos que nos despistemos al programar y metamos lo que no es. Y lo mejor, depura a ver qué ha pasado.

¿Cómo resolvemos esto en C++?

Esta sería la parte más sencillas de las plantillas de C++. En este caso, lo que podemos hacer es poner algo que identifique el tipo que queremos y se sustituya por el que queremos:

template<typename T>
struct Lista
{
   Lista* siguiente;
   T dato;
};

Ya está, ahora ya podemos aprovechar las ventajas del sistema de tipos para garantizar que metemos en la lista los tipos adecuados.

Ojo, a efectos prácticos es lo mismo que hacer una versión para cada tipo que le indiquemos a la lista. El compilador genera una versión para cada tipo.

En la próxima entrada empezaremos a ver cómo se integran los tipos de plantillas para pasarlos a funciones.

Curso de plantillas en C++: qué son las plantillas y para qué sirven

Las plantillas en C++ son un mecanismo que nos pertite crear tipos y funciones dependientes de otros tipos y valores no definidos. El mejor modo de entenderlo es pensando en un formulario, por ejemplo, el típico formulario de una administración pública, donde hay un montón de texto y huecos que rellenar. Cada hueco recibe un valor. La plantilla es parecido, es definir un tipo o función donde dejamos huecos para ser rellenados más tarde.

Según wikipedia:

Templates are a feature of the C++ programming language that allows functions and classes to operate with generic types. This allows a function or class to work on many different data types without being rewritten for each one.

Fuente: Wikipedia

La utilidad principal de las plantillas es poder crear código genérico. Un caso típico son los contenedores (listas, arrays, mapas, etc.). Sabemos que en C hay dos formas de desarrollar listas:

  1. Almacenando la información con un puntero tipo void* o
  2. Haciendo una implementación nueva para cada tipo que necesitemos.

El primer modo tiene la ventaja de poder contener cualquier cosa, pero a cambio, no hay control de tipos. Esto podría implicar problemas a largo plazo si no se programa con cuidado.

El segundo mecanismo tiene la ventaja del control de tipado, pero incrementa de forma lineal el tiempo de desarrollo y mantenimiento. Si tenemos un problema en una función y está implentada para 10 tipos, habría que cambiarla 10 veces…

Otra de las cosas que nos ofrecen las plantillas es la capacidad de metaprogramar y realizar acciones en tiempo de compilación.

En las prómimas entradas vamos a ir viendo en detalle ejemplos de plantillas en C++03 y, finalmente, veremos qué cambios se han ido introduciendo las versiones modernas.

El desarrollador como impedimento de Scrum

No hace mucho tiempo que hemos empezado a utilizar Scrum como framework para los desarrollos que hacemos en el trabajo. En este corto periodo de tiempo he visto mucha información sobre los problemas en las grandes empresas en la adopción de Scrum (renombrado de roles, «flexibilización» de Scrum, etc.). No voy a entrar en detalles en esto porque ya hay mucho.

Un problema que he visto es que no sólo la empresa puede ser un obstáculo para la adopción de Scrum (y cualquier sistema ágil), sino que los propios desarrolladores pueden ser un obstáculo.

En las metodologías clásicas, lo desarrolladores suelen ser tratados como meros obreros que ponen ladrillos.  Concretamente, los desarrolladores suelen recibir instrucciones muy precisas de lo que deben hacer, sin posibilidad de intervención de ningún tipo. Con las metodologías ágiles, esto cambia y los programadores pasan a formar parte del proceso creativo. Este cambio de paradigma puede hacer que mucho programadores (especialmente lo que lleven más tiempo) se sientan incómodos ya que ahora tienen que hacer más funciones. Éstos desarrolladores, si no quieren adaptarse, pueden convertirse en un impedimento importante en la estrategia de paso a un modelo ágil.

Y tú, ¿has tenido algún problema similar?

Como hacer que tu código sea legible

Reconozco que no soy el mejor programador del mundo, pero intento mejorar cada día. Puede sonar tonto, pero una de las mayores complicaciones del trabajo de un programador es conseguir que el código que escribes un día sea entendible por otras personas !y nosotros mismos! en el futuro.

Casi todo lo que voy a contar a continuación lo he aprendido a partir de diversas lecturas y por propia experiencia.

Algunos de los consejos más útiles que creo que son importantes tener en cuenta son:

Utiliza el inglés al programar

Puede resultar extraño para algunos que dominando un idioma (español, portugués, francés, etc.) tengan que programar en inglés. El motivo es sencillo, la mayor parte de la documentación en nuestro sector está en inglés por lo que acabamos mezclando idiomas. ¿A quién no le ha pasado tener funciones llamadas setPeso o getNombre?

Por otra parte, el inglés permite abrirnos puertas en dos sentidos:

  • Para empezar como programadores que dominemos el inglés podremos acceder a más puestos de trabajo. La globalización no es sólo para las empresas; nosotros también podemos aprovecharnos de ella.
  • Podemos contratar programadores extranjeros que no tendrán problemas al leer el código.

Utiliza normas de codificación

Esto en algunas metodologías se llama gestión de configuración del software. También se puede encontrar como convención de codificación (code convention) o guías de estilo. La idea es tener una serie de normas que indica cómo debe ser la nomenclatura de codificación y tabulación para que todo el desarrollo sea consistente. Con esto conseguimos un programa más homogéneo y fácil de entender.

Tampoco es necesario inventarse una propia porque ya existen muchas. A modo de ejemplo:

Elige bien los nombre

La verdad es que el tema de los nombres es uno de los más complejos. Sobre todo para funciones y clases. Por norma los nombres de métodos, funciones, clases, etc. deben ser descriptivos y breves. Por ejemplo, un nombre como ApplicationModelAbstractObjectFactory quizá sea un poco largo, pero parece descriptivo. Igualmente, algo como EntraSola (caso real) es muy breve, pero no dice nada; no es posible determinar realmente su funcionalidad (además, puede inducir a tener pensamientos impuros).

Utiliza comentarios

Este punto es bastante controvertido porque mucho programadores piensan eso de «el código bien escrito se entiende solo». Y es verdad. El problema es cuando el código no hace lo que toca.

No hay que llenar el código de comentarios, deben ser lo mínimo posible y sólo para cosas que claramente lo necesiten. Esto suele ser para aquellas construcciones que sean muy abstractas y que cueste mas comprender.

Se organizado

Otro punto que quizá no sea obvio, pero es cierto que tener bien organizado el código ayuda mucho a su legibilidad. Es más fácil entender una función que está dividida en partes (preparación de datos, realización de acciones, generación de resultado) que otra en la que se vayan mezclando todas estas tareas.

Refactoriza

Por mucho que nos esforcemos en que nuestro código sea claro y legible, con el tiempo acabará por convertirse todo en algo caótico. Es normal, porque conforme evoluciona un software se añaden nuevas funcionalidades para las que no estaba inicialmente pensado.

Para evitar esto hay que hacer ciclos de refactorización que mantengan el código limpio y optimizado.

Conclusiones

Está claro que una parte importante para evitar la deuda técnica reside en la calidad del código un código más claro facilita el mantenimiento al poder ser entendido fácilmente (ojo, que a parte de esto es necesario un buen diseño del mismo). El uso de unas normas que permitan que éste sea entendible ayudan mucho para evitar este problema.

Finalmente me gustaría comentar que este artículo es meramente introductorio y faltan muchas cosas que contar. Estoy más que seguro que habrá discrepancias con algunas cosas; si es así, te animo a que dejes un comentario.

Bibliografía

La mayor parte de lo que he expuesto aquí procede del libro The Art of Readable Code de O’Relly.

Errata

  • 2017-02-01: Corrijo la parte de la deuda técnica a sugerencia de Fioddor.

Perezosidad

Algo que me ha sorprendido mucho de haskell y, que aún me cuesta mucho lidiar con ello, es el hecho de que sea perezoso.

¿Y eso qué significa?

Significa que no va a ejecutar las cosas sin más, sino que se esperará hasta que no le quede más remedio.

Por ejemplo, si tenemos algo como esto:

Como se puede ver, se «crea» una lista de infinitos elementos que empiezan en 1 (intinite_list). Como no accedemos a ningún elemento, el compilador en realidad no crea nada en memoria.

Luego indicamos que queremos elevar todos los elementos al cuadra para después quedarnos con aquellos que no son múltiplos de dos ni de tres. Eso lo almacenamos en values. En haskell esto significa que values representa esa operación, no que contenga dichos valores, así que aquí tampoco ha hecho nada: no ha elevado los infinitos elementos al cuadrado ni ha filtrado después.

Lo último que hacemos es un take 10. En este momento (si utilizamos ghci), en este momento intenta mostrar los 10 primeros elementos, lo que fuerza a hacer todo lo anterior. ¡Pero sólo lo hace sobre los 10 primeros! Ni elevamos la lista entera al cuadro, ni comprobamos los infinitos elementos. Sólo vamos a hacer esto las veces justas.

Esto lo hace todo el compilador por su cuenta.

¿A dónde quieres ir a parar?

Pues estaba hace poco mirando documentación de Rust y me he encontrado con algo muy parecido:

Básicamente hace lo miso y me sorprendió mucho porque Rust no es un lenguaje que haga evaluación perezosa.

¿Entonces cómo lo hace?

Pues simulando la evaluación perezosa. Para ello lo que hace es jugar con iteradores personalizados. Cada iterador tiene información de qué es lo que va a hacer cuando se llame a su función next. Así que cuando llamamos a map lo que hace es crear en memoria la información necesaria para hacer la operación, pero no la ejecuta. Lo mismo sucede con los filtros. Cuando llamamos al next de iterador resultante, este ejecuta el next del iterador que contiene y luego comprueba o ejecuta la condición que tenga definida.

Conclusión

Sinceramente me ha encantado ver esta aproximación. Es una modo fantástico de lidiar con el problema de infinito de un modo sencillo. Esto permite tratar muchas cosas como iteradores que, aún dando listas potencialmente infinitas, no tengan un gran impacto en rendimiento (o sí, todo dependen de como esté diseñado).

Si quieres saber más sobre evaluación perezosa quizá te interese ver la entrada en Wikipedia.

Aprendiendo haskell

Ya hace unos meses que, con tiempo que dispongo, estoy aprendiendo haskell. El motivo principal es que quería empezar con un lenguaje que fuera completamente distinto a los que ya conozco.

Confieso que de momento me parece todo un galimatías. Si bien llego a resolver algunos problemas sencillos, aún me pierdo con cosas medianamente complejas.

Además, para ir aprendiendo e ir estrujándome los sesos utilizo codewars para ir resolviendo ejercicios. Creo que es un buen modo para ir tomando contacto con el lenguaje.

¿Qué me aporta haskell?

La verdad es que no lo estoy estudiando porque sí. El objetivo es poder aprender nuevos mecanismos para afrontar los problemas. Haskell te obliga a afrontarlos desde una perspectiva completamente diferente a lo que es habitual en los lenguajes imperativos. Con ello espero que a futuro pueda incorporar nuevas formas para resolver problemas a mi día a día (siempre que valgan la pena).

¿Por qué haskell?

Porque al ser un lenguaje funcional puro me obliga a desconectar totalmente de lo que ya sé. Hay otros lenguajes funcionales, pero no llegan a la pureza de haskell¹ lo que me podría permitir desviarme de la intención de aprender programación funcional.

¿Qué haré con él?

Cuando tenga más claro como funciona y no me pierda tanto me pondré con yesod, un framework para desarrollo web. Creo que será toda una experiencia.

¿Y después?

Una vez que ya tenga haskell dominado espero poder ponerme con Rust. La verdad es que de un tiempo a esta parte ando muy desencantado con C. Cada día me parece peor lenguaje de programación, pero eso ya será otro artículo.

Notas

  1. Probablemente haya más lenguajes funcionales puros, pero haskell es de los que tiene la comunidad más amplia y, por tanto, en la que es más fácil encontrar documentación.

La necesidad de invertir en herramientas

Supongo que de una vez os habrá  pasado que ante ciertas tareas (tanto de desarrollo como de administración) repetís una y otra vez las mismas acciones o el mismo código. Otras veces, simplemente os encontráis que hay cosas que son fáciles de automatizar, pero no se hace (por ejemplo, rellenar cierto tipo de fichero en donde hay patrones muy comunes).

En este punto surge un gran problema: no se quiere automatizar/desarrollar herramientas que faciliten dicha tarea. Sí, suena extraño y contraintuitivo, pero es así. Las razones pueden ser muchas:

  • Sería otra herramienta a mantener y no hay recursos para ello.
  • Si el que hace la herramienta se va quién lo va a mantener.
  • Todo trabajo que no sea directamente la tarea asignada es tirar el dinero.
  • No lo hacemos porque no nos lo van a agradecer (sí, no siempre es cosa de la empresa no hacerlo).

Seguro que hay más motivos, pero los anteriores los he oído más de una vez.

Es muy importante tener en cuenta que todo el tiempo que se utilice en automatizar (o simplificar) este tipo de tareas debe ser considerado inversión. Y es así porque se pueden llegar a ahorrar muchas horas de trabajo (pude hablarse, en algunos casos, de hasta cientos de horas anuales) y, por tanto, el retorno de la inversión será muy importante.

Como siempre, hay que ir con cuidado: para una tarea que se hace para un proyecto concreto que sabemos que no durará más de X tiempo puede que no compense la automatización. Por tanto hay que estudiar cada caso. Pero lo que está claro es que es necesario tener esto en cuenta.

En mi caso concreto, que estoy en desarrollo, veo como poco a poco se mejora el proceso de creación de software. Por una parte podemos escribir código menos propenso a errores, y, por otra parte, tenemos más tiempo para realizar las pruebas y ofrecer un mejor producto.

Control de errores

Uno de los grandes problemas al programar es realizar el control de los errores. Por diversos motivos me ha tocado hacer un trabajo de estudio sobre qué opciones tenemos para realizar el control de los errores de una aplicación.

La verdad es que hubiese estado muy bien hacer una tormenta de ideas al respecto, pero en estos momentos estamos todos demasiado atareados.

El siguiente texto, sin pretender ser una guía exhaustiva de lo que es el control de errores, sí busca poner un punto de partida para todo aquel que quiera ver qué mecanismos hay para este fin.

Seguir leyendo Control de errores

Tratamiento de cadenas en C

Cuando empezamos a programar en C una de las cosas que probablemente más nos cuesta controlar es el tratamiento de cadenas.

El lenguaje C es de bastante bajo nivel, por lo que sus tipos de datos básicos son muy cercanos a lo que la máquina puede utilizar. Los ordenadores no entienden de cadenas, pero sí de direcciones de memoria. De hecho, no existe un tipo de cadena, sino que se usan arrays de bytes. Y lo mejor de todo, ¡en C es el programador el que debe conocer la longitud del array! (cuanto bien, y a la vez cuánto mal, han hecho los lenguajes modernos).

Todas las funciones del estándar de C presuponen que una cadena es un array de bytes donde el último carácter es un nulo.

No voy a entrar en más detalles sobre que son las cadenas. Si quieres aprender cómo se programa con ellas puedes leer Cadenas de caracteres del manual Programación en C de  wikibooks.

Quien más y quien menos habrá visto que muchos problemas de seguridad en bibliotecas en C viene precisamente del tratamiento de cadenas. Casi se podría decir que si tienes que hacer un programa que tenga que manejar cadenas es aconsejable que mires cualquier otra cosa que no sea C. Aún así, esto no siempre será posible.

En el lugar en el que trabajo hemos empezado a cambiar el modo en que utilizamos las cadenas. Para facilitar su uso y reducir la cantidad de errores que pueden producirse, hemos creado un nuevo tipo de cadena. Bueno, se parece más a un StringBuilder de java. Las motivaciones son varias:

  • Queremos un tratamiento de cadenas más rápido (tener que recorrer una cadena cada vez que quieres conocer su longitud no es óptimo).
  • Queremos poder trabajar con cadenas sin preocuparnos por reasignar constantemente memoria. ¿Habéis probado un strcat cuando estás usando arrays y el buffer de destino es más pequeño que la cadena a concatenar?
  • Para ciertas operatorias queremos poder tener cadenas con caracteres nulos. Las cadenas de C no se llevan bien con esto, pero nosotros creamos cadenas para mandar a dispositivos que no sólo aceptan el nulo, sino que ciertas operaciones lo requieren. Con las funciones estándar sería muy complicado esto.

Personalmente no creo que seamos los primeros en hacer algo así; no tengo la más mínima duda de que hay muchas personas con los mismos problemas.

Las ventajas de usar esta biblioteca son inmediatas:

  • Simplificamos el código por no tener que preocuparnos en si hay o no espacio para almacenar la cadena y en la gestión de este espacio.
  • Minimizamos errores pues no nos pasaremos nunca del buffer (siempre podemos quedarnos sin memoria).
  • Agilizamos el desarrollo al poder escribir directamente código que haga lo que queremos (es una consecuencia del primer punto).
  • Se pueden optimizar ciertas operaciones. Esto se debe a que no hay que estar constantemente recorriendo cadenas para medirlas o que al «liberar» una cadena, no hacemos free y malloc, sino que marcamos la cadena como de longitud 0 (que es más rápido y permite aprovechar la memoria).

¡Mucho ojo con esto! Que en el día a día de mi trabajo esta haya sido una buena solución no significa que lo sea para todos los casos. Siempre hay que evaluar convenientemente cada situación antes de decantarse por una u otra solución.