Yazılımla uğraşıyorsan şu anı mutlaka yaşamışsındır: karar senden bekleniyor, ama doğru cevabı kimse bilmiyor. Ne kitapta yazıyor, ne internette.
Ben bunu ilk kez TÜBİTAK projemizde yaşadım. Fizyoterapi hastalarının egzersizlerini kameradan takip eden bir sistem geliştirdik; sistem hareketi izliyor ve doğru yapılıp yapılmadığını söylüyordu.
Bir noktada tek bir sayıya karar vermem gerekti. Hastanın kolu, olması gereken açıdan kaç derece saparsa "yanlış yaptın" diyecektim? 5 derece mi, 15 derece mi?
Bu sayı hiçbir kitapta yazmıyor. Küçük tutarsan sistem sürekli hata veriyor ve hasta sıkılıp bırakıyor. Büyük tutarsan yanlış hareketi de onaylıyorsun, sistem işe yaramaz hâle geliyor. İkisinin ortası diye bir şey de yok; bir yere basmak zorundasın.
Sonunda birkaç deneme yaptım, bir sayı seçtim, neden onu seçtiğimi yazdım ve sonucuna katlandım.
İşte mühendislik tam olarak bu. Yaptığım şey bir hesap değildi, bir karardı.
Aşağıda o günden beri biriktirdiğim yedi madde var. Hiçbiri kural değil; hepsi, benzer bir anda karşına çıkınca işini kolaylaştıracak şeyler. Beraber bakalım.
Doğru cevabın olmadığı yerde
Bilim "ne var?" diye sorar. Mühendislik "ne yapmalıyım?" diye sorar.
Aradaki fark önemli. Bilimde bir doğru cevap vardır; bulana kadar ararsın. Mühendislikte çoğu zaman doğru cevap yoktur. Ama yine de bugün bir şey seçmen gerekir.
Billy Vaughn Koen'in tarifi bunu iyi anlatıyor: mühendislik, tam olarak anlamadığın bir durumu, elindeki imkânlarla iyileştirmeye çalışmaktır. Üç şeye dikkat: tam bilgin yok, sınırsız zamanın yok ve kullandığın kuralların çoğu kanıtlanmış değil. Sadece bugüne kadar işe yaramışlar.
Yazılımda bu fark şurada görünüyor. "Sıralama şu hızdan daha hızlı yapılamaz" bir matematik teoremidir, tartışılmaz. Ama "400 milyon satırı 8 GB bellekle nasıl sıralarım" sorusunun cevabı o teoremde yok. Cevap ölçümde, kullandığın donanımda ve kabul etmeye razı olduğun hata payında.
Her seçim, bir şeyden vazgeçmek
Bu madde kulağa en sıkıcı olanı gibi geliyor, ama gündelik işin tam ortasında duruyor.
Vitruvius iki bin yıl önce iyi bir yapının üç şartını yazmış: sağlam olacak, işe yarayacak, güzel görünecek. Bugün listeye maliyet, süre ve güvenlik de eklendi. Ama değişmeyen bir kural var: hepsini birden en üste çıkaramazsın. Birini yükseltirken başka birini indirirsin.
Yazılımda bu takasları görmek kolay:
- İstekleri toplu işlersen sunucu daha çok iş yapar. Karşılığında tek bir kullanıcı cevabını daha geç alır.
- Önbellek kullanırsan aynı hesabı tekrar tekrar yapmazsın. Karşılığında bellek harcarsın.
- Ağ koptuğunda ya herkese aynı veriyi gösterirsin ya da servisi ayakta tutarsın. İkisi birden olmuyor.
- Her yeni ayar seçeneği, test etmen gereken bir kombinasyon daha demek.
Bu yüzden bir tasarım için "daha iyi" demek bana bir şey anlatmıyor. Anlatan cümle şöyle: Şunu kazanmak için şundan vazgeçiyorum ve bu iş için buna değer, çünkü…
Birinin tasarım kararını dinlerken tek şeye bakıyorum: neyden vazgeçtiğini söyleyebiliyor mu? Söyleyemiyorsa ortada bir karar yok. Sadece öylece olmuş bir şey var.
Her katman altındakini saklar
Şunu bir düşün: karmaşık şeyler kurabiliyoruz çünkü her şeyi aynı anda düşünmüyoruz. Sen de bir web isteği atarken kablodan geçen paketleri düşünmüyorsun. Veritabanına sorgu yazarken diskin nasıl döndüğünü düşünmüyorsun.
Buna soyutlama deniyor. Her katman altındaki karmaşayı saklıyor ve sana basit bir söz veriyor: "Sen şunu iste, gerisini ben hallederim."
Sorun şu ki bu bir söz, garanti değil. Joel Spolsky'nin dediği gibi, işe yarayan her soyutlama bir yerinden sızar. Yani sakladığı karmaşa bir gün mutlaka yüzüne vurur. Genelde iki durumda oluyor: bir şey yavaşladığında ve bir şey bozulduğunda.
Şekil 1 — Etkileşimli
Basit bir web isteği altı katmandan geçiyor. Katmana tıkla: sana ne söz veriyor, neyi saklıyor ve nerede seni yarı yolda bırakıyor?
- Sana verdiği söz
- "Uzaktaki veriyi, kendi bilgisayarındaymış gibi çağır."
- Sakladığı
- Verinin paketlere bölünüp kablolardan geçmesi, yolda kaybolması, tekrar gönderilmesi.
- Sızdığı yer
- Cevap hiç gelmeyebilir. Ya da iki kez gelebilir. "Ya çalışır ya çalışmaz" diye düşünürsün ama üçüncü bir durum vardır: ne olduğunu bilmiyorsun.
- Sana verdiği söz
- "Veritabanını düşünme, sen nesnelerle çalış."
- Sakladığı
- Arka planda yazılan sorgular ve açılan bağlantılar.
- Sızdığı yer
- Basit bir döngü yazarsın, farkında olmadan yüzlerce sorgu atılır. Kod tertemiz görünür ama veritabanı boğulur.
- Sana verdiği söz
- "Belleği ben yönetirim, sen kodunu yaz."
- Sakladığı
- Bellek nereden alınıyor, ne zaman geri veriliyor.
- Sızdığı yer
- Bellek temizliği çalışırken program bir anlığına duruyor; kullanıcı bunu takılma olarak görüyor. Ya da unuttuğun bir bağlantı yüzünden bellek sessizce şişiyor.
- Sana verdiği söz
- "Bu makine tamamen senin. Kesintisiz bir işlemcin ve kocaman bir belleğin var."
- Sakladığı
- Aslında işlemciyi başka programlarla sırayla paylaşıyorsun. Bellek de göründüğü kadar büyük değil.
- Sızdığı yer
- Aynı kod, aynı makinede iki farklı sürede biter. Sıra sende değilse beklersin ve bunu göremezsin.
- Sana verdiği söz
- "Komutlar yazdığın sırayla, hep aynı hızda çalışır."
- Sakladığı
- Veriyi nereden aldığı. İşlemcinin içindeki küçük hafızadan okumak, dışarıdaki büyük bellekten okumaktan onlarca kat hızlıdır.
- Sızdığı yer
- Aynı işi yapan iki döngü arasında on kat fark olabilir. Biri veriyi sırayla okuyordur, öteki rastgele.
- Sana verdiği söz
- "Dünyada iki değer var: 0 ve 1."
- Sakladığı
- Aslında ortada elektrik var. 0 ve 1 dediğimiz şey, belirlenmiş gerilim aralıkları.
- Sızdığı yer
- İşlemci ısınınca kendi hızını düşürür. Çok nadiren de olsa bir bit kendiliğinden değişebilir. En altta bile kesin garanti yok; sadece çok küçük bir hata ihtimali var.
Bazı duvarlar gerçek
Bu bölüm bence en pahalı hatayı anlatıyor, o yüzden biraz yavaşlayalım.
Kısıtların bir kısmı bütçedir. Konuşursan, ikna edersen değişir.
Bir kısmı ise doğa kanunudur. Ne kadar uğraşırsan uğraş değişmez.
Bu ikisini karıştırmak, gördüğüm en pahalı hata: aşılmayacak bir duvara aylarca emek harcamak. Yazılımda sık karşıma çıkan üç duvar var.
- Bir programın sonsuza kadar çalışıp çalışmayacağını önceden söyleyen bir program yazılamaz. Bu 1936'da kanıtlandı. Kod analiz araçlarının neden hep "olabilir" dediğinin sebebi bu.
- Işık hızı bir alt sınır koyar. Grace Hopper derslerinde öğrencilerine 30 santimlik teller dağıtırmış: ışık bir nanosaniyede işte bu kadar yol alıyor. İstanbul'dan Frankfurt'a gidip gelen bir istek, o mesafenin gerektirdiği süreden hızlı olamaz.
- Amdahl yasası: işin paralelleşmeyen kısmı, hızlanmanın tavanını tek başına belirler.
Şekil 2 — Etkileşimli
Amdahl yasası. Kaydıracı oynat: işin ne kadarı paralelleşirse hızlanmanın tavanı nereye çıkıyor?
Kaydıracı oynatınca şunu görüyorsun: işin %90'ı paralelleşiyorsa, kaç çekirdek koyarsan koy 10 kattan fazla hızlanamıyorsun. Sonsuz çekirdek bile yetmiyor.
Bunu bir kez gördükten sonra "sunucu ekleriz" cümlesi kulağa farklı geliyor. Eklemeden önce sorulacak soru şu: işin ne kadarı gerçekten paralelleşiyor?
Karmaşıklık faiz gibi birikir
Şimdi bir kere kurduktan sonra peşini bırakmayan şeye gelelim.
Fred Brooks 1987'de karmaşıklığı ikiye ayırmıştı.
Bir kısmı problemin kendisinden geliyor. Muhasebe yazılımı karmaşıksa bunun sebebi muhasebenin karmaşık olması. Diğer kısmı ise bizim araçlarımızdan geliyor: kötü bir dil, dağınık bir kurulum, anlaşılmaz bir kütüphane.
Araçlar iyileştikçe ikinci kısım azalıyor. Ama birinci kısım yerinde duruyor. Brooks'un söylediği şuydu: tek bir yeni teknoloji gelip yazılımı on kat kolaylaştırmayacak. Kırk yıl geçti, hâlâ haklı görünüyor.
Buna Meir Lehman'ın gözlemini ekleyin: kullanılan bir yazılım sürekli değişmek zorundadır ve her değişiklikte biraz daha karmaşıklaşır. Bunu durdurmanın tek yolu, sadeleştirmek için ayrıca zaman ayırmak. Yani karmaşıklık bir hata değil, faiz gibi bir şey: ödemezsen büyüyor.
Bir de Melvin Conway'in 1968'de yazdığı şey var: bir sistemi tasarlayan ekip, farkında olmadan kendi yapısını sisteme kopyalar. Üç ayrı ekip varsa sistem de üç parçaya ayrılır. Mimari tartışması sandığın şey, çoğu zaman ekiplerin nasıl bölündüğü tartışmasıdır.
Bir şeyler kırılınca öğreniyoruz
Henry Petroski'nin söylediği ilk bakışta rahatsız edici ama doğru: mühendislik bilgisinin çoğu, işler ters gittiğinde ortaya çıkıyor.
Çalışan bir sistem sana çok şey söylemez. Hangi önlemin gereksiz olduğunu, hangi payın fazla olduğunu bilemezsin. Ama çöken bir sistem nerede yanıldığını tam olarak gösterir.
| Vaka | Ne oldu | Aklımda kalan |
|---|---|---|
| Therac-25 1985–87 |
Kanser tedavisinde kullanılan bir ışın cihazı. Eskiden mekanik parçaların yaptığı güvenlik kontrolü yazılıma devredildi. Nadir görülen bir zamanlama hatası, hastalara aşırı doz verilmesine yol açtı. | "Nadir" demek "imkânsız" demek değil. Yazılım hatası artık sadece ekranda kalmıyor. |
| Ariane 5 1996 |
Eski rokette çalışan kod, yeni rokete olduğu gibi konuldu. Yeni roket daha hızlıydı; bir sayı beklenenden büyük çıktı ve program çöktü. İki yedek birim de aynı yazılımı çalıştırdığı için ikisi birden düştü. Roket 40 saniyede kayboldu. | Yedek, ancak farklıysa yedektir. Başka bir yerden alınan kod, yeni yerinde yeniden test edilir. |
| Knight Capital 2012 |
Yeni kod sekiz sunucudan yedisine kuruldu, biri atlandı. O sunucuda yıllardır uyuyan eski bir kod parçası çalışmaya başladı. 45 dakikada 460 milyon doların üzerinde zarar. | Kodu yazmak işin yarısı; nasıl kurulduğu da sistemin parçası. Silinmeyen eski kod bir gün uyanır. |
Ortadaki satır benim favorim. Ariane roketinde iki tane yedek birim vardı; yani "biri bozulursa diğeri devreye girer" diye tasarlanmıştı. Ama ikisinde de aynı yazılım çalışıyordu. Aynı hatalı veri geldi ve ikisi aynı anda çöktü.
Buradan çıkan ders şu: iki kopya çalıştırmak, o kopyaların ortak hatasına karşı seni korumaz.
Başarılı bir teknoloji için gerçeklik halkla ilişkilerin önünde gelmelidir; çünkü doğa kandırılamaz. Richard Feynman, Challenger raporuna eklediği notta
Bu yüzden iyi ekipler hata sonrası toplantılarını suçlu arama seansına çevirmiyor. Sorulan soru "kim yaptı" değil, "bu hata üretime kadar nasıl geldi".
Yeterince iyi olan, iyidir
Mühendislik en iyi çözümü bulmaz. Savunabileceğin çözümü bulur.
Herbert Simon buna "yetinmecilik" demiş. İnsanlar gerçek hayatta en iyi seçeneği aramaz; yeterli gördükleri ilk seçeneği alır. Çünkü aramanın kendisi de zaman ve para harcıyor.
Knuth'un meşhur cümlesini çoğu kişi yarım alıntılıyor: "Erken optimizasyon tüm kötülüklerin anasıdır." Devamı şöyle: "…ama kritik %3'te fırsatı kaçırmamalıyız." İkinci yarısı olmadan bu söz tembelliğe dönüşüyor. O %3'ün hangi kısım olduğunu da tahminle bulamazsın; ölçmen gerekir.
Bir çözümün yeterince iyi olduğunu şuradan anlıyorum: hangi durumda bozulacağını yazabiliyorsam yeterince iyidir. Yazamıyorsam elimde bir çözüm yok, sadece henüz karşılaşmadığım bir hata var.
Son madde en önemlisi: kararı sen veriyorsun ama sonucunu kullanıcı yaşıyor.
Fizyoterapi projesindeki o tolerans sayısını ben seçtim. Yanlış seçseydim bedelini ben değil, ekranın karşısındaki hasta ödeyecekti.
Bugün yazılım ışın dozu ayarlıyor, fren basıncı hesaplıyor, kredi başvurusu değerlendiriyor. Bu yüzden işin sıkıcı görünen kısımları — hatayı sessizce yutmamak, varsayımını bir yere yazmak, ölçmediğin bir şeye "çalışıyor" dememek — aslında işin süsü değil, kendisi.
Başa dönersek: mühendislik, eksik bilgiyle ve kısıtlı imkânla, başkasının taşıyacağı bir riski üstlenip çalışan bir şey kurmaktır. "Çalışan kod" ile arasındaki fark da burada. Çalışan kod bir gözlemdir: bugün çalıştı. Mühendislik ise bir iddiadır: hangi şartlarda çalışacağını, ne zaman bozulacağını ve bozulduğunda ne olacağını da söylersin.
Kendime not ettiklerim
- Doğru cevabın olmadığı yerde iş başlar.
- Neyden vazgeçtiğini söyleyemiyorsan karar vermemişsindir.
- Her katman bir söz verir. O söz garanti değildir.
- Bütçe kısıtıyla doğa kanununu karıştırma.
- İki kopya aynı hatayı taşıyorsa yedek sayılmaz.
- Ölçmeden optimize etme.
- Ne zaman bozulacağını yazamıyorsan henüz hazır değildir.
Devamını okumak istersen
Buradaki fikirlerin çoğunu şu metinlerden aldım. Hepsi ücretsiz:
- Joel Spolsky — The Law of Leaky Abstractions: soyutlamanın nereden sızdığı fikrinin kaynağı. On dakikalık bir yazı.
- Melvin Conway — How Do Committees Invent?: ekip yapısının sisteme kopyalandığını anlatan 1968 tarihli asıl makale.
- Therac-25 kazalarının incelemesi: yazılım güvenliği üzerine okuduğum en sarsıcı metin.
- Ariane 5 soruşturma raporu: kısa, net ve bir hata raporunun nasıl yazılması gerektiğinin örneği.
- Feynman'ın Challenger raporuna eklediği not: yukarıdaki alıntının geçtiği birkaç sayfa.
Buraya kadar okuduysan bu işi zaten ciddiye alıyorsun demektir. Yedi maddenin hepsini birden aklında tutmana gerek yok; bir sonraki kararında hangisinin aklına düştüğüne bak, yeter. Kolay gelsin.
Yapay zekâ notu: Bu yazıyı yapay zekâ yardımıyla derlediğimi belirtmek isterim. Kaynakları taramakta ve metni toparlamakta yardım aldım.