Takip et

StrongResult ile İlk NuGet Paketinizi Yayımlama Rehberi

C# uygulamalarınızda hata yönetimi karmaşık ve okunmaz hale mi geliyor? Belki de try-catch bloklarının katmanları arasında boğuluyor, kodunuzun iş mantığını görmekte zorlanıyorsunuz. İşte bu noktada, sonuç odaklı programlama (result-oriented programming) imdadınıza yetişiyor ve kodunuzu daha temiz, anlaşılır ve güvenli hale getiriyor. Bu makalede, bir hafta sonu öğrenim projesi olarak geliştirdiğim StrongResult adında basit bir NuGet paketi oluşturma ve yayımlama sürecimi adım adım sizlerle paylaşacağım. Böylece hem hata yönetiminde yeni bir perspektif kazanacak hem de kendi NuGet paketinizi nasıl oluşturup tüm geliştirici topluluğuyla paylaşacağınızı öğreneceksiniz.

Modern yazılım geliştirme süreçlerinde, uygulamalarımızın güvenilirliği ve hatasız çalışması kritik öneme sahiptir. Geleneksel olarak, C# dilinde hataları yönetmek için istisnaları (exceptions) kullanırız. try-catch blokları, bir işlem sırasında beklenmedik bir durum oluştuğunda program akışını keserek hata durumunu yakalamamızı sağlar. Ancak, bu yaklaşımın bazı dezavantajları bulunmaktadır. Özellikle iş mantığı hataları veya beklenen başarısız durumlar için istisnaları kullanmak, kod okunabilirliğini düşürebilir ve performans üzerinde olumsuz etkiler yaratabilir. Örneğin, bir API isteği sırasında bağlantı hatası veya geçersiz giriş gibi durumlar, programın tamamen çökmesine neden olacak “istisna”lardan ziyade, işlem sonucunun bir parçası olarak ele alınmalıdır.

İstisnalar, genellikle “olağanüstü durumlar” için tasarlanmıştır. Veritabanı bağlantısının aniden kesilmesi, disk alanının dolması gibi beklenmedik olaylar istisna fırlatmayı hak eder. Ancak, kullanıcıdan alınan verinin geçerli olmaması veya bir kaydın veritabanında bulunamaması gibi durumlar, iş akışının doğal bir parçası olarak değerlendirilebilir. Bu gibi durumlarda istisna fırlatmak ve yakalamak, aslında bir kontrol akışı mekanizması olarak kullanıldığında kodun karmaşıklığını artırır ve beklenmedik durumlarla, beklenen başarısız durumları ayırt etmeyi zorlaştırır. İşte bu noktada, sonuç odaklı programlama (Result-Oriented Programming) devreye girer. Bu yaklaşım, bir işlemin sonucunu, başarı veya başarısızlık durumunu ve ilgili verileri açıkça ifade eden bir tür olarak temsil etmeyi önerir. Bu sayede, fonksiyon imzaları, olası sonuçları net bir şekilde belirtir ve çağıran kodun bu durumları zarif bir şekilde ele almasını kolaylaştırır. StrongResult da tam olarak bu felsefeyi benimseyen, basit ama etkili bir yardımcı pakettir.

StrongResult Nedir ve Hata Yönetimini Nasıl Kolaylaştırır?

StrongResult, bir işlemin sonucunu Success (başarılı) veya Failure (başarısız) olarak ifade etmenizi sağlayan basit bir yapı sunar. Geleneksel try-catch mekanizmasının aksine, StrongResult, başarılı bir işlemin değerini veya başarısız bir işlemin hata detaylarını tip güvenli bir şekilde taşıyan jenerik bir Result veya Result yapısı sağlar. Buradaki T, işlemin başarılı olması durumunda döndürülecek veri tipini, E ise başarısızlık durumunda döndürülecek hata tipini temsil eder. Bu sayede, fonksiyonunuzun imzası, olası tüm sonuçları açıkça belirtir ve çağrı yapan kodun bu durumları ele alması gerektiğini garanti eder.

