Thursday, June 17, 2010

Optimizing C# code

Let us try to understand what is called optimizing a code.
Write a simple logging function which shall
write the given string message to a file.

Step #1 - Write the Logger class with a function which will open/create the text file and append the string message along with time stamp and close the file.

using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Diagnostics;
using System.Threading;

namespace LoggerTimeElapsed
{

    class Logger
    {
        public string FilePath { get; set; }
        private object m_objLocker;
        public Logger()
        {
            m_objLocker = new object();
            FilePath = @"d:\log.txt";
        }
        //make sure the StreamWriter is closed
        //make sure thr Monitor is exit
        public bool WriteToFile(string strMessage)
        {
            bool blnStatus = false;
            StreamWriter objStreamWriter = null;
            if (strMessage != string.Empty && strMessage != null)
            {
                try
                {
                    Monitor.Enter(m_objLocker);
                    objStreamWriter = new StreamWriter(FilePath, true);
                    //logs only the time and not the date
                    objStreamWriter.WriteLine(
                        DateTime.Now.TimeOfDay + " - " + strMessage
                        );
                    objStreamWriter.Close();
                    Monitor.Exit(m_objLocker);
                    blnStatus = true;
                }
                catch (Exception objEx)
                {
                    //use messagebox if winforms application
                    Console.WriteLine(objEx.StackTrace);
                    if (objStreamWriter != null)
                    {
                        objStreamWriter.Close();
                    }
                    Monitor.Exit(m_objLocker);
                }
                finally
                {
                }
            }
            return blnStatus;
        }
    }
    class Program
    {
        static void Main(string[] args)
        {
            Logger objLogger = null;
            Stopwatch objSW = null;
            int iCount = 100;
            objSW = new Stopwatch();
            objLogger = new Logger();
            objSW.Reset();
            objSW.Start();
            for (int i = 0; i < iCount; i++)
            {
                objLogger.WriteToFile("Unoptimized" + i.ToString());
            }
            objSW.Stop();
            Console.WriteLine("WriteToFile - TimeElapsed");
            Console.WriteLine("TimeElapsed (Stopwatch float):{0}ms",
                objSW.Elapsed.TotalMilliseconds);
            Console.WriteLine("TimeElapsed (Stopwatch rounded):{0}ms",
                objSW.ElapsedMilliseconds);
            Console.ReadLine();
        }
    }
}
Step #2 - Measure the time taken by the function to log 100 messages to the text file.In the above code Stopwatch class is used for measuring the time.
Output:
WriteToFile - TimeElapsed
TimeElapsed (Stopwatch float):319.2352ms
TimeElapsed (Stopwatch rounded):319ms
Step #3 - If the time taken by the function does not meet the throughput requirement, then try to optimize the code. So let us now change the Logger class such that, the text file is opened only once and closed only once.
using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Diagnostics;
using System.Threading;

namespace LoggerTimeElapsed
{

    class Logger
    {
        public string FilePath { get; set; }
        private StreamWriter m_objStreamWriter = null;
        private object m_objLocker;
        public Logger()
        {
            m_objLocker = new object();
            FilePath = @"d:\log.txt";
        }
        public bool Open()
        {
            bool blnStatus = false;
            try
            {
                m_objStreamWriter = new StreamWriter(FilePath, true);
                blnStatus = true;
            }
            catch (Exception objEx)
            {
                Console.WriteLine(objEx.StackTrace);
            }
            finally
            {
            }
            return blnStatus;
        }
        public bool Close()
        {
            bool blnStatus = false;
            try
            {
                if (m_objStreamWriter != null)
                {
                    m_objStreamWriter.Close();
                    blnStatus = true;
                }
            }
            catch (Exception objEx)
            {
                Console.WriteLine(objEx.StackTrace);
            }
            finally
            {
            }
            return blnStatus;
        }
        //make sure Open is called before calling WriteToFile
        //make sure Close is called once all logging is done
        public bool WriteToFile(string strMessage)
        {
            bool blnStatus = false;
            if (strMessage != string.Empty && strMessage != null)
            {
                try
                {
                    lock (m_objLocker)
                    {
                        if (m_objStreamWriter != null)
                        {
                            //logs only the time and not the date
                            m_objStreamWriter.WriteLine(
                                DateTime.Now.TimeOfDay
                                + " - "
                                + strMessage
                                );
                            blnStatus = true;
                        }

                    }
                }
                catch (Exception objEx)
                {
                    //use messagebox if winforms application
                    Console.WriteLine(objEx.StackTrace);
                }
                finally
                {
                }
            }
            return blnStatus;
        }
    }
    class Program
    {
        static void Main(string[] args)
        {
            Logger objLogger = null;
            Stopwatch objSW = null;
            int iCount = 100;
            objSW = new Stopwatch();
            objLogger = new Logger();
            objLogger.Open();
            objSW.Reset();
            objSW.Start();
            for (int i = 0; i < iCount; i++)
            {
               objLogger.WriteToFile("Optimized" + i.ToString());
            }
            objSW.Stop();
            objLogger.Close();
            Console.WriteLine("WriteToFile - TimeElapsed");
            Console.WriteLine("TimeElapsed (Stopwatch float):{0}ms",
                objSW.Elapsed.TotalMilliseconds);
            Console.WriteLine("TimeElapsed (Stopwatch rounded):{0}ms",
                objSW.ElapsedMilliseconds);
            Console.ReadLine();
        }
    }
}
Step #4 - Measure the time take by the Logger class to log 100 message to the text file.In the above code Stopwatch class is used for measuring the time.
Output:
WriteToFile - TimeElapsed
TimeElapsed (Stopwatch float):4.6166ms
TimeElapsed (Stopwatch rounded):4ms
Step #5 - Analyze the advantages and disadvantages of different approaches and decide the best approach.

