27.12.05

Utilizando Drools II. Caso práctico 2/2

Como ya hemos visto, Drools es un motor de reglas de negocio. ¿Cuándo podemos utilizar Drools?. Pues,por ejemplo:



  • Cuando la lógica de negocio sea compleja pero se pueda expresar mediante cláusulas IF-THEN.

  • Cuando vaya a haber muhcos cambios.

  • Cuando el cliente no tiene clara su lógica de negocio.

  • Cuando el cliente se exprese de manera natural mediante IF-THEN.


Básicamente, al utilizar Drools tendremos que escribir dos elementos: las reglas de negocio y el sistema que utilizará dichas reglas (a a través de Drools). Drools permite escribir reglas en lenguaje Java, Python o Groovy. En este ejemplo vamos a escribir las reglas utilizando Java. Las reglas se almacenan en un archivo DRL, que no es más que un archivo XML con la definición de las reglas. La estructura (simplificada) de un archivo DRL se muestra a continuación



  • Imports de elementos de paquetes.

  • Declaración de funciones auxiliares.

  • Nombre del conjunto de reglas.

  • Reglas


Para cada regla podemos indicar:



  • Nombre de la regla.

  • Parámetros (los nombres son importantes).

  • Conciones para que la regla se dispare.

  • Consecuencias.


A continuación se muestra un ejemplo de un archivo DRL completo para el ejemplo que expusimos en el post aterior. Concretamente este archivo DRL incluye una regla por la que, si el precido de un pedido es superior a 100, le descuenta el 5%.


<?xml version="1.0"?>
<rule-set name="BusinessRulesSample"
xmlns="http://drools.org/rules"
xmlns:java="http://drools.org/semantics/java"
xmlns:xs="http://www.w3.org/2001/XMLSchema-instance"
xs:schemaLocation="http://drools.org/rules rules.xsd
http://drools.org/semantics/java java.xsd">

<java:import> java.lang.Object </java:import>
<java:import> java.lang.String </java:import>
<java:import> ebr.beans.Pedido </java:import>

 <rule name="Calculo del total del pedido">
<parameter identifier="pedido">
<class>ebr.beans.Pedido</class>
<java:condition>
pedido.getCalculoTotalOriginal() >= 100.0
</java:condition>
<java:consequence>
pedido.setTotalFinal(pedido.getCalculoTotalOriginal() * 0.95);
</java:consequence>
</rule>

Para disparar esta regla vamos a escribir, en prime lugar, los beans que van a guardar la información
del workspace y un sencillo programa que utilice el motor. Uno de los beans guardará información sobre cada producto (nombre cantidad y precio) y el otro guardará la lista de productos. El sgeundo bean se muestra a continuación.

import java.util.*;

public class Pedido {

 private List entradas;
private double totalFinal;

 public Pedido() {
entradas = new ArrayList();
this.totalFinal = -1.0;
}

 public void addEntrada (Entrada e) {
this.entradas.add(e);
}

public List getEntradas() {
return this.entradas;
}

public void getEntradas(List entradas) {
this.entradas = entradas;
}

public double getCalculoTotalOriginal() {
Entrada e;
double total = .0;
Iterator it = this.entradas.iterator();

while(it.hasNext()) {
e = (Entrada) it.next();
total += e.getCantidad();
}

return total;
}

public void setTotalFinal(double total) {
this.totalFinal = total;
}

Para trabajar con Drools necesitamos importar los sigientes elementos:


  import java.io.IOException;
import org.drools.DroolsException;
import org.drools.RuleBase;
import org.drools.WorkingMemory;
import org.drools.event.DebugWorkingMemoryEventListener;
import org.drools.io.RuleBaseLoader;
import org.xml.sax.SAXException;

Lo primero que tenemos que hacer es crear un nuevo pedido:


  Entrada e1 = new Entrada("A", 10, 5);
Entrada e2 = new Entrada("B", 6, 6);
Pedido pedido = new Pedido();
pedido.addEntrada (e1);

En este pedido tenemos 10 unidades del producto A cada una a un precio de 5 y 6 unidades del B a un
precio de 6. A continuación creamos nuestro motor de reglas para que procese el archivo de reglas visto más arriba:
  BREngine br = new BREngine();
RuleBase businessRules = RuleBaseLoader.loadFromUrl(
BREngine.class.getResource( "rules01.drl" ) );

Limpiamos el espacio de trabajo e instalamos un listener que será avisado cada vez que Drools ejecute una regla. Este listener mostrará un mnesaje por la salida estándar.
  WorkingMemory workingMemory = businessRules.newWorkingMemory();
workingMemory.addEventListener(new DebugWorkingMemoryEventListener());

A continuación añadimos el pedido:


 workingMemory.assertObject(fact);

Y a trabajar:


 workingMemory.fireAllRules();

Nota, los try / catch de todas las pisbles excepciones han sido omitios.

19.12.05

Cumplimos un año. Gracias !!!!!!

Saludos a todos.

Recientemente este blog ha cumplido un año. Sinceramente, cuando empecé no esperaba que fuera capaz de mantenerlo todo este tiempo, no por falta d einetrés sino de tiempo.

Os agradezco de todo corazón vuestro tiempo a todos los que de vez en cuando venís por aquí. Lamento no poder actualizar esto con tanta frecuencia como me gustaría pero espero que, lo que ponga, os sea de utilidad.

Felices fiesta a todos.

(PD: sí, sí, está pendiente la continuación del post de Drools, a ver si llegan ya las vacaciones y tengo tiempo de ponerme con ello).

30.11.05

Drools I. Introducción a los motores de reglas de negocios.

Saludos.

Drools (http://drools.org) es una implementación libre de un motor de reglas de negocio compatible con la especificación JSR-94 Rules Engine API (http://www.jcp.org/en/jsr/detail?id=94). ¿Y qué es un motor de reglas de negocio?. Es un componente que, a partir de una información inicial y un conjunto de
reglas, detectas qué reglas deben aplicarse en un instante determinado y cuáles son los resultados de esas reglas.

Uno de los puntos fuertes de los motores de reglas de negocios es que hablan el mismo lenguaje que el dominio del problema. Un ejemplo típico: supongamos que estamos hablando con un cliente para el desarrollo de una tienda virtual.
Algunas de las cosas que nuestro cliente nos pediría pudieran ser:

Si la cuantía del pedido supera cierta cantidad o si es un cliente de confianza, entonces se le aplica un descuento. Si se compra más de N productos del mismo tipo se le aplica un descuento. Entre las fechas X e Y, un producto está en oferta y, si se lleva 2, se le añade un 3º gratis

En una aplicación normal, todo lo anterior deberíamos codificarlo en métodos de clases que se ejecutaran al procesar un pedido, lo cual no siempre es sencillo. Con un motor, solo tenemos que indicarle las reglas de negocio, e ir indicándole los hechos. En este ejemplo, tendríamos que suministrarle al motor la información sobre los pedidos, clientes y productos mediante clases JavaBean.

Otra ventaja es a la hora de realizar cambios o mantenimiento. Si nuestro cliente desea cambiar el descuento por pedido, o el descuento por número de productos, o las fechas o tipos de productos (o quitar todos los descuentos), con un motor de reglas de negocio sólo es necesario cambiar las reglas, sin necesidad de modificar código, recompilar, ni siquiera de parar la aplicación.

Básicamente un motor de reglas de negocio está compuesto de tres elementos: un conjunto de reglas, el espacio de trabajo (o el conocimiento que tiene), y el procesador de reglas. Las reglas son sentencias de la forma IF-THEN, de tal manera que si se cumplen todas las condiciones del IF se ejecutan todas las acciones del THEN. El espacio de trabajo es donde se guarda el conocimiento (objetos Java) que el motro utilizará para decidir que reglas deben activarse.

Dentro de las reglas pueden existir conflictos. Un conflicto es cuando varias reglas distintas pueden activarse para el mismo conjunto de hechos y, la aplicación de dichas reglas, pueden tener resultados contradictorios. Algunas de las estrategias para resolver conflictos son: asignarle una prioridad a cada regla, estrategias FIFO o LIFO, aplicarlas en el orden en que se declararon o aplicarlas en orden aleatorio.

Existen otros motores de reglas de negocio para java, por ejemplo JESS (http://herzberg.ca.sandia.gov/jess/), Mandarax (http://mandarax.sourceforge.net/) o OpenRules (http://java-source.net/open-source/rule-engines/openrules). También hay
motores de reglas de negocio para plataforma .NET como Quick Rules, JRules o Blaze.

Los motores de reglas de negocio pueden utilizarse, por ejemplo, para desarrollar prototipos rápidos o, incluso, para implementar la lógica de la aplicación. También pueden utilizarse como parte de un sistema de workflow.

En el próximo mensaje pondré un ejemplo de como implementar el caso práctico que hemos visto.

16.11.05

Extendiendo JUnit para que verifique expresiones regulares

Saludos.

A veces, escribiendo pruebas unitarias con JUnit, hemos de comprobar resultados complejos.
Utilizando el soporte para expresiones regulares de J2SE a partir de la versión 4 que comentamos en el mensaje anterior, vamos a escribir un nuevo método assert, por ejemplo assertRegExp, que nos permita validar una cadena respecto de una expresión regular. Una posible implementación de este método se muestra a continuación.


public static void assertRegExp(String message, String regexp, String s) {
if (message==null)
message="";
if (!Pattern.matches(regexp, s))
fail(message+" ["+regexp+"] over ["+s+"]");
}

public static void assertRegExp(String regexp, String s) {
assertRegExp(null, regexp, s);
}


Para que funcione, ambos métodos deben estar en una clase que herede de Assert, TestCase o TestDecorator.

Veamos un ejemplo. Supongamos un método "String getDNI()" para el que queremos escribir una prueba que verifique que el resultado está compuesto de 8 dígitos y una letra mayúscula. Una posible prueba utilizando nuestro método assertRegExp se muestra a continuación.


String dni = persona.getDNI();
String expReg = "[0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][A-Z]";
assertRegExp("DNI incorrecto", expReg, dni);

26.10.05

Soporte de expresiones regulares en Java

Saludos.

Existen muy buenas herramientas libres para trabajar con expresiones regulares en Java, como (http://lucene.apache.org/). Sin embargo, a partir de la versión 1.4, el kit estándar de Java incluye su propio soporte para expresiones regulares.
En el paquete 'java.util.regexp' encontramos las clases Matcher y Pattern. La primera clase permite aplicar una expresión sobre una cadena de texto. La segunda clase representa una expresión regular compilada para ganar en eficiencia.

La manera más sencilla y rápida de comprobar si una cadena satisface una expresión regular es mediante el método estático 'boolean Pattern.matches(cadena, expresión regular)'. Es posible conseguir el mismo efecto invocando el método matches sobre un objeto String. A continuación se muestra un sencillo ejemplo.


import java.util.regex.*;

public class EjemploRegExp {

// Números de 2 cifras que empiecen por 1 o 2
static String re = "\\b[1][0-9]|[2][0-9]";
static String cadena1 = "10";
static String cadena2 = "110";

public static void main(String[] args) {
if (Pattern.matches(cadena1, re))
System.out.println(cadena1+" cumple: "+re);
else
System.out.println(cadena1+" no cumple: "+re);
if (cadena2.matches(re))
System.out.println(cadena2+" cumple: "+re);
else
System.out.println(cadena2+" no cumple: "+re);
}
}

Además, la clase String también nos ofrece la manera de dividir un String mediante una expresión regular con el método:

String[] split(String regex)
String[] split(String regex, int limit)

En el próximo mensaje veremos una aplicación práctica extendiendo la herramienta JUnit (www.junit.org) para que soporte expresiones regulares.

10.10.05

Andalucía, tierra de SUN y Oracle

La Junta de Andalucía es uno de los principales generadores de empleo en el mercado de la informática, bien de manera directa, bien creando y participando en empresas, o bien contratando trabajos a empresas privadas.
Por este motivo es muy importante para quien quiera trabajar en Andalucía conocer las tecnologías que la Junta de Andalucía ha decidido utilizar. En este caso y, hoy por hoy, la Junta desarrolla una apuesta clara por Java y Oracle para sus nuevos sistemas web y estos sistemas son los que salen a concurso.
Ya sabéis, todos los informáticos de Andalucía a estudiar SUN Y Oracle.

23.5.05

El lado oscuro de las pruebas.

Saludos.

De un tiempo a esta parte, con la aparición de metodologías extremas, diseño guiado a pruebas y herramientas tipo jUnit, se han puesto de moda las pruebas. Parece que, cuantas más pruebas tenga un programa, mejor. Pero tener un número elevado de pruebas tiene un lado oscuro que puede traer problemas.

Si las pruebas son pequeñas y simples, las podemos escribir en un momento, pero si aumenta su tamaño (y no podemos dividirlas en pruebas más pequeñas) se hacen difíciles de escribir. Además a más código más errores, por lo que tenemos que perder tiempo depurándolas. Si nuestra prueba tiene mucho código o es compleja, ¿cómo podemos asegurarnos de que nuestra prueba funciona adecuadamente? ¿Tenemos que escribir una prueba para probar la prueba?.

Esto es malo, pero podemos encontrar algo todavía peor. Supongamos que tenemos una clase y 10 pruebas que dependen de dicha clase. Con el uso se ha descubierto que hay una manera mejor de crear objetos de esa clase cambiando los constructores. Ese cambio supondría, no solo cambiar en el código donde se crean objetos de esa clase (muy pocos si programamos bien 8) ) sino tener que modificar esas 10 pruebas.
Además habría que repasarlas para comprobar que sigan teniendo sentido después de la modificación y, probablemente, escribir pruebas nuevas.

En este caso el problema no es muy grande, pero si cambiamos (o refactorizamos) mucho código, el número de casos de casos de prueba a modificar aumenta, con el consiguiente gasto de tiempo para modificarlas. Recordemos que las pruebas no son parte del sistema (no se la entregamos al cliente ni, probablemente, el cliente aprecie su utilidad), por lo que no debemos gastar demasiado tiempo con ellas.

Esto nos puede llevar a dos decisiones igual de malas: o bien no modificar el código para seguir aprovechando las pruebas que ya tenemos, o, lo más común, modificar el código y tener un conjunto de pruebas obsoletas e inservibles.

Este problema no es exclusivo en las pruebas de código "tipo jUnit". También sucede lo mismo trabajando, por ejemplo, con robots de prueba que capturan pulsaciones de teclado o ratón y luego las reproducen. En este caso, un mínimo cambio en la interfaz gráfica puede obligar a modificar (o a volver a grabar) un buen número de pruebas.

En resumen. Cuanto más pruebas, por ejemplo pruebas de código, tengamos, más atados estamos a dicho código y menos ganas de cambiar nada tenemos..

¿Cuál puede ser una la solución?. Creo que una buena alternativa sería generar las pruebas de manera automática. Si tuviéramos un programa que permitiera apretar un botón y obtener un buen conjunto de pruebas (e incluso ejecutarlas), no nos preocuparíamos a lo hora de cambiar el código. En cada cambio descartaríamos todas las pruebas obsoletas y generaríamos pruebas nuevas fácilmente.

Esto es muy fácil de decir pero muy difícil de poner en práctica (por no decir imposible). Espero seguir hablando de esto en futuras entradas.

2.5.05

Texto con formato e imágenes en aplicaciones web

Saludos.

Recientemente he estado haciendo una miniconsultoría (en plan "Amigetes Conculting") que me ha llevado a buscar editores web, o editores de texto lo más completos posibles para integrarlos en una aplicación PHP. A continuación comento lo que he encontrado.

En el siguiente enlace se puede encontrar una lista muy completa con opciones para todo tipo de plataformas y lenguajes: http://www.htmlarea.com/

De todos los que he visto, el que más me ha gustado para PHP (también disponible para ASP.NET) es Spaw, que se distribuye bajo licencia GPL para usos no comerciales (www.solmetra.com/spaw/ y sourceforge.net/projects/spaw/).

Sin embargo, aunque es un componente bastante evolucionado y cuenta con un foro bastante dinámico (en SourceForge), yo no he consegido hacerlo funcionar. Una lástima.

Por ese motivo he optado por htmlArea (http://www.dynarch.com/projects/htmlarea/) que, si bien no es tan vistoso ni icluye tantas opciones, cumple sobradamente. Además está íntegramente en JavaScript, por lo que se adapta a casi cualquier plataforma.

También existen plug-in que lo extienden, por ejemplo a la hora de incluir imágenes
(http://www.zhuo.org/htmlarea/). Su licencia es BSD.

HtmlArea lo utilizan aplicaciones tan importante como la plataforma de e-Learning Moodle.

Ya no hay excusas para crear formularios que no admitan texto con formato.

3.4.05

Lo mejor de la semana

Saludos.

Sin duda lo mejor que esta semana he encontrado en la red es esta colección de enlaces a tiras cómicas.

La tira de Linux Hispano
http://www.linuxhispano.net/tira/tira.html

Raulito el Friki
http://recurrente.afraid.org/myblog/?q=ultima

La tira de Bit y Byte
http://tira.emezeta.com/

Tira Frikis
http://www.geocities.com/tirasfrikis/

Tira Ecol
http://tira.escomposlinux.org/

Un millón de gracias a sus autores por su tiempo y esfuerzo.

31.3.05

Una pregunta en el aire

Saludos.

El otro día, leyendo una página web sobre una aplicación encontré lo siguiente:

Robusto: Más de 230 pruebas Junit.

La primera pregunta que se plantea es bastante obvia: ¿Podemos medir el grado de robustez o fiabilidad de un sistema mediante el número de pruebas?.

No. Un ejemplo muy claro, aunque tal vez un poco extremo. Imaginemos el
siguiente código (que sirve tanto para C como para Java).


int factorial(int n) {
if (n == 1)
return n;
return n * factorial(n-1);
}


Supongamos que escribo 100 pruebas para comprobar los 100 primeros factoriales. ¿Puedo asegurar que el código anterior es robusto?. No. en cuanto reciba un 0 o un número negativo fallará a pesar de sus 100 (o 11000) pruebas.

Otra pregunta, tal vez no tan obvia, es si la robustez o fiabilidad de un sistema está garantizada únicamente por pruebas unitarias.

No. Un ejemplo muy claro lo encontramos en las aplicaciones web. Con pruebas
tipo JUnit, u otra herramienta similar, difícilmente podremos comprobar la respuesta del sistema a un gran número de peticiones concurrentes de distintos clientes, ni si el sistema es capaz de seguir funcionando o se colapsa.

La gran pregunta que habría que responder es: ¿Como podemos garantizar la robustez del sistema?. Lo cual nos lleva a plantearnos: ¿y qué es la robustez?.

Habrá que seguir con esto.

24.3.05

Probando exhaustivamente

El siguiente ejemplo está sacado de un libro sobre pruebas bastante famoso.
Nos piden un programa para clasificar un triángulo en equilátero, isósceles o escaleno. Para que tres lados a, b y c formen un triángulo debe cumplirse que s (s = (a +b +c) / 2) sea mayor estricto que a, b y c.
Una posible solución se muestra a continuación.



static final int NOTRIANGULO = 0;
static final int EQUILATERO = 0;
static final int ISOSCELES = 0;
static final int ESCALENO = 0;

public static int tipoTriangulo(int a, int b, int c) {
int s;

s = (a+b+c) / 2;
if ( (s <= a) || (s <= b) || (s <= c))
return NOTRIANGULO;

if ( (a==b) && (b==c) )
return EQUILATERO;

if ( (a==b) || (b==c) || (a==c) )
return ISOSCELES;

return ESCALENO;
}



Sin embargo, lo más interesante es plantear que pruebas necesitamos para verificar a fondo que el código anterior funciona. En este caso, una prueba va a ser un conjunto de valores concretos y el resultado esperado, por ejemplo:

(3, 3, 3) : EQUILATERO
(3, 3, 4) : ISOSCELES
(3, 4, 5) : ESCALENO
(2, 2, 6) : NOTRIANGULO

¿Cuantas pruebas necesitamos para tener la certeza que hemos probado todas las posibles combinaciones?. ¿Cómo obtenemos esas pruebas?. Una técnica clásica que podemos utilizar es analizar las estructuras de control. Esta es una técnica de caja blanca.
Por ejemplo, en el caso de un triángulo isósceles, vemos que ha de cumplirse una de tres condiciones posibles. Por tanto, en este caso hemos de verificar las tres condiciones. Las pruebas resultantes se muestran a continuación:

(3, 3, 4), (3, 4, 3), (4, 3, 3) : ISOSCELES

Aplicando este mismo proceso al resto de los casos obtenemos el resto de pruebas. Sin embargo esto aún no es suficiente. Por ejemplo: ¿qué sucede cuando un lado es 0 o negativo, o cuando un lado vale el valor máximo que un tipo int puede almacenar?. ¿Es posible introducir lados decimales o lados que no sean valores numéricos?. Todas estas circunstancias deberían ser probadas también.
En este caso, para completar el conjunto de pruebas, podemos aplicar una técnica de prueba de caja negra llamada análisis de valores límites. Por ejemplo, para verificar que el programa sigue funcionando cuando uno de sus lados es 0 las pruebas serán:

(0, 3, 3), (3, 0, 3), (3, 3, 0) : NOTRIANGULO

Calculando todas las posibles combinaciones se obtiene aproximadamente 30 casos de prueba distintos, lo cual da una medida de lo importante que es probar convenientemente cualquier código.

22.3.05

Enlaces sobre refactorización

Saludos.

Para aquellos interesados en tener a mano información breve y funcional sobre refactorizaciones, puede echarle un vistazo a estos dos enlaces publicados en Agile Spain (www.agile-spain.com/):

En el primero nos muestran una tabla con malos olores y enlaces a las refactorizaciones recomendadas para eliminarlas:
http://wiki.java.net/bin/view/People/SmellsToRefactorings

En el segundo nos explican las refactorizaciones mediante sencillos (muy sencillos) diagramas parecidoa a UML:
http://www.refactoring.be/thumbnails.html

Además de estos enlaces no hay que dejar de visitar el blog en español:
http://www.programacion.com/blogs/14_refactoring

15.3.05

Pruebas con aspectos

Saludos.

Nos han publicado en JavaHispano un trabajo que presentamos el año pasado en un estupendo workshop. El trabajo versa sobre como aplicar programación orientada a aspectos para escribir pruebas unitarias, y que ventajas aporta frente a la clásica herramienta JUnit. Los pocos ejemplos están escritos en Java y AspectJ.

Para quien tenga interés, el enlace es: http://www.javahispano.org/articles.article.action?id=96

12.3.05

Fin de la operación limpieza.

Saludos.

Aunque aún quedan algunos ejercicios con semáforos, la operación limpieza queda oficalmente clausurada. Si alguien quiere los ejercicios que faltan puede pedírmelos por correo electrónico.
Dentro de unos pocos meses empezaré otra operación de limpieza, pero esta vez para ejercicios básicos en Java.
Mientras tanto retomaré la idea original de este blog e iré escribiendo comentarios referentes a Java y reflexiones pruebas del software.

16.2.05

Sincroniazación entre procesos

El siguiente porgrama crea 2 hijos. Uno de ellos pondrá los números del 1 al 5 en una variable compartida y el otro leerá esos valores y los mostrará por pantalla.

El primer procso pone el número 1 en la variable compartida, después, el segundo proceso lee el valor 1 y lo muestra por pantalla. Después el primer proceso pone el número 2 en la variable compartida, y así hasta el 5.

Para sincronizar ambos procesos se han utilizado dos grupos de un semáforo cada uno.



#include < errno.h>
#include < stdio.h>
#include < unistd.h>
#include < wait.h>
#include < sys/ipc.h>
#include < sys/sem.h>
#include < sys/stat.h>
#include < sys/types.h>

#include "sv.h"


union senum {
int val;
};

int main() {

pid_t pid;
int c, r;
int num;
int sem_prod, sem_cons;
struct sembuf arriba = {0,1,0};
struct sembuf abajo = {0,-1,0};
union senum arg;


sv_init();

sem_prod = semget(IPC_PRIVATE, 1, IPC_CREAT | IPC_EXCL | S_IRUSR | S_IWUSR);
sem_cons = semget(IPC_PRIVATE, 1, IPC_CREAT | IPC_EXCL | S_IRUSR | S_IWUSR);
if ((sem_prod == -1) || (sem_cons==-1)) {
perror("Error creando semáforos");
return -1;
}

arg.val = 1;
r = semctl(sem_prod, 0, SETVAL, arg);
if (r == -1) {
perror("Error incializando el semáforo del productor - ");
//Borrar los semáforos
return -1;
}

for (c = 0; c < 2; c++) {
pid = fork();
if (pid == -1) {
perror("Error creando procesos - ");
// Borrar semáforos.
return -1;
}

if (pid == 0) {

// Productor
if (c==0) {
for (num = 1; num < 6; num++) {
// Mi turno de producir
r = semop(sem_prod, &abajo, 1);
if (r == -1) {
perror("Error bajando el semáforo del productor - ");
// Eliminar semáforos
return -1;
}

printf("Valor producido: %i\n", num);
sv_set(num);

// El turno de consumir
r = semop(sem_cons, &arriba, 1);
if (r == -1) {
perror("Error subiendo el semáforo del consumidor - ");
// Eso
return -1;
}
}
exit(0);
}

// Consumidor
if (c==1) {
for (num = 0; num < 5; num++) {
// Voy a consumir 5 números
r = semop(sem_cons, &abajo, 1);
if (r == -1) {
perror("Error bajando semáforo del consumidor - ");
return -1;
}

printf("Valor consumido: %i\n", sv_get() );

// Hora de producir
r = semop(sem_prod, &arriba, 1);
if (r == -1) {
perror("Error subiendo semáforo del productos - ");
return -1;
}
}
exit(0);
}

}
}

pid = wait(&r);

while ( (pid != -1) ||
( (pid == -1) && (errno == EINTR)) )
pid = wait(&r);

r = semctl(sem_prod, 0, IPC_RMID);
if (r==-1) {
perror("Error eliminando semáforo productor.");
}
r = semctl(sem_cons, 0, IPC_RMID);
if (r == -1) {
perror("Error eliminando semáforo consumidor. ");
}

sv_finish();

printf("Fin.\n");
return 0;
}


Acceso a un recurso compartido

Saludos.

El siguinte código crea cuatro procesos hijos. Cada hijo incrementará una variable compartida en 5 unidades. Para garantizar la exclusión mútua sobre la variable compartida se utiliza un grupo de un semáforo que funciona como semáforo binario.

Hay que cabiar todas las " de los includes por mayor y menor excepto el include de sv.h.


/**
Implementa un semáforo binario
utilizando directamente las funciones del sistema
**/

#include "sv.h"

#include "stdio.h"
#include "wait.h>
#include "unistd.h"
#include "sys/types.h"

#include "sys/stat.h"
#include "sys/ipc.h"
#include "sys/sem.h"


// Incrementa en 5 la variable compartida
#define INC 5

void inc(int);


// Esta unión hay que declararla
union semun {
int val;
struct semid_ds *buf;
ushort *array;
};

int main() {
int c;
int hijos = 4;
pid_t pid;

int semaforo;
union semun arg;
int r;
// { semaforo, valor, banderas }
struct sembuf arriba = {0, 1, 0};
struct sembuf abajo = {0, -1, 0};


sv_init();
printf("Valor de inicio: %i\n", sv_val());

// Crear e inicializar un semaforo binario
// para garantizar la exclusion mutua.
semaforo = semget(42, 1, IPC_CREAT | IPC_EXCL | S_IRUSR | S_IWUSR);
if (semaforo == -1) {
perror ("Error en la creación del semáforo - ");
return -1;
}

arg.val = 1;
r = semctl(semaforo, 0, SETVAL, arg);
if (r == -1) {
perror ("Error inicializando semáforo - ");
// Hay que eliminarlo.
return -1;
}


for (c = 0; c < hijos; c++) {
pid = fork();
if (pid == -1) {
perror("Error en fork - ");
exit(-1);
}
if (pid == 0) {

// Decrementar semaforo
r = semop(semaforo, &abajo, 1);
if (r == -1) {
perror("Error decrementando semáforo - ");
// Eliminar el semáforo
return -1;
}

inc(c);

// Incrementar semaforo
r = semop(semaforo, &arriba, 1);
if (r == -1) {
perror("Error incrementando semáforo - ");
// Es necesario eliminarlo
return -1;
}

exit(0);
}
}

while ( wait(NULL)!=-1 );

printf("-------------------------------\n");
printf("Valor esperado: %i\n", hijos * INC);
printf("Valor real: %i", sv_val());

// Borrar semaforo
r = semctl(semaforo, 0, IPC_RMID);
if (r == -1)
{
perror("Error elminando semáforo - ");
return -1;
}

sv_finish();
printf("\n");
}


//----------------------------
void inc(int id) {
int c;
int tmp;
for(c=0; c < INC; c++) {
tmp = sv_get();
if ( (id == 0) && (c < 2)) sleep(1);
tmp++;
sv_set(tmp);
}
}

14.2.05

Libería para implementar una variable compartida

A continuación se incluye el código de una librería que permite crear y manipular una variable compartida, esto es, una variable común para todos los procesos que utilicen esta librería. Así, los cambios que haga un proceso sobre esta variable, serán visibles para los demás procesos.

Esta librería es un buen ejemplo de como utilizar el mecanismo IPC de memoria compartida.

Esta librería se utilizará en ejemplos posteriores con semáforos.

sv.h


#ifndef _SV_
#define _SV_

/**
Gestiona una variable entera mediante memoria compartida.
La variable es común a todos los procesos creados
después de llamar a sv_init();
**/


/**
Reserva la memoria compartida e inicializa la variable
a 0.
-1 si error.
**/
int sv_init();

/**
Libera la memoria compartida.
-1 si error.
**/
int sv_finish();

/**
Devuelve el valor de la variable común
sv_get es un alias para sv_vsl
**/
int sv_val();
int sv_get();

/**
Incrementa la variable entera con el valor
indicado como parámetro y devuelve su nuevo valor.
**/
int sv_inc(int);

/**
Asigna un valor a la variable común
Devuelve el valor asignado.
**/
int sv_set(int);

#endif





sv.c


#include "stdio.h"
#include "sys/ipc.h"
#include "sys/shm.h"
#include "sys/stat.h"
#include "sys/types.h"

int s_id;
int *sv = NULL;

//---------------------------------
int sv_init() {
int tam = sizeof(int);
s_id = shmget(IPC_PRIVATE, tam, IPC_CREAT | IPC_EXCL | S_IRUSR | S_IWUSR);
if (s_id == -1)
return -1;
sv = (int *)shmat(s_id, NULL, 0);
if (sv == (int *)-1) {
sv = NULL;
return -1;
}
(*sv)=0;
return 1;
}

//--------------------------------
int sv_inc(int val) {
int tmp;
if (sv == NULL)
return -1;
tmp = (*sv);
tmp += val;
(*sv)= tmp;
return (tmp);
}

//--------------------------------
int sv_val() {
if (sv == NULL)
return 0;
return (*sv);
}

//--------------------------------
int sv_get() {
return sv_val();
}

//--------------------------------
int sv_set(int val) {
(*sv) = val;
return val;
}

//--------------------------------
int sv_finish() {
if (s_id == -1)
return -1;
return shmctl(s_id, IPC_RMID, 0);
}

Mensaje con preaviso

Saludos.

El siguiente código crea un proceso hijo que enviará cuatro mensajes al proceso padre, el cual los mostrará por pantalla. Para evitar que cualquier de los dos procesos se quede esperando en la tubería, el padre, antes de leer un mensaje de la tubería, avisará al hijo con una señal SIGUSR1. El hijo, al recibir la señal, pondrá un mensaje en la tubería y esperará a la siguiente señal.


#include "stdio.h"
#include "unistd.h"
#include "signal.h"
#include "wait.h"
#include "sys/types.h"

#define SIZE 256

int tub[2];
char msg[SIZE];

void manejador_hijo(int);

int main() {

int c = 0;
int status;
pid_t pid;

pipe(tub);
pid = fork();
if (pid == -1) {
perror("Error bifurcando proceso - ");
return -1;
}

if (pid == 0) {
if (signal (SIGUSR1, manejador_hijo) == SIG_ERR) {
perror("Error instalando manejador - ");
exit(-1);
}
close(tub[0]);
while (1)
pause();

printf("Hijo, terminación inesperada.\n");
exit(-1);
}

close(tub[1]);
while(c < 4) {
sleep(1);
if ( kill(pid, SIGUSR1) == -1) {
perror("Error enviando señal al hijo.");
return -1;
}

if (read(tub[0], msg, SIZE) == -1) {
perror("Error leyendo de la tuberia - ");
return -1;
}

printf("%i: %s\n", c, msg);
c++;
}

close(tub[0]);

if (kill(pid, SIGTERM) == -1) {
perror("Error terminando hijo.");
return -1;
}

while( pid != wait(&status) );

printf("Fin\n");
return 0;
}

//--------------------------------------------------

void manejador_hijo(int sig)
{
if ( signal (SIGUSR1, manejador_hijo) == SIG_ERR) {
perror("Error reinstalando el controlador.");
exit(-1);
}
strcpy(msg, "Mensaje del hijo.");
if ( write(tub[1], msg, strlen(msg)) == -1) {
perror("Error escribiendo en la tuberia.");
exit(-1);
}
}

12.2.05

Mensaje repetitivo

El siguiente código muestra un mensaje por pantalla drante 2 segundos, pasado estetiempo recibe una señal SIGALRM y el mensaje cambia. Este proceso se repite infinitas veces, o hasta que alguna variable se desborde.

Hay que cambiar las " de los includes por los signos de mayor y menor.


#include "unistd.h"
#include "signal.h"
#include "string.h"
#include "sys/types.h"

void sig_alarm(int);

char men[50];
int num_alarm = 0;


int main()
{
if ( signal(SIGALRM, sig_alarm) == SIG_ERR) {
perror("Error instalando manejador - ");
return -1;
}

sprintf(men, "Alarma %i", num_alarm);
alarm(2);

while(1) {
printf("%s\n", men);
pause();
}

// Se ejcutará alguna vez esta línea ??
printf("Se acabó.\n");

return 0;
}

//---------------------------

void sig_alarm(int sig) {

if ( signal(SIGALRM, sig_alarm) == SIG_ERR) {
perror("Error instalando manejador - ");
}

alarm(2);
num_alarm++;
sprintf(men, "Alarma %i", num_alarm);
}

Matar al hijo perezoso

El siguiente programa lanza un proceso hijo, y si después de X segundos el proceso sige activo, lo mata con una señal SIGKILL.

Es conveniente jugar con el valor del sleep(..) del hijo para ver el comportamiento de este programa.


#include "unistd.h"
#include "signal.h"
#include "wait.h"
#include "sys/types.h"

int main()
{
pid_t pid, pid_hijo;
int status;
int sleep_padre = 2;
int sleep_hijo = 1;


if ( (pid_hijo=fork()) == -1) {
perror("Error en el fork - ");
return -1;
}

if (pid_hijo == 0) {
sleep(sleep_hijo);
printf("Termina hijo.\n");
exit(0);
}

sleep(sleep_padre);
pid = waitpid(pid_hijo, &status, WNOHANG);

printf("pid = %ld / pid_hijo = %ld / status = %i\n", pid, pid_hijo, status);

if (kill(pid_hijo,0)==0) {
printf("Hijo aún en activo. Lo termino\n");
kill(pid_hijo, SIGTERM);
waitpid(pid_hijo, NULL, 0);
}
printf("Termina padre.\n");

return 0;
}

Contar señales SIGUSR1

Saludos.

El siguiente programa queda a la espera de recibir una señal. Cunado recibe una señal SIGUSR1 incrementa un contador, y cuando recibe una señal SIGTERM muestra el contador y termina.

Una variante muy sencilla es añadirle un manejador para SIGUSR2 de tal manera que, al recibir esta señal, muestre el valor del contador pero continue su ejecución.

Para ejecutar un programa o comando en sgeundo plano, sin que deje la consola ocupada, hay que añadir & al final del nombre del programa o comando.


#include "signal.h"
#include "unistd.h"

void manejadorUSR1(int sig);
void manejadorTERM(int sig);

int cuenta = 0;

int main()
{

if ( signal(SIGUSR1, manejadorUSR1) == SIG_ERR) {
perror("Error instalando manejadorUSR1 - ");
return -1;
}

if ( signal(SIGTERM, manejadorTERM) == SIG_ERR) {
perror("Error instalando manejadorTERM - ");
return -1;
}

while(1)
sleep(5);

return 0;
}

//-------------------------
void manejadorUSR1(int sig) {

if ( signal(SIGUSR1, manejadorUSR1) == SIG_ERR) {
perror("Error instalamdno manejadorUSR1 - ");
exit(0);
}

cuenta++;
}

//---------------------------
void manejadorTERM(int sig) {

printf("Señales USR1 recibidas: %i \n", cuenta);
exit(0);
}

8.2.05

Sincronización padre-hijo con señales

El siguiente programa muestra los números del 1 al 6 en orden creciente por pantalla. Los números impares los muestra el padre y los números pares los muestra el hijo.


#include "signal.h>
#include "unistd.h>
#include "sys/types.h>

void manejadorPadre(int sig);
void manejadorHijo(int sig);

int cuenta = 0;

int main() {

pid_t pid;

if ( (pid = fork()) == -1) {
perror("Error en el fork - ");
return -1;
}

if (pid == 0) {
printf("Hijo instala manejador.\n");
if ( signal(SIGUSR1, manejadorHijo) == SIG_ERR) {
perror("Error instalando manejadorHijo - ");
exit(-1);
}

printf("Hijo entra en el bucle\n");
while(1)
pause();
exit(0);
}

// sleep(1);
printf("Uno\n");

if ( signal(SIGUSR1, manejadorPadre) == SIG_ERR) {
perror("Error instalando manejadorHijo - ");
exit(-1);
}

while(cuenta < 2) {
kill(pid, SIGUSR1);
pause();
}
kill(pid, SIGUSR1);


printf("Esperando al hijo.\n");
wait(NULL);
return 0;
}


//--------------------------
void manejadorHijo(int sig)
{

static int contador = 0;

if ( signal(SIGUSR1, manejadorHijo) == SIG_ERR) {
perror("Error instalando manejadorHijo - ");
exit(-1);
}

if (contador == 0){
printf("Dos\n");
contador++;
kill(getppid(), SIGUSR1);
} else if (contador == 1) {
printf("Cuatro\n");
contador++;
kill(getppid(), SIGUSR1);
} else {
printf("Seis\n");

// Restauro el manejador original
if (signal(SIGUSR1, SIG_DFL) == NULL)
perror("Error restaurando manejador original en el hijo - ");

exit(0);
};
}

//---------------------------------
void manejadorPadre(int sig)
{

if ( signal(SIGUSR1, manejadorPadre) == SIG_ERR) {
perror("Error instalando manejadorPadre - ");
exit(-1);
}

if (cuenta == 0) {
printf("Tres\n");
cuenta++;
} else if (cuenta == 1) {
printf("Cinco\n");
cuenta++;
// Restauro el manejador original
if (signal(SIGUSR1, SIG_DFL) == NULL)
perror("Error restaurando manejador original en el padre - ");
}
}


Listado ordenado.

El siguiente programa crea dos procesos hijos. El primer hijo ejecuta el comando "ls -l" y le pasa el resultado al segundo hijo. El segundo hijo ejecuta "sort -n +4" y redirecciona la salida al archivo "lsord.txt".

En otras palabras, el resultado de este programa es el mismo que el de ejecutar la siguiente expresión:

$ ls -l | sort -n +4 > lsord.txt


// Sustituir las "s por mayor y menos
#include "stdio.h"
#include "stdlib.h>
#include "unistd.h>
#include "fcntl.h>
#include "errno.h>
#include "sys/types.h>
#include "sys/wait.h>

int main()
{
int fd[2];
int status;
int fd_salida;

pipe(fd);

if ( (fd_salida=open("lsord.txt", O_WRONLY | O_CREAT | O_TRUNC, 0600)) == -1) {
perror("Error - ");
return -1;
}

if (fork()==0)
{
// El hijo
dup2(fd[1], STDOUT_FILENO);
close(fd[0]);
close(fd[1]);
execlp("ls", "ls ", "-l", NULL);
perror("Exec falló en ls");
exit(-1);
}
if (fork()==0) {
// El otro hijo
dup2(fd[0], STDIN_FILENO);
close(fd[0]);
close(fd[1]);
dup2(fd_salida, 1);
close(fd_salida);
execlp("sort", "sort", "-n", "+4", NULL);
perror("Exec falló en sort");
exit(-1);
}

close(fd[0]);
close(fd[1]);

// El padre espera a que terminen los hijos
status = wait(NULL);
while ( (status != -1) ||
(status == -1 && errno == EINTR))
status = wait(NULL);

exit(0);
}

6.2.05

Familia numerosa.

El siguiente código crea tres procesos hijos y, cada proceso hijo, crea tres nuevos procesos nietos.
Este código se puede simplificar mucho utilizando exit(int).


#include "unistd.h"
#include "sys/types.h"

int main()
{
int i, padre, prim_padre;

prim_padre = padre = 1;

for(i=0; i<3; i++) {
if (padre == 1) {
if (fork() == 0) {
// Proceso hijo
if (fork()==0) {
// Proceso nieto
printf("Nieto %i con padre %ld\n", getpid(), getppid());
} else {
printf("Hijo %i con padre %ld\n", getpid(), (long)getppid() );
}
padre = 0;
} else {
sleep(2);
if (prim_padre) {
printf("Padre %i\n", getpid());
prim_padre = 0;
}
}
}
}
return 0;
}


Ejemplo de tuberías

En el siguiente código, un proceso padre envía un mensaje a un proceso hijo mediante una tubería y el proceso hijo lo muestra por pantalla.


#include "unistd.h"
#include "sys/types.h"

int main()
{
int tub[2];
char msg[256];
pid_t pid;
int status;

pipe(tub);
pid = fork();

if (pid !=0) {
// El padre
write(tub[0], "Soy padre.\n", 12);
}
else {
// El hijo
read(tub[1], msg, 256) ;
printf("Hijo > %s", msg);
}

}


Copiar entrada en salida.

El siguiente código espera una entrada por la consola y muestra dicha entrada en la salida estándar.


#include "stdio.h"
#include "stdlib.h"
#include "sys/file.h"

#define TAM_BUF 255


int main(int argc, char *argv[])
{
char buf[TAM_BUF];
int readed;

readed = read(0, buf, TAM_BUF);
while(readed > 0)
{
write(1, buf, readed);
readed = read(0, buf, TAM_BUF);
}


printf("Hello, world!\n");

return EXIT_SUCCESS;
}

Escribir un mensaje por la salida estándar

Saludos.

El siguiente código escribe un mensaje por la salida estándar utilizando el mecanismo de descriptores de archivos.


#include "stdio.h"
#include "stdlib.h"

#include "sys/file.h"

#define TAM_BUF 255


int main(int argc, char *argv[])
{
char buf[TAM_BUF];

strcpy(buf, "Soy tocon.\n");
write(1, buf, 10);

printf("Hello, world!\n");

return EXIT_SUCCESS;
}

5.2.05

Adivinanza 2

¿Cuántas veces aparece por pantalla la palabra "Proceso" y que valor tiene en cada aparición la variable p?


#include "stdio.h"
#include "sys/types.h"
#include "unistd.h"

int main(void) {

int c;
int p=0;
for (c = 0; c < 3 ; c++ )
{
fork();
}
p++;
printf("Proceso %d\n", p);
}

Adivinanza 1

¿Cuantas veces aparece por pantalla la palabra "Proceso"?.


// Cambiar las " por mayor y menor.
#include "stdio.h"
#include "sys/types.h"
#include "unistd.h"

int main(void) {

int c;
for (c = 0; c < 3 ; c++ )
{
fork();
}
printf("Proceso\n");
}

Redireccionar salia de errores

Saludos.

El siguiente programa redirecciona la salida estándar de errores de la pantalla hasta un fichero y después simula un error.


// Cambiar las " de los include por mayor y menos.
#include "fcntl.h"
#include "stdio.h"
#include "unistd.h"
#include "sys/file.h"
#include "sys/types.h"

int main()
{
int fp;
int fpdup;

fp = open("errores.txt", O_WRONLY | O_CREAT | O_TRUNC, 00600);
if (fp == -1)
{
perror("Error abriendo fichero.\n");
return -1;
};

fpdup=dup2(fp, 2);
printf("%i / %i\n", fp, fpdup);

close(fp);

perror("Error simulado.\n");

return 0;
}

Mostrar argumentos de la línea de comandos.

Saludos.

El siguiente código muestra uno a uno y numerados los argumentos de la línea de comandos. También podría hacerse con un bucle for hasta el valor de la variable argc.


#include "stdio.h"

int main(int argc, char *argv[])
{
int i = 0;
while(argv[i]!=NULL) {
printf("%i : %s \n", i, argv[i]);
i++;
}
return 0;
}

2.2.05

Listar la línea de argumentos

Saludos.

El siguiente código lista todos los argumentos introducidos en la línea de comandos a continuación del nombre del programa y los numera. También podría hacerse mediante un bucle for con el valor de argc.



/**
* Este programa prueba si argv termina en NULL
**/
#include

int main(int argc, char *argv[])
{
int i = 0;
while(argv[i]!=NULL) {
printf("%i : %s \n", i, argv[i]);
i++;
}
return 0;
}

29.1.05

Comprara dos archivos.

Saludos.

El siguiente programa permite comparar dos archivos de texto binários. Aunque funciona la mayoría de las veces, tiene bastantes errores de diseño y, con ciertas condiciones especiales falla.


#include
#include

#define SIZE 256

// Devuelve 1 si son distintos y 0 si son iguales.
int cmpArrays(char a1[], char a2[], int size);

int main(int argc, char *args[])
{
char buff1[SIZE], buff2[SIZE];
int fd1, fd2;
int readed1, readed2;

fd1 = open(args[1], O_RDONLY);
fd2 = open(args[2], O_RDONLY);

while ( (readed1=read(fd1, buff1, SIZE)) >0) {
readed2=read(fd2, buff2, SIZE);
if (readed1 != readed2) {
printf("\nArchivos de distinto tamaño.\n");
return 0;
}
if (cmpArrays(buff1, buff2, readed1)) {
printf("\nArchivos con distinto contenido.\n");
return 0;
}
}

close(fd1);
close(fd2);

printf("\nArchivos iguales.\n");

return 0;
}


//------------------------------------

int cmpArrays(char a1[],char a2[], int size)
{
int c;

for (c=0; c < size;c++)
if (a1[c] != a2[c])
return 1;

return 0;
}

Guardar la entrada estándar en un fichero.

Saludos.

El siguiente código va leyendo la entrada estándar y guardando lo leido en un archivo buffer.f



#include
#include
#include

int main()
{
int fd;
char *buffer[256];
int b_leidos, b_escritos;

fd = open("buffer.f", O_WRONLY | O_APPEND);
if (fd == -1)
{
printf("\nCreando fichero 'buffer.f'\n");
fd = open("buffer.f", O_WRONLY | O_CREAT, 00600);
if (fd == -1)
{
perror("Error creando 'buffer.f'\n");
return -1;
}
}

b_leidos = read(0, buffer, 256);
while (b_leidos > 0)
{
b_escritos = write(fd, buffer, b_leidos);
if (b_escritos == -1)
{
perror("Error escribiendo.\n");
return -1;
}
b_leidos = read(0, buffer, 256);
}
close(fd);
}

23.1.05

Funciones potencial y factorial

Saludos.

Como recordatorio de la manera de hacer bibliotecas de funciones y de las técnicas recursivas, se propuso como ejercicio implementar una biblioteca de funciones con una función potencial y otra factorial. A continuación se muestra dicha biblioteca con versiones recursivas y no recursivas.

Próximamente más.

pot_fac.h

#ifndef __pot_fac__
#define __pot_fac__

int potencial_nr(int, int);
int potencial_r(int, int);
int factorial_nr(int);
int factorial_r(int);

#endif





pot_fac.c

#include "pot_fac.h"

int potencial_nr(int valor, int potencia)
{
int i, resultado;
resultado = 1;
for (i=0; i 0; i--)
resultado *= i;
return resultado;
}

//---------------------------------------------
int factorial_r(int valor)
{
if (valor == 1)
return 1;
return valor * factorial_r(valor-1);
}





prueba_pot_fac.c

#include "pot_fac.h"

int main() {

int resultado;

resultado=potencial_nr(4, 3);
printf("4 elevado a 3 %i (no resursivo)\n", resultado);

resultado=potencial_r(3, 3);
printf("3 elevado a 3 %i (resursivo)\n", resultado);

resultado=factorial_nr(4);
printf("4 factorial %i (no resursivo)\n", resultado);

resultado=factorial_r(6);
printf("6 factorial %i (resursivo)\n", resultado);

return 0;
}


Contar caracteres de una frase

Saludos.

Este ejercicio se propuso para que los alumnos repasaran sus conocimientos de manejo de cadenas en C. El objetivo es introducir una frase en la línea de comandos. El programa devolverá el número de veces que se repite cada una de las letras en la frase.

Próximamente más.


#include "stdlib.h"
#include "string.h"

// Variables globales;
char caracteres[29];
int car_index = 0;
int repeticiones[29];

// Prototipos
void incCaracter(char caracter)
{
int pos = -1;
int i;

for (i=0; i< car_index; i++)
if (caracteres[i] == caracter)
pos = i;

if (pos == -1) {
//car_index++;
caracteres[car_index] = caracter;
repeticiones[car_index] = 0;
pos = car_index;
car_index++;
}

repeticiones[pos] ++;
}

//-----------------------------------------------------
int main(int argc, char *argv[])
{
char cadena[255];
int i = 0;

if (argc < 2) {
printf("Parámetros insuficientes. \n");
return -1;
}
strcpy(cadena, argv[1]);

while (cadena[i] != '\0')
{
incCaracter(cadena[i]);
i++;
}

printf("Estadísticas de <%s>: \n", cadena);
for (i=0; i < car_index; i++)
printf("%c : %i \n", caracteres[i], repeticiones[i]);

return 0;
}



19.1.05

Operación Limpieza

Aviso a navegantes.

Por estas fechas termino de impartir una serie de clases sobre programación C básica en sistemas Unix/Linux (Fedora y SunOS). Por este motivo voy a ir publicando aquí en los próximos días el código de los ejemplos y problemas extra que he ido proponiendo a mis alumnos.

También pondré de vez en cuando algún otro post como los que hay más abajo para que esto no se convierta en un monográfico C bajo Unix.


16.1.05

Convertir double a caddena

Saludos.

El otro día me preguntaron como pasar un tipo double a cadena con un número determinado de decimales, por ejemplo 2, en Java. Una posible solución se muestra a continuación:



import java.text.DecimalFormat ;

public class Decimal {

public static void main(String args[]) {

double d1 = 123456789.123456789;
double d2 = 1.7976931348623157d;
double d3 = 4415961481999.03D;
DecimalFormat df = new DecimalFormat ("##############.##");

System.out.println ("d1:"+df.format(d1));
System.out.println ("d2:"+df.format(d2));
System.out.println ("d3:"+df.format(d3));

/**
Salida:
>java Decimal
d1:123456789,12
d2:1,8
d3:4415961481999,03
}
}



14.1.05

Pruebas como indicadores del avance de un proyecto.

¿Cuándo finaliza la construcción de un programa?. ¿Cuánto hemos avanzado esta semana?. ¿Vamos más rápidos o más lentos que el mes pasado?.

Todas estas preguntas se refieren a la velocidad con la que se crea o construye un programa y, en muchas ocasiones no son fáciles de responder. Una primera idea podría ser pensar que el avance de un programa tiene relación con las líneas de código escritas o número de clases implementadas, pero nada más lejos de la realidad. Es posible, por ejemplo, escribir 100.000 líneas de código o 50 clases en una semana y que el proyecto no avance ni un ápice, porque, por ejemplo, el código no funcione correctamente y necesite una depuración a fondo, o no pueda incorporarse al resto del programa o tenga que ser reescrito o, simplemente, se haya escrito código para una característica o función que no va a tener el sistema, por lo que no habrá servido de nada.

Una aproximación más fiable, pero aún no perfecta desde mi punto de vista, es relacionar el avance del proyecto con el cumplimiento de los requisitos. Un requisito es una característica que el sistema debe tener o algo que debe permitir hacer. A medida que vayamos escribiendo código, el sistema permitirá hacer cada vez más cosas, hasta que el sistema sea capaz de hacer todo lo que le piden los requisitos, momento en el que estará terminado. Sin embargo a veces es difícil cuantificar, a partir del código, el cumplimiento de requisitos. Por ejemplo, en un sistema típico de gestión comercial, si empleamos dos semanas en construir las bases de datos y las clases de acceso a las bases de datos (algo que será necesario para todos los requisitos), ¿qué grado de cumplimiento de los requisitos hemos conseguido un 1%, un 10%?. ¿Algún requisito habrá avanzado más que otro?. ¿Cuánto nos queda para satisfacer todos los requisitos?.

La mejor aproximación desde mi punto de vista, y que aúna características de las dos anteriores, es medir el avance del proyecto en función de la superación de pruebas. El proyecto estará terminado cuando sea capaz de superar todas las pruebas que lo verifiquen. Una semana se avanzará más que otra si ha sido capaz de superar todas las pruebas de la semana anterior y un número mayor de nuevas pruebas. Esta aproximación nos permite además, medir el desarrollo del código, mediante pruebas unitarias, y medir el cumplimiento de requisitos, mediante pruebas del sistema o pruebas de aceptación. En el momento en que ambas medidas alcancen el máximo, el sistema hará todo o que debe hacer sin errores en su código.

Un motivo más que justifica escribir pruebas para nuestros proyectos: no solo nos garantizan el cumplimiento de requisitos y la ausencia de errores, sino que nos permiten conocer el estado de nuestro proyecto, a que ritmo avanza y cuanto queda para su conclusión.


6.1.05

Límite de procesos en Unix/Linux

Saludos.

Preparando una clase de programación en C Unix/Linux he intentado hacer un ejemplo donde un gran número de procesos accedieran simultáneamente a un trozo de memoria compartida y los resultados no fueran los esperados. Con esto pretendía ilustrar la necesidad de garantizar la exclusión mútua.

No lo he conseguido. Mi Knoppix solo me ha permitido lanzar 487 procesos simultáneamente, mientra que SunOS me dejaba algunos más, hasta 496, 497 más o menos. Por desgracia con este número de procesos todo funcionaba a la perfección y, aunque no garantizara la exclusión mútua, ningún proceso se pisaba a otro.

Tnedré que escribir un programa donde fuerce la situación con unos sleep estratégicamente colocados.

Es una alegría que los Linux/Unix funcionen bien incluso cuando uno quiere que fallen.