Clean Code

Berkay Çoban
6 min readApr 22, 2021

--

Photo by Chris Ried on Unsplash

“Bu kitabı iki sebepten ötürü okuyorsunuz: ilki yazılımcısınız, ikincisi daha iyi bir yazılımcı olmak istiyorsunuz. Güzel, çünkü daha iyi yazılımcılara ihtiyacımız var.” Clean Code, Robert C. Martin

Clean Code Nedir?

Temiz kod, anlaşılması ve değiştirilmesi kolay koddur.

Yukardaki açıklama Clean Code yaklaşımını çok iyi tanımlıyor. Anlaşılması ve değiştirilmesi kolay kodlar geliştirmenin birçok faydası vardır. Örneğin;

  • Anlamak kolaydır
  • Kodun yapacağı iş bellidir.
  • Kodları 6 ay sonra bile anlayabilirsiniz. (Sonraki yazılımcı için çok önemli)
  • Test etmek daha kolaydır. (Fonksiyon tek bir işi kaliteli yapmalı)

vb.

Clean Code yaklaşımında dikkat edilmesi gereken başlıklar: İsimlendirme, Koşullar, Döngüler, Yorum satırları, Fonksiyonlar, Hata Yönetimi, OOP, Tasarım Prensibleri, SOLID, DRY, Tasarım Kalıpları

İsimlendirme

Her şeye bir isim veririz; değişkenlerimize, fonksiyonlarımıza, argümanlarımıza, sınıflarımıza ve paketlerimize. O kadar çok isimlendirme yaparız ki, bunu iyi yapsak iyi olur aslında. :)

İyi isim seçmek zaman alır, ancak uzun vadede daha çok zaman kazandırır.

Değişken isimlerinde özel, açıklayıcı kelimeler kullanmak iyidir. İsimlendirme yaparken çok uzun olacak diye endişe etmemeliyiz.

int s; // gun cinsinden gecen sure

s hiçbir şeyi açıklamıyor. s yerine aşağıdakiler daha açıklayıcıdır.

int gunCinsindenGecenSure;
int gecenSureninGunDegeri;
int gecmisGunSuresi;

Kötü

public List<float> liste = new List<float>
{10, 56, 36, 78, 90, 85, 100, 74};
public List<float> listeGetir() {
List<float> list1 = new List<float>();
foreach(var item in liste){
if(item >= 70){
list1.add(item);
}
}
return list1;
}

İyi

public List<float> ogrenciNotlari = new List<float>
{10, 56, 36, 78, 90, 85, 100, 74};
public const float GECME_NOTU = 70;public List<float> dersiGecenNotlariGetir() {
List<float> gecenNotlar = new List<float>();
foreach(var not in ogrenciNotlari ){
if(not >= GECME_NOTU){
gecenNotlar.add(item);
}
}
return gecenNotlar;
}

Aranabilir İsimler

Tek harfli değişken isimlendirmesi yerine anlamlı ve aranabilir isimler kullanmalıyız. Tek harfli değişkenlerde döngülerde i gibi kullanılabilir fakat abartmamak gerekir. Tek harfli isimlendirmeler kullanırsak kodu anlamak ve okumak zor olur.

Sınıf İsimlendirmesi

Oluşturulacak sınıfın Single Responsibility (Tek Sorumluluk) Prensibine uygun ve isminin de bu prensibi net bir biçimde yansıtıyor olması gerekir. Sınıf isimleri fiil değil isim olmalıdır.

Common, Manager, Data, Info          // Kötü
Customer, Account, ProductRepository // İyi

Metot İsimlendirmesi

Metod isimleri metodun ne yaptığını açıklamalıdır. Kötü isimlendirme kafa karışıklığına sebep olur. Metod isimlendirmesinde camelCase yaklaşımı kullanılabilir.

Kötü

function aMetodu(int a, int b) { return a + b; }  

İyi

function ikiSayiTopla(int a, int b) { return a + b; }

Koşullar

Kodlarda hiç koşul olmasaydı okuması çok kolay olurdu. Kod ne kadar dallanırsa anlamak o kadar zorlaşır. Rahat anlaşılan kodlar yazmak istiyorsak koşullarımız doğal olsun. Bu sayede anlam bütünlüğü sağlanır ve geliştirici tekrar tekrar dönüp anlamaya çalışmak zorunda kalmaz.

Argüman Sırası

Örneğin “Sınav notu 50 üzeri olanlar dersi geçebilir” koşulunu oluşturmak istiyoruz diyelim. Bu durumda “Sınav notu 50 den düşük olanlar dersi geçemez” koşulu yerine “Sınav notu 50 ve üzeri olanlar dersi geçer” demek daha doğal bir yaklaşımdır. Kodumuzda bu doğallıkta olursa anlaşılması kolay olur.

