Hitta alla anropare av en föråldrad funktion

Så här hittar du alla anropare av en föråldrad funktion innan du tar bort den

Att ta bort en föråldrad funktion från en kodbas är konceptuellt sett en av de enklaste sakerna en utvecklare kan göra. Ta bort definitionen, bekräfta att ingenting använder den, och genomför commit. I praktiken, för alla funktioner som har funnits tillräckligt länge för att vara föråldrade, är det steget "bekräfta att ingenting använder den" där processen bryter samman. Funktionen kan anropas från kod som skrevs för flera år sedan av någon som inte längre är i teamet, i ett arkiv som får ändringar sällan, av ett språk eller ramverk som det nuvarande teamet inte äger. Den kan anropas indirekt genom en wrapper, genom reflektion eller genom en runtime-dispatchmekanism som inte visas i någon statisk anropsgraf. Den kan refereras till i genererad kod, i test scaffolding eller i en konfigurationsfil som utlöser den med namn. Utvecklaren som markerar den som föråldrad och utvecklaren som så småningom tar bort den kanske inte har något sätt att veta något av detta utan ett verktyg som kan bygga en komplett, arkivövergripande anropsinventering.

Hitta alla som ringer innan du tar bort något

SMART TS XL bygger en samtalsgraf över flera språk som identifierar alla anropare av vilken funktion som helst innan du gör en ändring.

Klicka här

Kostnaden för att göra detta fel är omedelbar och konkret. En funktion som tas bort med ofullständig identifiering av anropare orsakar körtidsfel i de system som fortfarande är beroende av den. I en monolit med en enda distribution är felytan begränsad. I ett distribuerat system med flera tjänster, som var och en distribueras oberoende, kaskadförökar felen: tjänsten som tillhandahöll funktionen uppdateras, konsumenterna gör det inte, och fel uppstår vid körning i produktion i system som kan ägas av olika team. I en stordatormiljö där COBOL-program anropar delade verktygsparagrafer vid namn, kanske felet inte manifesteras förrän ett specifikt batchjobb körs, vilket kan vara veckovis eller månadsvis, vilket gör den saknade referensen osynlig genom normala testcykler. Som undersökts i det bredare sammanhanget av hantering av föråldrad kod ökar riskerna över tid: föråldrad kod som tas bort ofullständigt är farligare än föråldrad kod som lämnas kvar, eftersom borttagningen skapar en illusion av fullständighet medan de återstående anroparna fortsätter att köras mot en definition som inte längre finns.

Den här artikeln är en praktisk guide till identifiering av anropare innan funktioner tas bort: vad en komplett anroparinventering kräver, varför de verktyg som utvecklare först använder är strukturellt otillräckliga, hur olika typer av anropsrelationer kräver olika analysmetoder och hur genuin systemövergripande anroparuppräkning ser ut i kodbaser på företagsnivå som blandar språk, plattformar och databaser.

Varför det är svårare att upptäcka vem som ringer än det ser ut

Ytversionen av anroparidentifiering är bekant för alla utvecklare: högerklicka på ett funktionsnamn i en IDE, välj "Hitta alla referenser" eller "Visa anropshierarki" och granska resultaten. Detta fungerar tillförlitligt inom ramen för ett enda projekt som laddas i en enda IDE-instans. I det ögonblick som kodbasen sträcker sig bortom detta omfång blir resultaten ofullständiga på sätt som inte syns i utdata. IDE:n indikerar inte vilka anropare den inte hittade eftersom den inte indexerade de arkiv som innehåller dem. Utvecklaren ser en resultatuppsättning som verkar komplett och fortsätter därefter.

Detta är det strukturella problemet med anroparidentifiering i stor skala: de verktyg som utvecklare använder mest flytande begränsas av sitt indexeringsomfång, och i stora, distribuerade, flerspråkiga system täcker det omfånget en bråkdel av de platser där en given funktion kan anropas. Utvecklarens förtroende för resultatens fullständighet är omvänt proportionellt mot sökningens faktiska fullständighet. I en liten, enspråkig kodbas är IDE-anropshierarkin genuint tillförlitlig. I ett företagssystem som spänner över flera arkiv, språk och distributionsmiljöer är den systematiskt vilseledande. Som analyserats i samband med kodentropi och refaktoreringsrisk kan äldre moduler vara beroende av föråldrade gränssnitt medan nyare tjänster fortfarande anropar rutiner som ursprungligen utformades för tidigare miljöer, och dessa systemövergripande anropsrelationer är just de som IDE-begränsad sökning inte kan se.

