Neden, önce, olacağını düşünürsün ki?

programlamakendimce

4 yıl önce 0 yorum

Baştan belirteyim +5 senesini programlama ile geçirenler için bu makale bir çerez olabilir. Ancak gelişmek isteyen arkadaşlara ışık tutacağını düşündüğüm bir makale olduğunu da belirteyim. Çoğu zamanımız kod yazarak veya bir hatayı düzeltmek için kod yazarak geçiyor.

Peki açtığınız o "ulu" methodun içindeki imlecin yanıp sönmesini izlerken aklınızdan neler geçiyor?

Ben söyleyim:

"Şu değere göre şunu yaparım, sonra şunu yaparım".

Aslında bunu dediğimiz için bu programcılık oyunu bizi çabuk yaşlandırıyor. Şu ana kadar anlattıklarım size "e bu çok doğal, ne bekliyordun ki?" sorusunu sordurabilir. Evet çok doğal bir olay. Yani o methodu oluşturduğunuzda zaten içerisine ne yapması gerekiyorsa onu yazacaksınız.

Biraz daha derin düşünmek gerekiyor, ne gibi mesela?

Ya bazı şeyler ters giderse?

İşte başından beri anlatmak istediğim de bu. Yani aslında yapmamız gereken bu soruyu başında sormak! Tamam o method o işi yapacak ama ya başarısız olursa veya bir şeyler eksik gelirse ne olur? gibi soruları ilk önce sormak gerekir.

İki sayıyı bölen bir method yazdığımızı düşünelim. Hemen aklımıza gelen kod şunun gibi olur:

sayiBol(sayi1, sayi2) {
    return sayi1/sayi2;
}

Ve bunun doğru olduğunu düşünürüz. Ancak başında düşünmemiz gereken bir şey vardı onu unuttuk. Ya sayi2 0 ise?

Peki sayi2 0 değerini kontrol eden ve eğer sayi2 0 ise hata veren bir kod yazalım:

sayiBol(sayi1, sayi2) {
    if (sayi2 != 0) {
        return sayi1/sayi2;
    }
    else {
        throw 'sayi2 sıfır olamaz';
    }
}

Yukarıdaki kodun da doğru olduğunu ve sıkıntı olmadığını mı düşünüyorsunuz? Peki bu makalenin başlığında yazan şey neydi? Uyarlarsak: "Neden, önce, sayıları böleceğini düşünüyorum ki?"

En son verdiğim kodda return işlemi zaten yapılıyor neden else'e ihtiyaç duyalım? if çalışır çalışmaz method return olacak zaten, else gibi bir ifadeye gerek yok. Düzenlersek:

sayiBol(sayi1, sayi2) {
    if (sayi2 != 0) {
        return sayi1/sayi2;
    }

    throw 'sayi2 sıfır olamaz';
}

Ve hala istediğimiz gibi bir method yazmadık. İşte sorunlarımızla yüzleşme zamanı geldi. Yukarıdaki methodda da ilk önce olacağını düşünüyoruz. Sorun yok kod gerçekten doğru bir şekilde çalışıyor, bundan eminiz hata kontrolü de var.

E peki sorun ne?

Yazdığımız method bölme işi yapacak doğru mu? Biz o methodun gerçek işini neden en altta yapmıyoruz, yani ilk önce pesimist bir kod yazıp geri kalan kısımlarda da optimist kodumuzu yazsak olmuyor mu? Aslında bir programcının program yazarken pesimist olması gerekir. Hayatta da böyle yapmıyor muyuz zaten? Örneğin (ikinci el) bir araba alacağınızı düşünelim, şöyle olsun:

Araba pazarına gittiniz:

  1. Cebinize uygun bir araba seçtiniz
  2. Parayı ödediniz ve kullanmaya başladınız
  3. İkinci ay sıkıntı çıkardı
  4. Satıcıya gittiniz
  5. Arabayı iade ettiniz.

Böyle bir şeyi tabii ki de yapmazsınız. Neden? çünkü alacağınız arabanın sıkıntılarına bakmadan arabayı almak akıl kârı değil. Arabanın yedek parçası, lastikleri, kaç yaşında olduğu vb. kriterler sizin eleme yapmanıza sebep olmalı. 

Anahtar kelime "Eleme"

Programın da aynı şekilde işi yapmadan önce eleme yapması gerekir. Üstteki bahsettiğimiz bölme methodunda her şeyden önce sayi2'nin 0 olup olmadığına bakmalıyız ve 0 ise orada programı kırmalıyız. Yani:

sayiBol(sayi1, sayi2) {
    if (sayi2 == 0)
        throw 'sayi2 sıfır olamaz';

    return sayi1/sayi2;
}

Gördünüz mü? asıl iş, methodun altında. Bu kod sanki kral tahtına yerleşecekmiş de, o sırada temizlikçiler tahtın önünü temizliyormuş gibi değil mi? Kral gelmeden tahtın önü tertemiz olmuş oldu.