Kötü

if(sinavNotu < 50) {
// dersi gecemez
}else {
// dersi gecer
}

İyi

if(sinavNotu >= 50) {
// dersi gecer
}else {
// dersi gecemez
}

Boolean Kullanımı

Boolean bir değeri koşul içerisinde tekrardan true veya false’a eşitlemek kod kalabalığı oluşturur. Bunun yerine aşağıdaki kullanım daha iyidir.

if(loggedIn == true) { }    // Kötüif(loggedIn) { }            // İyi

Üçlü (Ternary) Operatörü

Ternary operatörü if else işlemini kısa şekilde yapılmasına olanak verir. Satır sayısını azaltır. Koşul içinde birçok işlem yapılıyorsa kullanmak mantıklı değildir.

Örneğin aşağıdaki kod bloğunda ternary operatörü kullanmak iyi olabilir.

time_str += (hour >= 12) ? "pm" : "am";

Fonksiyonlar

Fonksiyonlar tek bir işi çok iyi şekilde yapmalıdır.

Eğer bir fonksiyon sadece isminde belirtilen adımları yapıyorsa, o fonksiyon bir şey yapıyordur.

Bir fonksiyonun birçok parametreye sahip olması sıkıntılı bir durum veya fonksiyonun birden fazla iş yaptığının habercisi olabilir. Mümkün olduğunca fonksiyon parametre sayısını 0–2 arasında tutmalıyız.

Sınıf Parametreler

Eğer fonksiyonlar 2–3 ten fazla parametreye ihtiyaç duyuyorsa, benzer parametreleri sınıf olarak gönderebiliriz.

Kötü

public void CreateMenu(string title, string body, string buttonText, bool cancellable)
{
// ...
}

İyi

public class MenuConfig
{
public string Title { get; set; }
public string Body { get; set; }
public string ButtonText { get; set; }
public bool Cancellable { get; set; }
}

var config = new MenuConfig
{
Title = "Foo",
Body = "Bar",
ButtonText = "Baz",
Cancellable = true
};

public void CreateMenu(MenuConfig config)
{
// ...
}

Yan Etkiler (Side Effects)

Aşağıdaki kod bloğunda fonksiyon global bir değişken kullanıyor. Zararsız görünüyor olabilir fakat fonksiyonu çalıştırdığımızda global değişkendeki veri de değişiyor. Global değişken kullanıldığında yanlış veri ile bizi yanıltır.

Kötü

var name = "Ryan McDermott";

public void SplitAndEnrichFullName()
{
var temp = name.Split(" ");
name = $"First name: {temp[0]}, Last name: {temp[1]}";
// side effect
}

SplitAndEnrichFullName();

Console.WriteLine(name); // First name: Ryan, Last name: McDermott

İyi

public string SplitAndEnrichFullName(string name)
{
var temp = name.Split(" ");
return $"First name: {temp[0]}, Last name: {temp[1]}";
}

var name = "Ryan McDermott";
var fullName = SplitAndEnrichFullName(name);

Console.WriteLine(name); // Ryan McDermott
Console.WriteLine(fullName); // First name: Ryan, Last name: McDermott

Olumsuz Koşullar

Fonksiyon isimlendirmelerinde olumsuz koşullardan kaçınmalıyız. Bu durum kod okunurluğunu azaltacaktır ve anlamamızı zorlaştıracaktır.

Kötü

public bool IsDOMNodeNotPresent(string node)
{
// ...
}

if (!IsDOMNodeNotPresent(node))
{
// ...
}

İyi

public bool IsDOMNodePresent(string node)
{
// ...
}

if (IsDOMNodePresent(node))
{
// ...
}

Böyle Temiz Kodlar Nasıl Yazılabilir?

“Ben kod yazıyorken fonksiyonlarımın ilk halleri uzun ve karışık olur. Bir sürü girintilemeler ve iç içe döngüleri olur. Uzun argüman listeleri olur. Verdiğim isimler keyfidir ve tekrarlanmış (duplicate) kodlar vardır. Ancak bu acemi satırların her birini kapsayan testlerim de vardır.” Robert C. Martin

İlk önce testleri yazmak, temiz kodlara ulaşmamızda kolaylık sağlar.

Döngüler

Do while döngüsü kullanmaktan kaçınmalıyız. Do-while döngüleri koşula göre çalışabilir veya çalışmayabilir. Bu kafa karıştırıcı bir durumdur. Neyse ki do-while döngüleri while döngüsü olarak yazılabilir.

Bjarne Stroustrup, C++ yaratıcısı bu konu hakkında şöyle der;

Deneyimime göre, do-ifadeleri hatalara ve kafa karışlığına yol açar. Yukarıda görebileceğim şartları tercih ederim. sonuç olarak, do-ifadeleri kullanmaktan kaçınırım.

