Bu ders için video bulunmamaktadır.

Bu derse başlamak veya ilerlemenizi kaydetmek için lütfen giriş yapın veya kayıt olun.

Ders İçeriği

Giriş: Hata Yönetiminin Önemi

Hata yönetimi ve debugging (hata ayıklama), yazılım geliştirmenin en önemli aşamalarından biridir. Mükemmel bir kod yazmak imkansıza yakındır ve her programcı, kodunda hatalarla karşılaşır. Önemli olan, bu hataları nasıl tespit edeceğinizi, nasıl ele alacağınızı ve nasıl çözeceğinizi bilmektir.

JavaScript'te hata yönetimi, uygulamanızın beklenmedik durumlarla karşılaştığında bile çalışmaya devam etmesini sağlar. İyi bir hata yönetimi stratejisi, kullanıcı deneyimini iyileştirir, uygulamanızın güvenilirliğini artırır ve hata ayıklama sürecini kolaylaştırır.

Bu derste, JavaScript'te hata türlerini, hata yakalama ve işleme mekanizmalarını, debugging tekniklerini ve en iyi uygulamaları öğreneceğiz. Ayrıca, modern tarayıcılarda ve Node.js ortamında kullanabileceğiniz debugging araçlarını da inceleyeceğiz.

JavaScript'te Hata Türleri

JavaScript'te karşılaşabileceğiniz çeşitli hata türleri vardır. Bu hataları anlamak, onları daha etkili bir şekilde tespit etmenize ve çözmenize yardımcı olur.

SyntaxError (Sözdizimi Hatası)

JavaScript kodunuzun sözdizimi kurallarına uymadığında oluşur. Bu hatalar genellikle kod çalıştırılmadan önce, derleme aşamasında tespit edilir.

Örnek:

if (x === 5 {
  console.log("x is 5");
}

Hata: Parantez eksik if (x === 5 {

ReferenceError (Referans Hatası)

Tanımlanmamış bir değişkene veya fonksiyona erişmeye çalıştığınızda oluşur.

Örnek:

console.log(undefinedVariable);

Hata: undefinedVariable is not defined

TypeError (Tip Hatası)

Bir değer, beklenen tipte olmadığında veya bir değişken üzerinde geçersiz bir işlem yapmaya çalıştığınızda oluşur.

Örnek:

const num = 123;
num.toUpperCase();

Hata: num.toUpperCase is not a function

RangeError (Aralık Hatası)

Bir değer, izin verilen aralığın dışında olduğunda oluşur.

Örnek:

const arr = new Array(-1);

Hata: Invalid array length

URIError (URI Hatası)

encodeURI()decodeURI() gibi URI işleme fonksiyonlarına geçersiz parametreler verildiğinde oluşur.

Örnek:

decodeURIComponent('%');

Hata: URI malformed

EvalError (Eval Hatası)

eval() fonksiyonu ile ilgili hatalarda oluşur. Modern JavaScript'te nadiren görülür.

InternalError (İç Hata)

JavaScript motorunda meydana gelen iç hatalarda oluşur. Örneğin, çok fazla özyineleme (recursion) kullanıldığında "too much recursion" hatası alabilirsiniz.

AggregateError (Toplu Hata)

Birden fazla hatayı tek bir hata olarak gruplandırmak için kullanılır. ES2020 ile eklenmiştir.

Örnek:

Promise.any([
  Promise.reject('Hata 1'),
  Promise.reject('Hata 2')
]);

Hata: AggregateError: All promises were rejected

Özel Hatalar Oluşturma

JavaScript'te kendi özel hata sınıflarınızı da oluşturabilirsiniz. Bu, uygulamanıza özgü hata durumlarını daha iyi yönetmenize yardımcı olur.

ozel_hatalar.js
// Temel Error sınıfından miras alan özel hata sınıfı
class ValidationError extends Error {
    constructor(message) {
        super(message);
        this.name = "ValidationError";
        this.code = "E_VALIDATION";
    }
}

// Başka bir özel hata sınıfı
class DatabaseError extends Error {
    constructor(message, query) {
        super(message);
        this.name = "DatabaseError";
        this.code = "E_DATABASE";
        this.query = query;
    }
}

// Özel hataları kullanma
function validateUser(user) {
    if (!user) {
        throw new ValidationError("Kullanıcı nesnesi gereklidir.");
    }
    
    if (!user.username) {
        throw new ValidationError("Kullanıcı adı gereklidir.");
    }
    
    if (user.username.length < 3) {
        throw new ValidationError("Kullanıcı adı en az 3 karakter olmalıdır.");
    }
    
    return true;
}

function queryDatabase(query) {
    if (!query) {
        throw new DatabaseError("Sorgu boş olamaz.", query);
    }
    
    // Veritabanı sorgusu simülasyonu
    if (query.includes("DROP")) {
        throw new DatabaseError("Tehlikeli sorgu tespit edildi.", query);
    }
    
    return { success: true, data: "Sorgu sonuçları" };
}

// Hataları yakalama ve işleme
try {
    // Geçersiz kullanıcı
    validateUser({ username: "ab" });
} catch (error) {
    if (error instanceof ValidationError) {
        console.error(`Doğrulama hatası: ${error.message}`);
    } else {
        console.error(`Beklenmeyen hata: ${error.message}`);
    }
}

try {
    // Tehlikeli sorgu
    queryDatabase("DROP TABLE users");
} catch (error) {
    if (error instanceof DatabaseError) {
        console.error(`Veritabanı hatası: ${error.message}`);
        console.error(`Sorunlu sorgu: ${error.query}`);
    } else {
        console.error(`Beklenmeyen hata: ${error.message}`);
    }
}

İpucu: Özel hata sınıfları oluştururken, hata mesajlarınızı açık ve bilgilendirici yapın. Ayrıca, hata kodları eklemek, hataları programatik olarak işlemeyi kolaylaştırır.

Try-Catch-Finally ile Hata Yakalama

JavaScript'te, try-catch-finally bloğu, kodunuzda oluşabilecek hataları yakalamak ve işlemek için kullanılır. Bu yapı, uygulamanızın beklenmedik durumlarla karşılaştığında bile çalışmaya devam etmesini sağlar.

Temel Sözdizimi

try_catch_temel.js
try {
    // Hata oluşturabilecek kod
    const result = riskyOperation();
    console.log("İşlem başarılı:", result);
} catch (error) {
    // Hata durumunda çalışacak kod
    console.error("Bir hata oluştu:", error.message);
} finally {
    // Hata olsun veya olmasın her zaman çalışacak kod
    console.log("İşlem tamamlandı.");
}

Pratik Bir Örnek

try_catch_ornek.js
function getUserData(userId) {
    try {
        // Kullanıcı verisini almaya çalış
        const userData = fetchUserFromDatabase(userId);
        
        // Veri işleme
        const processedData = processUserData(userData);
        
        return processedData;
    } catch (error) {
        // Hata türüne göre işleme
        if (error.name === "DatabaseError") {
            console.error("Veritabanı hatası:", error.message);
            // Yedek veri kaynağını dene
            return fetchUserFromBackupSource(userId);
        } else if (error.name === "ProcessingError") {
            console.error("İşleme hatası:", error.message);
            // Temel veriyi döndür
            return { userId, name: "Bilinmeyen Kullanıcı", isBackup: true };
        } else {
            // Beklenmeyen hatalar için
            console.error("Beklenmeyen hata:", error);
            throw error; // Hatayı yeniden fırlat
        }
    } finally {
        // Temizlik işlemleri
        closeConnections();
        logAccessAttempt(userId);
    }
}

// Simüle edilmiş fonksiyonlar
function fetchUserFromDatabase(userId) {
    if (userId < 0) {
        const error = new Error("Geçersiz kullanıcı ID'si");
        error.name = "DatabaseError";
        throw error;
    }
    
    if (userId === 0) {
        return null; // Kullanıcı bulunamadı
    }
    
    return { userId, name: "John Doe", email: "john@example.com" };
}

function processUserData(userData) {
    if (!userData) {
        const error = new Error("Kullanıcı verisi bulunamadı");
        error.name = "ProcessingError";
        throw error;
    }
    
    return {
        ...userData,
        displayName: userData.name.toUpperCase(),
        lastAccess: new Date().toISOString()
    };
}

function fetchUserFromBackupSource(userId) {
    return { userId, name: "John Doe (Yedek)", email: "john@backup.com", isBackup: true };
}

function closeConnections() {
    console.log("Bağlantılar kapatıldı.");
}

function logAccessAttempt(userId) {
    console.log(`Kullanıcı ID ${userId} için erişim girişimi kaydedildi.`);
}

// Fonksiyonu test etme
try {
    console.log(getUserData(1)); // Normal durum
    console.log(getUserData(0)); // Kullanıcı bulunamadı
    console.log(getUserData(-1)); // Veritabanı hatası
} catch (error) {
    console.error("Ana programda hata yakalandı:", error.message);
}

Hata Nesnesi Özellikleri

JavaScript'teki Error nesnesi, hata hakkında bilgi sağlayan çeşitli özelliklere sahiptir:

  • name: Hata türünün adı (örn. "Error", "SyntaxError", "TypeError")
  • message: Hata mesajı
  • stack: Hatanın oluştuğu noktaya kadar olan çağrı yığını (stack trace)
  • cause: (ES2022+) Hatanın nedeni olan başka bir hata
hata_ozellikleri.js
try {
    // Bir hata oluştur
    throw new Error("Bu bir test hatasıdır");
} catch (error) {
    console.log("Hata Türü:", error.name);
    console.log("Hata Mesajı:", error.message);
    console.log("Çağrı Yığını:", error.stack);
}

// ES2022+ özelliği: Error Cause
try {
    try {
        // İç hata
        throw new Error("Veritabanı bağlantısı başarısız");
    } catch (dbError) {
        // Dış hata (iç hatayı neden olarak belirt)
        throw new Error("Kullanıcı verisi alınamadı", { cause: dbError });
    }
} catch (error) {
    console.log("Ana Hata:", error.message);
    console.log("Neden Olan Hata:", error.cause?.message);
}

Not: error.cause özelliği ES2022 ile eklenmiştir ve eski tarayıcılarda desteklenmeyebilir. Uyumluluğu kontrol etmek için Can I Use sitesini ziyaret edebilirsiniz.

Asenkron Kodda Hata Yakalama

Asenkron JavaScript kodunda hata yakalama, senkron koddan biraz farklıdır. Promise'lar ve async/await ile çalışırken, hataları nasıl ele alacağınızı bilmek önemlidir.

Promise'larda Hata Yakalama

promise_hata_yakalama.js
// Promise ile hata yakalama
function fetchData(url) {
    return fetch(url)
        .then(response => {
            if (!response.ok) {
                throw new Error(`HTTP error! Status: ${response.status}`);
            }
            return response.json();
        })
        .then(data => {
            console.log("Veri başarıyla alındı:", data);
            return data;
        })
        .catch(error => {
            console.error("Veri alınırken hata oluştu:", error.message);
            // Hata durumunda varsayılan veri döndür
            return { error: true, message: error.message };
        })
        .finally(() => {
            console.log("Fetch işlemi tamamlandı.");
        });
}

// Promise zincirinde hata yayılımı
function processUserData(userId) {
    return fetchUserById(userId)
        .then(user => {
            if (!user) {
                throw new Error("Kullanıcı bulunamadı");
            }
            return fetchUserPosts(user.id);
        })
        .then(posts => {
            return { success: true, posts };
        })
        .catch(error => {
            console.error("İşlem hatası:", error.message);
            return { success: false, error: error.message };
        });
}

// Simüle edilmiş fonksiyonlar
function fetchUserById(userId) {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            if (userId === 0) {
                resolve(null);
            } else if (userId < 0) {
                reject(new Error("Geçersiz kullanıcı ID'si"));
            } else {
                resolve({ id: userId, name: "John Doe" });
            }
        }, 500);
    });
}

