Feature Switches

Continuous Deployment is arguably the holy grail of development. The ability to regularly and automatically deploy changes for an application increases team responsiveness, encourages Agile Development, and allows the end-user to provide quick feedback influencing future work.

But how do you release changes that aren’t “complete” from the business perspective? Typically, Continuous Deployment dictates small, releasable chunks of a feature that would can exist in your production environment without any negative impact. However, that’s not always the case. Maybe a feature can’t go live until a specific date, or you want to release the feature to a select set of users ahead of everyone else.

One option is to wrap the new feature in a feature switch:

if(featureIsOn){
    //Do feature logic
}

Simple right? Obviously the featureIsOn variable has to be driven by a datapoint somewhere (config file, database value, etc), but now your feature can be toggled on or off.

But what happens when logic starts weaving its way throughout a class? For example, I’ll introduce the ability to brake into my Car class:

public interface ICar {
    void Drive();
}

public class Car : ICar
{
    private void Brake() {...}
 
    public void Drive(){
        //New functionality 
        if(ObstacleDetected()) { Brake(); }
 
        var upcomingLight = GetUpcomingLight();
        if(upcomingLight != null){
            if(upcomingLight.Color == "Yellow" && CanBeatLight(upcomingLight)){
                SpeedUp(); 
            }
            else if(upcomingLight.Color == "Yellow" 
                 || upcomingLight.Color == "Red"){
                Brake();
            }
        }
 
        //Old functionality...
 
        //More new functionality
        if(ArrivedHome()) {
            Brake();
        }
    }
 
    //Other methods
}

I’m not ready for my brake logic to be introduced to everyone; I want some test drivers to vet it first. The logic is spider webbing its way through the Drive() method and it’s only a matter of time before it reaches other pieces of Car functionality, so wrapping a couple of lines in an if block isn’t an option.

Enter the Liskov Substitution Principle, which states “objects in a program should be replaceable with instances of their subtypes without altering the correctness of that program.” More specifically, we can use an interface to provide a buffer between the underlying class logic and any users of the interface.

Because the ICar interface acts as a contract (aka any implementer must have a Drive() method), we can craft a new concrete implementation which includes our new braking functionality.

public interface ICar {
    void Drive();
}

public class Car : ICar {
    public void Drive(){
        //Old functionality...
    }
 
    //Other methods
}

public class CarWithBraking : ICar {
    private void Brake() {...}
 
    public void Drive(){
        //New functionality 
        if(ObstacleDetected()) { Brake(); }
 
        var upcomingLight = GetUpcomingLight();
        if(upcomingLight != null){
            if(upcomingLight.Color == "Yellow" && CanBeatLight(upcomingLight)){
                SpeedUp(); 
            }
            else if(upcomingLight.Color == "Yellow" 
                 || upcomingLight.Color == "Red"){
                Brake();
            }
        }
 
        //Old functionality...
 
        //More new functionality
        if(ArrivedHome()) {
            Brake();
        }
    }
 
    //Other methods
}

Now whenever we need some type of car, as long as we use the ICar interface as the type, we can new up the car we need depending on the feature switch just like did in the first example.

ICar myCar;
if(featureIsOn){
    myCar = new CarWithBraking();
}
else{
    myCar = new Car();
}

Eventually, we may decide to permanently use the new feature we’ve added, at which point we’ll have to remove our feature switching in favor of the new functionality. But a little post-release cleanup is a small price to pay for deploying code quickly and limiting its visibility.

Leave a Reply

Your email address will not be published. Required fields are marked *