Najít všechny volající zastaralé funkce

Jak najít všechny volající zastaralé funkce před jejím odebráním

Odstranění zastaralé funkce z kódové základny je koncepčně jednou z nejjednodušších věcí, které může vývojář udělat. Smazat definici, potvrdit, že ji nic nepoužívá, commit. V praxi je pro jakoukoli funkci, která existuje dostatečně dlouho na to, aby byla zastaralá, krok „potvrdit, že ji nic nepoužívá“, místem, kde se proces rozpadá. Funkce může být volána z kódu, který byl napsán před lety někým, kdo již není v týmu, v repozitáři, který jen zřídka přijímá změny, jazykem nebo frameworkem, který současný tým nevlastní. Může být vyvolána nepřímo prostřednictvím wrapperu, reflexe nebo mechanismu běhového odesílání, který se neobjevuje v žádném statickém grafu volání. Může být odkazována v generovaném kódu, v testovacím scaffoldingu nebo v konfiguračním souboru, který ji spouští podle názvu. Vývojář, který ji označí jako zastaralou, a vývojář, který ji nakonec odstraní, se o tom nemusí dozvědět bez nástroje schopného vytvořit kompletní inventář volajících napříč repozitáři.

Najděte každého volajícího, než cokoli odstraníte

SMART TS XL vytváří graf volání v různých jazycích, který identifikuje každého volajícího libovolné funkce před provedením změny.

Klikněte zde

Cena za takovou chybu je okamžitá a konkrétní. Funkce odstraněná s neúplným zjištěním volajícího způsobuje běhová selhání v systémech, které jsou na ní stále závislé. V monolitu s jediným nasazením je plocha selhání omezená. V distribuovaném systému s více službami, z nichž každá je nasazena nezávisle, se selhání kaskádovitě šíří: služba, která funkci poskytla, je aktualizována, uživatelé nikoli a k ​​poruchám dochází za běhu v produkčním prostředí v systémech, které mohou vlastnit různé týmy. V prostředí mainframů, kde programy v COBOLu volají odstavce sdílených utilit jménem, ​​se selhání nemusí projevit, dokud se nespustí specifická dávková úloha, což může být týdenní nebo měsíční, čímž se chybějící odkaz stane neviditelným v běžných testovacích cyklech. Jak bylo zkoumáno v širším kontextu správy zastaralého kódu , rizika se časem sčítají: zastaralý kód, který je neúplně odstraněn, je nebezpečnější než zastaralý kód, který je ponechán na místě, protože odstranění vytváří iluzi úplnosti, zatímco zbývající volající pokračují v běhu s definicí, která již neexistuje.

Tento článek je praktickým průvodcem pro vyhledávání volajících před odstraněním funkce: co vyžaduje kompletní inventář volajících, proč jsou nástroje, po kterých vývojáři sahají jako první, strukturálně nedostatečné, jak různé typy vztahů volání vyžadují různé analytické přístupy a jak vypadá skutečný výčet volajících napříč systémy v podnikových kódových bázích, které kombinují jazyky, platformy a repozitáře.

Proč je nalezení volajícího těžší, než se zdá

Povrchová verze vyhledávání volajících funkcí je známá každému vývojáři: klikněte pravým tlačítkem myši na název funkce v IDE, vyberte „Najít všechny reference“ nebo „Zobrazit hierarchii volání“ a zkontrolujte výsledky. Toto funguje spolehlivě v rámci jednoho projektu načteného v jedné instanci IDE. V okamžiku, kdy kódová základna přesáhne tento rozsah, se výsledky stanou neúplnými způsoby, které nejsou ve výstupu viditelné. IDE neindikuje, které volající funkce nenašlo, protože neindexovalo repozitáře, které je obsahují. Vývojář vidí sadu výsledků, která se jeví jako úplná, a podle toho pokračuje.

