Refactor Disiplini
Davranışı koruyarak kod yapısını iyileştiren disiplinli refactoring skill'i — scope yönetimi, küçük adım stratejisi, test güvencesi, refactor vs feature ayrımı, anti-pattern farkındalığı.
İçerik
Refactor Disiplini
Refactoring = davranış aynı, yapı daha iyi. Bu skill disiplinli refactor'ın — ne zaman, nasıl, ne kadar — çerçevesini öğretir.
Ne zaman aktif olur
- "Şu kodu temizleyelim" ihtiyacı duyulduğunda
- Yeni feature eklenmeden önce ilgili alanı temizlemek istendiğinde
- Code review'da reviewer "bu parçayı refactor et" dediğinde
- Teknik borç ödenirken
Çekirdek prensipler
1. Refactor ≠ feature, ikisini karıştırma
- Refactor: input/output aynı, implementasyon değişiyor
- Feature: davranış değişiyor veya yeni davranış ekleniyor
Aynı PR'da refactor + feature yapma. Önce refactor (tests pass), sonra feature (tests pass, new tests added).
2. Testin güvencesi olmadan refactor yok
Testleri olmayan kodu refactor etmek = kör operasyon. Önce characterization test yaz:
// Kod ne yapıyor? Öğren + dondur.
test("calculatePrice: %20 indirim basic", () => {
expect(calculatePrice({ base: 100, discount: 0.2 })).toBe(80);
});
// Tüm kritik davranışları capture et, sonra refactor başla.
3. Küçük adım, sık commit
Büyük refactor = "iki haftalık dev branch'i" = merge conflict cehennemi. Her 15-30 dakikada commit edebileceğin parçalara böl.
4. Her adımda testler yeşil
Refactor adımları arasında kırmızı test bırakma. Bir adımın testleri kırıldıysa geri al, daha küçük adım dene.
5. Tool'a güven, eline güvenme
IDE rename (F2), move, extract method — gözle değil, semantic-aware refactor. Manuel find-replace bug kaynağı.
Workflow
Adım 1: Niyeti yaz
Refactor'dan önce tek cümle:
"
OrderServiceiçinde duplike olan price calculation logic'ini ortak birPriceCalculatorclass'ına çıkar."
Bu niyet değişmez. PR title olur. Sapma olursa ayrı PR.
Adım 2: Scope'u sabitle
In-scope:
- Price calculation duplication'ı kaldırma
PriceCalculatorclass'ı oluşturma
Out-of-scope:
- Order model'inin genel iyileştirmesi
- "Şu variable adı da kötüydü"
- Bağlantılı ama farklı bir smell
Out-of-scope bir şey görürsen: TODO note al, ayrı issue aç, devam etme.
Adım 3: Güvence
Testlerin durumu:
- Olan testler geçiyor mu? → evet → devam
- Kritik yol için test yok? → önce test yaz, sonra refactor başla
- Test yazmak mümkün değil (legacy)? → characterization test (snapshot gibi)
Adım 4: En küçük dönüşüme başla
Büyük refactor = birçok küçük tek-amaçlı dönüşüm:
- Extract variable
- Extract function
- Rename variable/function
- Inline variable (gereksizse)
- Move function to another module
- Introduce parameter object
- Replace magic number with constant
Her biri mekanik, düşük risk, geri alınabilir. IDE destekler.
Adım 5: Adım adım ilerle
Her adım:
- Değişikliği yap
- Testleri çalıştır → yeşil mi?
- Yeşilse commit
- Kırmızıysa geri al, daha küçük adım dene
Böylece git history refactor'ın hikayesini anlatır.
Adım 6: Review ve ölç
- PR açılınca diff'in okunabilirliğini gör
- Her commit anlamlı mı?
- Test sayısı / kapsamı aynı veya arttı mı (azalmamalı)?
- Bundle size / performance metric değişti mi (shouldn't)?
Refactoring kataloğu (sık kullanılanlar)
Extract Function
Uzun bir fonksiyonu anlamlı parçalara böl:
// Önce
function processOrder(order: Order) {
// 40 satır validation
// 30 satır pricing
// 20 satır notification
}
// Sonra
function processOrder(order: Order) {
validateOrder(order);
const priced = priceOrder(order);
notifyCustomer(priced);
}
Inline Variable
Tek kullanılan ara variable gereksizse:
// Önce
const base = order.price;
const total = base + order.tax;
// Sonra
const total = order.price + order.tax;
Rename
İsim yanıltıyorsa:
// Önce
const d = 7; // days
// Sonra
const RETENTION_DAYS = 7;
IDE'nin refactor tool'u ile (referanslar güncellenir).
Introduce Parameter Object
Çok parametreli fonksiyon → object:
// Önce
createUser(name, email, phone, role, companyId, timezone);
// Sonra
createUser({ name, email, phone, role, companyId, timezone });
Replace Conditional with Polymorphism
// Önce
function area(shape: Shape) {
if (shape.type === "circle") return Math.PI * shape.r ** 2;
if (shape.type === "square") return shape.side ** 2;
// ...
}
// Sonra
class Circle { area() { return Math.PI * this.r ** 2; } }
class Square { area() { return this.side ** 2; } }
Guard Clause
Nested if → erken return:
// Önce
function foo(user) {
if (user) {
if (user.active) {
if (user.permissions.canEdit) {
// ...
}
}
}
}
// Sonra
function foo(user) {
if (!user) return;
if (!user.active) return;
if (!user.permissions.canEdit) return;
// ...
}
Dead Code Removal
Kullanılmayan kodu sil. Git history'de zaten yaşar.
- Unused imports
- Unused functions (IDE "Find usages" ile doğrula)
- Commented-out code blocks (git'ten geri alırsın)
- Dead branches (
if (false), unreachable after return)
Ne zaman refactor etme
- Fonksiyon çalışıyor, kimse değiştirmiyor, anlaşılır: bırak
- Deadline'dan 1 gün önce: risk, sonraya bırak
- Test yoksa ve yazamıyorsan: kör operasyon
- Sen olmadan kimse anlamayacak abstraction ekliyorsan: gereksiz complexity
- Farklı takımın sahipliğindeki kod: onlarla konuş önce
"While you're there" tehlikesi
Bir bug fix'e başladın, dosyada 5 başka smell gördün, hepsini düzeltmeye koyuldun. Yapma.
- Fix odaklı PR → reviewer kolay review eder, merge hızlı
- Fix + 5 refactor PR → 300 satır, review yorgunluğu, bug sızabilir
- Ayrı PR: refactor'u sonra yap
İstisna: trivial fixler (1-2 satır), fix ile aynı alan ise scope'a alınabilir.
Performans refactor'u
Performance refactor'u ölçümle başlar, ölçümle biter:
- Baseline benchmark
- Değişiklik
- Yeni benchmark
- Fark anlamlı mı? (microbenchmark noise değil)
"Daha hızlı olur sanırım" ile commit yapma.
Aynı zamanda type güvenliği ekleme
TypeScript projesinde sık: any → unknown → proper type. Bunu ayrı ayrı PR'larda yap:
- PR1:
any→unknown+ narrowing (davranış aynı) - PR2: Proper type, schema validation
Birleştirirsen review ağırlaşır.
Somut örnek — "Extract + Rename + Inline"
Başlangıç
function processPayment(order: any) {
const x = order.items.reduce((a: any, i: any) => a + i.price * i.qty, 0);
const tax = x * 0.18;
const total = x + tax;
if (order.coupon) {
const discount = total * order.coupon.percentOff / 100;
const final = total - discount;
return final;
}
return total;
}
Adım 1: Extract "subtotal"
function processPayment(order: any) {
const subtotal = order.items.reduce((a: any, i: any) => a + i.price * i.qty, 0);
const tax = subtotal * 0.18;
const total = subtotal + tax;
if (order.coupon) {
const discount = total * order.coupon.percentOff / 100;
return total - discount;
}
return total;
}
x → subtotal. final inline oldu. Testler yeşil. Commit.
Adım 2: Magic number → constant
const TAX_RATE = 0.18;
function processPayment(order: any) {
const subtotal = order.items.reduce((a: any, i: any) => a + i.price * i.qty, 0);
const tax = subtotal * TAX_RATE;
// ...
}
Testler yeşil. Commit.
Adım 3: Extract calculateSubtotal
const TAX_RATE = 0.18;
function calculateSubtotal(items: Array<{ price: number; qty: number }>): number {
return items.reduce((sum, item) => sum + item.price * item.qty, 0);
}
function processPayment(order: any) {
const subtotal = calculateSubtotal(order.items);
const tax = subtotal * TAX_RATE;
const total = subtotal + tax;
if (order.coupon) {
const discount = total * order.coupon.percentOff / 100;
return total - discount;
}
return total;
}
Hem extract hem tip eklendi (bu adımda). Testler yeşil. Commit.
Adım 4: Extract applyCouponDiscount
function applyCouponDiscount(amount: number, coupon: { percentOff: number } | null): number {
if (!coupon) return amount;
return amount - amount * coupon.percentOff / 100;
}
function processPayment(order: any) {
const subtotal = calculateSubtotal(order.items);
const tax = subtotal * TAX_RATE;
const total = subtotal + tax;
return applyCouponDiscount(total, order.coupon ?? null);
}
Güzelleşti. Commit.
Not: order: any hala any. Onu ayrı PR'da type'la — scope disiplini.
Anti-pattern'ler
- ❌ Büyük-bang refactor (haftalık dev branch)
- ❌ Refactor + feature aynı PR
- ❌ Testler kırmızıyken refactor'a devam
- ❌ "While you're there" scope genişletmek
- ❌ Manuel find-replace (IDE refactor tool'u var)
- ❌ Abstraction eklemek "ileride lazım olabilir" diye
- ❌ Performans iddiasında bulunup ölçmemek
- ❌ Davranışı "hafifçe" değiştirmek refactor adı altında
- ❌ Dead code commented-out bırakmak
- ❌ Refactor sırasında branch'i 2 haftadan fazla açık tutmak
- ❌ "Bu kod kötü yazılmış" yargısıyla toptan yeniden yazmak (genelde önceki yazan haklıydı)
- ❌ Rename'de IDE kullanmadan string search-replace
Checklist — refactor PR'ı öncesi
- PR title refactor niyetini tek cümleyle söylüyor
- Tüm testler yeşil (hem önce hem sonra)
- Davranış değişmedi (aynı input → aynı output)
- PR'da feature eklentisi / davranış değişikliği yok
- Commit'ler anlamlı, her biri geri alınabilir
- Diff reviewer için okunaklı
- Performans / bundle / memory regression yok
- Kapsam disiplinli — out-of-scope şeyler ayrı issue/PR'a