Peki, bu yaklaşım hata yönetimini nasıl kolaylaştırır? Öncelikle, fonksiyon imzaları daha açıklayıcı hale gelir. Bir metodun Result döndürdüğünü gördüğünüzde, bu metodun ya bir User nesnesiyle başarılı olacağını ya da bir Error nesnesiyle başarısız olacağını hemen anlarsınız. Bu, metodun yan etkilerini veya olası sonuçlarını tahmin etmeye çalışmak yerine, doğrudan ne bekleyeceğinizi bilmenizi sağlar. İkinci olarak, Result nesnesi üzerinde IsSuccess veya IsFailure gibi özellikler veya Match gibi metodlar kullanarak, başarılı ve başarısız durumları ayrıştırılmış bir şekilde ele alabilirsiniz. Bu, koşullu dallanmaları daha okunur hale getirir ve hata durumlarını atlamanızı engeller.

Üçüncü olarak, Result nesneleri genellikle bir zincirleme (chaining) mekanizması sunar. Örneğin, bir Bind veya Map metodu kullanarak, bir işlemin başarılı sonucunu alıp bir sonraki işleme aktarabilirsiniz. Eğer ara işlemlerden biri başarısız olursa, zincir kesilir ve hata nesnesi doğrudan sonuca taşınır. Bu, iç içe if veya try-catch bloklarından kaçınarak daha akıcı ve işlevsel bir kod yazmanızı sağlar. StrongResult, özellikle mikroservis mimarileri veya karmaşık iş akışları olan uygulamalarda, hata propogasyonunu (hata yayılımını) standart ve anlaşılır bir hale getirerek geliştiricilerin işini büyük ölçüde kolaylaştırır. Kodunuzun her yerinde tutarlı bir hata yönetimi stratejisi izlemenize olanak tanır, bu da daha az sürprizle ve daha güvenilir uygulamalarla sonuçlanır.

Kendi StrongResult Paketinizi Nasıl Oluşturursunuz? Adım Adım Rehber

Kendi NuGet paketinizi oluşturmanın ilk adımı, paketin temel işlevselliğini sağlayacak kodu yazmaktır. StrongResult için bu, Result yapısını ve yardımcı metodlarını tanımlamak anlamına geliyor. Bu bölümde, basit bir Result sınıfını nasıl tasarlayacağımızı ve temel özelliklerini nasıl uygulayacağımızı göreceğiz.

1. Yeni Bir Class Library Projesi Oluşturmak

Öncelikle, Visual Studio’da veya .NET CLI kullanarak yeni bir Class Library projesi oluşturun. Bu proje, NuGet paketinizin çekirdeğini oluşturacaktır.


dotnet new classlib -n StrongResult
cd StrongResult
    

2. Temel Result Sınıfını Tasarlamak

Basitlik adına, StrongResult'ı Result ve Result olmak üzere iki ana jenerik yapı olarak tasarlayabiliriz. İlk adım olarak, sadece başarılı veya başarısız durumu belirten jenerik olmayan bir Result ve ardından değer taşıyan Result yapısını oluşturalım. Hataları temsil etmek için ise basit bir Error sınıfı tanımlayabiliriz.


// StrongResult/Error.cs
namespace StrongResult
{
    public class Error
    {
        public string Code { get; }
        public string Message { get; }
        public Dictionary Metadata { get; }

        private Error(string code, string message, Dictionary metadata = null)
        {
            Code = code;
            Message = message;
            Metadata = metadata ?? new Dictionary();
        }

        public static Error Validation(string message, string code = "ValidationError")
            => new Error(code, message);
        public static Error NotFound(string message, string code = "NotFoundError")
            => new Error(code, message);
        public static Error Unauthorized(string message, string code = "UnauthorizedError")
            => new Error(code, message);
        public static Error Unexpected(string message = "An unexpected error occurred.", string code = "UnexpectedError")
            => new Error(code, message);

        public override string ToString() => $"{Code}: {Message}";
    }
}

// StrongResult/Result.cs (Non-generic base for easier pattern matching/utility)
namespace StrongResult
{
    public abstract class Result
    {
        public bool IsSuccess { get; }
        public bool IsFailure => !IsSuccess;
        public Error Error { get; }

        protected Result(bool isSuccess, Error error)
        {
            IsSuccess = isSuccess;
            Error = error; // Should be null if IsSuccess is true
        }

        // Static factory methods for convenience
        public static Result Success(TValue value) => new SuccessResult(value);
        public static Result Success(TValue value) where TError : Error => new SuccessResult(value);
        public static Result Failure(Error error) => new FailureResult(error);
        public static Result Failure(TError error) where TError : Error => new FailureResult(error);
    }

    public abstract class Result : Result
    {
        public TValue Value { get; }

        protected Result(TValue value, bool isSuccess, Error error) : base(isSuccess, error)
        {
            Value = value; // Default or actual value
        }