Toto je strukturální problém s vyhledáváním volajících funkcí ve velkém měřítku: nástroje, které vývojáři používají nejplynuleji, jsou omezeny svým rozsahem indexování a ve velkých, distribuovaných, vícejazyčných systémech tento rozsah pokrývá zlomek míst, kde by daná funkce mohla být volána. Důvěra vývojáře v úplnost výsledků je nepřímo úměrná skutečné úplnosti vyhledávání. V malé kódové základně s jedním jazykem je hierarchie volání IDE skutečně spolehlivá. V podnikovém systému zahrnujícím více repozitářů, jazyků a prostředí nasazení je systematicky zavádějící. Jak bylo analyzováno v kontextu rizika entropie kódu a refaktoringu , starší moduly mohou záviset na zastaralých rozhraních, zatímco novější služby stále volají rutiny původně navržené pro dřívější prostředí, a právě tyto vztahy mezi voláními mezi systémy jsou ty, které vyhledávání omezené na IDE nevidí.

Pochopení konkrétních důvodů, proč selhává vyhledávání volajícího, vyžaduje prozkoumání každého hlavního typu volání: přímá volání, nepřímá volání, volání napříč jazyky a dynamické odesílání. Každý z nich selhává z jiných důvodů a pro správné vyřešení vyžaduje různé analytické techniky.

Přímá volání přes hranice repozitáře

Přímá volání jsou nejjednodušším typem volání: jedna funkce explicitně volá jinou podle názvu. V rámci jednoho repozitáře IDE tato volání spolehlivě zpracovává. Napříč hranicemi repozitáře analýza selže, protože indexování IDE nepřesahuje hranice repozitáře. Pokud repozitář A definuje sdílenou užitnou funkci a repozitáře B, C a D ji importují a volají, IDE pro kterýkoli z těchto repozitářů vidí pouze volání v rámci svého vlastního indexovaného rozsahu.

Tento vzorec volání z více repozitářů je spíše normou než výjimkou v architekturách mikroslužeb, kde jsou sdílené knihovny publikovány jako balíčky a spotřebovávány desítkami služeb. Správce knihovny, který zastarává funkci ve sdíleném balíčku, potřebuje vědět, které služby, které ji spotřebovávají, ji stále volají. Jeho IDE neví nic o uživatelích. Správce balíčků ví, které služby na balíčku závisí, ale neví, kterou konkrétní funkci v balíčku každá služba volá. Mapování z „tato služba používá verzi X tohoto balíčku“ na „tato služba volá tuto konkrétní zastaralou funkci“ vyžaduje indexování zdrojového kódu každého uživatele a vyřešení volání na definici konkrétní funkce.

Nepřímá volání: Obálky, delegáti a fasády

Funkce nemusí být volána přímo svými příjemci. Může být volána prostřednictvím obalové funkce, která poskytuje dodatečné protokolování, ošetření chyb nebo transformaci parametrů. Může být přiřazena delegátovi nebo ukazateli funkce a vyvolána prostřednictvím delegáta. Může být registrována v registru služeb nebo v frameworku pluginu a volána jménem prostřednictvím mechanismu odesílání. V každém z těchto případů přímé vyhledávání volání zastaralé funkce vrací neúplný výsledek, protože skuteční volající volají obalovou funkci nebo dispečer, nikoli samotnou zastaralou funkci.

Vyvolání zprostředkované wrapperem je obzvláště běžné ve velkých kódových základech, kde jsou průřezové záležitosti, jako je protokolování, autorizace a logika opakování, vrstveny kolem základních funkcí prostřednictvím wrapperových vzorů. Zastaralá funkce obalená logovacím nástrojem je efektivně volána každým volajícím wrapperu, nikoli žádným kódem, který obsahuje název zastaralé funkce. Identifikace těchto volajících vyžaduje trasování přes wrapper: logovací nástroj volá zastaralou funkci a každý volající logovacího nástroje je tedy nepřímým volajícím zastaralé funkce. Toto rekurzivní procházení grafu volání je to, co odlišuje důkladnou inventuru volajících od povrchového vyhledávání referencí.

Uvažujme reprezentativní příklad v Javě, kde je k zastaralé metodě přistupováno prostřednictvím delegační vrstvy:

Jáva

// 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
}

Vyhledávání „Najít všechny reference“ pro calculateLegacyFee Vrací computeFee jako jediný volající. Nevrací processMonthlyBilling, což je skutečný příjemce zastaralého chování. Úplný inventář volajících vyžaduje procházení grafu volání proti proudu skrz computeFee identifikovat každou cestu, která nakonec vyvolá zastaralou metodu.

Volání v různých jazycích

