Rob Pike'ın Programlama Kuralları (1989)
(users.ece.utexas.edu)- 1989'da Rob Pike'ın programlamaya dair 5 kuralını anlatan bir makale
- Kural 1: Programın zamanının çoğunu nerede harcayacağını varsayma; darboğazlar beklenmedik yerlerde ortaya çıkabilir. Darboğaz kanıtlanana kadar hız hack'lerinden kaçın.
- Kural 2: Hız için ayarlama yapmadan önce her zaman ölç. Yalnızca kodun bir bölümünün geri kalan üzerinde anlamlı bir etkisi varsa optimize et.
- Kural 3:
nküçükken karmaşık algoritmalar yavaştır. Çoğu durumda da böyledir. Karmaşık algoritmaları yalnızcansık sık büyük olduğunda kullan; o zaman bile önce Kural 2'yi uygula. - Kural 4: Basit algoritmalar ve veri yapıları tercih edilir. Karmaşık olanlara göre hatalara daha az açıktırlar ve uygulanmaları daha kolaydır.
- Kural 5: Doğru veri yapısı programlama için belirleyicidir. Veri iyi yapılandırılmışsa algoritmalar kendiliğinden ortaya çıkar.
- Pike'ın 1. ve 2. kuralları, Tony Hoare'un "erken optimizasyon tüm kötülüklerin kaynağıdır" aforizmasını yansıtır.
- Ken Thompson, Pike'ın 3. ve 4. kurallarını "şüphe duyduğunda kaba kuvvet kullan" diye yeniden ifade etti.
-
- ve 4. kurallar, KISS (Keep It Simple, Stupid) tasarım felsefesini hayata geçirir.
-
- kural, Fred Brooks'un 'The Mythical Man-Month' kitabındaki sözüyle örtüşür ve sık sık "akıllı nesneler kullanan aptal kod yaz" diye kısaltılır.
1 yorum
Hacker News yorumları
“Veri hükmeder” sözüne tamamen katılıyorum
Bu yüzden LeetCode mülakatları bana hep tuhaf gelmiştir. Genelde algoritmalara odaklanırlar; oysa gerçekte en baştan böyle yaklaşmamak gereken çok durum vardır ve veri yapıları daha merkezde olmalıdır
Elbette algoritmaları hiç bilmiyorsanız, istisnai durumları ya da belirli bir nedenle belirli bir algoritmaya yaslanmanız gereken anları fark edemeyebilirsiniz. Yine de algoritmalar görece kısa sürede öğretilebilirken, hangi veri yapısının kullanılacağına dair sezgi kazanmak insanlara daha zor geliyor gibi
O anda atmosfer “Ah, gerçekten kıdemli bir mühendis geldi” havasına dönüyor; teknik sorunlar daha açık biçimde konuşuluyor ve “kod yazmayı biliyor mu”yu kanıtlama tavrı da azalıyor
Tersine, iyi değişimler yaratmakta, kilometre taşlarına ulaşmakta ve ekip iş birliğinde en çok zorlandığım ekipler, veri yapılarını ve kod mimarisini düzgün oturtan kimsenin olmadığı yerlerdi. Çerçevenin her şeyi halledeceğine, olmazsa daha akıllı birinin yaptığı bir eklenti ya da ara katmanın çözeceğine alışmış çok kişi var gibi
Veri yapılarından kaçan mühendis kendi ayağına sıkmış olur; en yararlı araçlardan birinden vazgeçtiği için sınırları gündelik olarak ortaya çıkar
Örneğin kod, ağırlıklı yönlü çevrimsiz grafikte (DAG) en uzun yolu bulmak için çokça kullanılabilir; ama asıl mesele, problemi ağırlıklı DAG olarak ifade edebileceğini fark etmekti. Bunu göremezseniz yine çözebilirsiniz, ama çözüm çok daha yavaş ve karmaşık olur
Mülakatçı baştan öncelik kuyruğu, komşuluk matrisi, trie gibi şeyler kullanmanız gerektiğini söylemez. Takılırsanız ipucu verebilir, ama fazla yönlendirme güçlü bir işe alım sinyali olarak görülmesi zor bir şeydir
“Süslü algoritmalar n küçükken yavaştır ve n genelde küçüktür” sözüyle ilgili olarak, yakın zamanda bir projede hissettiğim şey büyük n’in sanıldığından çok daha büyük olabileceği
“100 bin işlem yapmam gerekiyor, kesin optimize etmeliyim” diye düşünmek kolay; ama bilgisayarlar hızlıdır ve 100 bin çarpma çoğu zaman o kadar hızlıdır ki üzerinde derinlemesine düşünmeye gerek kalmayabilir
Hiç düşünmeyelim demiyorum; ama modern donanımın ne kadar çılgınca hızlı olduğu sık sık şaşırtıyor
Yanlışlıkla ikinci dereceden zamana dönüşen kod yüzünden üretim kesintisi yaşandığını gördüm; kullanıcıların %99’u her zaman küçük n kullansa bile bazı kullanıcılar sık sık büyük n ile karşılaşıp çok yavaş bir uygulama yaşayabilir
Çoğu durumda, genel durumda biraz daha yavaş ve uygulaması biraz daha karmaşık olsa bile ikinci dereceden zamandan daha iyi bir algoritma seçmek isterim. Sık görülen yavaş yollar optimize edilir; ama nadir yavaş yollar geliştiricinin bizzat üstüne basmadan geçip gidebildiği ya da üretimde patlayan şeylerdir
Elbette algoritma çok karmaşıksa basit bir ikinci dereceden zaman uygulamasını seçebilirim; ama varsayılanım mümkünse ikinci dereceden küçük bir şeyden yana olur. Bununla ilgili yazdığım bir yazı da var: https://kevincox.ca/2023/05/09/less-than-quadratic/
Bu yüzden CPU’nun belleğe kıyasla o kadar hızlı olmadığı ve tüketici donanımında dal tahmini hatalarına pek önem verilmediği 40 yıl önce daha isabetli bir söz olabilir
Her mülakatta işe alım yöneticisi bunu ister; ama operasyonun yaralarını henüz yaşamamış LeetCode acemisi kararı reddeder
https://www.frankmcsherry.org/assets/COST.pdf
Son 20 yıldaki bilgisayar performansı artışını düşününce bu eşiği 100 bine çıkarmak oldukça makul görünüyor
“Erken optimizasyon tüm kötülüklerin kaynağıdır” şeklindeki ünlü özdeyiş aslında Tony Hoare’dan değil, Donald Knuth’tan gelir ve çoğu zaman bağlamından koparılarak genel olarak optimizasyona karşı bir söz gibi kullanılır.
Cümlenin tamamı şöyledir: “Küçük verimlilikleri, diyelim ki vakaların %97’sini unutmalıyız. Erken optimizasyon tüm kötülüklerin kaynağıdır. Ancak kritik %3’te fırsatları kaçırmamalıyız.”
Ana fikir, etkisi olan yerlerde zaman harcayıp optimizasyon yapmaktır.
Muhtemelen Tony önce söylemiş, Knuth da bunu rafine edip yayımlamış olabilir. Gerekli bağlamı veren uzun alıntıyı yanında bulundurmak her zaman iyidir.
O dönemde programlama bugünkünden çok farklıydı. O zamanki “erken optimizasyon”, “ölçeklenebilirliği iyi olan popüler bir kütüphaneyi kullanalım”dan ziyade, “yalnızca bu donanımda çalışan, anlaşılması imkânsız bir bit oynama algoritması kullanalım”a daha yakındı.
“Erken optimizasyon tüm kötülüklerin kaynağıdır” sözünün içinde zaten bu anlam var; özdeyiş “optimizasyon tüm kötülüklerin kaynağıdır” değil.
Şirketin veri yapıları ve algoritmalar mülakatlarında, bubble sort’un en iyi seçenek olduğunu söyleyen sayısız frontend geliştirici gördüm. Anında türetmesini beklemeye gerek yok; birkaç yöntemi bilip probleme uygun iyi bir seçimi söylemesi yeterli.
“Erken optimizasyon yapma”yı o kadar uçlarda yaşayıp verimli yöntemleri bile bilmiyorsanız, nerelerin önemli olduğunu nasıl anlayacaksınız?
“Veri yapıları esastır” sözü veritabanları için iki kat önemlidir.
DB’yi aptal bir bit deposu ya da nesne tanımlarının 1:1 yansıması gibi kullananlar, DB bunu kişisel algılayıp performansı bozduğunda şaşırırlar.
ORM’in ürettiği bir DB şemasını tekrar görürsem, bu fazla erken bir yeniden karşılaşma olacak gibi.
Sorun, bazı ya da birçok geliştiricinin SQL bilmemesi ve ORM kullanmak için gereken DB bilgisinden de yoksun olmasıdır.
ORM, altında ne olduğunu bilmeniz gereken, epey sızdıran bir soyutlamadır. Bunu anlarsanız çoğu ORM ile de makul şemalar oluşturabilirsiniz.
Veri yapılarını iyi organize etmek ve tasarım değişse bile bunu korumak için veri ile kodu organizasyon düzeyinde ayırmak gerekir.
DB şema tasarımı, kullanım senaryoları ve aralarındaki eşlemeler uygulamanın geri kalanından ayrılmalı; bu grup bütünlük kontrollerini vb. de yazmalıdır. Organizasyon yapısı veri ile kodu ayırmıyorsa, kod ile veriyi ayırmak zordur.
Benim ek kuralım şu: küçük performans israfları biriktikçe, her biri tek başına önemsiz olsa da sonunda programı yavaşlatır.
Karmaşıklık, okunabilirlik, sürdürülebilirlik ve uygulama maliyetine etkisi yoksa performansı öylece harcamamak gerekir. Diğer koşullar neredeyse eşitse, iki seçenekten daha yavaş olanı seçmek doğru değildir.
Ayrıca n’in küçük olduğunu varsayarsanız neredeyse her şey çalışır. Ama n 100’ün altındayken iyi çalışan, 10000’in üstünde dağılan bir kod, örneğin O(n²) gibi bir şey kullanıyorsanız, doğrudan bir sınır koymalısınız. Küçük n varsayımı bozulduğunda büyük bir hata vermek, AWS faturası bombası ya da kilitlenmiş bir programdan iyidir.
Bu ilkelerin önemli bir kısmı, sonuçta aşırı tasarımı engelleyen stratejilere varıyor.
Deneyimime göre erken optimizasyon en pahalı tuzaklardan biridir. Olası bir sorunu fazla erken baypas ederseniz, o varsayım doğrulanmaz ve sonraki ekip gereksiz karmaşıklığı çözmek için pahalı çözümler üretmek zorunda kalır.
Öğrendiğim yaklaşım şu: optimizasyon tahminlere dayanır ve baştaki tahminler sık sık yanlıştır.
Ayrıca insanların gereğinden karmaşık kod üretmesini engellemek için ego yönetimi ve psikolojiyi anlamanın oldukça önemli olduğunu da öğrendim.
Aşırı üretim genellikle en kötü israf sayılır; çünkü yalnızca gereksiz bir şey üretmekle kalmaz, gerçekten gereken şeye harcanabilecek emeği de tüketir. Aşırı tasarım da buna benzer.
Örneğin yalnızca 100 kullanıcınız varken bile, bir gün 1 milyon kullanıcıya ulaşırsanız monoliti yeniden tasarlamak zor olur diye mikroservis mimarisi ile başlamak gibi.
Bu yüzden önce kodun zamanla neden daha az esnek hâle geldiğini ele almak gerekir.
Genel olarak iyi kurallar, ama pratikte 1. madde olduğu gibi geçerli değil
Başlarken neyin darboğaz olacağına dair bir hipoteze ihtiyaç var. XYZ’yi uyguladıktan sonra neyin yavaş olduğunu ölçüp düzeltmek her zaman mümkün değil. X, Y, Z birbirine bağlı olduğundan Y’yi hızlı yapmak için X ve Z’yi belirli bir şekilde tasarlamak gereken durumlar var; ayrıca Y’nin darboğaz olacağını zaten bildiğiniz durumlar da var
Sonradan ölçüp neyin yavaş olduğunu öğrenseniz bile, onu daha hızlı hâle getirecek yaklaşıma bahis oynamanız gerekir. Bu bahis ne kadar bilgili olursa o kadar iyi
İyi programcılar ölçüm yapar; ama neyin yavaş, hataya açık ve çok bellek tüketen olacağını öngörebildikleri için daha az yineleme yaparlar. Performans davranışının öngörülemeyeceğini bir kural gibi söylemek, iyi programcıların biriktirdiği deneyim ve beceriyi yok saymak olur
Çünkü 1. maddeye uyma süreci, darboğazları tahmin etmek için gereken iyi sezgiyi ve ampirik arka planı kazanmanın en iyi yoludur
Hız tahmininiz yanlış çıkarsa, projenin ömrü boyunca gereksiz yere karmaşık kod taşımış olursunuz
İnsanlar algoritma hızını sık sık yanlış tahmin eder. Bilgisayar zamanının %99’unu DB sunucusundan n’i getirmekle geçiriyorsa O(n) ile O(n²) çoğu zaman gerçek süre açısından aynı görünebilir
C ile yazılmış bir algoritmanın eşdeğer Python kodundan daha yavaş olduğu durumlar da olur; bunun nedeni bytecode derleyicisinin akıllı şeyler yapması olabilir
Legacy kodları hızlandırma işini çok yaptım; genellikle sanılandan çok daha kolay olur ve asıl yazara açık gelmemiş nedenlerle yavaştır. Çoğu zaman gerçekte yavaş olmasının nedeni, kod tabanının fazla karmaşıklaşması ve ilk yazarın artık üzerinde akıl yürütememesidir. Benim elimde “fazla yavaş” olan somut bir örnek olduğundan, çalıştırıp yavaş noktaları gözlemleyerek debug etmek kolaydır
Çok sayıda fiziksel nesne içeren bir video oyunu yapıyorsanız ve deneyimden çarpışma algılamanın büyük sorun olacağını kesin biliyorsanız, oyunu ve sistemi bunun etrafında tasarlamak hız hack’i değildir
Performansın büyük bir konu olacağını bildiğiniz bir işte elbette ölçüm yapmalısınız. Amaç bunun bir konu olup olmadığını doğrulamak değil, bu konuyu ne kadar iyi ele aldığınızı doğrulamaktır
Yeni gereksinimlerle yeni bir sistem yapıyorsanız, çoğu zaman doğrudan başlamak sorun olmaz gibi geliyor. Yaparsınız, test edip ölçersiniz, atar veya refactor edersiniz, tekrar edersiniz
Rust’ı örnek alırsak, taslak bir dil ve OCaml ile yapılmış bir derleyiciyle başlayıp yinelediler. Bir gün OCaml’dan self-hosting’e geçebileceklerini bilseler bile bunun büyük fark yaratıp yaratmadığından pek emin değilim
Kullanıcınız yoksa saatler süren bir fonksiyon bile, optimize edilince milisaniyelere inen bir fonksiyona kıyasla hâlâ yeterince hızlıdır. Böyle tahminleri doğru yapabildiğini kanıtlamış biri var mı, pek bilmiyorum
Örneğin BinaryTree nesnesi yerine sıralı bir dizide ikili arama yaparsanız, birleştirme concat sonrası sıralama olduğundan basitleşir; pointer olmadığı için serileştirme kolaylaşır ve bazı durumlarda serileştirmenin kendisi gereksiz olur. Dizi diskte ya da bellekte, hatta mmap ile ikisinde birden bulunabilir; RAM’den büyük verilerle de çalışabilir ve yalnızca bir dosyayı veya mapping’i gösterip hemen çalıştıran cold start da mümkün olur. Cache-oblivious özellikleri de vardır
Huffman kodlaması da bir örnek. Üniversitede muhtemelen ağaç tabanlı algoritma ve O(n log n) karmaşıklığıyla öğrenmişsinizdir; ama Huffman ağacını yerinde, dizi tabanlı olarak doğrusal zamanda kurmanın bir yolu olduğunu bilmiyordum
Elbette zamanın %99’unda backend microservice geliştirirken standart collection veri yapılarını kullanıyoruz. Ama işte big data çalışıyorsam, o dönemin popüler MapReduce türevlerini devreye almak yerine, yerelde büyük diskleri olan tek bir makinede işlemeyi çok daha fazla tercih ederim
Rob Pike olsa önce kodu profiling ile inceler, sonra havalı kodun ya da alternatif veri yapısının gerçekten daha hızlı olup olmadığına bakın derdi
Bu yazıyı 10 yılı aşkın süre önce ilk kez cat-v’de okudum; tasarıma ve karmaşıklığa yaklaşma ve bunları düşünme biçimimde silinmez bir etkisi oldu
http://doc.cat-v.org/bell_labs/pikestyle
“Veri yapıları esastır” şeklindeki özgün kuralın nasıl “akıllı nesneler kullanan aptal kod yazın”a indirgenebildiğini anlamıyorum
“smart objects” ifadesi çok kötüydü; özgün kural daha uzun olsa da çok daha iyi