What Is Success-Driven Development?
Overview
Section titled “Overview”Success-Driven Development is a paradigm that designs code around the success path by using explicit result types instead of exceptions.
Exception-Centric vs Success-Centric Development
Section titled “Exception-Centric vs Success-Centric Development”Problems with Exception-Centric Development
Section titled “Problems with Exception-Centric Development”In traditional development, exceptions are used to handle errors:
// ❌ Exception-centric development - problematic approachpublic User CreateUser(string email, int age){ if (string.IsNullOrEmpty(email)) throw new ArgumentException("Email is required."); if (!email.Contains("@")) throw new ArgumentException("Email format is invalid."); if (age < 0 || age > 150) throw new ArgumentException("Age is not valid.");
return new User(email, age);}
// The caller "must remember" to handle exceptionstry{ var user = CreateUser("invalid", -5);}catch (ArgumentException ex){ // Exception handling... but what if you forget?}Problems:
| Problem | Description |
|---|---|
| Easy to forget | The compiler does not enforce exception handling even if the caller omits it |
| Not visible in signature | You cannot tell from the function signature which exceptions may be thrown |
| Performance cost | Exceptions incur high performance costs such as stack trace generation |
| Violates pure functions | Throwing exceptions is a side effect that violates purity |
The Solution: Success-Driven Development
Section titled “The Solution: Success-Driven Development”Success-Driven Development solves these problems:
// ✅ Success-driven development - recommended approachpublic Fin<User> CreateUser(string email, int age){ return from validEmail in Email.Create(email) from validAge in Age.Create(age) select new User(validEmail, validAge);}
// Result handling is "enforced" at the call sitevar result = CreateUser("user@example.com", 25);result.Match( Succ: user => Console.WriteLine($"User created: {user.Email}"), Fail: error => Console.WriteLine($"Failed: {error.Message}"));Advantages:
| Advantage | Description |
|---|---|
| Type system enforces | The compiler enforces result handling |
| Failure possibility is explicit | The function signature explicitly states that failure is possible |
| Maintains pure functions | Purity is maintained without exceptions |
| Performance optimized | No exception stack traces |
The LanguageExt Library
Section titled “The LanguageExt Library”This tutorial uses the LanguageExt library. LanguageExt is a powerful library that enables functional programming in C#.
Installation
Section titled “Installation”dotnet add package LanguageExt.CoreBasic using Statements
Section titled “Basic using Statements”using LanguageExt;using LanguageExt.Common;using static LanguageExt.Prelude;Core Types: Fin and Validation<Error, T>
Section titled “Core Types: Fin and Validation<Error, T>”Fin - Final Result
Section titled “Fin - Final Result”Fin<T> is a type representing Success or Failure.
// Success casesFin<int> success = 42; // Implicit conversionFin<int> success2 = Fin<int>.Succ(42); // Explicit creation
// Failure casesFin<int> fail = Error.New("Value is not valid");Fin<int> fail2 = Fin<int>.Fail(Error.New("Error"));
// Result handlingvar result = Fin<int>.Succ(42);var output = result.Match( Succ: value => $"Success: {value}", Fail: error => $"Failure: {error.Message}");Validation<Error, T> - Validation Result (Error Accumulation)
Section titled “Validation<Error, T> - Validation Result (Error Accumulation)”Validation<Error, T> is a type that can collect all validation errors.
// Single validationValidation<Error, string> ValidateEmail(string email) => email.Contains("@") ? email : Error.New("Email requires @");
// Parallel validation via Apply (collects all errors)var result = (ValidateEmail(email), ValidateAge(age), ValidateName(name)) .Apply((e, a, n) => new User(e, a, n));// On failure, all errors are collected as ManyErrorsWhen to Use Exceptions vs Result Types
Section titled “When to Use Exceptions vs Result Types”Use Result Types (Predictable Failures)
Section titled “Use Result Types (Predictable Failures)”- User input errors (invalid email, negative age, etc.)
- Business rule violations (division by zero, invalid date, etc.)
- Domain constraints (exceeding maximum, below minimum, etc.)
Use Exceptions (Unpredictable Failures)
Section titled “Use Exceptions (Unpredictable Failures)”- System resource exhaustion (out of memory, out of disk space)
- External system errors (network connection failure, database connection failure)
- Unexpected system errors (file deletion, insufficient permissions)
Q1: Does Success-Driven Development mean never using exceptions at all?
Section titled “Q1: Does Success-Driven Development mean never using exceptions at all?”A: No. Exceptions are still used for unpredictable system errors like network connection failures or out-of-memory conditions. Success-Driven Development handles only predictable failures such as user input errors or business rule violations with result types.
Q2: Does using Fin<T> degrade performance?
Section titled “Q2: Does using Fin<T> degrade performance?”A: It actually performs better than exceptions. Exceptions have a high cost for generating stack traces, while Fin<T> is a simple value wrapper that only incurs allocation cost.
Q3: Can it be adopted incrementally in an existing codebase?
Section titled “Q3: Can it be adopted incrementally in an existing codebase?”A: Yes. Start by making the Create methods of newly written value objects return Fin<T>. Since it can coexist with existing exception-based code, there is no need to change everything at once.
Next Steps
Section titled “Next Steps”Now that you understand the concept of Success-Driven Development, let’s prepare the practice environment. In the next chapter, we proceed with .NET SDK installation and LanguageExt package setup.