🧟 El mismo problema en los lenguajes que siguen vivos — Clase 137

⬅️ Volver a la clase 137 · 🧬 Primos del Atlas · 🧟 Índice de lenguajes vivos · 📚 Índice

Clasificar un error por su fase. La clase 123 dio el mapa de las fases; esta lo usa para lo que importa: saber en qué fase falló ahorra la mitad del trabajo. Y aquí hay dos casos extremos: Ada detecta en la fase de bind lo que C++ descubre en ejecución (clase 123), y M no detecta casi nada hasta que la línea se ejecuta — porque su código es texto que se construye en marcha.

🎯 Estos lenguajes no están aquí por ser antiguos

El criterio es doble y se declara en la ficha de cada uno: que se ejecute hoy —banca, sanidad, aviónica, ERP, diseño de chips, CAD— y que deje a la vista un concepto que los diez del núcleo esconden.

Aquí el concepto es la taxonomía del fallo, y estos lenguajes la enseñan porque en ellos las fases están separadas y cada una tiene su vocabulario. Un syntax error, unexpected AREA de GnuCOBOL es del analizador (clase 100); un not dispatching (must be defined in a package spec) de GNAT es del semántico (clase 111); un undefined reference es del enlazador; y un Constraint_Error es de ejecución.

Y muestran el eje que de verdad importa: cuánto se detecta antes. Ada y Fortran con módulos comprueban entre unidades; C y PL/I emparejan por nombre y no comprueban nada (clase 088).

Y ninguno es una foto fija: casi todos han incorporado en los últimos años JSON, REST, GPU, Unicode o Git. Cada ficha lo detalla en su sección 🔄 Lo que se ha modernizado.

El contrato, igual para todos

stdin esperado
1 error=sintaxis
3 error=enlace
4 error=ejecucion

Qué está verificado en esta página. Los lenguajes de la sección 🟢 se ejecutan en CI contra este mismo casos.json, igual que las diez implementaciones del núcleo (workflow Labs). Los de la sección 🟡 no pueden cumplir este contrato sin falsear el lenguaje, y se explica por qué. Los de la sección ⚪ sí podrían, pero su cadena de herramientas no está en los runners: son correctos, sin sello de máquina.


🟢 Se ejecutan en CI

COBOL

Ficha completa · Banca, seguros, gobierno, medios de pago · cobc -x -free prog.cob

IDENTIFICATION DIVISION.
PROGRAM-ID. ERRORES.

DATA DIVISION.
WORKING-STORAGE SECTION.
01  LINEA   PIC X(80).
01  CODIGO  PIC 9(2) COMP.
01  TIPO    PIC X(12).

PROCEDURE DIVISION.
    ACCEPT LINEA
    COMPUTE CODIGO = FUNCTION NUMVAL(LINEA)

    EVALUATE CODIGO
        WHEN 1  MOVE "sintaxis"  TO TIPO
        WHEN 2  MOVE "tipos"     TO TIPO
        WHEN 3  MOVE "enlace"    TO TIPO
        WHEN 4  MOVE "ejecucion" TO TIPO
        WHEN OTHER MOVE "?"      TO TIPO
    END-EVALUATE

    DISPLAY "error=" FUNCTION TRIM(TIPO)
    STOP RUN.

Lo que esta clase enseña en COBOL. La cadena de COBOL (clase 123) tiene más fases que ninguna otra de esta página, y cada una falla con su propio vocabulario:

Fase Ejemplo de error Cuándo
Librarian COPY member not found expansión
Precompilador DB2 DSNH104I ILLEGAL SYMBOL antes de compilar
Traductor CICS DFH7011I antes de compilar
Compilador IGYPS0037-S Syntax error análisis
Enlazador IEW2456E UNRESOLVED EXTERNAL enlace
BIND de DB2 DSNT408I SQLCODE = -204, TABLE NOT FOUND después de compilar
Ejecución S0C7 data exception producción

El BIND es una fase que casi ningún lenguaje tiene: el plan de acceso a la base de datos se valida por separado, así que una tabla que falta se detecta al hacer BIND, no al compilar ni al ejecutar.