        public TValue GetValueOrDefault(TValue defaultValue = default)
        {
            return IsSuccess ? Value : defaultValue;
        }

        // Implicit conversions for convenience
        public static implicit operator Result(TValue value) => Result.Success(value);
        public static implicit operator Result(Error error) => Result.Failure(error);
    }

    public class SuccessResult : Result
    {
        public SuccessResult(TValue value) : base(value, true, null) { }
    }

    public class FailureResult : Result
    {
        public FailureResult(Error error) : base(default, false, error)
        {
            if (error == null)
                throw new ArgumentNullException(nameof(error), "Error cannot be null for a failure result.");
        }
    }
}
    

Bu temel yapılar sayesinde, fonksiyonlarımız artık sadece bir değer döndürmek yerine, işlemin başarılı mı yoksa başarısız mı olduğunu açıkça belirten bir Result nesnesi döndürebilecek. Bu, çağrı yapan kodun hata durumlarını if blokları veya switch ifadeleriyle kolayca ele almasını sağlar. Özellikle implicit operator tanımları, başarılı bir değer veya bir hata nesnesi döndürdüğünüzde, bunların otomatik olarak bir Result nesnesine dönüştürülmesini sağlayarak kod yazımını basitleştirir.

3. StrongResult ile Akıcı Kod Yazmak: Map ve Bind Metotları

StrongResult'ın gücü, sadece başarılı/başarısız durumlarını taşımakta değil, aynı zamanda bu durumlar üzerinde akıcı işlemler yapabilme yeteneğinde yatar. Map ve Bind (veya Then) metodları, bu akışkanlığı sağlayan temel yapı taşlarıdır. Bu metodlar, işlevsel programlama prensiplerinden ilham alarak, bir Result nesnesi üzerindeki dönüşümleri ve zincirleme işlemleri basitleştirir.

Map Metodu: Map metodu, başarılı bir Result nesnesinin içindeki değeri başka bir tipe dönüştürmek istediğinizde kullanılır. Eğer Result başarısız durumdaysa, Map hiçbir işlem yapmadan orijinal hata nesnesini koruyarak yeni bir başarısız Result döndürür. Bu, bir Result'i Result'e dönüştürmek gibi senaryolarda oldukça kullanışlıdır.


// Result.cs içine ekleyin
public Result Map(Func mapper)
{
    return IsSuccess
        ? Result.Success(mapper(Value))
        : Result.Failure(Error);
}
    

Bind Metodu: Bind metodu ise daha güçlü bir araçtır. Bir Result nesnesinin içindeki değeri, kendisi de bir Result döndüren başka bir fonksiyona geçirmek istediğinizde kullanılır. Yani, başarılı bir işlem sonucunu alıp, bu sonucun üzerine başka bir potansiyel olarak başarısız olabilecek işlemi zincirlemek için idealdir. Eğer mevcut Result başarısızsa, Bind hiçbir işlem yapmadan doğrudan hata nesnesini taşıyan yeni bir başarısız Result döndürür. Bu, bir dizi işlemi birbiri ardına sıralarken, herhangi bir noktadaki hatanın tüm zinciri kesmesini ve hemen hata durumunu döndürmesini sağlar.


// Result.cs içine ekleyin
public Result Bind(Func> binder)
{
    return IsSuccess
        ? binder(Value)
        : Result.Failure(Error);
}

// Ayrıca asenkron versiyonları da ekleyebiliriz (Task tabanlı):
public async Task> BindAsync(Func>> binder)
{
    return IsSuccess
        ? await binder(Value)
        : Result.Failure(Error);
}

public async Task> MapAsync(Func> mapper)
{
    return IsSuccess
        ? Result.Success(await mapper(Value))
        : Result.Failure(Error);
}
    

Bu metodlarla, karmaşık iş akışlarını daha okunabilir ve yönetilebilir bir şekilde yazabilirsiniz. Örneğin, bir kullanıcının kaydını oluşturup, ardından ona bir hoş geldin e-postası gönderme işlemini zincirleme olarak ele alalım:


public class UserService
{
    public Result CreateUser(string email, string password)
    {
        if (string.IsNullOrWhiteSpace(email))
            return Error.Validation("Email cannot be empty.");
        // ... user creation logic ...
        return new User { Email = email, Id = Guid.NewGuid() };
    }

    public Result SendWelcomeEmail(User user)
    {
        if (user == null)
            return Error.Unexpected("User cannot be null for welcome email.");
        // ... email sending logic ...
        return true; // Email sent successfully
    }

