COBOL-till-Java-omvandlare producerar "Jobol"

Varför COBOL-till-Java-omvandlare producerar "Jobol" och hur man undviker det

Konverteringen slutfördes på tre månader. Java-applikationen kompilerades, klarade grundläggande röktester och förklarades klar för driftsättning. Sex månader in i produktionsfasen märkte plattformsteamet något oroande: ingen i Java-teamet kunde underhålla koden. Granskningarna tog dubbelt så lång tid som vanliga Java-granskningar. Buggrapporterna refererade till koncept, WORKING-STORAGE, PROCEDURE DIVISION-logik, överfulla decimalfältkonventioner, som Java-utvecklare utan stordatorerfarenhet inte kunde följa. Applikationen hade konverterats från COBOL till Java, men den resulterande Java tänkte, strukturerade och betedde sig som COBOL. Det var Jobol.

Jobol (ibland kallat JOBOL) är en portmanteau för Java-kod som behåller COBOLs procedurella semantik utan att omfamna Javas objektorienterade funktioner. Jason Bloomberg, grundare av analysföretaget Intellyx, definierade det direkt: Jobol är tekniskt giltig Java-kod som lämnar den ursprungliga programvaruarkitekturen på plats och bevarar COBOL-semantiken, vilket kräver att utvecklare behandlar Java som om det vore COBOL. Den kritiska konsekvensen följer omedelbart: ett företag som konverterar COBOL till Jobol har inte befriat sig från behovet av COBOL-expertis. Det har helt enkelt flyttat det expertiskravet till ett språk som inte har nativt stöd för de COBOL-mönster som koden innehåller. Det har skapat ett system som är svårare att underhålla än det ursprungliga COBOL och ger ingen av de förväntade Java-fördelarna.

Beroendediagram före domändesign

SMART TS XL mappar varje PERFORM- och CALL-beroende så att Java-tjänstlagret återspeglar den faktiska kopplingsstrukturen, inte COBOL-klassstrukturen.

TA REDA PÅ MER…

Hur Jobol egentligen ser ut

Innan orsakerna diagnostiseras måste mönstret vara synligt. Här är samma affärslogik, en kundkreditkontroll, i tre former: den ursprungliga COBOL, den Jobol som automatiserade konverterare vanligtvis producerar och den idiomatiska Java som en skicklig modernisering producerar.

Den ursprungliga COBOL:

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.

Jobol producerad av en linje-för-linje-omvandlare:

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";
        }
    }
}

Det idiomatiska Java som en skicklig modernisering producerar:

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();
    }
}

Jobol-versionen har 32 rader statiskt globalt tillstånd, är procedurellt strukturerad, har COBOL-namn och är omöjlig att enhetstesta utan att först initialisera det globala klasstillståndet. Den idiomatiska versionen har 20 rader, är injicerbar, testbar och omedelbart läsbar av alla Java-utvecklare utan COBOL-bakgrund. Båda implementerar samma affärsregler. Endast en kan underhållas av det team som moderniseringen skulle möjliggöra.

De sex Jobol-mönstren och varför konverterare producerar dem

Mönster 1: Globalt statiskt tillstånd som ersätter WORKING-STORAGE

COBOLs WORKING-STORAGE SECTION innehåller programnivåvariabler som behålls över PERFORM-anrop. Det är programmets delade, muterbara tillstånd, varje stycke läser och skriver samma WS-variabler. Automatiserade konverterare mappar detta direkt till statiska Java-fält i en enda klass, vilket bevarar semantiken för delat tillstånd samtidigt som de bryter mot alla Java-inkapslingsprinciper.

Varför konverterare gör detta: WORKING-STORAGE till statisk fältmappning är en mekanisk en-till-en-transformation. Konverteraren förstår inte att Javas inkapslingsmodell ersätter delat tillstånd på programnivå med instansvariabler, metodparametrar och returvärden. Den konverterar det den ser utan att förstå varför den ursprungliga strukturen existerade.

Konsekvensen: Statiskt tillstånd är otestbart isolerat, trådosäkert utan explicit synkronisering och strukturellt identiskt med den COBOL som det ersatte. Alla utvecklare som behöver lägga till samtidighet i systemet måste först reda ut det statiska tillståndet, ett arbete som skjutits upp från konverteringen till varje efterföljande underhållare.

Lösningen: Mappa WORKING-STORAGE-variabler till instansfält, metodparametrar eller returvärden baserat på deras användningsområde. Variabler som bara används inom ett stycke blir lokala variabler. Variabler som skickas mellan stycken blir metodparametrar eller returvärden. Variabler som verkligen representerar delat objekttillstånd blir instansfält på en korrekt inkapslad domänklass.

