DEV Community

Spyros Ponaris
Spyros Ponaris

Posted on • Edited on

Refactoring a Simple C# Method Step by Step

Refactoring in C# — Step by Step Guide

Refactoring is not about changing what the code does. It is about improving how the code is written.

The goal is to make the code easier to read, easier to test, and easier to maintain.

In this tutorial, we will refactor a simple user registration method step by step.


The Original Code

Imagine we have a method like this:

public void RegisterUser(string username, string password, int age, string email)
{
    if (username == null || username == "")
        throw new Exception("Username is required");

    if (password == null || password == "")
        throw new Exception("Password is required");

    if (password.Length < 8)
        throw new Exception("Password must be at least 8 characters");

    if (age < 18 || age > 120)
        throw new Exception("Invalid age");

    if (email == null || !email.Contains("@") || !email.Contains("."))
        throw new Exception("Invalid email");

    Console.WriteLine("User registered successfully!");
}
Enter fullscreen mode Exit fullscreen mode

The code works, but it has some problems.


What Is Wrong With This Code?

The method has too many responsibilities:

  • It validates input
  • It performs business logic
  • It mixes concerns

This makes it harder to read and test.

Other issues:

  • Validation logic is mixed with business logic
  • The method is too long
  • Repeated checks
  • Primitive obsession (string, int instead of domain types)

Step 1: Use Guard Clauses

public void RegisterUser(string username, string password, int age, string email)
{
    ArgumentException.ThrowIfNullOrWhiteSpace(username);
    ArgumentException.ThrowIfNullOrWhiteSpace(password);

    if (password.Length < 8)
        throw new ArgumentException("Password must be at least 8 characters.", nameof(password));

    if (age is < 18 or > 120)
        throw new ArgumentOutOfRangeException(nameof(age));

    if (!IsValidEmail(email))
        throw new FormatException("Invalid email format.");

    Console.WriteLine("User registered successfully!");
}

private bool IsValidEmail(string email)
{
    return !string.IsNullOrWhiteSpace(email)
           && email.Contains("@")
           && email.Contains(".");
}
Enter fullscreen mode Exit fullscreen mode

Step 2: Use a Request Object (DTO)

public class UserRegistrationRequest
{
    public string Username { get; set; } = string.Empty;
    public string Password { get; set; } = string.Empty;
    public int Age { get; set; }
    public string Email { get; set; } = string.Empty;
}
Enter fullscreen mode Exit fullscreen mode

Updated method:

public void RegisterUser(UserRegistrationRequest request)
{
    ArgumentNullException.ThrowIfNull(request);

    ArgumentException.ThrowIfNullOrWhiteSpace(request.Username);
    ArgumentException.ThrowIfNullOrWhiteSpace(request.Password);

    if (request.Password.Length < 8)
        throw new ArgumentException("Password must be at least 8 characters.");

    if (request.Age is < 18 or > 120)
        throw new ArgumentOutOfRangeException(nameof(request.Age));

    if (!IsValidEmail(request.Email))
        throw new FormatException("Invalid email format.");

    Console.WriteLine("User registered successfully!");
}
Enter fullscreen mode Exit fullscreen mode

Step 3: Extract Validation Into a Validator

public class UserRegistrationValidator
{
    public void Validate(UserRegistrationRequest request)
    {
        ArgumentNullException.ThrowIfNull(request);

        ArgumentException.ThrowIfNullOrWhiteSpace(request.Username);
        ArgumentException.ThrowIfNullOrWhiteSpace(request.Password);

        if (request.Password.Length < 8)
            throw new ArgumentException("Password must be at least 8 characters.");

        if (request.Age is < 18 or > 120)
            throw new ArgumentOutOfRangeException(nameof(request.Age));

        if (!IsValidEmail(request.Email))
            throw new FormatException("Invalid email format.");
    }

    private static bool IsValidEmail(string email)
    {
        return !string.IsNullOrWhiteSpace(email)
               && email.Contains("@")
               && email.Contains(".");
    }
}
Enter fullscreen mode Exit fullscreen mode

Service:

public void RegisterUser(UserRegistrationRequest request)
{
    var validator = new UserRegistrationValidator();
    validator.Validate(request);

    Console.WriteLine("User registered successfully!");
}
Enter fullscreen mode Exit fullscreen mode

Step 4: Inject the Validator

public interface IUserRegistrationValidator
{
    void Validate(UserRegistrationRequest request);
}
Enter fullscreen mode Exit fullscreen mode
public class UserRegistrationValidator : IUserRegistrationValidator
{
    public void Validate(UserRegistrationRequest request)
    {
        ArgumentNullException.ThrowIfNull(request);

        ArgumentException.ThrowIfNullOrWhiteSpace(request.Username);
        ArgumentException.ThrowIfNullOrWhiteSpace(request.Password);

        if (request.Password.Length < 8)
            throw new ArgumentException("Password must be at least 8 characters.");

        if (request.Age is < 18 or > 120)
            throw new ArgumentOutOfRangeException(nameof(request.Age));

        if (!IsValidEmail(request.Email))
            throw new FormatException("Invalid email format.");
    }