Write the code → Measure → Optimize the code → Measure

C# measurement tools

There are two important factor which has to be measured to identify the improvement in the code
  • Time taken to complete some operation
  • Memory usage of the application
Below is the code for measuring memory consumption.
using System;
using System.Collections.Generic;
using System.Text;
using System.Diagnostics;

namespace StructMemoryUsage
{
    struct EmployeeStruct
    {
        public string Name;
        public int EmpId;
    }

    class Program
    {
        static void Main(string[] args)
        {
            long lngGCMemStart = 0;
            long lngGCMemEnd = 0;
            long lngProcessMemStart = 0;
            long lngProcessMemEnd = 0;

            //employee as struct
            EmployeeStruct[] objEmployee;
            int iCount;
            iCount = 100000;

            Process objProcess = Process.GetCurrentProcess();
            //Measure starting point memory
            lngGCMemStart = System.GC.GetTotalMemory(true);
            lngProcessMemStart = objProcess.PrivateMemorySize64;

            objEmployee = new EmployeeStruct[iCount];

            //Measure memory after allocating
            lngGCMemEnd = System.GC.GetTotalMemory(true);
            lngProcessMemEnd = objProcess.PrivateMemorySize64;

            for (int i = 0; i < iCount; i++)
            {
                objEmployee[i].Name = "Emp" + i.ToString();
                objEmployee[i].EmpId = i;
            }
            Console.WriteLine("Struct memory usage.");
            //memoryusage difference
            Console.WriteLine("GC Memory Use:{0} (bytes)",
                lngGCMemEnd - lngGCMemStart);
            Console.WriteLine("Process Memory Use:{0} (bytes)",
                lngProcessMemEnd - lngProcessMemStart);

            Console.WriteLine("GC Memory Start:{0} (bytes)",
                lngGCMemStart.ToString());
            Console.WriteLine("Process Memory Start:{0} (bytes)",
                lngProcessMemStart.ToString());

            Console.WriteLine("GC Memory End:{0} (bytes)",
                lngGCMemEnd.ToString());
            Console.WriteLine("Process Memory End:{0} (bytes)",
                lngProcessMemEnd.ToString());

            Console.ReadLine();
        }
    }
}
Output:
Struct memory usage.
GC Memory Use:802380 (bytes)
Process Memory Use:0 (bytes)
GC Memory Start:126592 (bytes)
Process Memory Start:8937472 (bytes)
GC Memory End:928972 (bytes)
Process Memory End:8937472 (bytes)

Note: Alternatively use perfmon->Add Counters->.NET CLR Memory counters can be used for viewing the memory consumption of selected .NET process.

Below is the code for measuring the time.
using System;
using System.Collections.Generic;
using System.Text;
using System.Diagnostics;

namespace MeasuringTime
{
    class Program
    {
        static void Main(string[] args)
        {
            long lngStartTime = 0;
            long lngEndTime = 0;
            DateTime dtStartTime;
            TimeSpan tsTimeDiff;
            long lngSWFrequency=0;
            Stopwatch objSW;// Only from .Net 2.0

            //If the computer is kept running continuously
            //for 49 days then the Environment.TickCount would
            //start counting backwards
            //So the most significant bit is extracted
            lngStartTime = Environment.TickCount
                & Int32.MaxValue;
            System.Threading.Thread.Sleep(5000);
            lngEndTime = Environment.TickCount
                & Int32.MaxValue;

            Console.WriteLine("TimeElapsed (TickCount):{0}ms",
                lngEndTime - lngStartTime);
            //Resolution of DateTime.Now is 10+ milliseconds
            dtStartTime = DateTime.Now;
            System.Threading.Thread.Sleep(5000);
            tsTimeDiff = DateTime.Now - dtStartTime;


            Console.WriteLine("TimeElapsed (DateTime):{0}ms",
                tsTimeDiff.TotalMilliseconds);
            //Stopwatch uses the hardware timer if it
            //highresolution timer or else uses the DateTime.Now
            //To check the availability of the high
            //resolution timer use IsHighResolution property
            if (true == Stopwatch.IsHighResolution)
            {
                Console.WriteLine(
                    "HighResolutionTimer is present"
                    );
            }
            else
            {
                Console.WriteLine(
                    "HighResolutionTimer is absent"
                    );
            }

            lngSWFrequency = Stopwatch.Frequency;
            Console.WriteLine(
                "Timer frequency in ticks/second:{0}",
                lngSWFrequency);
            objSW = Stopwatch.StartNew();
            System.Threading.Thread.Sleep(5000);
            objSW.Stop();
            Console.WriteLine(
                "TimeElapsed (Stopwatch float):{0}ms",
                objSW.Elapsed.TotalMilliseconds
                );
            Console.WriteLine(
                "TimeElapsed (Stopwatch rounded):{0}ms",
                objSW.ElapsedMilliseconds
                );

            Console.ReadLine();

        }
    }
}

Output:
TimeElapsed (TickCount):4992ms
TimeElapsed (DateTime):5002ms
HighResolutionTimer is present
Timer frequency in ticks/second:14318180
TimeElapsed (Stopwatch float):5000.7317ms
TimeElapsed (Stopwatch rounded):5000ms

Note: Profiling tools can be used for the measuring time and memory leak.

