Nawigacja po kodzie ulega awarii

Dlaczego nawigacja po kodzie zawodzi, gdy używasz więcej niż jednego języka programowania

Nawigacja po kodzie działa dobrze, gdy programista pozostaje w obrębie jednego języka w jednej bazie kodu. Naciśnij F12, aby przejść do definicji. Kliknij prawym przyciskiem myszy metodę, aby znaleźć wszystkie odwołania. Te interakcje wydają się natychmiastowe, ponieważ IDE ma kompletny, spójny model kodu: zna każdy symbol, każdy typ, każdy łańcuch importu. Jednak w momencie, gdy granica ta rozszerza się o drugi język, model ten pęka. IDE zna swój język, ale nie drugi. Programista widzi wywołanie, podąża za nim do krawędzi bieżącego pliku, a następnie uderza w ścianę: wywoływana funkcja znajduje się w innym języku, być może w innym repozytorium, rządzona innymi konwencjami, których narzędzie nie rozumie. Od tego momentu nawigacja staje się ręczna. Programista zmienia narzędzia, wyszukuje po tekście i ma nadzieję, że wynik jest tym, czego szukał.

Nawigacja po kodzie między językami

SMART TS XL zapewnia ujednolicone rozpoznawanie symboli i nawigację po kodzie w każdym języku w Twoim środowisku.

Kliknij tutaj

Dla zespołów pracujących w prawdziwie wielojęzycznych środowiskach nie jest to sporadyczna niedogodność. To domyślny stan każdego istotnego zadania. Systemy korporacyjne rutynowo obejmują COBOL i Javę, JCL i SQL, Pythona i C++ lub dowolną liczbę kombinacji, które odzwierciedlają dekady decyzji technologicznych nakładających się na siebie. Każda granica językowa w tym stosie to miejsce, w którym kończy się automatyczna nawigacja, a rozpoczyna się ręczna rekonstrukcja. Tarcie narasta u każdego programisty, każdego zadania i każdego zespołu, aż koszty stają się strukturalne: wolniejsze wdrażanie, bardziej ryzykowne zmiany, dłuższe dochodzenia w sprawie incydentów i rosnące poleganie na nielicznych osobach, które znają wiele języków. Jak zbadano w kontekście Rozwiązania do analizy statycznej w języku COBOLWyzwanie związane z rozumowaniem wykraczającym poza granice językowe to nie tylko problem z narzędziami. To fundamentalna przeszkoda w bezpiecznym funkcjonowaniu dużych, heterogenicznych systemów.

Zrozumienie przyczyn tej awarii i jej kosztów w praktyce to pierwszy krok do jej rozwiązania. Niniejszy artykuł analizuje problem od jego technicznych źródeł po konsekwencje operacyjne, analizuje, dlaczego powszechnie używane narzędzia zawodzą na granicach językowych i wyjaśnia, czego wymaga prawdziwa nawigacja międzyjęzykowa, aby funkcjonować w skali przedsiębiorstwa.

Czego tak naprawdę wymaga nawigacja po kodzie, aby działać

Nawigacja po kodzie nie jest operacją wyszukiwania. Jest operacją rozwiązywania. Gdy programista pyta „gdzie jest zdefiniowana ta funkcja?”, IDE nie skanuje plików w poszukiwaniu pasującego tekstu. Rozwiązuje identyfikator na podstawie ustrukturyzowanego modelu bazy kodu: przeanalizowanej reprezentacji każdej klasy, metody, zmiennej i typu, które znajdują się w zakresie, wraz z relacjami między nimi. Model ten jest budowany podczas indeksowania, stale utrzymywany w miarę zmian plików i natychmiast odpytywany po uruchomieniu akcji nawigacyjnej. Dokładność i kompletność modelu decydują o dokładności i kompletności każdego wyniku nawigacji otrzymywanego przez programistę.

To rozróżnienie między wyszukiwaniem a rozwiązaniem ma znaczenie, ponieważ definiuje wymagania dotyczące nawigacji międzyjęzykowej. Wyszukiwanie tekstowe może przeszukiwać dowolne pliki, niezależnie od języka, ponieważ nie odczytuje kodu jako kodu. Narzędzie nawigacyjne nie może działać w różnych językach, dopóki nie zbuduje modelu obejmującego oba języki, a nie tylko modelu jednego języka, który może również wyszukiwać ciągi znaków w plikach należących do drugiego. Zbudowanie takiego ujednoliconego modelu jest technicznie wymagające w sposób, w jaki nawigacja jednojęzyczna nie jest, a poziom trudności rośnie wraz z liczbą zaangażowanych języków. Jak wyjaśniono w szczegółowym omówieniu analiza przepływu danych i sterowaniaKod, który działa poprawnie na różnych ścieżkach wykonywania, wymaga strukturalnego zrozumienia całej ścieżki, a nie tylko segmentów mieszczących się w zakresie pojedynczego narzędzia.

Trzy konkretne możliwości, których wymaga nawigacja w kodzie, a które w różny sposób zawodzą na granicach językowych, to rozpoznawanie symboli, konstruowanie grafu wywołań i śledzenie zależności. Każda z nich zasługuje na osobne omówienie przed rozważeniem ich interakcji w praktyce.

Rozdzielczość symboli i jej przyczyny, dla których jest ona przerywana na granicach językowych

Rozpoznawanie symboli to proces mapowania identyfikatora w kodzie źródłowym na jego definicję. W środowisku jednojęzycznym proces ten jest dobrze zrozumiały: kompilator lub interpreter już go wykonuje, a środowiska IDE replikują tę logikę rozwiązywania, używając tej samej gramatyki i reguł systemu typów. Rozpoznawanie jest dokładne, ponieważ reguły są jednoznaczne w obrębie jednego języka.

Na granicy językowej rozwiązanie wymaga modelu pomostowego, który może reprezentować symbole z obu języków w ujednoliconej strukturze i śledzić połączenie od identyfikatora w języku A do odpowiadającej mu definicji w języku B. Taki pomost nie istnieje w standardowych środowiskach programistycznych (IDE) ani serwerach językowych, ponieważ protokół Language Server Protocol został zaprojektowany z założeniem, że każdy serwer językowy obsługuje jeden język. Gdy metoda Java wywołuje program COBOL poprzez zdefiniowany interfejs, serwer języka Java rozumie wywołanie metody, ale nie może rozwiązać celu COBOL. Programista widzi wywołanie, wie, że prowadzi ono do jakiegoś celu i nie może go śledzić bez całkowitego opuszczenia narzędzia.

Rozważmy przykład reprezentatywny. Usługa Java wywołuje program COBOL po nazwie za pośrednictwem warstwy pośredniczącej:

Jawa

// Java service calling a COBOL program via a legacy middleware adapter
LegacyAdapter.invoke("CUSTINQ", customerRequest);

Środowisko IDE Java rozwiązuje LegacyAdapter.invoke bez problemu. Zna sygnaturę metody i może przejść do jej implementacji. Ale "CUSTINQ" jest literałem ciągu na poziomie Javy. IDE nie ma pojęcia o nazwach programów COBOL, nie rozumie, że CUSTINQ Odnosi się do konkretnej skompilowanej jednostki programu z własnymi definicjami danych i strukturą akapitów. Nawigacja zatrzymuje się na ciągu znaków. Programista musi ręcznie zlokalizować kod źródłowy COBOL, otworzyć go w innym edytorze i rozpocząć czytanie bez żadnego kontekstu strukturalnego, dotyczącego relacji programu z wywołującym go kodem Java.

Konstrukcja grafu wywołań w heterogenicznych bazach kodu

Graf wywołań to struktura danych reprezentująca, które funkcje wywołują poszczególne funkcje w bazie kodu. Środowiska programistyczne (IDE) używają grafów wywołań do implementacji funkcji takich jak „znajdź wszystkich wywołujących” i „hierarchia wywołań”, które pokazują programiście każdą ścieżkę prowadzącą do danej funkcji i każdą funkcję, którą dana funkcja wywołuje. W środowisku jednojęzycznym tworzenie grafu wywołań jest naturalnym efektem ubocznym indeksowania bazy kodu.

W środowisku wielojęzycznym graf wywołań musi obejmować granice językowe, aby był kompletny. Graf wywołań, który kończy się w każdym punkcie, w którym wykonanie przechodzi do innego języka, nie jest grafem wywołań systemu; jest to zbiór grafów częściowych, po jednym na język, z rozłączonymi krawędziami na każdej granicy językowej. Dla programisty śledzącego ścieżkę wykonania w systemie, który łączy języki, oznacza to, że ślad kończy się za każdym razem, gdy ścieżka przekracza granicę językową, wymagając ręcznego kroku, aby wznowić go w kolejnym języku.

Problem jest szczególnie dotkliwy w środowiskach mainframe, gdzie pojedyncza transakcja biznesowa może obejmować JCL koordynujący sekwencję wykonywania, programy COBOL realizujące podstawową logikę biznesową oraz zapytania SQL odczytujące i zapisujące dane. Jak szczegółowo opisano w analizie Mapowanie JCL na COBOLTe trzy warstwy są ze sobą głęboko powiązane: JCL definiuje, co jest uruchamiane i w jakiej kolejności, COBOL definiuje, co robią programy, a SQL definiuje, do jakich danych uzyskują dostęp. Graf wywołań obejmujący tylko COBOL, JCL lub SQL opisuje fragment systemu, a nie sam system. Śledzenie czegokolwiek sensownego wymaga połączenia wszystkich trzech warstw w jednym modelu.

Śledzenie zależności, gdy języki współdzielą dane

Zależności między komponentami w systemie wielojęzycznym są często pośredniczone za pośrednictwem współdzielonych danych: tabeli bazy danych, którą zapisuje COBOL, a odczytuje Java, pliku generowanego przez zadanie wsadowe i pobieranego przez API, lub kolejki komunikatów, którą zapisuje producent Pythona, a odczytuje konsument Go. Te zależności pośredniczone przez dane są rzeczywiste i mają istotne konsekwencje. Zmiana schematu tabeli, formatu pliku lub struktury komunikatów wpływa zarówno na producenta, jak i konsumenta, ale nie są one reprezentowane w modelu zależności żadnego pojedynczego języka.

Śledzenie zależności w środowisku wielojęzycznym wymaga zatem zrozumienia nie tylko wywołań kod-kod, ale także relacji między danymi-kodem: które programy odczytują lub zapisują daną kolumnę tabeli, które usługi zależą od określonego formatu pliku, na których użytkowników wpływa zmiana schematu komunikatów. Ten rodzaj śledzenia całkowicie wykracza poza zakres standardowej nawigacji w środowisku IDE i wymaga narzędzia, które modeluje cały system, w tym warstwę danych, a nie traktuje kodu każdego języka w izolacji.

Konkretne sposoby, w jakie nawigacja zawodzi w typowych stosach wielojęzycznych

Tryby awarii nawigacji kodu między językami nie są abstrakcyjne. Pojawiają się w konkretnych, przewidywalnych sytuacjach, które rutynowo występują w środowiskach programistycznych przedsiębiorstw. Konkretna analiza tych sytuacji jasno pokazuje, dlaczego ogólne narzędzia wyszukiwania nie mogą zastąpić prawdziwej nawigacji międzyjęzykowej.

COBOL i Java: najczęstsza granica w przedsiębiorstwach

Granica między COBOL-em a Javą jest najczęstszą granicą językową w dużych systemach korporacyjnych, szczególnie w sektorze usług finansowych, ubezpieczeniowych i administracji publicznej. Dekady inwestycji w COBOL współistnieją z działaniami modernizacyjnymi Javy w architekturze hybrydowej, w której COBOL obsługuje przetwarzanie wsadowe, a Java przetwarzanie transakcji i interfejsy API. Te dwa języki komunikują się za pośrednictwem zdefiniowanych interfejsów: transakcji CICS, kolejek komunikatów, współdzielonych baz danych i przekazywania plików.

Przekroczenie tej granicy w praktyce ujawnia głębię problemu. Programista Javy badający nieoczekiwane zachowanie transakcji musi prześledzić ścieżkę wykonania do programu wsadowego COBOL, który przetworzył dane bazowe. Środowisko programistyczne Java IDE pokazuje, gdzie wywoływany jest interfejs. Nie pokazuje jednak, co program COBOL robi z danymi wejściowymi, jakie dane odczytuje, jakie obliczenia wykonuje ani co zapisuje. Programista potrzebuje wiedzy i narzędzi COBOL, aby kontynuować, a żadne z nich nie jest łatwo dostępne w zespole zorientowanym na Javę. Rezultatem jest albo powolne, ręczne dochodzenie, albo eskalacja do osoby posiadającej wymaganą wiedzę, co w obu przypadkach oznacza błędy w nawigacji, które kosztują czas i wydłużają czas trwania incydentu.

W przypadku języka COBOL podobny błąd występuje, gdy programista COBOL musi zrozumieć, które usługi Java pobierają dane generowane przez program COBOL. Standardowe narzędzia COBOL nie posiadają modelu kodu Java. Programista może zobaczyć dane wyjściowe programu COBOL, w tym zapis do bazy danych lub aktualizację pliku, ale nie może prześledzić tych danych, aby zidentyfikować, które usługi Java je odczytują. Każda zmiana formatu wyjściowego wymaga ręcznej koordynacji z zespołami programistów Java, ponieważ nie ma narzędzia, które automatycznie wyliczyłoby odbiorców. Modernizacja COBOL-a zależy przede wszystkim od rozwiązania właśnie tej luki: dopóki pełen łańcuch zależności nie będzie widoczny w obu językach, bezpieczna modernizacja nie będzie możliwa.

JCL i COBOL: orkiestracja bez widoczności

JCL to warstwa orkiestracji dla przetwarzania wsadowego na komputerach mainframe. Kontroluje ona, które programy są uruchamiane, w jakiej kolejności, z jakimi parametrami oraz dla jakich plików i zestawów danych są uruchamiane. Relacja między JCL a wywoływanymi przez niego programami COBOL jest fundamentalną zależnością strukturalną: zmiana JCL powoduje zmianę działania programów COBOL. Zmiana oczekiwanego formatu wejściowego programu COBOL może również wymagać zmiany zestawów danych JCL, które go zasilają.

