Barış

WFN Üye
Konular
552
Mesajlar
552
Çözüm
0
Ticaret Puanı
0
Üyelik
24 Gün
Konum
Kastamonu
Web Sitesi
Var
Alanı
Google ADS
1/1
Konu sahibi
Java konusunda sık yapılan hataları önlemenin en etkili yolu, kodu yalnızca çalışıp çalışmadığına göre değil; hata durumlarını, kaynak kullanımını, veri türlerini ve bakım kolaylığını da kontrol ederek geliştirmektir. Bunun için özellikle değişkenleri doğru başlatmalı, equals() ile == farkını gözetmeli, null durumlarını açıkça yönetmeli, kaynakları try-with-resources ile kapatmalı, koleksiyonları değiştirirken yineleme kurallarına uymalı ve istisnaları gelişigüzel yakalamamalısınız. Küçük bir örnek üzerinden birim test yazmak ve derleyici uyarılarını düzeltmek, hataların üretim ortamına taşınmasını büyük ölçüde azaltır.

Hataları yalnızca derleme aşamasında aramayın​


Java derleyicisi sözdizimi ve tür uyumsuzluğu gibi sorunları yakalar; ancak yanlış sonuç üreten bir hesaplamayı, hatalı bir iş kuralını veya kapanmayan bir dosyayı her zaman tespit edemez. Bu nedenle bir programın derlenmesi, programın doğru olduğu anlamına gelmez.

Kodunuzu kontrol ederken üç ayrı soruyu birlikte sorun:
  • Kod derleniyor mu?
  • Beklenmeyen veya boş girdilerde nasıl davranıyor?
  • Uzun süre çalıştığında kaynakları serbest bırakıyor ve beklenen sonucu koruyor mu?
Örneğin bir kullanıcı yaşı için yalnızca pozitif sayı bekliyorsanız, 18 girdisinin doğru işlenmesi yeterli değildir. Boş metin, null, negatif sayı ve sayıya dönüştürülemeyen bir değer için de davranış belirlenmelidir. Hata önleme yaklaşımı, geçerli girdiyi işlemek kadar geçersiz girdiyi kontrollü biçimde reddetmeyi de kapsar.

Değişkenleri açıkça başlatın ve kapsamını dar tutun​


Bir değişkeni kullanılmadan önce anlamlı bir değerle başlatmak, hem derleme hatalarını hem de yanlış varsayımları azaltır. Özellikle sınıf alanlarını varsayılan değerlerine bırakmak, alanın henüz hazırlanmadığını gizleyebilir. int alanı otomatik olarak 0, boolean alanı false, nesne referansı ise null değerini alır; ancak bu değerlerin iş uygulaması açısından geçerli olup olmadığını siz belirlemelisiniz.

Aşağıdaki örnekte toplam değişkeni başlangıçta açıkça tanımlanır:

Java:
public int toplam(int[] sayilar) {
    int toplam = 0;

    for (int sayi : sayilar) {
        toplam += sayi;
    }

    return toplam;
}

Metoda boş bir dizi verilirse sonuç 0 olur. Bu, toplam işlemi için anlamlı olabilir; ancak null dizi gönderilmesi farklı bir durumdur. Metodun null kabul etmemesi gerekiyorsa bunu baştan belirleyip açık bir hata üretmek daha doğrudur:

Java:
import java.util.Objects;

public int toplam(int[] sayilar) {
    Objects.requireNonNull(sayilar, "sayilar null olamaz");

    int toplam = 0;
    for (int sayi : sayilar) {
        toplam += sayi;
    }
    return toplam;
}

Burada boş dizi ile null birbirinden ayrılır. Boş dizi geçerli bir girdi olarak 0 döndürür; null ise programcının sözleşmeye aykırı çağrı yaptığını gösteren açık bir hata oluşturur. Bu davranış, daha sonra oluşabilecek belirsiz bir NullPointerException yerine sorunun kaynağını daha erken gösterir.