    public Result RegisterUserAndSendWelcomeEmail(string email, string password)
    {
        return CreateUser(email, password)
            .Bind(user => SendWelcomeEmail(user)); // Eğer CreateUser başarılı olursa SendWelcomeEmail çağrılır
    }
}

// Kullanım örneği
var userService = new UserService();
var registrationResult = userService.RegisterUserAndSendWelcomeEmail("test@example.com", "password123");

if (registrationResult.IsSuccess)
{
    Console.WriteLine("Kullanıcı başarıyla kaydedildi ve hoş geldin e-postası gönderildi!");
}
else
{
    Console.WriteLine($"Kayıt başarısız oldu: {registrationResult.Error}");
}
    

Bu örnekte, CreateUser metodundan dönen Result nesnesi, Bind metodu aracılığıyla SendWelcomeEmail metoduna aktarılır. Eğer CreateUser herhangi bir nedenle başarısız olursa (örneğin email geçersizse), Bind metodu SendWelcomeEmail'i hiç çağırmadan doğrudan CreateUser'ın hata sonucunu döndürecektir. Bu, hata yönetimini çok daha akıcı ve kontrol edilebilir hale getirir.

Bir NuGet Paketi Nasıl Yayımlanır? Adım Adım Kılavuz

Kodunuzu StrongResult olarak hazırladıktan sonra, sıra geldi bu kodu bir NuGet paketi olarak paketlemeye ve yayımlamaya. Bu süreç, projenizi tüm .NET geliştiricileriyle paylaşmanızı sağlar. İşte adım adım nasıl yapacağınız:

1. Proje Dosyasını (.csproj) NuGet İçin Yapılandırmak

İlk olarak, Class Library projenizin .csproj dosyasını düzenleyerek NuGet paketi için gerekli meta verileri eklemeniz gerekir. Bu meta veriler, paketinizi NuGet Gallery'de tanımlayacak ve kullanıcıların paketinizi kolayca bulmasını sağlayacaktır.




  
    net8.0
    enable
    enable

    true 
    StrongResult 
    1.0.0 
    Adınız Soyadınız 
    Şirketiniz (isteğe bağlı)
    C# uygulamaları için sonuç odaklı, tip güvenli hata yönetimi sağlayan hafif bir kütüphane. 
    https://github.com/kullaniciadiniz/StrongResult 
    https://github.com/kullaniciadiniz/StrongResult 
    result, error-handling, functional, csharp, nuget, strongresult 
    MIT 
    true 
  


    

Bu özellikler, paketinizi NuGet Gallery'de daha bulunabilir ve profesyonel hale getirecektir. Özellikle PackageId'nin benzersiz olduğundan emin olun.

2. NuGet Paketinizi Oluşturmak (.nupkg)

Proje dosyanızı yapılandırdıktan sonra, paketi oluşturmak oldukça basittir. Projenizin kök dizininde aşağıdaki komutu çalıştırın:


dotnet pack --configuration Release
    

Bu komut, bin/Release klasörü altında StrongResult.1.0.0.nupkg gibi bir dosya oluşturacaktır. Bu dosya, tüm paketlenmiş kodunuzu, meta verilerinizi ve bağımlılıklarınızı içerir.

3. NuGet.org API Anahtarı Almak

Paketinizi nuget.org'a yayımlamak için bir nuget.org hesabınızın olması ve bir API anahtarı oluşturmanız gerekir. nuget.org'a giriş yaptıktan sonra, sağ üstteki kullanıcı adınıza tıklayarak "API Keys" bölümüne gidin ve yeni bir anahtar oluşturun. Bu anahtarı güvende tutun, çünkü paketinizi yayımlamak için gerekli olacaktır.

NuGet.org API Anahtarı Oluşturma Sayfası

(Bu bir görsel betimlemesidir, gerçek bir görsel değildir. NuGet.org sitesinde, API Anahtarları bölümünde yeni bir anahtar oluşturma formu bulunur. Anahtarın süresini ve hangi paketler üzerinde işlem yapabileceğini belirleyebilirsiniz.)

4. NuGet Paketini Yayımlamak

Son adım, oluşturduğunuz .nupkg dosyasını nuget.org'a göndermektir. API anahtarınızı kullanarak aşağıdaki komutu çalıştırın:


dotnet nuget push "bin/Release/StrongResult.1.0.0.nupkg" --api-key  --source https://api.nuget.org/v3/index.json
    

kısmını kendi API anahtarınızla değiştirin. Komut başarılı olursa, paketinizi nuget.org'da görebilirsiniz. İlk yayımlama biraz zaman alabilir (genellikle birkaç dakika), ancak daha sonra güncellemeler daha hızlı yayımlanacaktır.

Bu süreç, kendi yarattığınız kodu geniş bir geliştirici topluluğuyla paylaşmanızı sağlar. StrongResult gibi bir yardımcı kütüphane, sizin veya ekibinizin hata yönetimini standartlaştırmasına ve kod kalitesini artırmasına yardımcı olabilir.

Gerçek Dünya Senaryosu: Bir E-ticaret Uygulamasında Sipariş Yönetimi

StrongResult'ın gerçek bir uygulamada nasıl fark yaratabileceğini daha iyi anlamak için, bir e-ticaret uygulamasındaki sipariş oluşturma sürecini inceleyelim. Bu süreç, genellikle birden fazla adımdan ve olası hata durumundan oluşur:

  1. Kullanıcı sepetindeki ürünlerin geçerliliğini kontrol etme.
  2. Kullanıcının bakiyesini veya ödeme yöntemini doğrulama.
  3. Siparişi veritabanına kaydetme.
  4. Stokları güncelleme.
  5. Kullanıcıya sipariş onay e-postası gönderme.

Geleneksel try-catch yaklaşımıyla bu işlemlerin her birinde oluşabilecek hataları yönetmek, iç içe try-catch bloklarına veya erken return ifadeleriyle dolu karmaşık bir koda yol açabilir. Ancak StrongResult ile bu süreç çok daha akıcı ve okunabilir hale gelir.

Senaryo Uygulaması

Aşağıdaki örnek kod, StrongResult kullanarak bu adımları nasıl zincirleyebileceğinizi göstermektedir. Her metod, başarılı bir durumda bir Result veya hata durumunda bir Error döndürür.


using StrongResult;
using System;
using System.Threading.Tasks;

public class Order
{
    public Guid Id { get; set; }
    public Guid UserId { get; set; }
    public decimal TotalAmount { get; set; }
    public List ProductSkus { get; set; }
}

public class OrderCreationRequest
{
    public Guid UserId { get; set; }
    public List ProductSkus { get; set; }
    public decimal PaymentAmount { get; set; }
}

public class ECommerceService
{
    // 1. Ürünlerin geçerliliğini kontrol etme
    public Result> ValidateProducts(List productSkus)
    {
        if (productSkus == null || !productSkus.Any())
            return Error.Validation("Sipariş sepeti boş olamaz.");

        // Örnek: Ürünlerin varlığını ve stok durumunu kontrol et
        foreach (var sku in productSkus)
        {
            if (sku == "INVALID_SKU") // Varsayımsal hata durumu
                return Error.NotFound($"'{sku}' koduyla ürün bulunamadı.");
            // Gerçek dünyada DB'den veya başka bir servisten kontrol edilir
        }
        return productSkus;
    }

    // 2. Ödeme yöntemini doğrulama
    public Result ProcessPayment(Guid userId, decimal amount)
    {
        if (amount <= 0)
            return Error.Validation("Ödeme miktarı sıfırdan büyük olmalıdır.");

        // Örnek: Kullanıcının bakiyesi yetersiz veya ödeme servisi hatası
        if (userId == Guid.Parse("f00dbeef-f00d-f00d-f00d-f00dbeef0001")) // Varsayımsal yetersiz bakiye kullanıcısı
            return Error.Unauthorized("Yetersiz bakiye.");

        // Gerçek dünyada bir ödeme gateway'i ile entegrasyon
        Console.WriteLine($"Kullanıcı {userId} için {amount} TL ödeme başarıyla işlendi.");
        return true;
    }

    // 3. Siparişi veritabanına kaydetme
    public Result SaveOrder(Guid userId, List productSkus, decimal totalAmount)
    {
        var newOrder = new Order
        {
            Id = Guid.NewGuid(),
            UserId = userId,
            ProductSkus = productSkus,
            TotalAmount = totalAmount
        };
        // Gerçek dünyada DB kaydı yapılır
        Console.WriteLine($"Sipariş {newOrder.Id} başarıyla veritabanına kaydedildi.");
        return newOrder;
    }