function fetchUserPosts(userId) {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            if (userId === 1) {
                resolve([
                    { id: 1, title: "İlk Gönderi" },
                    { id: 2, title: "İkinci Gönderi" }
                ]);
            } else {
                reject(new Error("Gönderiler alınamadı"));
            }
        }, 500);
    });
}

Async/Await ile Hata Yakalama

async_await_hata_yakalama.js
// Async/await ile hata yakalama
async function fetchData(url) {
    try {
        const response = await fetch(url);
        
        if (!response.ok) {
            throw new Error(`HTTP error! Status: ${response.status}`);
        }
        
        const data = await response.json();
        console.log("Veri başarıyla alındı:", data);
        return data;
    } catch (error) {
        console.error("Veri alınırken hata oluştu:", error.message);
        // Hata durumunda varsayılan veri döndür
        return { error: true, message: error.message };
    } finally {
        console.log("Fetch işlemi tamamlandı.");
    }
}

// Async/await ile daha karmaşık hata yönetimi
async function processUserData(userId) {
    try {
        const user = await fetchUserById(userId);
        
        if (!user) {
            throw new Error("Kullanıcı bulunamadı");
        }
        
        const posts = await fetchUserPosts(user.id);
        return { success: true, user, posts };
    } catch (error) {
        console.error("İşlem hatası:", error.message);
        
        // Hata türüne göre farklı işlemler
        if (error.message.includes("Geçersiz kullanıcı")) {
            return { success: false, error: "Kullanıcı bilgisi geçersiz", code: "INVALID_USER" };
        } else if (error.message.includes("bulunamadı")) {
            return { success: false, error: "Kullanıcı mevcut değil", code: "USER_NOT_FOUND" };
        } else {
            return { success: false, error: error.message, code: "UNKNOWN_ERROR" };
        }
    }
}