För att förstå de specifika orsakerna till att uppringningsidentifiering misslyckas krävs det att man undersöker varje större samtalstyp: direkta samtal, indirekta samtal, anrop mellan språk och dynamisk avsändning. Var och en misslyckas av olika anledningar och kräver olika analystekniker för att lösa problemet korrekt.

Direkta anrop över arkivgränser

Direkta anrop är den enklaste anropstypen: en funktion anropar explicit en annan vid namn. Inom ett enda arkiv hanterar IDE:er dessa tillförlitligt. Över arkivgränser misslyckas analysen eftersom IDE:ns indexering inte sträcker sig över gränsen. Om arkiv A definierar en delad verktygsfunktion och arkiven B, C och D var för sig importerar och anropar den, ser IDE:n för något av dessa arkiv endast anropen inom sitt eget indexerade omfång.

Detta anropsmönster för flera arkiv är normen snarare än undantaget i mikrotjänstarkitekturer, där delade bibliotek publiceras som paket och konsumeras över dussintals tjänster. Biblioteksansvarig som avskriver en funktion i det delade paketet behöver veta vilka konsumerande tjänster som fortfarande anropar den. Deras IDE vet ingenting om konsumenterna. Pakethanteraren vet vilka tjänster som är beroende av paketet, men inte vilken specifik funktion inom paketet varje tjänst anropar. Mappning från "denna tjänst använder version X av det här paketet" till "denna tjänst anropar denna specifika avskrivna funktion" kräver att varje konsuments källkod indexeras och att anropet löses till den specifika funktionsdefinitionen.

Indirekta samtal: Omslag, delegater och fasader

En funktion får inte anropas direkt av sina konsumenter. Den kan anropas via en wrapper-funktion som tillhandahåller ytterligare loggning, felhantering eller parametertransformation. Den kan tilldelas en delegat eller en funktionspekare och anropas via delegaten. Den kan registreras i ett tjänstregister eller ett plugin-ramverk och anropas med namn via en dispatch-mekanism. I vart och ett av dessa fall returnerar en direkt sökning efter anrop till den föråldrade funktionen ett ofullständigt resultat, eftersom de faktiska anroparna anropar wrappern eller dispatchern, inte själva den föråldrade funktionen.

Wrapper-medierat anrop är särskilt vanligt i stora kodbaser där övergripande aspekter som loggning, auktorisering och återförsökslogik är lagrade runt kärnfunktioner genom wrapper-mönster. En föråldrad funktion som lindas in av ett loggverktyg anropas i praktiken av varje anropare av wrappern, inte av någon kod som innehåller den föråldrade funktionens namn. Att identifiera dessa anropare kräver spårning genom wrappern: loggverktyget anropar den föråldrade funktionen, och varje anropare av loggverktyget är därför en indirekt anropare av den föråldrade funktionen. Denna rekursiva genomgång av anropsgrafen är det som skiljer en grundlig anropsinventering från en referenssökning på ytnivå.

Betrakta ett representativt Java-exempel där en föråldrad metod nås via ett delegeringslager:

Java

// Deprecated in the core service
@Deprecated
public BillingResult calculateLegacyFee(Account account) {
    // original implementation
}

// Facade that delegates; not visible as a direct caller in a simple reference search
public BillingResult computeFee(Account account) {
    return calculateLegacyFee(account);  // indirect caller
}

// Actual consumer; calls computeFee, unaware of the underlying deprecated method
public void processMonthlyBilling(List<Account> accounts) {
    accounts.forEach(a -> computeFee(a));  // two hops from the deprecated function
}