Yorum Satırları Nasıl Olmalı

Kötü koda yorum yazmayın onu yeniden yazın.

Yorum satırları kodun ne yaptığını açıklamak için kullanırız. Fakat bu iyi bir şey değildir. Yazdığımız kod kendini açıklayabilmelidir.

public int HashIt(string data)
{
// The hash
var hash = 0;
// Length of string
var length = data.length;
// Loop through every character in data
for (var i = 0; i < length; i++)
{
// Get character code.
const char = data.charCodeAt(i);
// Make the hash
hash = ((hash << 5) - hash) + char;
// Convert to 32-bit integer
hash &= hash;
}
}

Yukarıdaki kod ne yaptığını açıklayabiliyor. Gereksiz yorumlar kullanılmış. Gereksiz yorumları kaldırabiliriz. Kodun yeni hali aşağıdaki şekilde olacaktır.

public int HashIt(string data)
{
var hash = 0;
var length = data.length;
for (var i = 0; i < length; i++)
{
const char = data.charCodeAt(i);
hash = ((hash << 5) - hash) + char;
// Convert to 32-bit integer
hash &= hash;
}
}

TODO Yorumları

To-do yapılması gerektiğini düşündüğümüz, ancak herhangi bir sebepten dolayı şu anda yapamadığımız işlerdir.

//TODO bunlara gerek yok
// Ödeme modelini yaptığımızda bunun ortadan kalkmasını bekliyoruz
protected VersionInfo makeVersion() throws Exception {
return null;
}

Todo lar yazılımcılar için çok kullanışlıdır. Neler yapılıp nelerin yapılmaması gerektiğini çok rahat takip edebiliriz.

Gereksiz Yorum Kullanımı

Kod kendisini ifade edebiliyorsa yorum satırına gerek yoktur. Yorum satırı koda “hayır sen böyle yapıyorsun” demek gibi bir şey olur ve kafa karıştırıcıdır.

int i = 5; // sets i 5

Ne İş Yaptığını Açıklamaya Çalışan Yorumlar

Kodda bilinmeyen değişkenler kullanıp bunları yorumlamak iyi bir şey değildir. Bilinmeyenlerden kurtulup kodun kendisini açıklamasını sağlamalıyız. İkinci kod kendini kolayca açıklayabiliyor.

if(isActive == 2) // be sure that is activeif(isActive == Status.Active)

Zombi Kod

Zombi kodlar kullanılmayan kodların geliştiriciler tarafından yorum satırına alınmasıdır. Zombi kodlar kafa karışıklığına sebep olur ve okunurluğu azaltır.

Kötü

/*
public void OldRequestModule(string url)
{
// ...
}
*/

public void NewRequestModule(string url)
{
// ...
}

var request = NewRequestModule(requestUrl);
InventoryTracker("apples", request, "www.inventory-awesome.io");

İyi

public void RequestModule(string url)
{
// ...
}

var request = RequestModule(requestUrl);
InventoryTracker("apples", request, "www.inventory-awesome.io");

Hata (Exception) Yönetimi

Program istediğimiz şekilde çalışmayabilir. Bu durumlarda nerde hata oluştuğunu bulup düzeltmek isteriz. Fakat iyi hazırlanmamış Hata (Exception) Yönetimi işimizi zorlaştırır. Bu problemin önüne geçmek için;

  • İstisna oluşabilecek yerlerde Try-Catch blokları kullanılmalıdır.
  • Oluşturulan Exception’lar hatanın yeri ve kaynağı hakkında bilgileri içermelidir.

Kötü

try
{
FunctionThatMightThrow();
}
catch (Exception ex)
{
// silent exception
}

İyi

try
{
FunctionThatMightThrow();
}
catch (Exception error)
{
NotifyUserOfError(error);
// Another option
ReportErrorToService(error);
}
  • Try-Catch bloğu çok büyümeye başladıysa fonksiyon olarak ele almak kod okunabilirliğini arttırır.
try {
//
//
//
//
} catch(Exception e) {
throw;
}

Yukarıda çok satırlı bir işlem olduğunu varsayalım. Aşağıdaki kullanımımda yapılan işlemleri fonksiyon şekline getirdik (tabi tek iş prensibini unutmuyoruz) ve bu sayede kolay anlaşılan bir try-catch yapısı oluşturduk.

try {
doSomething();
} catch(Exception e) {
throw;
}

Yazıda yer yer kendi yorumlarımı ekledim. Yanlış düşünüyorsun dediğiniz yerler varsa paylaşırsanız çok sevinirim böylece yeni şeyler öğrenmiş olurum. Zaman ayırdığınız için teşekkür ederim. İyi çalışmalar 🙂

--

--