// Birden fazla asenkron işlemi paralel yürütme ve hata yakalama
async function fetchMultipleResources() {
    try {
        // Promise.all tüm promise'ların tamamlanmasını bekler
        // Herhangi biri başarısız olursa, hata fırlatır
        const [users, posts, comments] = await Promise.all([
            fetchUsers(),
            fetchPosts(),
            fetchComments()
        ]);
        
        return { users, posts, comments };
    } catch (error) {
        console.error("Kaynaklar alınırken hata oluştu:", error.message);
        return { error: true, message: "Veriler alınamadı" };
    }
}

// Promise.allSettled ile tüm sonuçları alma (başarılı veya başarısız)
async function fetchAllResourcesWithStatus() {
    const results = await Promise.allSettled([
        fetchUsers(),
        fetchPosts(),
        fetchComments()
    ]);
    
    // Her bir sonucun durumunu kontrol et
    return results.map((result, index) => {
        const resourceType = ["users", "posts", "comments"][index];
        
        if (result.status === "fulfilled") {
            return { type: resourceType, status: "success", data: result.value };
        } else {
            return { type: resourceType, status: "error", error: result.reason.message };
        }
    });
}

Uyarı: Asenkron fonksiyonlarda try-catch bloğu, yalnızca await ifadelerinden kaynaklanan hataları yakalar. Asenkron fonksiyonun kendisi çağrıldığında oluşabilecek hataları yakalamak için, fonksiyonu çağıran yerde de hata yönetimi yapmanız gerekir.

Debugging Teknikleri ve Araçları

Debugging (hata ayıklama), kodunuzdaki hataları tespit etme ve düzeltme sürecidir. JavaScript'te debugging için kullanabileceğiniz çeşitli teknikler ve araçlar vardır.

Temel Debugging Teknikleri

 Console Metodları

JavaScript'in console nesnesi, debugging için çeşitli metodlar sunar:

  • console.log(): Genel amaçlı loglama
  • console.error(): Hata mesajları için
  • console.warn(): Uyarı mesajları için
  • console.info(): Bilgi mesajları için
  • console.debug(): Debug mesajları için
  • console.table(): Tablosal veriyi görüntülemek için
  • console.dir(): Nesne özelliklerini incelemek için
  • console.trace(): Çağrı yığınını görüntülemek için
  • console.time() ve console.timeEnd(): Kod bloklarının çalışma süresini ölçmek için
  • console.group() ve console.groupEnd(): Log mesajlarını gruplamak için

 Debugger İfadesi

debugger ifadesi, kodunuzda bir kesme noktası (breakpoint) oluşturur. Tarayıcı geliştirici araçları veya Node.js debugger açıkken, kod bu noktada duraklar ve adım adım çalıştırma imkanı sunar.

Örnek:

function calculateTotal(items) {
    let total = 0;
    for (let i = 0; i < items.length; i++) {
        debugger; // Kod burada duraklar
        total += items[i].price * items[i].quantity;
    }
    return total;
}

 Koşullu Breakpoint'ler