Volání napříč jazyky jsou kategorií, kde standardní nástroje pro vyhledávání volajících selhávají nejčastěji. Když služba Java volá program v COBOLu podle jeho názvu prostřednictvím middlewarové vrstvy, když skript v Pythonu volá uloženou proceduru, která obaluje zastaralou funkci, nebo když úloha JCL volá program podle jeho PROGNAME, který interně volá zastaralý odstavec, žádný z těchto vztahů se neobjeví v grafu volání daného jazyka. Nástroje každého jazyka vidí pouze svou vlastní stranu volání.

V prostředích sálových počítačů jsou volání napříč jazyky strukturální a všudypřítomná. Proud úloh JCL určuje název programu COBOL, který spouští. Program COBOL volá odstavce a podprogramy jménem. Užitkové odstavce definované v knihovnách kopií jsou sdíleny mezi mnoha programy. Pokud je odstavec v knihovně kopií zastaralý, nalezení všech volajících vyžaduje pochopení vztahů volání COBOL (které programy zahrnují knihovnu kopií a které volají daný odstavec), vztahů volání JCL (které úlohy tyto programy volají) a všech rozhraní napříč jazyky (Java nebo SQL, která s těmito programy interagují). Žádný jednotlivý nástroj nepokrývá všechny tyto vztahy. Jak bylo zkoumáno v analýze statické analýzy na starších systémech , nástroje statické analýzy vytvořené pro moderní prostředí nemohou vidět úplný obraz o tom, jak jsou starší programy spouštěny, volány a propojeny, když vztahy volání zahrnují současně rozhraní JCL, COBOL a rozhraní napříč systémy.

Dynamické odesílání a reflexe

Některé volající volají funkci ne podle jejího doslovného názvu ve zdrojovém kódu, ale prostřednictvím mechanismu, který funkci řeší za běhu: reflexe v Javě nebo .NET, getattr/__call__ v Pythonu, pozdní vazba v COBOLu prostřednictvím CALL identifier, dynamické odesílání pomocí polymorfismu nebo volání pomocí řetězce v frameworkech pluginů a systémech řízených konfigurací. Tyto volající funkce neobsahují název zastaralé funkce v žádné formě, kterou by statická analýza dokázala spolehlivě detekovat.

Konfigurační soubor, který specifikuje název funkce jako řetězec, načtený za běhu a použitý k vyvolání funkce prostřednictvím reflexe, je volající proces, který se nikde v žádné analýze zdrojového kódu neobjevuje. Framework pluginu, který vyhledává a volá registrované obslužné rutiny pomocí rozhraní, je volající proces, který se v grafu volání objevuje pouze jako volání mechanismu odesílání, nikoli jako volání konkrétního obslužného programu. Identifikace těchto volajících vyžaduje kombinaci statické analýzy k nalezení dynamických vzorů odesílání, trasování za běhu k pozorování skutečných volání a ruční kontroly konfigurační a registrační logiky, která určuje, které funkce jsou odesílány. Jak bylo diskutováno při zkoumání statické analýzy na obfuskovaném a generovaném kódu , pokud cesty provádění nejsou vyjádřeny přímo ve zdrojovém kódu, musí statická analýza rekonstruovat pravděpodobné cesty ze strukturálních vzorů, nikoli z přímých textových odkazů, a tyto rekonstrukce vyžadují jazykově vědomou analýzu samotného mechanismu odesílání.

Nástroje, po kterých vývojáři sahají nejdříve a kde se zastaví

Existuje předvídatelná posloupnost nástrojů, které vývojáři používají při pokusu o nalezení volajícího, a stejně předvídatelný bod, ve kterém každý z nich přestane poskytovat spolehlivé výsledky. Pochopení této posloupnosti je důležité, protože výstup každého nástroje vypadá kompletně, i když ve skutečnosti není.

Hierarchie volání IDE: Spolehlivá v rámci jednoho projektu

Funkce hierarchie volání IDE jsou nejpřirozenějším prvním krokem. IntelliJ IDEA, Visual Studio, VS Code a Eclipse poskytují nějakou formu „nalezení všech volajících“ nebo „zobrazení hierarchie volání“, která rekurzivně vyjmenovává volající vybrané funkce v rámci indexovaného rozsahu aktuálního projektu nebo pracovního prostoru. Pro funkci, která se používá výhradně v rámci jednoho repozitáře a jednoho jazyka, jsou tyto funkce přesné a dostatečné.