Y S0C7 merece una explicación porque es el error de ejecución más famoso del mainframe: excepción de datos, y ocurre cuando una instrucción decimal recibe algo que no es un decimal empaquetado válido.

En la práctica significa un campo numérico con basura, casi siempre porque el registro leído no tenía el formato esperado (clase 106) o porque un campo no se inicializó.

Es la razón de la disciplina de INITIALIZE y de comprobar FILE STATUS tras cada operación (clase 104): en un lenguaje sin tipos comprobados en la frontera de los datos, el error aparece al usarlos.

Y COBOL tiene un mecanismo de diagnóstico que esta clase debe destacar y que es de los mejores de esta página: el listado de compilación.

OPTIONS: XREF, MAP, LIST, OFFSET, SOURCE

El listado incluye el fuente expandido, una tabla de referencias cruzadas de cada nombre, el mapa de almacenamiento con el desplazamiento de cada campo, y el ensamblador generado. Es un documento de decenas de páginas y responde a casi cualquier pregunta sobre qué entendió el compilador.

En una época sin depuradores interactivos, ese listado era la herramienta de diagnóstico, y sigue siendo la forma de resolver un S0C7: buscar el desplazamiento en el mapa y ver qué campo hay ahí.

Fortran

Ficha completa · HPC, clima, física, BLAS/LAPACK · gfortran -O2 prog.f90

program errores
   implicit none
   integer :: codigo
   character(len=12) :: tipo

   read(*, *) codigo

   select case (codigo)
   case (1);     tipo = 'sintaxis'
   case (2);     tipo = 'tipos'
   case (3);     tipo = 'enlace'
   case (4);     tipo = 'ejecucion'
   case default; tipo = '?'
   end select

   write(*, '(A)') 'error=' // trim(tipo)
end program errores

Lo que esta clase enseña en Fortran. Fortran tiene una historia con esta clase que explica muchas de sus decisiones modernas: durante décadas, casi nada se comprobaba.

El implicit none que abre todos los programas de esta serie es la prueba. Sin él, Fortran aplica la regla implícita:

      X = 3.14        ! X es REAL: empieza por A-H o O-Z
      I = 42           ! I es INTEGER: empieza por I-N
      LONGITUD = 10     ! ¡INTEGER! empieza por L

Cualquier nombre no declarado se convierte en una variable nueva, con el tipo según su primera letra. Y de ahí el error más famoso de la historia de Fortran:

      DO 10 I = 1.10        ! un PUNTO en vez de una coma

Eso no es un bucle: es una asignación a una variable llamada DO10I con el valor 1.10. Compilaba sin avisar, y la leyenda —probablemente apócrifa— lo culpa de la pérdida de la sonda Mariner 1.

implicit none cierra ese agujero, y es la primera línea obligatoria de cualquier Fortran moderno.

Y las fases de Fortran fallan así:

Fase Ejemplo
Sintaxis Error: Expecting END PROGRAM statement
Semántica con módulos Error: Type mismatch in argument 'x' at (1)
Semántica SIN módulos silencio: el procedimiento externo no se comprueba
Enlace undefined reference to 'calcular_'
Ejecución Fortran runtime error: Index '11' of dimension 1 above upper bound of 10

La tercera fila es la clave y ya apareció en la clase 109: un procedimiento externo suelto no tiene interfaz explícita, así que llamarlo con argumentos de otro tipo compila y enlaza.

Ese es el motivo real de que la recomendación número uno para modernizar Fortran sea meter todo en módulos: no es organización, es activar la comprobación.

Y el subrayado del nombre —calcular_— es el name mangling de Fortran, que la clase 157 detallará: el compilador añade un guion bajo, y por eso los errores de enlace con C aparecen con ese sufijo.

Ada

Ficha completa · Aviónica, espacio, ferrocarril, defensa · gnatmake prog.adb

with Ada.Text_IO; use Ada.Text_IO;
with Ada.Integer_Text_IO; use Ada.Integer_Text_IO;

procedure Errores is
   Codigo : Integer;
begin
   Get (Codigo);

   case Codigo is
      when 1      => Put_Line ("error=sintaxis");
      when 2      => Put_Line ("error=tipos");
      when 3      => Put_Line ("error=enlace");
      when 4      => Put_Line ("error=ejecucion");
      when others => Put_Line ("error=?");
   end case;
