Temiz Kod Nasıl Yazılır? – Basitlik İlkesi – II

Bir önceki yazıda ele aldığımız basit kodun özelliklerine devam edelim.

Basit kodun en temel özelliği odaklı olmasıdır. Ancak sadece bir kavramı, bir işi, bir ihtiyacı halletmeye odaklanan kodlar anlaşılır olur. Bir kod ne kadar fazla şeyi bir arada halletmeye kalkıyorsa o derece anlaşılmaz olacaktır. Odaklanmak hem anlaşılır hem de kısa kodun dolayısıyla da basit kodun en temel özelliklerindendir. Odaklanmanın kendisi de odaklanılacak bir kavram olduğundan, örneğin “bir satırda neye odaklanılır”, “bir sınıfta neye odaklanılır” gibi soruları etraflıca ele almak gerektiğinden, odaklanmanın detayları sonraki yazılarda ele alınacaktır.

Basit yazılan kodun diğer özelliği olan kısa olmasıdır. Kısa kod için şu iki şeye dikkat etmek gereklidir: Odaklanmak ve budamak.

Öncelikle kısa kod, ancak içeriğinden dolayı kısadır. Yani kod ancak küçük bir şeyi ya da bir seferde sadece bir şeyi hedefliyorsa kısa olur. Kısa kod ancak hem yapılan işin küçük olması hem de bu küçük işin kısa bir şekilde yapılmasıyla mümkün olur. Yapılacak işi ne kadar küçük seviyede tutulabilirse, yazılacak kod da o kadar kısa olacaktır. Yapılacak iş ne kadar büyük ve geniş tutulursa, kodda o kadar çok şey yapılır, bu da odaklanma prensibine terstir. Burada bahsedilen “şey” ve “iş” kavramları ise nihayetinde sorumluluklardır ve odaklanmaktan kasıt da “tek sorumluluk” (single responsibility) prensibidir.

Odaklanma olmadan kısa ve anlaşılır kod yazılamaz, uzun kod yazılır, çok kod yazılır, iç-içe geçmiş, çorba kod yazılır, dolayısıyla karmaşık kod yazılır, basit kod yazılamaz.

Örneğin aşağıdaki arayüzü odaklı olarak nasıl gerçekleştirelim?

public void login(String tckn, String password) throws 
   NoSuchCustomerException,CustomerAlreadyLoggedException, 
   WrongCustomerCredentialsException, 
   MaxNumberOfFailedLoggingAttemptExceededException, 
   CustomerLockedException;

Yukarıdaki metodun arayüzündeki sıra dışı durumlardan da anlaşıldığı gibi, aslında bu metotta bir kaç farklı şey kontrol edilmekte ve bu kontrollerin hepsi olumlu sonuçlanırsa, hiç sıra dışı durum nesnesi fırlatılmayacağından, metot başarılı bir şekilde sonlanacaktır. Dolayısıyla bu metot aşağıdaki gibi yazılabilir:

public void login(String tckn, String password) throws NoSuchCustomerException, 
                  CustomerLockedException, CustomerAlreadyLoggedException,  
                  WrongCustomerCredentialsException,   
                  MaxNumberOfFailedLoggingAttemptExceededException {
    Customer customer = customerDao.retrieveCustomer(tckn);
 
    // If passwords match, customer hasn't already been locked nor logged in
    // Customer loggs in and it is now currentCustomer
    if (customer.getPassword().equals(password) & 
                           !customer.isLocked() & !customer.isLoggedIn()) {
        // Database is updated when a customer logs in.
        customer.setLoggedIn(true);
        if (customerDao.updateCustomer(customer))
        currentCustomer = customer;
        loginAttemptCount = 0;
    } else if (customer.isLoggedIn()) {
        throw new CustomerAlreadyLoggedException("Customer is already logged in. 
                                                  Please first log out.");
    } else if (customer.isLocked()) {
        throw new CustomerLockedException("Customer is locked. 
                                           Please consult your admin.");
    } else if (!customer.getPassword().equals(password)) {
        loginAttemptCount++;
        if (loginAttemptCount == Integer.parseInt(ATMProperties.getProperty(
                                                  "customer.maxFailedLoginAttempt"))) {
            customer.setLocked(true);
            customerDao.updateCustomer(customer);
            throw new MaxNumberOfFailedLoggingAttemptExceededException("Max number of 
                                                              login attempt reached: "
                                                              + loginAttemptCount);
        }
        throw new WrongCustomerCredentialsException("TCKN/password is wrong.");
    }
}

Peki sizce bu kod odaklı mıdır? Yani “login” metodu, sadece bir şeyi mi hallediyor? Aslında bakarsanız bir şeyi hallediyor, müşterinin login olmasını. Ama belli ki bu üst seviye bir sorumluluk ve alt seviyede müşterinin bulunup getirilmesi, kilitli (locked) veya zaten sistemde (logged in) olup olmadığının belirlenmesi, passwordünün uygunluğunun kontrol edilmesi ve eğer yanlış password girilmiş ve bu şekilde yanlış password girme sayısı, belirli bir değeri aşmışsa, müşterinin kilitlenmesi (locked) sorumluluklarını içermektedir. Bu durumda aslolan şey, tüm bu alt sorumlulukların ayrı metotlarda ifade edilmesidir. Çünkü, bu alt sorumlulukların her birisi, sistemde farklı yerlerde tekrar tekrar ihtiyaç duyulabilecek ve bu yüzden metotları çağrılabilecektir. Dolayısıyla bu haliyle “login” metodunun yukarıdaki gerçekleştirilmesi, odaklı değildir, “tek sorumluluk” prensibine uygun değildir.

Yukarıdaki kodun bir diğer problemi ise, ayrık olması gereken sorumlulukları bir araya getirdiği için, copy-paste yardımıyla, tekrarlı kod yazmayı teşvik ediyor olmasıdır. Örneğin, sisteme bir başka yerde ihtiyaç duyulacak ve örneğin müşterinin kilitli olup-olmadığını kontrol eden kod, buradan copy-paste ile alınarak çoğaltılacaktır.

Bakın aşağıdaki kod ise, aynı metodun farklı sorumlulukları, farklı metotlarla halleden şeklidir ve bu yüzden herşey daha odaklıdır ve sorumluluk sahibidir.

public void checkIfCustomerAlreadyLoggedIn(Customer customer) throws CustomerAlreadyLoggedException {
    if (customer.isLoggedIn()) {
        throw new CustomerAlreadyLoggedException("Customer is already logged in. 
                                                  Please first log out.");
    }
}
public void checkIfCustomerLocked(Customer customer) throws CustomerLockedException {
    if (customer.isLocked()) {
        throw new CustomerLockedException("Customer is locked. Please consult your admin.");
    }
}
public void checkCustomerPassword(Customer customer, String password) throws 
        MaxNumberOfFailedLoggingAttemptExceededException, WrongCustomerCredentialsException {
    if (!customer.getPassword().equals(password)) {
        loginAttemptCount++;
        checkLoginAttempCount(customer);
        throw new WrongCustomerCredentialsException("Wrong password!");
    }
}
public void checkLoginAttempCount(Customer customer) throws 
                                            MaxNumberOfFailedLoggingAttemptExceededException{
    if (loginAttemptCount == Integer.parseInt(ATMProperties.getProperty(
                                                           "customer.maxFailedLoginAttempt"))) {
        lockCustomer(customer);
    }
}
public void lockCustomer(Customer customer) throws MaxNumberOfFailedLoggingAttemptExceededException{
    customer.setLocked(true);
    throw new MaxNumberOfFailedLoggingAttemptExceededException("Max number of login 
                                                                attempt reached: "
                                                                + loginAttemptCount);
}
private void loginCustomer(Customer customer, String password) throws  
   CustomerAlreadyLoggedException, 
   WrongCustomerCredentialsException, 
   MaxNumberOfFailedLoggingAttemptExceededException, 
   CustomerLockedException{
		
      boolean login = false;
      checkIfCustomerAlreadyLoggedIn(customer);
      checkIfCustomerLocked(customer);
      checkCustomerPassword(customer, password);
      customerDao.updateCustomer(customer);
}

Bu şekilde sorumluluklar, ayrı metotlarda ifade edildikten sonra gerçekleştirdiğimiz metot, sadece bu alt sorumlulukları çağırır:

public void login(String tckn, String password) throws 
   NoSuchCustomerException, CustomerAlreadyLoggedException, 
   WrongCustomerCredentialsException, 
   MaxNumberOfFailedLoggingAttemptExceededException, 
   CustomerLockedException{

      Customer customer = customerDao.retrieveCustomer(tckn);
      loginCustomer(customer, password);
}

Odaklı kod yazmak, tek sorumluluk prensibine uymaktır demiştir. Bu da sorumlulukları önce bulmak sonra da ayırmakla olur. Bu anlamda, metot seviyesini düşündüğümüzde, daha alt parçalara bölünemeyecek ve sistemde tekrar tekrar kullanılacak her sorumluluk bir metotta halledilmelidir.

Yukarıdaki örnekten de açıkça görüldüğü gibi, kısa kod ancak ve ancak odaklanmakla yazılır. Bu anlamda esas amaç kısa kod değildir, odaklı koddur. Odaklı kod her halukarda kısa olma eğilimindedir. Odaklanmadan kısa kod yazmaya çalışmak, kodu okunmaz ve anlaşılmaz hale getirir. Kod, sorumluluklar tabanında bölünmeli ve yazılmalıdır.

Sınıf seviyesindeki sorumlulukların en temel olanları ise örneğin GOF’un tasarım kalıpları ile belirlenmiştir. Örneğin bu yazıda bu sorumluluklardan bazıları kısaca ifade edilmiştir. Dolayısıyla tasarım kalıplarını bilmeden sorumlulukları bulup dağıtmaya çalışmak, hem zordur hem de tekerleği tekrardan keşfetmeye çalışmaktır.

Bir sonraki yazıda budamayı ele alalım.

Son derece odaklı kodlar dilerim 🙂

Toplam görüntülenme sayısı: 1072