Standardowe narzędzia analizy COBOL nie analizują kodu JCL. Standardowe narzędzia analizy JCL nie analizują kodu COBOL. Połączenie między krokiem JCL, który wywołuje PGM=CUSTINQ i program COBOL o nazwie CUSTINQ istnieje w działającym systemie, ale nie w modelu żadnego pojedynczego narzędzia. Programista korzystający z któregokolwiek z tych narzędzi w izolacji nie widzi pełnego obrazu. Wie, co krok JCL wywołuje po nazwie, ale nie wie, co program robi. Albo wie, co robi program COBOL, ale nie wie, jak jest wywoływany, z jakimi parametrami ani w jakiej kolejności w strumieniu zadań.

Ta luka stwarza szczególne ryzyko dla systemów produkcyjnych. Programista modyfikujący definicje pamięci roboczej programu COBOL może nieumyślnie zmienić sposób, w jaki program obsługuje dane przekazywane z określonego kroku JCL, bez żadnego narzędzia ostrzegającego o wpływie tej zmiany na zdefiniowany przez JCL kontekst wykonania. Programista restrukturyzujący procedurę JCL może zmienić kolejność uruchamiania programów, bez żadnego narzędzia wskazującego, które programy COBOL wymagają tej kolejności do poprawnego działania. Jak szczegółowo opisano w analizie Rozwiązania do analizy statycznej JCLAby uzyskać wgląd w zależności między programami i wykorzystanie zestawów danych w środowiskach JCL, konieczna jest specjalistyczna analiza, której standardowe narzędzia po prostu nie zapewniają.

Oto jak wygląda ta sama zależność z perspektywy każdego języka przy użyciu standardowych narzędzi w porównaniu z tym, co pokazałby ujednolicony model:

Co widzi deweloperWidok tylko dla JCLWidok tylko dla COBOL-aUjednolicony widok międzyjęzykowy
Wywołanie programuPGM=CUSTINQ (tylko nazwa)NiewidoczneCUSTINQ jest wywoływany przez 3 procedury JCL ze specyficznymi wartościami PARM
Zestawy danych wejściowychWymieniono nazwy DDNiewidoczneOdczytuje plik CUSTFILE (zdefiniowany w kroku 2 pliku CUSTMAST.JCL)
Zestawy danych wyjściowychWymieniono nazwy DDNiewidoczneZapisuje CUSTRPT (zużywany przez zadanie RPTPRT)
Logika biznesowaNiewidocznePODZIAŁ PROCEDUR widocznyPełny przepływ od wywołania JCL przez logikę COBOL do wyjścia
Wpływ zmianAutor nieznanyAutor nieznany4 procedury JCL, 2 programy COBOL, 1 tabela bazy danych

Nowoczesne stosy językowe: Python, Go i C# w usługach

W systemach rozproszonych zbudowanych z nowoczesnych języków problem nawigacji przybiera inną formę. Zamiast luki między językami COBOL i Java, wyzwaniem jest granica usług połączona ze stosem wielojęzycznym. Usługa przetwarzania danych w Pythonie zasila API Go, które z kolei zasila front-end C#. Każda usługa jest zbudowana z własnymi narzędziami, własną konfiguracją IDE i własnym modelem zależności. Połączenia między usługami istnieją na poziomie API, ale standardowe narzędzia nawigacyjne nie posiadają modelu relacji API między usługami.

Programista modyfikujący strukturę odpowiedzi w usłudze Python musi wiedzieć, od których pól zależy API Go, a które pola są ostatecznie wyświetlane przez front-end C#. Bez nawigacji międzyjęzykowej i międzyusługowej musi ręcznie sprawdzać kod każdej usługi podrzędnej, wyszukiwać odniesienia do odpowiednich nazw pól i mieć nadzieję, że konwencje nazewnictwa są wystarczająco spójne, aby wyszukiwanie było niezawodne. Jak omówiono w kontekście Narzędzia do analizy statycznej GoNawet w ramach pojedynczej usługi Go, zrozumienie hierarchii wywołań i śledzenie zależności między modułami nie jest trywialnym problemem. Jednoczesne rozszerzenie tego problemu na granice usług i języków jest o rząd wielkości trudniejsze.

Ten sam wzór dotyczy Systemy C# które wywołują usługi współdzielone napisane w Javie lub Potoki Pythona które zapisują dane w bazach danych wykorzystywanych przez aplikacje .NET. W każdym przypadku standardowe narzędzia dla każdego języka zapewniają precyzyjną nawigację w obrębie danego języka i nie generują żadnych przydatnych informacji na granicy, na której wykonywanie przechodzi do innego języka lub usługi.

SQL i kod aplikacji: niewidoczna warstwa danych