How to write a program?

Program accepts input, performs the function and produces the output.

Accept inputs → Perform the function (logic) → Produce output

Let us try to write a simple calculator. The calculator has to support addition, subtraction, multiplication and division and also display the result.
Try to write the calculator program on a paper by following the steps below
Step #1 - Identify the user inputs.
number1
number2
operation
Step #2 - Identify the output.
result
Step #3 - Write the logic.
if operation is "add"
{
    number1+number2=result
}
if operation is "sub"
{
    number1-number2=result
}
if operation is "mul"
{
    number1*number2=result
}
if operation is "div"
{
    number1/number2=result
}
Step #4 - Display the output (result).
Now try to execute the program on the paper by following the steps
Step #1 - Accept user inputs
number1=8
number2=7
operation="sub"
Step #2 - Nothing to do in this step
Step #3 - Execute the logic
if operation is "add"
{
    number1+number2=result
}
if operation is "sub"
{
    number1-number2=result
    8 - 7 = 1(result)
}
if operation is "mul"
{
    number1*number2=result
}
if operation is "div"
{
    number1/number2=result
}
Step #4 - Display the result
Result is 1
The program works fine on the paper.
C# example- Now open Microsoft Visual Studio
or Microsoft Visual C# 2008 Express Edition and create a
console application (File → New Project → Console Application) and try to write the program as below.
using System;

namespace calculator
{
    class Program
    {
        static void Main(string[] args)
        {
            //Identify the user inputs
            float Number1;
            float Number2;
            string Operation;

            //Identify the output
            float Result=0;

            //Accept user inputs
            //can write code to get the input through the console or user interface
            Number1=8;
            Number2=7;
            Operation = "sub";

            //Write the logic
            if ("add" == Operation)
            {
            Result = Number1+Number2;
            }
            if ("sub" == Operation)
            {
            Result = Number1 - Number2;
            }
            if ("mul" == Operation)
            {
            Result = Number1 * Number2;
            }
            if ("div" == Operation)
            {
            Result = Number1 / Number2;
            }

            //Display the output
            Console.Write("Result is {0}",Result);

            //To make the console wait for the user to read the result
            Console.ReadLine();
        }
    }

}
Output:
Result is 1
The above program works fine. So now try to write the logic
or function part as class as below.
using System;

namespace ConsoleApplication6
{
    class Calc//also called as type
    {
        public Calc()//constructor
        {

        }
        //Logic as function
        public float Add(float Num1, float Num2)//function with arguments/inputs
        {
            float Result;//local variable
            Result = Num1 + Num2;
            return Result;//return Result/output
        }
        //Logic as function
        public float Sub(float Num1, float Num2)//function with arguments/inputs
        {
            float Result;//local variable
            Result = Num1 - Num2;
            return Result;//return Result/output

        }
        //Logic as function
        public float Mul(float Num1, float Num2)//function with arguments/inputs
        {
            float Result;//local variable
            Result = Num1 * Num2;
            return Result;//return Result/output
        }
        //Logic as function
        public float Div(float Num1, float Num2)//function with arguments/inputs
        {
            float Result;//local variable
            Result = Num1 / Num2;
            return Result;//return Result/output
        }
    }
    class Program
    {
        static void Main(string[] args)
        {
            //Identify the user inputs
            float Number1;
            float Number2;
            string Operation;

            //Identify the output
            float Result = 0;

            //Accept user inputs
            //can write code to get the input through the console or user interface
            Number1 = 8;
            Number2 = 7;
            Operation = "sub";

            Calc Calc1 = new Calc();//create new Calc

            //Display the inputs
            Console.WriteLine("Number1 is {0},Number2 is {1}", Number1, Number2);
            //Write the logic
            if ("add" == Operation)
            {
                //Display the operation
                Console.WriteLine("Operation is Add");
                Result=Calc1.Add(Number1, Number2);
            }
            if ("sub" == Operation)
            {
                //Display the operation
                Console.WriteLine("Operation is Sub");
                Result = Calc1.Sub(Number1, Number2);
            }
            if ("mul" == Operation)
            {
                //Display the operation
                Console.WriteLine("Operation is Mul");
                Result = Calc1.Mul(Number1, Number2);
            }
            if ("div" == Operation)
            {
                //Display the operation
                Console.WriteLine("Operation is Div");
                Result = Calc1.Div(Number1, Number2);
            }

            //Display the output
            Console.Write("Result is {0}", Result);

            //To make the console wait for the user to read the result
            Console.ReadLine();



        }
    }
}
Output:
Number1 is 8,Number2 is 7
Operation is Sub
Result is 1
JavaScript example- Now let us write the same
program in JavaScript and dhtml, so open a notepad and try to
write the program as below and save it as .html file.
<html>
    <head>
        <title>Calculator</title>
        <style>
        body{
        background:blue;
        color:white;
        }
        td{
        background:white;
        color:maroon;
        font-family:verdana;
        font-size:15px;
        }
        #cssresult{
        background:fuchsia;
        color:lime;
        }
        .cssbutton{
        background:yellow;
        }
        </style>
        <script>
        function calc(op)
        {
            //Identify the user inputs
            var number1;
            var number2;
            var operation;
            //Identify the output
            var result;
            //Accept user inputs
            number1 = (parseFloat)(document.getElementsByName("txtnum1")[0].value);
            number2 = (parseFloat)(document.getElementsByName("txtnum2")[0].value);
            operation=op;
            //Write the logic
            if(operation == "add")
            {
                result = number1 + number2;
                //Display the result
                document.getElementsByName("txtresult")[0].value=result;
                //Display the result (dhtml)
                document.getElementById("divResult")
                .innerHTML="<b>Result is "+result+"</b>";
                alert("Result is "+result);
            }
            if(operation == "sub")
            {
                result = number1 - number2;
                //Display the result
                document.getElementsByName("txtresult")[0].value=result;
                //Display the result (dhtml)
                document.getElementById("divResult")
                .innerHTML="<b>Result is "+result+"</b>";
                alert("Result is "+result);
            }
            if(operation == "mul")
            {
                result = number1 * number2;
                //Display the result
                document.getElementsByName("txtresult")[0].value=result;
                //Display the result (dhtml)
                document.getElementById("divResult")
                .innerHTML="<b>Result is "+result+"</b>";
                alert("Result is "+result);
            }
            if(operation == "div")
            {
                result = number1 / number2;
                //Display the result
                document.getElementsByName("txtresult")[0].value=result;
                //Display the result (dhtml)
                document.getElementById("divResult")
                .innerHTML="<b>Result is "+result+"</b>";
                alert("Result is "+result);
            }
            if(operation == "clear")
            {
                document.getElementsByName("txtnum1")[0].value="";
                document.getElementsByName("txtnum2")[0].value="";
                document.getElementsByName("txtresult")[0].value="";
                document.getElementById("divResult").innerHTML="";
            }
        }
        </script>
    </head>
    <body >
        Calculator
        <table border="2">
        <tr>
        <td>Number 1</td>
        <td><input type="textbox" name="txtnum1"></td>
        <td class="cssbutton">
            <input type="button" value="Add" onclick="calc('add')">
        </td>
        </tr>
        <tr>
        <td>Number 2</td>
        <td><input type="textbox" name="txtnum2"></td>
        <td class="cssbutton">
            <input type="button" value="Sub" onclick="calc('sub')">
        </td>
        </tr>
        <tr >
        <td>Result</td>
        <td id="cssresult"><input type="textbox" name="txtresult"></td>
        <td class="cssbutton">
            <input type="button" value="Mul" onclick="calc('mul')">
        </td>
        </tr>
        <tr>
        <td colspan="2" class="cssbutton">
            <input type=button value="Clear" onclick="calc('clear')">
        </td>
        <td class="cssbutton">
            <input type="button" value="Div" onclick="calc('div')">
        </td>
        </tr>
        </table>
        <div id="divResult"></div>
    </body>
