# Kentsel Dönüşüm #2: Yık-Yap mı, Güçlendirme mi? – Baştan Yazmanın Faturası

> Ajanla baştan yazmak hiç bu kadar ucuz görünmemişti, ama faturanın büyük kalemi kodda yazmıyor. WordPress'ten çıkan bir blog ile servisi değişen bir PHP kütüphanesi üzerinden yık-yap ve güçlendirme kararının dört sorusu.

- Yazar: [Halit Yeşil](https://halityesil.com/hakkimda/)
- Yayın tarihi: 2026-10-31
- Dil: Türkçe
- Kaynak adres: https://halityesil.com/kentsel-donusum-2-yik-yap-guclendirme/
- İngilizce sürümü: https://halityesil.com/en/urban-renewal-2-rewrite-or-refactor.md
- Seri: [Kentsel Dönüşüm](https://halityesil.com/seri/kentsel-donusum/), bölüm 2
- Kategori: Yazılım Mimarisi
- Etiketler: AI Ajanları, Claude Code, Codex, Cursor, Kentsel Dönüşüm, Legacy Kod, OpenCode, php, Refactoring, WordPress
- Lisans: https://creativecommons.org/licenses/by-nc/4.0/
- Atıf: Halit Yeşil. “Kentsel Dönüşüm #2: Yık-Yap mı, Güçlendirme mi? – Baştan Yazmanın Faturası”. halityesil.com, 2026-10-31. https://halityesil.com/kentsel-donusum-2-yik-yap-guclendirme/

---

[Kentsel Dönüşüm #1](https://halityesil.com/kentsel-donusum-1-risk-tespiti/)'i bir haritayla kapatmıştık: on bir bulgu, bir sıra ve tablonun en altında ertelenmiş tek bir satır. "`http://`, kaynakta kimlik bilgisi, eski SOAP adresi: yık-yap mı güçlendirme mi kararıyla." O satırı artık erteleyemiyoruz, çünkü binanın bağlı olduğu şebeke değişmiş. Bu bölümde **legacy kod refactoring** işinin ilk büyük sorusuna bakıyoruz: eski binayı güçlendirmek mi, yıkıp yeniden yapmak mı?

Ajanla çalışırken bu soru eskisinden sık çıkıyor karşımıza. Baştan yazmak hiç bu kadar ucuz görünmemişti. Boş bir dosyaya temiz bir sınıf yazdırmak, ajana altı yıllık bir dosyayı anlatmaktan daha az bağlam istiyor. Görünen fiyat düştü. Fatura düşmedi, çünkü faturanın büyük kalemi kodda yazmıyor.

Tezim şu: **baştan yazmanın faturası, eski kodun bilip hiçbir yere yazmadığı şeylerdir. Güçlendirmenin faturası ise eski kodun bildiğini sandığı ama artık doğru olmayan şeyler.** İkisini de son birkaç haftada iki ayrı binada gördüm.

## Yirmi altı yıllık uyarı

Konunun klasik metni Joel Spolsky'nin 6 Nisan 2000 tarihli [Things You Should Never Do, Part I](https://joelonsoftware.com/2000/04/06/things-you-should-never-do-part-i/) yazısı. Netscape 6.0 ilk beta sürümüne giderken yazılmış; 5.0 hiç çıkmamış, 4.0'ın üzerinden neredeyse üç yıl geçmiş. Spolsky kodu sıfırdan yazma kararını, bir yazılım şirketinin yapabileceği en kötü stratejik hata olarak koyuyor.

Gerekçesinin özü bugün de ayakta. Eski kodda tuhaf görünen satırlar çoğu zaman hata düzeltmesidir ve her biri, Spolsky'ye göre, bulunana kadar haftalarca gerçek kullanım görmüştür. İki karakterlik bir düzeltmenin arkasında haftaların emeği durur. Kodu silince o emeği de silersiniz. Düzeltme hiçbir belgede yazmadığı için kaybolduğunu ancak hata geri geldiğinde anlarsınız.

[Vibe Coder 5.1](https://halityesil.com/vibe-coder-5-1-teknik-borc/)'de Ward Cunningham'ın borç tanımını konuşmuştuk; o da erken kestirmenin hızlı bir yeniden yazımla ödenmesini öneriyordu. İkisi çelişmiyor. Cunningham, kodu yazan ekibin kodun ne bildiğini henüz hatırladığı erken bir yeniden yazımdan söz ediyor. Spolsky ise yılların biriktirdiği bir binayı yıkmaktan. Fark binanın yaşında değil, binanın bildiklerinin kimin kafasında durduğunda.

## Aynı evde iki karar

Bu blog son beş hafta içinde iki kararı da yaşadı.

**Güçlendirme:** Eylül'de sunucudaki PHP'yi 8.1'den 8.5'e taşıdık ve [bütün yolu yazdık](https://halityesil.com/php-surum-yukseltme-8-1-den-8-5-e/). Kolonlar yerinde kaldı, kod yerinde kaldı. Dört basamaklık sıçramanın faturası bir deprecation seli, bir log tuzağı ve `.htaccess`'ten düşen tek bir `#` işaretiydi.

**Yık-yap:** 5 Ekim 2026'da blog WordPress'ten çıktı. Yerine çerçevesiz, native PHP 8.3 ve MariaDB ile yazılmış bir platform geldi. Yazılar Markdown; yayın akışı bir REST API'den ve ajanlara açılmış bir MCP sunucusundan geçiyor. Bu yazının taslağı da o yoldan kaydedildi.

Neden güçlendirme yetmedi? Çünkü sorun kolonlarda değil, binanın kime göre yapıldığındaydı. WordPress milyonlarca sitenin birbirinden farklı ihtiyacına cevap vermeye ve geriye dönük desteği korumaya çalışan bir platform. Bu gayretin yükü her kullanıcının sırtına biniyor: dışarıya uzanan bağımlılıklar, kontrolüm dışında genişleyen bir kod tabanı, yılların getirdiği hantallık. Güvenlik çekincesi de buradan doğuyor. Tema temizlesem, eklenti azaltsam da sürekli başkasının koduna bağlı kalacaktım. O kalabalığın bir parçası olmak istemedim.

İkinci sebep daha gündelik. Yapacağım küçük bir değişiklik için bile birden fazla eklentiyi, modülü, temayı birlikte idare etmek bir orkestra yönetmek gibiydi. Ben orkestra şefi değilim. Bu blogun işi, günlük iş akışında başıma gelen vakaları başkalarına aktarmak; bunu yaparken bir gazeteci gibi bütün hayatımı ona veremem, hayatın devam etmesi lazım. İstediğim kafamın rahat olduğu, akışkan bir sistemdi: bakım maliyeti düşük, güvenilir, stabil.

### Faturanın kalemleri

Taşımanın ilk günü önüme gelen liste, Spolsky'nin uyarısının küçük ölçekli bir provasıydı. Kalemlerin hiçbiri yeni platformun eksik bir özelliği olarak görünmüyordu. Hepsi eski sistemin ya kendisi hallettiği ya da hiç sorun olarak göstermediği işlerdi:

- **Numaralar değişti.** Doksan üç yazı ve sekiz sayfa yeni ID'lerle geldi. Eski numarayla yenisini eşlemek için slug'a güvenmek gerekti. Çeviri eşleşmesini taşıyan alandaki sayı eski yazı numarasına benziyordu, ama değildi: çeviri eklentisinin kendi grup numarasıydı.
- **Kategoriler düştü.** On altı yazı kategorisiz geldi, on beşi Vibe Coder serisinin bölümüydü. Yeni sistemde seri ile kategori ayrı şeyler; seri bir yazıyı kategori sayfasına taşımıyor.
- **Sıra kaydı.** Seri sıra numaralarından bazıları karışık geldi; index yazısı "1" numarasını almıştı.
- **Eski adresler.** Arama motorlarının ve başka sitelerin bildiği her adres, yeni rotalarla çakışmadan yaşamak zorunda. Bu bir tapu meselesi ve serinin sekizinci bölümünde kendi başlığıyla duruyor.
- **Dosya adı çakışması.** En öğretici kalem bu. WordPress'te yüklenen dosyaya çakışmasız bir ad veren bir fonksiyon var, [`wp_unique_filename()`](https://developer.wordpress.org/reference/functions/wp_unique_filename/), 2.5.0 sürümünden beri. Yıllarca varlığını düşünmedim bile. Yeni platformun ilk akşamı bir kapak görselinin silindiğini sandık. Kanıta bakınca silme değil üzerine yazma çıktı: ad seçimi diske bakıyordu, ama adı seçmekle dosyayı yazmak arasında bir boşluk vardı. Aynı anda gelen iki yükleme aynı adı alabiliyordu.

Düzeltmenin fikri, kontrol ile oluşturmayı tek adıma indirmek. PHP'de `fopen()`'ın `x` kipi tam bunu yapıyor:

```php
<?php

declare(strict_types=1);

/**
 * Klasörde boş bir dosya adı bulur ve onu hemen sahiplenir.
 * "x" kipi dosya zaten varsa açmaz: kontrol ile oluşturma tek adımdır,
 * aynı anda gelen iki yükleme aynı adı alamaz.
 */
function bosAdAl(string $klasor, string $kok, string $uzanti): string
{
    for ($sira = 0; $sira < 100; $sira++) {
        $ad = $sira === 0 ? "{$kok}.{$uzanti}" : "{$kok}-{$sira}.{$uzanti}";
        $tutamac = @fopen("{$klasor}/{$ad}", 'xb'); // varsa false: "File exists"

        if ($tutamac !== false) {
            fclose($tutamac);
            return $ad; // ad artık bizim; dosyanın içeriği bundan sonra yazılır
        }
    }

    throw new RuntimeException("Boş ad bulunamadı: {$kok}.{$uzanti}");
}
```

Aynı klasörde üç kez çağırınca `kapak.webp`, `kapak-1.webp`, `kapak-2.webp` veriyor; dolu bir adı açmaya çalışan `fopen()` "Failed to open stream: File exists" deyip `false` dönüyor.

**Acı Gerçek:** Ajana "WordPress'in yerine geçecek bir blog yaz" deseydik bu listeyi o da çıkaramazdı. Liste kodda değil, eski sistemin davranışındaydı. Okuyarak bulunmuyor; ya taşırken çarparak ya da taşımadan önce davranışı yazılı hâle getirerek bulunuyor.

## İkinci bina: şebeke değişti

Altınkaynak'a dönelim. #1'de kütüphanenin konuştuğu SOAP adresinin açılmadığını, Altınkaynak'ın [servis sayfasının](https://altinkaynak.com/Araclar/Servisler) bugün yalnızca iki JSON ucu listelediğini görmüştük. Bu sabah (29 Ekim 2026, 11:33 güncellemesi) iki uca da tarayıcıdan baktım. Alan adları aynı: `Kod`, `Aciklama`, `Alis`, `Satis`, `GuncellenmeZamani`; yanlarına bir de `IsMobile` gelmiş. Döviz ucu 14, altın ucu 53 kalem dönüyor.

"Alan adları aynıysa adresi değiştirir geçeriz" cümlesi tam burada kuruluyor. Üç ayrıntı o cümleyi bozuyor.

**1. Noktanın anlamı tersine dönmüş.** [2020 yazısının](https://halityesil.com/php-ile-altinkaynak-doviz-kuru-cekmek/) örnek çıktısında Has Altın Toptan'ın alışı `464.14`: nokta ondalık ayırıcı. Bugün aynı kod (`HH_T`) `"6.450"` dönüyor ve bu altı virgül kırk beş değil, altı bin dört yüz elli lira. Nokta artık binlik ayırıcı. Döviz ucunda virgül ondalık (`"49,794"`), altın kalemlerinde ondalık hiç yok (`"6.450"`, `"3.208.928"`), gümüşte ikisi birden (`"19.908,45"`).

**2. Bir metodun karşılığı yok.** Kütüphanenin üç genel metodu var: `GetCurrency`, `GetGold`, `GetMain`. Yeni serviste iki uç var. "Çok kullanılan altın ve döviz" listesini dönen `GetMain` için bir karşılık yok.

**3. Bir kod değişmiş.** 2020 çıktısında onsun kodu `ONS`. Yeni altın ucunda ons `XAUUSD` olarak geliyor. Listede `ONS` arayan bir çağıran, hata almadan boş sonuç alır.

Şimdi bir ajanın, ya da yorgun bir akşam bizim, yapacağımız en makul görünen düzeltmeyi deneyelim. Eski kod `floatval()` kullanıyor; virgülü noktaya çevirmek yetecek gibi görünüyor. Bu sabahki gerçek değerlerle, PHP 8.3.6'da:

| Kalem | Servisten gelen | `floatval()` | Virgülü noktaya çevir | Doğrusu |
|---|---|---|---|---|
| Dolar alış | `49,794` | 49 | 49.794 | 49.794 |
| Japon yeni alış | `0,3110` | 0 | 0.311 | 0.3110 |
| Has altın alış | `6.450` | 6.45 | 6.45 | 6450 |
| 500 gr altın alış | `3.208.928` | 3.208 | 3.208 | 3208928 |
| 250 gr gümüş alış | `19.908,45` | 19.908 | 19.908 | 19908.45 |

Makul düzeltme dövizi kurtarıyor, altını ve gümüşü bin kat ucuzlatıyor. Hata yok, uyarı yok, test yok. #1'de "çalışan ama yanlış fiyat basan kod" demiştik. Bu tablo ondan beter, çünkü yarısı düzelmiş göründüğü için kimse ikinci kez bakmaz.

Doğrusu Türkçe sayı biçimini açıkça tanımak ve tanımadığı bir biçimde susmak yerine düşmek:

```php
<?php

declare(strict_types=1);

/**
 * Altınkaynak'ın JSON uçlarındaki fiyat metnini ondalık metne çevirir.
 * Biçim Türkçe: nokta binlik, virgül ondalık. "6.450" altı bin dört yüz elli demek.
 * Bilerek float döndürmez; para hesabı bcmath ya da kuruş cinsinden tamsayıyla yapılır.
 */
function fiyatOku(string $metin): string
{
    $metin = trim($metin);

    if (preg_match('/^(?:\d{1,3}(?:\.\d{3})+|\d+)(?:,\d+)?$/', $metin) !== 1) {
        throw new UnexpectedValueException("Beklenmeyen fiyat biçimi: '{$metin}'");
    }

    return str_replace(['.', ','], ['', '.'], $metin);
}
```

Tablonun son sütunu bu fonksiyonun çıktısı. `fiyatOku('6.45')` ise `Beklenmeyen fiyat biçimi: '6.45'` diyerek istisna fırlatıyor; servis bir gün biçimi değiştirirse sessizce yanlış fiyat basmak yerine bağırıyor. Sınırı da açık: `"6.450"`'yi İngilizce biçimde, altı virgül kırk beş anlamında gönderen bir kaynakta bu fonksiyon yanılır. Biçim, servisle aramızdaki sözleşmenin bir parçası. Onu kodun dışında, bir yerde yazılı hâle getirmek gerekiyor.

## Karar tablosu: güçlendirme mi, yık-yap mı?

İki binayı yan yana koyunca sorular şunlar:

| Soru | Güçlendirmeye iter | Yık-yapa iter | Blog | Altınkaynak |
|---|---|---|---|---|
| Zemin taşıyor mu? (dil, çalışma ortamı, bağımlılıklar) | Taşıyor | Çürümüş ya da yön değişmiş | Bağımlılık ve bakım yükü | PHP 8.3'te `php -l` temiz |
| Tapu kimde? (dışarıdan size bağlı olanlar) | Çok çağıran, açık bir sözleşme | Sözleşme küçük ve tamamen sizde | Eski adresler, RSS, arama motorları | Üç genel metot, `Currency` nesnesi ve beş alanı |
| Eski davranış yazılı mı? | Değil: önce yazılı hâle getirin | Yazılı: test, döküm, trafik kaydı var | Değildi; fatura kalemleri buradan çıktı | Değil; depoda tek test yok |
| Değişen ne? | Tesisat | Taşıyıcı sistem | Taşıyıcı sistem | Tesisat: SOAP gidiyor, JSON geliyor |

Altınkaynak için karar **güçlendirme**. Zemin sağlam, değişen tesisat. Korunacak en değerli şey de tapu: bu kütüphaneyi kullanan bir kod `GetCurrency()` çağırıp `$Currency->buying` okuyor ve ona dokunan her değişiklik kiracının kapısını kırmak demek. Planım şu: SOAP katmanının yerine JSON okuyan bir katman gelecek, genel metotlar yerinde kalacak. `GetMain()` karşılığı olmayan bir sözleşme; onu uydurma bir listeyle doldurmak yerine açıkça kullanımdan kaldırıp anlamlı bir istisna fırlatmak daha dürüst. `ONS` ile `XAUUSD` arasına bir eşleme girecek. `buying` ve `selling` alanlarının türü ise tapuyu değiştiren tek madde: `float` kalırsa para hatası sürer, `string` olursa çağıran kod kırılabilir. Bu yüzden değişiklik ana sürüm numarasıyla ve yazılı bir notla gelmeli.

Blog için yık-yap kararının doğru olup olmadığını bir aylık bir sistem söyleyemez. Söyleyebildiğim, faturanın nereden geldiği: kalemlerin hepsi tablonun üçüncü satırından, yazılı olmayan davranıştan çıktı.

## Ajana bu kararı nasıl sordurmalı?

Ajan bu soruda tarafsız bir danışman değil. Önünde iki iş var: altı yıllık bir dosyayı anlamak ya da boş bir dosyaya temiz bir sınıf yazmak. İkincisi ona hem daha kısa hem daha az riskli görünür, çünkü eski kodun bildiklerini görmediği sürece onların kaybını da görmez. O yüzden ona "yeniden yazalım mı?" diye sormuyoruz. Kararın girdisini istiyoruz.

Görev metni #1'deki salt okuma kiplerinde çalışacak şekilde yazıldı: Claude Code'da plan modu, Codex'te `--sandbox read-only`, Cursor'da Ask, OpenCode'da Plan ajanı. Kiplerin farkları ve atılabilir kopya kuralı [#1'de](https://halityesil.com/kentsel-donusum-1-risk-tespiti/) duruyor.

```markdown
Görev: Bu depo için "baştan yaz mı, adım adım dönüştür mü" kararına girdi hazırla.
Kararı verme. Hiçbir dosyayı değiştirme, ağ isteği yapma, raporu yalnızca
bu sohbete yaz.

1. Sözleşme envanteri: dışarıdan çağrılan her genel sınıf, metot, alan,
   adres ve komut. Her biri için: kim çağırıyor olabilir, kırılırsa nasıl
   fark edilir.
2. Örtük davranış envanteri: tuhaf görünen ama bir hata düzeltmesi olabilecek
   her satır (özel durum, sihirli sayı, sessizce yutulan hata, biçim
   dönüşümü). Her biri için dosya:satır ve "bu neden burada olabilir".
3. Dış varsayımlar: servis, veri biçimi, yerel ayar, saat dilimi, karakter
   kodlaması. Hangisi koddan anlaşılıyor, hangisi ancak çalıştırınca?
4. Baştan yazımda kaybolma riski en yüksek beş madde, gerekçesiyle.

Son bölüm: Okuyarak öğrenemeyeceğin, bana sorman gereken sorular.
```

Altınkaynak'ta bu envanterin üçüncü maddesi tek başına kararın yönünü belirliyor. Sayı biçimi koddan anlaşılmıyor; ancak servise bakınca anlaşılıyor. İyi bir ajan bunu "okuyarak öğrenemediklerim" bölümüne yazar, kötü bir şartname ise ona bu bölümü hiç sormaz. Bu görev metnini bu yazı için ajanların içinde çalıştırmadım; aynı görevi farklı ekosistemlerde koşturup sonuçları karşılaştırmak serinin dördüncü bölümü Kaçak Kat'ın işi.

## Ne zaman yık-yap doğru, ne zaman değil?

**Doğru:** Zemin gerçekten çürümüşse, yani desteği biten bir platform ya da artık kurulamayan bağımlılıklar varsa. Sözleşme küçük ve tamamen sizin elinizdeyse. Eski davranışı önce yazılı hâle getirebiliyorsanız: test, döküm ya da gerçek trafik kaydıyla. Yazılı davranış, yıkımdan önce çekilmiş tapu fotokopisidir.

**Yanlış:** Tek gerekçe "kod çirkin" ise. Eski davranışı kimse yazmamışsa ve yazmaya vakit de ayrılmayacaksa. Ajan, eskiyi anlamadan yenisini yazmayı önerdiği için.

Güçlendirmenin de yanlış olduğu yer var. Taşıyıcı sistemi değişmesi gereken bir binayı boya badanayla ayakta tutmak faturayı ertelemekten başka bir şey değil; Cunningham'ın faizi orada işlemeye devam eder.

## Tapu yerinde, tesisat sökülecek

Bu bölümde de binaya çivi çakmadık, ama bu sabah çekilen iki JSON yanıtı serinin ilk somut malzemesi oldu. Bir sonraki bölümde, Kentsel Dönüşüm #3: İskele Kurmak – Davranışı Dondur, Sonra Dokun'da, o yanıtları sabitleyip kütüphanenin bugünkü davranışını, hatalarıyla birlikte teste çevireceğiz. Tesisat ancak iskele kurulduktan sonra sökülür. Serinin bütün bölümleri [Kentsel Dönüşüm](https://halityesil.com/seri/kentsel-donusum/) sayfasında.

Yıkmak bir günlük iş. Eski binanın neyi bildiğini yıkımdan önce öğrenmezseniz, yıkımdan sonra hata kayıtlarından öğrenirsiniz. Teknolojiyle kalın.
