Mostrando las entradas con la etiqueta guía. Mostrar todas las entradas
Mostrando las entradas con la etiqueta guía. Mostrar todas las entradas

martes, 27 de mayo de 2025

Las buenas maneras de las llamadas recursivas

The Power of 10 Rules (El poder de las 10 reglas) es una guía para código seguro creada en 2006 por Gerard J. Holzmann del Laboratorio de Software Fiable de la NASA/JPL.

Las reglas prohíben ciertas prácticas de codificación en C que dificultan la revisión o el análisis estático del código. Estas reglas son un complemento a las directrices MISRA C y se han incorporado en el mayor conjunto de estándares de codificación de JPL.

Las diez reglas

  1. Restrinja todo el código a construcciones de flujo de control muy simples. No utilice sentencias GOTO, construcciones set jmp o long jmp, ni recursividad directa o indirecta.
  2. Todos los bucles deben tener un límite superior fijo. Debe ser trivialmente posible para una herramienta de comprobación demostrar estáticamente que no se puede superar un límite superior preestablecido en el número de iteraciones de un bucle. Si el límite del bucle no puede demostrarse estáticamente, se considera que se ha infringido la norma.
  3. No utilice la asignación dinámica de memoria después de la inicialización.
  4. Ninguna función debe ser más larga de lo que se puede imprimir en una sola hoja de papel.
  5. Use un mínimo de dos aserciones en tiempo de ejecución por función. Las aserciones siempre deben estar libres de efectos secundarios y deben definirse como pruebas booleanas.
  6. El scope de los datos debe ser el más pequeño posible.
  7. Cada función de llamada debe comprobar los valores de retorno de función no vacíos, y la validez de los parámetros debe comprobarse dentro de cada función.
  8. El uso del preprocesador debe limitarse a los headers y definiciones de macros simples.
  9. El uso de punteros debe estar restringido a un nivel de des referencia. No se permiten punteros a funciones.
  10. Todo el código debe ser compilado, desde el primer día de desarrollo, con todas las advertencias del compilador habilitadas en la configuración más estricta del compilador. Todo el código debe ser comprobado diariamente con al menos un analizador estático de código fuente actualizado, y debe pasar los análisis con cero advertencias.

La primera regla prohíbe el uso de llamadas recursivas. Las normas de la NASA se centran en la robustez de sistemas de tiempo real que son siempre críticos y inaccesibles para mantenimiento: La recursión puede ser impredecible en sistemas embebidos o de tiempo real.El consumo de recursos no acotados (pila/stack), es crítico en hardware con memoria limitada (ej.: satélites, rovers). Dificultad para verificación formal (requerida en software de seguridad crítica).

Recursión Eficiente: Optimización del Compilador

Para hacer que la recursión sea eficiente y segura, los compiladores modernos aplican optimizaciones clave, pero requieren que el código cumpla ciertas condiciones.

Tail-Call Optimization (TCO)

El compilador reemplaza la llamada recursiva por un bucle (jump) si la recursión está en posición de cola (tail position), evitando crecimiento de la pila (stack). La llamada recursiva debe ser la última operación de la función. Compiladores como GCC/Clang la optimizan con -O2 o -O3.

Trampolining (para lenguajes sin TCO nativo)

Transforma la recursión en un bucle usando una estructura intermedia ("trampolín").

Memoization (Cache de Resultados)

Almacena resultados previos para evitar recalcular en recursión múltiple.

Conversión Manual a Iteración

Si el compilador no optimiza, transforma la recursión en un bucle manualmente.

Fuentes:

NASA JPL Institutional Coding Standard for the C Programming Language (2009).

DO-178C (Estándar para software aeronáutico).

Resumen y extractos en el repositorio técnico de la NASA: NASA Technical Reports Server (NTRS) (Busca "JPL C Coding Standard 2009")

Versión similar y pública (JPL Power of Ten Rules): NASA JPL Power of 10 Rules (Reglas clave resumidas).

DO-178C (Estándar para Software Aeronáutico) Documento oficial: Publicado por RTCA (Requiere compra): RTCA DO-178C (Costo aproximado: $200 USD).