Değişkenleri mümkün olan en dar kapsamda tanımlayın. Bir değeri yalnızca döngü içinde kullanıyorsanız sınıf alanı yapmayın. Geniş kapsamlı ve sık değişen alanlar, hangi metodun veriyi değiştirdiğini izlemeyi zorlaştırır.

== ile equals() kullanımını karıştırmayın​


Nesnelerde ==, iki referansın aynı nesneyi gösterip göstermediğini karşılaştırır. equals() ise sınıfın tanımladığı mantıksal eşitliği kontrol etmek için kullanılır. Metin karşılaştırırken == kullanmak, bazı durumlarda çalışıyor gibi görünen ancak başka girdilerde bozulan hatalara yol açar.

Java:
String beklenen = "admin";
String girilen = new String("admin");

System.out.println(beklenen == girilen);       // false
System.out.println(beklenen.equals(girilen));  // true

İki metnin içeriği aynı olduğu hâlde == sonucu false olabilir; çünkü iki ayrı String nesnesi oluşturulmuştur. Metin içeriğini karşılaştırmak için equals() kullanın. Değişkenlerden biri null olabilecekse karşılaştırmayı sabit metin üzerinden yapmak daha güvenlidir:

Java:
if ("admin".equals(kullaniciRolu)) {
    // Kullanıcı rolü admin ise
}

kullaniciRolu null olsa bile bu koşul hata üretmez ve false olur. Bu yaklaşım, yalnızca kullaniciRolu.equals("admin") yazmaktan daha güvenlidir.

null değerleri varsaymak yerine sözleşmeyi belirleyin​


null, bir referansın herhangi bir nesneyi göstermediğini belirtir. Hata çoğu zaman null değerinden değil, kodun bu değerin gelemeyeceğini varsaymasından kaynaklanır. Her metodun hangi parametreleri kabul ettiğini ve hangi durumlarda null döndürebileceğini belirlemek gerekir.

Bir arama metodunun sonuç bulamadığında null döndürmesi mümkünse çağıran kod bunu kontrol etmelidir:

Java:
Kullanici kullanici = kullaniciBul(id);

if (kullanici == null) {
    System.out.println("Kullanıcı bulunamadı.");
    return;
}

System.out.println(kullanici.getAd());

Buradaki null sonucu, veritabanı bağlantısının kesin olarak bozuk olduğunu kanıtlamaz; yalnızca bu çağrıda kullanıcı nesnesi bulunamadığını gösterir. Kayıt bulunamaması ile bağlantı hatası farklı durumlar olduğundan, veri erişim katmanı bu durumları mümkünse farklı istisnalarla veya açık bir sonuç türüyle ayırmalıdır.

Bir metodun geçerli sonuç vermediği durumlarda Optional kullanmak bazı API'lerde okunabilirliği artırabilir; ancak her alanı ve her parametreyi Optional yapmak zorunlu veya uygun değildir. Asıl amaç, null sözleşmesini gizlememektir.

Kaynakları her durumda kapatın​


Dosya, ağ bağlantısı veya veritabanı bağlantısı gibi kaynaklar bellekteki sıradan nesnelerden farklıdır. Bu kaynaklar işletim sistemi veya uzak servis üzerinde yer tuttuğu için yalnızca çöp toplayıcıya güvenmek doğru değildir.

Kaynak yönetiminde try-with-resources kullanın:

Java:
import java.io.BufferedReader;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;

public String ilkSatiriOku(Path dosya) throws IOException {
    try (BufferedReader okuyucu = Files.newBufferedReader(dosya)) {
        return okuyucu.readLine();
    }
}

Blok sona erdiğinde veya okuma sırasında istisna oluştuğunda okuyucu kapatılır. Bu, finally içinde elle kapatma işlemine göre daha az tekrar içerir ve kapatma sırasındaki hataların daha doğru yönetilmesini sağlar.

Bir bağlantının kapatılması, işlemin başarıyla tamamlandığını kanıtlamaz. Örneğin dosyanın ilk satırını okuyup döndürmek, dosyanın bütün içeriğinin işlendiği anlamına gelmez. Uygulamanızın hedefi tüm kayıtları okumaksa döngünün tamamını ve okunan kayıt sayısını ayrıca kontrol etmelisiniz.

