Selamlar, @'drose başlattığı nasıl temiz kod yazılır serisine ben de katkıda bulunmak istedim. Eğer nasıl temiz kod yazılır serisini görmediyseniz aşağıdaki linkten detaylı açıklamaları ve serinini ilk iki konusunu görebilirisiniz. O konuya bir göz gezdirmenizi öneririm çünkü bu konu da o serinin devamı niteliğinde olacaktır. Şimdiden iyi okumalar.
Nasıl temiz kod yazılır serisi
Fonksiyonlar bir kez yazıp birden fazla kez kullanmamıza olanak sağlayan programlamanın vazgeçilmez bir özelliğidir. Aşağıdaki kod düzeninde fonksiyonlara sırayla bakalım.
Bu upuzun bir kod ama kod içeriğini değil, değişken isimlerine bakın. Kafa karıştırıcı gözüküyor ama sadece 3 dakika okuyun kodu. Sanki kitap okur gibi okuyun
Okurken biraz zorlandıysanız peki kodunuzun daha okunabilir ve profesyonel olmasını nasıl sağlayabiliriz veya anlaması ve okunması kolay olan fonksiyonları sağlayan şeyler nedir ?
Fonksiyon oluşturmanın birinci kuralı küçük olmalıdırlar. Fonksiyon boyutu arttıkça hata almanız daha yüksek olasılıklı olacak ve bu hataları ayıklamanız daha fazla zaman ve performansa neden olacaktır.
Peki ideal bir fonksiyon örneği nasıl olmalı ?
Özellikle if-else-for gibi parantez veya noktalamalı işaretleri için fazladan bir satır atmamaya dikkat etmeniz gerekir. Belki size göre kod daha temiz gözükecektir ama bu aslında fonksiyonun bir satır daha büyümesine sebebiyet verecektir. Buna hiç gerek yok.
İlk önce konu başındaki fonksiyona bakın. Görünüşe göre o fonksiyon tek bir amaçtan daha fazla amaçlara yönelik çalışıyor. Bir de ikinci örneğimize bakın. Kısa ve tek bir amaca yönelik yazılmış bir fonksiyon. İlk örnekte aslında birden fazla fonksiyon mevcuttur. OOP (Nesne Tabanlı Programlama) Soyutlama denilen özellikler kullanılmıştır. Bunu anlamak için ikinci örneğe bakın. İki adet soyutlama işlemi yapılmış fonksiyon içinde.
O zaman bir fonksiyon içinde bir soyutlama işlemi varsa birden fazla amaca hizmet ediyor eğer yok ise tek bir amaca hizmet ettiğini gösterebiliriz. Fonksiyonunuz genel bir işlem yapacaksa kendi içinde soyutlama işlemleri yaparak parçalara bölerek tek bir amaca hizmet eden küçük fonksiyoncuklar yaparak parçadan bir bütüne gidecek şekilde fonksiyon yapmak temiz kod yazımı için doğru bir harekettir.
Yazılan kodlarda yukarıdan aşağıya okunacak şekilde yazılmalıdır. Şimdi zaten kodlar yukarıdan aşağıya yazılıyor der gibisiniz. Bu kuralda demek istenen şey yukarıdan aşağıya işlem yapılmalı.
C işlemi yapmak için önce A ve B işlemlerini yapmalıyız. Direkt C işlemeni yazamayız veya D işlemeni C işlemi olmadan yazmamalısınız. Eğer bu şekilde yapılırsa kodu yukarıdan aşağıya okuyan bir kişi C işlemini ilk görür sonra A sonra D sonra B okuyan kişi fonksiyon içinde ne olduğunu anlayamaz. Muhtemelen 6 ay sonra kendiniz okuduğunuzda da anlayamayacaksınız.
Switch ifadesini programlamaya başlayan herkes biliyordur. Bir diğer adıyla switch-case diyebiliriz. if-else koşullarının daha farklı bir yöntemidir. Birden fazla if-else bloğu açmak yerine switch case kullanarak daha temiz bir kod yazımına ulaşabiliriz.
Bir fonksiyon ve değişken isimlendirirken uzun süre boyunca düşünmekten veya bu isim çok uzun oldu değiştireyim gibi durumlardan geri çekilmeyin. Bırakın bir değişkene 1 saat boyunca düşünüp uzun olsun. Eğer o isim yaptığınız işlemi tam olarak anlamlandırabiliyorsa bu doğru bir isimlendirmedir. Mesela bir hesap makinesi yapıyorsunuz ve
islemleri "islem" değişkenine atadınız bu sonuç değişkeni biraz havada kalabilir. Hepsine teker teker toplamaIslem, cikarmaIslem gibi işlemin türünü belirtirsek hep kodda yapılmak istenen şey daha kodu okumadan anlamanıza hem de ileriki zamanlarda kodu okuyup güncellemeye çalışırken işinize yarayacaktır.
Fonksiyonlarda argüman kullanacağımız zaman en fazla 3 argüman almasına dikkat etmeliyiz. 3'ten fazla argüman kafa karışıklığına ve işlem karmaşasına neden olur. Çok spesifik durumlar harici 3'ten fazla argüman kullanmamaya özen göstermeliyiz. Ayrıca fonksiyonlarda argüman kullanmak biraz göz yorucu olabilir bir fonksiyon düşünelim includeSetupPage() bunu rahatça okuyabiliyoruz ama bu fonksiyona argüman eklersek includeSetupPageInto(newPage
Content) bu gibi örneklerde biraz ne oluyor ya kafasına girebiliyoruz. Bu yüzden argümanları gerçekten ihtiyaç halinde kullanmamız gerekiyor.
Monadik formlar dediğimiz şey tek bir parametre alıp bunu monad adı verilen veri yapısı diyebiliriz. Peki bunun temiz kod yazımıyla ne alakası var der iseniz monad için kullanılan parametrelerde isimlendirmeler önemlidir. Okuyucu açık bir şekilde bunun bir monad olacağını anlamasını sağlamalısınız.
Bayrak argümanları pek önerilmez. Bir boolean'ı bir fonksiyona geçirmek gerçekten berbat bir yoldur ayrıca işlem
imzasını anında karmaşık hale getirir ve bu işlevi ilan edilmesi için berbat bir durum yaratır çünkü birden fazla şey yapar. Eğer bayrak doğruysa bir şey yapar, bayrak yanlışsa daha farklı (yapmak istemediğimiz) işler yapar.
İkili Fonksiyonlar genel olarak tekli fonksiyonları anlamaktan zordur. Örnek olarak writeField(name) ile writeField(output-Stream, name) arasındaki farkı görebiliriz. Bariz bir şekilde hangisi tekli hangisi ikili fonksiyon olduğunu anlayabiliriz. Gerektiği zaman iki fonksiyon örneği de iş görür ama bunları doğru zamanda kullanmak lazımdır. Sadece ikili fonksiyonları anlamak ve okumak bir tık daha zordur.
Üçlü fonksiyonlar diğer iki fonksiyona göre mantıken daha zordur. Sıralama işlemleri ve problemler iki kat daha artar.
Fonksiyonlara argüman eklerken türlerini belirtmektir. Örnek olarak
Burada x,y,radius değerlerini double değişken türüyle atadık örnek olarak.
Bazı zamanlar bir fonksiyona atanacak değişkenlerin sayısı farklı olabilir. Kısaca değişkenlerin sayısını bilemediğimiz durumlarda tek tek her yeni değişkeni fonksiyona tanıtmak rezalet bir kod yapısıdır. Bunun yerine alttaki örnekteki yapı çok daha temiz ve dinamik bir kod yapısı sunar.
...args demek yeni eklenecek değişkenleri de ekle anlamı taşımaktadır.
Yazılan fonksiyonlar tek bir amaca hizmet etmelidir. Temel olarak tek bir amaca hizmet edebiliyor olabilir ama yan etkileri dediğimiz etkileri de olabilir. Belki bir değişkenin değerini değiştiriyor mesela kullanıcı giriş fonksiyonu düşünün. Fonksiyon amacı kullanıcıdan alınan bilgilerle veritabanındaki bilgileri karşılaştırıp doğruysa kullanıcı girişini sağlamaktır. Bu fonksiyonda yan etki olarak girisYapildiMİ false olarak başlatırız eğer kullanıcı giriş yaparsa bu false değeri true olarak döndürürüz. Aslında başlık biraz akıl oyunu yapmış bizlere. Fonksiyonların yan etkileri olabilir. Örnek olarak aşağıdaki kodu örnek verebiliriz.
Bir fonksiyon yazarken kendini tekrar etmemek en önemli etkendir. Peki neden?
Bir hesap makinesi yapıyorsunuz ve bunun için fonksiyonlarınız negatifCikar,negatifTopla,negatifCarp,negatif vs. diye gidiyor diyelim. Neden hepsini direkt bir fonk. içine yazmıyoruz sonuçta aynı işlemi 10 kez yazıp programı çalıştırmak var veya 4 tane fonk. yapıp hepsini tek bir fonkta birleştirip çalıştırmak var. Hem daha az kod hem daha iyi okunabilirlik sağlama yolları varken neden hem kendimize hem de okuyana eziyet edelim ki.
Fonksiyon yazarken yapılacak işlemleri önceden belirlenmiş olması kodunuzun temiz olmasını sağlayacaktır. Planlaması olmayan kodu her dakika güncellersiniz veya özellik ekler-çıkarırsınız günün sonunda tek bir işlem için koca gününüz gitmiş olur. Eğer önceden planlaması yapılsaydı eklenecek özellikler belli eklenmeyecekler belli işiniz çok daha kolay olacaktı.
Bölüm 3 bitti.
Şu anki ilerleme oranımız 53/ 400 syf.
Nasıl temiz kod yazılır serisi
Bölüm 3 Fonksiyonlar
Fonksiyonlar bir kez yazıp birden fazla kez kullanmamıza olanak sağlayan programlamanın vazgeçilmez bir özelliğidir. Aşağıdaki kod düzeninde fonksiyonlara sırayla bakalım.
Kod:
public static String testableHtml(
PageData pageData,
boolean includeSuiteSetup
) throws Exception {
WikiPage wikiPage = pageData.getWikiPage();
StringBuffer buffer = new StringBuffer();
if (pageData.hasAttribute("Test")) {
if (includeSuiteSetup) {
WikiPage suiteSetup =
PageCrawlerImpl.getInheritedPage(
SuiteResponder.SUITE_SETUP_NAME, wikiPage
);
if (suiteSetup != null) {
WikiPagePath pagePath =
suiteSetup.getPageCrawler().getFullPath(suiteSetup);
String pagePathName = PathParser.render(pagePath);
buffer.append("!include -setup .")
.append(pagePathName)
.append("\n");
}
}
WikiPage setup =
PageCrawlerImpl.getInheritedPage("SetUp", wikiPage);
if (setup != null) {
WikiPagePath setupPath =
wikiPage.getPageCrawler().getFullPath(setup);
String setupPathName = PathParser.render(setupPath);
buffer.append("!include -setup .")
.append(setupPathName)
.append("\n");
}
}
buffer.append(pageData.getContent());
if (pageData.hasAttribute("Test")) {
WikiPage teardown =
PageCrawlerImpl.getInheritedPage("TearDown", wikiPage);
if (teardown != null) {
WikiPagePath tearDownPath =
wikiPage.getPageCrawler().getFullPath(teardown);
String tearDownPathName = PathParser.render(tearDownPath);
buffer.append("\n")
.append("!include -teardown .")
.append(tearDownPathName)
.
}
Bu upuzun bir kod ama kod içeriğini değil, değişken isimlerine bakın. Kafa karıştırıcı gözüküyor ama sadece 3 dakika okuyun kodu. Sanki kitap okur gibi okuyun
Okurken biraz zorlandıysanız peki kodunuzun daha okunabilir ve profesyonel olmasını nasıl sağlayabiliriz veya anlaması ve okunması kolay olan fonksiyonları sağlayan şeyler nedir ?
1) Küçük Olması
Fonksiyon oluşturmanın birinci kuralı küçük olmalıdırlar. Fonksiyon boyutu arttıkça hata almanız daha yüksek olasılıklı olacak ve bu hataları ayıklamanız daha fazla zaman ve performansa neden olacaktır.
Peki ideal bir fonksiyon örneği nasıl olmalı ?
Kod:
public static String renderPageWithSetupsAndTeardowns(
PageData pageData, boolean isSuite) throws Exception {
if (isTestPage(pageData))
includeSetupAndTeardownPages(pageData, isSuite);
return pageData.getHtml();
}
2) Parantezlere Dikkat !
Özellikle if-else-for gibi parantez veya noktalamalı işaretleri için fazladan bir satır atmamaya dikkat etmeniz gerekir. Belki size göre kod daha temiz gözükecektir ama bu aslında fonksiyonun bir satır daha büyümesine sebebiyet verecektir. Buna hiç gerek yok.
3) Tek Bir Amaca Hizmet
İlk önce konu başındaki fonksiyona bakın. Görünüşe göre o fonksiyon tek bir amaçtan daha fazla amaçlara yönelik çalışıyor. Bir de ikinci örneğimize bakın. Kısa ve tek bir amaca yönelik yazılmış bir fonksiyon. İlk örnekte aslında birden fazla fonksiyon mevcuttur. OOP (Nesne Tabanlı Programlama) Soyutlama denilen özellikler kullanılmıştır. Bunu anlamak için ikinci örneğe bakın. İki adet soyutlama işlemi yapılmış fonksiyon içinde.
O zaman bir fonksiyon içinde bir soyutlama işlemi varsa birden fazla amaca hizmet ediyor eğer yok ise tek bir amaca hizmet ettiğini gösterebiliriz. Fonksiyonunuz genel bir işlem yapacaksa kendi içinde soyutlama işlemleri yaparak parçalara bölerek tek bir amaca hizmet eden küçük fonksiyoncuklar yaparak parçadan bir bütüne gidecek şekilde fonksiyon yapmak temiz kod yazımı için doğru bir harekettir.
4) Yukarıdan Aşağıya Okuma Kuralı
Yazılan kodlarda yukarıdan aşağıya okunacak şekilde yazılmalıdır. Şimdi zaten kodlar yukarıdan aşağıya yazılıyor der gibisiniz. Bu kuralda demek istenen şey yukarıdan aşağıya işlem yapılmalı.
C işlemi yapmak için önce A ve B işlemlerini yapmalıyız. Direkt C işlemeni yazamayız veya D işlemeni C işlemi olmadan yazmamalısınız. Eğer bu şekilde yapılırsa kodu yukarıdan aşağıya okuyan bir kişi C işlemini ilk görür sonra A sonra D sonra B okuyan kişi fonksiyon içinde ne olduğunu anlayamaz. Muhtemelen 6 ay sonra kendiniz okuduğunuzda da anlayamayacaksınız.
5) Switch İfadesi
Switch ifadesini programlamaya başlayan herkes biliyordur. Bir diğer adıyla switch-case diyebiliriz. if-else koşullarının daha farklı bir yöntemidir. Birden fazla if-else bloğu açmak yerine switch case kullanarak daha temiz bir kod yazımına ulaşabiliriz.
Kod:
public Money calculatePay(Employee e)
throws InvalidEmployeeType {
switch (e.type) {
case COMMISSIONED:
return calculateCommissionedPay(e);
case HOURLY:
return calculateHourlyPay(e);
case SALARIED:
return calculateSalariedPay(e);
default:
throw new InvalidEmployeeType(e.type);
}
}
6) Mantıklı ve Açıklayıcı İsimlendirmeler Yapın
Bir fonksiyon ve değişken isimlendirirken uzun süre boyunca düşünmekten veya bu isim çok uzun oldu değiştireyim gibi durumlardan geri çekilmeyin. Bırakın bir değişkene 1 saat boyunca düşünüp uzun olsun. Eğer o isim yaptığınız işlemi tam olarak anlamlandırabiliyorsa bu doğru bir isimlendirmedir. Mesela bir hesap makinesi yapıyorsunuz ve
islemleri "islem" değişkenine atadınız bu sonuç değişkeni biraz havada kalabilir. Hepsine teker teker toplamaIslem, cikarmaIslem gibi işlemin türünü belirtirsek hep kodda yapılmak istenen şey daha kodu okumadan anlamanıza hem de ileriki zamanlarda kodu okuyup güncellemeye çalışırken işinize yarayacaktır.
7) Fonksiyon Argümanları
Fonksiyonlarda argüman kullanacağımız zaman en fazla 3 argüman almasına dikkat etmeliyiz. 3'ten fazla argüman kafa karışıklığına ve işlem karmaşasına neden olur. Çok spesifik durumlar harici 3'ten fazla argüman kullanmamaya özen göstermeliyiz. Ayrıca fonksiyonlarda argüman kullanmak biraz göz yorucu olabilir bir fonksiyon düşünelim includeSetupPage() bunu rahatça okuyabiliyoruz ama bu fonksiyona argüman eklersek includeSetupPageInto(newPage
Content) bu gibi örneklerde biraz ne oluyor ya kafasına girebiliyoruz. Bu yüzden argümanları gerçekten ihtiyaç halinde kullanmamız gerekiyor.
8) Monadik Formlar
Monadik formlar dediğimiz şey tek bir parametre alıp bunu monad adı verilen veri yapısı diyebiliriz. Peki bunun temiz kod yazımıyla ne alakası var der iseniz monad için kullanılan parametrelerde isimlendirmeler önemlidir. Okuyucu açık bir şekilde bunun bir monad olacağını anlamasını sağlamalısınız.
9) Bayrak Argümanları
Bayrak argümanları pek önerilmez. Bir boolean'ı bir fonksiyona geçirmek gerçekten berbat bir yoldur ayrıca işlem
imzasını anında karmaşık hale getirir ve bu işlevi ilan edilmesi için berbat bir durum yaratır çünkü birden fazla şey yapar. Eğer bayrak doğruysa bir şey yapar, bayrak yanlışsa daha farklı (yapmak istemediğimiz) işler yapar.
10) İkili Fonksiyonlar
İkili Fonksiyonlar genel olarak tekli fonksiyonları anlamaktan zordur. Örnek olarak writeField(name) ile writeField(output-Stream, name) arasındaki farkı görebiliriz. Bariz bir şekilde hangisi tekli hangisi ikili fonksiyon olduğunu anlayabiliriz. Gerektiği zaman iki fonksiyon örneği de iş görür ama bunları doğru zamanda kullanmak lazımdır. Sadece ikili fonksiyonları anlamak ve okumak bir tık daha zordur.
11) Üçlü Fonksiyonlar
Üçlü fonksiyonlar diğer iki fonksiyona göre mantıken daha zordur. Sıralama işlemleri ve problemler iki kat daha artar.
12) Argüman Objeleri
Fonksiyonlara argüman eklerken türlerini belirtmektir. Örnek olarak
Kod:
Circle makeCircle(double x, double y, double radius);
Circle makeCircle(Point center, double radius);
Burada x,y,radius değerlerini double değişken türüyle atadık örnek olarak.
13) Argüman Listesi
Bazı zamanlar bir fonksiyona atanacak değişkenlerin sayısı farklı olabilir. Kısaca değişkenlerin sayısını bilemediğimiz durumlarda tek tek her yeni değişkeni fonksiyona tanıtmak rezalet bir kod yapısıdır. Bunun yerine alttaki örnekteki yapı çok daha temiz ve dinamik bir kod yapısı sunar.
Kod:
public String format(String format, Object... args)
...args demek yeni eklenecek değişkenleri de ekle anlamı taşımaktadır.
13) Yan Etkisi Olmamalı
Yazılan fonksiyonlar tek bir amaca hizmet etmelidir. Temel olarak tek bir amaca hizmet edebiliyor olabilir ama yan etkileri dediğimiz etkileri de olabilir. Belki bir değişkenin değerini değiştiriyor mesela kullanıcı giriş fonksiyonu düşünün. Fonksiyon amacı kullanıcıdan alınan bilgilerle veritabanındaki bilgileri karşılaştırıp doğruysa kullanıcı girişini sağlamaktır. Bu fonksiyonda yan etki olarak girisYapildiMİ false olarak başlatırız eğer kullanıcı giriş yaparsa bu false değeri true olarak döndürürüz. Aslında başlık biraz akıl oyunu yapmış bizlere. Fonksiyonların yan etkileri olabilir. Örnek olarak aşağıdaki kodu örnek verebiliriz.
Kod:
private Cryptographer cryptographer;
public boolean checkPassword(String userName, String password) {
User user = UserGateway.findByName(userName);
if (user != User.NULL) {
String codedPhrase = user.getPhraseEncodedByPassword();
String phrase = cryptographer.decrypt(codedPhrase, password);
if ("Valid Password".equals(phrase)) {
Session.initialize();
return true;
}
}
return false;
}
}
14) Kendini Tekrar Etme
Bir fonksiyon yazarken kendini tekrar etmemek en önemli etkendir. Peki neden?
Bir hesap makinesi yapıyorsunuz ve bunun için fonksiyonlarınız negatifCikar,negatifTopla,negatifCarp,negatif vs. diye gidiyor diyelim. Neden hepsini direkt bir fonk. içine yazmıyoruz sonuçta aynı işlemi 10 kez yazıp programı çalıştırmak var veya 4 tane fonk. yapıp hepsini tek bir fonkta birleştirip çalıştırmak var. Hem daha az kod hem daha iyi okunabilirlik sağlama yolları varken neden hem kendimize hem de okuyana eziyet edelim ki.
Fonksiyon yazarken yapılacak işlemleri önceden belirlenmiş olması kodunuzun temiz olmasını sağlayacaktır. Planlaması olmayan kodu her dakika güncellersiniz veya özellik ekler-çıkarırsınız günün sonunda tek bir işlem için koca gününüz gitmiş olur. Eğer önceden planlaması yapılsaydı eklenecek özellikler belli eklenmeyecekler belli işiniz çok daha kolay olacaktı.
Bölüm 3 bitti.
Şu anki ilerleme oranımız 53/ 400 syf.