end Errores;

Lo que esta clase enseña en Ada. Ada está diseñado para que la mayor parte de los errores caigan en las fases tempranas, y esta clase permite ver cuántas categorías traslada hacia arriba:

Error Dónde falla en C Dónde falla en Ada
Tipo incompatible compilación (a veces) compilación, siempre
Unidad no recompilada enlace o ejecución bind (clase 123)
Orden de inicialización ejecución bind
Índice fuera de rango ejecución (o nada) ejecución, con excepción clara
Valor fuera de rango nada ejecución, Constraint_Error
Método mal redefinido ejecución compilación, con overriding
Contrato incumplido nada ejecución, o DEMOSTRADO con SPARK

Cada fila que sube una fila ahorra órdenes de magnitud, que es el argumento del cierre de esta clase.

Y los errores de ejecución de Ada tienen nombres que dicen qué pasó, no dónde:

raised CONSTRAINT_ERROR : prog.adb:15 index check failed
raised STORAGE_ERROR : stack overflow
raised PROGRAM_ERROR : access before elaboration
raised TASKING_ERROR : dependent task terminated

PROGRAM_ERROR: access before elaboration es el que ilustra mejor esta clase: se usó algo antes de que su paquete se inicializara (clase 123). En C++, ese mismo fallo es el fiasco del orden de inicialización estática y produce basura sin mensaje.

Y GNAT tiene un conjunto de comprobaciones que conviene conocer porque suben más errores de fase:

gnatmake -gnatwa -gnatVa -gnato -gnata prog.adb

-gnatwa activa todos los avisos, -gnatVa todas las comprobaciones de validez, -gnato la comprobación de desbordamiento y -gnata las aserciones y los contratos.

Y gnatprove cierra el cuadro (clase 118): lo que no se puede comprobar al compilar, se demuestra — y lo demostrado se puede dejar de comprobar en ejecución, sin perder la garantía.

Es la única vía de esta página que permite quitar comprobaciones sin quitar seguridad.

Pascal

Ficha completa · Escritorio empresarial, TPV, industria (vía Delphi y Free Pascal) · fpc -Mobjfpc prog.pas

program Errores;
{$MODE OBJFPC}{$H+}
uses SysUtils;

var
  Codigo: Integer;

begin
  Read(Codigo);

  case Codigo of
    1: WriteLn('error=sintaxis');
    2: WriteLn('error=tipos');
    3: WriteLn('error=enlace');
    4: WriteLn('error=ejecucion');
  else
    WriteLn('error=?');
  end;
end.

Lo que esta clase enseña en Pascal. Pascal fue diseñado para que el compilador de una pasada cazara mucho (clase 123), y su fama entre generaciones de estudiantes viene precisamente de eso: avisaba de todo, a veces con exceso de celo.

Sus errores clásicos son didácticos:

Error: Identifier not found "Contador"          { declara antes de usar }
Error: Incompatible types: got "Real" expected "Integer"
Error: Illegal expression                        { falta un ; o sobra }
Fatal: Syntax error, ";" expected but "identifier" found
Warning: Variable "X" does not seem to be initialized
Note: Local variable "Y" is assigned but never used

Los avisos y las notas de Free Pascal son de los más útiles de esta página, y -Sew los convierte en errores — que es la disciplina recomendada en un proyecto serio.

Y los errores de ejecución de Pascal tienen números que la comunidad conoce de memoria:

Runtime error 201: Range check error       { con {$R+} }
Runtime error 215: Arithmetic overflow      { con {$Q+} }
Runtime error 216: General protection fault  { puntero inválido }
Runtime error 202: Stack overflow
Runtime error 203: Heap overflow

RTE 201 y RTE 215 solo aparecen si las comprobaciones están activadas, y ahí está la trampa que esta clase debe señalar: Free Pascal las trae DESACTIVADAS en la configuración de publicación.

{$R+}      { comprobación de rango }
{$Q+}       { comprobación de desbordamiento }
{$S+}        { comprobación de pila }

Un programa compilado sin ellas no avisa de un índice fuera de rango: escribe donde no debe, que es el comportamiento de C.

