La conversión se completó en tres meses. La aplicación Java se compiló, superó las pruebas básicas de humo y se declaró lista para su implementación. Seis meses después de su puesta en producción, el equipo de la plataforma notó algo preocupante: nadie del equipo de Java podía mantener el código. Las revisiones duraban el doble que las revisiones normales de Java. Los informes de errores hacían referencia a conceptos, como WORKING-STORAGE, la lógica de PROCEDURE DIVISION y las convenciones de campos decimales empaquetados, que los desarrolladores de Java sin experiencia en mainframes no podían comprender. La aplicación se había convertido de COBOL a Java, pero el Java resultante pensaba, se estructuraba y se comportaba como COBOL. Era Jobol.
Jobol (a veces escrito JOBOL) es el acrónimo para código Java que conserva la semántica procedimental de COBOL sin adoptar las capacidades orientadas a objetos de Java. Jason Bloomberg, fundador de la firma de análisis Intellyx, lo definió directamente: Jobol es código Java técnicamente válido que mantiene la arquitectura de software original y conserva la semántica de COBOL, lo que obliga a los desarrolladores a tratar el código Java como si fuera COBOL. La consecuencia crítica es inmediata: una empresa que convierte COBOL a Jobol no se ha liberado de la necesidad de conocimientos especializados en COBOL. Simplemente ha trasladado ese requisito a un lenguaje que no admite de forma nativa los patrones de COBOL que contiene el código. Ha creado un sistema más difícil de mantener que el COBOL original y que no ofrece ninguno de los beneficios esperados de Java.
Grafo de dependencias antes del diseño del dominio
SMART TS XL mapea cada dependencia PERFORM y CALL para que la capa de servicio Java refleje la estructura de acoplamiento real, no la estructura de clases COBOL.
DESCUBRE MÁS…Cómo luce realmente Jobol
Antes de diagnosticar las causas, es necesario visualizar el patrón. Aquí se muestra la misma lógica de negocio, una verificación de crédito del cliente, en tres formatos: el COBOL original, el Jobol que suelen generar los convertidores automatizados y el Java idiomático que produce una modernización experta.
El COBOL original:
cobol
PERFORM-CREDIT-CHECK.
MOVE WS-CUST-ID TO DB-CUST-KEY
READ CUSTOMER-FILE KEY IS DB-CUST-KEY
INVALID KEY
MOVE 'NOT FOUND' TO WS-STATUS
PERFORM HANDLE-MISSING-CUSTOMER
END-READ
IF CUST-CREDIT-SCORE < 650
MOVE 'DECLINED' TO WS-CREDIT-RESULT
MOVE 'Score below threshold' TO WS-DECLINE-REASON
ELSE IF CUST-OUTSTANDING-BALANCE > CUST-CREDIT-LIMIT
MOVE 'DECLINED' TO WS-CREDIT-RESULT
MOVE 'Balance exceeds limit' TO WS-DECLINE-REASON
ELSE
MOVE 'APPROVED' TO WS-CREDIT-RESULT
END-IF.
El Jobol producido por un convertidor línea por línea:
Java
// Jobol: procedural COBOL logic mapped directly to Java
// No OOP, no encapsulation, static global state, COBOL naming conventions
public class CreditCheckProgram {
static String WS_CUST_ID;
static String DB_CUST_KEY;
static String WS_STATUS;
static String WS_CREDIT_RESULT;
static String WS_DECLINE_REASON;
static int CUST_CREDIT_SCORE;
static double CUST_OUTSTANDING_BALANCE;
static double CUST_CREDIT_LIMIT;
public static void PERFORM_CREDIT_CHECK() {
DB_CUST_KEY = WS_CUST_ID;
Customer customer = CustomerFile.read(DB_CUST_KEY);
if (customer == null) {
WS_STATUS = "NOT FOUND";
HANDLE_MISSING_CUSTOMER();
return;
}
CUST_CREDIT_SCORE = customer.getCreditScore();
CUST_OUTSTANDING_BALANCE = customer.getOutstandingBalance();
CUST_CREDIT_LIMIT = customer.getCreditLimit();
if (CUST_CREDIT_SCORE < 650) {
WS_CREDIT_RESULT = "DECLINED";
WS_DECLINE_REASON = "Score below threshold";
} else if (CUST_OUTSTANDING_BALANCE > CUST_CREDIT_LIMIT) {
WS_CREDIT_RESULT = "DECLINED";
WS_DECLINE_REASON = "Balance exceeds limit";
} else {
WS_CREDIT_RESULT = "APPROVED";
}
}
}
El Java idiomático que produce una modernización experta:
Java
// Idiomatic Java: domain model, encapsulation, value objects, Spring service
@Service
public class CreditCheckService {
private final CustomerRepository customerRepository;
public CreditCheckService(CustomerRepository customerRepository) {
this.customerRepository = customerRepository;
}
public CreditDecision check(CustomerId customerId) {
Customer customer = customerRepository.findById(customerId)
.orElseThrow(() -> new CustomerNotFoundException(customerId));
if (customer.creditScore() < CreditThresholds.MINIMUM_SCORE) {
return CreditDecision.declined("Score below threshold");
}
if (customer.outstandingBalance().compareTo(customer.creditLimit()) > 0) {
return CreditDecision.declined("Balance exceeds limit");
}
return CreditDecision.approved();
}
}
La versión Jobol consta de 32 líneas de estado global estático, estructura procedimental, con nombres propios de COBOL e imposible de probar unitariamente sin inicializar primero el estado de la clase global. La versión idiomática tiene 20 líneas, es inyectable, comprobable y legible de inmediato para cualquier desarrollador Java sin conocimientos de COBOL. Ambas implementan las mismas reglas de negocio. Solo una es mantenible por el equipo al que se suponía que la modernización debía habilitar.
Los seis patrones Jobol y por qué los convertidores los producen
Patrón 1: Estado estático global que reemplaza a WORKING-STORAGE
La sección WORKING-STORAGE de COBOL almacena variables a nivel de programa que persisten entre llamadas a PERFORM. Representa el estado mutable compartido del programa; cada párrafo lee y escribe las mismas variables WS. Los convertidores automáticos asignan esto directamente a campos estáticos de Java en una sola clase, preservando la semántica de estado compartido, pero violando todos los principios de encapsulación de Java.
¿Por qué los convertidores hacen esto? La asignación de WORKING-STORAGE a campos estáticos es una transformación mecánica uno a uno. El convertidor no comprende que el modelo de encapsulación de Java reemplaza el estado compartido a nivel de programa con variables de instancia, parámetros de método y valores de retorno. Convierte lo que ve sin comprender por qué existía la estructura original.
La consecuencia: el estado estático es imposible de probar de forma aislada, no es seguro para subprocesos sin una sincronización explícita y es estructuralmente idéntico al COBOL al que reemplazó. Cualquier desarrollador que necesite agregar concurrencia al sistema debe primero desenredar el estado estático, una tarea que se pospuso desde la conversión hasta cada mantenedor posterior.
La solución: Asignar las variables de WORKING-STORAGE a campos de instancia, parámetros de método o valores de retorno según su ámbito de uso. Las variables utilizadas solo dentro de un párrafo se convierten en variables locales. Las variables que se pasan entre párrafos se convierten en parámetros de método o valores de retorno. Las variables que realmente representan el estado de un objeto compartido se convierten en campos de instancia en una clase de dominio debidamente encapsulada.
Patrón 2: Cadenas PERFORM que se convierten en métodos estáticos largos
Los programas COBOL se organizan como cadenas PERFORM. El procedimiento principal ejecuta una secuencia de párrafos con nombre, cada uno de los cuales puede ejecutar otros. Los convertidores automáticos traducen cada párrafo en un método estático y cada PERFORM en una llamada a un método estático. El resultado es una única clase con docenas de métodos estáticos que se llaman entre sí mediante un grafo de llamadas que refleja la estructura PERFORM de COBOL.
Java
// Jobol: PERFORM chain mapped to static method calls
public class ProcessOrderProgram {
public static void main() {
validateOrder();
checkInventory();
calculateTotal();
applyDiscounts();
processPayment();
generateConfirmation();
updateInventory();
}
public static void validateOrder() { /* ... */ }
public static void checkInventory() { /* ... */ }
// ... 40 more static methods
}
¿Por qué los convertidores hacen esto? La asignación estructural del párrafo COBOL al método Java es directa. El convertidor identifica correctamente que PERFORM significa "llamar a esta unidad de código". Lo que no tiene en cuenta es que el modelo de objetos de Java organiza el código en torno a entidades de dominio, no en torno a pasos de procesamiento.
La solución: Identifique los conceptos de dominio integrados en la cadena PERFORM. validateOrder, calculateTotal, y applyDiscounts son operaciones en una Orden. processPayment es una operación en un contexto de pago. checkInventory y updateInventory son operaciones sobre el inventario. La refactorización distribuye los métodos estáticos a las clases de dominio a las que pertenecen, reemplazando una larga cadena de llamadas procedimentales con un método de orquestación corto que delega en los objetos de dominio apropiados.
Patrón 3: Tipos numéricos sin manejo preciso
Los campos decimales empaquetados (COMP-3) de COBOL tienen aritmética decimal exacta. Los convertidores que asignan COMP-3 a Java double or float Introducen errores sistemáticos de redondeo de punto flotante que se acumulan a lo largo de millones de transacciones financieras.
Java
// Jobol: COMP-3 mapped to double (wrong)
static double WS_TRANSACTION_AMOUNT; // Was PIC S9(9)V99 COMP-3
static double WS_RUNNING_TOTAL;
// After 10M additions of 0.1:
// Expected: 1,000,000.00
// Actual: 1,000,000.0000000002 (floating point accumulation)
Java
// Idiomatic Java: COMP-3 mapped to BigDecimal (correct)
private BigDecimal transactionAmount; // Preserves decimal precision
private BigDecimal runningTotal;
// After 10M additions of 0.1:
// Result: exactly 1,000,000.00
¿Por qué hacen esto los convertidores? double es el tipo numérico predeterminado de Java. Un convertidor que no entienda específicamente la semántica COMP-3 utilizará por defecto el tipo numérico más común de Java, que no es la opción adecuada para la aritmética decimal exacta.
La solución: Cada campo COMP-3 se corresponde con BigDecimal. Cada cálculo numérico que involucre valores COMP-3 utiliza BigDecimal aritmética. La scale() de BigDecimal coincide con las posiciones decimales implícitas en la cláusula PIC: PIC S9(9)V99 tiene 2 decimales implícitos, mapeándose a BigDecimal con escala 2.
Patrón 4: Cadenas EBCDIC y reliquias de matrices de bytes
COBOL procesa los datos de caracteres como cadenas rellenas de longitud fija en codificación EBCDIC. Los convertidores suelen generar código Java que conserva la semántica de cadena rellena de espacios de longitud fija incluso después de la conversión a UTF-8, lo que da como resultado un Java que maneja las cadenas como un programador de mainframe de la década de 1970.
Java
// Jobol: preserves COBOL fixed-length string handling
static char[] WS_CUSTOMER_NAME = new char[40]; // Was PIC X(40)
static char[] WS_CUSTOMER_ADDR = new char[80]; // Was PIC X(80)
// COBOL-style space-padding comparison
if (new String(WS_CUSTOMER_NAME).trim().equals("SMITH")) { ... }
// COBOL-style string concatenation with padding
System.arraycopy(name.toCharArray(), 0, WS_CUSTOMER_NAME, 0,
Math.min(name.length(), 40));
Java
// Idiomatic Java: proper String types and domain objects
private String customerName; // Trim on read, no padding
private String customerAddress;
// Clean comparison
if ("Smith".equalsIgnoreCase(customer.name())) { ... }
¿Por qué los convertidores hacen esto? La definición COBOL PIC X(40) tiene una longitud que el convertidor conserva como tamaño de matriz Java. La conversión de EBCDIC a UTF-8 puede manejarse, pero la semántica de longitud fija se mantiene innecesariamente.
La solución: Mapear PIC X(n) campos a String objetos, recorte durante la lectura y eliminación de restricciones de longitud fija. El único contexto donde importan las restricciones de longitud es en los límites de persistencia (longitudes de columna de la base de datos, posiciones del formato de archivo), no en el modelo de dominio.
Patrón 5: Manejo de errores mediante códigos de estado
COBOL comunica las condiciones de error mediante códigos de estado: FILE STATUS, SQLCODE, RETURN-CODE. Los convertidores conservan este patrón en Java, generando código que comprueba los códigos de estado enteros después de cada operación, en lugar de utilizar el modelo de excepciones de Java.
Java
// Jobol: COBOL status code pattern in Java
static int WS_FILE_STATUS;
static int WS_SQL_CODE;
CustomerFile.read(DB_CUST_KEY);
WS_FILE_STATUS = CustomerFile.getLastStatus();
if (WS_FILE_STATUS == 10) {
// Not found
} else if (WS_FILE_STATUS == 35) {
// File not available
} else if (WS_FILE_STATUS != 0) {
// Unknown error
}
Java
// Idiomatic Java: exceptions for error conditions
try {
Customer customer = customerRepository.findById(customerId)
.orElseThrow(() -> new CustomerNotFoundException(customerId));
// process customer
} catch (DatabaseUnavailableException e) {
log.error("Database unavailable during credit check", e);
throw new CreditCheckServiceException("Service temporarily unavailable", e);
}
¿Por qué los convertidores hacen esto? La comprobación del código de estado es el patrón de COBOL; convertirlo a un método Java que devuelve o establece un campo de estado es una transformación mecánica. Reconocer que las excepciones de Java son el equivalente idiomático requiere criterio arquitectónico, no una traducción mecánica.
La solución: Mapea las condiciones de error de COBOL a excepciones de Java. FILE STATUS 10 (registro no encontrado) se convierte en Optional.empty() o una excepción específica del dominio. Los errores SQLCODE se convierten en excepciones de base de datos tipadas. La estructura del código Java refleja la intención: try/catch para los modos de fallo previstos y excepciones no controladas para los inesperados.
Patrón 6: Estructura de clases monolítica
Los programas COBOL son monolíticos por diseño; un solo programa gestiona toda una función empresarial. Los convertidores crean una única clase Java por cada programa COBOL, lo que da como resultado clases Java que pueden tener miles de líneas y gestionar múltiples responsabilidades que el diseño orientado a objetos distribuiría entre varias clases especializadas.
¿Por qué los convertidores hacen esto? Un programa COBOL se asigna a una clase Java; la unidad de traducción del convertidor es el programa. La estructura interna del programa (sus párrafos, secciones, cadenas PERFORM) se convierte en la estructura interna de la clase sin descomposición en clases más pequeñas.
La solución: aplicar el principio de responsabilidad única a cada clase convertida. Una clase Jobol de 3,000 líneas que valida la entrada, lee de una base de datos, aplica reglas de negocio, formatea la salida y escribe en un archivo debería convertirse en: un validador de entrada, un repositorio, un servicio de dominio, un formateador y un escritor, cada uno comprobable de forma aislada y centrado en una única responsabilidad.
Requisito previo para el análisis estructural
Cada patrón de Jobol tiene una refactorización correspondiente. Sin embargo, la refactorización no puede ser aplicada por el convertidor; requiere comprender el funcionamiento del programa antes de decidir cómo reestructurarlo. Precisamente esa comprensión es la que proporciona el análisis estructural del código fuente COBOL antes de ejecutar cualquier herramienta de conversión.
En concreto, el análisis estructural produce los datos necesarios para tomar decisiones de conversión:
Inventario de lógica de negocio. Enumeración completa de las condiciones EVALUATE, las bifurcaciones IF y las fórmulas COMPUTE en cada programa; es decir, qué decide el programa, no solo cómo está estructurado. Este inventario es la especificación que debe cumplir el código Java refactorizado. Un programa con 847 condiciones EVALUATE requiere una estructura de clases de destino que admita 847 puntos de decisión; la salida del convertidor es el borrador inicial, no el producto final.
Grafo de dependencias. Qué programas llaman a qué otros programas. La cadena PERFORM, que abarca varios programas COBOL, se convierte en un grafo de llamadas de servicio en Java. El grafo de dependencias determina qué programas pueden refactorizarse de forma independiente (pocas dependencias entrantes) y cuáles deben refactorizarse como grupos coordinados (muchas dependencias compartidas a través de copybooks o datos compartidos).
Identificación de código muerto. Los párrafos y secciones que nunca se ejecutan en ninguna ruta de ejecución de producción son código muerto y no deberían aparecer en el código Java convertido. Los convertidores que incluyen código muerto aumentan el tamaño del código base convertido y el esfuerzo de refactorización posterior.
Recuperación del modelo de datos. Las entradas VSAM FD, las definiciones de tablas DB2 y las estructuras de copybook que definen los datos del programa. Cada estructura de datos COBOL se corresponde con uno o más objetos de dominio Java; la correspondencia viene determinada por el modelo de datos, no por los nombres de los campos en WORKING-STORAGE.
| Entrada de análisis | Decisión de conversión que impulsa |
|---|---|
| EVALUAR/SI número de sucursales | Descomposición de clases de dominio, cuántos casos de decisión maneja cada servicio |
| Recuento de dependencias entrantes | Secuencia de refactorización: primero los programas con menos dependencias. |
| Porcentaje de código muerto | Reducción del alcance, excluir el código inaccesible de la conversión |
| Campos COMP-3 | BigDecimal mapeo para cada campo numérico |
| longitudes de campo PIC X | Mapeo de tipos de cadena y restricciones de la capa de persistencia |
| EJECUTAR la estructura del gráfico | Descomposición de la capa de servicio, las cadenas PERFORM se convierten en llamadas de servicio de dominio |
| Uso del ESTADO DEL ARCHIVO | Diseño del modelo de excepciones: qué códigos de estado se corresponden con qué tipos de excepciones. |
En cambio, ¿qué debería producir la conversión?
La alternativa a Jobol es un proceso de conversión que utiliza COBOL como especificación en lugar de como plantilla. El programa COBOL conoce la lógica de negocio; el programa Java debe expresar esa lógica utilizando el lenguaje propio de Java.
El principio de conversión: Convierte la intención, no la sintaxis. COBOL
IF CUST-CREDIT-SCORE < 650 MOVE 'DECLINED' TO WS-CREDIT-RESULTdice “un cliente con una puntuación crediticia inferior a 650 es rechazado”. Java debería decir lo mismo en el lenguaje de Java:if (customer.creditScore() < CreditThresholds.MINIMUM_SCORE) return CreditDecision.declined(...). Noif (CUST_CREDIT_SCORE < 650) { WS_CREDIT_RESULT = "DECLINED"; }.
El flujo de trabajo de conversión práctico que produce Java idiomático:
Paso 1: Análisis estructural del código fuente COBOL, grafo de dependencias, inventario de lógica de negocio, recuperación del modelo de datos e identificación de código muerto. Este paso genera la especificación. No se realiza ninguna conversión hasta que este paso esté completo.
Paso 2: Diseño del modelo de dominio. Se identifican las entidades de negocio (Cliente, Cuenta, Transacción, Política) sobre las que operan los programas COBOL. Estas se convierten en clases de dominio Java. Las variables WORKING-STORAGE que representan el estado de la entidad se convierten en campos de instancia en la clase de dominio correspondiente.
Paso 3: Diseño de la capa de servicio. Se identifican las operaciones comerciales (verificación de crédito, procesamiento de pagos, cálculo de saldo) que implementan los párrafos y secciones de COBOL. Estas se convierten en métodos de servicio Java en la clase de servicio de dominio correspondiente. La cadena PERFORM, que secuencia los párrafos, se convierte en la lógica de orquestación del servicio.
Paso 4: Conversión automatizada como generador de borradores. Utilice la herramienta de conversión para producir un borrador Java inicial para cada programa, basándose en el modelo de dominio diseñado en los pasos 2 y 3. Revise el borrador comparándolo con el inventario de lógica de negocio para verificar que todas las ramas de decisión estén representadas.
Paso 5: Refactorización sistemática contra los patrones de Jobol, aplicar las seis correcciones de antipatrones: reemplazar el estado estático con el estado de instancia, distribuir los métodos estáticos a las clases de dominio, reemplazar los códigos de estado con excepciones, reemplazar double con BigDecimal, reemplazar matrices de longitud fija con Stringdescomponer las clases monolíticas en servicios de responsabilidad única.
Paso 6: Pruebas de equivalencia de comportamiento. Ejecute el código Java refactorizado contra el corpus de pruebas de caracterización generado a partir del análisis COBOL. Cada condición EVALUATE y cada rama IF deben tener al menos un caso de prueba que la evalúe y verifique su equivalencia con la salida COBOL.
Cómo SMART TS XL Evita el fraude en la fuente
El análisis estructural previo para la prevención de Jobol, el gráfico de dependencias, el inventario de lógica de negocio, la identificación de código muerto y la recuperación del modelo de datos, es exactamente lo que SMART TS XLEl análisis del código fuente COBOL se realiza antes de que se ejecute cualquier herramienta de conversión.
SMART TS XL, análisis de código estático Enumera cada condición EVALUATE, cada rama IF, cada fórmula COMPUTE y cada relación PERFORM en el código fuente COBOL, el inventario completo de lógica de negocio que el Java refactorizado debe representar fielmente. Cuando la herramienta de conversión genera Jobol, el inventario de lógica de negocio es la especificación con la que se valida la refactorización: cada condición del inventario debe aparecer en el Java, en la clase de dominio apropiada, expresada en el lenguaje Java.
El mapeo de dependencias de la aplicación genera el grafo PERFORM y el grafo de llamadas a programas que determinan la estructura de la capa de servicios de Java. Los programas que se llaman entre sí suelen pertenecer al mismo contexto delimitado. Los programas que son llamados por muchos otros constituyen los servicios compartidos, los cuales deben diseñarse cuidadosamente para dar soporte a todos sus consumidores en la arquitectura Java. El grafo de dependencias es la entrada para la etapa de diseño de la capa de servicios, convirtiendo la estructura de acoplamiento implícito de COBOL en la estructura de inyección de dependencias explícita de Java.
El análisis de modernización del código heredado identifica código obsoleto, párrafos y secciones que nunca se ejecutan en las rutas de producción, y que deben excluirse por completo del alcance de la conversión. Convertir código COBOL obsoleto a Jobol obsoleto y luego refactorizar este último supone un esfuerzo innecesario que se evita mediante la identificación del código obsoleto antes de que comience la conversión.
La capacidad de análisis de impacto valida la secuencia de refactorización: cuando la interfaz de un programa COBOL cambia durante la refactorización de Jobol a Java idiomático, el análisis de impacto identifica todos los demás programas cuyo equivalente en Java también debe actualizarse para mantener la coherencia con la interfaz modificada. La refactorización en orden de dependencias, comenzando por los programas con menos dependencias, minimiza la coordinación necesaria en cada paso.
Los convertidores traducen la sintaxis. La modernización transforma el significado.
Los convertidores automáticos de COBOL a Java son más rápidos que la reescritura manual. Además, están optimizados para un objetivo diferente: la correspondencia sintáctica, en lugar de la equivalencia semántica, en el lenguaje de destino. La correspondencia sintáctica se produce cuando el lenguaje de origen (COBOL) y el lenguaje de destino (Java) tienen modelos estructurales fundamentalmente diferentes.
Las organizaciones que evitan Jobol no son las que utilizan mejores convertidores. Son las que tratan el convertidor como un generador de borradores en lugar de un producto terminado, invierten en análisis estructurales antes de que comience la conversión, diseñan el modelo de dominio Java antes de que se ejecute el convertidor y aplican una refactorización sistemática a la salida del convertidor utilizando el inventario de lógica de negocio como especificación de validación.
El uso de Jobol es evitable. El costo de evitarlo radica en el análisis estructural y el modelado de dominio que deben preceder a toda conversión. El costo de no evitarlo es un sistema Java que requiere conocimientos de COBOL para su mantenimiento, lo que significa que el beneficio esperado de la modernización, la capacidad de mantener el sistema con desarrolladores Java, nunca se materializa.
Transforma la intención, no la sintaxis. La diferencia radica en todo el valor de la modernización.