Język SQL jest obecny w niemal każdym systemie korporacyjnym, a mimo to jest najczęściej pomijanym elementem nawigacji międzyjęzykowej. Kod aplikacji tworzy zapytania SQL, które odwołują się do nazw tabel, nazw kolumn, warunków łączenia i procedur składowanych. Schemat bazy danych definiuje te tabele i kolumny. Relacja między kodem aplikacji a schematem bazy danych to zależność, która, jeśli zostanie zerwana przez zmianę schematu, powoduje błędy w czasie wykonywania. Jednak standardowe środowiska IDE traktują ciągi SQL jako ciągi, a nie jako kod o strukturze umożliwiającej nawigację.

Programista, który zmienia nazwę kolumny w schemacie, musi znaleźć każde odwołanie do tej kolumny w każdej aplikacji, w każdym języku, w każdym zapytaniu. Wyszukiwanie tekstowe nazwy kolumny jest zawodne: krótkie nazwy kolumn kolidują z nazwami zmiennych, komunikatami dziennika i komentarzami. Wyszukiwanie z uwzględnieniem symboli wymaga narzędzia, które modeluje zarówno schemat SQL, jak i kod aplikacji, który się do niego odwołuje, rozumiejąc, że "customer_id" w ciągu zapytania Java znajduje się odwołanie do kolumny bazy danych customer_idi może wyliczyć wszystkie takie odniesienia w różnych językach. Bez tego modelu zmiany schematu są czasochłonne i statystycznie niekompletne.

Dlaczego rozszerzenia IDE i serwery językowe nie mogą rozwiązać tego problemu

Rozszerzenia IDE i serwery językowe zostały zaprojektowane z myślą o zapewnieniu inteligencji specyficznej dla danego języka. Analizują one kod zgodnie z określoną gramatyką, budują indeks symboli specyficzny dla danego języka i obsługują zapytania za pośrednictwem protokołu Language Server Protocol, który definiuje standardowy interfejs dla funkcji językowych, takich jak „przejdź do definicji”, „znajdź referencje” i „wyświetlanie dokumentacji za pomocą kursora myszki”. Protokół jest niezależny od języka w warstwie transportowej, ale specyficzny dla języka w swojej treści: każdy serwer językowy generuje wyniki tylko dla swojego własnego języka.

Połączenie dwóch serwerów językowych w ramach tego samego środowiska IDE nie rozwiązuje problemu nawigacji międzyjęzykowej. Każdy serwer ma swój własny indeks. Gdy programista żąda „znajdź wszystkie referencje” dla symbolu, żądanie trafia do serwera językowego dla języka bieżącego pliku. Serwer ten zwraca referencje, o których wie, a które ograniczają się do plików, które zindeksował. Nie wysyła zapytania do drugiego serwera językowego, a nawet gdyby to robił, nie istniałby wspólny model symboli, za pomocą którego można by wyrazić relacje międzyjęzykowe.

Jest to ograniczenie strukturalne architektury LSP, a nie problem konfiguracyjny. Można je częściowo obejść w określonych, wąskich przypadkach, takich jak serwer językowy, który również analizuje inline SQL w ciągach znaków języka Python, ale nie można go uogólnić na dowolne zależności międzyjęzykowe bez zbudowania dokładnie takiego rodzaju zunifikowanego modelu wielojęzycznego, który wykracza poza to, do czego został zaprojektowany każdy serwer językowy. wyzwania, z jakimi mierzy się analiza statyczna w kontekście metaprogramowania w ramach jednego języka ilustrują głębię problemu: jeśli rozumowanie o dynamicznie generowanym kodzie w ramach jednego języka wymaga specjalistycznych technik, to rozumowanie w wielu językach z różnymi gramatykami i modelami środowiska wykonawczego wymaga zupełnie odmiennego podejścia architektonicznego.

Które serwery językowe działają dobrze (i gdzie się zatrzymują)

Serwery językowe doskonale radzą sobie z zadaniami, do których zostały zaprojektowane: diagnostyką w czasie rzeczywistym, inteligentnym uzupełnianiem, rozwiązywaniem symboli w jednym języku oraz refaktoryzacją w edytorze w ograniczonym zakresie. Te możliwości są cenne i nie należy ich ignorować. Problem nie polega na tym, że serwery językowe są niewystarczającymi narzędziami, ale na tym, że są narzędziami w jednym języku, stosowanymi do rozwiązywania problemów wielojęzycznych, a niedopasowanie prowadzi do przewidywalnych i kosztownych awarii dokładnie tam, gdzie precyzja ma największe znaczenie.

Poniższa tabela zestawia konkretne zadania nawigacyjne z tym, co oferują serwery językowe, i pokazuje, gdzie zaczyna się różnica:

Zadanie nawigacyjneLSP w jednym językuLSP ponad granicami językowymi
Przejdź do definicjiDokładnie, natychmiastNiepowodzenia: zatrzymuje się w miejscu połączenia
Znajdź wszystkie odniesieniaUzupełnij w plikach indeksowanychNiekompletne: brakuje odniesień w innych językach
Hierarchia połączeńDokładne dla osób dzwoniących w jednym językuUcięte: osoby dzwoniące na granicy są nieobecne
Zmień nazwę symboluBezpieczny w jednym językuNiebezpieczne: zmiana nazw, brak użycia w różnych językach
Analiza wpływuZakres zgodny z bieżącym językiemNiewidoczny dla odbiorców końcowych w innych językach

Grep i wyszukiwanie tekstowe: dlaczego nie są akceptowalnym zamiennikiem

Gdy serwery językowe zawodzą na granicy swoich możliwości, programiści sięgają po wyszukiwanie tekstowe. grep, wyszukiwanie na poziomie IDE i wyszukiwanie platformowe, takie jak GitHub Code Search, znajdują ciągi znaków w plikach bez względu na język. Nie znają pojęcia „symbolu” ani „referencji”, a jedynie wystąpienia ciągu znaków. W przypadku krótkich, wspólnych identyfikatorów oznacza to ogromne zbiory wyników, które wymagają ręcznego filtrowania. W przypadku identyfikatorów istniejących w wielu językach i o różnym znaczeniu, wyniki łączą różne elementy kodu, które akurat mają tę samą nazwę.

Bardziej niebezpieczne niż szum jest niekompletność. Wyszukiwanie tekstowe pomija odniesienia, w których konwencje nazewnictwa różnią się w zależności od języka, gdzie identyfikator jest tworzony dynamicznie, gdzie połączenie jest pośredniczone przez konfigurację lub rejestr nazw, lub gdzie relacja jest wyrażana za pomocą danych, a nie bezpośredniego odniesienia do kodu. Luki te nie są widoczne w wynikach wyszukiwania: programista widzi to, co zostało znalezione, nie ma możliwości dowiedzenia się, co zostało pominięte, i podejmuje decyzje w oparciu o niepełny obraz, który wydaje się kompletny. W szerszym kontekście statyczna analiza kodu pod kątem łatwości utrzymania, niemożność dokładnego zrozumienia, co robi kod i z czym się łączy, to nie jest drobna niedogodność, która jest główną przyczyną gromadzenia się długu technicznego, usterek ujawnianych podczas konserwacji i rosnących kosztów bezpiecznego wprowadzania zmian.

Koszty operacyjne kumulujące się na granicach językowych

Opisane powyżej awarie nawigacji nie ujawniają się jako problemy z pojedynczym incydentem. Kumulują się one w każdym zadaniu, u każdego programisty i w każdym zespole działającym w środowisku wielojęzycznym. Zrozumienie kosztów wymaga analizy powtarzających się sytuacji, w których nawigacja zawodzi, i obliczenia łącznego efektu.

Wdrażanie zespołów poliglotycznych trwa znacznie dłużej

Programista dołączający do zespołu pracującego w jednym języku i z jedną bazą kodu może stosunkowo szybko osiągnąć produktywność. IDE obsługuje nawigację, kod dokumentuje się samoczynnie dzięki swojej strukturze, a model mentalny budowany przez programistę odzwierciedla rzeczywisty system. Programista dołączający do zespołu pracującego w wielu językach staje w obliczu zupełnie innej sytuacji. Narzędzia nie poruszają się po granicach, dlatego model mentalny musi zostać zbudowany ręcznie poprzez dokumentację, programowanie w parach oraz metodę prób i błędów.

Ręczne budowanie modelu zajmuje tygodnie, a nie dni. Programista musi nauczyć się nie tylko kodu w swoim języku ojczystym, ale także wystarczająco dużo o językach pokrewnych, aby zrozumieć, jak się je nazywa, jak się je nazywa i jakie dane przepływają przez granice. W dużych organizacjach z dużą rotacją lub częstą rotacją zespołów, ten wydłużony czas wdrożenia jest kosztem cyklicznym, a nie jednorazową inwestycją. Każda osoba dołączająca do zespołu poliglotów ponosi pełny koszt rekonstrukcji wielojęzykowego modelu mentalnego od podstaw, ponieważ narzędzia nie oferują niczego, co pozwoliłoby na jego dalszy rozwój.

Incydenty produkcyjne trwają dłużej, gdy ślady przekraczają granice językowe

Gdy incydent produkcyjny wymaga śledzenia ścieżki wykonania, która przekracza granice językowe, każde przekroczenie granicy jest krokiem manualnym. Programista dyżurny, działający pod presją czasu, musi zmieniać narzędzia, przeszukiwać tekst w bazie kodu innego języka i ręcznie łączyć wyniki ze tworzonym śladem. W systemie z trzema lub czterema warstwami językowymi, pełne badanie przyczyn źródłowych może wymagać czterech lub pięciu takich przekroczeń granic, z których każde wydłuża czas badania, mierzony czasem oddziaływania na użytkowników.

