Eylül 2, 2019 · 8 dakikaKodlarken ne oluyor? Ne olmalı?
Her gün bir takım gereksinimler karşısında kodlama ihtiyacımız doğuyor ve bizlerde kodluyoruz, kodluyoruz ve yine kodluyoruz. Peki kodlarken dikkat ettiklerimiz neler? Bir şeylere dikkat ediyor muyuz? Etmelimiyiz? Etmesek ne olur sanki, çalışıyor işte. Ben mutlu, kullanıcı mutlu patron mutlu.
Evet şimdi mutluluğumuzun gerçekten mutluluk olup olmadığını düşünmeye davet edeceğim. Sana bu yazıda The Pragmatic Programmer kitabının 6. bölümü olan “WHILE YOU ARE CODING” bölümünden arta kalanları kısmen çeviri, kısmen kendi yorumlarımı katarak edindiğim bilgileri aktarmayı düşünüyorum.
Öncelikle senle içeriğe bir göz atalım. Geniş bir bakış attıktan sonra teker teker ele alalım.
- Tesadüfi Kodlama (Programming by Coincidence)
- Algoritma hızı (Algorithm Speed)
- Yeniden Düzenleme (Refactoring)
- Test Etmesi Kolay Kod (Code That’s Easy to Test)
- Şeytani Sihirbazlar (Evil Wizards)
Konu başlıklarımız bunlar güzel değil mi? Zaman kaybetmeden tesadüfi kodlamadan başlıyorum.
Tesadüfi Kodlama
Bi’ garip çeviri oldu ama olsun. Bundan daha garip bir o kadar da durumu net bir şekilde açıklayan hikayeyle devam ediyorum.
Eski siyah beyaz savaş filmlerini hatırlar mısınız? Ucu bucağı belli olmayan sahile tekneyle bırakılan askerlerin can havli ile akın akın tepelere koşmasını… Evet, biliyorum gözünde o sahne ve sahil canlandı.

Bir gün bir asker bu tarz bir araziden geçmek zorunda kalır. Fakat etrafta sahilin mayın tarlası olduğuna dair hiç bir işaret bulunmuyor. Buna rağmen askerimiz süngüsüyle toprağı eşeleyerek ilerler. Eşelemesinin sonucunda herhangi bir mayına rastlamadan arazinin öbür ucuna varmış olur. Yani başarmıştır ve arazinin mayınsız olduğunu söyleyebilir.
Peki gerçekten bunu söyleyebilir mi?
Ne kadar saçma geldi değil mi? Bu arada haklıda olabilir veya sadece şanslı… Bu hikayeye kod yazan kişi olarak baktığında, kodunun çalıştığını doğrulayabilirsin. İstenilen çıktıyı eksiksiz veriyor da olabilir. Peki ya istenmeyenleri de barındırıyor olabilir mi? Sadece beklendik değerler için doğru sonucu veriyor olmasın. 😱
Bir uygulama belirli sınır değerleri içerisinde düzgün çalışıyor olabilir. Peki bu sınır değerleri değişirse yine de çalışmaya devam edecek mi? Beklenmeyen hataları kapsam dışı mı bırakacaksın? Mesela her kullanıcının adının 12 karakterden uzun olamayacağını varsaymak, olamayacağı anlamına mı gelir? Türkçede ki en uzun isimle test etmek tüm sorunları çözer mi? Ya yarın yabancı bir kişi kullanırsa, yazdığın kod bu durumda nasıl davranacak?

Tamam, temkinli olacağız. Fakat tesadüfi kodlamaya neden olan şey ne?
Tesadüfi kodlama yapmanın temel nedeni aslında kullanılan algoritmanın tam olarak ne yaptığını kavrayamamaktan gelir. Tamamını anlamadığınız bir kod parçasını kendi kod tabanınıza (code base) eklemeyin. Tesadüfler gereksinimlerde de karşımıza çıkabilir, test aşamasında da. Tesadüf eseri değil de planlı olarak geliştirilen kodun gerçekten de tesadüf olmadığını kanıtlayın. Örneğin sizin dizininizde bulunan bir dosya var diyelim. Bu dosya farklı işletim sistemlerinde olmayabilir. O dosyanın orada olduğunu test ile kanıtlayın değilse de yaratın izniniz yoksa farklı çözüm yolları arayın.
Peki güzel konuşuyorsun da hep soru soruyorsun ortada bir cevap göremiyoruz diyenler için tesadüfi değil, bile isteye - kasıtlı kodlama nasıl yapılır tarifini veriyorum:
- Tetikte olmalısın, felaket geldiğinde değil belirtileri sezinlediğinde çözüm arayışına girmelisin. (Kırık cam teorisi)
- Aşina olmadığın veya stabil olduğunu kanıtlayamadığın kod parçalarını kullanmamalısın.
- Başlamadan önce planlamalı ve planını her daim göz önünde bulundurmalısın.
- Güvenilir olana güven. Güvenemediklerinde hata olasılığı varsa en kötüsünü düşünüp aksiyon almalısın.
- Varsayımlarını belirlemeli ve diğer geliştiriciler için belgelemelisin. (Kodunuzun çalışacağı örneklem uzayını belirle)
- Sadece kodunun doğruluğu değil varsayımların karşısında da doğruluğunu test edin. Yani varsayımlarını kontrol etmelisin. Böylelikle varsayımlarını belgelemişte olursun.
- Tarihin kölesi olma. Geçmiş dönemde yazmış oldukların, seni işinden alı koyup farklı bir çözüm arayışına sokmasına izin verme.
Algoritma Hızı
Önceki bölümü çok hızlı bitirdim. İnşallah yazdığım kodlarda bu denli hızlı biter. Amin. Amiiiin 🤲🏼 ’de bildiğiniz gibi sadece dua ile bitmiyor. Peki ne yapmalı?
Pragmatik programcıların genelde her gün yaptığını. Yazdığın kodun daha yazma aşamasında ne kadar sürede çalışacak, ne kadar yer kaplayacak gibi soruları düşünmen gerekiyor. Gayet hızlı çalışıyor dediğin algoritma, girdi kümesi büyüdüğünde nasıl tepki veriyor? Tepki verebiliyor mu?
Giriş ne kadar büyürse algoritmanın çalışma süresi o denli büyüyecektir. İlişki her zaman doğrusal olsaydı, yani zaman değeri ile orantılı olarak bir artış sağlanmış olsaydı herhangi bir sorun olmayacaktı. 1 elemanın kontrolü 1 saniye sürüyorsa 100 elemanın kontrolü 100 saniye sürüyor diyebilecektin. Fakat her algoritma doğrusal değil maalesef. İyi haber şu ki doğrusal algoritmaların alt kümesi ile sorun çözülebilir. Örneğin; ikili aramada (Binary Search) her elemana bakmak gerekmemektedir. Belli başlı alt kümelere bakmak yeterli olacaktır. Fakat her şey bu kadar mükemmel değil tabii. Aksine doğrusaldan da kötü çalışan algoritmalar da olabilir. 1 elamana bakmak 1 saniye iken 100 elemana bakmak 100 değilde 1000 saniye alabilir.
Her döngü oluşturduğumuzda yahut öz yinelemeli bir algoritma yazdığımızda istemsizce programın çalışma süresini hesaplamak istiyoruz. Bu alışkanlık gerçekten de güzel fakat “Daha iyi olabilir miydi?” düşüncesi de her daim aklımızın bir köşesinde yer tutuyor bu durumda Big O[O(n)] devreye giriyor.
Nedir bu Big O dedikleri?
O(n) Notasyonu
Bir sıralama algoritması düşünün. Bu algoritmanın n² ‘de çalıştığını varsayalım. Bu algoritmaya verilen girdi sayısı 2 katına çıktığında. O(n) değeri 4 katına çıkacaktır. O(n) notasyonunda en yüksek değer diğer değerleri domine eder. Yani, O(n²/2+ 2n) değerinde her bir ifadeyi ayırırsak: O((n². 1/2 )+ 2.n) değerler içerisinde tam sayı değerlerinin bulunması bizi ilgilendirmiyor. Tüm tam sayı değişkenlerini silelim. O(n²+n) şeklinde sadeleştirebiliriz. O(n²), O(n) notasyonundan büyük olduğu için O(n) değerini ekarte edecektir (n² > n ). O(n) notasyonunun zayıflığı bir algoritma sonucunda elde edilen O(n²)’nin, herhangi bir diğer algoritma sonucunda elde edilen O(n²)’den ne kadar farklı olduğunun bilinmemesidir. İkisi arasında 1000 kat belki daha fazla bir hız farklı olabilir bunu tespit edemeyiz.
Örneğin; 100 kaydın işlemesinin 1 saniye sürdüğü bir yordamınız olduğunu varsayalım.