Toto omezení je explicitně uvedeno v rozsahu: „v rámci indexovaného rozsahu“. Volající v jiných repozitářích, v jiných běhových prostředích jazyka nebo ve službách, které jsou na tomto kódu závislé prostřednictvím správce balíčků, nikoli prostřednictvím přímého odkazu na projekt, jsou mimo rozsah. IDE neuvádí, co neprohledalo. Vývojář obdrží sadu výsledků a nemá přehled o tom, kolik dalších repozitářů existuje, které nebyly indexovány, kolik z nich používá zastaralou funkci nebo zda výsledek „nula volajících“ znamená skutečně nula volajících nebo „nula volajících v rámci části systému, kterou tento nástroj vidí“.

grep a textové vyhledávání: Široké, ale strukturálně slepé

Pokud existuje podezření, že vyhledávání v IDE je neúplné, dalším krokem je obvykle textové vyhledávání: grep napříč dostupnými zdrojovými adresáři nebo vyhledávání na platformě pomocí vyhledávání kódu na GitHubu nebo GitLabu. Tím se značně rozšiřuje rozsah a vyhledávají se volající funkce v jiných repozitářích, pokud jsou tyto repozitáře dostupné. Strukturální problém spočívá v tom, že textové vyhledávání nachází řetězce, nikoli volání. Vrací se každý výskyt názvu funkce, včetně komentářů, které funkci zmiňují, řetězců dokumentace, zpráv protokolu, které funkci pojmenovávají pro účely ladění, a řetězcových literálů, které obsahují název funkce, ale nevolají ji. Také se přehlíží volající funkce, u kterých se název funkce liší od hledaného řetězce: volající prostřednictvím aliasů, prostřednictvím částečně shodných názvů v COBOLu, kde mohou být názvy zkráceny, nebo prostřednictvím dynamického volání, kde je název sestaven za běhu.

Výsledná sada z textového vyhledávání vyžaduje ruční filtrování, aby se určilo, které výskyty jsou skutečnými webovými stránkami volání, které odkazy na dokumentaci a které falešně pozitivní výsledky z kolizí řetězců. Ve velkém systému je toto filtrování samo o sobě značné úsilí a proces filtrování nemůže ověřit úplnost: pokud byl volající přehlédnut, protože používá jiné jméno, filtrovaná výsledná sada neobsahuje žádnou indikaci opomenutí.

Varování kompilátoru a @Deprecated Anotace

Moderní jazyky a nástroje poskytují mechanismy anotací zastaralých funkcí, které generují varování při volání zastaralých funkcí. @Deprecated anotace v kombinaci s -Xlint:deprecation generuje varování během kompilace na místech volání. C# [Obsolete] Atribut generuje varování v době sestavení. Konvence jazyka Go pro pojmenovávání zastaralých funkcí a jejich dokumentování v souboru godoc negeneruje varování automaticky. Tyto mechanismy jsou cenné, ale omezené určitým způsobem: fungují pouze pro volající, kteří se kompilují se stejnou kódovou základnou, ve které je zastaralost anotována.

Volající, který používá starší verzi knihovny, starší než @Deprecated anotace, neobdrží žádné varování. Volající, který používá binární artefakt místo kompilace ze zdrojového kódu, neobdrží žádné varování. Volající v jiném jazyce, který volá prostřednictvím rozhraní napříč jazyky, neobdrží žádné varování. A co je zásadní, varování generovaná během kompilace jsou lokální pro zobrazení kompilátoru: varují před voláními, která vidí, nikoli před voláními v jiných repozitářích, která jsou kompilována samostatně. Použití varování kompilátoru jako jediného mechanismu pro detekci volajícího v systému s více službami mine každého volajícího, který se kompiluje nezávisle, což je normální stav v architekturách mikroslužeb.

Nástroje pro statickou analýzu: Lepší, ale s omezeným rozsahem

Účelové nástroje pro statickou analýzu poskytují přesnější výčet volajících než IDE pro daný cílový jazyk a často mohou překračovat hranice repozitářů, pokud jsou nakonfigurovány pro indexování více kódových základen. Vytvářejí správné grafy volání, místo aby se spoléhaly na porovnávání textu, zpracovávají aliasing a nepřímá volání lépe než vyhledávání v IDE a lze je spouštět v kanálech CI pro zachycení nových volajících, jakmile jsou přidávány. Jsou nejschopnějším dostupným přístupem pro jeden jazyk.

