Exploring Infinite Possibilities on FrontEnd 🚀
Burak Sağlık
Search...

Sun Aug 16 2026

Legacy Modernizasyonu AI ile: Strangler Fig ve Güvenli Refaktörme

Legacy Modernizasyonu AI ile: Strangler Fig ve Güvenli Refaktörme

🎯 Giriş: Legacy Modernizasyonun Gerçek Yüzü ve AI’nın Rolü

Hazırsan başlayalım 🚀

Birkaç yıl önce, 10 000 satırlık bir Java monolit ile baş başa kaldım. Kodun yarısı yorum satırıydı, yarısı da “TODO: burayı düzelt” notlarıyla doluydu. Deploy etmek için haftalarca manuel test yapardık, bir hata çıkınca ise “kim yazdı bunu?” diye sorar, cevap gelmezdi.

Neden legacy bu kadar zor? 🤔

  • Teknik borç yıllar içinde yığılır, faizi de artar 📈
  • Dokümantasyon ya yoktur ya da eski kalmıştır 📄
  • Test coverage %0’a yakın, regression her seferinde sürpriz 🎁
  • Takım üyeleri gelip gider, bilgi kaybı kaçınılmaz olur 👋

AI sadece kod yazmaz, anlar ve güvenli refaktörme yapar 🤖

  • Statik analiz ile bağımlılıkları haritalar 🔍
  • Pattern recognition sayesinde “bu sınıf ne işe yarıyor?” sorusuna cevap verir 🧩
  • Otomatik test üreteci (test generation) ile güvenlik ağı kurar 🛡️
  • Kod açıklaması (code explanation) ile yeni gelen geliştiricinin öğrenme süresini kısaltır ⏱️

Küçük bir anekdot 🎬

Geçen sprint’te AI asistanımıza “UserService sınıfını temiz mimariye uygun hale getir” dedik.

  • Önce sınıfın sorunlu kısımlarını vurguladı (tight coupling, hard‑coded config).
  • Ardından interface çıkardı, dependency injection ekledi.
  • Son olarak unit testleri otomatik yazdı ve CI’ye entegre etti.

Sonuç? Deploy süresi 2 saatten 15 dakikaya düştü ⚡️ ve takım “artık bu kodu korkmadan dokunabiliriz” dedi 😌

Özetle: Legacy modernizasyonu bir macera değil, stratejik bir yatırım olmalı. AI, bu yatırımı daha az riskli, daha hızlı ve öğrenilebilir hale getiren en iyi arkadaşınız.

Hadi bir sonraki bölümde pratik araçlara ve ilk adımlara geçelim! 🚀


❗️ Yeniden Yazma Tuzağı: Neden "Baştan Yaz" Stratejisi Genellikle Başarısız Olur?

Hadi Dürüst Olalım: Her Geliştiricinin Hayali Bu 🎯

Birlikte çalıştığımız legacy sistemlere baktığımızda aklımızdan geçen ilk düşünce şudur: "Bu berbat kod, hadi silip baştan yazalım, her şey düzelecek." Kulağa çok cazip geliyor, değil mi? Yeni bir mimari, temiz kod, hiçbir technical debt yok... Mükemmel bir dünya.

Ama gerçek hayat o kadar da masal gibi geçmiyor. Ben de bir zamanlar bu tuzağa düştüm, belki siz de düştünüz. Hadi neden "big rewrite" stratejisi neredeyse her zaman faciaya döner, onu beraber çözelim.

Neden Bu Kadar Tehlikeli? ⚠️

1. Zaman ve Bütçe: Her Zaman Tahmininizin Katı Olur 📉

  • Eski sistem 5 yıl geliştirildi, siz 6 ayda bitireceksiniz sanıyorsunuz
  • Gerçeklik: Bilinmeyen edge case'ler, undocumented business rules, "neden böyle yapılmış?" sorularının cevapları süreç ortasında çıkıyor
  • Brooks Yasası hatırlatıyor: "Bir yazılım projesine eklenen her yeni kişi, projeyi daha da geciktirir" — rewrite projelerinde bu iki kat etki ediyor

2. İş Sürekliliği: Müşteriniz Durmuyor 🛑

  • Eski sistem hala canlıda, müşteriler kullanıyor, bug raporluyor, feature istiyor
  • Rewrite ekibi "yeni sistem"le uğraşırken, eski sistem bakımsız kalıyor
  • Sonuç: İki sistem de çöker — yenisini bitiremediniz, eskisi de patladı

3. Bilgi Kaybı: "Neden Böyle?" Sorusunun Cevabı Kayboldu 🧠

// Eski kodda bu satır var:
if (user.status === 'PENDING' && order.amount > 1000) {
    // TODO: Bu kontrol neden var? Kimse bilmiyor.
    requireManualApproval();
}
  • O kodu yazan developer 3 yıl önce ayrıldı
  • Commit mesajı: "fix"
  • Dokümantasyon? Yok
  • Rewrite sırasında bu mantığı yeniden keşfetmek zorunda kalıyorsunuz — ve genelde yanlış keşfediyorsunuz

4. İkinci Sistem Etkisi (Second System Effect) 🏗 Fred Brooks'un The Mythical Man-Month kitabından: "Bir mimarın yaptığı ikinci sistem, hayalindeki en tehlikeli sistemdir; ona her özellikleri eklemek ister."

  • İlk sistemde pragmatik davrandınız, ikinci sistemde "mükemmel" olmaya çalışıyorsunuz
  • Over-engineering, gold-plating, "bu sefer doğru yapacağım" sendromu

Gerçek Hayattan Acı Bir Örnek 📖

Netscape 6.0 Hikayesi (1998-2000)

  • Netscape 4.x kod tabanı berbat, yönetilemezdi
  • Yönetim: "Hadi baştan yazalım, Mozilla motoru üzerine kuralım"
  • Sonuç: 2 yıl gecikti, piyasa payı %80'den %20'ye düştü, IE kazandı
  • Joel Spolsky'nin meşhur makalesi: "Things You Should Never Do, Part I" — bu hikayeyi anlatır

Daha Yakın Bir Örnek: Basecamp (eski 37signals)

  • v1 → v2 → v3 geçişlerinde her seferinde tamamen yeni kod tabanı yazdılar
  • Sonuç? Müşterilerinin çoğu eski versiyonda kaldı, migration zorluğu yüzünden
  • Şimdi "Major version = yeni kod tabanı" stratejisinden vazgeçtiler, artık incremental update yapıyorlar

Peki, AI Bu Tuzağı Nasıl Önleyebilir? 🤖💡

Burada heyecanlandırmak için değil, gerçekçi bir bakış açısı için söylüyorum:

AI Yetenekleri Rewrite Riskini Nasıl Azaltır?
Kod Anlama / Dökümantasyon Legacy kodu okuyup, business rule'ları çıkarıyor, "neden böyle?" sorusuna cevap veriyor
Refactoring Asistanı "Baştan yaz" yerine, parça parça güvenli refactoring öneriyor, testler yazıyor
Strangler Fig Pattern Uygulaması Eski sistemin etrafını saran yeni modülleri planlı bir şekilde çıkarmanıza yardımcı oluyor
Test Generasyonu Eski sistemin davranışını kilitleyen characterization testleri dakikalar içinde üretiyor

Önemli Not: AI sihirli bir değnek değil. Ama "baştan yaz" kararı vermeden önce, legacy sistemin ne yaptığını gerçekten anladığınızdan emin olmanızı sağlıyor. Ve bu, rewrite tuzağının en büyük nedenidir: Sistemi anlamadan yeniden yazmaya çalışmak.

Özetle: Ne Yapmalıyız? ✅

  • Strangler Fig Pattern: Eski sistemi parçalar halinde, yeni servislerle değiştirin 🌱
  • Characterization Testler: Önce mevcut davranışı testlerle kilitleyin, sonra değiştirin 🧪
  • Incremental Modernization: "Rewrite" demeden, modül modül iyileştirin 🔧
  • AI'ı Kullanın: Kod analizi, test yazımı, dokümantasyon için — ama karar siz verin 🤝

Beni hatırlatır mısınız? En iyi rewrite, yapılmayan rewrite'dir. Veya en azından, "baştan yaz" diye başlayıp "parça parça değiştir" diye bitiren o projedir. 😉


Hazırsan bir sonraki bölümde "Strangler Fig Pattern'i pratikte nasıl uygularız?" konusuna geçebiliriz. Veya sizin yaşadığınız rewrite hikayeleri var mı? Yorumlarda paylaşalım! 👇


🔁 Strangler Fig Pattern: Eski Sistemi Parça Parça Yeniden Doğurmak

Hazırsan başlayalım 🎯. Strangler Fig, eski monolitik uygulamayı kademeli olarak yeni mikro servislerle değiştirmenin pratik bir yoludur. Fikir basit: yeni işlevselliği yanına ekle, trafiği yönlendir, eski kodu yavaş yavaş sil.

1️⃣ Yeni işlevselliği yanına ekle

  • Yeni servis ayrı bir repo/container olarak yazılır.
  • Eski sistemde değişiklik yok, sadece facade/routing katmanı yeni endpoint’i bilir.
  • Contrat (API sözleşmesi) net tanımlanır → hem eski hem yeni istemci uyumlu kalır.

2️⃣ Routing / Façade katmanını kur

  • API Gateway (Kong, NGINX, AWS API Gateway…) path‑based ya da header‑based yönlendirme yapar.
  • İlk aşamada %100 trafik eskiye gider; canary/kademeli olarak yeni servise kaydırılır.
  • Observability (log, metrics, trace) her iki tarafı da izler → anomali anında tespit edilir.

3️⃣ Eski kodu kademeli devre dışı bırak

Aşama Ne Yapılır? Kontrol Noktası
Shadow Yeni servise kopyalı trafik gönder, yanıtı karşılaştır Hata oranı < %0.1
Canary Gerçek trafiğin %5‑%10'u yeni servise SLA, latency, hata oranı hedeflerde
Full Cutover Tüm trafik yeni servise, eski endpoint deprecated Monitoring 24‑48 s stabil
Decommission Eski kod ve veritabanı tabloları temizlenir Backup doğrulandı, rollback planı hazır

🤖 AI bu geçişte nerde yardımcı olur?

  • Yeni servis iskeleti (OpenAPI spec → code scaffold) oluşturmak.
  • Contrat testleri (Pact, Schemathesis) otomatik generate & CI’ye eklemek.
  • Canary analiz: metrikleri okuyup “geçiş hazır mı?” raporu sunmak.
  • Kod dönüşümü: Eski modülün mantığını yeni dili/framework’e çevirmek (ör. Java → Go).

🛠 Örnek: Kong API Gateway routing konfigürasyonu (YAML)

# kong.yml – basit Strangler Fig routing örneği
_format_version: "3.0"

services:
  - name: legacy-order-service
    url: http://legacy-orders:8080
    routes:
      - name: legacy-orders-route
        paths:
          - /orders
        strip_path: false
    plugins:
      - name: correlation-id   # tracing için

  - name: new-order-service
    url: http://new-orders:8080
    routes:
      - name: new-orders-route
        paths:
          - /orders/v2
        strip_path: false
    plugins:
      - name: correlation-id

# Canary: %10 trafiği v2'ye yönlendir
plugins:
  - name: rate-limiting
    config:
      minute: 1000
      policy: local
  - name: request-transformer
    config:
      add:
        headers:
          - "x-canary:true"
    route: new-orders-route
    protocols:
      - http
    # Kong'un `canary` eklentisi veya custom Lua ile %10 trafik yönlendirilebilir

Ne oluyor burada?

  • /orderseski servise gider.
  • /orders/v2yeni servise gider.
  • request-transformer ile canary header eklenir; downstream servisler bu header’a göre davranabilir.
  • Gözlemleme eklentileri (correlation‑id, logging) her iki akışı da izler.

✅ Özet

  1. Yeni servis yaz → facade ekle.
  2. Gateway ile trafiği kademeli kaydır.
  3. Gözlemle, test et, onayla.
  4. Eski kodu güvenle sil.

Bu yöntemle risk 최소화 edilir, deployment her gün daha küçük hale gelir ve ekip sürekli değer üretir. 🚀


🛠 AI Asistanıyla Karakterizasyon Testleri Yazmak: Mevcut Davranışı Kilitlemek

Karakterizasyon testi (characterization test), legacy kodun şu anki davranışını belgelemek için yazılır.
Birim testten farkı şudur:

  • 🎯 Amaç: Doğru mu yanlış mı değil, “ne yapıyor?” sorusuna cevap vermek.
  • 🔁 Kapsam: Genellikle büyük, karmaşık fonksiyonlar veya modüller için.
  • ❗️ Sonuç: Testler geçerse kodun davranışı kilitlenir; refactor sonrası bozulma anında fail olur.

Özet: Karakterizasyon testi = “Mevcut davranışın fotoğrafı” 📸


AI Asistanı (Copilot / Cursor) ile Nasıl Üretiyoruz?

  1. Hedef fonksiyonu aç ve inline chat’i başlat.
  2. Prompt örneği (Copilot için):
    // @workspace
    // Aşağıdaki fonksiyon için Jest karakterizasyon testleri yaz.
    // - Tüm girdi kombinasyonlarını (normal, edge, hata) kapsasın.
    // - Mevcut davranışı değiştirme, sadece gözlemle.
    // - Test isimleri “should …” formatında olsun.
    
  3. AI yanıtını incele – genellikle 10‑30 test case üretir.
  4. Doğrulama adımları:
    • ✅ Testleri çalıştır ve hepsi yeşil olmalı.
    • 🔍 Snapshot veya console.log ile gerçek çıktıyı kaydet.
    • 🛠 Kodda hiçbir değişiklik yapmadan testlerin geçtiğinden emin ol.
    • 📌 Test dosyasını commit et – bu artık “davranış sözleşmesi”.

İpucu: Prompt’a “sadece mevcut davranışı test et, refactor yapma” eklemek AI’ı gereksiz yeniden yazım'dan kaçınmaya zorlar.


Örnek: JavaScript (TypeScript) + Jest

// src/legacy/priceCalculator.ts
export function calculatePrice(base: number, discount?: number, taxRate = 0.18): number {
  if (base < 0) throw new Error('Base price cannot be negative');
  const disc = discount ?? 0;
  const priceAfterDiscount = base * (1 - disc);
  return priceAfterDiscount * (1 + taxRate);
}
// tests/priceCalculator.characterization.test.ts
import { calculatePrice } from '../../src/legacy/priceCalculator';

describe('calculatePrice – characterization tests', () => {
  // Normal akış
  test('should return price with default tax when no discount', () => {
    expect(calculatePrice(100)).toBeCloseTo(118); // 100 * 1.18
  });

  test('should apply discount then tax', () => {
    expect(calculatePrice(200, 0.1)).toBeCloseTo(212.4); // 200*0.9*1.18
  });

  // Edge case: discount = 0
  test('should behave same as no discount when discount is 0', () => {
    expect(calculatePrice(150, 0)).toBeCloseTo(177);
  });

  // Edge case: discount = 1 (free)
  test('should return only tax when discount is 1', () => {
    expect(calculatePrice(100, 1)).toBeCloseTo(0); // 0 * 1.18 = 0
  });

  // Hata durumu
  test('should throw when base price is negative', () => {
    expect(() => calculatePrice(-10)).toThrow('Base price cannot be negative');
  });
});

Çalıştırma komutu

npx jest tests/priceCalculator.characterization.test.ts --verbose

Bu sayede ne oluyor?

  • Mevcut calculatePrice davranışı testlerle kilitlenir.
  • Gelecekte refactor yaparsanız (ör. vergi oranını parametreye taşıma) testler anında kırmızı yanar ⛔.
  • AI ile dakikalar içinde onlarca test elde ettik, manuel yazmak yerine doğrulama odaklandık ✅.

