Przejdź do głównej zawartości

Refaktoryzacja na dużą skalę

Refaktoryzacja to sztuka poprawiania struktury kodu bez zmiany jego zachowania. To miejsce, gdzie dług techniczny spotyka się ze swoim rozrachunkiem, gdzie legacy systemy otrzymują drugie życie, a gdzie Claude Code naprawdę błyszczy. Ta lekcja bada, jak wykorzystać pomoc sztucznej inteligencji do refaktoryzacji na dużą skalę, która ręcznie zajęłaby miesiące.

Scenariusz: Odziedziczyłeś 5-letnią platformę e-commerce. 200 000 linii kodu. jQuery spaghetti we frontend. PHP 5.6 w backend. Brak testów. Niespójne wzorce. Biznes chce dodać funkcje w czasie rzeczywistym, ale dotknięcie czegokolwiek grozi zepsuciem wszystkiego. Brzmi znajomo?

Miesiąc 1: Analiza i planowanie
- Ręczny przegląd kodu
- Dokumentowanie wszystkich zależności
- Tworzenie roadmapy refaktoryzacji
Miesiąc 2-3: Pisanie testów dla krytycznych ścieżek
- Ręczne pisanie przypadków testowych
- Nadzieja, że złapiesz edge cases
Miesiąc 4-6: Stopniowa refaktoryzacja
- Aktualizacja jednego modułu na raz
- Naprawianie błędów w miarę pojawiania się
- Modlitwa, że nic nie umknie
Miesiąc 7+: Ciągłe poprawki i regresje
  1. Utwórz sieć bezpieczeństwa

    > Zanim zaczniemy refaktoryzację:
    > 1. Utwórz kompleksowy zestaw testów dla obecnego zachowania
    > 2. Skonfiguruj git hooks dla testowania pre-commit
    > 3. Skonfiguruj CI/CD do łapania regresji
    > 4. Udokumentuj obecne zachowanie
  2. Przeanalizuj obecny stan

    > Przeanalizuj naszą bazę kodu i utwórz plan refaktoryzacji:
    > - Zidentyfikuj code smells
    > - Znajdź zduplikowany kod
    > - Zmapuj zależności
    > - Wylistuj przestarzałe wzorce
    > - Oszacuj złożoność dla każdego modułu
  3. Skonfiguruj infrastrukturę refaktoryzacji Utwórz .claude/commands/refactor-safely.md:

    Refaktoryzuj $ARGUMENTS zgodnie z tymi zasadami:
    1. Utrzymaj dokładną funkcjonalność
    2. Pisz testy przed zmianą kodu
    3. Aktualizuj jeden wzorzec naraz
    4. Uruchamiaj testy po każdej zmianie
    5. Twórz opisowe commity
    6. Aktualizuj dokumentację
  4. Skonfiguruj podejście przyrostowe

    {
    "refactoring": {
    "batchSize": 10,
    "testFirst": true,
    "preserveComments": true,
    "updateDocs": true,
    "commitAfterEach": true
    }
    }

Od chaosu jQuery do nowoczesnego React/Vue/Angular

> Przeanalizuj naszą bazę kodu jQuery i utwórz plan modernizacji.
> Chcemy migrować do React stopniowo zachowując funkcjonalność aplikacji.

Strategia przyrostowa Claude:

Faza 1: Przygotowanie

// 1. Utwórz warstwę abstrakcji
class LegacyBridge {
static $(selector) {
// Owinięcie jQuery dla stopniowego zastąpienia
return {
original: $(selector),
onClick: (handler) => {
$(selector).on('click', handler);
// Śledzenie do migracji
this.trackUsage('click', selector);
}
};
}
}
// 2. Zastąp bezpośrednie wywołania jQuery
// Przed: $('#button').click(handler)
// Po: LegacyBridge.$('#button').onClick(handler)

Faza 2: Wyodrębnianie komponentów

// Identyfikuj wzorce UI w jQuery
> Znajdź wszystkie wzorce jQuery UI i wyodrębnij je jako komponenty
// Claude identyfikuje wzorce jak:
$('.user-card').each(function() {
$(this).find('.name').text(userData.name);
$(this).find('.email').text(userData.email);
});
// Konwertuje na komponent React:
const UserCard = ({ user }) => (
<div className="user-card">
<div className="name">{user.name}</div>
<div className="email">{user.email}</div>
</div>
);