En sökning efter "Hitta alla referenser" calculateLegacyFee återgår computeFee som den enda uppringaren. Den svarar inte processMonthlyBilling, vilket är den verkliga konsumenten av det föråldrade beteendet. En komplett uppringningsinventering kräver att man går igenom samtalsgrafen uppströms genom computeFee för att identifiera varje sökväg som slutligen anropar den föråldrade metoden.

Korsspråkiga anrop

Anrop mellan språk är den kategori där standardverktyg för anroparidentifiering misslyckas mest. När en Java-tjänst anropar ett COBOL-program med namn via ett mellanprogramlager, när ett Python-skript anropar en lagrad procedur som omsluter en föråldrad funktion, eller när ett JCL-jobb anropar ett program med sitt PROGNAME som internt anropar ett föråldrat stycke, visas ingen av dessa relationer i något enskilt språks anropsgraf. Varje språks verktyg ser bara sin egen sida av anropet.

I stordatormiljöer är anrop mellan språk strukturella och utbredda. En JCL-jobbström anger namnet på det COBOL-program som den kör. COBOL-programmet anropar stycken och underprogram med namn. Verktygsstycken som definieras i kopieringsbibliotek delas mellan många program. När ett stycke i ett kopieringsbibliotek är föråldrat kräver det att man förstår COBOL-anropsrelationerna (vilka program som inkluderar kopieringsbiblioteket och vilka som anropar stycket), JCL-anropsrelationerna (vilka jobb anropar dessa program) och eventuella gränssnitt mellan språk (Java eller SQL som interagerar med dessa program) för att hitta alla anropare. Inget enskilt verktyg omfattar alla dessa relationer. Som undersökts i analysen av statisk analys på äldre system kan statiska analysverktyg som är byggda för moderna miljöer inte se hela bilden av hur äldre program utlöses, anropas och sammankopplas när anropsrelationerna omfattar JCL-, COBOL- och systemgränssnitt samtidigt.

Dynamisk dispatch och reflektion

Vissa anropare anropar en funktion inte med dess bokstavliga namn i källkoden utan genom en mekanism som löser funktionen vid körning: reflektion i Java eller .NET, getattr/__call__ i Python, sen bindning i COBOL till och med CALL identifier, dynamisk dispatch genom polymorfism, eller invocation-by-string i plugin-ramverk och konfigurationsdrivna system. Dessa anropare innehåller inte den föråldrade funktionens namn i någon form som statisk analys tillförlitligt kan upptäcka.

En konfigurationsfil som anger ett funktionsnamn som en sträng, laddad vid körning och används för att anropa funktionen genom reflektion, är en anropare som inte förekommer någonstans i någon källkodsanalys. Ett plugin-ramverk som upptäcker och anropar registrerade hanterare via gränssnitt är en anropare som endast visas i anropsgrafen som ett anrop till dispatch-mekanismen, inte som ett anrop till någon specifik hanterare. Att identifiera dessa anropare kräver en kombination av statisk analys för att hitta dynamiska dispatch-mönster, körtidsspårning för att observera faktiska anrop och manuell inspektion av konfigurations- och registreringslogik som avgör vilka funktioner som skickas till. Som diskuterats i undersökningen av statisk analys på obfuskerad och genererad kod , när exekveringsvägar inte uttrycks direkt i källkoden, måste statisk analys rekonstruera de sannolika vägarna från strukturella mönster snarare än direkta textreferenser, och dessa rekonstruktioner kräver språkmedveten analys av själva dispatch-mekanismen.

Verktygen utvecklarna siktar på först och var de slutar

Det finns en förutsägbar sekvens av verktyg som utvecklare använder när de försöker identifiera anropare, och en lika förutsägbar punkt då vart och ett slutar ge tillförlitliga resultat. Att förstå denna sekvens är viktigt eftersom varje verktygs utdata ser komplett ut även när den inte är det.

IDE-anropshierarki: Tillförlitlig inom ett projekt