İstisnaları sessizce yutmayın​


Aşağıdaki yaklaşım hatayı gizler:

Java:
try {
    dosyaOku();
} catch (IOException e) {
    // Hiçbir işlem yapılmıyor
}

Bu durumda kullanıcı işlem başarısız olduğu hâlde uygulama başarılıymış gibi devam edebilir. İstisnayı yakaladığınızda en azından şu kararlardan birini açıkça verin:
  • İstisnayı anlamlı bir mesajla üst katmana iletmek.
  • Güvenli bir varsayılan değer döndürmek.
  • İşlemi yeniden denemek; ancak sonsuz deneme döngüsü oluşturmamak.
  • İşlemi iptal edip kullanıcıya veya uygulama günlüğüne yeterli bilgi vermek.
Bir istisnayı yakalarken yalnızca gerçekten yönetebileceğiniz türü yakalayın. Exception veya Throwable gibi geniş türleri her yerde yakalamak, programlama hatalarını ve sistem düzeyi sorunları maskeleyebilir. Hata mesajına işlem adı, ilgili kimlik ve güvenli bağlam bilgisi ekleyin; parola, erişim anahtarı ve kişisel verileri günlüğe yazmayın.

Koleksiyonları yineleme sırasında değiştirmeyin​


Bir koleksiyon üzerinde for-each ile dolaşırken aynı koleksiyondan eleman silmek, çoğu durumda ConcurrentModificationException oluşturabilir veya kodun davranışını belirsiz hâle getirebilir. Silme ihtiyacı varsa yineleyicinin kendi remove() metodunu kullanın ya da koşula göre removeIf tercih edin.

Java:
import java.util.ArrayList;
import java.util.List;

List<String> roller = new ArrayList<>(List.of("admin", "misafir", "editor"));
roller.removeIf(rol -> rol.equals("misafir"));

System.out.println(roller); // [admin, editor]

Bu örnekte işlem sonucunda yalnızca misafir rolü çıkarılır. Koleksiyonu başka bir iş parçacığı aynı anda değiştiriyorsa konu yalnızca removeIf kullanımıyla çözülmez; eşzamanlı erişim için uygun veri yapısı veya kilitleme stratejisi ayrıca seçilmelidir. Tek iş parçacıklı bir döngü hatası ile çok iş parçacıklı erişim sorunu birbirine karıştırılmamalıdır.

Değişebilir nesneleri anahtar olarak kullanırken dikkatli olun​


HashMap ve HashSet gibi yapılar, hashCode() ve equals() sonuçlarına göre çalışır. Bir nesne haritaya anahtar olarak eklendikten sonra, eşitlik veya karma değerini etkileyen alanını değiştirirseniz nesne haritada bulunamayabilir.

Bu nedenle anahtar olarak kullanılan sınıflarda:
  • equals() ve hashCode() kurallarını birlikte ve tutarlı uygulayın.
  • Anahtar alanlarını mümkünse final yapın.
  • Anahtarın haritaya eklenmesinden sonra eşitliği etkileyen alanları değiştirmeyin.
Bir sınıf equals() metodunu geçersiz kılıyor ancak hashCode() metodunu geçersiz kılmıyorsa, eşit kabul edilen iki nesne farklı karma değerlerine sahip olabilir. Bu durumda HashMap veya HashSet beklenmedik sonuç verir. Bir nesne eşitlik açısından aynı kabul ediliyorsa hashCode() sonucu da aynı olmalıdır.

Tamsayı bölmesi ve tür dönüşümlerini kontrol edin​


Java'da iki tamsayının bölümü tamsayıdır. Bu nedenle 5 / 2 sonucu 2 olur; 2.5 bekliyorsanız işleme en az bir kayan noktalı değer katmalısınız.

Java:
int tamamlanan = 5;
int toplam = 2;

double oran = (double) tamamlanan / toplam;
System.out.println(oran); // 2.5

Burada dönüşüm bölmeden önce yapılır. double oran = tamamlanan / toplam; yazılırsa bölme önce tamsayı olarak gerçekleşir ve sonuç 2.0 olur. Değişkenin türünü double yapmak tek başına yeterli değildir; dönüşümün işlemin doğru noktasında yapılması gerekir.