Mönster 2: UTFÖR Kedjor som blir långa statiska metoder

COBOL-program är organiserade som PERFORM-kedjor, där huvudproceduren PERFORM utför en sekvens av namngivna stycken, som vart och ett kan PERFORM-a andra. Automatiserade konverterare översätter varje stycke till en statisk metod och varje PERFORM till ett statiskt metodanrop. Resultatet är en enda klass med dussintals statiska metoder som anropar varandra genom en anropsgraf som speglar COBOL PERFORM-strukturen.

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
}

Varför konverterare gör detta: Den strukturella mappningen från COBOL-stycke till Java-metoden är direkt. Konverteraren identifierar korrekt att PERFORM betyder "anropa denna kodenhet". Vad den missar är att Javas objektmodell organiserar kod kring domänentiteter, inte kring bearbetningssteg.

Fixen: Identifiera de domänbegrepp som är inbäddade i PERFORM-kedjan. validateOrder, calculateTotaloch applyDiscounts är operationer på en order. processPayment är en operation i ett betalningssammanhang. checkInventory och updateInventory är operationer på Inventory. Refaktoreringen distribuerar de statiska metoderna till de domänklasser de tillhör, och ersätter en lång proceduranropskedja med en kort orkestreringsmetod som delegerar till lämpliga domänobjekt.

Mönster 3: Numeriska typer utan precisionshantering

COBOLs packade decimalfält (COMP-3) har exakt decimalaritmetik. Konverterare som mappar COMP-3 till Java double or float introducera systematiska flyttalsfel som ackumuleras över miljontals finansiella transaktioner.

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

Varför konverterare gör detta: double är Javas standardnumeriska typ. En konverterare som inte specifikt förstår COMP-3-semantik kommer som standard att använda den vanligaste numeriska Java-typen, vilket är fel val för exakt decimalaritmetik.

Fixen: Varje COMP-3-fält mappas till BigDecimalVarje numerisk beräkning som involverar COMP-3-värden använder BigDecimal aritmetik. Den scale() av BigDecimal matchar de implicita decimalerna i PIC-klausulen: PIC S9(9)V99 har 2 implicita decimaler, mappning till BigDecimal med skala 2.

Mönster 4: EBCDIC-strängar och byte-array-rester

COBOL bearbetar teckendata som utfyllda strängar med fast längd i EBCDIC-kodning. Konverterare producerar ofta Java-kod som bevarar den utfyllda strängsemantiken med fast längd även efter konvertering till UTF-8, vilket resulterar i Java som hanterar strängar som en stordatorprogrammerare på 1970-talet.

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())) { ... }

Varför konverterare gör detta: COBOL PIC X(40)-definitionen har en längd som konverteraren bevarar som en Java-arraystorlek. Konverteringen från EBCDIC till UTF-8 kan hanteras, men semantiken med fast längd överförs i onödan.

Fixen: Karta PIC X(n) fält till String objekt, trimning vid läsning och borttagning av begränsningar med fast längd. Det enda sammanhanget där längdbegränsningar spelar roll är vid persistensgränser (databas-kolumnlängder, filformatpositioner), inte i domänmodellen.

Mönster 5: Felhantering via statuskoder

COBOL kommunicerar feltillstånd genom statuskoder: FILE STATUS, SQLCODE, RETURN-CODE. Konverterare bevarar detta mönster i Java och producerar kod som kontrollerar heltalsstatuskoder efter varje operation istället för att använda Javas undantagsmodell.

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);
}

Varför konverterare gör detta: Statuskodkontroll är COBOL-mönstret; att konvertera det till en Java-metod som returnerar eller anger ett statusfält är en mekanisk transformation. Att inse att Java-undantag är den idiomatiska motsvarigheten kräver arkitektonisk bedömning, inte mekanisk översättning.

Fixen: Mappa COBOL-felvillkor till Java-undantag. FILSTATUS 10 (posten hittades inte) blir Optional.empty() eller ett domänspecifikt undantag. SQLCODE-fel blir typade databasundantag. Java-kodstrukturen återspeglar avsikt, try/catch för förväntade fellägen, okontrollerade undantag för oväntade.

Mönster 6: Monolitisk klassstruktur

COBOL-program är monolitiska till sin design, ett enda program hanterar en hel affärsfunktion. Konverterare skapar en enda Java-klass per COBOL-program, vilket producerar Java-klasser som kan vara tusentals rader långa och hantera flera ansvarsområden som objektorienterad design skulle fördela över flera fokuserade klasser.

Varför konverterare gör detta: Ett COBOL-program mappas till en Java-klass, konverterarens översättningsenhet är programmet. Programmets interna struktur (dess stycken, avsnitt, PERFORM-kedjor) blir klassens interna struktur utan uppdelning i mindre klasser.