IDE-anropshierarkifunktioner är det mest naturliga första steget. IntelliJ IDEA, Visual Studio, VS Code och Eclipse erbjuder alla någon form av "hitta alla anropare" eller "visa anropshierarki" som rekursivt räknar upp anroparna för en vald funktion, inom det indexerade omfånget för det aktuella projektet eller arbetsytan. För en funktion som används exklusivt inom ett repository och ett språk är dessa funktioner korrekta och tillräckliga.

Begränsningen är explicit i omfattningen: "inom det indexerade omfånget". Anropare i andra repositorier, i andra språkkörningar eller i tjänster som är beroende av denna kod via en pakethanterare snarare än via direkt projektreferens ligger utanför omfånget. IDE:n anger inte vad den inte sökte efter. Utvecklaren får en resultatuppsättning och har ingen insyn i hur många ytterligare repositorier som finns som inte indexerades, hur många av dessa som använder den föråldrade funktionen, eller om resultatet "noll anropare" verkligen betyder noll anropare eller "noll anropare inom den del av systemet som detta verktyg kan se".

grep och textsökning: Bred men strukturellt blind

När IDE-sökning misstänks vara ofullständig är nästa steg vanligtvis textsökning: grep över tillgängliga källkataloger, eller plattformssökning via GitHub- eller GitLab-kodsökning. Detta utvidgar omfattningen avsevärt och hittar anropare i andra repositorier om dessa repositorier är tillgängliga. Det strukturella problemet är att textsökning hittar strängar, inte anrop. Den returnerar varje förekomst av funktionsnamnet, inklusive kommentarer som nämner funktionen, dokumentationssträngar, loggmeddelanden som namnger funktionen för felsökningsändamål och stränglitteraler som innehåller funktionsnamnet men inte anropar det. Den missar också anropare där funktionsnamnet skiljer sig från söksträngen: anropare genom alias, genom delvis matchade namn i COBOL där namn kan förkortas, eller genom dynamisk anrop där namnet sätts samman vid körning.

Resultatuppsättningen från textsökningen kräver manuell filtrering för att avgöra vilka förekomster som är faktiska anropsplatser, vilka som är dokumentationsreferenser och vilka som är falska positiva resultat från strängkollisioner. I ett stort system är denna filtrering i sig en betydande ansträngning, och filtreringsprocessen kan inte verifiera fullständigheten: om en anropare missades eftersom den använder ett annat namn, innehåller den filtrerade resultatuppsättningen ingen indikation på utelämnandet.

Kompilatorvarningar och @Deprecated Anteckningar

Moderna språk och verktygskedjor tillhandahåller annoteringsmekanismer för föråldrade funktioner som genererar varningar när föråldrade funktioner anropas. Javas @Deprecated annotering kombinerad med -Xlint:deprecation producerar varningar vid kompileringstid på anropsplatser. C# [Obsolete] Attributet genererar varningar vid byggtid. Gos konvention att namnge föråldrade funktioner och dokumentera dem i godoc producerar inte varningar automatiskt. Dessa mekanismer är värdefulla men begränsade på ett specifikt sätt: de fungerar bara för anropare som kompilerar mot samma kodbas där föråldringen är annoterad.

En uppringare som använder en äldre version av biblioteket, som är äldre än @Deprecated annotering, får ingen varning. En anropare som använder en binär artefakt istället för att kompilera från källkod får ingen varning. En anropare på ett annat språk som anropar via ett gränssnitt mellan språk får ingen varning. Och avgörande är att varningarna som genereras under kompileringen är lokala för kompilatorns vy: de varnar för de anrop den ser, inte för anrop i andra databaser som kompileras separat. Att använda kompilatorvarningar som enda mekanism för att identifiera anropare i ett multitjänstsystem missar varje anropare som kompilerar oberoende, vilket är det normala tillståndet i mikrotjänstarkitekturer.

Statiska analysverktyg: Bättre, men begränsade till omfattningen

Specialbyggda statiska analysverktyg ger mer exakt uppräkning av anropare än IDE:er för sitt målspråk och kan ofta korsa arkivgränser om de konfigureras för att indexera flera kodbaser. De bygger korrekta anropsgrafer snarare än att förlita sig på textmatchning, hanterar aliasing och indirekta anrop bättre än IDE-sökning och kan köras i CI-pipelines för att fånga upp nya anropare allt eftersom de läggs till. De är den mest kapabla enspråkiga metoden som finns tillgänglig.

