Calidad del código generado por modelos de IA para COBOL
Esto es a lo que me refería cuando hablaba de la calidad del código.
Los ejemplos siguientes muestran el resultado del código generado por el modelo antes y después de corregir el prompt.
El problema es que, en lugar de producir código de calidad, el modelo genera inicialmente un código sintácticamente correcto, pero con una estructura extremadamente amateur.
Si el modelo hubiera tenido acceso durante su entrenamiento a una gran cantidad de código COBOL bien escrito —cientos de miles de ejemplos, y no solamente uno o dos—, el resultado probablemente habría sido incluso mejor que el que finalmente obtuvimos.
Prompt utilizado
Código:
Implement a menu selection system on the form 'forms/Common/checkboxes-form.cfrm'.
1. UI Layout:
- Add CheckBox controls for a McDonald's style menu consisting of:
- 4 Hamburger options
- 2 Beverage options
- 4 Dessert options
- Each CheckBox 'Caption' must include the item name and its price.
- Add a DataGrid to serve as the receipt, displaying the selected items, their individual costs, and the total price.
2. Data Requirements:
- Generate random, realistic names for the food and beverages during the design.
- Assign random prices for each item between 2,99 and 9,99 as global data items.
3. Functional Logic:
- Use the 'onCheckedChanged' event for each CheckBox to trigger a total cost update.
- When an item is checked or unchecked, the DataGrid must update to reflect the current list of selected items and the recalculated total price.
4. Formatting:
- Use COBOL edited data-items to format all currency values.
- Ensure the decimal separator is a comma (e.g., 5,99).
Estructura de datos generada inicialmente
Obsérvese que este código no es adecuado para una solución basada en programas anidados (
nested programs):
Código:
*> Preços dos Hambúrgueres
01 PRICE-HAMB-1 PIC 99V99 COMP-5 VALUE 7,49.
01 PRICE-HAMB-1-ED PIC ZZZ,99.
01 PRICE-HAMB-2 PIC 99V99 COMP-5 VALUE 8,90.
01 PRICE-HAMB-2-ED PIC ZZZ,99.
01 PRICE-HAMB-3 PIC 99V99 COMP-5 VALUE 6,50.
01 PRICE-HAMB-3-ED PIC ZZZ,99.
01 PRICE-HAMB-4 PIC 99V99 COMP-5 VALUE 9,99.
01 PRICE-HAMB-4-ED PIC ZZZ,99.
*> Preços das Bebidas
01 PRICE-BEV-1 PIC 99V99 COMP-5 VALUE 3,50.
01 PRICE-BEV-1-ED PIC ZZZ,99.
01 PRICE-BEV-2 PIC 99V99 COMP-5 VALUE 4,25.
01 PRICE-BEV-2-ED PIC ZZZ,99.
*> Preços das Sobremesas
01 PRICE-DESS-1 PIC 99V99 COMP-5 VALUE 5,99.
01 PRICE-DESS-1-ED PIC ZZZ,99.
01 PRICE-DESS-2 PIC 99V99 COMP-5 VALUE 3,90.
01 PRICE-DESS-2-ED PIC ZZZ,99.
01 PRICE-DESS-3 PIC 99V99 COMP-5 VALUE 7,20.
01 PRICE-DESS-3-ED PIC ZZZ,99.
01 PRICE-DESS-4 PIC 99V99 COMP-5 VALUE 4,80.
01 PRICE-DESS-4-ED PIC ZZZ,99.
*> Variáveis de Cálculo e Exibição do Total
01 WS-TOTAL-AMOUNT PIC 999V99 COMP-5 VALUE 0.
01 WS-TOTAL-EDITED PIC ZZZ,99.
Código generado después de mejorar el contexto del modelo
Después de implementar un RAG, añadir
skills, revisar el prompt interno e incorporar un documento con buenas prácticas de desarrollo COBOL —todavía en versión preliminar—, el modelo generó la siguiente estructura:
Código:
01 WS-MENU-DATA GLOBAL.
05 WS-HAMBURGERS-GROUP.
10 WS-HAMBURGERS-TABLE OCCURS 4 TIMES.
15 WS-HAMB-DESC PIC X(40).
15 WS-HAMB-PRICE PIC 9(2)V99.
15 WS-HAMB-SELECTED PIC X(1).
05 WS-BEVERAGES-GROUP GLOBAL.
10 WS-BEVERAGES-TABLE OCCURS 2 TIMES.
15 WS-BEV-DESC PIC X(40).
15 WS-BEV-PRICE PIC 9(2)V99.
15 WS-BEV-SELECTED PIC X(1).
05 WS-DESSERTS-GROUP GLOBAL.
10 WS-DESSERTS-TABLE OCCURS 4 TIMES.
15 WS-SOBR-DESC PIC X(40).
15 WS-SOBR-PRICE PIC 9(2)V99.
15 WS-SOBR-SELECTED PIC X(1).
01 WS-CALCULATION-VARS GLOBAL.
05 WS-TOTAL-AMOUNT PIC 9(4)V99 VALUE 0.
05 WS-TOTAL-DISPLAY PIC Z(3)9,99.
05 WS-INDEX PIC 9(2) VALUE 0.
01 WS-FORM-STATE GLOBAL.
05 WS-RECEIPT-ITEM PIC X(40).
05 WS-RECEIPT-PRICE PIC 9(2)V99.
Código procedural generado por el modelo
Código:
ENVIRONMENT DIVISION.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 WS-SOMA-TEMP PIC 9(4)V99 VALUE 0.
PROCEDURE DIVISION.
*> Limpa o total e o DataGrid antes de recalcular
MOVE 0 TO WS-TOTAL-AMOUNT.
dgReceipt::ClearRows().
*> Processa Hambúrgueres
PERFORM VARYING WS-INDEX FROM 1 BY 1
UNTIL WS-INDEX > 4
IF WS-HAMB-SELECTED(WS-INDEX) = '1'
MOVE WS-HAMB-DESC(WS-INDEX)
TO WS-RECEIPT-ITEM
MOVE WS-HAMB-PRICE(WS-INDEX)
TO WS-RECEIPT-PRICE
dgReceipt::AddRow(
WS-RECEIPT-ITEM,
WS-RECEIPT-PRICE
)
ADD WS-HAMB-PRICE(WS-INDEX)
TO WS-TOTAL-AMOUNT
END-IF
END-PERFORM.
*> Processa Bebidas
PERFORM VARYING WS-INDEX FROM 1 BY 1
UNTIL WS-INDEX > 2
IF WS-BEV-SELECTED(WS-INDEX) = '1'
MOVE WS-BEV-DESC(WS-INDEX)
TO WS-RECEIPT-ITEM
MOVE WS-BEV-PRICE(WS-INDEX)
TO WS-RECEIPT-PRICE
dgReceipt::AddRow(
WS-RECEIPT-ITEM,
WS-RECEIPT-PRICE
)
ADD WS-BEV-PRICE(WS-INDEX)
TO WS-TOTAL-AMOUNT
END-IF
END-PERFORM.
*> Processa Sobremesas
PERFORM VARYING WS-INDEX FROM 1 BY 1
UNTIL WS-INDEX > 4
IF WS-SOBR-SELECTED(WS-INDEX) = '1'
MOVE WS-SOBR-DESC(WS-INDEX)
TO WS-RECEIPT-ITEM
MOVE WS-SOBR-PRICE(WS-INDEX)
TO WS-RECEIPT-PRICE
dgReceipt::AddRow(
WS-RECEIPT-ITEM,
WS-RECEIPT-PRICE
)
ADD WS-SOBR-PRICE(WS-INDEX)
TO WS-TOTAL-AMOUNT
END-IF
END-PERFORM.
*> Atualiza o Label de Total
MOVE WS-TOTAL-AMOUNT
TO WS-TOTAL-DISPLAY.
STRING
"Total: R$ "
WS-TOTAL-DISPLAY
INTO lblTotal::Caption.
CONTINUE.
El resultado mejoró considerablemente, pero todavía contenía un error bastante elemental.
El modelo no añadió la cláusula
a los registros de nivel
definidos en la
Código:
WORKING-STORAGE SECTION
del formulario.
Sin embargo, el código procedural asociado al formulario fue generado correctamente y asumía que esos elementos de datos eran globales.
Corregí el error manualmente y revisé las instrucciones proporcionadas al modelo para evitar que vuelva a cometerlo.
Este es precisamente el tipo de conocimiento que hay que enseñar explícitamente al modelo para que produzca código correctamente, ya que actualmente no dispone de suficientes referencias de código COBOL bien escrito para aprender por sí solo estos patrones.
Algunos modelos —por ejemplo, los de Anthropic— probablemente producirían resultados mejores, aunque tampoco serían perfectos.
Todavía existe mucho trabajo para desarrolladores que conozcan profundamente los lenguajes de programación. Sin embargo, creo que en pocos años este escenario cambiará de forma considerable.
[hr]
Prompt: buenas prácticas para la generación de código COBOL
Eres un desarrollador experto en COBOL, responsable de generar código claro, mantenible, compacto y estructuralmente coherente.
Aplica las siguientes convenciones cuando crees, modifiques, refactorices o revises código fuente COBOL. Estas reglas deben considerarse estándares de codificación del proyecto.
Todos los ejemplos incluidos en este documento son únicamente ilustrativos. Demuestran la estructura y la intención, pero no deben copiarse literalmente en programas no relacionados.
Adapta los identificadores, valores, tamaños de tablas, secciones y flujo de ejecución a los requisitos reales.
1. Organizar los datos bajo registros significativos de nivel 01
Por qué
Un registro significativo de nivel
establece un límite claro de propiedad para los datos relacionados, mejora la legibilidad, facilita el mantenimiento y permite ampliar la estructura sin crear múltiples declaraciones de nivel superior.
No utilizar un elemento de nivel
únicamente para declarar un campo elemental con una cláusula
.
En lugar de hacer esto:
Código:
01 WS-ITEM-PRICE PIC 99V99 COMP.
01 WS-ITEM-NAME PIC X(40).
Hacer esto:
Código:
01 WS-APPLICATION-DATA GLOBAL.
05 WS-ITEM-PRICE PIC 99V99 COMP.
05 WS-ITEM-NAME PIC X(40).
El nivel
debe representar un registro lógico, un contexto, el estado de un módulo, una entidad de negocio o un área de datos de la aplicación.
Evitar colisiones entre nombres de elementos de datos. Cada nombre definido con el mismo número de nivel dentro de un registro debe ser único.
2. Declarar todos los registros de nivel 01 como GLOBAL
Por qué
Declarar el registro raíz como
proporciona un modelo coherente de visibilidad para los programas anidados y evita la necesidad de aplicar esta cláusula a cada elemento subordinado.
Por convención del proyecto, todos los registros de aplicación de nivel
deben utilizar la cláusula
:
Código:
01 MC-APPLICATION-DATA GLOBAL.
No añadir
a los elementos subordinados. Declararlo en el nivel
y organizar debajo de este todos los campos relacionados.
3. Utilizar comentarios para identificar grupos lógicos
Por qué
La agrupación lógica facilita la navegación por secciones grandes de
, tanto para desarrolladores como para modelos de IA.
Utilizar comentarios breves para dividir los registros en grupos funcionales o de negocio:
Código:
01 MC-MENU GLOBAL.
*> HAMBURGUESAS
05 HAMBURGER-DATA.
10 HAMBURGER-PRICE
PIC 99V99 COMP
OCCURS 4 TIMES.
*> BEBIDAS
05 BEVERAGE-DATA.
10 BEVERAGE-PRICE
PIC 99V99 COMP
OCCURS 2 TIMES.
Los comentarios deben explicar la estructura, la intención, las restricciones o algún comportamiento que no resulte evidente.
No añadir comentarios que simplemente repitan lo que ya expresa el código.
4. Preferir tablas en lugar de elementos repetidos
Por qué
Las tablas reducen la verbosidad, simplifican las iteraciones, minimizan errores de copia y permiten añadir nuevos elementos modificando únicamente el tamaño y la inicialización de la tabla.
Cuando varios elementos tengan la misma estructura y finalidad, utilizar una tabla con
.
En lugar de hacer esto:
Código:
10 ITEM-PRICE-1 PIC 99V99 COMP.
10 ITEM-PRICE-2 PIC 99V99 COMP.
10 ITEM-PRICE-3 PIC 99V99 COMP.
Hacer esto:
Código:
10 ITEM-PRICE
PIC 99V99 COMP
OCCURS 3 TIMES.
Utilizar campos con nombres separados únicamente cuando representen significados o comportamientos realmente diferentes.
5. Utilizar REDEFINES solamente para vistas alternativas útiles
Por qué
es una función poderosa, pero su uso excesivo reduce la legibilidad.
Utilizarlo únicamente cuando se necesiten dos vistas semánticamente diferentes del mismo almacenamiento, por ejemplo:
- un registro inicializado individualmente y una vista indexada;
- un registro bruto y otro interpretado;
- varios formatos de registro que compartan el mismo almacenamiento.
No introducir
únicamente para hacer que el código parezca más sofisticado.
6. Reutilizar elementos numéricos editados
Por qué
Los campos editados son elementos temporales utilizados para presentación, no datos de negocio.
Su reutilización reduce el tamaño de
y evita duplicaciones innecesarias.
No crear un elemento editado para cada valor numérico.
Cuando los valores se formateen de forma secuencial, reutilizar un elemento compatible:
Código:
05 FORMATTING-DATA.
10 EDITED-CURRENCY
PIC Z9,99.
Mover el valor numérico de origen al campo editado inmediatamente antes de asignar el resultado a un control, informe o salida visual.
Relacionar el PICTURE editado con el campo de origen
El campo numérico editado debe corresponder al tamaño, signo, precisión decimal y requisitos de edición del elemento numérico que formatea.
No utilizar el mismo
para campos con tamaños, signos o precisiones incompatibles.
Ejemplos:
Código:
Origen: PIC 99V99
Editado: PIC Z9,99
Origen: PIC S9(09)V99
Editado: PIC ZZZ.ZZZ.ZZ9,99-
Origen: PIC 9(05)
Editado: PIC ZZZZ9
Seleccionar el formato exacto considerando:
- cantidad de dígitos enteros;
- cantidad de dígitos decimales;
- presencia de signo;
- posición requerida del signo;
- separadores de miles y decimales;
- supresión o conservación de ceros iniciales.
Cuando varios campos compartan la misma estructura numérica, reutilizar un único campo editado compatible.
Cuando sus estructuras sean diferentes, crear un campo reutilizable para cada clase de formato necesaria, no uno por cada valor de negocio.
7. Seguir la convención del proyecto para importes monetarios
Por qué
Una convención uniforme mejora la consistencia visual y evita discrepancias entre diferentes partes de la aplicación.
Al formatear valores monetarios, utilizar siempre un
obligatorio en la posición de las unidades, inmediatamente antes del separador decimal.
Utilizar
solamente para dígitos iniciales opcionales.
Hacer esto:
En lugar de esto:
El
obligatorio garantiza que siempre exista un dígito visible en la posición de las unidades.
Antes de generar constantes numéricas o cláusulas
, determinar si el programa contenedor define:
Código:
DECIMAL-POINT IS COMMA
en su párrafo
.
Si está definido, utilizar la coma como separador decimal:
Si no está definido, utilizar el punto:
Continuará en la siguiente publicación.