Lösningen: Tillämpa principen om ett enda ansvar på varje konverterad klass. En Jobol-klass på 3 000 rader som validerar indata, läser från en databas, tillämpar affärsregler, formaterar utdata och skriver till en fil bör bli: en indatavaliderare, ett arkiv, en domäntjänst, en formaterare och en skribent, var och en testbar isolerat, var och en fokuserad på ett ansvar.

Förutsättningen för strukturell analys

Varje Jobol-mönster har en motsvarande omstrukturering. Men omstruktureringen kan inte tillämpas av konverteraren, det kräver förståelse för vad programmet gör innan man bestämmer sig för hur det ska omstruktureras. Den förståelsen är precis vad strukturell analys av källkoden COBOL ger innan något konverteringsverktyg körs.

Mer specifikt producerar strukturell analys de indata som konverteringsbeslut kräver:

Affärslogikinventering. Den fullständiga uppräkningen av EVALUATE-villkor, IF-grenar och COMPUTE-formler i varje program, vad programmet bestämmer, inte bara hur det är strukturerat. Denna inventering är den specifikation som den omstrukturerade Java-koden måste matcha. Ett program med 847 EVALUATE-villkor kräver en målklassstruktur som rymmer 847 beslutspunkter; konverterarens utdata är startutkastet, inte den färdiga produkten.

Beroendegraf. Vilka program anropar vilka andra program. PERFORM-kedjan som sträcker sig över flera COBOL-program blir en serviceanropsgraf i Java. Beroendegrafen avgör vilka program som kan omstruktureras oberoende (få inkommande beroenden) och vilka som måste omstruktureras som koordinerade grupper (många delade beroenden genom kopieböcker eller delade data).

Identifiering av död kod. Stycken och avsnitt som aldrig UTFÖRS från någon produktionsexekveringsväg är död kod, de ska inte alls visas i den konverterade Java-filen. Konverterare som inkluderar död kod blåser upp den konverterade kodbasen och den efterföljande omstruktureringen.

Återställning av datamodell. VSAM FD-posterna, DB2-tabelldefinitionerna och copybook-strukturerna som definierar programmets data. Varje COBOL-datastruktur mappas till ett eller flera Java-domänobjekt; mappningen bestäms av datamodellen, inte av fältnamnen i WORKING-STORAGE.

AnalysinmatningKonverteringsbeslutet det driver
UTVÄRDERA/OM-grenantalDomänklassnedbrytning, hur många beslutsfall varje tjänst hanterar
Antal inkommande beroendenRefaktoreringssekvens, färsta beroendeprogram först
Procentuell dödkodOmfattningsminskning, exkludera oåtkomlig kod från konvertering
COMP-3-fältBigDecimal mappning för varje numeriskt fält
PIC X-fältlängderSträngtypmappning och begränsningar för persistenslager
PERFORM grafstrukturNedbrytning av tjänsteskikt, PERFORM-kedjor blir domäntjänstanrop
FILSTATUS användningUndantagsmodelldesign, vilka statuskoder mappas till vilka undantagstyper

Vad konverteringen borde producera istället

Alternativet till Jobol är en konverteringsprocess som använder COBOL som en specifikation snarare än som en mall. COBOL-programmet vet vad affärslogiken är, Java-programmet bör uttrycka den logiken i Javas idiom.

Konverteringsprincipen: Konvertera avsikten, inte syntaxen. COBOL IF CUST-CREDIT-SCORE < 650 MOVE 'DECLINED' TO WS-CREDIT-RESULT säger "en kund med en kreditvärdighet under 650 avvisas." Java borde säga samma sak i Javas språk: if (customer.creditScore() < CreditThresholds.MINIMUM_SCORE) return CreditDecision.declined(...). Inte if (CUST_CREDIT_SCORE < 650) { WS_CREDIT_RESULT = "DECLINED"; }.

Det praktiska konverteringsarbetsflödet som producerar idiomatisk Java:

Steg 1: Strukturell analys av COBOL-källan, beroendegraf, inventering av affärslogik, återställning av datamodell, identifiering av död kod. Detta steg producerar specifikationen. Ingenting konverteras förrän detta steg är slutfört.

Steg 2: Domänmodelldesign, identifiera de affärsentiteter (kund, konto, transaktion, policy) som COBOL-programmen arbetar på. Dessa blir Java-domänklasser. WORKING-STORAGE-variablerna som representerar entitetstillstånd blir instansfält på lämplig domänklass.

