আমি আপনার এক্সপার্ট সফটওয়্যার ইঞ্জিনিয়ারিং ট্রেইনার। কোর্স আউটলাইন অনুযায়ী, এটি হলো Section 15: xUnit testing-এর সর্বশেষ লেকচার। আগের লেকচারে আমরা DeletePerson মেথডের জন্য Unit Test লিখেছিলাম। এই লেকচারে আমরা মেথডটির আসল লজিক (Implementation) লিখব, যাতে টেস্টগুলো পাস করে এবং এই সেকশনের সমাপ্তি টানব।

চলুন, আজকের লেকচারটি গুছিয়ে ও বিস্তারিতভাবে বুঝে নেওয়া যাক।

📝 Lecture Summary (Quick Revision)

ভবিষ্যতে দ্রুত রিভিশন দেওয়ার জন্য সম্পূর্ণ লেকচারের মূল কাজগুলো নিচে লিস্ট আকারে দেওয়া হলো:

  • Parameter Naming Fix: ইন্টারফেস এবং সার্ভিস ক্লাসে PersonId প্যারামিটারটিকে PascalCase থেকে camelCase (personId)-এ পরিবর্তন করা (কোডিং স্ট্যান্ডার্ড অনুযায়ী)।
  • Null Parameter Check: ইনপুট personId যদি null হয়, তবে ArgumentNullException থ্রো করা।
  • Fetching the Record: FirstOrDefault() ব্যবহার করে ডাটাবেস (লিস্ট) থেকে নির্দিষ্ট personId-এর ইউজারকে খুঁজে বের করা।
  • Not Found Handling: যদি ইউজারকে না পাওয়া যায় (অবজেক্ট null হয়), তবে Exception থ্রো না করে false রিটার্ন করা।
  • Removing the Record: RemoveAll() মেথড ব্যবহার করে লিস্ট থেকে ওই ইউজারের ডেটা মুছে ফেলা।
  • Success Return: ডেটা ডিলিট করার পর অপারেশন সফল হয়েছে বোঝানোর জন্য true রিটার্ন করা।
  • Test Verification: টেস্ট রান করে দেখা যে “ValidPersonID” এবং “InvalidPersonID” টেস্টগুলো পাস করেছে কিনা।

🧠 Comprehensive Breakdown

এই লেকচারে আমরা ডিলিট অপারেশনের ফুল লাইফসাইকেল ইমপ্লিমেন্ট করেছি। চলুন প্রতিটি ধাপ বিস্তারিতভাবে বুঝে নিই।

১. Naming Convention Fix (Priority: 3/10)

Concept & Why: C# এর স্ট্যান্ডার্ড গাইডলাইন অনুযায়ী, মেথডের প্যারামিটারের নাম সবসময় camelCase-এ হওয়া উচিত (অর্থাৎ প্রথম অক্ষর ছোট হাতের হবে, যেমন personId)। লেকচারার ভুলে PascalCase (PersonId) লিখেছিলেন, যা তিনি এই লেকচারে কারেকশন করে নেন।

২. Null Check & Fetching Record (Priority: 10/10)

Concept & Why: ডিলিট করার আগে আমাদের ইনপুট ভ্যালিডেট করতে হবে এবং নিশ্চিত হতে হবে যে ওই ID-এর কেউ সিস্টেমে আছে কিনা।

  • personId null হলে আমরা Exception থ্রো করব।
  • personId ঠিক থাকলে আমরা FirstOrDefault দিয়ে ডেটা খুঁজব।
  • ডেটা না পেলে আমরা false রিটার্ন করব (কারণ লেকচার ১৪৫-এ আমরা এভাবেই ডিজাইন করেছিলাম)।
public bool DeletePerson(Guid? personId)
{
    // ১. Parameter null check
    if (personId == null)
    {
        throw new ArgumentNullException(nameof(personId));
    }
 
    // ২. Fetch matching person from the data store
    Person? person = _persons.FirstOrDefault(temp => temp.PersonID == personId);
 
    // ৩. If not found, return false (Do NOT throw exception)
    if (person == null)
    {
        return false;
    }
 
    // ... পরবর্তী ডিলিট লজিক
}
 

৩. Removing the Data (RemoveAll vs Remove) (Priority: 9/10)

Concept & Why: লিস্ট থেকে ডেটা রিমুভ করার জন্য আমরা সাধারণত .Remove(object) ব্যবহার করি। কিন্তু লেকচারার এখানে .RemoveAll(condition) ব্যবহার করেছেন।

  • কেন RemoveAll? RemoveAll একটি ল্যাম্বডা এক্সপ্রেশন (Lambda expression) গ্রহণ করে এবং সেই কন্ডিশন অনুযায়ী যতগুলো ডেটা আছে সব মুছে ফেলে। যেহেতু আমাদের PersonID ইউনিক (Unique), তাই এটি শুধুমাত্র একটি রেকর্ডই মুছে ফেলবে। এটি অনেক সময় .Remove(object) এর চেয়ে ক্লিন কোড তৈরি করে।
    // ৪. Remove the person from the list
    _persons.RemoveAll(temp => temp.PersonID == personId);
 
    // ৫. Return true indicating success
    return true;
}
 

৪. Final Unit Test Run & Debugging Tips (Priority: 6/10)

লজিক লেখা শেষ করে Test Explorer রান করলে দেখা যায়, আগের লেকচারে লেখা DeletePerson_ValidPersonID (যা true রিটার্ন করার কথা) এবং DeletePerson_InvalidPersonID (যা false রিটার্ন করার কথা) টেস্ট দুটি সফলভাবে পাস করেছে।

Trainer’s Tip: লেকচারার একটি দারুণ টিপস দিয়েছেন— যদি কখনো কোনো টেস্ট অপ্রত্যাশিতভাবে ফেইল করে, তবে টেস্ট কোডে ITestOutputHelper (যা আমরা এর আগের লেকচারগুলোতে শিখেছি) ব্যবহার করে ভ্যারিয়েবলের ভ্যালুগুলো প্রিন্ট করে ডিবাগ করবেন।


🚀 Modern C# (.NET 10 Update) & Smarter Approach

লেকচারের পদ্ধতিটি ইন-মেমরি লিস্টের জন্য শতভাগ কাজ করে। তবে আমরা মডার্ন C# এবং Entity Framework (যা ভবিষ্যতে আসবে) এর জন্য একটু ভিন্ন অ্যাপ্রোচ ফলো করি।

1. Smarter Null Checking:

// Modern C# (C# 10+) null check
ArgumentNullException.ThrowIfNull(personId);
 

2. List Remove vs RemoveAll in Real Memory: ইন-মেমরি List<T> এর ক্ষেত্রে, যেহেতু আমরা FirstOrDefault দিয়ে আগেই অবজেক্টটি মেমরিতে লোড করে ফেলেছি (যা person ভ্যারিয়েবলে আছে), তাই আবার নতুন করে কন্ডিশন দিয়ে RemoveAll কল করার দরকার নেই। সরাসরি Remove(person) কল করাটা পারফরম্যান্সের দিক থেকে সামান্য ভালো, কারণ এটি আবার লিস্টটিকে লুপ করে চেক করবে না।

Optimized Code:

    Person? person = _persons.FirstOrDefault(temp => temp.PersonID == personId);
    if (person == null) return false;
 
    // মেমরিতে থাকা অবজেক্টটিকেই সরাসরি রিমুভ করা
    _persons.Remove(person); 
    return true;
 

3. Database Perspective (Future Preview): বর্তমানে আমরা _persons.Remove কল করছি যা শুধু মেমরি (RAM) থেকে ডেটা মুছছে। কোর্সের পরবর্তী সেকশনে যখন Entity Framework Core (EF Core) যুক্ত হবে, তখন ডিলিট করার পর অবশ্যই _dbContext.SaveChanges() বা SaveChangesAsync() কল করতে হবে, নতুবা ডাটাবেসে পরিবর্তন সেভ হবে না।


🏆 Best Practices & Conclusion of Section 15

  1. Keep Service Logic Clean: এই সেকশনে আমরা দেখেছি কিভাবে Controller বা UI ছাড়াই শুধুমাত্র Service এবং Unit Test ব্যবহার করে একটি সম্পূর্ণ CRUD (Create, Read, Update, Delete) অপারেশন তৈরি করা যায়। একেই বলে Test Driven Development (TDD) এবং Separation of Concerns
  2. Idempotency is Key: Delete অপারেশনটি সবসময় Idempotent হওয়া উচিত। এর মানে হলো, আমি যদি একই ইউজারকে বারবার ডিলিট করার রিকোয়েস্ট পাঠাই, প্রথমবার সে ডিলিট হবে, কিন্তু পরের বারগুলোতে সিস্টেম ক্র্যাশ করবে না; সে শুধু বলবে “ডেটা পাওয়া যায়নি” (আমাদের কোডে false রিটার্ন করবে)।
  3. What’s Next? লেকচারার বলেছেন, আমরা বিজনেস লজিক (Service) তৈরি করেছি, কিন্তু কোনো ইউজার ইন্টারফেস (UI) নেই। কোর্সের পরবর্তী সেকশনে আমরা ASP.NET Core MVC ব্যবহার করে এই CRUD অপারেশনগুলোর জন্য UI (Views) তৈরি করব।