VOOZH about

URL: https://dev.to/stevsharp/refactoring-a-simple-c-method-step-by-step-4kkk

⇱ Refactoring a Simple C# Method Step by Step - DEV Community


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!");
}

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(".");
}

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;
}

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!");
}

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(".");
 }
}

Service:

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

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

Step 4: Inject the Validator

public interface IUserRegistrationValidator
{
 void Validate(UserRegistrationRequest request);
}
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(".");
 }
}
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!");
 }
}

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;
}
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;
 }
}

Step 6: Specification Pattern

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

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);
}
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();
}

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!");
 }
}

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;
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);
}
public interface IParserState
{
 void Handle(ParserContext ctx, char c);
}
public class OutsideState : IParserState
{
 public void Handle(ParserContext ctx, char c)
 {
 if (c == '[')
 {
 ctx.Current.Clear();
 ctx.State = new InsideState();
 }
 }
}
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);
 }
 }
}

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);
}

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.