    // 4. Stokları güncelleme (asenkron olabilir)
    public async Task> UpdateStockAsync(List productSkus)
    {
        await Task.Delay(100); // Varsayımsal asenkron işlem
        foreach (var sku in productSkus)
        {
            if (sku == "OUT_OF_STOCK_SKU") // Varsayımsal stok hatası
                return Error.Unexpected($"'{sku}' ürününün stoğu güncellenemedi.");
        }
        Console.WriteLine("Stoklar başarıyla güncellendi.");
        return true;
    }

    // 5. Sipariş onay e-postası gönderme
    public async Task> SendOrderConfirmationEmailAsync(Order order)
    {
        await Task.Delay(50); // Varsayımsal asenkron işlem
        if (order.UserId == Guid.Parse("f00dbeef-f00d-f00d-f00d-f00dbeef0002")) // Varsayımsal email gönderim hatası
            return Error.Unexpected("Sipariş onay e-postası gönderilemedi.");
        Console.WriteLine($"Sipariş {order.Id} için onay e-postası gönderildi.");
        return true;
    }

    // Tüm sipariş sürecini zincirleme
    public async Task> CreateOrderAsync(OrderCreationRequest request)
    {
        return await Task.FromResult(ValidateProducts(request.ProductSkus))
            .Bind(validatedSkus => ProcessPayment(request.UserId, request.PaymentAmount)
                .Map(_ => validatedSkus)) // Ödeme başarılıysa validatedSkus'u bir sonraki adıma geçir
            .Bind(async validatedSkus => await SaveOrder(request.UserId, validatedSkus, request.PaymentAmount)
                .Bind(async order => await UpdateStockAsync(order.ProductSkus)
                    .Map(_ => order))) // Stok güncellenirse order'ı bir sonraki adıma geçir
            .Bind(async order => await SendOrderConfirmationEmailAsync(order)
                .Map(_ => order)); // E-posta gönderilirse order'ı geri döndür
    }
}

// Uygulama girişi (Program.cs)
// var service = new ECommerceService();
// var request = new OrderCreationRequest
// {
//     UserId = Guid.NewGuid(),
//     ProductSkus = new List { "PRODUCT_A", "PRODUCT_B" },
//     PaymentAmount = 150.00m
// };

// var result = await service.CreateOrderAsync(request);

// if (result.IsSuccess)
// {
//     Console.WriteLine($"Sipariş başarıyla oluşturuldu: {result.Value.Id}");
// }
// else
// {
//     Console.Error.WriteLine($"Sipariş oluşturulamadı: {result.Error}");
// }
    

Yukarıdaki örnekte, her bir işlem bir Result döndürdüğü için, Bind ve Map metodlarını kullanarak tüm süreci tek bir akıcı zincirde birleştirebiliyoruz. Eğer zincirdeki herhangi bir adım başarısız olursa, zincir otomatik olarak kesilir ve hata, en son Result nesnesine taşınır. Bu sayede, kodunuzda karmaşık if/else merdivenlerinden veya iç içe try-catch bloklarından kurtulursunuz. Hata yönetimi merkezi ve belirgin hale gelir, bu da kodun hem okunabilirliğini hem de bakımını büyük ölçüde artırır. Ayrıca, BindAsync ve MapAsync sayesinde asenkron işlemleri de bu akışa dahil etmek oldukça kolaydır. Bu, özellikle modern, I/O yoğun uygulamalar için paha biçilmez bir avantaj sağlar.

Gelişmiş StrongResult Kullanımı ve İpuçları

StrongResult gibi bir kütüphane, temel hata yönetiminin ötesinde, daha karmaşık senaryolarda da etkili bir şekilde kullanılabilir. Deneyimli geliştiriciler için bu bölümde bazı ileri düzey ipuçları ve püf noktaları paylaşacağım.

1. Özel Hata Tipleri Oluşturma

StrongResult'ın Error sınıfı basit bir başlangıç noktasıdır, ancak iş uygulamalarında genellikle daha spesifik hata tiplerine ihtiyaç duyulur. Örneğin, "Geçersiz Kullanıcı Adı", "Ürün Stokta Yok" veya "Ödeme Başarısız" gibi farklı hata senaryolarını ayırt etmek isteyebilirsiniz. StrongResult'ı jenerik Result olarak genişleterek, kendi özel hata sınıflarınızı kullanabilirsiniz.


// StrongResult/CustomError.cs
namespace MyProject.Errors
{
    public abstract class AppError : StrongResult.Error
    {
        protected AppError(string code, string message, Dictionary metadata = null)
            : base(code, message, metadata) { }
    }