Ayrıca büyük sayılarda taşma ihtimalini değerlendirin. Bir int değeri sınırını aşarsa sonuç hata vermeden beklenmeyen bir değere dönebilir. Para, kimlik numarası veya yüksek hacimli sayaçlarda alanın gerektirdiği türü seçin; para hesaplarında kayan noktalı türlerin yuvarlama davranışını ayrıca değerlendirin.

Paylaşılan değişebilir durumu azaltın​


Birden fazla metodun aynı sınıf alanını değiştirmesi, hatanın hangi işlemden kaynaklandığını bulmayı zorlaştırır. Mümkün olduğunda nesneleri oluşturulduktan sonra değiştirilemeyecek şekilde tasarlayın, alanları private tutun ve doğrudan değişiklik yerine kontrollü metodlar kullanın.

Bir sınıfa dışarıdan doğrudan değiştirilebilir liste vermek de benzer bir sorundur:

Java:
import java.util.ArrayList;
import java.util.List;

public final class Siparis {
    private final List<String> urunler;

    public Siparis(List<String> urunler) {
        this.urunler = List.copyOf(urunler);
    }

    public List<String> getUrunler() {
        return urunler;
    }
}

List.copyOf, sınıfın içindeki listenin dışarıdan değiştirilmesini önlemeye yardımcı olur. Ancak listedeki elemanların kendisi değişebilir nesnelerse yalnızca listenin kopyalanması elemanları otomatik olarak değişmez yapmaz. Bu nedenle sınıfın bütün veri modelini, yalnızca dış kabını değil, değişebilirlik açısından değerlendirin.

İş parçacığı hatalarını sıradan değişken hatası gibi ele almayın​


Birden fazla iş parçacığı aynı değişkeni okuyup yazıyorsa, işlemler beklediğiniz sırada gerçekleşmeyebilir. sayac++ gibi görünen tek bir ifade bile okuma, artırma ve yazma adımlarından oluşur; bu adımlar eşzamanlı erişimde birbirinin üzerine yazılabilir.

Sorunu çözmek için önce paylaşımın gerçekten gerekli olup olmadığını belirleyin. Paylaşılan durum gerekmiyorsa her iş parçacığına kendi verisini verin. Gerekiyorsa erişim için uygun bir eşzamanlılık aracı seçin; yalnızca volatile kullanmak, bileşik işlemleri otomatik olarak atomik hâle getirmez.

Bir eşzamanlılık hatasını düzeltirken yalnızca hatanın görünmemesine bakmayın. Aynı işlemi çok sayıda iş parçacığı altında çalıştırıp beklenen toplam, kayıt sayısı ve işlem sırasını kontrol edin. Hata ara sıra oluşuyorsa bu, sorunun ortadan kalktığını değil, yeniden üretilmesinin zor olduğunu gösterebilir.

Hataları önlemek için kısa bir kontrol listesi kullanın​


Kod incelemesinden veya göndermeden önce aşağıdaki sorulara cevap verin:
KontrolSorulacak soruSorun varsa ilk adım
Veri türüBölme, taşma veya dönüşüm sonucu beklenen türde mi?İşlemi ve dönüşümün yapıldığı satırı kontrol edin.
Nesne karşılaştırmasıİçerik mi, aynı nesne mi karşılaştırılıyor?Metin ve değer karşılaştırmalarında equals() kullanımını inceleyin.
Null yönetimiParametre veya dönüş değeri null olabilir mi?Sözleşmeyi belirleyip erken kontrol veya uygun sonuç türü ekleyin.
Kaynak yönetimiDosya, bağlantı veya akış her durumda kapanıyor mu?Kaynağı try-with-resources içine alın.
İstisna yönetimiHata kayboluyor mu, yanlış katmanda mı yönetiliyor?İstisnayı anlamlı biçimde iletin veya kontrollü olarak yönetin.
Koleksiyon işlemiYineleme sürerken koleksiyon değişiyor mu?removeIf, yineleyici veya uygun eşzamanlı yapı kullanın.
Paylaşılan durumBirden fazla iş parçacığı aynı veriyi değiştiriyor mu?Paylaşımı kaldırın veya erişimi uygun biçimde senkronize edin.
Bu tablo bir hata bulma aracıdır; tek başına testin yerini tutmaz. Her önemli davranış için beklenen sonucu belirleyen test yazın. Testte yalnızca başarılı senaryoyu değil, boş girdi, sınır değer, bulunamayan kayıt ve beklenmeyen hata durumunu da ayrı ayrı kontrol edin.

