Writing flexible code

Writing flexible code is a never ending battle between getting features out on time, keeping code readable, and making it expandable. It easy in crunch time to rush a feature and code things strictly to get the product out, but this tends to bite us in the end. We’re also constantly learning how to do things better, and learning from mistakes we made before. So how do we keep our code maintainable? I’ will likely write more of these as I learn more, but here is what I’ve gathered from short time working. I’ll be writing more of these, going into more depth in more topics.

Abstraction

The key to writing flexible code comes down to abstraction. Any code you write should be independent and testable in a unit test. Some code may not make sense to be tested, but theoretically it should be testable. Methods and functions should generally perform one task. Classes should contain methods and properties that functionally and conceptually make sense together. Classes should also only contain methods and properties immediately related to them. Others should be be broken down through composition. I break down several important aspects of abstraction on this website in the Abstraction page.

Static properties are evil

Avoid using static properties unless absolutely necessary. Static properties can very easily break object abstraction patterns by allowing a stateless value to be used throughout an entire application. If you are writing a script that generally has one purpose, this is fine, but what if down the road you need to have differing values in different threads during the same frame of execution? Static won’t help you there.

I’ve had to write various data migration scripts and utilities, and have admittedly run into this problem multiple times. I’ll have a set of project ids that I need to migrate data to, so when I originally wrote the utility things just checked a static project id. I also didn’t have proper class abstractions, so the migrator couldn’t be created with different contexts in the same process. Due to a time constraint, I needed to process multiple projects in parallel. If I had originally written the migrator with proper object abstraction, this would’ve been simpler. Take this example:

class ProjectMigrator
{
    private long projectId;
    private List<DataObject> data;
    
    public ProjectMigrator(long projectId, List<DataObject> data) {
        this.projectId = projectId;
        this.data = data;
    }
    
    public void MigrateData() {
        // run migration code
    }
}

This would allow us to use the migrator in a powerful way like this

// Mock data
var projects = new Dictionary<Project, List<DataObject>>();

Parallel.ForEach(projects, project => {
    var migrator = new ProjectMigrator(project.Key.Id, project.Value);
    migrator.MigrateData();
})

Leveraging polymorphism

Its clear what each piece does in the above example. If you’re migrating data, you need to talk to some database at some point. You could just hard code that information in, or you could go a step further and abstract out any database or network classes into an interface. The important part is to not make your interfaces and classes too complex and specific. Here’s an example.

interface IDataBaseProvider
{
    void InsertData<T>(T data);
    T GetData<T>(long id);
    void UpdateData<T>(long id, T data);
}

interface IMariaDbProvider : IDataBaseProvider
{
}

public class MariaDbProvider : IMariaDbProvider {
    public void InsertData<T>(T data) {
        // Insert data
    }
    public T GetData<T>(long id) {
        // Get data
    }
    public void UpdateData<T>(long id, T data) {
        // Update data
    }
}

With the above database example, you’re able to define any type of data base provider. You could then use that in the data migrator class, allowing you the flexibility to seamlessly switch to a different database type given that you can implement your base class for it.

There’s obviously more to it, so if you want to be ready to hear from me, subscribe to my newsletter so you know as soon as one comes out!