Steg 3: Design av tjänsteskikt, identifiera de affärsverksamheter (kreditkontroll, betalningsbehandling, saldoberäkning) som COBOL-styckena och avsnitten implementerar. Dessa blir Java-tjänstmetoder på lämplig domäntjänstklass. PERFORM-kedjan som sekvenserar stycken blir tjänstens orkestreringslogik.

Steg 4: Automatiserad konvertering som en utkastgenerator, använd konverteringsverktyget för att skapa ett utgångsutkast i Java för varje program, baserat på domänmodellen som utformats i steg 2 och 3. Granska utkastet mot affärslogikinventeringen för att verifiera att varje beslutsgren är representerad.

Steg 5: Systematisk refaktorering mot Jobol-mönstren, tillämpa de sex anti-mönsterfixarna: ersätt statiskt tillstånd med instanstillstånd, distribuera statiska metoder till domänklasser, ersätt statuskoder med undantag, ersätt double med BigDecimal, ersätt arrayer med fast längd med String, dela upp monolitiska klasser till tjänster med ett enda ansvar.

Steg 6: Beteendeekvivalenstestning, kör den refaktorerade Java-filen mot karakteriseringstestkorpusen som genererats från COBOL-analysen. Varje EVALUATE-villkor och IF-gren bör ha minst ett testfall som utövar det och verifierar ekvivalens med COBOL-utdata.

Hur SMART TS XL Förhindrar Jobol vid källan

Den strukturella analys som krävs för Jobol-förebyggande, beroendegrafen, affärslogikinventering, identifiering av död kod och återställning av datamodeller, är precis vad SMART TS XLs analys av COBOL-källkoden tillhandahåller innan något konverteringsverktyg körs.

SMART TS XLÄr statisk kodanalys räknar upp varje EVALUATE-villkor, varje IF-gren, varje COMPUTE-formel och varje PERFORM-relation i COBOL-källkoden, det kompletta affärslogikinventariet som den omfaktorerade Java-filen måste representera korrekt. När konverteringsverktyget producerar Jobol är affärslogikinventariet den specifikation mot vilken omfaktoreringen valideras: varje villkor i inventariet måste visas i Java, i lämplig domänklass, uttryckt i Javas idiom.

Mappningen av applikationsberoenden producerar PERFORM-grafen och programanropsgrafen som bestämmer Java-tjänsteskiktets struktur. Program som anropar varandra hör ofta hemma i samma begränsade kontext. Program som anropas av många andra är de delade tjänster som måste utformas noggrant för att stödja alla sina konsumenter i Java-arkitekturen. Beroendegrafen är indata till tjänsteskiktets designsteg, där den omvandlar COBOLs implicita kopplingsstruktur till Javas explicita beroendeinjektionsstruktur.

Analysen av den äldre moderniseringen identifierar död kod, stycken och avsnitt som aldrig nåtts från produktionsexekveringsvägarna, och som helt bör exkluderas från konverteringsomfånget. Att konvertera död COBOL till död Jobol och sedan omstrukturera död Jobol slösar bort ansträngning som identifieringssteget för död kod eliminerar innan konverteringen startar.

Effektanalysfunktionen validerar refaktoreringssekvensen: när ett COBOL-programs gränssnitt ändras under refaktoreringen från Jobol till idiomatisk Java, identifierar effektanalysen alla andra program vars Java-motsvarighet också måste uppdateras för att förbli konsekvent med det ändrade gränssnittet. Refaktorering i beroendeordning, med färsta beroenden först, minimerar den samordning som krävs i varje steg.

Konverterare översätter syntax. Modernisering omvandlar betydelse.

Automatiserade COBOL-till-Java-omvandlare är snabbare än mänsklig omskrivning. De är också optimerade för ett annat mål: syntaktisk korrespondens snarare än semantisk ekvivalens i målspråkets idiom. Jobol är vad syntaktisk korrespondens producerar när källspråket (COBOL) och målspråket (Java) har fundamentalt olika strukturella modeller.

De organisationer som undviker Jobol är inte de som använder bättre konverterare. Det är de som behandlar konverteraren som en utkastgenerator snarare än en färdig produkt, investerar i strukturell analys innan konverteringen startar, designar Java-domänmodellen innan konverteraren körs och tillämpar systematisk refaktorering på konverterarens utdata med affärslogikinventeringen som valideringsspecifikation.

Jobol är undvikbart. Kostnaden för att undvika det är den strukturella analys och domänmodelleringsarbete som bör föregå varje konvertering. Kostnaden för att inte undvika det är ett Java-system som kräver COBOL-expertis för att underhållas, vilket innebär att den förväntade nyttan av moderniseringen, möjligheten att underhålla systemet med Java-utvecklare, aldrig realiseras.

Konvertera avsikten. Inte syntaxen. Skillnaden är moderniseringens totala värde.