Bu bölme methodu kolay görünmüş olabilir. Daha güzel bir örnek verelim. Örneğin bir kullanıcı girişi uygulaması yapıyorsunuz, username ve password gibi iki değer gelsin ve biz kullanıcıya giriş yaptıralım.

import Database from '...';
import MD5 from '...';

class UserManager {
    login(username, password) {
        const pool = Database.pool();

        const count = pool.execQuery('SELECT COUNT(*) AS count FROM users WHERE username = ? AND password = ?', [username, MD5(password)]);

        pool.close();

        return count > 0;
    }
}

Yukarıdaki kod ne yapıyor? Methoda gelir gelmez bir database erişimi için bir pool alıyor. aldığı pool ile username değeri ve password değeri eşit olan kullanıcı sayısını getiriyor. Dönüş olarak da eğer sayı 0'dan büyükse yani böyle bir kullanıcı varsa true yoksa false dönüyor.

Koddaki hatayı gördünüz mü? :)

Kod çok optimist yazılmış. Method çalışır çalışmaz gidip database pool açmış. E peki password değeri gelmezse ya da username değeri gelmezse niye veritabanı pool açayım ki. Ya database'den pool alamazsam, ya aldığım pool işlemi yaparken hata verirse açtığım pool'u kim kapatacak?

Amaç aslında bu programı yazıp ardından bunları düşünmek değildir. Verdiğim bu son örnekte böyle yapıyorum ama olayı anlatabilmek için.

Asıl amaç programı yazarken olumsuzlukları başında düşünmek.

Yani en başa dönersek, bizim username ve password alanına göre kişinin giriş yapıp yapmadığınız anlamak. Öncelikle kullanıcı username veya password alanlarından herhangi birini göndermezse kullanıcıya hata dönelim. Sonra bir database pool açalım. Pool'u başarıyla açtıktan sonra çıkan sorunda pool'u kapatalım (dikkat edin hala iş'i yazmıyorum). Sonra işimizi yazalım. Yani kısaca kodu şöyle yapabiliriz:

import Database from '...';
import MD5 from '...';

class UserManager {
    login(username, password) {
        if (!username || !password)
            throw 'username ve password gereklidir!';

        const pool = Database.pool();

        try {
            const count = pool.execQuery('SELECT COUNT(*) AS count FROM users WHERE username = ? AND password = ?', [username, MD5(password)]);

            return count > 0;
        }
        catch(e) {
            console.error("Database error", e);
            throw e;
        }
        finally {
            pool.close();
        }
    }
}

Bunun gibi ve buna benzer birçok örnekle bunlar açıklanabilir. (Not: yukarıdaki örneği throwable olarak düşündüm, dolayısıyla pool sabitine atama yaparken bir try/catch'e ihtiyaç duymadım, patlarsa orda patlasın)

Son iki senedir bu konu ile ilgili başıma çok olay geldi :) Birkaç arkadaşımı kod yazarken izlediğimde hep şu yaşanıyor.

- Arkadaşım: Knk güzelmiş ya, ne yapayım şimdi?
- Ben: Şimdi kullanıcıyı kaydetmen lazım POST /users şeklinde gelince kullanıcıyı kaydedeceksin, models'de createUser var zaten.
- O sırada arkadaşım POST /users'ı karşılayan yerde hemen createUser methodunu çalıştırır ve .then yazıp içini doldurmaya başlar
- Ben: Knk catch'i yazaydın ya.
- Arkadaşım: Onu sonra yazarım yea.
- Ve kodu ilk çalıştırmasında response dönmez, çünkü bir hata oluşur ve ortada henüz yazılı bir catch olmadığı için hatayı yakalamaz. :D

Bir başka arkadaşımla da şunu yaşamıştım.  

- Arkadaşım: Tamamdır knk o zaman geolocation'ı alıyorum burdan
- Ben: tamam then(res => ...) yapıyorsun da, ya res gelmezse?
- Arkadaşım: res hep geliyo ki knk
- Ve kodu çalıştırır çalıştırmaz res undefined gelir :D

Genel Özet

Ne kadar acı catch yazmamak bu NodeJS'de
Dönmezsen o promise'ı, ararsın hatayı Sublime Text'te!

Makalelerimi merak ediyorsanız bloguma abone olarak bana destek olabilirsiniz. Böylelikle makaleleri kaçırmamış olursunuz. Beni okuduğunuz için teşekkürler :) sağlıcakla kalın :)

Düşündüklerin nedir ?

Abdurrahman Eker

(1010 Eylül 11111001100)

  • Full Stack Developer Turkey/Sivas
  • İnternette Avare Kodcu
  • coffee
  • github
  • instagram
  • linkedin
  • youtube
  • Yeni içeriklerden haberdar olmak ister misin ?