Faza 3: Zarządzanie stanem

// Z rozproszonego stanu jQuery
var userLoggedIn = false;
$('#login').click(function() {
userLoggedIn = true;
updateUI();
});
// Do scentralizowanego stanu
const useAuthStore = create((set) => ({
isLoggedIn: false,
login: () => set({ isLoggedIn: true }),
logout: () => set({ isLoggedIn: false })
}));

Faza 4: Kompletna migracja

// Końcowe czyszczenie
> Usuń zależności jQuery
> Zaktualizuj system budowania
> Usuń kod legacy bridge
> Optymalizuj rozmiar bundle
// Wynik: Nowoczesna aplikacja React
// 60% mniejszy bundle
// 3x szybsze renderowanie
// Bezpieczne typy z TypeScript

Od PHP 5.6 spaghetti do nowoczesnego PHP 8+ lub Node.js

> Nasz backend to PHP 5.6 z:
> - Brakiem organizacji namespace
> - Mieszanymi plikami HTML/PHP
> - Bezpośrednimi zapytaniami MySQL (bez ORM)
> - Zmiennymi globalnymi wszędzie
>
> Utwórz plan modernizacji do PHP 8.3 z Laravel

Systematyczne podejście Claude:

// Przed: styl PHP 5.6
function calculateTotal($items, $tax) {
$total = 0;
foreach ($items as $item) {
$total += $item['price'] * $item['quantity'];
}
return $total + ($total * $tax);
}
// Po: PHP 8.3 z typami
declare(strict_types=1);
function calculateTotal(
array $items,
float $tax
): float {
$subtotal = array_reduce(
$items,
fn($carry, $item) => $carry + ($item->price * $item->quantity),
0.0
);
return $subtotal * (1 + $tax);
}

Refaktoryzacja struktury bazy danych bez przestoju

> Musimy zrefaktoryzować naszą bazę danych:
> - tabela users ma 80 kolumn (zdenormalizowana)
> - Brak kluczy obcych lub ograniczeń
> - Niespójne nazewnictwo (userId vs user_id)
> - Brak indeksów poza kluczami głównymi
>
> Zaplanuj stopniową migrację do znormalizowanego schematu
  1. Utwórz strategię migracji

    -- Faza 1: Dodaj nowe znormalizowane tabele obok starych
    CREATE TABLE user_profiles (
    id BIGINT PRIMARY KEY,
    user_id BIGINT NOT NULL,
    first_name VARCHAR(100),
    last_name VARCHAR(100),
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    FOREIGN KEY (user_id) REFERENCES users(id)
    );
    CREATE TABLE user_addresses (
    id BIGINT PRIMARY KEY,
    user_id BIGINT NOT NULL,
    type ENUM('billing', 'shipping'),
    street VARCHAR(255),
    city VARCHAR(100),
    -- ... więcej pól
    FOREIGN KEY (user_id) REFERENCES users(id),
    INDEX idx_user_type (user_id, type)
    );
  2. Zaimplementuj wzorzec dual-write

    class UserService {
    public function updateProfile($userId, $data) {
    // Pisz do obu starych i nowych struktur
    DB::transaction(function() use ($userId, $data) {
    // Zapis legacy
    DB::table('users')->where('id', $userId)->update([
    'fname' => $data['first_name'],
    'lname' => $data['last_name']
    ]);
    // Zapis nowej struktury
    UserProfile::updateOrCreate(
    ['user_id' => $userId],
    [
    'first_name' => $data['first_name'],
    'last_name' => $data['last_name']
    ]
    );
    });
    }
    }
  3. Migruj dane stopniowo

    > Utwórz job w tle do migracji danych użytkowników:
    > - Przetwarzaj w batches po 1000
    > - Śledź postęp
    > - Weryfikuj integralność danych
    > - Obsługuj niepowodzenia z gracją
  4. Przełącz odczyty na nowy schemat

    // Kontrola przez feature flag
    if (Feature::enabled('use_normalized_schema')) {
    return User::with(['profile', 'addresses'])->find($id);
    } else {
    return DB::table('users')->where('id', $id)->first();
    }
  5. Dokończ migrację

    -- Po weryfikacji, usuń stare kolumny
    ALTER TABLE users
    DROP COLUMN fname,
    DROP COLUMN lname,
    DROP COLUMN address1,
    -- ... itd