Begränsningen är samma omfångsgräns som begränsar IDE:er, men nu på verktygsnivå: ett statiskt Java-analysverktyg indexerar inte COBOL-program, en COBOL-analysator indexerar inte Java-tjänster och indexerar inte heller JCL-jobbströmmar. I ett system där den föråldrade funktionen är ett COBOL-verktyg som anropas från COBOL-program som anropas från JCL-jobb och vars datautdata förbrukas av Java-tjänster, ser varje statiskt analysverktyg ett fragment av anropsrelationen. Såsom undersökts i samband med viktiga refaktoreringstekniker kräver identifiering av alla exekveringsvägar till en given del av koden, inklusive sällsynta feltillstånd och reservgrenar, den typ av komplett anropsgrafmappning som enspråkiga verktyg inte kan konstruera över språkgränser.

Vad en komplett inventering av samtalspartners faktiskt kräver

En komplett anroparinventering för en föråldrad funktion i ett företagssystem är inte ett sökresultat. Det är en strukturerad uppräkning av varje exekveringsväg genom vilken den föråldrade funktionen kan nås, inklusive vägar som är direkta, indirekta, språköverskridande och dynamiskt skickade. Att bygga denna uppräkning kräver flera funktioner som inget enskilt standardverktyg tillhandahåller.

En enhetlig anropsgraf över flera språk. Anropsgrafen måste omfatta alla språk i systemet. Ett anrop från en JCL-procedur till ett COBOL-program, ett anrop från COBOL-programmet till ett delat verktygsstycke och ett anrop från en Java-tjänst till samma COBOL-program via ett mellanprogramgränssnitt måste alla vara noder och kanter i samma graf. Den föråldrade funktionen är en nod i denna graf, och en anroparuppräkning är en genomfart av alla inkommande kanter, direkta och transitiva, oavsett vilket språk de har sitt ursprung i.

Rekursiv genomgång genom hela anropsgrafen. Direkta anropare är bara det första lagret. En fullständig inventering kräver att man följer anropsgrafen uppströms genom indirekta anropare, omslagsfunktioner och fasadlager tills genomgången når funktioner som inte har några egna anropare, vilka är de verkliga startpunkterna för anropskedjorna. Varje funktion i en sökväg som slutar vid den föråldrade funktionen är en anropare i relevant mening: att ta bort den föråldrade funktionen kommer att bryta varje sökväg som passerar genom den.

Indexering mellan arkiv. Anropsgrafen måste innehålla kod från varje arkiv som potentiellt kan anropa funktionen, inklusive arkiv som är beroende av funktionen via ett delat bibliotek eller paket. Detta kräver att alla arkiv indexeras samtidigt och att importrelationer mellan arkiv löses för att koppla anrop i ett arkiv till definitioner i ett annat.

Detektion av indirekta anropsmönster. Analysen måste identifiera anrop som görs genom reflektion, dynamisk dispatch, funktionspekare, delegater och strängbaserade anrop i konfigurationsfiler. Dessa kräver mönsterbaserad detektion snarare än direkt anropskantupplösning: att hitta de dynamiska dispatch-mekanismerna i koden och bestämma vilka funktioner de kan dispatcha till under vilka förhållanden.

Skillnad mellan aktiva anropare och endast test- eller döda anropare. Alla anropare kräver inte samma svar. En anropare som bara finns i en testfixtur för själva den föråldrade funktionen måste tas bort som en del av rensningen, inte migreras. En anropare i kod som i sig har identifierats som död kod genom användningsanalys blockerar inte borttagning av funktioner. För att förstå dessa skillnader krävs att man kombinerar anroparuppräkningen med information om vilka kodvägar som faktiskt är aktiva. Som beskrivs i detalj i undersökningen av detektering av död kod genom statisk analys kan oåtkomlig kod och oanvända funktioner finnas kvar i åratal i verksamhetskritiska system på grund av ofullständig dokumentation eller osäkerhet om historiska beroenden, och anroparinventeringen för en föråldrad funktion måste skilja mellan anropare som själva är aktiva och anropare som själva är döda.