</html>

Output:
Result is 1

Note: Extend the above program to accept input through the user interface and support more features.

Happy programming!

Note: Please read the guidelines listed in this website for efficient programming.

Software requirements

Requirements of a software form the foundation of the software based product.

Functional and non-functional requirements are derived from the "voice of the customer" (VOC), documents (artifacts) and similar products etc. Success of the software product mainly depends on the non-functional requirements like
  • performance – time taken to perform an operation (throughput) and memory usage
  • scalability - ability to easily expanded or upgraded on demand
  • usability – ease of use and intuitiveness
  • architecture – standalone or client server
  • reliability - ability to yield same result on repeated trials
  • security – ability to protect the system from intruders and protecting the privacy of the users of the software product
  • etc.
and the productivity of software development mainly depends on the non-functional requirements like
  • maintainability (includes extensibility, testability)
  • etc.
Make sure the derived requirements are
  • simple
  • not ambiguous
  • testable
Following are the minimum set of attributes of a requirement
  1. Tag – Requirement tag will be used for traceability (e.g. R1, R2, REQ1, REQ10, FR1, FR10, etc.)
  2. Description

Compact xml

Xml is one of most popular format for storing and exchanging data.
For example if we want to store address in an xml file then it would look some thing as below
<AddressBook>
  <Contact>
    <Name>Person1</Name>
    <Address1><![CDATA[Number101]]></Address1>
    <Address2><![CDATA[Address501]]></Address2>
    <Zip>50001</Zip>
    <Phone><![CDATA[999991]]></Phone>
 </Contact>
</AddressBook>
If we use the xml format to store the data and exchange data in an internal or intranet application then consider changing the format compact as below
<AB>
  <C>
    <N>Person1</N>
    <A1><![CDATA[Number101]]></A1>
    <A2><![CDATA[Address501]]></A2>
    <Z>50001</Z>
    <P><![CDATA[999991]]></P>
 </C>
</AB>
Keeping the tag name shot reduces the overall xml file size. Also have bandwidth advantage if transfered across the network.
Create an xml file with normal tag names with 10000 Contact nodes. Create another xml file with short tag names with 10000 C nodes.
Output:
The size of the xml file with normal tag name is approximately 2.08 MB.
The size of the xml file with short tag name is approximately 1.57 MB.
Below is an example of xml file with short attribute name.
<AB>
  <C N="Person1" Z="50001">
    <A1><![CDATA[Number101]]></A1>
    <A2><![CDATA[Address501]]></A2>
    <P><![CDATA[999991]]></P>
 </C>
</AB>

Rule: Consider short names for xml tags (element name or node name) and attributes.

Return variable

Initialize the failure status value to return variable. So that even if the success value is not set to the return value, the call would always fail and the developer can debug and fix the issue during unit testing or development.

C# example- In the below example blnStatus is initialized to false.
//should not allow empty string or null string
public bool SetName(string strName)
{
    bool blnStatus = false;
    if (strName != string.Empty && strName != null)
    {
        Name = strName;
        blnStatus = true;
    }
    return blnStatus;
}

Rule: Initialize the return variable to failure status value.

Rule: Make sure return variable is set to success status value at the right place.

Single return

Avoid multiple return statements.
C# example- Code with multiple return statement.
//should not allow empty string or null string
public bool SetName(string strName)
{

    if (strName != string.Empty && strName != null)
    {
        m_strName = strName;
        return true;
    }
    return false;

}

C# example- Code with single return statement.
//should not allow empty string or null string
public bool SetName(string strName)
{
    bool blnStatus = false;
    if (strName != string.Empty && strName != null)
    {
        m_strName = strName;
        blnStatus = true;
    }
    return blnStatus;
}

Rule: Have single return statement.

Thread safety

If you are developing a multi threaded application. Make sure you code is thread safe. For better performance try to protect only the resource being accessed for reading or writing across threads and some lines of code.

C#- Make sure the mutex, monito or semaphore, if used, are released properly.

Note: A semaphore with a capacity of one is similar to mutex or lock. Semaphore is thread-agnostic because it has no owner. Any thread can call release a semaphore, but with lock and mutex, only the thread that acquired the resource can release it.


C# example- Below Logger class covers using lock and monitor, releasing monitor and protecting only the resource being shared across threads.
class Logger
{
    public string FilePath { get; set; }
    private StreamWriter m_objStreamWriter = null;
    private object m_objLocker;
    public Logger()
    {
        m_objLocker=new object();
        FilePath = @"d:\log.txt";
    }
    public bool Open()
    {
        bool blnStatus = false;
        try
        {
            m_objStreamWriter = new StreamWriter(FilePath, true);
            blnStatus = true;
        }
        catch (Exception objEx)
        {
            Console.WriteLine(objEx.StackTrace);
        }
        finally
        {
        }
        return blnStatus;
    }
    public bool Close()
    {
        bool blnStatus = false;
        try
        {
            if (m_objStreamWriter != null)
            {
                m_objStreamWriter.Close();
                blnStatus = true;
            }
        }
        catch (Exception objEx)
        {
            Console.WriteLine(objEx.StackTrace);
        }
        finally
        {
        }
        return blnStatus;
    }
    //make sure Open is called before calling WriteToFile
    //make sure Close is called once all logging is done
    public bool WriteToFile(string strMessage)
    {
        bool blnStatus = false;
        if (strMessage != string.Empty && strMessage != null)
        {
            try
            {
                lock (m_objLocker)
                {
                    if (m_objStreamWriter != null)
                    {
                        //logs only the time and not the date
                        m_objStreamWriter.WriteLine(
                            DateTime.Now.TimeOfDay
                            + " - "
                            + strMessage
                            );
                        blnStatus = true;
                    }

                }
            }
            catch (Exception objEx)
            {
                //use messagebox if winforms application
                Console.WriteLine(objEx.StackTrace);
            }
            finally
            {
            }
        }
        return blnStatus;
    }
    //make sure the StreamWriter is closed
    //make sure thr Monitor is exit
    public bool WriteToFile_Local(string strMessage)
    {
        bool blnStatus = false;
        StreamWriter objStreamWriter = null;
        if (strMessage != string.Empty && strMessage != null)
        {
            try
            {
                Monitor.Enter(m_objLocker);
                objStreamWriter = new StreamWriter(FilePath,
                    true);
                //logs only the time and not the date
                objStreamWriter.WriteLine(
                    DateTime.Now.TimeOfDay + " - " + strMessage
                    );
                objStreamWriter.Close();
                Monitor.Exit(m_objLocker);
                blnStatus = true;
            }
            catch (Exception objEx)
            {
                //use messagebox if winforms application
                Console.WriteLine(objEx.StackTrace);
                if (objStreamWriter != null)
                {
                   objStreamWriter.Close();
                }
                Monitor.Exit(m_objLocker);
            }
            finally
            {
            }
        }
        return blnStatus;
    }
    //exception has to be handled by the caller
    //can not be unit tested using UnitTestingTool class
    public void WriteToFile_Local_Using(string strMessage)
    {
        StreamWriter objStreamWriter = null;
        if (strMessage != string.Empty && strMessage != null)
        {
            //private member variable
            lock (m_objLocker)
            {
                using (objStreamWriter =
                    new StreamWriter(FilePath, true))
                {
                    //logs only the time and not the date
                    objStreamWriter.WriteLine(
                        DateTime.Now.TimeOfDay + " - " +
                        strMessage
                        );
                }
            }
        }
    }
}

Rule: Write thread safe code if your application is multi threaded.

Rule: Protecting only the resource being shared across threads or processes.

Rule: Releasing the mutex, monitor or semaphore if used.

Release resources

Make sure you release all the used resource. Below are some of the examples
  • closing the database connection
  • closing the file handle
  • closing the communication port (RS232, Socket, USB, UDP, etc)
C#- If you are using try catch block, make sure you have
the finally block to release the resources. In some case the resource may
have to be released in catch block. Otherwise the “Using”
(gets compiled to try/finally block) block can be used.

C# example- Below code has StreamWriter and try/catch/finally block.
//make sure the StreamWriter is closed
//make sure thr Monitor is exit
public bool WriteToFile_Local(string strMessage)
{
    bool blnStatus = false;
    StreamWriter objStreamWriter = null;
    if (strMessage != string.Empty && strMessage != null)
    {
        try
        {
            Monitor.Enter(m_objLocker);
            objStreamWriter = new StreamWriter(FilePath, true);
            //logs only the time and not the date
            objStreamWriter.WriteLine(
                DateTime.Now.TimeOfDay + " - " + strMessage
                );
            objStreamWriter.Close();
            Monitor.Exit(m_objLocker);
            blnStatus = true;
        }
        catch (Exception objEx)
        {
            //use messagebox if winforms application
            Console.WriteLine(objEx.StackTrace);
            if (objStreamWriter != null)
            {
               objStreamWriter.Close();
            }
            Monitor.Exit(m_objLocker);
        }
        finally
        {
        }
    }
    return blnStatus;
}

C# example- Below code has StreamWriter and Using block.
//exception has to be handled by the caller
//can not be unit tested using UnitTestingTool class
public void WriteToFile_Local_Using(string strMessage)
{
    StreamWriter objStreamWriter = null;
    if (strMessage != string.Empty && strMessage != null)
    {
        //private member variable
        lock (m_objLocker)
        {
            using (objStreamWriter =
                new StreamWriter(FilePath, true))
            {
                //logs only the time and not the date
                objStreamWriter.WriteLine(
                    DateTime.Now.TimeOfDay + " - " + strMessage
                    );
            }
        }
    }
}

Note: When you are done with the reference variable it need
not be set to null, because once a variable fall out of scope,
it is popped from the stack and the reference is removed.

Rule: Close the database connection.

Rule: Close the communication port.

Rule: Close the file handle.

Handle exception

If you are using try/catch block for handling exception then make sure the catch block handles the exception. It can be showing a message to the user and/or logging the exception details to a log file along with realsing the resources and transcation rollbacks.

Rule: Handle the exception in the catch block.

Rule: Rollback transaction in the catch block if applicable.

Exit (break) loop

Exit the loop (for, while, etc.) if the expected condition is met and if applicable.

C# example
public Employee GetEmployee(string strName)
{
    Employee objEmployee = null;
    if (strName != "" && strName != null)
    {
        for (int i = 0; i < m_arrEmployeeCollection.Count; i++)
        {
            if (strName == m_arrEmployeeCollection[i].Name)
            {
                objEmployee = m_arrEmployeeCollection[i];
                break;
            }
        }
    }
    return objEmployee;
}

Rule: Exit the loop if expected condition is met.

Hardcoding values

Do not hardcode any values (numbers and string used with in application). Use a separate file for declaring and defining the constant values either at the assembly (module) level or at the application level.

C# example- In the below example constants class is used for storing all the constants.
class Constants
{
    public const int MinEmpId = 0;
}
//employee class
class Employee
{
    public string Name { get; set; }
    public int EmpId { get; }
    public Employee()
    {
    }
    //should not allow negative numbers
    public bool SetEmpId(int iEmpId)
    {
        bool blnStatus = false;
        if (iEmpId >= Constants.MinEmpId)
        {
            EmpId = iEmpId;
            blnStatus = true;
        }
        return blnStatus;
    }
}

Note: Follow the internationalization standards if your application has to support localization.

Rule: Do not hardcode values.

Rule: Follow internationalization standards if required.

Argument validation

Validate the function arguments for the following
1) null or empty value
2) invalid datatype
3) out of range if applicable

Alternatively you can validate the arguments in the calling function and have called function perform only the business logic.

C# example
//should not allow empty string or null string
public bool SetName(string strName)
{
    bool blnStatus = false;
    if (strName != string.Empty && strName != null)
    {
        Name = strName;
        blnStatus = true;
    }
    return blnStatus;
}

Note: The intranet applications can handle the argument validations before calling the function in the server so that the server can focus only on executing the business logic.
Due to security reasons the internet applications should authenticate the caller, authorize the caller and validate the function arguments.

Rule: Validate the arguments for null or empty values.

Rule: Validate the arguments for wrong datatype.

Rule: Validate the arguments for out of range.

Effective logging

To make the overall logging mechanism effective consider the following
  • Make sure the information logged is effective and really help us identify and fix the defect
  • Make sure the logging is used in the right place
  • Make sure logging is enabled only if a defect is reported

Note: In multi threaded application make sure “all the threads” log their code flow.

Rule: Log effective and useful information.

Rule: Log at the right place.

Rule: Support enabling and disabling of logging.

Logging mechanism

Make sure the application has a logging mechanism. Consider the following while designing and implementing the logging functionality
  • Log only the exceptions which can not be handled by the end user and provide proper guidance for the end user to report the same.
  • Logging functionality shall be controlled through external configuration for enabling and disabling.
  • Alternatively the logging functionality shall be controlled through different build configurations like debug build with logging enabled and release build with logging disabled.
  • Logging mechanism for debugging the code (code flow) has to be disabled in the production version. The logging can be enabled if any crash is reported in the production version.
  • Do not use text file logging mechanism for recording audit trail information (user actions, transactions, etc.) instead use a database.
  • If the logs are recoded in test files consider a proper archival mechanism, so that the log file does not grow to the extend which can not be opened by any text file viewer.
  • Logging functionality has to be thread safe so that it can be used in multiple threaded application.
  • If the log information can be sent to an UDP port, then an UDP client application can be used for viewing the log information online.Overall this can make the debugging process very effective. Also this approach can be used for remote debugging if possible.
  • Make sure the logging functionality is optimized to the maximum.

Rule: Have logging mechanism for the application.

Simple tool for unit testing

The below unit testing example has UnitTestingTool class, Logger class and few unit test cases for the Logger class.

UnitTestingTool is the class used for unit testing.
It supports testing function which returns bool, object,
string data types. It also has the counts of passed and failed test cases.
The pass and fail counts can be reset to zero and also displayed on the console.

Logger is the class which is unit tested.
The Logger class is used for logging string message to a text file.
The Logger class function call sequence is
  1. Create Logger object
  2. Call Open function
  3. Call WriteToFile function
  4. Call Close function
C# example- Simple unit testing tool.
using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Threading;

namespace UTLogger
{
    //unit testing tool class
    class UnitTestingTool
    {
        private int m_iPassCount = 0;
        private int m_iFailCount = 0;
        public UnitTestingTool()
        {
        }
        public void Reset()
        {
            m_iPassCount = 0;
            m_iFailCount = 0;
        }
        public bool ValidateBool(bool blnResult,
            bool blnExpectedResult, string strRemarks)
        {
            bool blnStatus = false;
            StringBuilder objSBResult = new StringBuilder();
            if (blnExpectedResult == blnResult)
            {
                m_iPassCount++;
                blnStatus = true;
            }
            else
            {
                m_iFailCount++;
                objSBResult.AppendLine("Bool test failed:"
                    + strRemarks);
                objSBResult.AppendLine("Result:"
                    + blnResult);
                objSBResult.AppendLine("Expected Result:"
                    + blnExpectedResult);
                Console.WriteLine(objSBResult);
            }
            return blnStatus;
        }
        public bool ValidateObject(object objResult,
            object objExpectedResult, string strRemarks)
        {
            bool blnStatus = false;
            StringBuilder objSBResult = new StringBuilder();
            if (objExpectedResult == objResult)
            {
                m_iPassCount++;
                blnStatus = true;
            }
            else
            {
                m_iFailCount++;
                objSBResult.AppendLine("Object test failed:"
                    + strRemarks);
                objSBResult.AppendLine("Result:"
                    + objResult);
                objSBResult.AppendLine("Expected Result:"
                    + objExpectedResult);
                Console.WriteLine(objSBResult);
            }
            return blnStatus;
        }
        public bool ValidateString(string strResult,
            string strExpectedResult, string strRemarks)
        {
            bool blnStatus = false;
            StringBuilder objSBResult = new StringBuilder();
            if (strExpectedResult == strResult)
            {
                m_iPassCount++;
                blnStatus = true;
            }
            else
            {
                m_iFailCount++;
                objSBResult.AppendLine("string test failed:"
                    + strRemarks);
                objSBResult.AppendLine("Result:"
                    + strResult);
                objSBResult.AppendLine("Expected Result:"
                    + strExpectedResult);
                Console.WriteLine(objSBResult);
            }
            return blnStatus;
        }
        public void DisplayResult()
        {
            StringBuilder objSBResult = new StringBuilder();
            objSBResult.AppendLine("Test result");
            objSBResult.AppendLine("------------------------");
            objSBResult.AppendLine("Pass count:" + m_iPassCount);
            objSBResult.AppendLine("Fail count:" + m_iFailCount);
            objSBResult.AppendLine("------------------------");
            objSBResult.AppendLine("Total count:"
                + (m_iPassCount + m_iFailCount));
            Console.WriteLine(objSBResult);

        }

    }
    class Logger
    {
        public string FilePath { get; set; }
        private StreamWriter m_objStreamWriter = null;
        private object m_objLocker;
        public Logger()
        {
            m_objLocker=new object();
            FilePath = @"d:\log.txt";
        }
        public bool Open()
        {
            bool blnStatus = false;
            try
            {
                m_objStreamWriter = new StreamWriter(FilePath, true);
                blnStatus = true;
            }
            catch (Exception objEx)
            {
                Console.WriteLine(objEx.StackTrace);
            }
            finally
            {
            }
            return blnStatus;
        }
        public bool Close()
        {
            bool blnStatus = false;
            try
            {
                if (m_objStreamWriter != null)
                {
                    m_objStreamWriter.Close();
                    blnStatus = true;
                }
            }
            catch (Exception objEx)
            {
                Console.WriteLine(objEx.StackTrace);
            }
            finally
            {
            }
            return blnStatus;
        }
        //make sure Open is called before calling WriteToFile
        //make sure Close is called once all logging is done
        public bool WriteToFile(string strMessage)
        {
            bool blnStatus = false;
            if (strMessage != string.Empty && strMessage != null)
            {
                try
                {
                    lock (m_objLocker)
                    {
                        if (m_objStreamWriter != null)
                        {
                            //logs only the time and not the date
                            m_objStreamWriter.WriteLine(
                                DateTime.Now.TimeOfDay
                                + " - "
                                + strMessage
                                );
                            blnStatus = true;
                        }

                    }
                }
                catch (Exception objEx)
                {
                    //use messagebox if winforms application
                    Console.WriteLine(objEx.StackTrace);
                }
                finally
                {
                }
            }
            return blnStatus;
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            Logger objLogger = null;
            UnitTestingTool objUTT = null;
            objLogger = new Logger();
            objUTT = new UnitTestingTool();
            bool blnStatus = false;
            blnStatus = objUTT.ValidateBool(
                objLogger.WriteToFile(""),
                false,
                "EmptyString Message"
                );
            if (true == blnStatus)
            {
                blnStatus=objUTT.ValidateBool(
                    objLogger.WriteToFile(null),
                    false,
                    "null Message"
                    );
            }
            if (true == blnStatus)
            {
                blnStatus = objUTT.ValidateBool(
                    objLogger.WriteToFile("test1"),
                    false,
                    "Valid Message without opening the logger"
                    );
            }
            if (true == blnStatus)
            {
                blnStatus = objUTT.ValidateBool(
                    objLogger.Close(),
                    false,
                    "Close objLogger without opening"
                    );
            }
            //invalid filepath
            objLogger.FilePath = @"z:\log.txt";
            //expect the exception message on the console
            if (true == blnStatus)
            {
                blnStatus = objUTT.ValidateBool(
                    objLogger.Open(),
                    false,
                    "Open objLogger with invalid filepath"
                    );
            }
            //valid filepath
            objLogger.FilePath = @"d:\log.txt";
            if (true == blnStatus)
            {
                blnStatus = objUTT.ValidateBool(
                    objLogger.Open(),
                    true,
                    "Open objLogger with valid filepath"
                    );
            }
            if (true == blnStatus)
            {
                blnStatus = objUTT.ValidateBool(
                    objLogger.WriteToFile("test1"),
                    true,
                    "Valid Message"
                    );
            }
            if (true == blnStatus)
            {
                blnStatus = objUTT.ValidateBool(
                    objLogger.WriteToFile("test2"),
                    true,
                    "Valid Message"
                    );
            }
            if (true == blnStatus)
            {
                blnStatus = objUTT.ValidateBool(
                    objLogger.Close(),
                    true,
                    "Close objLogger"
                    );
            }
            objUTT.DisplayResult();
            Console.ReadLine();
        }
    }
}

Output:
   at System.IO.__Error.WinIOError(Int32 errorCode, String maybeFullPath)
   at System.IO.FileStream.Init(String path, FileMode mode, FileAccess access, I
nt32 rights, Boolean useRights, FileShare share, Int32 bufferSize, FileOptions o
ptions, SECURITY_ATTRIBUTES secAttrs, String msgPath, Boolean bFromProxy)
   at System.IO.FileStream..ctor(String path, FileMode mode, FileAccess access,
FileShare share, Int32 bufferSize, FileOptions options)
   at System.IO.StreamWriter.CreateFile(String path, Boolean append)
   at System.IO.StreamWriter..ctor(String path, Boolean append, Encoding encodin
g, Int32 bufferSize)
   at System.IO.StreamWriter..ctor(String path, Boolean append)
   at UTLogger.Logger.Open() in D:\code2009\Sample\UTLogger\Program.cs:line 121
Test result
------------------------
Pass count:9
Fail count:0
------------------------
Total count:9

Note: The above UnitTestingTool class can be extended for other data types.

Unit testing

Generally the developers tend to write the code which works only for valid user input or arguments and end up releasing the same for testing (no unit testing, no refactoring and no optimization).
C# example
Step #1 - Write the code for all possible invalid condition.
//should not allow empty string or null string
public bool SetName(string strName)
{
    bool blnStatus = false;
    if (strName != string.Empty && strName != null)
    {

    }
    return blnStatus;
}

Step #2 - Perform a quick unit test.

Example unit test cases.
  • Test the code for empty string and expect the function to return false
  • Test the code for null string and expect the function to return false
  • If any of the above case fails fix the code without writing the code for invalid case
  • When all the above cases succeed write the code for valid case

Step #3 - Fix the defects and verify the fixes if any.

C# example

Step #4 - Write the code for all valid conditions.

//should not allow empty string or null string
public bool SetName(string strName)
{
    bool blnStatus = false;
    if (strName != string.Empty && strName != null)
    {
        m_strName = strName;
        blnStatus = true;
    }
    return blnStatus;
}

Step #5 - Perform the unit test for the entire functionality.

Example unit test cases.
  • Test the code for empty string and expect the function to return false
  • Test the code for null string and expect the function to return false
  • Next add the test case for valid string value and expect the function to return true
  • If any of the above case fails fix the code and verify the same

Step #6 - Fix the defects and verify the fixes if any.

Write invalid condition code → Unit test → Fix & Verify → Write valid condition code → Unit test → Fix & Verify

Note: Unit testing automation strategy has to planned well before starting the implementation. There are unit testing automation frameworks available in the open source community as well as COTS.

Rule: Unit test the function.

Testable function

Make sure the public function returns some value or reference or status of the execution. So that the function can be unit tested.

C# example- Below is a non testable function.
//should not allow value less than 1 and greater than 100
public void SetAge(int iAge)
{
    if (iAge >= 1 && iAge <= 100)
    {
        m_iAge = iAge;
    }
}
The above function, if called gets executed but the caller would not be sure about the result of execution, so the caller has to get the age again and check if it was set correctly.
C# example- Below is a testable function.
//should not allow value less than 1 and greater than 100
public bool SetAge(int iAge)
{
    bool blnStatus = false;
    if (iAge >= 1 && iAge <= 100)
    {
        m_iAge = iAge;
        blnStatus = true;
    }
    return blnStatus;
}

Note: Minimize coupling (dependency between classes and modules) as much as possible, which could highly improve the testability of the entire module.

Rule: Write testable function.

Constructor

The constructor should have code only for initializing
the data members either with arguments or with some default values.

C# example
public Employee(string strName, int iAge)
{
 m_strName = strName;
 m_iAge = iAge;
}

Note: Use factory pattern for sophisticated construction of objects.

Rule: Have only initialization code in the constructor.

Friday, June 11, 2010

The most important

Before starting the implementation of the software make sure the following aspects are addressed as part of the requirements collection and design
  • logging mechanism for debugging
  • exception reporting and handling
  • internationalization
  • unit testing strategy
  • user management
  • licensing
Introducing the above functionalities during implementation of the software would involve lot of rework and risk.