Temiz Kod Nedir?
Robert C. Martin'in 2008 yılında yayınladığı "Clean Code" kitabı, yazılım mühendisliğinde bir dönüm noktası olmuştur. Temiz kod, yalnızca çalışan kod değil; okunabilir, anlaşılabilir, bakımı kolay ve genişletilebilir koddur. Bir kodun temiz olup olmadığını anlamanın en basit yolu, başka bir geliştiricinin bu kodu okuduğunda ne kadar çabuk anlayabildiğidir.
Martin Fowler'ın meşhur sözüyle: "Herhangi bir aptal bilgisayarın anlayacağı kodu yazabilir. İyi programcılar insanların anlayacağı kodu yazar." Temiz kod yazma, bir beceri olduğu kadar bir disiplindir ve sürekli pratik gerektirir.
Temiz Kodun Özellikleri
- Okunabilir: Kod, bir hikaye gibi okunabilmelidir
- Basit: Gereksiz karmaşıklıktan arındırılmış olmalıdır
- Test Edilebilir: Birim testlerle doğrulanabilir yapıda olmalıdır
- Tekrarsız: Aynı mantık birden fazla yerde tekrarlanmamalıdır
- Minimal: İhtiyaç duyulmayan özellikler içermemelidir
- Tutarlı: Aynı problemler aynı şekilde çözülmelidir
İsimlendirme Kuralları
İsimlendirme, temiz kodun en temel ve en etkili bileşenidir. İyi bir isim, kodun ne yaptığını açıklar ve yorum ihtiyacını ortadan kaldırır.
Anlamlı ve Niyeti Açıklayan İsimler
// Kötü isimlendirme
int d; // geçen gün sayısı
List<int[]> list1;
string tp; // ???
// İyi isimlendirme
int gecenGunSayisi;
List<int[]> isaretlenenHucreler;
string telefonNumarasi;
İsimlendirme Kuralları
- Niyet belirten isimler kullanın: Değişkenin ne olduğunu, fonksiyonun ne yaptığını, sınıfın neyi temsil ettiğini açıkça belirtin
- Kısaltmalardan kaçının:
kullaniciAdresiyazın,klncAdryazmayın - Aranabilir isimler kullanın: Tek harfli değişkenler yalnızca çok kısa scope'larda kullanılmalıdır
- Sınıflar isim, metotlar fiil olmalıdır:
KullaniciServisi,siparisiOlustur() - Boolean değişkenlerde soru formatı kullanın:
isActive,hasPermission,canEdit - Tutarlı olun: Aynı kavram için her yerde aynı terimi kullanın (get/fetch/retrieve arasında bir tanesini seçip onunla devam edin)
İsimlendirme Kalıpları
| Eleman | Kural | Örnek |
|---|---|---|
| Sınıf | PascalCase, isim | KullaniciServisi, SiparisYonetici |
| Metot | camelCase/PascalCase, fiil | kullaniciGetir(), SiparisiOnayla() |
| Değişken | camelCase, isim | toplamTutar, aktifKullaniciSayisi |
| Sabit | UPPER_SNAKE_CASE | MAX_DENEME_SAYISI, API_URL |
| Interface | PascalCase, I prefix (C#) | IKullaniciRepository |
| Boolean | is/has/can prefix | isVisible, hasAccess |
Fonksiyon Tasarımı
Fonksiyonlar, yazılımın yapı taşlarıdır. Temiz fonksiyonlar yazmak, temiz kod yazmanın en önemli adımıdır.
Küçük Fonksiyonlar Yazın
İdeal bir fonksiyon 20-30 satırı geçmemelidir. Bir fonksiyon uzadığında, içindeki mantık parçaları ayrı fonksiyonlara çıkarılmalıdır. Küçük fonksiyonlar daha kolay okunur, test edilir ve bakımı yapılır.
Tek Sorumluluk (Single Responsibility)
Her fonksiyon yalnızca bir iş yapmalıdır ve o işi iyi yapmalıdır. Bir fonksiyonun ne yaptığını tek bir cümleyle "ve" kelimesi kullanmadan açıklayamıyorsanız, muhtemelen birden fazla iş yapıyordur:
// Kötü: Birden fazla sorumluluk
public void KullaniciKaydetVeEmailGonder(Kullanici kullanici) {
// Veritabanına kaydet
_dbContext.Kullanicilar.Add(kullanici);
_dbContext.SaveChanges();
// Email gönder
var mesaj = new EmailMesaji(kullanici.Email, "Hoş Geldiniz!");
_emailServisi.Gonder(mesaj);
// Log kaydet
_logger.LogInfo($"Yeni kullanıcı: {kullanici.Email}");
}
// İyi: Her fonksiyon tek bir iş yapıyor
public void KullaniciKaydet(Kullanici kullanici) {
_repository.Ekle(kullanici);
}
public void HosgeldinEmailiGonder(Kullanici kullanici) {
var mesaj = new EmailMesaji(kullanici.Email, "Hoş Geldiniz!");
_emailServisi.Gonder(mesaj);
}
public void KullaniciKayitLogla(Kullanici kullanici) {
_logger.LogInfo($"Yeni kullanıcı: {kullanici.Email}");
}
Fonksiyon Parametre Sayısı
İdeal parametre sayısı sıfır veya birdir. İki parametre kabul edilebilir, üç parametre dikkatle değerlendirilmelidir, dörtten fazla parametre ise neredeyse her zaman bir tasarım sorununa işaret eder:
// Kötü: Çok fazla parametre
public void SiparisOlustur(string urunAdi, decimal fiyat,
int miktar, string adres, string sehir,
string postaKodu, string odemeYontemi) { }
// İyi: Nesne parametresi kullanın
public void SiparisOlustur(SiparisOlusturmaDto siparis) { }
public class SiparisOlusturmaDto {
public string UrunAdi { get; set; }
public decimal Fiyat { get; set; }
public int Miktar { get; set; }
public Adres TeslimatAdresi { get; set; }
public string OdemeYontemi { get; set; }
}
Yan Etkilerden Kaçının
Fonksiyonlar, adlarından beklenmeyecek gizli yan etkiler içermemelidir. Bir fonksiyon "kullaniciDogrula" olarak adlandırılmışsa, yalnızca doğrulama yapmalı; oturum başlatma veya sayacı artırma gibi ek işlemler yapmamalıdır.
SOLID Prensipleri
SOLID, nesne yönelimli programlamada temiz ve sürdürülebilir kod yazmak için beş temel prensibi ifade eder. Robert C. Martin tarafından tanımlanan bu prensipler, yazılım tasarımının temel taşlarıdır.
S - Single Responsibility Principle (Tek Sorumluluk)
Bir sınıf yalnızca bir nedenden dolayı değişmelidir. Her sınıfın tek bir sorumluluğu olmalıdır:
// Kötü: Birden fazla sorumluluk
public class Rapor {
public string IcerikOlustur() { /* ... */ }
public void PdfOlarakKaydet() { /* ... */ }
public void EmailIleGonder() { /* ... */ }
public void VeritabaninaKaydet() { /* ... */ }
}
// İyi: Her sınıfın tek sorumluluğu var
public class RaporIcerikOlusturucu { /* ... */ }
public class PdfDonusturucu { /* ... */ }
public class EmailGonderici { /* ... */ }
public class RaporRepository { /* ... */ }
O - Open/Closed Principle (Açık/Kapalı)
Yazılım varlıkları genişlemeye açık, değişikliğe kapalı olmalıdır. Yeni davranış eklemek için mevcut kodu değiştirmek yerine genişletmek gerekir:
// Kötü: Yeni ödeme yöntemi eklemek için mevcut kodu değiştirmek gerekiyor
public decimal IndirimHesapla(string musteriTipi, decimal tutar) {
if (musteriTipi == "Standart") return tutar * 0.05m;
if (musteriTipi == "Premium") return tutar * 0.10m;
if (musteriTipi == "VIP") return tutar * 0.20m;
return 0;
}
// İyi: Yeni müşteri tipi eklemek için yeni sınıf oluşturmak yeterli
public interface IIndirimStratejisi {
decimal IndirimHesapla(decimal tutar);
}
public class StandartIndirim : IIndirimStratejisi {
public decimal IndirimHesapla(decimal tutar) => tutar * 0.05m;
}
public class PremiumIndirim : IIndirimStratejisi {
public decimal IndirimHesapla(decimal tutar) => tutar * 0.10m;
}
L - Liskov Substitution Principle (Liskov Yerine Koyma)
Alt sınıflar, üst sınıfların yerine kullanılabilmelidir. Alt sınıf, üst sınıfın davranış sözleşmesini bozmamalıdır. Klasik örnek: Kare sınıfı, Dikdörtgen sınıfından türetilmemelidir, çünkü karenin genişlik ve yükseklik davranışı dikdörtgenden farklıdır.
I - Interface Segregation Principle (Arayüz Ayrımı)
İstemciler, kullanmadıkları arayüzlere bağımlı olmaya zorlanmamalıdır. Büyük, şişman arayüzler yerine küçük ve odaklı arayüzler tercih edilmelidir:
// Kötü: Şişman arayüz
public interface ICihaz {
void Yazdir();
void Tara();
void FaksGonder();
void Fotokopi();
}
// İyi: Ayrılmış arayüzler
public interface IYazici { void Yazdir(); }
public interface ITarayici { void Tara(); }
public interface IFaks { void FaksGonder(); }
// Basit yazıcı sadece yazdırma yapabilir
public class BasitYazici : IYazici {
public void Yazdir() { /* ... */ }
}
// Çok fonksiyonlu cihaz hepsini yapabilir
public class CokFonksiyonluCihaz : IYazici, ITarayici, IFaks {
public void Yazdir() { /* ... */ }
public void Tara() { /* ... */ }
public void FaksGonder() { /* ... */ }
}
D - Dependency Inversion Principle (Bağımlılık Tersine Çevirme)
Yüksek seviye modüller, düşük seviye modüllere bağımlı olmamalıdır. Her ikisi de soyutlamalara bağımlı olmalıdır:
// Kötü: Somut sınıfa doğrudan bağımlılık
public class SiparisServisi {
private SqlSiparisRepository _repository = new SqlSiparisRepository();
public void SiparisOlustur(Siparis siparis) {
_repository.Kaydet(siparis);
}
}
// İyi: Soyutlamaya bağımlılık (Dependency Injection ile)
public class SiparisServisi {
private readonly ISiparisRepository _repository;
public SiparisServisi(ISiparisRepository repository) {
_repository = repository;
}
public void SiparisOlustur(Siparis siparis) {
_repository.Kaydet(siparis);
}
}
DRY, KISS ve YAGNI
DRY - Don't Repeat Yourself
Her bilgi parçası, sistem içinde tek ve kesin bir temsile sahip olmalıdır. Kod tekrarı, bakım maliyetini artırır ve hata riskini yükseltir. Bir değişiklik yapılması gerektiğinde, tekrarlanan kodun tüm kopyalarını bulmak ve güncellemek zor ve hata eğilimlidir.
Ancak DRY'ı aşırıya kaçırmamak da önemlidir. Yalnızca yüzeysel olarak benzer ama farklı nedenlerle değişen kodları birleştirmek, gereksiz soyutlama ve karmaşıklığa yol açar (Wrong Abstraction sorunu).
KISS - Keep It Simple, Stupid
Çözüm, mümkün olan en basit şekilde uygulanmalıdır. Gereksiz karmaşıklık, kodun okunabilirliğini, test edilebilirliğini ve bakımını zorlaştırır. Basit çözümler genellikle daha güvenilir ve performanslıdır:
// Karmaşık ve gereksiz
public bool IsEven(int number) {
return number % 2 == 0 ? true : false;
}
// Basit ve temiz
public bool IsEven(int number) {
return number % 2 == 0;
}
YAGNI - You Aren't Gonna Need It
Şu anda ihtiyaç duyulmayan bir özelliği eklemeyin. "İleride lazım olabilir" düşüncesiyle yazılan kod, çoğu zaman hiç kullanılmaz ama bakım maliyetini artırır. İhtiyaç ortaya çıktığında eklemek, önceden tahmin ederek eklemekten her zaman daha verimlidir.
Kod Yorumları
İyi kod, kendini açıklar. Yorumlar, kodun "ne" yaptığını değil, "neden" yaptığını açıklamalıdır:
İyi Yorum Kullanımları
- Yasal bildirimler (copyright)
- Karmaşık algoritmaların niyetini açıklayan yorumlar
- Performans tercihlerinin nedenini açıklayan yorumlar
- Uyarılar ve sonuçlar hakkında bilgilendirme
- TODO ve FIXME notları (geçici olarak)
Kötü Yorum Örnekleri
// Kötü: Kodu tekrarlayan gereksiz yorum
// Kullanıcı adını döndürür
public string GetKullaniciAdi() { return _kullaniciAdi; }
// Kötü: Güncelliğini yitirmiş yanıltıcı yorum
// Maksimum 10 ürün döndürür (artık 50 döndürüyor!)
public List<Urun> GetUrunler() { /* ... */ }
// İyi: Neden bu yaklaşımın tercih edildiğini açıklayan yorum
// Thread-safe olması için ConcurrentDictionary kullanıyoruz.
// Normal Dictionary, paralel erişimde veri bozulmasına neden oluyordu. (Bug #1234)
private readonly ConcurrentDictionary<string, Kullanici> _onbellek = new();
Hata Yönetimi
Hata yönetimi, temiz kodun ayrılmaz bir parçasıdır. İyi hata yönetimi, uygulamanın güvenilirliğini ve hata ayıklama kolaylığını doğrudan etkiler:
Exception Kullanım Prensipleri
- Hata kodları yerine exception kullanın: Exception'lar, hata akışını ana iş mantığından ayırır
- Checked exception'lardan kaçının: Java'daki checked exception'lar, gereksiz boilerplate koda yol açar
- Exception'larda bağlam bilgisi verin: Hangi işlem sırasında, hangi verilerle hata oluştuğunu belirtin
- Null döndürmeyin: Null yerine Optional/Maybe pattern veya boş koleksiyon döndürün
- Null geçirmeyin: Parametre olarak null göndermek, NullReferenceException riskini artırır
// Kötü: Generic exception ve yetersiz mesaj
try {
SiparisOlustur(siparis);
} catch (Exception ex) {
Console.WriteLine("Hata oluştu");
}
// İyi: Spesifik exception, anlamlı mesaj ve loglama
try {
SiparisOlustur(siparis);
} catch (StokYetersizException ex) {
_logger.LogWarning(ex, "Sipariş oluşturulamadı. Ürün: {UrunId}, İstenen: {Miktar}",
siparis.UrunId, siparis.Miktar);
throw new IsKuraliException("Stok yetersiz olduğu için sipariş oluşturulamadı.", ex);
} catch (OdemeHatasiException ex) {
_logger.LogError(ex, "Ödeme işlemi başarısız. Sipariş: {SiparisNo}", siparis.SiparisNo);
throw;
}
Test Yazımı
Temiz kod, test edilebilir koddur. Testler, kodun doğruluğunu garanti eder ve refactoring yapılırken güvenlik ağı oluşturur:
Test Piramidi
- Birim Testler (Unit Tests): En fazla sayıda olmalı. Hızlı, bağımsız ve tekrarlanabilir
- Entegrasyon Testleri: Bileşenler arası etkileşimi doğrular
- E2E (End-to-End) Testleri: En az sayıda olmalı. Tüm sistemi doğrular
İyi Test Yazımı Prensipleri
// AAA (Arrange-Act-Assert) pattern
[Fact]
public void IndirimHesapla_PremiumMusteri_YuzdeOnIndirimUygular()
{
// Arrange (Hazırlık)
var indirimServisi = new IndirimServisi();
var musteri = new Musteri { Tip = MusteriTipi.Premium };
var tutar = 1000m;
// Act (Eylem)
var sonuc = indirimServisi.IndirimHesapla(musteri, tutar);
// Assert (Doğrulama)
Assert.Equal(100m, sonuc);
}
FIRST Prensipleri
- Fast: Testler hızlı çalışmalıdır
- Independent: Testler birbirinden bağımsız olmalıdır
- Repeatable: Her ortamda aynı sonucu vermelidir
- Self-Validating: Geçti/kaldı şeklinde net sonuç vermelidir
- Timely: Testler, kodla birlikte yazılmalıdır
Refactoring Teknikleri
Refactoring, kodun dış davranışını değiştirmeden iç yapısını iyileştirme sürecidir. Martin Fowler'ın "Refactoring" kitabı, bu konudaki temel kaynaktır:
Yaygın Refactoring Teknikleri
- Extract Method: Uzun fonksiyonları anlamlı alt fonksiyonlara bölme
- Rename: Değişken, fonksiyon veya sınıf isimlerini daha açıklayıcı hale getirme
- Extract Class: Çok fazla sorumluluk taşıyan sınıfları bölme
- Move Method: Bir metodu daha uygun sınıfa taşıma
- Replace Conditional with Polymorphism: Karmaşık if/switch yapılarını polimorfizm ile değiştirme
- Introduce Parameter Object: İlişkili parametreleri bir nesne içinde gruplama
- Remove Dead Code: Kullanılmayan kodu silme
- Replace Magic Number with Constant: Sihirli sayıları anlamlı sabitlerle değiştirme
Refactoring Ne Zaman Yapılmalı?
Boy Scout kuralını uygulayın: "Kamp alanını bulduğunuzdan daha temiz bırakın." Bir dosyaya her dokunduğunuzda küçük iyileştirmeler yapın. Büyük refactoring'ler için ayrı zaman dilimi ayırın ve her adımda testlerin geçtiğinden emin olun.
Kod İnceleme (Code Review) En İyi Uygulamaları
Kod inceleme, kod kalitesini artırmanın ve bilgi paylaşımının en etkili yollarından biridir:
İnceleyici İçin
- Yapıcı ve nazik bir dil kullanın
- Neden bir değişiklik önerdiğinizi açıklayın
- Önemsiz konularda (formatting, stil) takılmayın; bunları otomatik araçlara bırakın
- Olumlu geri bildirim de verin; iyi yazılmış kodu takdir edin
- Küçük PR'ları tercih edin; büyük PR'lar etkili incelemeyi zorlaştırır
Geliştirici İçin
- PR'ları küçük ve odaklı tutun (ideal: 200-400 satır)
- PR açıklamasında değişikliğin neden yapıldığını belirtin
- Self-review yapın (PR'ı göndermeden önce kendiniz inceleyin)
- Geri bildirimleri kişisel almayın; amaç kodun iyileşmesidir
Araçlar: Linter ve Formatter
Otomatik araçlar, stil tutarlılığını sağlar ve yaygın hataları erken yakalar:
- ESLint: JavaScript/TypeScript için linter. Hata tespiti ve stil kuralları
- Prettier: Otomatik kod formatlama (JavaScript, TypeScript, CSS, HTML)
- ReSharper / Rider: C# ve .NET için güçlü analiz ve refactoring araçları
- SonarQube: Kod kalitesi analizi, güvenlik açıkları ve teknik borç takibi
- StyleCop: C# kodlama stil kurallarının uygulanması
- EditorConfig: Editör bağımsız stil kuralları tanımlama
Bu araçları CI/CD pipeline'ına entegre ederek her commit'te otomatik kontrol yapılmasını sağlayın. Lint hatası olan kodun merge edilmesini engelleyin.
Kod Kokuları (Code Smells)
Kod kokuları, daha derin tasarım sorunlarına işaret eden yüzeysel belirtilerdir. Yaygın kod kokuları:
- Long Method: 20-30 satırdan uzun fonksiyonlar
- Large Class: Çok fazla sorumluluk taşıyan sınıflar
- Duplicate Code: Birden fazla yerde tekrarlanan mantık
- Feature Envy: Başka bir sınıfın verilerine aşırı bağımlı metotlar
- Data Clumps: Her zaman birlikte kullanılan veri grupları
- Primitive Obsession: Anlamlı nesneler yerine primitif tiplerin aşırı kullanımı
- Switch Statements: Tekrarlayan switch/if blokları (polimorfizm ile çözülür)
- Dead Code: Hiçbir zaman çalıştırılmayan kod
- Comments: Kötü yazılmış kodu açıklamak için kullanılan aşırı yorumlar
Temiz kod yazmak bir varış noktası değil, sürekli bir yolculuktur. Her gün biraz daha iyi kod yazmaya çalışmak, zamanla büyük farklar yaratır. Mükemmel değil, sürekli iyileşen kod hedefleyin.
Sonuç
Clean Code prensipleri, yazılım geliştirmenin kalitesini ve sürdürülebilirliğini doğrudan etkileyen temel kavramlardır. İyi isimlendirme, küçük ve odaklı fonksiyonlar, SOLID prensipleri, DRY/KISS/YAGNI kuralları, etkili hata yönetimi, kapsamlı test yazımı ve düzenli refactoring, profesyonel bir geliştiricinin araç kutusunun vazgeçilmez parçalarıdır. Bu prensipleri bir gecede mükemmelleştirmek mümkün değildir; ancak her gün bilinçli olarak uygulamak, zamanla doğal bir alışkanlık haline dönüşür. Unutmayın: kod, yazdığınızdan çok daha fazla okunur. Bu nedenle okuyucu için yazın, sadece derleyici için değil.