    private static bool IsValidEmail(string email)
    {
        return !string.IsNullOrWhiteSpace(email)
               && email.Contains("@")
               && email.Contains(".");
    }
}
Enter fullscreen mode Exit fullscreen mode
public class UserRegistrationService
{
    private readonly IUserRegistrationValidator _validator;

    public UserRegistrationService(IUserRegistrationValidator validator)
    {
        _validator = validator;
    }

    public void RegisterUser(UserRegistrationRequest request)
    {
        _validator.Validate(request);
        Console.WriteLine("User registered successfully!");
    }
}
Enter fullscreen mode Exit fullscreen mode

Step 5: Value Objects

public sealed record Email
{
    public string Value { get; }

    public Email(string value)
    {
        if (string.IsNullOrWhiteSpace(value) ||
            !value.Contains("@") ||
            !value.Contains("."))
        {
            throw new FormatException("Invalid email format.");
        }

        Value = value;
    }

    public override string ToString() => Value;
}
Enter fullscreen mode Exit fullscreen mode
public sealed record Password
{
    public string Value { get; }

    public Password(string value)
    {
        if (string.IsNullOrWhiteSpace(value))
            throw new ArgumentException("Password is required.");

        if (value.Length < 8)
            throw new ArgumentException("Password must be at least 8 characters.");

        Value = value;
    }
}
Enter fullscreen mode Exit fullscreen mode

Step 6: Specification Pattern

public interface ISpecification<T>
{
    bool IsSatisfiedBy(T entity);
}
Enter fullscreen mode Exit fullscreen mode
public class AgeSpecification : ISpecification<int>
{
    public bool IsSatisfiedBy(int age)
    {
        return age is >= 18 and <= 120;
    }
}
Enter fullscreen mode Exit fullscreen mode

Step 7: Result Pattern

public record Result(bool Success, string Error)
{
    public static Result Ok() => new(true, string.Empty);
    public static Result Fail(string error) => new(false, error);
}
Enter fullscreen mode Exit fullscreen mode
public Result RegisterUser(UserRegistrationRequest request)
{
    if (string.IsNullOrWhiteSpace(request.Username))
        return Result.Fail("Username is required.");

    if (string.IsNullOrWhiteSpace(request.Password))
        return Result.Fail("Password is required.");

    return Result.Ok();
}
Enter fullscreen mode Exit fullscreen mode

Final Service

public class UserRegistrationService
{
    private readonly IUserRegistrationValidator _validator;

    public UserRegistrationService(IUserRegistrationValidator validator)
    {
        _validator = validator;
    }

    public void RegisterUser(UserRegistrationRequest request)
    {
        _validator.Validate(request);
        Console.WriteLine("User registered successfully!");
    }
}
Enter fullscreen mode Exit fullscreen mode

Summary

Technique Benefit
Guard Clauses Fail fast validation
DTO Cleaner input structure
Validator Separation of concerns
DI Better testability
Value Objects Stronger domain rules
Specification Pattern Flexible business rules
Result Pattern Better API/UI flow

Bonus: State Pattern Example

Initial code :

foreach (char c in input)
        {
            if (c == '[')
            {
                insideBrackets = true;
                currentValue.Clear();
            }
            else if (c == ']' && insideBrackets)
            {
                results.Add(currentValue.ToString());
                insideBrackets = false;
            }
            else if (insideBrackets)
            {
                currentValue.Append(c);
            }
        }

        return results;
Enter fullscreen mode Exit fullscreen mode
public class ParserContext
{
    public List<string> Results { get; } = new();
    public StringBuilder Current { get; } = new();
    public IParserState State { get; set; } = new OutsideState();

    public void Process(char c) => State.Handle(this, c);
}
Enter fullscreen mode Exit fullscreen mode
public interface IParserState
{
    void Handle(ParserContext ctx, char c);
}
Enter fullscreen mode Exit fullscreen mode
public class OutsideState : IParserState
{
    public void Handle(ParserContext ctx, char c)
    {
        if (c == '[')
        {
            ctx.Current.Clear();
            ctx.State = new InsideState();
        }
    }
}
Enter fullscreen mode Exit fullscreen mode
public class InsideState : IParserState
{
    public void Handle(ParserContext ctx, char c)
    {
        if (c == ']')
        {
            ctx.Results.Add(ctx.Current.ToString());
            ctx.State = new OutsideState();
        }
        else
        {
            ctx.Current.Append(c);
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Usage :

var input = "Hello [John] and [Mary]";

var context = new ParserContext();

foreach (char c in input)
{
    context.Process(c);
}

foreach (var result in context.Results)
{
    Console.WriteLine(result);
}
Enter fullscreen mode Exit fullscreen mode

Conclusion

Start simple:

  • Guard clauses first
  • Extract methods next
  • Then introduce DTOs
  • Then validators
  • Then value objects
  • Then patterns (Specification, State, etc.)

Good refactoring makes code simpler, not more complicated.

Top comments (0)