Kontrol Yapıları Best Practices
Giriş — Çalışan Kod Yetmez
Bir kodu yazabilirsin ve çalışır. Ama 6 ay sonra aynı koda baktığında "ben ne yazmışım?" diye sormak istemezsin. Kontrol yapılarında (if-else, switch, döngüler) iyi alışkanlıklar edinmek, kodunu okunabilir, bakımı kolay ve hatasız tutar.
Bu ders, önceki dört derste öğrendiğin yapıları profesyonel düzeyde kullanmanı sağlayacak teknikler ve ilkeler içeriyor.
Analoji: Kontrol yapıları bir binanın koridorları gibi. Çalışıyor mu? Evet, insanlar bir odadan diğerine gidebiliyor. Ama koridorlar dar, tabelalar eksik ve bazı kapılar yanlış yere açılıyorsa, bina yaşanılmaz olur. Best practices, koridorlarını geniş ve tabelalarını okunaklı tutmaktır.
Guard Clause (Koruyucu Koşul)
Guard clause, metot veya bloğun başında geçersiz durumları hemen eleyip çıkma tekniğidir. Bu sayede "mutlu yol" (happy path) kodu girintisiz kalır.
Kötü: Derin İç İçe if
public void siparisOnayla(Siparis siparis) {
if (siparis != null) {
if (siparis.getUrunler() != null && !siparis.getUrunler().isEmpty()) {
if (siparis.getMüsteri() != null) {
if (siparis.getMüsteri().isAktif()) {
// Asıl iş burada — 4 seviye girinti!
double toplam = hesapla(siparis);
System.out.println("Sipariş onaylandı: " + toplam);
} else {
System.out.println("Müşteri aktif değil.");
}
} else {
System.out.println("Müşteri bilgisi eksik.");
}
} else {
System.out.println("Sipariş boş.");
}
} else {
System.out.println("Sipariş null.");
}
}Bu kodu okumak çok zor. Her if'in else'inin hangi koşula ait olduğunu takip etmek baş belası. Buna arrow anti-pattern denir — girinti sağa doğru ok gibi uzar.
İyi: Guard Clause ile
public void siparisOnayla(Siparis siparis) {
if (siparis == null) {
System.out.println("Sipariş null.");
return;
}
if (siparis.getUrunler() == null || siparis.getUrunler().isEmpty()) {
System.out.println("Sipariş boş.");
return;
}
if (siparis.getMüsteri() == null) {
System.out.println("Müşteri bilgisi eksik.");
return;
}
if (!siparis.getMüsteri().isAktif()) {
System.out.println("Müşteri aktif değil.");
return;
}
// Mutlu yol — girinti 0!
double toplam = hesapla(siparis);
System.out.println("Sipariş onaylandı: " + toplam);
}Her guard clause geçersiz durumu kontrol edip return ile metottan çıkar. Tüm kontrolleri geçen kod, ana iş mantığı — sıfır girinti ile okunaklı.
💡 İpucu: Guard clause'un altın kuralı: Olumsuz durumları başta ele, olumlu durumu sonda yap. Metottaki girintiyi 1-2 seviyede tut.
Guard Clause Döngülerde
Döngülerde guard clause için continue kullanırsın:
for (Ogrenci ogrenci : ogrenciler) {
if (ogrenci == null) continue;
if (!ogrenci.isAktif()) continue;
if (ogrenci.getOrtalama() < 2.0) continue;
// Sadece aktif ve ortalaması 2+ olan öğrencileri işle
onurListesineEkle(ogrenci);
}Early Return (Erken Dönüş)
Early return, guard clause'un bir genellemesidir. Sonucu belirleyebildiğin anda return et, gereksiz kodu çalıştırma.
Kötü: Tek Return Noktası
public String notHesapla(int puan) {
String sonuc;
if (puan >= 90) {
sonuc = "AA";
} else if (puan >= 80) {
sonuc = "BA";
} else if (puan >= 70) {
sonuc = "BB";
} else if (puan >= 60) {
sonuc = "CB";
} else if (puan >= 50) {
sonuc = "CC";
} else {
sonuc = "FF";
}
return sonuc;
}İyi: Early Return
public String notHesapla(int puan) {
if (puan >= 90) return "AA";
if (puan >= 80) return "BA";
if (puan >= 70) return "BB";
if (puan >= 60) return "CB";
if (puan >= 50) return "CC";
return "FF";
}Çok daha kısa ve okunabilir. Her satır bağımsız bir kural, else bile gerek yok çünkü return metottan çıkar.
Ne Zaman Early Return?
Metot bir değer döndürüyorsa ve koşula göre farklı değerler varsa
Hata kontrollerinde (guard clause)
Basit karar metotlarında
Ne Zaman Dikkatli Ol?
Early return'ün aşırı kullanımı kodu takip etmeyi zorlaştırabilir. Özellikle uzun metotlarda 5-6 farklı return noktası varsa, kodun hangi noktadan döneceğini anlamak zorlaşır. Metodu kısa tut (20-30 satır), o zaman early return sorun olmaz.
switch vs if-else — Karar Rehberi
Bu iki yapı arasında doğru seçim yapmak okunabilirliği artırır.
if-else Kullan Eğer:
Aralık kontrolü yapıyorsan (
>,<,>=,<=)Karmaşık boolean ifadeler varsa (
&&,||)Farklı değişkenleri karşılaştırıyorsan
2-3 basit koşul varsa
// if-else uygun — aralık kontrolü
if (sicaklik > 35) {
System.out.println("Çok sıcak");
} else if (sicaklik > 20) {
System.out.println("Güzel hava");
} else {
System.out.println("Serin");
}
// if-else uygun — farklı değişkenler
if (yas >= 18 && ehliyetVar && sigortaAktif) {
System.out.println("Araç kiralayabilir");
}switch Kullan Eğer:
Tek değişkenin sabit değerlerini karşılaştırıyorsan
Enum veya String eşleştirmesi yapıyorsan
4+ farklı sabit değer varsa
Tip kontrolü (pattern matching) yapıyorsan
// switch uygun — enum eşleştirme
String mesaj = switch (durum) {
case BEKLEMEDE -> "Siparişiniz alındı";
case HAZIRLANIYOR -> "Siparişiniz hazırlanıyor";
case YOLDA -> "Kargoya verildi";
case TESLIM -> "Teslim edildi";
};
// switch uygun — sabit değerler
String gunAdi = switch (gun) {
case 1 -> "Pazartesi";
case 2 -> "Salı";
// ...
default -> "Geçersiz";
};Karar Tablosu
Koşul tipi ne?
├── Sabit değer eşleştirme → switch
├── Aralık kontrolü → if-else
├── Boolean kombinasyon → if-else
├── Tip kontrolü → switch (Java 21+)
└── Enum değerleri → switch
Kaç koşul var?
├── 1-3 → if-else (daha basit)
├── 4+ sabit değer → switch
└── Değer döndürmek istiyorum → switch expressionKoşulları Basitleştirme
1. Karmaşık Koşulları Değişkene Ata
// KÖTÜ — koşul çok uzun, ne yaptığı belli değil
if (user.getAge() >= 18 && user.hasVerifiedEmail()
&& !user.isBanned() && user.getSubscription().isActive()) {
grantAccess(user);
}
// İYİ — koşulun ne anlama geldiği açık
boolean erisimIzniVar = user.getAge() >= 18
&& user.hasVerifiedEmail()
&& !user.isBanned()
&& user.getSubscription().isActive();
if (erisimIzniVar) {
grantAccess(user);
}Koşulu açıklayıcı bir değişkene atamak, belgeleme görevi görür. Kodu okuyan kişi erisimIzniVar ismini görünce ne kontrol edildiğini anlar.
2. Negatif Koşullardan Kaçın
// KÖTÜ — çift negatif, beyin bükülür
if (!kullanici.isNotActive()) { ... }
// İYİ
if (kullanici.isActive()) { ... }// KÖTÜ — negatif koşul + else
if (!girisBasarili) {
hataGoster();
} else {
anaSayfayaGit();
}
// İYİ — pozitif koşul önce
if (girisBasarili) {
anaSayfayaGit();
} else {
hataGoster();
}3. De Morgan Kurallarını Uygula
// Bu ikisi aynı anlama gelir:
!(a && b) == !a || !b
!(a || b) == !a && !b
// KÖTÜ — kafa karıştırıcı
if (!(yas >= 18 && ehliyetVar)) { ... }
// İYİ — De Morgan uygulandı
if (yas < 18 || !ehliyetVar) { ... }Döngülerde Okunabilirlik
1. Anlamlı Sayaç İsimleri
// KÖTÜ — i, j, k ne anlama geliyor?
for (int i = 0; i < ogrenciler.length; i++) {
for (int j = 0; j < ogrenciler[i].dersler.length; j++) {
...
}
}
// İYİ — basit döngülerde i/j kabul edilir
// Ama iç içe döngülerde anlamlı isim ver
for (int ogrenciIdx = 0; ogrenciIdx < ogrenciler.length; ogrenciIdx++) {
for (int dersIdx = 0; dersIdx < ogrenciler[ogrenciIdx].dersler.length; dersIdx++) {
...
}
}
// EN İYİ — for-each ile
for (Ogrenci ogrenci : ogrenciler) {
for (Ders ders : ogrenci.getDersler()) {
...
}
}i, j basit tek seviye döngülerde sorun değil. Ama iç içe döngülerde anlamlı isimler çok önemli.
2. Döngü Gövdesini Kısa Tut
// KÖTÜ — döngü gövdesi çok uzun
for (Siparis siparis : siparisler) {
// 30 satır işlem kodu...
// Ne yaptığını anlamak için tamamını okuman lazım
}
// İYİ — karmaşık mantığı metoda ayır
for (Siparis siparis : siparisler) {
siparisIsle(siparis);
}
private void siparisIsle(Siparis siparis) {
// 30 satır buraya — ama artık adı var
}3. Sihirli Sayılardan Kaçın
// KÖTÜ — 7 ne anlama geliyor?
for (int i = 0; i < 7; i++) { ... }
// İYİ — sabit kullan
static final int GUN_SAYISI = 7;
for (int i = 0; i < GUN_SAYISI; i++) { ... }
// Veya dizi kullanıyorsan
for (int i = 0; i < gunler.length; i++) { ... }İç İçe if'leri Azaltma Teknikleri
Derin iç içe yapılar (3+ seviye) kodu okunmaz hale getirir. Bunu azaltmanın yolları:
Teknik 1: Guard Clause (Yukarıda Gördük)
Olumsuz durumları başta ele, return/continue ile çık.
Teknik 2: Koşulları Birleştir
// KÖTÜ — gereksiz iç içe
if (x > 0) {
if (y > 0) {
if (z > 0) {
System.out.println("Hepsi pozitif");
}
}
}
// İYİ — && ile birleştir
if (x > 0 && y > 0 && z > 0) {
System.out.println("Hepsi pozitif");
}Teknik 3: Metoda Ayır
// KÖTÜ
if (kullanici.getYas() >= 18) {
if (kullanici.getRol() == Rol.ADMIN || kullanici.getRol() == Rol.EDITOR) {
if (kullanici.getIzinler().contains("yazma")) {
icerikYayinla();
}
}
}
// İYİ — mantığı metoda taşı
if (yayinIzniVarMi(kullanici)) {
icerikYayinla();
}
private boolean yayinIzniVarMi(Kullanici k) {
if (k.getYas() < 18) return false;
if (k.getRol() != Rol.ADMIN && k.getRol() != Rol.EDITOR) return false;
return k.getIzinler().contains("yazma");
}Teknik 4: Polimorfizm (İleri Seviye)
Çok fazla if-else veya switch varsa ve sürekli yeni case ekleniyor ise, strateji veya polimorfizm kullanmayı düşün. Bu OOP konusu ilerideki derslerde işlenecek.
Savunmacı Programlama (Defensive Programming)
1. Null Kontrolü Her Zaman Yap
// Dış kaynaktan gelen her şeye güvenme
public void isleVeri(String veri) {
if (veri == null || veri.isBlank()) {
throw new IllegalArgumentException("Veri boş olamaz");
}
// Güvenli alan — veri null veya boş değil
System.out.println(veri.trim().toUpperCase());
}2. switch'te default Yaz
// Enum'a yeni değer eklenince fark edersin
switch (durum) {
case AKTIF -> ...
case PASIF -> ...
default -> throw new IllegalStateException(
"Beklenmeyen durum: " + durum
);
}3. Dizi Sınırlarını Kontrol Et
if (index >= 0 && index < dizi.length) {
return dizi[index];
} else {
throw new IndexOutOfBoundsException("Geçersiz index: " + index);
}Anti-Pattern'ler — Bunları Yapma
1. Yoda Conditions (Gereksiz)
// "Yoda konuşması" — sabit sola
if (5 == x) { ... } // "5 eşittir x" — garip okuma
// Normal — değişken sola
if (x == 5) { ... } // "x eşittir 5" — doğal okumaC/C++'ta = ile == karışmasını önlemek için Yoda conditions kullanılırdı. Java'da bu sorun yok (if (x = 5) derleme hatası verir). Java'da Yoda kullanma, okunabilirliği azaltır.
İstisna: null kontrolünde "sabit".equals(degisken) hâlâ mantıklıdır.
2. Boolean Karşılaştırma
// GEREKSIZ — boolean zaten true/false
if (aktif == true) { ... }
if (gecerli == false) { ... }
// TEMİZ
if (aktif) { ... }
if (!gecerli) { ... }3. Boş else
// GEREKSIZ
if (x > 0) {
System.out.println("Pozitif");
} else {
// hiçbir şey yapma
}
// TEMİZ — else'i kaldır
if (x > 0) {
System.out.println("Pozitif");
}4. if-else ile boolean Atama
// GEREKSIZ
boolean yetiskin;
if (yas >= 18) {
yetiskin = true;
} else {
yetiskin = false;
}
// TEMİZ — doğrudan ata
boolean yetiskin = yas >= 18;5. Döngü İçinde Değişmeyen Koşul
// KÖTÜ — ayniDosyaMi her turda aynı sonucu verir
for (String satir : satirlar) {
if (ayniDosyaMi(dosya1, dosya2)) {
// ...
}
// ...
}
// İYİ — döngü dışına çıkar
boolean dosyalarAyni = ayniDosyaMi(dosya1, dosya2);
for (String satir : satirlar) {
if (dosyalarAyni) {
// ...
}
// ...
}Kontrol Yapılarında Tutarlılık
Süslü Parantez Stili
Takımın bir stil seçmeli ve tutarlı uygulamalı:
// Allman stili — açık parantez yeni satırda
if (kosul)
{
...
}
// K&R stili — açık parantez aynı satırda (Java standardı)
if (kosul) {
...
}Java dünyasında K&R stili standarttır. IntelliJ, Eclipse ve Google Java Style Guide bunu kullanır. Başka bir stile geçme.
Tek Satırlık Bloklar
// Stili seç ve her yerde uygula:
// Seçenek A: Her zaman süslü parantez (en güvenli)
if (x > 0) {
return x;
}
// Seçenek B: Tek satırda süslü parantezsiz (riskli ama kısa)
if (x > 0) return x;Google Java Style Guide ve çoğu şirket "her zaman süslü parantez" kuralını uygular. Tavsiyem de bu.
Gerçek Dünya Örneği: Sipariş Durumu İşleme
Tüm öğrendiklerimizi birleştirelim:
public String siparisIsle(Siparis siparis) {
// Guard clauses
if (siparis == null) {
return "Hata: Sipariş bulunamadı";
}
if (siparis.getUrunler().isEmpty()) {
return "Hata: Boş sipariş";
}
// switch expression ile durum işleme
return switch (siparis.getDurum()) {
case YENI -> {
double tutar = 0;
for (Urun urun : siparis.getUrunler()) {
if (urun.getStok() <= 0) continue; // Stoksuz ürünü atla
tutar += urun.getFiyat() * urun.getAdet();
}
yield String.format("Sipariş oluşturuldu. Tutar: %.2f TL", tutar);
}
case ODENDI -> "Sipariş onaylandı, hazırlanıyor";
case KARGODA -> "Kargo takip no: " + siparis.getKargoNo();
case TESLIM -> "Teslim tarihi: " + siparis.getTeslimTarihi();
case IPTAL -> "İptal nedeni: " + siparis.getIptalNedeni();
};
}Bu kodda:
✅ Guard clause ile null/boş kontrol
✅ switch expression ile durum işleme
✅ continue ile stoksuz ürünü atlama
✅ Early return (guard clause'larda)
✅ Okunabilir, düz yapı
Cyclomatic Complexity — Karmaşıklığı Ölç
Bir metodun cyclomatic complexity'si, içindeki bağımsız yol sayısını ölçer. Her if, else if, for, while, case, &&, || karmaşıklığı 1 artırır.
// Complexity: 1 (hiç dal yok)
public int topla(int a, int b) {
return a + b;
}
// Complexity: 4 (if + else if + else if + for)
public String notHesapla(int[] puanlar) {
int toplam = 0;
for (int p : puanlar) {
toplam += p;
}
double ort = (double) toplam / puanlar.length;
if (ort >= 80) return "Başarılı";
else if (ort >= 50) return "Geçer";
else return "Kaldı";
}Kural: Metot başına complexity'yi 10'un altında tut. 10'u aşarsa metodu parçala.
| Complexity | Değerlendirme |
|---|---|
| 1-5 | Basit, iyi |
| 6-10 | Orta, kabul edilebilir |
| 11-20 | Karmaşık, refactor düşün |
| 21+ | Çok karmaşık, kesinlikle parçala |
IntelliJ IDEA bu metriği otomatik hesaplar: Analyze → Calculate Metrics.
Erken Optimizasyon Tuzağı
"Premature optimization is the root of all evil." — Donald Knuth
Kontrol yapılarını optimize etmek güzel ama okunabilirlikten ödün verme:
// "Optimize" ama okunmaz
int r = (a > b) ? ((a > c) ? a : c) : ((b > c) ? b : c);
// Okunabilir — JVM zaten optimize eder
int max = a;
if (b > max) max = b;
if (c > max) max = c;Modern JVM'ler (HotSpot, GraalVM) kodu çalışma zamanında agresif şekilde optimize eder. Sen okunabilirliğe odaklan, performansı JVM'e bırak. Gerçek bir performans sorunu ölçümle ortaya çıkarsa o zaman optimize et.
Kod İnceleme (Code Review) Kontrol Listesi
Kontrol yapılarını review ederken şu soruları sor:
Guard clause kullanılabilir mi? İç içe if 2+ seviye ise evet.
else kaldırılabilir mi? If bloğu return/continue/break ile bitiyorsa else gereksiz.
switch daha uygun mu? Aynı değişken 3+ kez eşit mi diye kontrol ediliyorsa evet.
Koşul anlaşılır mı? Uzunsa açıklayıcı değişkene ata.
Döngü gövdesi kısa mı? 10 satırı aşıyorsa metoda ayırmayı düşün.
break/continue açık mı? Ne zaman ve neden çalıştığı belli mi?
default/else var mı? Beklenmeyen durumlar yakalanıyor mu?
Sihirli sayı var mı? Sabite çevir.
Kontrol Yapısı Seçim Cheatsheet
Karar vermem gerekiyor:
├── Tek koşul → if
├── İki yoldan biri → if-else
├── Basit değer atama → ternary (?:)
├── 3+ ardışık koşul → else if zinciri
├── Sabit değer eşleştirme → switch
├── Tip kontrolü → switch pattern matching (Java 21+)
└── Enum durumları → switch expression
Tekrar etmem gerekiyor:
├── Sayısı belli → for
├── Dizi/koleksiyon gezinme → for-each
├── Koşula bağlı → while
├── En az 1 kez → do-while
└── Erken çıkış → break
└── Tur atlama → continueÖzet
Guard clause ile olumsuz durumları başta ele, girintiyi azalt.
return(metot) veyacontinue(döngü) kullan.Early return ile sonucu belirleyebildiğin anda dön, gereksiz else zincirlerinden kaçın.
switch vs if-else: Sabit değerlerde switch, aralıklarda if-else. 4+ case varsa switch daha okunaklı.
Koşulları basitleştir: Uzun koşulları açıklayıcı boolean değişkenlere ata, negatif koşullardan kaçın.
Döngü gövdesini kısa tut — karmaşık mantığı metotlara ayır, anlamlı değişken isimleri kullan.
Anti-pattern'lerden kaçın: Boolean karşılaştırma (
== true), boş else, if-else ile boolean atama gibi gereksiz yapılar kullanma.Cyclomatic complexity'yi 10 altında tut — 10'u aşan metotları parçala. IDE araçlarıyla ölç.
Okunabilirliği her zaman performansın önünde tut — JVM kodu senin yerine optimize eder. Sen temiz, anlaşılır kod yaz.
AI Asistan
Sorularını yanıtlamaya hazır