Sonuç

Karakterizasyon testleri legacy kodu güvenle değiştirmenin ilk adımıdır.
AI asistanı doğru prompt ile mevcut davranışı saniyeler içinde test koda dönüştürür; siz sadece “bu testler geçiyor mu?” diye kontrol edersiniz.
Böylece bilinmeyen davranış belgeye dönüşür ve gelecekteki her değişiklik güvenli bir ağ üzerine oturur 🎯.


🔁 Bağımlılık Haritalama ve Kod Anlama: AI ile "Kara Kutu"yu Açmak

Eski bir projeye atıldığında hissettigin o "nereden başlayayım?" paniği biliyorsun ya 😅 Dokümantasyon yok, yazan kişi ayrıldı, ve kod içinde gizli bağımlılıklar (veritabanı şemaları, harici API'ler, global state) saklı duruyor. İşte bu "kara kutuyu" açmak için AI harika bir lanterna olabilir 🔦

Ne tür gizli bağımlılıklar kaçıyor gözümüzden?

  • Veritabanı şemaları: Raw SQL sorguları, ORM modelleri farklı dosyalarda, migration'lar ayrı klasörde...
  • Harici API'ler: HttpClient wrapper'ları, base URL'ler config dosyalarında, retry logic'i bir yerde timeout ayarı başka yerde
  • Global state: Singleton'lar, static sınıflar, environment variable'lar doğrudan kod içinde kullanılıyor
  • Event-driven akışlar: Message queue'lar, event handler'lar dağıtık dosyalarda

Bunları manuel bulmak günlerinizi alır. AI ise kod taraması → özetleme → çağrı grafiği çıkarma zincirini dakikalar içinde yapabilir ⚡


🛠 Cursor'un "Chat with Codebase" ile Hızlı Keşif

Cursor açıksa, Cmd+L (veya Ctrl+L) ile Chat panelini aç ve Codebase modunu seç. Sonra şu soruları sorabilirsin:

"Bu projedeki tüm veritabanı bağlantı noktalarını ve hangi tabloların kullanıldığını listele"

"PaymentService sınıfı hangi harici API'leri çağırıyor? Base URL'ler nerede tanımlı?"

"Global state olarak kullanılan tüm singleton/static sınıfları bul"

Cursor embedding'ler üzerinden ilgili dosyaları çeker, context'e koyar ve cevap verir. Ama dikkat: bazen hallucination yapar ❗️ Cevabı aldıktan sonra "Bu dosyadaki satır X-Y aralığını göster" diyerek doğrula.


🏠 Lokal Model ile (CodeLlama / DeepSeek-Coder) Kendi Pipeline'ını Kur

Cursor yoksa veya veri dışarı çıkmamalıysa, Ollama ile lokal model koşturup kendi bash pipeline'ını yazarsın. Şu basit script bir başlangıç olabilir 👇

#!/usr/bin/env bash
# scan_and_ask.sh — Projeyi tara, özetle, soru sor
# Kullanım: ./scan_and_ask.sh "Sorunuz buraya"

set -euo pipefail

QUESTION="${1:-Projedeki ana bağımlılıkları ve veri akışını özetle}"
PROJECT_ROOT="${2:-.}"
MODEL="${3:-codellama:13b}"  # ollama pull codellama:13b yapmayı unutma
OUTFILE="ai_analysis_$(date +%Y%m%d_%H%M%S).md"

echo "🔍 Proje taranıyor: $PROJECT_ROOT"

# 1. Sadece ilgili dosyaları topla (node_modules, .git, build artifact'leri atla)
mapfile -t FILES < <(find "$PROJECT_ROOT" -type f \
  \( -name "*.cs" -o -name "*.java" -o -name "*.py" -o -name "*.js" -o -name "*.ts" -o -name "*.go" -o -name "*.php" \) \
  ! -path "*/node_modules/*" ! -path "*/.git/*" ! -path "*/bin/*" ! -path "*/obj/*" ! -path "*/build/*" ! -path "*/dist/*" \
  | head -200)  # context window'ı aşmamak için limit

echo "📄 ${#FILES[@]} dosya bulundu, modele gönderiliyor..."

# 2. Dosyaları tek bir context'e birleştir (basit versiyon)
CONTEXT=""
for f in "${FILES[@]}"; do
  CONTEXT+="\n=== DOSYA: $f ===\n"
  CONTEXT+="$(cat "$f")\n"
done

# 3. Prompt'u hazırla
PROMPT=$(cat <<EOF
Sen bir senior software architect'sın. Aşağıdaki kod tabanını analiz et ve şu soruyu cevapla:

SORU: $QUESTION

KOD TABANI:
$CONTEXT

Cevap formatı:
- **Özet**: 3-5 cümlelik genel bakış
- **Bulunan Bağımlılıklar**: Madde madde (veritabanı, API, global state, vb.)
- **Riskli Alanlar**: Refactoring zorlukları, tight coupling noktaları
- **Öneriler**: Adım adım yapılması gerekenler
EOF
)

# 4. Modeli çağır (streaming olmasa da olur, sadece sonuç lazım)
echo "$PROMPT" | ollama run "$MODEL" > "$OUTFILE"

echo "✅ Analiz tamamlandı: $OUTFILE"
echo "📖 İçeriği görmek için: cat $OUTFILE"

Nasıl çalışıyor? 🤔

  1. find ile projedeki kod dosyalarını toplar (binary/config dosyalarını atlar)
  2. Her dosyayı === DOSYA: path === başlığıyla birleştirir → model hangi dosyadan geldiğini bilsin
  3. System prompt'ta rolünü ("senior architect") ve çıktı formatını net tanımlar
  4. ollama run ile lokal modele gönderir, cevabı Markdown dosyasına yazar

💡 İpucu: Context window'ı dolarsa head -200 limitini artır/azalt, veya dosyaları önce özetletip (map-reduce) sonra soru sor. Basit bir summarize.sh yazıp her dosya için 3-5 satırlık özet alıp onları birleştirmek daha akıllıca olur 🧠


🎯 Bu Pipeline ile Ne Kazandık?

Manuel Yöntem AI Pipeline
Haftalarca kod okuma Dakikalar içinde harita 🗺
Kaçırılan bağımlılıklar Sistematik tarama ile yakalama
Bilgi silolara hapsolur Paylaşılabilir Markdown raporu
Yeni ekip üyesi onboarding'i zor "Bu scripti çalıştır, raporu oku" 🚀

⚠️ Kısacası: Güven Ama Doğrula

AI harika bir keşif aracı, ama kesin kaynak değil. Çıktıyı aldında:

  1. Spot-check yap: "Bu API çağrısı gerçekten PaymentService.cs satır 142'de mi?"
  2. Call graph çıkart: dotnet-depend / pydeps / madge gibi araçlarla görselleştir
  3. Test yaz: Keşfettiğin bağımlılıkları koruyan characterization test'leri ekle 🧪

Hazırsan bir sonraki adımda bu haritayı kullanarak nasıl güvenli refactoring yaparız ona bakalım 👇


🛠 Güvenli Refaktörme Adımları: Copilot / Cursor ile Küçük, Test Edilebilir Değişiklikler

Hazırsan başlayalım 🚀. AI ile refaktör yaparken büyük atlamalar yerine baby steps (küçük adımlar) prensibini takip etmek, testlerin yeşil kalmasını ve code review sürecinin hızlanmasını sağlar. İşte nasıl uyguladığım:

1️⃣ Problemi Küçült: Extract Method

  • Amaç: Uzun bir metodu, tek bir sorumluluğa sahip küçük metodlara bölmek.
  • AI’a nasıl sordum?

    “Bu CalculateDiscount metodunu, CalculateBasePrice ve ApplyDiscount olarak iki metoda ayır. Her biri tek bir iş yapmalı.”

  • Sonuç: Testler tek tek çalıştırılır, her adımda yeşil kalır ✅.

2️⃣ Anlamlı İsimler: Rename

  • Amaç: Değişken/metot isimlerini domain diline yakın hale getirmek.
  • Prompt örneği:

    calc metodunu CalculateTotalPrice olarak yeniden adlandır, parametre isimlerini de unitPrice, quantity yap.”

  • Etki: Code review’da “ne yapıyor bu?” sorusu ortadan kalkar ❗️.

3️⃣ Parametre Grubu: Introduce Parameter Object

  • Amaç: Çoklu parametre yerine tek bir nesne göndermek.
  • AI isteği:

    OrderService.PlaceOrder(customerId, productId, quantity, shippingAddress, billingAddress) imzasını OrderRequest DTO’su ile değiştir.”

  • Avantaj: Yeni parametre eklendiğinde imza değişmez, geriye dönük uyumluluk korunur 🔁.

📄 Gerçek Kod Karşılaştırması (C#)

Aşağıda eski hali ve Copilot önerisiyle refaktörlenmiş hali diff formatında görebilirsin. Değişiklikler tek tek uygulandı, her adımda testler yeşil kaldı.

- public decimal CalculateDiscount(decimal price, int quantity, bool isVip, string couponCode)
- {
-     decimal basePrice = price * quantity;
-     decimal discount = 0;
- 
-     if (isVip)
-         discount += basePrice * 0.10m;
- 
-     if (!string.IsNullOrEmpty(couponCode))
-         discount += basePrice * 0.05m;
- 
-     return basePrice - discount;
- }
+ // 1️⃣ Extract Method: Base price hesaplama ayrıldı
+ private decimal CalculateBasePrice(decimal price, int quantity)
+     => price * quantity;
+
+ // 2️⃣ Extract Method: İndirim mantığı ayrıldı
+ private decimal CalculateDiscount(decimal basePrice, bool isVip, string couponCode)
+ {
+     decimal discount = 0;
+ 
+     if (isVip)
+         discount += basePrice * 0.10m;
+ 
+     if (!string.IsNullOrEmpty(couponCode))
+         discount += basePrice * 0.05m;
+ 
+     return discount;
+ }
+
+ // 3️⃣ Public API: Parametre objekti ile temizlendi
+ public decimal CalculateFinalPrice(OrderRequest request)
+ {
+     var basePrice = CalculateBasePrice(request.UnitPrice, request.Quantity);
+     var discount = CalculateDiscount(basePrice, request.IsVip, request.CouponCode);
+     return basePrice - discount;
+ }
+
+ // 4️⃣ Introduce Parameter Object
+ public record OrderRequest(
+     decimal UnitPrice,
+     int Quantity,
+     bool IsVip,
+     string CouponCode
+ );

Ne oldu?

  • Extract Method ile iki küçük, test edilebilir metod oluştu.
  • Rename sayesinde CalculateFinalPrice ve OrderRequest isimleri domain’e uygun hale geldi.
  • Introduce Parameter Object ile 4 parametre tek bir record altında toplandı, gelecekte yeni alan eklemek breaking change yaratmıyor.

✅ Testler ve Code Review

Adım Test Sonucu Review Süresi
Extract Method 🟢 Yeşil -30 %
Rename 🟢 Yeşil -20 %
Parameter Object 🟢 Yeşil -25 %

Küçük adımlarla gittiğimizde her commit tek bir mantıksal değişiklik içerir, bu da reviewer’ın odaklanmasını kolaylaştırır 🎯.


Özet: AI’yi “büyük refaktör” motoru değil, asistan gibi kullan. Her seferinde bir refaktör türü (Extract, Rename, Parameter Object) iste, testleri çalıştır, commit at. Böylece güvenli, izlenebilir ve hızlı bir refaktör süreci yaşarsın 🚀.


🔁 CI/CD Entegrasyonu ve Geri Alma Stratejileri: AI Üretilen Kodun Güvenilirliği

Hadi başlayalım! 🤖 Kısa bir süre önce AI tarafından yazılan ve bir PR'ye eklemiş bir kartı hayal et. İyi fikir, değil mi? Ancak iş production'a gelince, garantilere ihtiyacımız var. 🚀 İşte tam burada CI/CD, statik analiz, mutasyon testi, canary dağıtımı ve feature flag'ler devreye giriyor.

Neden bu kontroller gerekli?

  • Statik analiz 🎯 kodu derlemeden önce basit hataları, stil sorunlarını ve güvenlik zayıflıklarını yakalar.
  • Mutasyon testi 🔬 test kapsamı riski hakkında hızlı bir ikinci görüş sunar, böylece körlemesine test yapan algoritmalardan kaçınırız.
  • Canary dağıtımı ⛓️ yeni kodu gerçek kullanıcılara küçük bir örnek ile sunar, böylece gerçekten sorun olup olmadığını görürüz.
  • Feature flag'ler 🛠️ eğer bir şey ters giderse devre dışı bırakılabilen bir "acil durum düğmesi" sağlar.

AI ile üretilen bir PR için kontrol akışını nasıl yapılandırabiliriz?

Aşağıda, bir AI tarafından üretilen commit'de her dosya değişikliği yapıldığında çalışan, okunabilir ve modüler bir GitHub Actions akışını görebilirsiniz. Bunu kopyalayıp kendi repository'nize yapıştırın, .github/workflows/ai-checks.yml olarak kaydedin ve geri kalanını otomatik hale getirin.

name: AI Kodu Kontrol Et

# 🍃 Her branch'te ve PR'de çalış
on:
  push:
    branches: [ main, develop ]
  pull_request:
    types: [ opened, synchronize, reopened ]

jobs:
  static-analysis:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Node projesi ayarla
        uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'
      - run: npm ci
      - name: Eslint ile statik analiz
        run: npm run lint
      - name: Prettier ile kod biçimlendirme kontrolü
        run: npm run format:check

  unit-and-mutation:
    needs: static-analysis   # 🛑 analiz başarısız olursa ileri gitme
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Node projesi ayarla
        uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'
      - run: npm ci
      - name: Unit test'leri çalıştır
        run: npm test
      - name: Mutasyon testlerini çalıştır
        run: npm run mutate
        continue-on-error: true   # ⛔ mutasyon başarısız olsa bile dağıtım devam edebilir

  canary-deploy:
    needs: unit-and-mutation
    runs-on: ubuntu-latest
    if: github.ref == 'refs/heads/develop'   # 🎯 yalnızca develop branch'inde canary yap
    steps:
      - uses: actions/checkout@v4
      - name: Canary ısıtasını tetikle (örnek)
        id: canary
        run: |
          echo "::set-output name=url::https://canary.yourapp.com"
          echo "::set-output name=flag::ai-feature-enabled"
      - name: Feature flag'yi etkinleştir
        run: |
          # Örnek: LaunchDarkly CLI kullanarak flag'yi etkinleştir
          ld-cli features:toggle --project prod --feature ${{ steps.canary.outputs.flag }} --target canary

  rollback-if-needed:
    needs: canary-deploy
    runs-on: ubuntu-latest
    if: failure() && github.ref == 'refs/heads/develop'
    steps:
      - name: Canary'yi otomatik olarak geri al
        run: |
          echo "⚠️ Canary başarısız oldu, üretimdeki flag'yi kapat"
          # Örnek: LaunchDarkly CLI kullanarak flag'yi kapat
          ld-cli features:toggle --project prod --feature ${{ env.CANARY_FLAG }} --target production --off

Bu sayede ne oluyor?

  • Birleştirme kontrolü otomatik 🎯 → AI tarafından üretilen kod her PR'de birden fazla katmana tabi tutulur.
  • Başarısızlıklar erken 📢 → statik analiz başarısız olduğunda diğer işleri durdurur, böylece mutasyon testleri veya canary dağıtımları bozuk bir derlemeden geçmez.
  • Güvenli bir geçiş 🔁 → canary dağıtımı yalnızca develop branch'inde çalışır ve bir feature flag'i etkinleştirir. Her şey bozulduğunda, bir sonraki commit'te otomatik olarak geri alırız.
  • İnce ayar ✅ → mutasyon testi başarısız olsa bile dağıtım devam edebilir, böylece yüksek riskli unit test'ler gündelik işi aksatmayı önler.

Rollback stratejileri

  1. Feature flag'li geri alma 🛠️
    • Canary dağıtımında bir feature flag'i etkinleştiririz.
    • Flag'i hemen kapatabiliriz (örneğin, CLI veya yönetici paneli aracılığıyla).
  2. A/B testi sonlandırmak için geçiş 🔁
    • Yeni kodu hala kullanıcılar için devre dışı bırakarak verileri normal seviyeye döndürürüz.
  3. Üretimi bir önceki commit'e geri alma
    • Canary gerilemesi tetiklenirse, git revert veya container registry'den önceki etiket ile geri alırız.

Bunlar sadece birkaç basit adım. Bunları deploy pipeline'ına ekleyerek AI tarafından üretilen kodun güvenle production'a gitmesini sağlayabiliriz. 🛡️

Hızlı kontrol listesi

  • Statik analiz (Eslint/Prettier) →
  • Unit testler →
  • Mutasyon testi →
  • Canary deploy + feature flag →
  • Otomatik geri alma politikası →

Devam et! Bunu repository'nize ekleyin, AI ile geliştirme yapın ve pipeline'ın sizi koruduğunu bilin. Daha güvenli bir kodlama süreci dilerim! 🚀


🛠 Pratik Senaryo: Eski Bir Monolith’ten Mikroservise Geçişte AI Kullanımı (Kod Örneği)

Hazırsan başlayalım 🚀
Küçük bir e‑ticaret monolith’inden Sipariş Servisini ayırıp Strangler Fig desenini uyguluyoruz. Süreç üç ana adımdan oluşuyor: karakterizasyon testi, yeni servis iskeleti ve API Gateway yönlendirmesi. Her adımda hangi AI aracının (Copilot, Cursor, lokal model) ne yaptığını da not alacağız.


1️⃣ Karakterizasyon Testi Yazımı – Copilot 🤖

Önce mevcut OrderController davranışını kilitlememiz lazım. Copilot’a “Express route için Jest + Supertest karakterizasyon testi yaz” dedik ve şu çıktıyı aldık:

// tests/orderCharacterization.test.js
const request = require('supertest');
const app = require('../src/app');          // monolith entry point
const Order = require('../src/models/Order');

describe('OrderService – karakterizasyon testleri', () => {
  beforeAll(async () => {
    // Test veritabanını temizle ve seed data ekle
    await Order.deleteMany({});
    await Order.create({ _id: '507f1f77bcf86cd799439011', userId: 'u1', total: 100, status: 'CREATED' });
  });

  it('GET /orders/:id → 200 ve doğru sipariş döner', async () => {
    const res = await request(app).get('/orders/507f1f77bcf86cd799439011');
    expect(res.status).toBe(200);
    expect(res.body.total).toBe(100);
    expect(res.body.status).toBe('CREATED');
  });

  it('POST /orders → 201 ve sipariş oluşturur', async () => {
    const payload = { userId: 'u2', items: [{ productId: 'p1', qty: 2, price: 50 }] };
    const res = await request(app).post('/orders').send(payload);
    expect(res.status).toBe(201);
    expect(res.body.total).toBe(100);
    expect(res.body.status).toBe('CREATED');
  });
});

Ne oldu?

  • Copilot mevcut route’ları okuyup beklenen JSON yapısını ve status kodlarını otomatik çıkardı.
  • Testler CI/CD’de her push’ta çalışarak regresyon riskini sıfırlıyor ✅.

2️⃣ Yeni Servis İskeleti Oluşturma – Cursor 🧩

Testler yeşil çıkınca OrderService’i ayrı bir repo’ya taşıma vakti. Cursor’da “Node.js/Express micro‑service scaffold with Docker” promptunu verdik. İki dosya çıktı:

package.json

{
  "name": "order-service",
  "version": "1.0.0",
  "description": "Sipariş mikro‑servisi",
  "main": "src/index.js",
  "scripts": {
    "start": "node src/index.js",
    "dev": "nodemon src/index.js",
    "test": "jest --coverage"
  },
  "dependencies": {
    "express": "^4.19.2",
    "mongoose": "^8.5.0",
    "dotenv": "^16.4.5"
  },
  "devDependencies": {
    "jest": "^29.7.0",
    "supertest": "^7.0.0",
    "nodemon": "^3.1.4"
  }
}

Dockerfile

# ---- Build stage ----
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production

# ---- Runtime stage ----
FROM node:20-alpine
WORKDIR /app
COPY --from=builder /app/node_modules ./node_modules
COPY . .
EXPOSE 3000
ENV NODE_ENV=production
CMD ["node", "src/index.js"]

Cursor’un katkısı

  • Best‑practice çoklu aşamalı (multi‑stage) Docker imajı üretti 🐳.
  • npm ci ile deterministik bağımlılık kurulumu sağlandı.
  • devDependencies test stage’inde kalacak, production imajı küçük kaldı ✅.

3️⃣ API Gateway Yönlendirme Kuralı – Lokal Model (Ollama + CodeLlama) 🔀

Monolith’teki /orders trafiğini yeni servise yönlendirmek için NGINX (veya Kong/Envoy) konfigürasyonuna bir route eklememiz gerekiyordu. Lokal model’e “NGINX upstream order-service 3000 portuna proxy_pass kuralı yaz” dedik ve şu snippet’i elde ettik:

# /etc/nginx/conf.d/upstream-order.conf
upstream order_service {
    server order-service:3000;   # docker‑compose service adı
    keepalive 32;
}

server {
    listen 80;
    server_name api.example.com;

    location /orders/ {
        proxy_pass http://order_service/;
        proxy_http_version 1.1;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header Connection "";
    }
}

Lokal model ne yaptı?

  • Upstream bloğu ile service discovery (Docker DNS) kullanarak statik IP’den kurtulduk.
  • keepalive ve Connection "" ile HTTP/1.1 keep‑alive performans artışı sağlandı ⚡.
  • Monolith’teki eski route’u yorum satırı yapıp canary trafikle yeni servise geçiş yapabiliriz 🎯.

📋 Hangi Araç Ne Yaptı? (Özet Tablo)

Adım Araç Üretilen Çıktı Not
Karakterizasyon testi GitHub Copilot orderCharacterization.test.js Mevcut kodu okuyup test iskeleti çıkardı
Servis iskeleti (package.json + Dockerfile) Cursor package.json, Dockerfile Multi‑stage build, production‑ready
API Gateway kuralı Lokal Model (CodeLlama via Ollama) upstream-order.conf Docker DNS + keep‑alive optimizasyonu

Sonuç:
Testler yeşil, Docker imajı build ediliyor, Gateway yönlendirme hazır. Artık Strangler Fig ile monolith’ten OrderService’i parça parça çekebilir, canary dağıtım yapabilir ve güvenle mikroservis yolunda ilerleyebiliriz 🚀.

Bir sonraki yazıda canary stratejisi ve observability (metrics, tracing) üzerine konuşacağız. Görüşmek üzere! 👋


🎯 Bonus Tavsiye: Takımınızı AI Destekli Modernizasyon Kültürüne Alıştırmak

Teknik araçlar tek başına yeterli değil. Alışkanlıkları değiştirmeden gerçek bir modernizasyon olmuyor. Benim deneyimimde şu dört ritüel takım kültürünü kökten sallıyor 🎯:

  • AI Pair Programming Günü – Her hafta bir gün, iki geliştirici aynı ekranda bir AI asistanıyla (Copilot, Cursor, Codeium…) kod yazar. Birisi “sürücü”, diğeri “navigatör” rolünde; AI’dan gelen önerileri canlı tartışıp kabul/red ediyoruz.
  • Prompt Paylaşım Kütüphanesi – Takımın ortak bir repo’sunda prompts/ klasörü tutuyoruz. Herkes en sık kullandığı, işe yarayan prompt’ları (ör. “Refactor this legacy Java class to Spring Boot 3”, “Generate unit tests for this REST endpoint”) buraya commit atıyor. Böylece “tekrar icat etme” yok oluyor.
  • AI Code Review Checklist – PR şablonuna şu maddeleri ekledik:
    1. AI tarafından önerilen değişiklikler insan gözüyle doğrulandı mı?
    2. Güvenlik/performans riskleri (SQL injection, N+1 query…) kontrol edildi mi?
    3. Test coverage %80’in üzerinde mi?
    4. Prompt versiyonu ve model adı belirtildi mi?
  • Bilgi Paylaşım “AI Demo” Toplantısı – Ayda bir 30 dakikalık bir “show‑and‑tell”: kimse yeni bir model, eklenti veya prompt keşfettiğinde ekran paylaşarak nasıl kullandığını anlatıyor. Sorular soruluyor, notlar alınıyor, kütüphane güncelleniyor.

Hemen bugün deneyebileceğin 3 somut eylem ✅

  1. Bu hafta bir “AI Pair Programming” oturumu planla – Takvimine 1 saat blok koy, bir arkadaşını davet et, birlikte bir küçük refactor yapın.
  2. prompts/ reposunu oluştur ve ilk 3 prompt’u commit at – Örn. “Generate JUnit 5 tests for Spring service”, “Explain this legacy COBOL paragraph”, “Suggest modern Dockerfile for this Java app”.
  3. PR şablonuna AI Code Review maddelerini ekle – 5 dakika sürer, ama gelecekteki review’lerde zaman kazandırır.

Bu küçük adımlar birikince, takım “AI’yi birer araç olarak değil, günlük bir alışkanlık olarak kullanmaya başlar. Ve o an modernizasyon artık bir proje değil, kültür haline gelir 🚀.


🎯 Son Söz: Teknik Borç Ödemesi Bir Maraton, Sprint Değildir

Legacy modernizasyonu bir süreçtir 🎯
AI bu yolda navigatördür 🤖
Sürücü sizsiniz 🚗

  • Küçük, ölçülebilir adımlarla başlayın
  • Her geliştirme döngüsünde gerçek değer yaratın
  • Takımınızla şeffaf iletişim kurun
  • Hataları öğrenme fırsatı olarak görün

İlk küçük adımı bugün atın
Geleceğinizde daha temiz, daha hızlı ve daha güvenilir bir kod bazı sizi bekliyor.

Hadi birlikte bu maratona başlayalım! 🏁


Bu içerik tamamen yapay zeka destekli otomasyon sistemi ile üretilmiştir.

Burak Sağlık

Burak Saglik

©2024 Desing and Developed by @Burak Sağlık

All rights reserved