Kumulatywnym efektem w organizacji obsługującej wiele usług wielojęzycznych jest systematycznie wydłużany średni czas rozwiązywania każdego incydentu przekraczającego granicę językową. Nie jest to wina poszczególnych programistów, lecz strukturalna konsekwencja narzędzi, które nie modelują rzeczywistych powiązań w systemie. Organizacje, które zainwestowały w widoczność wielojęzyczną, konsekwentnie zgłaszają szybsze rozwiązywanie incydentów jako jedną z najbardziej bezpośrednich i mierzalnych korzyści, właśnie dlatego, że inwestycja ta eliminuje konieczność ręcznego przekraczania granic, co wydłuża czas dochodzenia.

Ryzykowne zmiany stają się bardziej ryzykowne bez widoczności ich wpływu w różnych językach

Każda zmiana współdzielonego kodu w systemie wielojęzycznym niesie ze sobą nieokreślone ryzyko, dopóki nie zostanie poznany pełny zestaw konsumentów we wszystkich językach. Bez nawigacji międzyjęzykowej ryzyko to nie jest określone przed wprowadzeniem zmiany. Jest ono odkrywane później, gdy niedziałający konsumenci pojawiają się w fazie testowania lub, co gorsza, w środowisku produkcyjnym. Nie jest to rzadki tryb awarii. Jest to standardowy wynik utrzymywania współdzielonych struktur danych, interfejsów lub narzędzi w systemie, w którym konsumenci w dalszej części kodu posługują się różnymi językami.

Konserwatywną odpowiedzią na tę niepewność jest nadmierna ostrożność: większe nakłady na testy, dłuższe cykle przeglądów, więcej spotkań koordynacyjnych i częstsze zamrożenie zmian w okresach krytycznych. Wszystkie te czynniki stanowią realne koszty, które kumulują się w każdym cyklu zmian w systemie wielojęzycznym. Reprezentują one czas i wysiłek poświęcony na kompensację braku nawigacji wielojęzycznej, a nie na inwestowanie w dostarczanie wartości. krajobraz modernizacji dziedzictwa jest w dużej mierze kształtowany przez te skumulowane koszty: organizacje decydują się na modernizację, ponieważ utrzymanie istniejących systemów stało się zbyt drogie, a awarie nawigacji międzyjęzykowej są głównym czynnikiem wpływającym na koszty utrzymania.

Czego tak naprawdę wymaga nawigacja międzyjęzykowa

Rozwiązanie problemu nawigacji w kodzie w wielu językach wymaga zbudowania ujednoliconego modelu, którego serwery językowe nie są w stanie zapewnić osobno. Model ten musi spełniać szereg wymagań, które są niezbędnymi warunkami dla użytecznej nawigacji międzyjęzykowej, a nie opcjonalnymi ulepszeniami.

Pojedynczy, współdzielony indeks symboli obejmujący wszystkie języki. Każdy nazwany element w każdym języku, w tym funkcje, klasy, pola, procedury, tabele i definicje danych, musi być reprezentowany w jednym indeksie ze wspólnym modelem tożsamości. Tożsamość symbolu nie może być specyficzna dla danego języka, jeśli odwołania międzyjęzykowe mają być na jego podstawie rozstrzygane.

Parsery rozpoznające język dla każdego języka w systemie. Każdy język musi być analizowany z wykorzystaniem własnej gramatyki, a nie aproksymowany przez parser generyczny lub dopasowywanie wzorców. Strukturalne dane wyjściowe każdego parsera muszą być odwzorowywane na wspólny model tożsamości, aby relacje międzyjęzykowe można było wyrazić jako połączenia między poprawnie zidentyfikowanymi symbolami.

Jawne modelowanie interfejsów międzyjęzykowych. Mechanizmy, za pomocą których różne języki wchodzą ze sobą w interakcje, w tym wywołania programów według nazwy, tabele bazy danych, formaty plików, schematy wiadomości i kontrakty API, muszą być przedstawione w modelu jako typy połączeń pierwszej klasy, a nie traktowane jako nieprzejrzyste ciągi znaków lub całkowicie pomijane w modelu.

Śledzenie zależności uwzględniające relacje na warstwie danych. Model musi odzwierciedlać nie tylko wywołania kodu z kodem, ale także zależności przekazywane za pomocą danych, ponieważ w systemach wielojęzycznych dane są często podstawowym medium, za pośrednictwem którego dane wyjściowe w jednym języku stają się danymi wejściowymi w innym języku.

Wydajność zapytań obsługująca interaktywną nawigację. Indeks musi obsługiwać odpowiedź na zapytanie w czasie krótszym niż sekunda dla typowych operacji nawigacyjnych. Model wymagający analiz wsadowych zamiast zapytań interaktywnych jest przydatny do analizy wpływu offline, ale nie może zastąpić nawigacji w czasie rzeczywistym podczas aktywnego rozwoju.