Guías gratuitas relacionadas:

FAA Advisory Circular 20-115C (basado en DO-178C): FAA.gov (Busca "AC 20-115C"). 

viernes, 16 de mayo de 2025

Peor es mejor

El artículo "Lisp: Good News, Bad News, How to Win Big" (Richard P. Gabriel, 1991), escrito en los años 90 por uno de los pioneros de LISP, durante el declive comercial de Lisp frente a lenguajes como C++ y Java, analiza por qué Lisp, pese a su elegancia técnica, no triunfó en la industria.

Gabriel muestra como Lisp es el lenguaje más potente (meta programación, macros, REPL); favorece abstracción pura y rapidez de desarrollo (ej.: IA simbólica). Gabriel critica la cultura UNIX/C por priorizar simplicidad sobre corrección, pero admite que eso las hizo más populares.

La sintaxis con paréntesis y la flexibilidad extrema de LISP asustaban a los desarrolladores. Los dialectos (Common Lisp vs. Scheme) fragmentaron el ecosistema. Lisp requería máquinas costosas (Lisp Machines) y no se adaptó a la era PC. Gabriel termina advirtiendo que la superioridad técnica no garantiza el éxito comercial.

Actualmente, ya en el segundo cuarto del siglo XXI, la IA permite meta programación implícita, es decir generar código desde descripciones en lenguaje natural). Asistentes como ChatGPT actúan como "REPLs cognitivos". La IA reduce la barrera de entrada, se afirma que ya no se requiere saber programar. Pero la ambigüedad es un problema fundamental que compromete la confiabilidad de los sistemas o aplicaciones desarrolladas por o con la asistencia de IA.

El ensayo de Edsger W. Dijkstra "On the Foolishness of Natural Language Programming" (EWD667, 1978) argumenta que el lenguaje natural es inadecuado para la programación debido a su ambigüedad inherente, y que la programación debe tratarse como una demostración matemática formal, donde la precisión es esencial. La ambigüedad del lenguaje natural lo hace propenso a malinterpretaciones, lo que es catastrófico en programación. La corrección del software solo puede garantizarse mediante pruebas formales (como en matemáticas), no mediante descripciones informales. Dijkstra criticaba los intentos de "programación en lenguaje natural" de su época, COBOL, por generar código inseguro y difícil de verificar.

El término "alucinaciones" en el contexto de la IA se refiere a situaciones en las que un modelo genera respuestas factualmente incorrectas o, específicamente en programación, código sintácticamente válido pero lógicamente erróneo (por ejemplo, un algoritmo de ordenación que no ordena correctamente). El término es ampliamente aceptado en la literatura (ej: estudios de OpenAI, DeepMind y MIT). Se aplica tanto a respuestas incorrectas como a código mal generado (Sutskever et al., 2023).

La IA genera código a partir de descripciones en lenguaje natural, lo que reproduce el problema de ambigüedad que Dijkstra señaló en sus críticas a la programación basada en lenguaje natural. Los modelos de lenguaje de gran escala (LLM, por sus siglas en inglés) no construyen demostraciones matemáticas del código ni verifican su corrección formal; en cambio, predicen secuencias de tokens basándose en patrones estadísticos aprendidos de su corpus de entrenamiento. Estos modelos no "comprenden" el código en un sentido lógico o semántico, sino que imitan estructuras recurrentes en los datos. Por lo tanto, no pueden garantizar la corrección del código generado, ya que carecen de razonamiento lógico formal (como los sistemas de tipos avanzados o demostradores de teoremas).los modelos LLM carecen de capacidad de verificación rigurosa más allá de la coherencia estadística. Su funcionamiento se basa en probabilidades, no en lógica deductiva (Bender et al., 2021). GitHub Copilot ha sugerido implementaciones de criptografía inseguras (estudio de NYU, 2022).

 Un modelo puede, por ejemplo,  generar un quicksort que funciona en el 90% de los casos, pero falla en edge cases (ej: listas vacías). La IA puede generar código con licencias conflictivas o soluciones sesgadas (ej: algoritmos de contratación discriminatorios). 