Es exactamente el compromiso de las clases 089 y 124, y la recomendación práctica es la misma que en todos los ecosistemas: comprobaciones activadas en desarrollo y en pruebas, y decidir conscientemente en producción.

Y hay un detalle de Delphi que merece nombrarse: Assert se elimina con {$C-}, así que las aserciones son gratis en producción — el mismo modelo que assert en C y pragma Assertion_Policy en Ada.

Common Lisp

Ficha completa · IA simbólica, CAD, investigación · sbcl --script prog.lisp

(let* ((codigo (read))
       (tipo (case codigo
               (1 "sintaxis")
               (2 "tipos")
               (3 "enlace")
               (4 "ejecucion")
               (t "?"))))
  (format t "error=~A~%" tipo))

Lo que esta clase enseña en Common Lisp. Lisp desdibuja las fases de esta clase, y de una forma que merece explicarse: la compilación es incremental y ocurre en cualquier momento (clase 124).

Un error de sintaxis en Lisp es un error del lector, y se detecta al leer:

end of file on #<STREAM> inside read     ; un paréntesis sin cerrar

Ese es el error de sintaxis más común de Lisp, y es notablemente pobre: dice que se acabó el fichero, no dónde faltaba el paréntesis. Los editores lo compensan con el emparejamiento automático y con la edición estructural —Paredit—, que es la razón de que la comunidad Lisp use editores tan específicos.

Y de tipos, Lisp comprueba en ejecución por defecto:

The value 3 is not of type STRING when binding X

Con una diferencia importante: SBCL hace inferencia de tipos y avisa en compilación cuando puede demostrar un error:

caught WARNING: Constant 3 conflicts with its asserted type STRING.
caught STYLE-WARNING: The variable X is defined but never used.
note: doing signed word to integer coercion, can't open code

Esa tercera es la más útil y la más peculiar (clase 124): SBCL avisa de que no puede optimizar y por qué. Es un diálogo con el compilador que ningún otro lenguaje de esta página ofrece.

Y "enlace" en Lisp no existe como fase, y su equivalente sí:

The function FOO is undefined.

Una función no definida se detecta al LLAMARLA, no al cargar. Es la máxima flexibilidad —se puede definir después— y la mínima detección temprana.

Con la contrapartida que ya se contó en la clase 103: ese error abre el depurador con reinicios.

Restarts:
  0: [CONTINUE] Retry calling FOO.
  1: [USE-VALUE] Call specified function.
  2: [RETURN-VALUE] Return specified values.
  3: [ABORT] Exit debugger.

Se puede definir la función que falta y elegir "reintentar", y el programa continúa como si nada. Es el mejor manejo de errores de ejecución de esta página, y compensa buena parte de la detección tardía.

Tcl

Ficha completa · Diseño de chips (EDA), redes, testing · tclsh prog.tcl

gets stdin linea
set codigo [string trim $linea]

switch -exact -- $codigo {
    1       { set tipo "sintaxis" }
    2       { set tipo "tipos" }
    3       { set tipo "enlace" }
    4       { set tipo "ejecucion" }
    default { set tipo "?" }
}

puts "error=$tipo"

Lo que esta clase enseña en Tcl. En Tcl casi todo es un error de ejecución, y esa es la consecuencia directa de su modelo: el código es texto que se analiza al ejecutarse (clase 123).

invalid command name "foo"                    ; el comando no existe: al LLAMARLO
can't read "x": no such variable               ; la variable no existe: al LEERLA
wrong # args: should be "proc a b"              ; aridad: al llamar
expected integer but got "hola"                  ; tipo: al operar

No hay fase de tipos ni de enlace, y un error de sintaxis solo se detecta cuando se va a ejecutar ese trozo:

proc nunca {} { esto { es } basura sin cerrar    ;# no falla hasta llamarla

Y de ahí la importancia de dos herramientas del ecosistema que esta clase debe nombrar:

info complete (clase 123), que comprueba si un texto es sintácticamente completo:

if {[info complete $codigo]} { eval $codigo }

Y los analizadores estáticos, que en Tcl son especialmente valiosos por lo tardío de la detección:

nagelfar prog.tcl        # el analizador estático de referencia
frink prog.tcl            # formateo y comprobación

Nagelfar comprueba aridades, nombres de comando, tipos aproximados y errores de citación, y es lo más parecido a una fase de comprobación que tiene Tcl.

Y lo que Tcl sí da, y muy bien, es la información del error de ejecución:

catch { ... } resultado opciones
puts [dict get $opciones -errorinfo]      ;# la pila COMPLETA
puts [dict get $opciones -errorcode]       ;# un código estructurado

-errorinfo da la traza con el texto de cada comando de la pila, no solo nombres de función. Es más informativo que una traza normal, porque muestra el comando que se estaba ejecutando.

Es el mismo principio que $stack("MCODE") en M (clase 127): cuando el código es texto accesible, el diagnóstico puede mostrarlo.

Perl

Ficha completa · Sysadmin, texto, bioinformática · perl prog.pl

use strict;
use warnings;

my $codigo = <STDIN>;
chomp $codigo;

my %tipos = (1 => 'sintaxis', 2 => 'tipos', 3 => 'enlace', 4 => 'ejecucion');

print "error=", ($tipos{$codigo} // '?'), "\n";

Lo que esta clase enseña en Perl. Perl compila el programa entero antes de ejecutarlo (clase 123), así que sí tiene una fase de sintaxis que caza mucho:

perl -c programa.pl        # comprobar sin ejecutar

Y sus errores de compilación son famosos por una razón concreta: el mensaje suele apuntar a la línea siguiente.

syntax error at prog.pl line 15, near "foo"
Global symbol "$x" requires explicit package name at prog.pl line 12.
Missing right curly or square bracket at prog.pl line 40, at end of line

Ese último es el error más temido de Perl: una llave sin cerrar en la línea 5 se detecta en la 40, porque el analizador sigue leyendo hasta que se acaba el fichero. Es el mismo problema del paréntesis de Lisp de esta página.

Y las dos directivas que convierten a Perl en un lenguaje comprobado son las de esta clase:

use strict;        # sin declarar, sin referencias simbólicas, sin barewords
use warnings;       # avisos de todo lo sospechoso

use strict mueve una familia entera de errores de la fase de ejecución a la de compilación: sin él, una variable mal escrita crea una variable nueva (como el implicit de Fortran de esta página), y el fallo aparece mucho después con un valor vacío.

Es probablemente la línea más importante de cualquier programa Perl, y su historia es instructiva: strict existe desde Perl 5.0 (1994) y no se activa por defecto por compatibilidad. Solo desde Perl 5.36, escribir use v5.36; lo activa automáticamente.

Treinta años entre saber la solución y poder aplicarla por defecto.

Y use warnings avisa de lo que el cierre de esta clase quiere cazar antes:

Use of uninitialized value $x in addition
Argument "hola" isn't numeric in numeric eq (==)      # clase 101
Deep recursion on subroutine "main::f"                 # clase 127
Odd number of elements in hash assignment

Y el ecosistema añade la capa de análisis estático:

perlcritic --brutal prog.pl        # las reglas del libro "Perl Best Practices"

C++

Ficha completa · Videojuegos, navegadores, finanzas, HPC · g++ -std=c++17 prog.cpp

#include <iostream>
#include <string>

int main() {
    int codigo{};
    if (!(std::cin >> codigo)) return 1;

    std::string tipo;
    switch (codigo) {
        case 1:  tipo = "sintaxis";  break;
        case 2:  tipo = "tipos";      break;
        case 3:  tipo = "enlace";      break;
        case 4:  tipo = "ejecucion";    break;
        default: tipo = "?";             break;
    }

    std::cout << "error=" << tipo << '\n';
    return 0;
}

Lo que esta clase enseña en C++. C++ tiene las cuatro fases muy marcadas, y sus errores son célebres por lo difíciles de leer:

error: expected ';' before '}' token                    # sintaxis
error: no matching function for call to 'f(int)'         # tipos, con 200 candidatos
undefined reference to `Clase::metodo()'                  # ENLACE
Segmentation fault (core dumped)                           # ejecución, sin información

El error de enlace más frecuente de C++ —undefined reference— tiene tres causas típicas que conviene reconocer:

  1. Se declaró un método y no se definió.
  2. Se olvidó enlazar la biblioteca (-lm, -lpthread).
  3. Discrepancia de nombres decorados: el .h es de C y no lleva extern "C" (clase 157).

Y esta clase es el sitio para el problema de los mensajes de plantillas, ya mencionado en la clase 118: un error dentro de una plantilla produce páginas de texto porque el compilador cuenta toda la cadena de instanciación.

C++20 lo alivió con los conceptos (clase 112):

template <std::integral T> void f(T x);
// error: constraints not satisfied: 'std::string' does not satisfy 'integral'

Un error de concepto dice qué requisito falla, no dónde reventó la instanciación. Es una de las mejoras de usabilidad más importantes de la historia del lenguaje.

Y sobre los errores de ejecución, C++ está en el peor sitio de esta página: el comportamiento indefinido no produce un error, produce un programa sin significado (clase 136).

Las herramientas son imprescindibles, y esta clase debe listarlas:

g++ -Wall -Wextra -Wpedantic -Werror     # avisos como errores
g++ -fsanitize=address,undefined          # memoria y comportamiento indefinido
g++ -fsanitize=thread                      # carreras (clase 136)
valgrind ./prog
clang-tidy prog.cpp                          # análisis estático

-fsanitize=undefined convierte comportamiento indefinido en un mensaje con fichero y línea, y es lo que más se acerca a subir esos errores de fase.

Es la conclusión de esta clase en C++: el lenguaje no detecta, y el ecosistema ha construido las herramientas que detectan — y usarlas no es opcional.


🟡 Contrato adaptado, y declarado

Estos lenguajes no pueden leer de stdin y escribir en stdout sin dejar de ser ellos mismos. No es una limitación del material: es su naturaleza. El cálculo es el mismo y la forma de entrar y salir es la de su anfitrión. No pasan por el verificador, y se dice.

RPG

Ficha completa · IBM i: ERP, retail, logística, manufactura · CRTBNDRPG sobre IBM i

En IBM i un programa recibe sus datos por parámetros, por un fichero o por una pantalla, nunca por la entrada estándar.

**free
ctl-opt dftactgrp(*no) actgrp(*caller);

dcl-pi ERRORES;
  codigo int(10) const;
end-pi;

dcl-s tipo varchar(12);

select;
  when codigo = 1;  tipo = 'sintaxis';
  when codigo = 2;  tipo = 'tipos';
  when codigo = 3;  tipo = 'enlace';
  when codigo = 4;  tipo = 'ejecucion';
  other;            tipo = '?';
endsl;

dsply ('error=' + tipo);

*inlr = *on;
return;

Lo que esta clase enseña en RPG. Los errores de RPG llevan códigos con prefijo que identifican la fase, y esa nomenclatura es sistemática en toda la plataforma:

Prefijo Fase
RNF compilación de RPG (RPG No Free, histórico)
RNS compilación, formato libre
CPF sistema operativo: objeto no encontrado, sin autoridad
MCH máquina: puntero inválido, división por cero
RNQ ejecución de RPG: índice fuera de rango, error de conversión
SQL precompilador o ejecución de SQL

RNQ0121 es índice fuera de rango y MCH3601 es puntero inválido, y quien trabaja en la plataforma los reconoce de memoria.

Y hay una particularidad del enlace en IBM i que ya apareció en la clase 086 y que aquí es un error de fase propio: la firma del programa de servicio.

CPF3EE1 - La firma del programa de servicio UTILES no coincide

Eso se detecta al CARGAR el programa, no al enlazarlo ni al ejecutar la llamada. Si alguien cambió el orden de las exportaciones de un programa de servicio, todos sus clientes fallan al arrancar con un mensaje claro — en lugar de llamar a la función equivocada.

Es una fase de comprobación que casi ningún sistema tiene, y es lo que hace segura la actualización de bibliotecas compartidas en esa plataforma.

Y el diagnóstico de los errores de ejecución tiene una propiedad que esta clase debe destacar y que la clase 138 desarrollará: el registro del trabajo lo conserva todo.

DSPJOBLOG JOB(123456/USUARIO/MIAPP)

Cada mensaje, con su código, su texto, su ayuda de segundo nivel, el programa y el número de sentencia que lo provocó, y la pila de llamadas en ese momento. Sin configurar nada y sin instrumentar el código.

Es observabilidad por defecto, y es la razón de que diagnosticar un fallo en IBM i sea a menudo más rápido que en un servidor moderno con registros dispersos.


⚪ Correctos, sin sello de máquina

Sí podrían cumplir el contrato, pero su cadena de herramientas no está en los runners de CI.

PL/I

Ficha completa · Mainframe z/OS: banca, seguros · IBM Enterprise PL/I for z/OS

 errores: procedure options(main);

    declare codigo fixed binary(31);
    declare tipo char(12) varying;

    get list (codigo);

    select (codigo);
       when (1) tipo = 'sintaxis';
       when (2) tipo = 'tipos';
       when (3) tipo = 'enlace';
       when (4) tipo = 'ejecucion';
       otherwise tipo = '?';
    end;

    put skip list ('error=' || tipo);

 end errores;

Lo que esta clase enseña en PL/I. PL/I tiene un sistema de diagnóstico de compilación que era excepcional para su época y que sigue siendo instructivo: los mensajes se clasifican por severidad.

I  - Informational   (nota)
W  - Warning          (aviso)
E  - Error             (error, pero compila)
S  - Severe            (grave)
U  - Unrecoverable      (no se puede continuar)

Y E significa "hay un error y he generado código igualmente", con una suposición documentada. Eso suena temerario y tenía un motivo: en la época de las tarjetas perforadas, una compilación tardaba horas, así que el compilador intentaba seguir para encontrar todos los errores de una vez en lugar de parar en el primero.

Esa filosofía —recuperación de errores para diagnosticar todo en una pasada— es la misma que hoy tienen los compiladores modernos, y en PL/I estaba llevada al extremo de generar código.

Y PL/I tiene la característica que más se le reprocha en esta clase, ya nombrada en la clase 107: las conversiones implícitas.

 declare x fixed decimal(5,2);
 declare c char(10);
 x = c;          /* convierte la cadena a número: si no lo es, CONVERSION */

Casi cualquier cosa se convierte a cualquier otra, y el error aparece en ejecución como la condición CONVERSION — que, eso sí, es capturable y permite corregir el dato y reanudar con onsource (clase 116).

Es el compromiso de PL/I entero: máxima flexibilidad, comprobación tardía y un mecanismo de recuperación excelente.

Y las condiciones de PL/I son la taxonomía de errores de ejecución más detallada de esta página:

 on conversion       ...     /* dato no convertible */
 on zerodivide        ...     /* división por cero */
 on overflow           ...     /* desbordamiento */
 on subscriptrange      ...     /* índice fuera de rango (clase 089) */
 on stringrange          ...     /* subcadena fuera de rango */
 on endfile(f)            ...     /* fin de fichero */
 on key(f)                 ...     /* clave no encontrada */
 on undefinedfile(f)        ...     /* no se pudo abrir */
 on storage                  ...     /* sin memoria */
 on error                     ...     /* cualquier otra */

Diez condiciones distintas para diez fallos distintos, cada una con su manejador. Es más fino que el catch de la mayoría de los lenguajes modernos, y es de 1964.

M / MUMPS

Ficha completa · Sanidad: historia clínica, VistA, Epic · YottaDB

ERRORES ; Tipos de error -- clase 137
 read codigo
 set tipo = $select(codigo=1:"sintaxis", codigo=2:"tipos", codigo=3:"enlace", codigo=4:"ejecucion", 1:"?")
 write "error=", tipo, !
 quit

Lo que esta clase enseña en M. En M, casi todo es un error de ejecución, y su modelo es el más tardío de esta página, por la razón de la clase 123: el código se puede construir con texto y ejecutar con xecute o indirección.

Los códigos de error de M están estandarizados y empiezan por M:

M6   - Variable indefinida
M7   - Nodo de global indefinido
M9   - División por cero
M26  - Etiqueta no encontrada
M56  - Identificador demasiado largo
M57  - Etiqueta duplicada
M53  - Demasiados niveles de anidamiento
ZLINKFILE, ZSRCHSTRINCL... (extensiones $Z de cada implementación)

M6 —variable indefinida— es el error más común de M, y es la consecuencia directa de no tener declaraciones (clase 082): escribir mal un nombre no es un error de compilación, es una variable que no existe.

Es exactamente el problema que use strict resuelve en Perl y implicit none en Fortran, y M no tiene equivalente. La defensa es $get con valor por defecto (clase 116) y la disciplina.

Y M tiene un mecanismo de manejo de errores propio y peculiar: $etrap y $ecode.

 set $etrap = "do error^UTIL quit"        ; código a ejecutar SI HAY ERROR
 set $ecode = ",M6,"                        ; el código de error actual
 write $stack($stack, "ECODE")               ; el error de un nivel de la pila
 write $zstatus                               ; texto completo (extensión)

$etrap es una cadena de código M que se ejecuta cuando salta un error, y su alcance es la pila: si el nivel actual no lo maneja, sube.

Es un manejador de errores guardado como texto en una variable especial, coherente con todo lo demás del lenguaje — y con la misma consecuencia: flexible e imposible de analizar.

Y merece cerrar con lo que M sí da y que la clase 138 desarrollará: $stack con MCODE devuelve el código fuente de cada nivel de la pila (clase 127).

Un error en M puede informar de la línea exacta de código de cada nivel, sin depurador y sin símbolos, porque el fuente vive en la base de datos.

Smalltalk

Ficha completa · Banca, seguros, trading · Pharo

| codigo tipo |

codigo := stdin nextLine trimBoth asNumber.

tipo := codigo = 1 ifTrue: [ 'sintaxis' ] ifFalse: [
        codigo = 2 ifTrue: [ 'tipos' ] ifFalse: [
        codigo = 3 ifTrue: [ 'enlace' ] ifFalse: [
        codigo = 4 ifTrue: [ 'ejecucion' ] ifFalse: [ '?' ] ] ] ].

Transcript show: 'error=', tipo; cr.

Lo que esta clase enseña en Smalltalk. Smalltalk casi no tiene fases (clase 123): se compila un método cada vez, al aceptarlo, y no hay enlace.

Y eso reparte los errores así:

Tipo Cuándo
Sintaxis al aceptar el método, en el editor, inmediatamente
Nombre desconocido al aceptar: el editor pregunta si es una variable nueva
Tipos al enviar el mensaje, en ejecución
Enlace no existe
Método inexistente al enviarlo: doesNotUnderstand:

La segunda fila es característica y muy buena: al aceptar un método que usa un nombre desconocido, el entorno pregunta —"¿es una variable de instancia, una temporal, una global, o lo has escrito mal?"— y ofrece corregirlo.

Es detección temprana con diálogo, y resuelve el problema de M6 en M y del implicit en Fortran de esta misma página preguntando en lugar de suponiendo.

Y los errores de ejecución de Smalltalk son, con diferencia, los mejores de esta página, por lo que la clase 127 explicaba: la pila es un objeto.

MessageNotUnderstood: Persona>>#nombreCompleto

Y al saltar, se abre el depurador sobre el proceso vivo, con:

Ese flujo —el error abre el editor en el punto exacto, escribes lo que falta y continúas— es la experiencia de depuración que la mayoría de los entornos no ha alcanzado en cuarenta años.

Y merece cerrar esta clase con lo que eso implica sobre su taxonomía: en Smalltalk, un error de ejecución no es el final de nada. Es una interrupción con toda la información disponible y la posibilidad de arreglar el programa sin perder el estado.

Es exactamente lo contrario del Segmentation fault de C++ de esta misma página, y las dos son consecuencias coherentes de las decisiones de la Parte 8 entera.


Y de vuelta a la clase

Lo transferible: cada fase que detecta un error ahorra órdenes de magnitud respecto a la siguiente. Un error de sintaxis cuesta segundos; uno de tipos, minutos; uno de enlace, una tarde; y uno de ejecución en producción, una llamada de madrugada. Por eso los lenguajes con comprobación fuerte piden ceremonia: están comprando detección temprana. Y por eso la pregunta útil ante un fallo nuevo no es "¿cómo lo arreglo?" sino "¿qué fase debería haberlo cazado?".

⏮️ Volver a la clase 137 · 🧬 Los primos del Atlas · 🧟 Índice de lenguajes vivos