Wymagania te opisują platformę Enterprise Code Intelligence, a nie rozszerzenie IDE ani serwer językowy. Zbudowanie i utrzymanie takiej platformy stanowi techniczną podstawę praktycznego działania nawigacji w kodzie wielojęzycznym. Alternatywa, czyli akceptacja błędów nawigacji i ponoszenie ich kosztów w nieskończoność, staje się mniej realna wraz ze wzrostem rozmiaru i złożoności systemu wielojęzycznego.

W jaki sposób SMART TS XL Adresy nawigacji wielojęzycznej

SMART TS XL opiera się na założeniu, że systemów korporacyjnych nie da się zrozumieć przez pryzmat jednego języka ani jednego repozytorium. Platforma Software Intelligence pobiera kod źródłowy z każdego języka i platformy w środowisku, analizuje go za pomocą analizy specyficznej dla danego języka i buduje ujednolicony indeks referencyjny, który reprezentuje relacje między elementami, niezależnie od języka, do którego należą. Zapytania nawigacyjne skierowane do tego indeksu zwracają wyniki wykraczające poza granice językowe, ponieważ indeks modeluje cały system, a nie jego wycinek specyficzny dla danego języka.

Platforma jawnie modeluje interfejsy międzyjęzykowe ignorowane przez standardowe narzędzia. Krok JCL wywołujący program COBOL po nazwie jest reprezentowany jako zależność na grafie odsyłaczy, łącząc krok JCL z jednostką programu COBOL. Metoda Java zapisująca dane do tabeli bazy danych jest reprezentowana jako zależność danych łącząca kod Java z definicją tabeli, a następnie z dowolnym innym językiem odczytującym tę samą tabelę. Kopibook COBOL, do którego odwołuje się wiele programów, jest reprezentowany jako współdzielona definicja, dzięki czemu każda zmiana w strukturze kopibooka natychmiast wyświetla wszystkie programy, których ta zmiana dotyczy, niezależnie od języka. To jawne modelowanie zależności międzyjęzykowych odróżnia prawdziwą platformę nawigacyjną dla wielu języków od zbioru narzędzi specyficznych dla danego języka działających równolegle.

SMART TS XLMożliwość analizy wpływu platformy udowadnia praktyczną wartość tego zunifikowanego modelu. Gdy programista musi zrozumieć konsekwencje zmiany współdzielonego komponentu, takiego jak definicja danych w języku COBOL, element schematu bazy danych, interfejs Java lub procedura JCL, platforma śledzi graf zależności od tego komponentu we wszystkich językach i zwraca pełny obraz tego, na co zmiana wpłynie. Wynik jest prezentowany w postaci nawigowalnego raportu uporządkowanego według języka, komponentu i konkretnej lokalizacji odniesienia, dzięki czemu programiści mają dostęp do pełnych informacji przed wprowadzeniem zmiany, zamiast odkrywać jej konsekwencje dopiero później. Ta funkcja bezpośrednio odnosi się do akumulacji ryzyka opisanej w poprzedniej sekcji, przekształcając nieokreślone ryzyko międzyjęzykowe w skwantyfikowany, policzalny wpływ.

Nawigacja międzyjęzykowa jako właściwość całego systemu

Głównym wnioskiem płynącym z tego artykułu jest to, że nawigacja w kodzie w środowiskach wielojęzycznych jest cechą całego systemu, a nie pojedynczego narzędzia językowego. Środowisko programistyczne (IDE), które doskonale porusza się w języku COBOL, i oddzielne środowisko programistyczne (IDE), które doskonale porusza się w języku Java, nie tworzą razem systemu, który porusza się na granicy między COBOL-em a Javą. Tworzą one dwa niezależne systemy nawigacji z przerwą między nimi, a ta przerwa to właśnie tam, gdzie znajdują się najważniejsze relacje w systemie.

Zniwelowanie tej luki wymaga innego rodzaju narzędzia: takiego, które modeluje system jako całość, reprezentuje relacje między językami jako jednostki najwyższej klasy i zapewnia nawigację, która podąża za tymi relacjami, dokądkolwiek one prowadzą. Dla organizacji zarządzających złożonymi, wielojęzycznymi systemami w skali korporacyjnej, taka możliwość nie jest luksusem. Każdy dzień rozwoju bez niej to dzień, w którym kumulują się koszty niepowodzeń nawigacji międzyjęzykowej: w postaci wolniejszego wdrażania, dłuższych incydentów, bardziej ryzykownych zmian i stopniowej koncentracji niezastąpionej wiedzy u osób, które ręcznie zbudowały mentalne modele międzyjęzykowe, czego narzędzia nie są w stanie zapewnić.