Alan Kay (Premio Turing): "La IA actual es como un loro que repite código sin comprenderlo. Necesitamos sistemas que razonen como humanos, no solo estadística."
Leslie Lamport (TLA+, Verificación Formal): "Los modelos generativos son útiles para prototipos, pero la corrección exige métodos formales."

Según un estudio del MIT (2023), solo el 40% del código generado por GPT-4 pasa pruebas formales de corrección. Google DeepMind (2024) propone combinar LLMs con *verificadores automáticos (ej: Lean, Coq).

En situaciones potencialmente catastróficas como la falla en un avión es esencial determinar quien es responsable. UE AI Act (2024) exige transparencia en sistemas de alto riesgo, pero no resuelve la verificación formal. Estamos entusiásticamente entrando en un proceso de automatización de errores: Si la IA escala la generación de código incorrecto, ¿aumentarán los desastres por software defectuoso? Si los programadores confían ciegamente en la IA, ¿se perderá el pensamiento crítico?

Dijkstra acertó en que el lenguaje natural es ambiguo y la programación exige precisión. La IA generativa agrava el problema, pues escala la generación de código no verificado. El futuro requiere modelos híbridos (IA + verificadores formales) y nuevos marcos legales para responsabilidad en código generado. Es necesario entrenar a los programadores en pensamiento crítico para que no deleguen ciegamente en la IA. La tecnología no triunfa solo por ser superior, sino por resolver problemas reales de manera accesible.

Lecturas clave:

Dijkstra, EWD667 (1978).

"Formal Verification in the Age of AI" (Communications of the ACM, 2024).

"Who is Liable for AI-Generated Bugs?" (Stanford Law Review, 2023).

domingo, 9 de marzo de 2014

Clear code that works

A partir de una visión clara e intima del proceso de desarrollo de software, Kent Beck a creado un enfoque metodológico que  a primera vista pareciera contra intuitivo pero que ha resultado exitoso y ampliamente aceptado en la comunidad de programadores.

En Test Driven Development: By Example (Addison-Wesley Signature Series), el libro seminal de TDD, Beck aplica el refrán de divide y vencerás al precepto de calidad en la producción de código:  Clear code that works.
Beck propone contracorriente que es posible separar las consideraciones de calidad de código, desde la perspectiva de ingeniería de software, de la verificación de la funcionalidad, y que el primer paso en cada iteración del proceso de desarrollo es definir y aplicar las pruebas de funcionalidad.
Beck utiliza un proceso de refactorización para pasar de código funcional a código limpio, utilizando la eliminación de redundancia o duplicidad  como guía metodológica.
Haciendo una analogía con un semáforo,  Beck describe un proceso iterativo de 3 pasos:
  1. Rojo. Empezar con una prueba que debe fallar, tal ves ni compilar siquiera.
  2. Verde. Hacer que el código pase la prueba de la manera más expedita y simple, sin consideración alguna a normas y patrones de calidad de código.
  3. Refactorizar. Eliminar redundancia en código, pruebas, y datos.
De tan sencillo enfoque Beck elabora la metodología de desarrollo dirigido por pruebas.

viernes, 13 de julio de 2007

Introducción a Perl

Practical Extraction and Reporting Language.


There is more than one way to do it



Puntos a favor de perl:

  • perl es un lenguaje de alto nivel

  • perl es gratis

  • perl puede escribir y leer archivos binarios

  • perl puede tener múltiples archivos de entra y salida abiertos al mismo tiempo

  • Tiene un generador de reportes

  • Maneja expresiones regulares

  • Maneja arreglos lineales y asociativos

  • Es poderoso y simplifica la programación

  • Puede procesar archivos muy grandes sin limites en el tamaño de registro

  • perl incluye un conjunto amplio y poderoso de instrucciones para manejo de cadenas de caracteres y arreglos

  • Cualquier cosa se puede realizar de múltiples formas


Ejemplo de programa en Perl:







# Este sencillo programa copia registros de un archivo
# y agrega un prefijo a cada línea con un numero en secuencia
while (< >){
# while () {} genera un lazo de control que continua mientras el
# enunciado en paréntesis es verdadero.
# la instrucciones en el lazo están dentro de los corchetes {}
# < > es un símbolo especial
# Le dice a Perl que busque en la línea de comando y vea si se
# especificaron algunos archivos.
# Si es el caso, entonces se lee cada uno en turno.
# Si no se especifica ningún archivo entonces se lee de
# la entrada normal (standard input)
# Cualquiera que sea el caso los caracteres que se leen se guardan
# en la variable especial $_
# Cuando <> llega al fin de archivo (end-of-file), regresa un valor de falso,
# lo cual termina el lazo.
print STDOUT ++$i, $_;# print es un método simple sin formato de impresión
# STDOUT es una referencia de archivo normal (standard filehandle)
# para la salida normal (Standard Output).
# Filehandles se especifican en MAYUSCULAS en perl.
# ++$i indica incrementar el valor de $i y dejar el valor disponible
# para la instrucción print
# Todos los valores escalares ( es decir cualquier cosa menos una instrucción,
# un arreglo lineal, un arreglo asociativo, filehandle, o nombre de procedimiento)
# empieza con $ en perl # $_ es el operador de default de cualquier instrucción
# en este caso, $_ contiene el último registro que leyó la instrucción<>
# ; termina cada instrucción en perl
}

Breve revisión de la sintaxis de perl



  • En perl es significativo el caso de los caracteres y se diferencia entre mayúsculas y minúsculas

  • No utilice nombres que empiezen con un numero, ya que estos comúnmente son símbolos especiales para perl, por ejemplo $1, $2, etc.

  • Todas las instrucciones en perl terminan con punto y coma ;

  • Comentarios se pueden insertar en un programa con el símbolo #, y cualquier cosa después de # hasta el fin de línea será ignorado

  • perl identifica cada tipo de variable o nombre de dato con un prefijo. Estos caracteres son:




































    TipoCarácterComentario
    Escalar$Un numero o cadena de caracteres
    Vector lineal@Un arreglo referenciado por un numero índice.
    Subí­ndices entre paréntesis cuadrados [].
    @cosa se refiere al arreglo completo.
    $cosa[1] se refiere al escalar que ocupa la segunda posición en el arreglo
    Vector asociativo%Un vector referenciado por una llave de texto, no necesariamente un número.
    Subí­ndices entre{}.
    %cosa se refiere al vector completo.
    $elemento{"x"} se refiere al escalar que corresponde a la llave "x"
    filehandleUCLos apuntadores se archivo se escriben en mayúsculas
    Subrutina&Una subrutina
    Etiquetaxx:Objeto de goto


  • Valores entre paréntesis () son listas. Las listas se usan frecuentemente como argumentos para una subrutina o llamada a función. No es necesario usar paréntesis si solo se usa un argumento o el programa conoce el limite de la lista.

  • Las variables $x, @x. %x, y &x, no necesitan estar relacionadas entres si, sin mencionar $X, @X, %X y &X.

  • Existen variables especiales, las más importantes son:
    $_
    Es el valor escalar de default. Si no se especifica un nombre de variable en una función donde se usa una variable escalar, se usa $_. Esto se usa bastante en perl
    @_
    Es la lista de argumentos a una subrutina
    @ARGV
    Es la lista de argumentos especificada en la línea de comando cuando el programa se ejecuta


Instrucciones básicas y control


Los corchetes {} se usan para contener un bloque de enunciados. Es posible tener variables locales dentro de un bloque. Bloques se usan como los objetos de la mayoría de los comandos de control

Asignación simple:


  • Asignación escalar

  • Listas de escalares

  • Lista a vector

  • Vector a lista

  • Vectores asociativos necesitan un llave, pero aparte de eso, funcionan como se espera de un vector

  • Al asignar un vector a un escalar se obtiene el numero de elementos del vector


Operaciones aritméticas


