Przejdź do głównej zawartości

Strategie dla milionowych linii kodu

Odziedziczyłeś trzymilionowy monolit. Pierwotni architekci odeszli dwa lata temu, dokumentacja opisuje system, który już nie istnieje, a twój pierwszy ticket dotyka klasy PaymentProcessor, którą importuje kilkanaście innych usług. Wrzucenie całości do asystenta AI po prostu przepełnia okno kontekstu i produkuje pewne siebie bzdury. Ten przewodnik pokazuje przepływy pracy, które naprawdę skalują się: wyszukiwanie semantyczne zamiast grepa, kontekst warstwowy zamiast „przeczytaj wszystko” i refactoring przyrostowy zamiast przepisywania na jeden raz.

  • Konfigurację MCP do wyszukiwania semantycznego (Zilliz Claude Context) podłączoną do Cursora, Claude Code i Codeksa, dzięki czemu AI znajduje kod według intencji, a nie dopasowania ciągu znaków
  • Wielokrotnego użytku prompt do rekonesansu architektury mapujący nieznaną bazę kodu z góry na dół
  • Technikę hierarchii kontekstu, która utrzymuje skupienie AI na właściwych 20% zamiast dławienia się całym repozytorium
  • Gotowy do skopiowania prompt do analizy wpływu zależności, pozwalający planować zmiany łamiące bez zaskakiwania innych zespołów
  • Prompt w stylu strangler fig do bezpiecznego opakowywania i dekompozycji kodu legacy
  • Konkretne kroki naprawcze na wypadek, gdy indeks się zdezaktualizuje, AI zacznie halucynować liczbę zależności albo równoległy refactoring się zderzy

Semantycznie, nie tekstowo

Dzięki indeksowi wektorowemu zapytanie „znajdź wszystkie przepływy uwierzytelniania” wydobywa kod OAuth, JWT i sesji, nawet gdy żaden z nich nie dzieli słowa kluczowego.

Śledzenie zależności

AI podąża za importami i miejscami wywołań przez granice modułów znacznie szybciej, niż zdołasz klikać przez „znajdź użycia”.

Testy charakteryzacyjne

Dla nieudokumentowanego kodu legacy AI tworzy testy, które przypinają obecne zachowanie, żebyś mógł refaktoryzować bez strachu.

To ty pozostajesz architektem

AI wykonuje mechaniczne skanowanie i boilerplate. To ty podejmujesz decyzje domenowe i architektoniczne, których ono podjąć nie potrafi.

Wyszukiwanie tekstowe zawodzi w skali, bo powiązany kod rzadko dzieli słownictwo. Indeks semantyczny zbudowany na embeddingach wektorowych to naprawia. Utrzymywany serwer to Zilliz Claude Context (@zilliz/claude-context-mcp — wcześniej publikowany jako code-context). Konfiguracja MCP jest niemal identyczna we wszystkich trzech narzędziach; różni się tylko polecenie rejestracji.

Dodaj do ~/.cursor/mcp.json:

{
"mcpServers": {
"claude-context": {
"command": "npx",
"args": ["-y", "@zilliz/claude-context-mcp@latest"],
"env": {
"EMBEDDING_PROVIDER": "OpenAI",
"OPENAI_API_KEY": "your-api-key",
"MILVUS_TOKEN": "your-zilliz-key"
}
}
}
}

Po zaindeksowaniu pytasz o koncepcje, a serwer zwraca odpowiednie pliki niezależnie od nazewnictwa. Dla wrażliwych baz kodu, które nie mogą sięgnąć do chmurowego API embeddingów, LuotoCompany/cursor-local-indexing uruchamia lokalny indeks ChromaDB i udostępnia go przez lokalny endpoint SSE:

Dodaj do ~/.cursor/mcp.json:

{
"mcpServers": {
"workspace-code-search": {
"url": "http://localhost:8978/sse"
}
}
}

To utrzymuje kod źródłowy na twojej własnej infrastrukturze — właściwy wybór dla usług finansowych, opieki zdrowotnej czy pracy w sektorze obronnym, gdzie kod nie może opuścić sieci.

Od czego zacząć z nieznanym monolitem? Z góry na dół. Skłoń AI do zbudowania modelu mentalnego, zanim czegokolwiek dotkniesz, a potem zagłęb się w obszar, którego naprawdę dotyczy twój ticket.

Agent Cursora sam zbiera kontekst z zaindeksowanej bazy kodu — wystarczy opisać, czego chcesz. Użyj @Folders, aby zawęzić pytanie do jednego obszaru, i @Code, aby wskazać konkretny fragment:

@Folders services/auth
Explain the authentication and authorization architecture: where tokens
are issued, how refresh works, and which services validate them.

Aby uzyskać precyzyjne odniesienie, zaznacz funkcję w edytorze i dodaj ją przez @Code, zanim poprosisz agenta o prześledzenie miejsc jej wywołań.

Największy błąd przy dużych bazach kodu to ładowanie wszystkiego naraz. Twój asystent nie potrzebuje wszystkich trzech milionów linii — potrzebuje właściwego wycinka we właściwym momencie. Pomyśl o tym jak o przybliżaniu na mapie: kontynent, kraj, miasto, ulica.

  1. Poziom domeny (widok z 10 000 stóp)

    What are the main bounded contexts in this system, and how do the payment,
    user, and inventory domains interact?
  2. Poziom usługi (widok z 1000 stóp)

    Within the payment domain, explain the service architecture and the main
    APIs each service exposes.
  3. Poziom komponentu (widok ze 100 stóp)

    Show me how PaymentProcessor handles credit-card transactions and what its
    retry strategy is for failed charges.
  4. Poziom implementacji (poziom gruntu)

    In PaymentProcessor.processCard(), why is there a 30-second timeout, and is
    the synchronized block safe to remove?