Processen från avskrivning till borttagning: En strukturerad metod

Att behandla borttagning av föråldrade funktioner som en enskild händelse snarare än en strukturerad process är källan till de flesta misslyckanden med att identifiera anropare. Rätt tillvägagångssätt behandlar borttagning som det sista steget i en flerfasprocess som börjar långt innan någon kod tas bort.

Fas 1: Märk och mät

Det första steget är att annotera funktionen som föråldrad med hjälp av språkets inbyggda mekanism (@Deprecated i Java, [Obsolete] i C#, #[deprecated] i Rust, eller motsvarande) och fastställande av ett baslinjevärde för anropare. Denna baslinje är inte resultatet av en enskild sökning; den är resultatet av att indexera varje känd kodbas som kan anropa funktionen och räkna resultaten. Baslinjen tjänar två syften: den kvantifierar migreringsomfattningen och den tillhandahåller en referens mot vilken framsteg kan mätas allt eftersom anropare migreras.

Baslinjen bör organiseras efter uppringartyp och plats:

UppringningskategoriAtt RäknaBudgetÄgare
Direkta anropare i samma arkivNHögNuvarande team
Direkta uppringare i beroende tjänsterNHögTjänsteägare
Anropare via wrapper-funktionerNMediumÄgare av omslag
Anropare i genererad kod eller ramverkskodNMediumRamverksteamet
Anropare i endast testkodNLågNuvarande team
Uppringare i död kodNEndast städningNuvarande team

Fas 2: Meddela och migrera

Med en komplett inventering av uppringar blir migreringen en organiserad insats snarare än en reaktiv. Varje uppringare meddelas om de specifika samtalsplatserna: inte "du kanske anropar den här funktionen" utan "du anropar den här funktionen på rad 247 i BillingService.java, rad 82 av AccountProcessor.java, och i integrationstestet på rad 14 av BillingServiceTest.java”Denna specificitetsnivå är vad anroparinventeringen möjliggör och vad generiska utfasningsvarningar inte kan ge.

Att tillhandahålla en migreringsväg tillsammans med meddelandet är viktigt. Avvecklingen bör inkludera dokumentation av ersättningsfunktionen, en beskrivning av eventuella beteendemässiga skillnader mellan den gamla och den nya implementeringen, och om ändringen inte är trivial, ett kodexempel som visar före och efter. För anropare i andra teams kodbaser bör tidslinjen för migreringen förhandlas explicit snarare än tillkännages ensidigt, eftersom dessa team har sina egna prioriteringar och leveransåtaganden. Som utforskats i samband med databasomstrukturering över beroende system , är infasning av konsumenter av den nya strukturen innan den gamla avvecklas den disciplin som förhindrar att brytande ändringar uppstår som oväntade incidenter.

Fas 3: Övervaka antalet samtal

Mellan baslinjemätningen och det planerade borttagningsdatumet bör antalet uppringare övervakas kontinuerligt. Varje gång en uppringare migrerar till ersättningsfunktionen minskar antalet. Borttagningsgränsen nås när antalet når noll för aktiva uppringare (testanropare och anropare med död kod kan tas bort samtidigt som själva funktionen). Kontinuerlig övervakning kräver att uppräkningen av uppringarna körs enligt ett schema som en del av CI-pipelinen, och inte förlitar sig på en engångsinventering som blir inaktuell när kod ändras.

Övervakningen fångar även upp nya anropare som läggs till under utfasningsperioden. I stora organisationer är det vanligt att ny kod skrivs som anropar en föråldrad funktion under migreringsfönstret, antingen för att utvecklaren inte var medveten om utfasningen, för att en kodgranskning missade den, eller för att en automatiserad kodgenerator producerar kod som anropar den föråldrade funktionen. CI-nivåidentifiering av anropare för den föråldrade funktionen, konfigurerad att misslyckas på nya anropsplatser, förhindrar att antalet anropare växer medan migreringen pågår.

Fas 4: Verifiera fullständighet före borttagning

Omedelbart innan funktionen tas bort bör anroparuppräkningen köras en sista gång mot hela omfattningen av alla kända kodbaser. Denna sista kontroll fungerar som säkerhetsgrind: den bekräftar att antalet anropare har nått noll för aktiva anropare och identifierar eventuella tillägg i sent skede som inte fångades upp av CI-övervakning. Vid denna tidpunkt bör inventeringen också verifiera frånvaron av dynamiska anropare: konfigurationsfiler som refererar till funktionen via sträng, reflektionsbaserade registreringar och andra indirekta anropsmekanismer som identifierades under den initiala analysen.

Verifieringen bör omfatta beroendegrafen för alla delade bibliotek eller paket som exponerar den föråldrade funktionen. Om funktionen är en del av ett offentligt API som konsumeras av externa parter måste tidslinjen för borttagning ta hänsyn till externa konsumenter som kanske inte är nåbara via intern kodanalys. För interna system täcker verifieringen varje indexerad kodbas. För offentligt publicerade API:er täcker verifieringen den kända uppsättningen konsumenter plus en definierad slutperiod under vilken externa konsumenter måste migrera.

Hur uppringningsidentifiering fungerar olika i äldre och stordatormiljöer

Utmaningarna som beskrivs ovan gäller alla stora programvarusystem, men de är särskilt akuta i stordator- och äldre miljöer eftersom samtalsrelationerna i dessa miljöer uttrycks genom mekanismer som moderna verktyg för uppringningsidentifiering inte är utformade för att analysera.

I COBOL-miljöer anropas funktioner via CALL-satser som kan referera till målet med en literal sträng, med ett dataelement som innehåller programnamnet eller med en procedurpekare. Literal-sträng-fallet kan lösas genom statisk analys; dataelement-fallet kräver dataflödesanalys för att avgöra vilket värde dataelementet kan ha vid anropstidpunkten; och procedurpekarfallet kräver spårning av hur pekaren tilldelas. Var och en av dessa anropsmekanismer visas olika i källkoden och kräver olika analyser för att lösas.

I JCL-miljöer anropas program med namn i EXEC PGM=-satser. Programnamnet är en sträng som mappas till en kompilerad modul i ett laddningsbibliotek. Att spåra anropare av ett COBOL-program via JCL kräver att JCL analyseras för att extrahera programnamn, mappa dessa namn till de kompilerade COBOL-program som implementerar dem och lösa vilka COBOL-stycken inom dessa program som anropar det föråldrade verktyget. Denna flerstegslösning ligger helt utanför ramen för vare sig en COBOL-analysator eller en JCL-analysator som arbetar isolerat.

Delade kopieböcker är ett särskilt viktigt fall i COBOL-miljöer. Ett föråldrat stycke som definieras i en kopiebok kan inkluderas i många program genom COPY-satser. Stycket dupliceras inte fysiskt i varje program; det inkluderas vid kompileringstillfället. En analys som räknar förekomster av styckenamnet i källfiler utan att lösa kopieboksinkluderingar kommer att både överräkna (hitta styckedefinitionen i själva kopieboken) och underräkna (missa det faktum att varje program som inkluderar kopieboken har åtkomst till stycket). Korrekt identifiering av anropare kräver förståelse för vilka program som inkluderar vilka kopieböcker och vilka stycken inom dessa kopieböcker de faktiskt anropar. Sambandet mellan hårdkodade referenser och deras nedströmskonsumenter illustrerar varför det är viktigt att lösa dessa anropsrelationer på programnivå innan någon strukturell förändring: det som verkar vara en enkel strängreferens kan vara den enda mekanismen genom vilken dussintals program når kritisk funktionalitet.

Hur SMART TS XL Bygger upp den kompletta inventeringen av uppringare

SMART TS XL konstruerar en enhetlig anropsgraf för alla språk, plattformar och arkiv i den indexerade miljön. COBOL-program, JCL-jobbströmmar, Java-tjänster, .NET-applikationer, SQL-lagrade procedurer, Python-skript och andra källartefakter analyseras alla med hjälp av språkspecifik analys till en gemensam korsreferensgraf. Varje funktion, stycke, procedur, metod och programenhet är en nod i den grafen. Varje anropsrelation, oavsett om det är en COBOL CALL-sats, ett Java-metodanrop, en JCL EXEC PGM eller en SQL EXEC, är en typad kant. Grafen representerar systemets fullständiga anropstopologi, inte en partiell vy per språk.

När en funktion är markerad för borttagning, SMART TS XLs uppräkning av anropare passerar samtalsgrafen som kommer in från målfunktionsnoden och samlar in alla anropare på varje nivå i samtalshierarkin. Genomgången är rekursiv och följer grafen genom omslagsfunktioner, fasadlager och mellanliggande verktyg tills den når funktioner utan anropare, vilka representerar de verkliga startpunkterna för samtalskedjorna. Resultaten organiseras efter språk, efter arkiv, efter anropartyp och efter samtalsdjup, vilket ger teamet en strukturerad inventering som separerar direkta anropare från indirekta anropare och aktiva anropare från anropare med död kod.

Plattformens konsekvensanalysfunktion utökar detta till en strukturerad rapport om förändringar och effekter: inte bara vilka funktioner som anropar den föråldrade funktionen, utan även vilka program, tjänster, batchjobb och JCL-procedurer som påverkas på varje nivå i beroendekedjan. Denna rapport är den artefakt som gör processen från föråldring till borttagning handlingsbar: den namnger ägarna, identifierar de specifika anropsplatserna och kvantifierar omfattningen av den migrering som krävs innan borttagningen kan ske på ett säkert sätt. Som undersökts i detalj i konsekvensanalys för företagsändringshantering är möjligheten att räkna upp berörda komponenter innan en strukturell förändring görs det grundläggande kravet för säker drift av komplexa, sammankopplade företagssystem.

SMART TS XL stöder även den pågående övervakningsfasen av utfasningsprocessen. Eftersom korsreferensgrafen uppdateras kontinuerligt allt eftersom källkodsändringar indexeras, är antalet anropare för en föråldrad funktion alltid aktuellt. Integrering av CI-pipeline gör att automatiserade kontroller misslyckas vid nya anrop till föråldrade funktioner, vilket upprätthåller migreringsdisciplinen vid den tidpunkt där ny kod introduceras snarare än att överträdelser upptäcks i efterhand. Denna kombination av initial uppräkning, migreringsvägledning och kontinuerlig övervakning täcker hela livscykeln för en föråldrad funktion från annotering till säker borttagning.

Funktionsborttagning utan ånger

Skillnaden mellan en funktionsborttagning som går smidigt och en som utlöser produktionsfel är nästan alltid en skillnad i fullständigheten av anroparidentifieringen. Borttagningen i sig är trivial: ta bort definitionen och driftsätt. Förberedelserna är där arbetet ligger, och förberedelserna är bara så bra som anroparinventariet den baseras på.

I system där anropsgrafen är ytlig, enspråkig och finns inom ett enda arkiv, är IDE-anropshierarki och kompilatorvarningar tillräcklig förberedelse. I system där anropsgrafen sträcker sig över flera språk, flera arkiv, flera plattformar och potentiellt flera decennier av kod, täcker dessa verktyg en liten och okänd del av den faktiska anropsytan. Gapet mellan vad de returnerar och vad som faktiskt anropar funktionen är där produktionsfel uppstår.

Specialbyggd uppräkning av anropare mellan olika språk och databaser är inte en förfining av utvecklarens arbetsflöde för borttagning av funktioner. Det är en förutsättning för att kunna köra arbetsflödet säkert i alla system som är tillräckligt komplexa för att ha ackumulerat den typ av systemövergripande anropsrelationer som föråldrade funktioner i företagskodbaser rutinmässigt bär med sig. Varje föråldrad funktion som tas bort utan en fullständig anropsinventering är en utgåva som innehåller ett okänt antal körtidsfel som väntar på den specifika exekveringsvägen som når den saknade definitionen. Att eliminera den okända är vad strukturerad anroparidentifiering är till för.