    public class UserNotFoundError : AppError
    {
        public UserNotFoundError(Guid userId)
            : base("UserNotFound", $"Kullanıcı ID '{userId}' bulunamadı.", new Dictionary { { "UserId", userId } }) { }
    }

    public class PaymentFailedError : AppError
    {
        public PaymentFailedError(string reason)
            : base("PaymentFailed", $"Ödeme başarısız: {reason}", new Dictionary { { "Reason", reason } }) { }
    }
}

// Kullanım örneği:
// public Result GetUser(Guid userId)
// {
//     // ... user bulma mantığı ...
//     if (user == null)
//         return new UserNotFoundError(userId);
//     return user;
// }
    

Bu, hatanın tipine göre daha hassas kontrol akışları yazmanıza olanak tanır ve hatanın ne anlama geldiğini daha açıkça belirtir.

2. Asenkron İşlemlerle Birlikte Kullanım

.NET dünyasında asenkron programlama (async/await) yaygın olarak kullanılmaktadır. StrongResult'ın BindAsync ve MapAsync metodları, asenkron işlemleri sonuç zincirine dahil etmeyi kolaylaştırır. Böylece, I/O yoğun uygulamalarda bile temiz ve okunabilir bir hata yönetimi sağlayabilirsiniz. Yukarıdaki gerçek dünya senaryosunda bu kullanıma örnek verilmiştir. Ancak daha genel bir kullanımı aşağıda görebilirsiniz:


public class DataService
{
    public async Task> FetchDataAsync(int id)
    {
        await Task.Delay(100); // Simulate network call
        if (id <= 0)
            return Error.Validation("ID geçerli değil.");
        if (id == 99)
            return Error.NotFound("Veri bulunamadı.");
        return $"Veri: {id}";
    }

    public async Task> ProcessAndParseDataAsync(string data)
    {
        await Task.Delay(50); // Simulate CPU-bound work
        if (!int.TryParse(data.Split(':')[1].Trim(), out int parsedValue))
            return Error.Unexpected("Veri ayrıştırılamadı.");
        return parsedValue * 2;
    }

    public async Task> GetDataAndProcess(int inputId)
    {
        return await FetchDataAsync(inputId)
            .BindAsync(async data => await ProcessAndParseDataAsync(data));
    }
}
    

Bu örnek, iki asenkron işlemi nasıl BindAsync ile zincirleyebileceğinizi gösterir. İlk işlem başarısız olursa, ikincisi hiç çalıştırılmaz.

3. StrongResult'ı Dependency Injection (DI) ile Entegre Etme

StrongResult'ın kendisi temel bir kütüphane olduğundan, doğrudan DI ile entegre edilecek bir servisi yoktur. Ancak, Result nesneleri döndüren servislerinizi DI kapsayıcısına kaydederken, StrongResult'ı verimli bir şekilde kullanabilirsiniz. Örneğin, tüm "Application Service" katmanınız Result döndürüyorsa, bu servisleri DI aracılığıyla kolayca yönetebilirsiniz. Bu, uygulama genelinde tutarlı bir desen oluşturmanıza yardımcı olur.

4. Performans ve Optimizasyon

StrongResult kullanmak, genellikle istisnaları yakalamaktan daha performanslıdır, çünkü istisna fırlatma ve yakalama işlemleri pahalıdır. Ancak, her zaman olduğu gibi, aşırı nesne oluşturmaktan kaçınmak önemlidir. Result sınıflarınızın hafif olduğundan ve gereksiz bellek tahsisinden kaçındığından emin olun. struct tabanlı Result uygulamaları, daha fazla performans isteyen senaryolarda değerlendirilebilir. Yine de, çoğu iş uygulaması için class tabanlı bir uygulama yeterli olacaktır.

StrongResult'ın bu gelişmiş kullanımları, uygulamanızın daha sağlam, esnek ve sürdürülebilir olmasına yardımcı olabilir. Hata yönetimini kodunuzun merkezine alarak, hem geliştirici deneyimini hem de kullanıcı deneyimini önemli ölçüde iyileştirebilirsiniz.

Sonuç ve Sıkça Sorulan Sorular

