Home
The Spec
The Language
The Practice
The Books
Links

Code Reduction with a Dynamic Language

In this example, the same code is implemented in both C# and JavaScript to illustrate the significant code reduction possible when a scripting language is used. This code defines an object that can be used to hide specific details about database access. It hides the use of the SqlConnection and SqlCommand as well as the connection string. This object simply performs non-query commands by passing a command string.

Here's the code in C# 2.0:

using System;
using System.Data;
using System.Data.SqlClient;
public class HlhDbCommander
{
    const string ConnectionString = "Application Name='App';" +
                "Data Source='ServerName';Database='DBName';User ID='Name';" +
                "Password='Password'";
    IDbConnection TheConnection;
    IDbCommand TheCommand;
    public DbCommander(IDbConnection Connection, IDbCommand Command)
    {
        TheConnection = Connection;
        TheCommand = Command;
    }
    public DbCommander()
    {
        TheConnection = new SqlConnection();
        TheCommand = new SqlCommand();
    }
    public void Execute(string Command)
    {
        TheConnection.ConnectionString = ConnectionString;
        TheConnection.Open();
        TheCommand.Connection = TheConnection;
        TheCommand.CommandText = Command;
        TheCommand.ExecuteNonQuery();
    }
}

This code has two constructors because it has been set up to be tested without having to set up a test database. We will look at this a bit later. This isn't really a complicated program but even with something this size, JavaScript makes the task smaller.

Here is the same code in JavaScript:

function newDbCommander () {
    return {
        command: new System.Data.SqlClient.SqlCommand,
        connection: new System.Data.SqlClient.SqlConnection,
        Execute: function (text) {
            this.connection.ConnectionString = "Application Name='App';" +
                "Data Source='ServerName';Database='DBName';User ID='Name';" +
                "Password='Password'";
            this.connection.Open();
            this.command.Connection = this.connection;
            this.command.CommandText = text;
            this.command.ExecuteNonQuery();
        }
    }
}

What we see here is almost a 50% reduction in lines of code. When a larger application is considered, this can represent a very significant savings in effort both in development time and maintenance.

Where the most significant savings is seen; however, is when unit testing is involved.

Here are the lines of code required to isolate and test this unit of code in C#. It is split into three files because it represents three classes:

C# 2.0 File StubDatabaseCommand:

using System;
using System.Data;
public class StubDatabaseCommand : IDbCommand
{
    private string mCommandText;
    public bool CommandTextWasProvided = false;
    public string CommandText
    {
        get
        {
            return mCommandText;
        }
        set
        {
            CommandTextWasProvided = true;
            mCommandText = value;
        }
    }
    public bool ConnectionWasProvided = false;
    private IDbConnection mConnection;
    public System.Data.IDbConnection Connection
    {
        get
        {
            return mConnection;
        }
        set
        {
            StubDatabaseConnection StubConnection;
            StubConnection = (StubDatabaseConnection)value;
            if (!StubConnection.IsOpen) throw new Exception("An attempt was made " +
               "to set a closed connection.");
            ConnectionWasProvided = true;
            mConnection = value;
        }
    }
    public bool ExecuteNonQueryWasRun = false;
    public int ExecuteNonQuery()
    {
        if (!ConnectionWasProvided) throw new Exception("An attempt was made to " +
            "ExecuteNonQuery without a connection.");
        if (!CommandTextWasProvided) throw new Exception("An attempt was made to " +
            "ExecuteNonQuery without command text.");
        ExecuteNonQueryWasRun = true;
        return 0;
    }
    public System.Data.IDataReader ExecuteReader()
    {
        throw new NotSupportedException();
    }
    public System.Data.IDataReader ExecuteReader(System.Data.CommandBehavior behavior)
    {
        throw new NotSupportedException();
    }
    public Object ExecuteScalar()
    {
        throw new NotSupportedException();
    }
    public void Cancel()
    {
        throw new NotSupportedException();
    }
    public int CommandTimeout
    {
        get
        {
            throw new NotSupportedException();
        }
        set
        {
            throw new NotSupportedException();
        }
    }
    public System.Data.CommandType CommandType
    {
        get
        {
            throw new NotSupportedException();
        }
        set
        {
            throw new NotSupportedException();
        }
    }
    public System.Data.IDbDataParameter CreateParameter()
    {
        throw new NotSupportedException();
    }
    public System.Data.IDataReader ExecuteReader1(System.Data.CommandBehavior behavior)
    {
        throw new NotSupportedException();
    }
    public System.Data.IDataParameterCollection Parameters
    {
        get
        {
            throw new NotSupportedException();
        }
    }
    public void Prepare()
    {
        throw new NotSupportedException();
    }
    public System.Data.IDbTransaction Transaction
    {
        get
        {
            throw new NotSupportedException();
        }
        set
        {
            throw new NotSupportedException();
        }
    }
    public System.Data.UpdateRowSource UpdatedRowSource
    {
        get
        {
            throw new NotSupportedException();
        }
        set
        {
            throw new NotSupportedException();
        }
    }
    public void Dispose()
    {
        throw new NotSupportedException();
    }
}

C# 2.0 File StubDatabaseConnection:

using System;
using System.Data;
public class StubDatabaseConnection : IDbConnection
{
    public bool IsOpen = false;
    private string mConnectionString;
    public void Close()
    {
        IsOpen = false;
    }
    public string ConnectionString
    {
        get
        {
            return mConnectionString;
        }
        set
        {
            mConnectionString = value;
        }
    }
    public void Open()
    {
        if (mConnectionString == "")
        {
            throw new Exception("No connection string was assigned before " +
                "opening the connection.");
        }
        IsOpen = true;
    }
    public System.Data.IDbTransaction BeginTransaction()
    {
        throw new NotSupportedException();
    }
    public System.Data.IDbTransaction BeginTransaction(System.Data.IsolationLevel il)
    {
        throw new NotSupportedException();
    }
    public void ChangeDatabase(string databaseName)
    {
        throw new NotSupportedException();
    }
    public int ConnectionTimeout
    {
        get
        {
            throw new NotSupportedException();
        }
    }
    public System.Data.IDbCommand CreateCommand()
    {
        throw new NotSupportedException();
    }
    public string Database
    {
        get
        {
            throw new NotSupportedException();
        }
    }
    public System.Data.ConnectionState State
    {
        get
        {
            throw new NotSupportedException();
        }
    }
    public void Dispose()
    {
        throw new NotSupportedException();
    }
}

And finally the C# 2.0 NUnit test:

using System;
using NUnit.Framework;
using System.Data;
[TestFixture]
public class TestTheDBCommander
{
    public IDbCommand TheStubCommand;
    public IDbConnection TheStubConnection;
    public string ExpectedConnectionString;
    public string ActualConnectionString;
    public DbCommander Commander;
    [SetUp]
    public void SetUp()
    {
        TheStubCommand = new StubDatabaseCommand();
        TheStubConnection = new StubDatabaseConnection();
        ExpectedConnectionString = "Application Name='App';" +
                "Data Source='ServerName';Database='DBName';User ID='Name';" +
                "Password='Password'";
        Commander = new DbCommander(TheStubConnection, TheStubCommand);

    }
    [Test]
    public void TestCommandExecution()
    {
        Commander.Execute("Some Command Text");
        Assert.AreEqual(ExpectedConnectionString,
            TheStubConnection.ConnectionString, "Connection String");
        Assert.AreEqual("Some Command Text", TheStubCommand.CommandText, "Command Text");
        Assert.IsTrue(((StubDatabaseCommand)TheStubCommand).ExecuteNonQueryWasRun, "Executed");
    }
}

As you can see, it is quite a chore to implement framework interfaces. Even if you don't use the entire interface for the test, strong typing requires that you implement it entirely. This is where scripting languages really shine. This same test can be done in JavaScript using JSNUnit test framework like this:

function registerAllTests() {
    newTest("Test the database commander").Execute = function() {
        var ExpectedConnectionString = "Application Name='App';" +
                "Data Source='ServerName';Database='DBName';User ID='Name';" +
                "Password='Password'";
                
        var Commander = newCommander();
            
        this.Assert(Commander.connection instanceof System.Data.SqlClient.SqlConnection, 
            "connection reference");
        this.Assert(Commander.command instanceof System.Data.SqlClient.SqlCommand, 
            "command reference");
            
        Commander.connection = {
            ConnectionString: "",
            isOpen: false,
            Open: function () {
                if (this.ConnectionString == "") {
                    throw new Exception("No connection string was assigned before " +
                        "opening the connection.");
                }
                this.isOpen = true;
            }
        }
        
        Commander.command = {
            executeNonQueryWasRun: false,
            ExecuteNonQuery: function () {
                if (!this.Connection) {
                    throw new Exception("No Connection was provided to the command.");
                }
                if (!this.Connection.isOpen) {
                    throw new Exception("The connection isn't open.");
                }
                this.executeNonQueryWasRun = true;
            }
        }
        
        Commander.Execute("Some Command Text");
        
        this.AreEqual(ExpectedConnectionString, Commander.connection.ConnectionString, 
            "Connection String");
        this.AreEqual("Some Command Text", Commander.command.CommandText, "Command Text");
        this.Assert(Commander.command.executeNonQueryWasRun, "command was executed");
    }
}
var allTests = registerAllTests();

This represents an 80% reduction in lines of code. It is true that much of this code can be generated by tools, but that means that tools must be purchased, learned and maintained in order to do this code generation. JavaScript doesn’t require any other tool.

Over all, the code reduction when using JavaScript both to unit test and code is about 25% of that required by C# 2.0.

The interface definition required for the stubbing of the C# 2.0 example makes it painfully clear that the issue is "strong typing". Because C# 2.0 is a system language for building systems "from the ground up", typing is enforced without exception by the compiler. Scripting languages, since they are used to put pieces together, usually do not enforce typing but treat interfaces implicitly and check them at run time.

If you would like to try some testing in a dynamic language, you can do it now, using JSNUnit with ASP .NET.


Troy Taft is the Principal Consultant and founder of Troy Taft Consulting, a firm specializing in high value software development. He also authors a free monthly newsletter called Software Matters.

Copyright 2007 Troy Taft All rights reserved, you may print this article for your personal use.