Eğer kodunuz Big O değeri:
O(1) ise: Hala 1 saniye sürecektir. (index ile dizinin elamanına erişme)
O(log(n)) ise: Sonucu bulmak 2 saniye sürecektir. (binary search)
O(n) ise: Doğrusal olarak 10 saniye sürecektir. (teker teker kontrol etme)
O(n.log(n)) ise: log100 = 2 saniye, 10*2 ile 20 saniye sürecektir. (quicksort, heapsort)
O(n²) ise: 100 saniye sürecektir. (Kare arama — iki dizi arasında ortak elemanları sıralı şekilde bulma - bubble sort)
O(2^n) ise: 10²⁶³ yıl (Seyyar satıcı problemi)
Bu arada, söylemeden geçmeyeyim. O(n) sadece zaman değil, bellek kullanımı içinde kullanılabilmektedir.
- Basit döngüler: 1. Elemandan başlayıp n. elemana kadar bulunmak istenen elemanın aranmasıdır. En kötü durum n’dir. O(n)
- İç içe döngüler: Dize içerisindeki 1 elemanın diğer dize içerisinde yerleştirileceği konumun aranması buna örnektir. Her bir eleman diğer dizenin boyutu kadar döngü kuracaktır. O(n.n) = O(n²)
- Yarılama metodu: Bir dizede elemanları yarıya bölerek hedefe ulaşma metodudur. Elemanın bulunduğu (matematiksel sayı dizisi aralığında var olması durumu) taraf tekrardan yarılanır. (binary search, binary tree search)
- Böl ve yönet metodu: Dize iki yarım dizeye bölünür ve her dize için bu işlem tekrarlanır. Bu aşamada n adet logn kullanılmaktadır.
- Kombinatoryal metod: Permütasyon temelli arama metodudur. Permütasyon, faktöriyelleri içerdiği için çalışma süreleri kontrolden çıkabilir. Gezgin satıcı veya sırt çantası problemlerinde kullanılır.
Yeniden Düzenleme (Refactoring)
Yazılım geliştirme süreci genellikle bina inşası metaforuyla açıklanmaktadır. Nedir bu bina inşası metatoforu?
- Bir mimar planları çizer.
- Ustalar temeli kazar çelikleri diker ve betonu ekler sonrasında ufak rötuşlarla bina tamamlanır.
- Kiracılar taşınır ve mutlu bir şekilde hayatlarını sürdürürler. Binada herhangi bir sorunla karşılaştıkları taktirde sorunu gidermek için bina bakımını ararlar.
Fakat düşünce olarak bina inşası değilde daha çok bir bahçe işine benzemektedir. Yani katı değişmez değil, organiktir ve dış etmenlere karşı bağımlılık içerir.
Bahçeye ilk planlamalara göre bir çok şey ekebilirsin. Fakat bu ekilenler arasında hepsi büyümeyebilir. İçerlerinden bazıları çürümeye mahkumdur. Ekilen bitkiler ışık, rüzgar, besin alımlarına göre bahçe içerisinde yer değiştirebilir. Hiç birinin yeri sabit değildir. Aşırı büyümüş bitkiler bölünür veya budanır. Bahçe içerisinde yabani otlar bitebilir ve bunlar ektiğin bitkilere zarar veriyor olabilir. Bunlardan kurtulman gerekir. Bahçenin sağlığını her zaman gözetmen gerekir. Bu nedenle bahçe metaforuyla olaya daha fazla yaklaşmış oluyoruz. Diğer türlü katı ve değişmez bir yapı inşası, sadece gerçeklerde ve fizik kanunlarında sıkışıp kalmış bir olgudur.
Peki ne zaman refactoring yapmalıyız?
- Çoklama: DRY (Dont’ repeat yourself) ilkesini ihlal ettiğini gördüğünde
- Ortogonal olmayan tasarım: non-orthogonal bir kod veya tasarım keşfettiğinde. (ortogonal tasarım, yazılım sistemlerinin birbirinden bağımsız ve işini doğru bir şekilde gerçekleştiren tasarım modelidir.)
- Eskimiş bilgi: İşler ve gereksinimler değiştiğinde.
- Performans: Performansı arttırmak için işlevselliği değiştirmek gerektiğinde.
Zaman baskısı genellikte refactoring’e girmeme bahanesi olabilir. Bu gerçek bir bahane değildir. Sorunları çözmediğimizi bu şekilde ertelediğimizi varsayalım. İlgili alanda bir geliştirme yapıldığında kötü kod üzerinde harcanan zaman sizin genel zamanınızı nasıl etkileyecek? Oluşacak bir sorunda, bu sorunu çözmesi refactoring yapılmış bir alandaki sorunu çözmekten ne kadar fazla sürecek? Refactoringle bağımlılıklardan kurtulmuş bir kod üzerinde değişiklik yapmak mı istersiniz yoksa pek çok bağımlığı düşünüp acaba nereyi patlatacağım düşüncesiyle hata mı bekleyeceksiniz? Refactoring etmeyi anlık es geçmek günü kurtaracaktır. Zaman kazandığınızı düşünüyorsunuz ama kazandığınız bu zamanı gelecekten çaldığınızı bilmiyorsunuz. Zamanı geldiğinde gelecek çaldığınız bu zamanı faiziyle sizden geri isteyecektir.
Hepimiz günlük yaşantımızda kod yazıyor. En azından buraya kadar okuyan birinin yani senin aktif olarak kod yazdığını düşünüyorum. Bir iş aldığımızda bu işin boyutuna göre belli adımlarda işler iyice sarpa sarmadan önce refactoring yapmamız gerekmektedir.
Nasıl refactoring yapılır?
Refactoring yaparken mevcutta ki kodu kurcaladığımız için kurcalama sonucunda bazı yerleri kırmış olabiliriz. Bunun önüne geçmek için:
- Aynı zamanda hem refactoring hemde yeni özellik eklemeye çalışmayın.
- Refactoring yapmadan önce o alanda testlerin olduğundan emin olun ve sık sık testten geçirin. Bir hata oluştuğunda hangi aşamada hata ile karşılaşıldığını daha erken testpit etmiş olursunuz.
- Küçük adımlarla nihayi sonuca varmayı hedefleyin. Mesela benzer methodları bir üst sınıfa taşıyın. Test edin. Bir alan başka bir sınıf içerisinde olması gerekiyorsa ilk önce sınıfa taşıyın sonra diğer değişiklikleri yapın.
Test Etmesi Kolay Kod (Code That’s Easy to Test)
Entegre devre yazılımı (The Software Integrated Circuit), reusability ve component-based development tartışılırken konuşulması sevilen bir metafordur. Yazılım bileşenlerini bir devre çipi gibi birleştirilebilmesi prensibidir. Bunu sağlayabilmek için her bir bileşen tam güvenilir olmalıdır. Devre kartlarındaki yonga setlerinde olduğu gibi yazılımda da aynısını sağlayabiliriz. Diğer elemanlarla birlikte çalıştığınında sorun çıkarmadığını ve tek başına tamamen güvenilir olduğunu kanıtlamamız gerekir.
Bu aşamada kapsamımıza birim test giriyor.
Birim Test (Unit Test)
Clean code aşamasında her bir metod kendine düşen işi yapması ve sadece bu sorumlulukla yaşam döngüsünü tamamlaması yönünde bir olgu vardır. Bu olgu birim testinin kuvvetini arttıracak en büyük etmenlerdendir. Her metodu hatta her metod içerisindeki işlemleri bile sahip oldukları sorumluluğa göre ayırabilir ve bu işlemleri birim testine tabi tutabiliriz. Birim testi gerçekleştirmek için yapay bir ortam kurulur. Test edilmek istenen özellik beklenen değeri verip vermediği test edilir. Birim test kendi içinde sonlanmış olur. Fakat bir işlem kendi çevresinde sorun yaratmayabilir. Asıl sorunu diğer modüllerle birlikte çalışırken yaratıyor olabilir. Bununda önüne geçebilmek için regresyon testi yapmak gerekecektir. Bu sayede proje baştan sona test edilmiş olur.
Birim testleri kodunuz için bir yorum satırı görevi görür. Kodunuzu açıklamış olursunuz. Beklentileriniz kesindir. O alanda geliştirme yapacak diğer geliştiriciler için yol gösterici bir örnek niteliği taşır.
Şeytani Sihirbazlar (Evil Wizards)

Burada ki sihirbaz tabiri kurulum sihirbazlarından gelmektedir. Kodlama aşamasında bazı adımların es geçilmesi için kod yaratıcılar (code generators) kullanılır. Bu kod yaratıcılar işini kolaylaştırıyor gibi görülebilir fakat bazıları ne kadar melek görünse de gelişme sağladığın bir projen için şeytanlık yapıyor olabilir. Burada ki kilit nokta bir kod üretici kullanıyorsan veya bir geliştirme ortamını otomatik bir şekilde kuruyorsan. Otomatik olarak gerçekleşen bu sürecin tamamına hakim olman gerekmektedir. Bizim kontrolümüz ve bilgimiz dışında herhangi bir şeyin proje içerisinde varlığını sürdürmesini istemeyiz. Proje başında otomatik oluşturulan bir işlem siz farketmeden arkaplanda hayatını sürdürüyor olabilir ve bir gün çözemediğin bir hatanın kaynağı olarak bu üretilen kodu bulabilirsin.