Każde narzędzie ma własny mechanizm zawężania tego, co widzi AI. Zasada jest identyczna: ładuj wąsko, rozszerzaj tylko, gdy odpowiedź tego wymaga.

Zawężaj za pomocą @Folders i @Code, a stałe reguły zakoduj jako Project Rule, żeby nie powtarzać ich w każdym prompcie:

# In .cursor/rules/payment.mdc (a Project Rule with glob: services/payment/**)
When working with payment code:
- All monetary amounts are integer cents — never floats
- Mutations require an idempotency key
- Never log full card numbers (PCI)
- Add audit logging for every state transition

Dla prostych projektów AGENTS.md w katalogu głównym repozytorium sprawdza się jako prostsza alternatywa dla strukturalnych reguł.

Refactoring milionowej bazy kodu to jak remont szpitala podczas trwającej operacji — nie możesz wszystkiego wyłączyć. Wzorzec, który działa: odkryj, ustanów szablon, migruj małymi partiami, zweryfikuj.

Weźmy bazę kodu Node.js wciąż naszpikowaną callbackami error-first. Ręczna migracja do async/await zajęłaby miesiące. Zamiast tego skłoń AI do skategoryzowania pracy według ryzyka, a potem wygenerowania jednej wielokrotnego użytku transformacji na kategorię:

// Before — error-first callback
function loadUser(id, callback) {
db.query('SELECT * FROM users WHERE id = ?', [id], (err, rows) => {
if (err) return callback(err);
callback(null, rows[0]);
});
}
// After — async, with a backward-compatible callback shim
async function loadUser(id, callback) {
try {
const rows = await db.query('SELECT * FROM users WHERE id = ?', [id]);
if (callback) return callback(null, rows[0]);
return rows[0];
} catch (err) {
if (callback) return callback(err);
throw err;
}
}

Shim pozwala wywołującym migrować we własnym tempie. Stosuj transformację katalog po katalogu, uruchamiaj istniejące testy po każdej partii i śledź postęp — nigdy nie transformuj całego drzewa w jednym przebiegu.

Przy dużym przedsięwzięciu rozłożonym na zespół skłoń AI do podzielenia pracy tak, by zminimalizować konflikty między zespołami, a potem pilnuj uczciwości gałęzi:

  1. Podziel według granic zależności

    Analyze module dependencies and propose how to split this refactor across
    four developers so their territories barely overlap. Flag any shared files
    that two teams would both need to edit.
  2. Gałąź na terytorium

    Okno terminala
    git checkout -b refactor/user-services
    git checkout -b refactor/payment-services
    git checkout -b refactor/shared-utils
  3. Wykrywaj kolizje wcześnie

    Review the diffs across all refactor/* branches and identify conflicting
    or breaking changes between teams before we attempt to merge.

Każda duża baza kodu ma warstwy archeologiczne — kod z różnych epok i filozofii, część z niego sprzed czasów obecnego zespołu. Klasyczny koszmar: 15 000-liniowa procedura składowana, której nikt nie rozumie, a która wciąż codziennie przetwarza prawdziwe pieniądze.

Wzorzec strangler fig pozwala modernizować bez przepisywania: opakuj kod legacy za czystym interfejsem, a potem wyodrębniaj fragmenty po jednym, uruchamiając stary i nowy równolegle, aż zaufasz nowej ścieżce.

Gdy dokumentacja nie istnieje, testy stają się dokumentacją. Poproś AI o napisanie testów charakteryzacyjnych, które przypną obecne zachowanie — łącznie z dziwnymi fragmentami — tak by każda przyszła zmiana zmieniająca wyjście zawodziła głośno:

describe('Legacy OrderProcessor — current behavior', () => {
it('returns status code 1 on a standard single-item order', async () => {
const result = await processOrder({
customerId: 123,
items: [{ sku: 'WIDGET-1', quantity: 1 }],
});
expect(result.status).toBe(1); // 1 = success (undocumented magic number)
expect(result.orderId).toMatch(/^ORD-\d{8}$/);
});
it('returns -99 when inventory is unavailable', async () => {
const result = await processOrder({
customerId: 123,
items: [{ sku: 'OUT-OF-STOCK', quantity: 1 }],
});
expect(result.status).toBe(-99); // -99 = inventory error
});
});

W milionowej bazie kodu różne zespoły posiadają różne terytoria. Najtrudniejsze jest wprowadzenie zmiany przekraczającej granicę bez zepsucia czegoś komuś innemu. Przed każdą zmianą łamiącą uzyskaj raport wpływu.

Połącz to z automatycznie generowanymi kontraktami. Poproś AI o wytworzenie specyfikacji OpenAPI i schematów zdarzeń dla usługi, z której korzysta inny zespół — to zamienia „idź przeczytaj nasz kod” w stabilną granicę, względem której mogą się integrować bez grzebania w twoich wnętrznościach.

Przepływy pracy AI w dużych bazach kodu zawodzą na konkretne, rozpoznawalne sposoby. Poznaj sposób naprawy każdego z nich.

  • Jakość kodu na skalę — utrzymywanie spójnych standardów, gdy zmieniasz kod w całym monolicie
  • Zarządzanie monorepo — czyste zawężanie kontekstu AI, gdy wiele pakietów dzieli jedno repozytorium
  • Testy jednostkowe z AI — przekształcanie testów charakteryzacyjnych w prawdziwą siatkę bezpieczeństwa
  • Niezbędne serwery MCP — podłączanie serwerów MCP do bazy danych, gita i przeglądarki, by rozszerzyć te przepływy pracy