if-then-else








  • if( condición ) {  rama verdadera  }  else  {  rama falsa  }









  • if (condición) {instrucciones}  elsif (condición) {instrucciones}

    elsif (condición) {instrucciones}









  • unless (condición)  {  rama verdadera  }



  • La condición tiene una gama amplia de operadores comparativos. Es importante observar la diferencia entre operadores numéricos y de cadenas de caracteres.



























    NuméricoCadenasSignificado
    = =eqigual
    !=neno igual
    >gtmayor que
    <ltmenor que


  • Cadenas de caracteres. que no están compuestas por números tienen un valor de cero.

  • perl cuenta con un conjunto extenso de pruebas de archivo:

    • -T cierto si archivo es de texto

    • -B cierto si archivo es binario

    • -M regresa el número de días desde la última modificación

    • -A regresa el número de días desde el último acceso al archivo

    • -C regresa el número de días desde la creación del archivo




Lazos de control
Los lazos más comunes son for y while

  • for ($i = 0; $i < 10; $i++) { instrucciones }

  • foreach $i (@items) { instrucciones }

  • foreach $i ($first .. $last) { instrucciones }

  • while (condición) { instrucciones }

  • until (condición) { instrucciones }

  • Las instrucciones next, last, redo, y continue se usan para escapar de un lazo.


Entrada/Salida
Abrir
Como en Unix, los tres primeros manejadores de archivos se abren automáticamente y son STDIN, STDOUT, y STDERR. Otros archivos se deben abrir explícitamente. La forma de la instrucción open es la siguiente:
open (FILEHANDLE,XFY);
donde X y Y son caracteres opcionales
X = <
Para abrir archivo F solo lectura
X = >
Para abrir archivo F solo escritura
X = > >
Para agregar datos al final de archivo F
X = |
Para escribir a un tubo (pipe) hacia programa F
Y = |
Para leer a un tubo (pipe) desde programa F
Si solo se da el nombre F, el archivo se abre de lectura/escritura

Lectura
La forma más básica de lectura es poner el manejador de archivos dentro de <>. Si no se provee una variable escalar para el registro, este se guarda en $_.
Escritura
La mayor parte de la escritura se hace usando la instrucción print o printf. Estas instrucciones se utilizan aún si el resultado no se va a imprimir realmente.
Cerrar
perl cierra automáticamente cualquier archivo al salir. Cuando se necesita cerrar un archivo se puede hacer con un cierre explicito.
Mensajes de error:


  • die se usa para imprimir un mensaje de error y terminar la ejecución

  • warn se usa para imprimir un mensaje de error pero continuar


Manejo de cadenas de caracteres:


  • split se usa para extraer fichas (tokens) o campos de una cadena a un vector.

  • sort ordena una lista o vector.

  • study optimiza operaciones de cadenas.


Codificación binaria:


  • pack empaca datos en una cadena usando un machote de formato

  • unpack recupera datos de una cadena usando un machote de formato

  • Existe una larga lista de formatos que se pueden usar

  • Se puede usar más de un formato a la vez

    • l long 32 bit signed integer

    • L long 32 bit unsigned integer

    • s short 16 bit signed integer

    • S short 16 bit unsigned integer

    • f float 32 bit floating point

    • d double 64 bit floating point

    • A ASCII string

    • c char a single byte (character)





Expresiones regulares:


perl añade un conjunto de caracteres al conjunto normal. Uno uso importante de expresiones regulares (RE) es el uso de () para seleccionar subconjuntos de la expresión regular. perl facilita el uso del operador (). Existen dos maneras de usar expresiones regulares en perl: Match y Substitute

Una expresión regular esta contenida en slashes, y el operador =~ evalúa.

Las expresiones regulares son sensitivas a mayúsculas y minúsculas

El operador !~ se usa para detectar diferencias.

Algunos caracteres especiales:

.
Cualquier Carácter menos newline
^
El principio de lí­nea o de cadena
$
El fin de línea o cadena
?
Cero o más del último Carácter
+
Uno o más del último Carácter
[]
Cualquiera de los caracteres dentro de los corchetes []
|
o inclusivo
()
Agrupar
\
Los caracteres especiales $, |, [, ), \, / deben ir precedidos por backslash para usarse en expresiones regulares
$` $& $'
$` , $& y $' se pueden usar para ver cuales fueron los caracteres que se encontraron antes, durante, y después de un empate

Referencias


Active State

Active Perl

Live tutorials

Distribuciones binarias

Open Perl IDE

Documentación.

Introduction to Perl