Tarayıcı geliştirici araçlarında, belirli bir koşul sağlandığında tetiklenen breakpoint'ler oluşturabilirsiniz. Bu, özellikle döngülerde veya belirli durumlarda hata ayıklamak için kullanışlıdır.

Örnek: Bir döngüde yalnızca i === 5 olduğunda duraklamak için, ilgili satıra sağ tıklayıp "Add conditional breakpoint" seçeneğini kullanabilir ve i === 5 koşulunu girebilirsiniz.

 Call Stack ve Scope İnceleme

Tarayıcı geliştirici araçlarında, bir breakpoint'te durduğunuzda, çağrı yığınını (call stack) ve mevcut kapsamdaki (scope) değişkenleri inceleyebilirsiniz. Bu, kodun nasıl çalıştığını ve değişkenlerin değerlerini anlamanıza yardımcı olur.

Tarayıcı Geliştirici Araçları

Modern tarayıcılar, JavaScript kodunu debug etmek için güçlü araçlar sunar. Bu araçlara genellikle F12 tuşuna basarak veya sağ tıklayıp "İncele" (Inspect) seçeneğini seçerek erişebilirsiniz.

 Chrome DevTools

Google Chrome'un geliştirici araçları, JavaScript debugging için kapsamlı özellikler sunar:

  • Sources Panel: Kodunuzu görüntülemek, breakpoint'ler eklemek ve adım adım çalıştırmak için
  • Console Panel: Log mesajlarını görüntülemek ve JavaScript kodunu interaktif olarak çalıştırmak için
  • Network Panel: Ağ isteklerini ve yanıtlarını incelemek için
  • Performance Panel: Kodunuzun performansını analiz etmek için
  • Memory Panel: Bellek kullanımını ve sızıntılarını tespit etmek için

 Firefox Developer Tools

Mozilla Firefox'un geliştirici araçları da benzer özellikler sunar:

  • Debugger: Kodunuzu debug etmek için
  • Console: Log mesajlarını görüntülemek için
  • Network: Ağ isteklerini incelemek için
  • Performance: Performans analizi için
  • Memory: Bellek kullanımını incelemek için

 Safari Web Inspector

Safari'nin Web Inspector'ı, macOS ve iOS için debugging araçları sunar. Önce Safari'nin Geliştirici menüsünü etkinleştirmeniz gerekebilir (Tercihler > Gelişmiş > "Menü çubuğunda Geliştir menüsünü göster").

 Edge DevTools

Microsoft Edge'in geliştirici araçları, Chrome DevTools'a benzer özellikler sunar, çünkü her ikisi de Chromium tabanlıdır.

Node.js Debugging

Node.js uygulamalarını debug etmek için çeşitli yöntemler vardır:

 Node.js Inspect

Node.js'in yerleşik inspect modu, komut satırından debugging yapmanıza olanak tanır:

node --inspect app.js
node --inspect-brk app.js  # İlk satırda duraklar

Bu komutu çalıştırdıktan sonra, Chrome DevTools'u açıp chrome://inspect adresine giderek Node.js uygulamanızı debug edebilirsiniz.

 VS Code Debugging

Visual Studio Code, Node.js uygulamalarını debug etmek için mükemmel bir entegrasyon sunar. launch.json dosyasını yapılandırarak, breakpoint'ler ekleyebilir ve kodunuzu adım adım çalıştırabilirsiniz.

 Nodemon

Nodemon, kodunuzdaki değişiklikleri izleyen ve otomatik olarak yeniden başlatan bir araçtır. Debugging ile birlikte kullanılabilir:

nodemon --inspect app.js

