Modernisasi legacy code dengan Claude Code secara aman
Workflow praktis modernisasi legacy code dengan Claude Code: test, TypeScript, risiko, contoh runnable, dan hasil verifikasi.
Modernisasi gagal kalau dimulai terlalu besar
Legacy code bukan sekadar kode lama. Ini adalah kode yang perilakunya sulit dibuktikan, test-nya tipis, aturan bisnisnya tersembunyi, dan perubahan kecil bisa merusak order, billing, support, atau publishing flow. Claude Code berguna dalam kondisi seperti ini, tetapi tidak aman jika dipakai sebagai tombol rewrite besar-besaran.
Workflow yang lebih aman adalah investigasi dulu, kunci perilaku saat ini dengan test, lalu refactor dalam langkah kecil. Characterization test berarti test yang mendokumentasikan perilaku sistem saat ini, bukan langsung mendefinisikan desain ideal. Harness adalah scaffolding agar agent bisa bekerja dengan aman: test, permission, command, aturan project, dan checklist review.
Dokumentasi resmi Claude Code common workflows membahas eksplorasi codebase, refactoring, testing, PR, dan worktree. Untuk legacy modernization, mental model itu tepat. Claude Code mempercepat eksekusi, tetapi batas risiko dan keputusan release tetap milik manusia.
Tiga use case yang sering muncul
Jangan mulai dari file yang sekadar paling jelek. Mulai dari area yang kalau salah akan paling mahal dampaknya.
| Use case | Tujuan | Apa yang bisa dilakukan Claude Code | Apa yang harus dicek manusia |
|---|---|---|---|
| Order, billing, payment logic | Mencegah kesalahan uang dan status customer | Memetakan perilaku, menambah test, mencari edge case | Pajak, diskon, pembulatan, compliance |
| Migrasi JavaScript ke TypeScript | Membuat perubahan berikutnya lebih aman | Menambah tipe, mengurangi any, memperbaiki type error bertahap | Kompatibilitas public API |
| Callback atau function besar | Meningkatkan maintainability tanpa mengubah behavior | Memisah tanggung jawab, menyarankan nama, menjelaskan diff | Error handling, retry, side effect, log |
Di ClaudeCodeLab, pola yang sama saya pakai untuk script publishing lama, integrasi checkout, dan transformasi konten. Poin utamanya bukan Claude Code menulis cepat. Poin utamanya adalah setiap perubahan cukup kecil untuk direview dan punya bukti berupa test atau catatan verifikasi manual.
flowchart LR
A[Explore] --> B[Kunci behavior dengan test]
B --> C[Refactor kecil]
C --> D[Tambah tipe dan rapikan dependency]
D --> E[Review risiko oleh manusia]
E --> B
Mulai dengan audit read-only
Prompt pertama harus melarang edit. Di fase ini kamu butuh peta sistem, bukan patch dari konteks yang belum lengkap.
Baca @src/legacy dan @test.
Jangan ubah file dulu.
Kembalikan audit ini:
1. File utama dan tanggung jawabnya
2. External I/O, database, API, file write, dan side effect
3. Behavior yang harus tetap kompatibel
4. Test yang hilang dan branch berisiko tinggi
5. Urutan paling aman untuk perubahan kecil
Kalau aturan tidak jelas, tulis "perlu konfirmasi manusia" daripada menebak.
Halaman resmi How Claude Code works menjelaskan bahwa Claude Code dapat membaca file, menjalankan command, dan mengedit kode. Kemampuan itu berguna, tetapi di sistem lama fase pertama sebaiknya dibatasi untuk memahami dan merencanakan. Perubahan yang terlihat idiomatis bisa saja mengubah kontrak yang dipakai client lama.
Contoh minimal yang bisa dijalankan
Contoh ini kecil, tetapi urutannya sama dengan yang saya pakai di pekerjaan legacy nyata.
mkdir legacy-modernization-demo
cd legacy-modernization-demo
npm init -y
npm install -D vitest typescript @types/node
npm pkg set type="module"
npm pkg set scripts.test="vitest run"
npm pkg set scripts.typecheck="tsc --noEmit"
mkdir -p src/legacy test
Implementasi lama mencampur validasi, kalkulasi, dan bentuk response dalam satu file. Ini bukan contoh yang dibuat berlebihan; ini bentuk legacy yang umum: masih jalan, tetapi membuat tim ragu untuk menyentuhnya.
// src/legacy/orderProcessor.js
export function processOrder(order) {
if (!order || !Array.isArray(order.items) || order.items.length === 0) {
return { status: "error", message: "items is required" };
}
const subtotal = order.items.reduce((sum, item) => {
return sum + item.price * item.qty;
}, 0);
const discount = order.customer?.type === "vip" ? subtotal * 0.1 : 0;
return {
status: "confirmed",
total: subtotal - discount,
items: order.items,
discount
};
}
Sekarang kunci behavior saat ini dengan Vitest. Test ini tidak mendesain ulang domain; test ini menjaga kontrak yang tidak boleh berubah tanpa keputusan eksplisit.
// test/orderProcessor.test.ts
import { describe, expect, it } from "vitest";
import { processOrder } from "../src/legacy/orderProcessor.js";
describe("processOrder legacy behavior", () => {
it("calculates total for a regular customer", () => {
const result = processOrder({
items: [
{ id: "A1", qty: 2, price: 1000 },
{ id: "B2", qty: 1, price: 500 }
],
customer: { id: "C1", type: "regular" }
});
expect(result).toMatchObject({
status: "confirmed",
total: 2500,
discount: 0
});
});
it("applies a 10 percent VIP discount", () => {
const result = processOrder({
items: [{ id: "A1", qty: 1, price: 10000 }],
customer: { id: "C2", type: "vip" }
});
expect(result.status).toBe("confirmed");
expect(result.total).toBe(9000);
expect(result.discount).toBe(1000);
});
it("returns an error when items are empty", () => {
const result = processOrder({
items: [],
customer: { id: "C3", type: "regular" }
});
expect(result.status).toBe("error");
expect(result.message).toContain("items");
});
});
Jalankan npm test. Setelah hijau, baru minta Claude Code mengedit.
Baca @src/legacy/orderProcessor.js dan @test/orderProcessor.test.ts.
Migrasikan kode ini ke TypeScript sambil menjaga test tetap hijau.
Aturan:
- Pertahankan nama fungsi public processOrder
- Pertahankan behavior status, total, discount, dan message
- Tambahkan type definition dulu, lalu pisahkan responsibility
- Jalankan npm test dan npm run typecheck setelah perubahan
- Jelaskan kompatibilitas apa yang dijaga tiap diff
Struktur TypeScript yang lebih mudah direview
Setelah modernisasi, tipe, validasi, kalkulasi, dan orkestrasi dipisahkan. Tujuannya bukan abstraksi yang terlihat rapi, tetapi logic yang berdampak ke uang dan status customer menjadi mudah direview.
// src/orderTypes.ts
export type CustomerType = "regular" | "vip";
export type OrderItem = {
id: string;
qty: number;
price: number;
};
export type OrderInput = {
items: OrderItem[];
customer: {
id: string;
type: CustomerType;
};
};
export type OrderResult =
| {
status: "confirmed";
total: number;
items: OrderItem[];
discount: number;
}
| {
status: "error";
message: string;
};
// src/validators.ts
import type { OrderInput } from "./orderTypes";
export function validateOrder(order: OrderInput | null | undefined): string | null {
if (!order || !Array.isArray(order.items) || order.items.length === 0) {
return "items is required";
}
return null;
}
// src/calculators.ts
import type { CustomerType, OrderItem } from "./orderTypes";
export function calculateSubtotal(items: OrderItem[]): number {
return items.reduce((sum, item) => sum + item.price * item.qty, 0);
}
export function calculateDiscount(subtotal: number, customerType: CustomerType): number {
return customerType === "vip" ? subtotal * 0.1 : 0;
}
// src/orderProcessor.ts
import { calculateDiscount, calculateSubtotal } from "./calculators";
import type { OrderInput, OrderResult } from "./orderTypes";
import { validateOrder } from "./validators";
export function processOrder(order: OrderInput): OrderResult {
const validationMessage = validateOrder(order);
if (validationMessage) {
return { status: "error", message: validationMessage };
}
const subtotal = calculateSubtotal(order.items);
const discount = calculateDiscount(subtotal, order.customer.type);
return {
status: "confirmed",
total: subtotal - discount,
items: order.items,
discount
};
}
Ubah import test ke ../src/orderProcessor, lalu jalankan npm test dan npm run typecheck. Diff sebesar ini masih bisa dipahami reviewer. Kalau PR yang sama juga memindahkan folder, meng-upgrade dependency, mengubah formatting, dan mengganti istilah domain, kualitas review akan turun.
Pisahkan update dependency
Kesalahan umum lainnya adalah mencampur refactor dengan major dependency upgrade. Saat test gagal, penyebabnya bisa TypeScript, breaking API, bundler, atau logic yang baru diubah.
Minta Claude Code membuat inventory dulu.
Baca package.json dan lockfile.
Jangan update apa pun dulu.
Kembalikan tabel:
- package
- versi saat ini
- target version yang direkomendasikan
- apakah major upgrade
- URL migration guide resmi
- file yang mungkin terdampak
- test yang perlu ditambahkan sebelum update
Untuk operasi yang luas atau destruktif, gunakan permission yang konservatif. Dokumentasi Claude Code permissions layak dibaca sebelum mengizinkan migration, delete, atau deploy command. Kecepatan agent tidak berguna kalau melewati approval gate yang seharusnya menjaga sistem.
Pitfall konkret
Pitfall pertama adalah refactor sebelum ada test. Kode lebih bersih tetap regresi kalau rounding, diskon, atau pesan error berubah.
Pitfall kedua adalah menerima saran Claude Code sebagai aturan bisnis. Return shape yang lebih idiomatis belum tentu kompatibel dengan client lama.
Pitfall ketiga adalah PR raksasa. Type migration, pemisahan logic, dependency update, file move, dan formatting sebaiknya dipisahkan.
Pitfall keempat adalah memperbaiki error handling terlalu agresif. Dalam sistem lama, null, string tertentu, atau status HTTP aneh bisa saja bagian dari kontrak.
Pitfall kelima adalah melewatkan dokumentasi. Minta Claude Code menulis compatibility note, langkah manual verification, dan rollback plan di deskripsi PR.
Review, link, dan CTA
Gunakan workflow ini bersama panduan refactoring automation, TDD dengan Claude Code, dan documentation generation. Untuk tim, tulis area terlarang, command test, istilah domain, dan aturan review di CLAUDE.md best practices.
ClaudeCodeLab membantu tim mengenalkan Claude Code ke produk yang sudah berjalan melalui training, template CLAUDE.md, dan konsultasi modernisasi. Tujuannya bukan rewrite dengan AI, melainkan workflow yang bisa diulang dengan test, permission, dan review manusia.
Hasil yang saya verifikasi
Saya menjalankan workflow ini sebagai legacy-modernization-demo: membuat processor JavaScript legacy, mengunci tiga behavior dengan Vitest, memigrasikan ke TypeScript, lalu menjalankan npm test dan npm run typecheck. Bagian paling efektif adalah memisahkan prompt audit read-only dari prompt edit. Diff menjadi lebih kecil, dan total, diskon VIP, serta error order kosong lebih mudah diverifikasi.
PDF gratis: cheatsheet Claude Code
Masukkan email dan unduh satu halaman berisi command, kebiasaan review, dan workflow aman.
Kami menjaga datamu dan tidak mengirim spam.
Tentang penulis
Masa
Engineer yang berfokus pada workflow Claude Code praktis dan adopsi tim.
Artikel terkait
Workflow Obsidian ke CLAUDE.md untuk Claude Code
Ubah catatan kerja Obsidian menjadi operating note CLAUDE.md agar konteks tidak dijelaskan ulang.
Claude Code Revenue CTA Routing: dari artikel ke PDF, Gumroad, dan konsultasi
Workflow Claude Code untuk mengarahkan pembaca ke PDF gratis, Gumroad, atau konsultasi sesuai intent.
Aturan handoff tim Claude Code: bukti review, permission, rollback, dan jalur revenue
Format handoff Claude Code untuk tim: bukti, permission rule, rollback, PDF gratis, Gumroad, dan konsultasi.