Omezení spočívá ve stejné hranici rozsahu, která omezuje IDE, ale nyní na úrovni nástroje: nástroj pro statickou analýzu Javy neindexuje programy v COBOLu, analyzátor COBOLu neindexuje služby Javy a ani neindexuje streamy úloh JCL. V systému, kde je zastaralá funkce utilita COBOL volaná z programů v COBOLu, které jsou volány z úloh JCL a jejichž datový výstup je spotřebováván službami Javy, každý nástroj pro statickou analýzu vidí jeden fragment vztahu volání. Jak bylo zkoumáno v kontextu základních technik refaktoringu , identifikace všech cest provádění k dané části kódu, včetně vzácných chybových stavů a ​​záložních větví, vyžaduje takový druh úplného mapování grafu volání, který jednomu jazyku podobné nástroje nedokážou vytvořit napříč jazykovými hranicemi.

Co vlastně vyžaduje kompletní inventář volajících

Kompletní inventář volajících zastaralé funkce v podnikovém systému není výsledkem vyhledávání. Jedná se o strukturovaný výčet všech cest provádění, kterými lze k zastaralé funkci dosáhnout, včetně cest přímých, nepřímých, mezijazykových a dynamicky odesílaných. Vytvoření tohoto výčtu vyžaduje několik funkcí, které žádný standardní nástroj neposkytuje.

Sjednocený graf volání napříč jazyky. Graf volání musí zahrnovat všechny jazyky v systému. Volání z procedury JCL do programu v COBOLu, volání z programu v COBOLu do odstavce sdíleného nástroje a volání ze služby Java do stejného programu v COBOLu prostřednictvím rozhraní middlewaru musí být všechny uzly a hranami ve stejném grafu. Zastaralá funkce je v tomto grafu uzlem a výčet volajícího je průchod všemi příchozími hranami, přímými i tranzitivními, bez ohledu na to, ve kterém jazyce pocházejí.

Rekurzivní procházení celým grafem volání. Přímé volající funkce jsou pouze první vrstvou. Kompletní inventarizace vyžaduje sledování grafu volání proti proudu přes nepřímé volající funkce, obalové funkce a fasádní vrstvy, dokud procházení nedosáhne funkcí, které nemají vlastní volající funkce, což jsou skutečné vstupní body řetězců volání. Každá funkce v cestě, která končí u zastaralé funkce, je volající v relevantním smyslu: odstranění zastaralé funkce přeruší každou cestu, která jí prochází.

Indexování napříč repozitáři. Graf volání musí obsahovat kód z každého repozitáře, který by mohl potenciálně volat funkci, včetně repozitářů, které jsou na funkci závislé prostřednictvím sdílené knihovny nebo balíčku. To vyžaduje současné indexování všech repozitářů a řešení vztahů importu napříč repozitáři pro propojení volání v jednom repozitáři s definicemi v jiném.

Detekce vzorů nepřímého volání. Analýza musí identifikovat volání uskutečněná prostřednictvím reflexe, dynamického odesílání, ukazatelů funkcí, delegátů a volání založeného na řetězcích v konfiguračních souborech. Tato volání vyžadují spíše detekci založenou na vzorcích než přímé rozlišení hrany volání: nalezení mechanismů dynamického odesílání v kódu a určení, do kterých funkcí mohou být odesílány za jakých podmínek.

Rozdíl mezi aktivními volajícími a volajícími pouze pro testování nebo mrtvými volajícími. Ne všechny volající vyžadují stejnou odpověď. Volající, který existuje pouze v testovacím přípravku pro samotnou zastaralou funkci, musí být v rámci čištění odstraněn, nikoli migrován. Volající v kódu, který byl sám identifikován jako mrtvý kód prostřednictvím analýzy použití, neblokuje odstranění funkce. Pochopení těchto rozdílů vyžaduje kombinaci výčtu volajících s informacemi o tom, které cesty kódu jsou skutečně aktivní. Jak je podrobně popsáno v analýze detekce mrtvého kódu pomocí statické analýzy , nedosažitelný kód a nepoužívané funkce mohou v kritických systémech přetrvávat roky kvůli neúplné dokumentaci nebo nejistotě ohledně historických závislostí a inventář volajících pro zastaralou funkci musí rozlišovat mezi volajícími, kteří jsou sami aktivní, a volajícími, kteří jsou sami mrtví.