Bu makalede, bir hafta sonu projesi olarak StrongResult adında basit bir NuGet paketi oluşturma ve yayımlama yolculuğumu sizlerle paylaştım. Geleneksel hata yönetimi yaklaşımlarının zorluklarını ele alarak, sonuç odaklı programlamanın (Result-Oriented Programming) kodunuzu nasıl daha temiz, daha güvenli ve daha okunabilir hale getirebileceğini gösterdik. StrongResult gibi bir yapının, başarılı ve başarısız işlemleri tip güvenli bir şekilde temsil etmesi, özellikle karmaşık iş akışlarına sahip uygulamalarda büyük bir kolaylık sağlamaktadır. Kendi paketinizi oluşturma, .csproj dosyasını yapılandırma, paketi derleme ve NuGet.org'a yayımlama adımlarını detaylıca inceledik. Ayrıca, bir e-ticaret uygulamasındaki sipariş yönetimi örneğiyle StrongResult'ın gerçek dünya senaryolarında nasıl kullanılabileceğini ve asenkron işlemlerle nasıl entegre edilebileceğini gözlemledik. Umarız bu rehber, hem hata yönetiminde yeni bir bakış açısı kazanmanıza hem de kendi NuGet paketlerinizi oluşturarak .NET ekosistemine katkıda bulunmanıza ilham verir.

Kendi kütüphanenizi yayımlamak, sadece teknik yetkinliğinizi geliştirmekle kalmaz, aynı zamanda açık kaynak topluluğuna değer katmanızı da sağlar. Bu süreç, bir geliştirici olarak büyümeniz için harika bir fırsattır.

Sıkça Sorulan Sorular (SSS)

1. StrongResult kullanmak, try-catch bloklarının tamamen gereksiz olduğu anlamına mı geliyor?
Hayır, kesinlikle değil. StrongResult, iş mantığı hataları veya beklenen başarısız durumlar için harikadır. Örneğin, "geçersiz giriş", "kayıt bulunamadı" gibi durumlar için idealdir. Ancak, NullReferenceException, OutOfMemoryException gibi olağanüstü ve beklenmedik sistem hataları için try-catch blokları hala en doğru çözümdür. StrongResult, beklenen iş akışındaki hataları ele alırken, try-catch beklenmedik sistem çöküşlerini engellemek için kullanılır.
2. StrongResult'ın performans etkisi nedir?
Genellikle, istisnaları yakalamaktan daha performanslıdır. İstisna fırlatma ve yakalama işlemleri, CLR (Common Language Runtime) üzerinde ek yük oluşturur. StrongResult ise sadece bir nesne oluşturma ve değer döndürme işlemidir. Ancak, her programlama yaklaşımında olduğu gibi, aşırı nesne oluşturmaktan kaçınmak ve struct tabanlı Result uygulamalarını performans kritik senaryolarda değerlendirmek gerekebilir. Çoğu iş uygulaması için class tabanlı bir StrongResult uygulaması yeterli performans sağlayacaktır.
3. Kendi NuGet paketimi yayımlarken nelere dikkat etmeliyim?
Paket kimliğinizin (PackageId) benzersiz olduğundan, sürüm numaranızın (Version) anlamlı olduğundan (SemVer kurallarına uymak iyi bir pratiktir), açıklamanızın (Description) net ve bilgilendirici olduğundan emin olun. Ayrıca, paketinize uygun bir lisans (PackageLicenseExpression) eklemeyi ve varsa kaynak kod deposu URL'sini (RepositoryUrl) belirtmeyi unutmayın. Bu, diğer geliştiricilerin paketinizi anlamasına ve güvenmesine yardımcı olacaktır.
4. StrongResult sadece C# için mi kullanılabilir?
Bu makalede sunulan StrongResult uygulaması C# dilinde ve .NET ekosistemi için tasarlanmıştır. Ancak, "sonuç odaklı programlama" kavramı, Rust, Swift, Kotlin gibi birçok modern programlama dilinde benzer yapılarla (örneğin Rust'taki Result enum'ı) mevcuttur. Bu bir programlama desenidir ve farklı dillerde benzer şekilde uygulanabilir.

Yorumlar
İçeriği beğendiniz mi? Bir tartışma başlatın veya görüşlerinizi paylaşın.
Yorum Yaz

Bir yanıt yazın

E-posta adresiniz yayınlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir

Gönder

E-posta Bülteni
Yazılım Topluluğuna Katılın
En son güncellemeleri, yaratıcı ipuçlarını ve özel kaynakları doğrudan e-posta kutunuza alın. Tasarım ve inovasyonun geleceğini birlikte keşfedelim.
Mobil sürümden çık