Debugging İpuçları ve En İyi Uygulamalar

  1. Sistematik Olun: Rastgele değişiklikler yapmak yerine, sorunu adım adım izleyin ve anlayın.
  2. Değişkenleri İzleyin: Kritik değişkenlerin değerlerini takip edin ve beklenen değerlerle karşılaştırın.
  3. Küçük Adımlarla İlerleyin: Büyük kod bloklarını debug etmek yerine, küçük parçalara bölerek test edin.
  4. Hata Mesajlarını Okuyun: Hata mesajları genellikle sorunun nerede olduğuna dair ipuçları içerir.
  5. Rubber Duck Debugging: Sorunu sesli olarak açıklamak (bir lastik ördek veya başka birine), çözümü bulmanıza yardımcı olabilir.
  6. Loglama Stratejisi Geliştirin: Farklı log seviyeleri kullanın ve üretim ortamında gereksiz logları kaldırın.
  7. Source Maps Kullanın: Minify edilmiş veya transpile edilmiş kodları debug ederken, source map'ler orijinal kodu görmenize yardımcı olur.
  8. Performans Sorunlarını Profilleyin: Yavaş çalışan kodları tespit etmek için performans profilleme araçlarını kullanın.
  9. Bellek Sızıntılarını Takip Edin: Heap snapshot'ları ve bellek profilleme araçlarını kullanarak bellek sızıntılarını tespit edin.
  10. Üçüncü Parti Kütüphaneleri Dışlayın: Önce kendi kodunuzu debug edin, sonra üçüncü parti kütüphanelere bakın.

Alıştırmalar

  1. Bir fonksiyon yazın ve bu fonksiyonda farklı türde hatalar oluşturun (SyntaxError, TypeError, ReferenceError, vb.). Her bir hata türünü yakalayıp, hata türüne göre farklı mesajlar gösterin.
  2. Özel bir hata sınıfı oluşturun (örneğin, ValidationError) ve bu sınıfı kullanarak bir form doğrulama fonksiyonu yazın. Fonksiyon, geçersiz girdiler için özel hatalar fırlatmalıdır.
  3. Asenkron bir fonksiyon yazın ve bu fonksiyonda try-catch bloğu kullanarak hataları yakalayın. Fonksiyon, bir API'den veri almayı simüle etmeli ve çeşitli hata durumlarını ele almalıdır.
  4. Bir web sayfasında, kullanıcı girdilerini doğrulayan ve hataları kullanıcıya gösteren bir form oluşturun. Form, JavaScript ile doğrulanmalı ve hatalar kullanıcı dostu mesajlarla gösterilmelidir.
  5. Bir debugging aracı kullanarak (tarayıcı geliştirici araçları veya VS Code debugger), basit bir JavaScript uygulamasındaki hataları bulun ve düzeltin. Hata ayıklama sürecinizi ve bulduğunuz hataları belgelendirin.

Bu derste, JavaScript'te hata yönetimi ve debugging konularını inceledik:

  • Hata Türleri: JavaScript'teki farklı hata türlerini (SyntaxError, ReferenceError, TypeError, vb.) ve özel hata sınıfları oluşturmayı öğrendik.
  • Try-Catch-Finally: Hataları yakalamak ve işlemek için try-catch-finally bloğunun nasıl kullanılacağını gördük.
  • Asenkron Hata Yönetimi: Promise'lar ve async/await ile asenkron kodda hataları nasıl ele alacağımızı öğrendik.
  • Debugging Teknikleri: console metodları, debugger ifadesi ve diğer debugging tekniklerini inceledik.
  • Debugging Araçları: Tarayıcı geliştirici araçları ve Node.js debugging araçlarını tanıdık.
  • En İyi Uygulamalar: Etkili debugging ve hata yönetimi için ipuçları ve en iyi uygulamaları öğrendik.

Etkili hata yönetimi ve debugging, güvenilir ve bakımı kolay JavaScript uygulamaları geliştirmenin temel unsurlarıdır. Bu beceriler, kodunuzdaki sorunları hızlı bir şekilde tespit etmenize ve çözmenize yardımcı olur, böylece kullanıcılarınıza daha iyi bir deneyim sunabilirsiniz.

Bu ders, JavaScript kursumuzun son dersidir. Şimdiye kadar, JavaScript'in temellerinden başlayarak, değişkenler, veri tipleri, operatörler, kontrol yapıları, fonksiyonlar, diziler, nesneler, DOM manipülasyonu, asenkron programlama, modüller, sınıflar ve hata yönetimi gibi konuları kapsamlı bir şekilde inceledik.

Artık JavaScript'in temel ve ileri düzey özelliklerini kullanarak kendi web uygulamalarınızı geliştirebilirsiniz. Öğrenme yolculuğunuza devam etmek için, projeler geliştirmeye, açık kaynak projelere katkıda bulunmaya ve JavaScript ekosistemindeki yeni teknolojileri takip