Bir hatayı düzeltirken sonucu nasıl doğrulayabilirsiniz?​


Önce hatayı mümkün olan en küçük örnekte yeniden üretin. Hatanın oluşması için gereken girdileri, beklenen sonucu ve gerçek sonucu kaydedin. Ardından tek bir değişiklik yapın ve aynı girdilerle tekrar çalıştırın. Sonuç düzelmişse bu, yalnızca o örneğin düzeldiğini gösterir; farklı sınır değerleri de test etmeden genel çözüm kabul edilmemelidir.

Örneğin 5 / 2 sorunu için yalnızca 5 ve 2 değerlerini değil, 0, negatif sayılar, eşit değerler ve büyük değerleri de test edin. Sıfıra bölme gibi bir durumda sonuç matematiksel olarak tanımsızdır; bunu yakalayıp kullanıcıya anlamlı bir hata vermek, rastgele bir değer döndürmekten daha doğrudur.

Üretimde görülen bir hata için günlüğe yalnızca istisna türünü değil, işlemi ayırt etmeye yarayan güvenli bilgileri de ekleyin. Ardından aynı hatayı geliştirme ortamında yeniden üretmek için kullanılan girdi, çağrı sırası ve ilgili kod sürümü belirlenmelidir. Bu bilgiler yoksa düzeltmenin gerçekten aynı soruna yönelik olup olmadığını doğrulamak zorlaşır.

Sık sorulan sorular​

Her null değeri için istisna fırlatmak gerekir mi?​

Hayır. null uygulamanın geçerli bir sonucuysa bunu açıkça temsil edip çağıran kodun işlemesini sağlayabilirsiniz. Ancak null kabul edilmeyen bir parametre geldiyse erken ve anlamlı bir hata üretmek, daha sonra belirsiz bir noktada oluşacak hatadan daha yararlıdır.

== hiç mi kullanılmaz?​

Kullanılır; ancak nesnelerin içeriğini karşılaştırmak için değil, aynı nesne referansını veya özel durumlarda aynı referansı kontrol etmek için kullanılır. İlkel türlerde değer karşılaştırması yapar. String, Integer ve kendi sınıflarınız gibi nesnelerde hangi karşılaştırmanın gerektiğini açıkça belirleyin.

Bir istisnayı yakalayıp yeniden fırlatmak sorunu çözmüş olur mu?​

Tek başına olmaz. Yeniden fırlatma, hatayı uygun katmana iletmek için kullanılabilir; ancak işlem bağlamı, kullanıcıya verilecek yanıt ve günlük kaydı da düşünülmelidir. İstisnayı yakalayıp aynı anlamı taşımayan yeni bir hata mesajıyla sarmalamak, asıl nedeni gizlememelidir.

Birim test yazmak bütün Java hatalarını önler mi?​

Hayır. Birim testler, yazılmış senaryolardaki beklenen davranışı doğrular; test edilmeyen eşzamanlılık, kaynak tüketimi, yapılandırma veya entegrasyon sorunlarını garanti etmez. Bu nedenle birim testleri kod incelemesi, sınır değer kontrolleri ve gerektiğinde entegrasyon testleriyle birlikte kullanın.
 

Sende şimdi bize katılmak ister misin?

Kayıt ol

Bize katılım kolay ve ücretsizdir!

Foruma git ?

Bu konuyu görüntüleyen kullanıcılar

Bu konuyu şu anda görüntüleyen üye bulunmuyor.
Robotlar:
  • Anthropic