Proces od zastarávání k odstranění: Strukturovaný přístup

Příčinou většiny selhání při vyhledávání volajícího je zacházení s odstraňováním zastaralých funkcí jako s jedinou událostí, nikoli se strukturovaným procesem. Správný přístup považuje odstraňování za poslední krok ve vícefázovém procesu, který začíná dlouho předtím, než je jakýkoli kód smazán.

Fáze 1: Označení a měření

Prvním krokem je označení funkce jako zastaralé pomocí vestavěného mechanismu jazyka (@Deprecated v Javě, [Obsolete] v C#, #[deprecated] (v Rustu nebo odpovídajícím ekvivalentu) a stanovení základního počtu volajících. Tento základní stav není výsledkem žádného jednotlivého vyhledávání; je výsledkem indexování každé známé kódové báze, která by mohla volat funkci, a spočítání výsledků. Základní stav slouží dvěma účelům: kvantifikuje rozsah migrace a poskytuje referenci, podle které lze měřit pokrok při migraci volajících.

Základní plán by měl být uspořádán podle typu volajícího a lokality:

Kategorie volajícíhoPočítatPrioritaMajitel
Přímé volající ve stejném repozitářiNVysokýSoučasný tým
Přímí volající v závislých službáchNVysokýVlastníci služeb
Volající prostřednictvím wrapperových funkcíNStředníMajitelé obalů
Volající v generovaném kódu nebo kódu frameworkuNStředníRámcový tým
Volající v testovacím kóduNNízkéSoučasný tým
Volající v mrtvém kóduNPouze úklidSoučasný tým

Fáze 2: Upozornění a migrace

S kompletním inventářem volajících se migrace stává organizovaným úsilím, nikoli reaktivním. Každý vlastník volajícího je informován o konkrétních místech volání: ne „možná voláte tuto funkci“, ale „tuto funkci voláte na řádku 247 BillingService.java, řádek 82 z AccountProcessor.javaa v integračním testu na řádku 14 BillingServiceTest.java„Tuto úroveň specifičnosti umožňuje inventář volajících a generická varování před zastaráním poskytnout nemohou.“

Spolu s oznámením je nezbytné uvést i cestu migrace. Zastarávání by mělo zahrnovat dokumentaci náhradní funkce, popis případných rozdílů v chování mezi starou a novou implementací a v případě, že změna není triviální, příklad kódu ukazující stav před a po. Pro volající v kódových základnách jiných týmů by měl být časový harmonogram migrace explicitně dohodnut, nikoli jednostranně oznámen, protože tyto týmy mají své vlastní priority a závazky k dodání. Jak bylo prozkoumáno v kontextu refaktoringu databází napříč závislými systémy , postupné zavádění uživatelů nové struktury před zastaráváním staré struktury je disciplínou, která zabraňuje tomu, aby se kritické změny jevily jako neočekávané incidenty.

Fáze 3: Sledování počtu volajících

Mezi základním měřením a plánovaným datem odstranění by měl být počet volajících nepřetržitě monitorován. Pokaždé, když volající migruje na náhradní funkci, počet se snižuje. Hranice odstranění je dosažena, když počet aktivních volajících dosáhne nuly (volající pouze pro testování a volající s mrtvým kódem mohou být odstraněni současně se samotnou funkcí). Nepřetržité monitorování vyžaduje spouštění výčtu volajících podle plánu jako součást kanálu CI, nikoli spoléhání se na jednorázový inventář, který se při změně kódu zastará.

Monitorování také zachycuje nové volající funkce přidané během období zastarávání. Ve velkých organizacích je běžné, že se během migračního okna napíše nový kód, který volá zastaralou funkci, a to buď proto, že vývojář o zastarávání nevěděl, protože jej revize kódu přehlédla, nebo proto, že automatizovaný generátor kódu vytvoří kód, který zastaralou funkci volá. Detekce volajícího na úrovni CI pro zastaralou funkci, nakonfigurovaná tak, aby selhala na nových webech volání, zabraňuje růstu počtu volajících během probíhající migrace.

Fáze 4: Ověření úplnosti před odstraněním

Bezprostředně před odebráním funkce by měl být výčet volajících naposledy spuštěn v plném rozsahu všech známých kódových základen. Tato závěrečná kontrola slouží jako bezpečnostní brána: potvrzuje, že počet volajících u aktivních volajících dosáhl nuly, a identifikuje všechny pozdní fáze přidání, které nebyly zachyceny monitorováním CI. V tomto okamžiku by inventář měl také ověřit absenci dynamických volajících: konfiguračních souborů, které odkazují na funkci pomocí řetězce, registrací založených na reflexi a jakýchkoli dalších mechanismů nepřímého volání, které byly identifikovány během počáteční analýzy.

Ověření by se mělo rozšířit na graf závislostí všech sdílených knihoven nebo balíčků, které zpřístupňují zastaralou funkci. Pokud je funkce součástí veřejného API spotřebovaného externími stranami, musí časový harmonogram odstranění zohledňovat externí uživatele, kteří nemusí být dostupní prostřednictvím interní analýzy kódu. U interních systémů se ověřování týká každé indexované kódové základny. U veřejně publikovaných API se ověřování týká známé sady uživatelů a definovaného období zákazu, během kterého musí externí uživatelé migrovat.

Jak funguje vyhledávání volajícího odlišně v prostředí starších systémů a sálových počítačů

Výše popsané problémy se vztahují na jakýkoli velký softwarový systém, ale jsou obzvláště akutní v prostředí mainframe a starších systémů, protože vztahy volání v těchto prostředích jsou vyjádřeny mechanismy, které moderní nástroje pro vyhledávání volajících nebyly navrženy k analýze.

V prostředí COBOLu se funkce volají pomocí příkazů CALL, které mohou odkazovat na cíl pomocí literálního řetězce, datové položky obsahující název programu nebo ukazatele procedury. Případ literálního řetězce lze řešit statickou analýzou; případ datové položky vyžaduje analýzu toku dat, aby se určila hodnota, kterou by datová položka mohla v okamžiku volání obsahovat; a případ ukazatele procedury vyžaduje sledování, jak je ukazatel přiřazen. Každý z těchto mechanismů volání se ve zdrojovém kódu jeví odlišně a k řešení vyžaduje odlišnou analýzu.

V prostředí JCL jsou programy volány podle názvu v příkazech EXEC PGM=. Název programu je řetězec, který se mapuje na kompilovaný modul v knihovně pro načítání. Sledování volajících COBOL programu pomocí JCL vyžaduje parsování JCL za účelem extrahování názvů programů, mapování těchto názvů na kompilované COBOL programy, které je implementují, a určení, které COBOL odstavce v rámci těchto programů volají zastaralý nástroj. Toto vícekrokové řešení je zcela mimo rozsah analyzátoru COBOL nebo analyzátoru JCL pracujícího samostatně.

Sdílené sešity jsou obzvláště důležitým případem v prostředí COBOL. Zastaralý odstavec definovaný v sešitě může být zahrnut do mnoha programů pomocí příkazů COPY. Odstavec není fyzicky duplikován v každém programu; je zahrnut při kompilaci. Analýza, která počítá výskyty názvu odstavce ve zdrojových souborech bez vyřešení zahrnutí v sešitě, bude jak nadhodnocovat (nalezení definice odstavce v samotné sešitě), tak podhodnocovat (chybí skutečnost, že každý program, který obsahuje sešit, má k odstavci přístup). Správné zjišťování volajícího vyžaduje pochopení toho, které programy zahrnují které sešity a které odstavce v rámci těchto sešitů skutečně volají. Vztah mezi pevně zakódovanými odkazy a jejich následnými příjemci ilustruje, proč je vyřešení těchto vztahů volání na úrovni programu nezbytné před jakoukoli strukturální změnou: to, co se jeví jako jednoduchý řetězcový odkaz, může být jediným mechanismem, kterým desítky programů dosáhnou kritické funkčnosti.

Jak SMART TS XL Vytvoří kompletní inventář volajících

SMART TS XL vytváří jednotný graf volání napříč všemi jazyky, platformami a repozitáři v indexovaném prostředí. Programy v COBOLu, streamy úloh JCL, služby Java, aplikace .NET, uložené procedury SQL, skripty Pythonu a další zdrojové artefakty jsou analyzovány pomocí jazykově specifické analýzy do společného grafu křížových odkazů. Každá funkce, odstavec, procedura, metoda a programová jednotka je v tomto grafu uzlem. Každý vztah volání, ať už se jedná o příkaz COBOL CALL, volání metody Java, příkaz JCL EXEC PGM nebo příkaz SQL EXEC, je typovaná hrana. Graf představuje kompletní topologii volání systému, nikoli částečný pohled pro jednotlivé jazyky.

Když je funkce označena k odstranění, SMART TS XLVýčet volajících funkcí prochází grafem volání z cílového uzlu funkce a shromažďuje všechny volající funkce na každé úrovni hierarchie volání. Procházení je rekurzivní, graf sleduje obalové funkce, vrstvy fasády a mezilehlé utility, dokud nedosáhne funkcí bez volajících, které představují skutečné vstupní body řetězců volání. Výsledky jsou uspořádány podle jazyka, repozitáře, typu volajícího a hloubky volání, což týmu poskytuje strukturovaný inventář, který odděluje přímé volající od nepřímých volajících a aktivní volající od volajících s mrtvým kódem.

Možnost analýzy dopadů platformy rozšiřuje tuto funkci do strukturované zprávy o dopadu změn: nejenže uvádí, které funkce volají zastaralou funkci, ale i které programy, služby, dávkové úlohy a procedury JCL jsou ovlivněny na každé úrovni řetězce závislostí. Tato zpráva je artefaktem, který umožňuje realizovat proces od zastarávání k odebrání: pojmenovává vlastníky, identifikuje konkrétní místa volání a kvantifikuje rozsah migrace potřebné před bezpečným provedením odebrání. Jak bylo podrobně popsáno v analýze dopadů pro řízení podnikových změn , schopnost vyjmenovat dotčené komponenty před provedením strukturální změny je základním požadavkem pro bezpečný provoz komplexních, propojených podnikových systémů.

SMART TS XL také podporuje fázi průběžného monitorování procesu zastarávání. Protože graf křížových odkazů je průběžně aktualizován s indexováním změn zdrojového kódu, je počet volajících zastaralé funkce vždy aktuální. Integrace CI pipeline umožňuje automatizovaným kontrolám selhání u nových volání zastaralých funkcí, čímž se vynucuje migrační disciplína v okamžiku zavedení nového kódu, spíše než aby se porušení odhalovala dodatečně. Tato kombinace počátečního výčtu, pokynů k migraci a průběžného monitorování pokrývá celý životní cyklus zastaralé funkce od anotace až po bezpečné odstranění.

Odstranění funkce bez lítosti

Rozdíl mezi odstraněním funkce, které proběhne hladce, a odstraněním, které spustí produkční selhání, spočívá téměř vždy v úplnosti detekce volajícího. Samotné odstranění je triviální: smažte definici a nasaďte ji. Příprava je místem, kde je práce, a příprava je jen tak dobrá, jako je inventář volajících, na kterém je založena.

V systémech, kde je graf volání mělký, jednojazyčný a obsažený v jednom repozitáři, je hierarchie volání IDE a varování kompilátoru dostatečnou přípravou. V systémech, kde graf volání zahrnuje více jazyků, více repozitářů, více platforem a potenciálně i několik desetiletí kódu, tyto nástroje pokrývají malou a nepoznatelnou část skutečného povrchu volajícího. Mezera mezi tím, co vracejí, a tím, co funkci skutečně volá, je místem, kde vznikají produkční selhání.

Účelově vytvořený výčet volajících, který je kompatibilní s různými jazyky a repozitáři, není zdokonalením pracovního postupu vývojáře pro odstraňování funkcí. Je předpokladem pro bezpečné spuštění tohoto pracovního postupu v jakémkoli systému dostatečně složitém na to, aby se v něm nashromáždily vztahy mezi voláními systémů, které běžně nesou zastaralé funkce v podnikových kódových základech. Každá zastaralá funkce, která je odstraněna bez kompletního inventáře volajících, je vydáním, které obsahuje neznámý počet selhání za běhu čekajících na konkrétní cestu spuštění, která dosáhne chybějící definice. Eliminace této neznámé funkce je účelem strukturovaného zjišťování volajících.