asp:Feature
Implementing .NET Coding Standards
By Dr. Adam Kolawa
With the .NET Framework, Microsoft has taken a big step
forward in making the Internet a true distributed computing platform. .NET
provides a complete framework that enables computers, devices, and services to
collaborate. The result is dynamic Web services that can be programmed in any
language without needing to worry about interoperability issues.
However, while the .NET Framework is dynamic and promises
to bring great advances in Web services, there are a few bumps on this rosy
road. The biggest is the fact that companies and development shops that choose
to migrate to .NET from their current programming language need a good deal of
time to ramp up to .NET. This is not a drawback of .NET, but rather a migration
issue. It takes time to learn a new way of doing things, and .NET, while
dynamic and innovative, is not easy to learn.
This issue of migration holds many hidden pitfalls. If
your development team has trouble migrating to .NET, you open your Web services
applications up to security gaps, functionality lapses, maintenance problems,
and other issues of quality.
How then, can you bring your team up to speed and not
bring these problems into your applications?
The answer is through .NET coding standards. Having the
group adhere to a basic set of .NET coding standards allows you to maintain an
internal and basic group Service Level Agreement (SLA) that your .NET team must
meet. It also gives programmers trying to learn .NET a way to understand what
coding constructs in .NET are critical, and which are not terribly crucial for
their immediate needs. As your team grows in its .NET skills, you can add
further coding standards to increase the scope of the code analysis.
Although the examples used in this article illustrate C#
coding standards, the same principles can be applied to any of the programming
languages that target the .NET Framework, such as VB.NET and Managed C++. This
article will consider some common pitfalls with .NET and explain the coding
standards that your development team can use to prevent these problems from
occurring.
Optimizing Performance
Developers write code and want it to perform as quickly as
possible. For example, developers often try to optimize C# by using String. String
is a constant object, which means that, by default, it can only be created and
read, but never modified. Developers can use String when they do not think they
will want to modify it as the program is executed:
public class MakeMessage
{
public string
GetMessage (string[] words)
{
string message =
"";
for (int i = 0; i <
words.Length; i++)
{
message +=
" " + words [i];
}
return message;
}
}
The segment of code above gets a new word every time it
passes through the loop. It appears that a new message string is created and
that the message is being appended, but these appearances are deceiving. In
fact, the old memory under the message is being disregarded and new memory is being
allocated. The old information from the message is copied to the new memory and
then a new character is added at the end. The new memory created is one word
longer than the old memory. The code itself is deceptive to read in this
instance. Because it contains message +=, the code looks as if it will
increment the message. However, the message is actually being created from
scratch each time.
.NET developers working with C# need to understand the
difference between String and StringBuilder. StringBuilder is a dynamic object.
Because it can be modified, StringBuilder can truly be appended, rather than
merely giving the false appearance of being appended. Developers can use
StringBuilder in performance-critical sections of code, such as loops.
In the following example, StringBuilder is used to modify
the string inside the loop:
public string GetMessage (string[] words)
{
StringBuilder message
= new StringBuilder();
for (int i = 0; i <
words.Length; i++)
{
message.Append(" ");
message.Append(words[i]);
}
return
message.ToString();
}
Therefore, developers should define the message as
StringBuilder rather than String in situations where they will want to modify
the code. Developers will then be able to use the method Append to modify the
memory without creating new memory each time through the loop.
Optimizing Memory
Developers want to optimize memory in their applications,
but how do they get rid of chunks of memory that are no longer needed?
The problem occurs when there are pointers in the program
that refer to the chunk of memory. They are essentially forgotten pointers
because the memory is no longer needed. The pointers are useless, and the
developer did not intend to keep them, but they remain in the program because
the developer neglected to null them.
In the following C# example, we have the function
MakeSplashImage. We assume it calls new and uses a lot of memory. This function
result can be a display or whatever else we want. We are sending the memory
reference for bigSplashImage to the function that displays it. After the image
is displayed, the memory is really not needed in the program:
public class GUI
{
public static void Main(string[]
args)
{
Graphics
bigSplashImage = MakeSplashImage();
DisplayMomentarily(bigSplashImage);
while
(MoreProcessInput())
{
Process();
}
}
}
However, notice in the above example that the reference
pointer has not been nulled. Therefore, developers who want to avoid leaving
unwanted references in their .NET programs should zero these references as soon
as they no longer need them:
public class GUI
{
public static void Main (string[] args)
{
Graphics
bigSplashImage = MakeSplashImage();
DisplayMomentarily(bigSplashImage);
bigSplashImage =
null;
while
(MoreProcessInput())
{
Process();
}
}
}
Memory issues are very common when developers use
temporary variables in .NET. When they forget to zero their temporary
variables, they end up with what essentially amounts to a memory leak. Therefore,
nullify temporary references to objects taking large amounts of memory as soon
as they are no longer needed.
Optimizing External Resource Usage
When programming in .NET, developers need to be aware that
there are many layers of code working underneath them. Though developers may be
dealing mainly with high-level language performing complicated functions, the
layers of code underneath that language are performing a host of other functions.
These layers will behave differently depending on what is done to the code at
the upper level.
The lower layers of code are vital to the proper
functioning of an application; they are the behind-the-scenes workers that make
high-level functionality possible. If these hidden layers are ignored, they
will most likely come back to cause problems in the application.
One lower layer of code that cannot be ignored is the
communication with external resources. There is a central rule that should
guide all interactions with external resources. Simply put, if an external
resource is opened, it should be closed as soon as it is finished being used.
The following coding example deals specifically with file inputs, which are
just one type of external resource. However, the lesson from this example
applies to interactions with any external resources. Notice that this C# code
looks as if it makes a clean exit:
public class ReadFile
{
public static string
Read(String path)
{
StreamReader reader =
new StreamReader(path);
String contents =
reader.ReadToEnd();
reader.Close();
return contents;
}
}
However, the code above is deceptive: The reader.Close
command does not make a clean exit. Whether talking to a database, opening
files, opening sockets, or sending instructions to the screen, developers need
to close any external resources they have opened. The dangerous aspect of
dealing with external resources is that when developers write a piece of code
such as the aforementioned segment, it seems to run well. However, developers
may encounter an error when dealing with an external resource.
In a case such as the above, the code will throw an
exception indicating a serious problem. Exceptions can transfer control to
different parts of the program. In the previous example, if the method
ReadToEnd throws an exception, control will be transferred out of the method
read and the file will not be closed.
In this situation, developers may choose to handle the
exception and ignore the problem. However, they should stop to consider that
the exception might have come from the interactions with external resources. If
developers merely handle the exception, they will face the strong possibility
of a resource leak. In other words, they will run out of resources at some
point, which means they will not be able to open files or sockets and will not
have access to the database.
If the code throws exceptions when using external
resources, developers need to write a finally block. The finally block will
always be executed, regardless of whether code is exited through exceptions or
through normal execution. When the finally block is used, developers are
guaranteed their code will clean up after them by closing all of their external
resources. Therefore, developers should always exit code cleanly by using the
finally block:
public class ReadFile
{
public static String Read(String path)
{
StreamReader reader =
null;
try
{
reader = new
StreamReader(path);
string contents
= reader.ReadToEnd();
return contents;
}
finally
{
if (reader !=
null)
{
reader.Close();
}
}
}
}
The segment of code above is an example of how to clean up
in the finally block rather than in the original code. The finally block is
inserted just before the reader.Close command. Developers now know that they
will be able to close the external resources and exit the code. They will also
be able to open the external resources the next time they need to use them.
Using the finally block guarantees that developers will not leak resources. Therefore,
it is useful to write a finally block to clean up when dealing with external
resources.
Using Implicit Cast Operators
When using implicit cast operators, developers must be
aware of all potential consequences. For example, errors in the code can stem
from the improper use of Vector and Figure classes in conjunction with implicit
cast operators:
using System;
public class Figure
{
public void
Transform(double d)
{
//this method
resize figure with d factor
}
public void
Transform(Vector v)
{
//this method
moves figure using vector
}
}
public class Vector
{
public static implicit
operator double(Vector v)
{
return
Math.Sqrt(v.x * v.x + v.y * v.y);
}
private double x;
private double y;
public Vector(double
x, double y)
{
this.x = x;
this.y = y;
}
public static void Main()
{
Figure f = new
Figure();
Vector v = new
Vector(1, 1);
f.Transform(v);
}
//...
}
As seen in the code above, the developer used the class
Vector with an implicit cast operator to convert Vector into its length as
double. In another part of this code, a Figure class provides methods for its
Transformations.
In its current state, calling Transform on the Figure
object with a Vector object as an argument causes figure translation. However,
if another programmer removes the method Transform(Vector)
and is not aware of the implicit cast operator for the Vector class, the code
will behave improperly:
using System;
public class Figure
{
public void
Transform(double d)
{
//this method
resize figure with d factor
}
}
public class Vector
{
public static
implicit operator double(Vector v)
{
return
Math.Sqrt(v.x * v.x + v.y * v.y);
}
private double x;
private double y;
public Vector(double
x, double y)
{
this.x = x;
this.y = y;
}
public static void Main()
{
Figure f = new
Figure();
Vector v = new
Vector(1, 1);
f.Transform(v);
}
//...
}
There is no error caused by the missing Transform(Vector)
method. Instead, the Transform(double) method is
called and Figure is scaled with vector length factor instead of being
translated.
If a developer changes the code design so that an implicit
cast operator is not necessary, the problems shown previously will not occur.
For example, a developer may instead use an explicit cast operator, as shown
below:
public static explicit operator double(Vector v)
{
return Math.Sqrt(v.x *
v.x + v.y * v.y);
}
Object-oriented Programming
Object-oriented programming (OOP) makes code reusable,
easily maintainable, and better organized. However, there are a number of
pitfalls; for example:
public class BankAccount
{
public int _balance;
}
The class BankAccount is used to represent a bank account,
but the variable used to represent the balance has been made public. Even
though declaring the variable public is legal according to the .NET framework,
it makes the code very difficult to modify and improve. There is a safer way of
writing the code and achieving the same effect:
public class BankAccount
{
private int _balance;
public int Balance
{
get
{
return
_balance;
}
set
{
_balance = value;
}
}
}
}
Here, the _balance variable has been declared private, and
a public property has been defined to access it. The code is now very easy to
maintain because you can change the BankAccount implementation without having
to change any of the client code.
For example, developers can make the BankAccount object
thread-safe just by adding synchronization to get/set Balance property methods.
Note that none of the other methods that may be using BankAccount objects need
to be modified in this case. Therefore, developers should declare their
variables private.
Conclusion
Coding standards are a vital and important error
prevention tool, but they must be used regularly, even religiously, in order to
be effective. This is especially true as development projects become more and
more complex in an effort to meet consumer demand for better features and more
functionality. Implementing and using coding standards early,
and enforcing them automatically throughout the development lifecycle, results
in many outstanding product benefits.
Dr. Adam Kolawa is
the co-founder and CEO of Parasoft, a
leading provider of Automated Error Prevention (AEP) software solutions. Kolawa s
years of experience with various software development processes has resulted in
his unique insight into the high-tech industry and the uncanny ability to
successfully identify technology trends. As a result, he has orchestrated the
development of several successful commercial software products to meet growing
industry needs to improve software quality - often before the trends have been
widely accepted. Kolawa, co-author of Bulletproofing
Web Applications (Hungry Minds, 2001), has contributed to and written
over 100 commentary pieces and technical articles for publications such as The Wall Street Journal, CIO, Computerworld,
Dr. Dobb s Journal, and IEEE Computer; he has also authored numerous
scientific papers on physics and parallel processing. His recent media
engagements include CNN, CNBC, BBC, and NPR. Kolawa holds a Ph.D. in
theoretical physics from the California
Institute of Technology, and has been granted 10 patents for his recent
inventions. In 2001, Kolawa was awarded the Los Angeles
Ernst & Young s Entrepreneur of the Year Award in the software category.