Wyzwanie: Wyodrębnij serwisy z monolitycznej aplikacji Rails

> Nasz monolit Rails obsługuje:
> - Uwierzytelnianie użytkowników
> - Katalog produktów
> - Przetwarzanie zamówień
> - Powiadomienia e-mail
> - Raportowanie
>
> Zaplanuj wyodrębnienie przetwarzania zamówień do mikroserwisu

Strategia wyodrębniania Claude:

# Claude analizuje i znajduje:
> Analizuję granice przetwarzania zamówień...
Znaleziono kod związany z zamówieniami w:
- app/models/order.rb (i 12 powiązanych modeli)
- app/controllers/orders_controller.rb (i 4 powiązane)
- app/services/order_service.rb (i 8 powiązanych)
- lib/payment_gateway.rb
- app/jobs/order_notification_job.rb
Wspólne zależności:
- Model User (dla informacji o kliencie)
- Model Product (dla katalogu)
- Serwis powiadomień

Przykład 2: Refaktoryzacja architektury asynchronicznej

Dział zatytułowany „Przykład 2: Refaktoryzacja architektury asynchronicznej”

Wyzwanie: Konwertuj operacje synchroniczne na architekturę event-driven

> Nasz system robi wszystko synchronicznie:
> - Wysyłaj e-mail po zamówieniu
> - Aktualizuj inwentarz natychmiast
> - Obliczaj analityki w żądaniu
> - Generuj PDF podczas wywołania API
>
> Refaktoryzuj na wzorzec asynchroniczny event-driven

Claude implementuje refaktoryzację event-driven:

// Przed: Sprzężenie synchroniczne
async function createOrder(orderData: OrderData) {
const order = await db.orders.create(orderData);
// Wszystko to blokuje odpowiedź
await sendOrderConfirmationEmail(order);
await updateInventory(order.items);
await calculateAnalytics(order);
await generateInvoicePDF(order);
return order;
}
// Po: Event-driven
async function createOrder(orderData: OrderData) {
const order = await db.orders.create(orderData);
// Opublikuj event i zwróć natychmiast
await eventBus.publish('order.created', {
orderId: order.id,
customerId: order.customerId,
items: order.items,
total: order.total
});
return order;
}
// Oddzielne handlery przetwarzają asynchronicznie
eventBus.subscribe('order.created', async (event) => {
await Promise.all([
emailService.sendOrderConfirmation(event),
inventoryService.updateStock(event.items),
analyticsService.recordOrder(event),
pdfService.generateInvoice(event.orderId)
]);
});
> Przeskanuj naszą bazę kodu w poszukiwaniu anti-wzorców i utwórz poprawki:
> - God objects (klasy robiące za dużo)
> - Shotgun surgery (zmiany wymagają wielu edycji)
> - Feature envy (metody używające danych innych klas)
> - Data clumps (te same parametry wszędzie)

Wykrywanie wzorców i poprawki Claude:

Poprawka God Object

// Wykryto: UserService z 47 metodami
class UserService {
// Metody uwierzytelniania
login() {}
logout() {}
resetPassword() {}
// Metody profilu
updateProfile() {}
uploadAvatar() {}
// Metody powiadomień
sendEmail() {}
sendSMS() {}
// ... 40 więcej metod
}
// Zrefaktoryzowano na skupione serwisy
class AuthService {
login() {}
logout() {}
resetPassword() {}
}
class ProfileService {
updateProfile() {}
uploadAvatar() {}
}
class NotificationService {
sendEmail() {}
sendSMS() {}
}

Poprawka Data Clump

// Wykryto: Te same 4 parametry w 15 metodach
function createInvoice(
street: string,
city: string,
state: string,
zip: string,
// ... inne parametry
) {}
function shipOrder(
street: string,
city: string,
state: string,
zip: string,
// ... inne parametry
) {}
// Zrefaktoryzowano z obiekt value
class Address {
constructor(
public street: string,
public city: string,
public state: string,
public zip: string
) {}
}
function createInvoice(
address: Address,
// ... inne parametry
) {}
function shipOrder(
address: Address,
// ... inne parametry
) {}

Do stopniowego zastępowania systemów legacy:

> Zaimplementuj wzorzec strangler fig dla naszego legacy systemu zamówień:
> - Obecny: Monolityczne przetwarzanie zamówień
> - Cel: Nowoczesna architektura mikroserwisów
> - Wymaganie: Zero przestoju, stopniowa migracja
  1. Utwórz fasadę

    class OrderFacade {
    async createOrder(data: OrderData) {
    if (await this.shouldUseNewSystem(data)) {
    return this.newOrderService.create(data);
    }
    return this.legacyOrderService.create(data);
    }
    private async shouldUseNewSystem(data: OrderData) {
    // Logika stopniowego rollout
    if (this.featureFlags.isEnabled('new_order_system', data.customerId)) {
    return true;
    }
    if (data.total < 100 && Math.random() < 0.1) { // 10% małych zamówień
    return true;
    }
    return false;
    }
    }
  2. Zaimplementuj tryb porównania

    // Uruchom oba systemy i porównaj
    class OrderComparison {
    async createAndCompare(data: OrderData) {
    const [legacy, modern] = await Promise.all([
    this.legacySystem.create(data),
    this.modernSystem.create(data)
    ]);
    const differences = this.compareResults(legacy, modern);
    if (differences.length > 0) {
    await this.logDifferences(differences);
    }
    // Zwróć wynik legacy ale zaloguj modern
    return legacy;
    }
    }
  3. Stopniowe przełączanie

    Tydzień 1: 1% ruchu do nowego systemu
    Tydzień 2: 10% ruchu
    Tydzień 3: 50% ruchu
    Tydzień 4: 90% ruchu
    Tydzień 5: 100% ruchu
    Tydzień 6: Usuń legacy kod
> Utwórz dashboard refaktoryzacji, który śledzi:
> - Poprawę pokrycia kodu
> - Redukcję złożoności cyklomatycznej
> - Poprawy wydajności
> - Redukcję błędów
> - Wynik długu technicznego

Claude generuje kod śledzący:

class RefactoringMetrics {
async analyze() {
const metrics = {
coverage: await this.getTestCoverage(),
complexity: await this.getCyclomaticComplexity(),
performance: await this.getPerformanceMetrics(),
codeQuality: await this.getCodeQualityScore(),
technicalDebt: await this.calculateTechnicalDebt()
};
return {
...metrics,
improvements: this.compareWithBaseline(metrics),
roi: this.calculateROI(metrics)
};
}
async getTestCoverage() {
const coverage = await exec('npm run coverage -- --json');
return {
statements: coverage.total.statements.pct,
branches: coverage.total.branches.pct,
functions: coverage.total.functions.pct,
lines: coverage.total.lines.pct
};
}
}
> Wygeneruj kompleksowe testy aby upewnić się, że refaktoryzacja nie zmienia zachowania:
> - Przechwyć obecne zachowanie
> - Utwórz golden master tests
> - Property-based testing
> - Zestaw testów regresji
> Dla każdej refaktoryzacji:
> 1. Utwórz branch funkcji
> 2. Rób przyrostowe commity
> 3. Uruchamiaj testy ciągle
> 4. Używaj pull requestów do przeglądu
> Podczas refaktoryzacji, również:
> - Aktualizuj komentarze w kodzie
> - Dokumentuj nowe wzorce
> - Utwórz przewodniki migracji
> - Zapisuj uzasadnienie decyzji
> Zmieniając API:
> - Dodaj nowe endpointy obok starych
> - Przestarzałe stopniowo
> - Dostarczaj narzędzia migracji
> - Wspieraj obie wersje tymczasowo
if (Feature.enabled('new_payment_system')) {
return this.newPaymentProcessor.charge(amount);
} else {
return this.legacyPaymentGateway.process(amount);
}

Nauczyłeś się, jak wykorzystać Claude Code do refaktoryzacji na dużą skalę, która byłaby niemożliwie czasochłonna ręcznie. Kluczem jest myślenie systemowe: analizuj kompleksowo, refaktoryzuj przyrostowo, waliduj ciągle.

Pamiętaj: Refaktoryzacja to nie dążenie do perfekcji - to ciągła poprawa. Użyj Claude Code do obsługi mechanicznych transformacji, podczas gdy Ty skupisz się na decyzjach architektonicznych i wartości biznesowej. Z pomocą AI możesz zająć się długiem technicznym, który gromadził się latami i przekształcić legacy systemy w nowoczesne, utrzymywalne bazy kodu.