Skip to content

Best Practices

Overview

This section contains recommended best practices for using the TinyResult library.

Return Values

Always Return Results

// Bad
public User GetUser(int id)
{
    var user = _repository.GetUser(id);
    if (user == null)
        throw new UserNotFoundException();
    return user;
}

// Good
public Result<User> GetUser(int id)
{
    var user = _repository.GetUser(id);
    if (user == null)
        return Result<User>.Failure("User not found");
    return Result<User>.Success(user);
}

Convert Exceptions to Results

// Bad
public Result<User> GetUser(int id)
{
    try
    {
        var user = _repository.GetUser(id);
        return Result<User>.Success(user);
    }
    catch (Exception ex)
    {
        throw;
    }
}

// Good
public Result<User> GetUser(int id)
{
    return Result<User>.Try(() => _repository.GetUser(id));
}

Error Handling

Use Custom Error Codes

// Bad
return Result<User>.Failure("User not found");

// Good
return Result<User>.Failure(ErrorCode.NotFound, "User not found");

Use Error Metadata

// Bad
return Result<User>.Failure("Invalid input");

// Good
return Result<User>.Failure(
    ErrorCode.ValidationError,
    "Invalid input",
    new Dictionary<string, object>
    {
        { "Field", "Email" },
        { "Value", email }
    }
);

Validation

Combine Validation Results

public ValidationResult ValidateUser(User user)
{
    return ValidationResult.Create()
        .AddError("Name", "Name is required")
        .Combine(ValidateEmail(user.Email))
        .Combine(ValidatePassword(user.Password));
}

Keep Validation Rules Separate

private ValidationResult ValidateEmail(string email)
{
    var result = ValidationResult.Create();

    if (string.IsNullOrEmpty(email))
        result = result.AddError("Email", "Email is required");
    else if (!IsValidEmail(email))
        result = result.AddError("Email", "Email is invalid");

    return result;
}

Async Operations

Use Async Methods Correctly

// Bad
public async Task<Result<User>> GetUserAsync(int id)
{
    var user = await _repository.GetUserAsync(id);
    if (user == null)
        return Result<User>.Failure("User not found");
    return Result<User>.Success(user);
}

// Good
public Task<Result<User>> GetUserAsync(int id)
{
    return Result<User>.TryAsync(() => _repository.GetUserAsync(id));
}

Use Async Chaining

public async Task<Result<User>> ProcessUserAsync(int id)
{
    return await Result<User>.TryAsync(() => _repository.GetUserAsync(id))
        .BindAsync(user => UpdateUserAsync(user))
        .MapAsync(user => TransformUser(user));
}

Performance

Avoid Unnecessary Result Transformations

// Bad
return Result<User>.Success(user).Map(u => u.Name);

// Good
return Result<string>.Success(user.Name);

Use Large Objects Carefully

// Bad
return Result<byte[]>.Success(largeFile);

// Good
return Result<Stream>.Success(fileStream);

Testing

Test Results Correctly

[Fact]
public void GetUser_WhenUserExists_ReturnsSuccess()
{
    // Arrange
    var user = new User { Id = 1, Name = "Test" };
    _repository.Setup(r => r.GetUser(1)).Returns(user);

    // Act
    var result = _service.GetUser(1);

    // Assert
    result.IsSuccess.Should().BeTrue();
    result.Value.Should().BeEquivalentTo(user);
}

[Fact]
public void GetUser_WhenUserNotFound_ReturnsFailure()
{
    // Arrange
    _repository.Setup(r => r.GetUser(1)).Returns((User)null);

    // Act
    var result = _service.GetUser(1);

    // Assert
    result.IsFailure.Should().BeTrue();
    result.Error.Message.Should().Be("User not found");
}

Next Steps