Clean Code in C# Part 6 Limits

Introduction

When developing application it is common to use third party libraries to speed up development. Some examples of back-end libraries are Entity Framework Core, Azure SDK, Redis, MongoDB, etc. According to uncle bob the integration between the developed software with libraries should be considered.

Integrating and limiting third party libraries

When importing libraries to applications many developers are trying to solve a problem according to their specific needs. However the provider of these libraries distributes code to solve the needs of a wider scope audience. In a sense that they distribute libraries, promote bug fixes, update and include new functionality. Therefore libraries, often provides more resources that developers use.

Limiting the use and exposure of third party libraries can reduce the risk of unnecessary or wrong usage of these libraries. The following sections has an example of how to prevent this and limit the use of these libraries.

Entity Framework Core

Analyze part of a public methods from the DbSet abstract class in the Entity Framework Core 6.0.1 package as displayed in the code below:

Example 1:

public abstract class DbSet<TEntity> : IQueryable<TEntity>, IInfrastructure<IServiceProvider>, IListSource
        where TEntity : class
{
   IAsyncEnumerable<TEntity> AsAsyncEnumerable().
   IQueryable<TEntity> AsQueryable();
   LocalView<TEntity> Local;
   TEntity? Find(params object?[]? keyValues);
   FindAsync(params object?[]? keyValues);
   FindAsync(object?[]? keyValues, CancellationToken cancellationToken);
   Add(TEntity entity);
   ValueTask<EntityEntry<TEntity>> AddAsync(
            TEntity entity,
            CancellationToken cancellationToken = default);
   Attach(TEntity entity);
   Remove(TEntity entity);
   Update(TEntity entity);
   //...
}

The code in Example 1 contains the interface of a couple of methods from the DbSet class. If an application needs to make use of a DbSet to manipulate a table in the database, one might see code that directly references the DbSet object as displayed here:

Example 2:

DataBaseContext DatabaseContext;
DbSet<User> DbSet = DatabaseContext.Set<User>();

DbSet.Add(user);
DatabaseContext.SaveChanges();

When the DbSet is used randomly in code the DbSet will need to be set and rules are then applied to it. A code like this should work, however its not a clean approach. When the DbSet class is exposed it might contain more functionality than one actually needs.

One way to solve this problem is to encapsulate the DbSet and create classes that exposes only what is necessary like the following:

Example 3:

public class UserData
{
    private readonly DatabaseContext _databaseContext;
    private readonly DbSet<User> _userDbSet;

    public UserData(DatabaseContext databaseContext)
    {
        _databaseContext = databaseContext;
        _userDbSet = databaseContext.Set<User>();
    }

    public int AddUser(User user)
    {
        _userDbSet.Add(user);

        return _databaseContext.SaveChanges();
    }
}

In Example 3 we see that to save information regarding the user one could use the UserData class. This approach is cleaner compared to the approach on Example 2 as it does not expose the DbSet object.

Learning through testing

Learning a new library takes time, integrating a library to existing code also takes time, upgrading a library could bring changes that introduces bugs.

One solution to explore a new libraries and their updates is through testing, uncle Bob calls this Learning through testing. Creating unit tests to explore libraries is a form of learning a library and have some sort of guarantee that if the library is updated it won't break existing code. The tests should off course have limits to them.

Conclusion

One of the main advantages of establishing limits are changes. Well developed software should provide resources to make changes with minimal cost.

When defining limits through code one should be clear of what to expose and make use of and testing should help evaluates expectations. One should avoid large sections of code with access to unnecessary third party resources. Dealing with external limitations through small sections of code that references third party libraries is an efficient way to apply limitations.

References

  1. Clean Code: A Handbook of Agile Software Craftsmanship by Robert C. Martin.

23