Switch Statements are Evil

How would you write a program that accepts command line inputs?
For example:

C:\>vl model

C:\>al repository

A common approach would be to use a switch statement to handle the commands strings: “vl” and “al”. Once the case for the command string is identified, then the arguments associated to the command would be processed in correlation.

Example:

  var someCommand = "al model";
        var commandRoot = someCommand.Split(' ');

        switch (commandRoot[0])
        {
            case "al":
                {
                    // Some logic...
                    break;
                }
        }

The problem with switch statements
Switch statements should only be used if they are operating on structures that do not have to be modified for extendibility. An example of this would be an enum structure that represents visibility states (Visible, Hidden, and Collapsed).

Example:

enum Visibility
{
    Visible,
    Hidden,
    Collapsed
}

In this case, the Visibility enum does not have many reasons to be extended based on its single responsibility of conveying the three states that this enum provides. As a result, using a switch statement for an enum that is less likely to change is low maintenance.

However attempting to use a switch statement that requires modification each time a new business rule is introduced into the system becomes problematic. Specifically, this is an anti-pattern that violates the Open/Close Principle. The Open/Close Principle states that code should be open for extensibility but closed for modification.

Returning back to the command line processing algorithm, we would have to modify the switch statement and add a new case block each time a new command gets. Thus, this code would have to be recompiled and so would all the code that depends on this code within the system that harbors it.

An alternative approach would be to implement a messaging system where observers can subscribe to a command that they’re interested in. When a command input is entered into the system, that command is then broadcasted to the subscribers of that particular command for processing.

The following is some code I wrote for practice:

Client Request:

            ExecuteCommand = new DelegateCommand(obj =>
                {
                    _subscription.SubscribeFirstPublication(Messages.COMMAND_PROCESSED, OnProcessed);

                    MessageBus.Instance.Publish(Messages.COMMAND_LINE_SUBMITTED, “addmodule my_module”);
                });

Command Dispatching:

    public class CommandLinePreprossor
    {
        static CommandLinePreprossor()
        {
            MessageBus.Instance.Subscribe(Messages.COMMAND_LINE_SUBMITTED, obj =>
                {
                    var commandLine = obj as string;

                    if (string.IsNullOrWhiteSpace(commandLine))
                    {
                        MessageBus.Instance.Publish(Messages.COMMAND_PROCESSED, CommandStatus.Failed);
                    }

                    var rootCommand = commandLine.Split(' ').First().ToLower();
                    var handled = false;

                    MessageBus.Instance.SubscribeFirstPublication(Messages.COMMAND_LINE_HANDLER_FOUND, o =>
                        {
                            handled = true;
                        });

                    MessageBus.Instance.Publish(rootCommand, commandLine);

                    if (!handled)
                    {
                        MessageBus.Instance.Publish(Messages.COMMAND_PROCESSED, new CommandContext() { Line = commandLine, Status = CommandStatus.Failed });
                    }
                });
        }
    }
}

Command Handler:

    public class AddModuleCommand : CommandBase
    {
        #region Constants
        const string ADD_MODULE_COMMAND_FULL_TEXT = "addmodule";
        const string ADD_MODULE_COMMAND_SHORT_TEXT = "am";
        #endregion

        public override void Initialize()
        {
            MessageBus.Instance.Subscribe(ADD_MODULE_COMMAND_FULL_TEXT, obj => Execute(obj as string));
            MessageBus.Instance.Subscribe(ADD_MODULE_COMMAND_SHORT_TEXT, obj => Execute(obj as string));
        }

        public void Execute(string line)
        {
            MessageBus.Instance.Publish(Messages.COMMAND_LINE_HANDLER_FOUND);

            var isValidInstruction = BaseValidator.Validate(line, ADD_MODULE_COMMAND_FULL_TEXT, ADD_MODULE_COMMAND_SHORT_TEXT);

            if (!isValidInstruction)
            {
                MessageBus.Instance.Publish(Messages.COMMAND_PROCESSED, new CommandContext() { Line = line, Status = CommandStatus.Failed });
            }
            else
            {
                var tokens = line.Split(' ');

                _services.AddModule(new Entities.Layer() { Id = tokens[1] });
                MessageBus.Instance.Publish(Messages.COMMAND_PROCESSED, new CommandContext() { Line = line, Status = CommandStatus.Succeeded });
            }
        }
    }

 

Messages:

    public class Messages
    {
        public const string COMMAND_LINE_SUBMITTED = "COMMAND_LINE_SUBMITTED";
        public const string COMMAND_PROCESSED = "COMMAND_LINE_PROCESSED";
        public const string COMMAND_LINE_HANDLER_FOUND = "COMMAND_LINE_HANDLER_FOUND";
    }

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: