Wednesday, July 24, 2013

Software Engineering - C# Coding Guidelines

"How do we convince people that in programming simplicity and clarity —in short: what mathematicians call "elegance"— are not a dispensable luxury, but a crucial matter that decides between success and failure? " - Edsger W Dijkstra
 
Introduction:  A common coding document is needed for development of tools and applications. The common coding standard should be a living document that can foster the development of robust, readable, transferable code. It is also designed to enforce a consistent style across the project and allow code reuse. A consistent coding standard across multiple teams will greatly reduce rework and encourage cross group collaboration. This document outlines the basic recommendations while doing C# coding.

Basics:



Comments:

Comments should follow the recommendations in the C# language reference.
Single line comments are prefixed by ///
Multi line comments are written as
                        /**
                        *
                        *
                        */

The line length should not exceed 120 characters to improve the readability of the code on a standard 15’’ monitor.

Issue markers.
Some times there is a need to leave reminder in the code about uncompleted work or special cases that might not be handled correctly.

Naming Convention:

Name Spaces, Class Names, Function names, Properties, Static Variables: ( Pascal Casing )
                        e.g., SendPacket( )       - good
                                send_packet( )      - bad
                                _Send_Packet( )    - bad

Variable names: ( Camel Casing )
                        e.g., int cnt = 0;             - good
                                int smallCounter = 0; - good
                                int small_counter = 0; - bad
                                int SmallCnt = 0;    - bad

Function Parameters: Use Camel casing for function parameters
                        e.g., DoSomething( int someInteger, int someOtherInt )

Give meaningful names to the vaiable names. For example, for looping through an array use the following
for(int index = 0; index < arraySize; index++)
{
}
Instead of
for(int x = 0; x < arraySize; x++)
{
}


Note that a Class, Function or variable name should reflect its purpose as closely as possible. The general convention to be followed is that class names should be nouns and Function names should be verbs.

Static member variables should be prefixed by an underscore to differentiate it from the property naming convention of a class. (Ex: _SampleStaticVariableName).

Coding:

Handling Namespaces: The following general rule is suggested for naming a namespace. <Company>.<Application>.<Feature>

Member Variables and Properties: Avoid use of public member variables within a class. In C#, the convention is to create private member variables and public Properties that expose these variables.

Function Modularity: The function should do exactly what its name indicates. Keep the function as modular as possible. The general rule of thumb is, if a function is performing more than one operation, consider breaking up the function into two independent modular functions and create a wrapper that calls these two independent functions.  Also also avoid deep nesting inside a function wherever possible. This makes the code difficult to comprehend and increases the complexity. General rule of thumb is if a function is nested more than three levels deep, consider breaking it down into separate modular functions.

Exit points from a function: The function should ideally have one exit point. This can be accomplished by use of a return variable that can be assigned several times within a function but will be returned only once.

Default constructor: If no default constructor is provided by the developer, the language will automatically provide a default constructor that returns a zero initialized instance of the value type. It is advisable to add the default constructor always to avoid versioning issues as adding a new constructor in later version will replace the default constructor, thus breaking older clients.

Destructor and Finalize: Since garbage collection is done automatically, the destructor cannot be invoked explicitly. The destructor with the ~ notation and Finalize( ) implementation in a class map to the same thing. When an instance of a class is destructed, the destructor in that inheritance chain is called in order from the most derived to the least derived
Design guidelines suggest the following for destructor/finalize:
  1. Provide destructor/Finalize to cleanup unmanaged resources.
  2. There is a performance penalty in using a destructor. ( orders of magnitude )
  3. Do not throw exceptions from the destructor.
  4. Do not block or wait in finalizers.
  5. Don’t do locking in a destructor/Finalize.
  6. Implement the Dispose pattern ( called from IDisposable ) whenever a finalize or a destructor is implemented. Dispose gives better control to clean up the resources than Finalize does.

Dead Code: Dead code should be removed and unused code shouldn’t be part of the active source code. Keeping dead code around adds more maintenance cost as the code is read more often than it is written through its life cycle. A developer can figure out specific changes in a project by using TFS Change History.

Exception Handling: It is advisable to use try - catch - finally mechanism around code that can fail. However, the following points must be considered when using exception handling.
  1. Exceptions must be used for truly exception situations.
  2. Exceptions shouldn’t be thrown if the try – catch – finally code should be re-executed as a result of the exception.
  3. Wherever possible, try – finally should be used instead of try-catch as catch usually eats up the exception.
  4. Blanket exception swallowing is generally not advisable.



                        Example1:

                        public void FooBar( )
                        {
                                    try
                                    {
                                                /// some code
                                    }
                                    catch( Exception e )
                                    {
                                               
                                    }
                        }

                        Example2:
                       
                        public void FooBar( )
                        {
                                    try
                                    {

                                                // some code
                                    }
                                    catch
                                    {

                                    }
                        }

            The above code is similar to the blanket exception handling in C++ using catch( ... ).

  1. Exceptions shouldn’t leak internal information that can be used for exploits against the target machine.

  1. One should try to use (or derive from ) the existing exceptions class as much as possible.

  1. An exception class should have at least the top three constructors

            public class XxxException : YyyException
            {
                        public XxxException ( ) {  }
                        public XxxException ( string message ) { }
                        public XxxException ( string message, Exception inner ) { }
            }

Passing by reference and the out parameter: Passing by reference should be considered if the structure is large. Passing large structs by value could cause the stack overflow problem. The design guidelines also suggest using a reference type, i.e., class if the size of the structure is greater than sixteen octets.

Structs and classes: C# structures are different than the C++ structures in a couple of important ways.
1.     C# structure doesn’t have a default constructor.
2.     An instance of a C# class is typically created using new. However, an instance of a structure can be created without using new.
3.     Inheritance is not supported for structures.

This can be illustrated using the following example.

Point a = new Point( 10, 10 );
Point b = a;
a.x = 100;
System.Console.WriteLine( b.x );

If point is a structure, b.x will be 10 and not 100. This is because at the time of assignment, a copy of “a” is made and assigned to b. If point were a class, b.x would also be 100 as “a” and “b” would refer to the same object.

Sealing a class: A class should be declared as sealed to prevent unintended derivation. An example of a singleton class can show this usage:

public sealed class DBNull
{
            private DBNull( ) { }
            public static readonly DBNull _SingletonInstance = new DBNull( );
            //
            // instance methods
            //
}
            A singleton pattern is sealed to prevent further instances of this class from being created via inheritance.

Inheritance and the base class: In C# a class can inherit from only one class. This being the case, the developer needs to be careful to identify if one should use a base interface as opposed to a base class. It is also good to avoid using both an interface and a class at the same time.

Public class MyClass : A, B
{
                        => Derivation from multiple classes is supported in C++ but not in C#.
}

Instead, use an instance of B inside of MyClass
Public class MyClass: A
{
            public B MyBe{ get { … } }
}

Interfaces should contain a few, focused methods.

Using FxCop: FxCop is a static code analysis tool that can be used for checks against design guidelines, possible performance improvements, security issues etc. It is highly recommended that FxCop be used against the code generated and the bugs fixed.

Nested classes: In general, using nested classes in a design is not advisable. It is generally advisable to have one file contain one class/interface implementation.

Unmanaged Code, calling win32 code: The following points must be considered when using unmanaged code from managed code.
  1. Review GC.KeepAlive calls when using P/Invoke calls and Finalize( ) does the cleanup of unmanaged resources.
  2. Don’t block indefinitely in unmanaged code – this could cause denial of service attacks.
  3. Finalize( ) should be free of synchronization problems.


References:
[2] Code Complete by Steve McConnell
<script async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-6962084931129525"
     crossorigin="anonymous"></script>