Namespaces in .NET help to prevent name collisions,
i.e. that two developers working on different projects uses exactly the same
name. This would create a problem when the projects should ever be joined. .NET
prevents this problem by adding a namespace to the names of programming
elements.
PeterBox.com is a registered name and shouldn抰 be
used by anyone else. By starting our namespace with PeterBox, we guarantee that
our code can be combined with any software written anywhere else.
Each software project should have its own namespace
allowing the use of the same name in different projects. The namespace name
looks like this: PeterBox.ProjextX. Code that can be used also in other
projects goes into the PeterBox.General namespace.
|
Namespace
|
Comment
|
|
PeterBox
|
Shouldn抰 be used directly
|
|
PeterBox.General
|
For project independent modules like libraries, lowest level
database access, exception handling
|
|
PeterBox.Website
|
PeterBox.com website
|
A .NET Solution is a container for several .NET
Projects. Its main use is for Visual Studio to provide a structured access to
all .NET projects needed to create an application. It mostly contains projects
and few project independent files. One solution might contain projects like:
•
1 ASP.NET website
•
Several library .DLLs
•
Several executable Windows programs (testing, utilities, ?
•
Other files
A .NET Project contains all files needed to create a
piece of executable code, usually a .dll (dynamic-link library), an .exe file
(executable program) or the files for an ASP.NET website. All files belonging
to a specific application should be included in one project. Code that can be
shared with other applications should be placed in their own .dll projects.
Remove as much functionality from the user interface (.exe, ASP.NET) into
.dlls, this allows separating of user interface from the other layers and reuse
of the code in other projects.
There are also other projects types like for setup
and deployment of the application, application center test projects, etc.
A project can contain any type of file and is the
perfect place for documentation.
A Solution should contain at least the following
projects:
|
Project
|
Description
|
|
Application Project
|
Windows or ASP.NET project
|
|
PeterBoxLibrary
|
.DLL with code used in various applications
|
|
ApplicationTest
|
Console application for running tests
|
|
Install
|
Setup and deployment of Application
|
Make sure to change the Default Namespace
property to the correct namespace, like PeterBox.Project, because Visual
Studio uses the file name of the first item added to a project, which is not helpful.
Set to true. We want to get an error message
if one of these errors occure.
Visual Studio creates a folder (windows file
directory) containing all files of a .NET project. It creates additional sub
folders to separate source code from executables, etc.
A project can contain code from different layers
(e.g. user interface and business rules). Add an additional directory MiddleTier
to keep the source code for these two layers separated.
Source files are stored in Visual SourceSafe, but
they need to be copied (checked out) to a PC for development.
Make a directory on you home drive for all your .NET
projects. Visual Studio will store all projects there, except Asp.NET projects,
which are stored under: C:\Inetpub\wwwroot
C# and Visual Basic are very similar and offer the
same kind of functionality. Visual Basic has the easier understandable syntax
and thanks to the immediate compilation whenever something is typed in, the
better reporting of errors. In C#, a build has to be performed to get the same
kind of error messages.
Nonetheless, C# is selected, because:
•
Hides less from the developer than Visual Basic
•
Close to C++ and Java
•
Seems Microsoft抯 choice of language
•
Advanced tools available (like XML comments)
Additionally to the general rules listed in 3.2 Coding Format, the following formatting should be used for C#:
•
Use 4 space for indentation (no tabs)
•
Use 1 space before and after equal sign in assignment Index = 0
•
Use // xxx xxxx 爁or one line comments
•
Use /* xxx xxxx */ 爁or
multi-line comments
•
Use title comments to help with orientation in big listings. They
can be used to group methods or to group lines of in long methods:
牋?code ...
牋?// Title
牋?// -----
牋?code ...
•
Write the opening curly bracket on the same line as its
definition (namespace, class, method, if(), ? preceded by one space.
•
Properties can be written in the following condensed form:
牋?///
<summary>
牋?///
presently connected database
牋?///
</summary>
牋?public DBConnection DatabaseUsed {
牋牋牋?get {return
_DatabaseUsed;}
牋?}
牋?private
DBConnection _DatabaseUsed;
•
Separate 2 methods be 2 empty lines
/*****************************************************************************
Sample Program
==============
Some comment
might be here
Copyright (c)
Peter Huber, Singapore, 2003
*****************************************************************************/
using System;
namespace PeterBox {
牋?///
<summary> Just a counter not telling anyone anything </summary>
牋?class
Counter {
牋牋牋?int
_Counter;
牋牋牋?
牋牋牋?///
<summary>
牋牋牋?///
Initialise counter, negative value means counting has not started yet
牋牋牋?///
</summary>
牋牋牋?public Counter(int
start) {
牋牋牋牋牋?
_Counter = start;
牋牋牋?}
牋牋牋?//
Example of a Title
牋牋牋?//
------------------
牋牋牋?///
<summary> Increment counter by one </summary>
牋牋牋?void Increment()
{
牋牋牋牋牋?if (_Counter<0)
{
牋牋牋牋牋牋牋?
// start counting
牋牋牋牋牋牋牋?
_Counter=0;
牋牋牋牋牋?} else
if (_Counter>=99) {
牋牋牋牋牋牋牋?
// do nothing
牋牋牋牋牋?} else
{
牋牋牋牋牋牋牋?//
increment
牋牋牋牋牋牋牋?
_Counter+= 1;
牋牋牋牋牋 爙
牋牋牋?}
牋?}
}
•
See example above for proper if syntax, other control statements
as follows:
switch (_Counter) {
牋?case 0:
牋牋牋?
//starting
牋牋牋?break;
牋?case
99:
牋牋牋?
//stopped
牋牋牋?break;
牋?default:
牋牋牋?
//counting or undefined
牋牋牋?break;
牋?}
for (int Index = 0; Index<9; ++Index) {
牋?// do
something
}
int Position=0;
while (Position<9) {
牋?++Position;
}
do {
牋?++Position;
} while (Position<10);
try {
牋?// do
something risky
} catch (Exception
e) {
牋?// handle
exception
} finally {
牋?//relase
resources
}
•
If a method has many parameters, write one parameter per line:
public static
void Ses_getStatistic(
牋?string Cookie,
牋?out int Sessions,
牋?out int Pages,
牋?out int Errors)
{
•
If a method with many long parameters is called, write one
parameter per line:
Ses_getStatistic(
牋?"ahsfd
ajkdhfjka dfjka dfkja kfjd akjsdfh akjshdf kjashdf lkja",
牋?out Sessions,
牋?out Pages,
牋?out Errors)
{
•
Order using statements alphabetically. List using statements
referencing the .Net framework first and separate them from other using
statements by one line.
using System;
using System.Data;
using System.Data.SqlClient;
using System.Data.SqlTypes;
using NUnit.Framework;
using PeterBox.General;
•
Declare local variables just before the first code line referring
them. If a local variable is used by different parts of a procedure, write the
declaration right after the method parameters.
The suggestions in this chapter improve code
readability and help also to avoid programming errors.
Try to avoid negation in if conditions. This
makes it difficult to understand code. Usually it抯 better to test for an error
condition and treat the error immediately.:
Bad:
if (!error1) {
牋?if (!error2)
牋?牋牋// do
processing as needed
牋?} else {
牋?// handle error 2
牋?}
} else {
牋?// handle
error 1
}
Good:
if (error1) {
牋?// handle
error 1
} else if (error2)
{
牋?// handle
error 2
} else {
牋?// do
processing as needed
}
If possible, use &&, which executes the
second part of the and condition only if the first part is true. This is
needed to avoid runtime error if the second part is defined only if the first
part is true. However, this problem is not easy to detect and code using &
instead of && will look like running correctly.
The & is only needed, if both parts of the and
condition need to be evaluated, which is only the case if the second part
changes some values (has side effects). In general, programming construct with
side effects should be avoided anyway, because they are difficult to
understand.
if ((Element==null) && (Element.Value==x)){
牋?// do
something
}
Try to be consistent how you write for loops.
There are good reasons to start a loop counter with 0 or with 1. But it is
confusing for other people reading your code, if you start sometimes with 0 and
sometimes with 1. It might also be a source of errors, because the testing for
the end condition is slightly difference, ?<?instead of ?lt;? It抯 very
easy to get the ??wrong, but quite difficult to detect the error. If all loop
counters start with 0, normally no ??is needed.
The recommendation is to start with 0, which makes especially
sense for array access.
Bad:
for (int x = 1;
x<=BorderPixels; ++x){
牋?// do
processing as needed
}
Good:
for (int x = 0;
x<BorderPixels; ++x){
牋?// do
processing as needed
}
Basically, XML comments would be a great tool for
commenting source code. However, since this is the first version, it has some
serious flaws:
•
Visual Studio creates a warning for each member without summary
description, if in project properties, configuration, build, XML
documentation file is used.
•
Comment cannot be placed on the same line after the member
definition, only on a preceding line. This is annoying if there are many
members.
•
The build web comment tool in Visual Studio supports only few of
the XML comments tags
•
If parameters in a method are changed, the <param> ?lt;/param>
don抰 get automatically updated.
•
Still many bugs
However, there are some benefits using XML comments,
especially using /// <summary>
for classes and members. Visual studio will use this for hints. Hopefully,
Visual Studio will improve in the future the support for XML comments.
Each class should have a method called Test:
static public
bool Test(){}
The test method is used to verify that the class still works
properly. It is run as part of every integration test. It returns true if every
test succeeds, otherwise wrong. If possible, exceptions should be caught and
reported by the test procedure. It should be the last method in a class.
•
Comment
•
Header. If there are many parameters in the method, put each
parameter on its own line. ???sample
•
If there are too many parameters, consider using a struct
instead. Combine only logically related parameter in one struct or class.
•
Test each parameter抯 range. During debugging, throw exception.
In release code, substitute the illegal value by something meaningful, if
possible, and write an error trace. If substitution is not possible, raise
exception.
•
Define local variables close to where they are used:
??? Sample
The test method should call every method of the
tested class, including constructors and destructors. Make calls with valid and
invalid parameters (value too low, minimum value, some reasonable values,
maximum value, value too big). If a method changes the state of a class, make
calls for every state in logical and illogical order.
Use properties instead of public fields. Keep the
code within properties short, put long code in its own method.
Use enumerations as much as possible, since they are
much easier to understand than numbers, but they have the speed advantage of
numbers over strings. Make sure that enumerations match with the corresponding
tables in the database (Undefined: 0, English: 1, German: 2). The code
has to guarantee (check) that no illegal values can be read from the database
or the user.
|
Identifier
|
Preferred Values
|
Description
|
Use
|
|
Min
|
0
|
Smallest allowed value
|
Value range test, loops
|
|
Undefined
|
0
|
Value not yet initialized (.Net writes 0 into not
initialized variables)
|
Replacement for null
|
|
Default
|
1
|
Use if value is not defined
|
Initialisation
|
|
Max
|
|
Highest allowed value
|
Value range test, loops
|
Example:
///
<summary>
/// Enumeration
of available languages.
///
</summary>
public enum Language{
牋牋?Min = 0,
牋牋?Undefined =
Min,?// for completeness sake, try to avoid use Undefined
牋牋?English,牋牋牋牋?
牋牋?Default =
English,
牋牋?German,
牋牋?Max = German
}
???
The exception handling infrastructure from .NET allows
writing code concentrating on the normal program flow, avoiding many if
statements handling unexpected error cases. A low level procedure throws
an exception if a problem is detected. .NET then unwinds the stack one calling
procedure at a time until it finds one that handles that exception.
A good overview over Exception Handling can be found
at:
•
?i>MSDN Library, .NET Development, Building Distributed
Applications, Architectural Topics, Exception Management in .NET.
•
MSDN Library, .NET Development, .NET Framework, Programming
with the .NET Framework, Handling and Throwing Exceptions, Best Practices for
Handling Exceptions
.NET organizes exceptions hierarchically through
class inheritance. All custom exceptions have to be derived from ApplicationException.
Their name should start with PBox to distinct them from other exceptions and to
avoid name collision.
Overview over part of exception hierarchy:
Exception
+-----SystemException
|牋牋 +-----IndexOutOfRangeException
|牋牋 +-----NullReferenceException
|牋牋 +-----InvalidOperationException
|牋牋 +-----ArgumentException
|牋牋 |牋牋 +-----ArgumentNullException
|牋牋 |牋牋 +-----ArgumentOutOfRangeException
|牋牋 +-----DataException?
for errors detected by ADO.NET, like datasets, etc.
|牋牋 | 牋牋+-----InvalidConstraintException
|牋牋 |牋牋 +-----MissingPrimaryKeyException
|牋牋 |牋牋 +-----NoNullAllowedException
|牋牋 |牋牋 +-----ReadOnlyException
|牋牋 |牋牋 +-----RowNotInTableException
|牋牋 +-----IOException
|牋牋 |牋牋 +-----DirectoryNotFoundException
|牋牋 |牋牋 +-----FileNotFoundException
|牋牋 +-----NullReferenceException
|牋牋 +-----SQLException牋
for errors detected by SQL server
+-----ApplicationException牋
|牋牋 +-----PeterBoxException牋
for PeterBox specific exceptions
|牋牋 |牋牋 +-----
PBoxDataBaseException
|牋牋 |牋牋牋牋牋
+-----PBoxNoDBAccessException牋 databse cannot be reached
|牋牋 |牋牋 牋牋牋+-----PBoxSPProblemException牋
Stored Procedure cannot be run
A custom exception needs at least three
constructors. Give the default constructor a default error message:
牋牋?///
<summary>
牋牋?/// No
acces to database exception
牋牋?///
</summary>
牋牋?public
class PBoxNoDBAccessException: PBoxDataBaseException{
牋牋牋牋牋?///
<summary> Default constructor </summary>
牋牋牋牋牋?public
PBoxNoDBAccessException(): base("No access to datbase") {
牋牋牋牋牋?}
牋牋?
牋牋牋牋牋?///
<summary> Constructor accepting a single string message</summary>
牋牋牋牋牋?///
<param name="message"></param>
牋牋牋牋牋?public
PBoxNoDBAccessException(string message): base(message) {
牋牋牋牋牋?}
牋牋牋牋牋?///
<summary>
牋牋牋牋牋?///
Constructor accepting a string message and an inner exception
牋牋牋牋牋?///
which will be wrapped by this custom exception class
牋牋牋牋牋?///
</summary>
牋牋牋牋牋?///
<param name="message"></param>
牋牋牋牋牋?///
<param name="innerException"></param>
牋牋牋牋牋?public
PBoxNoDBAccessException(string message,
牋牋牋牋牋牋牋牋?Exception
innerException): base(message, innerException) {
牋牋牋牋牋?}
牋牋?}
Any line of code can fail. It is the duty of the
programmer to make sure that any failure is detected, either automatically by
the system throwing an exception or programmed explicitly by throwing a custom exception.
Depending on the problem detected (for example missing parameter), it might
make sense to throw a system exception (ArgumentNullException) instead
of a custom exception.
The custom exception class should be defined in the
same file where it is thrown. If the custom exception class has its own
properties, remember to update the PeterBoxException.ToString method in DBHandler
accordingly.
Each exception must generate an error message
containing an explanation what was expected, what happened instead and keys
helping finding involved data records.
Example ???
Remember, it is bad programming style to used
exceptions to handle situations occurring quite often. Exception handling has
quite some overhead and should be used for exceptions, not regular cases.
•
Cleanup of resources
•
Tracing of errors
•
Debug / release code handling
•
Display of unhandled exceptions
Many simple projects use just one thread. But when a
project gets more complicate, often more than one thread is used, e.g. to
respond quickly to user request even if heavy computation is performed.
Multitasking programming is tricky, because errors can easily be produced which
can hardly be found by testing and occur only sporadically. There are 2 kind of
errors that occur:
1) Two threads try to access the same object.
If one thread is reading an object which is changed the same time by another
thread, the value read might be corrupted. To protect against this, locking is
used, i.e. the one who wants to write locks the object and delays the reading
until writing is finished. It抯 easily to forget locking and all tests will run
fine. But once in a while data gets corrupted and it抯 extremely difficult to find
the reason !
The .Net library protects all static methods and
properties against multithreading problems, but not non static members, because
locking can cause severe performance and other problems. However, objects are
often accessed by only one single thread, in which case no locking is needed. For
the case that an object is accessed by several threads, .Net provides locking
mechanisms for many of its classes.
2) Locking, the solution for protection
against concurrent access of multiple threads, introduces 2 new problems of its
own:
2a) Deadlock: Thread 1 locks first object A,
then object B. Another Thread 2 locks first object B, then object A. Usually
this will run just fine (no problems during testing), but it might happen that
both threats run at the same time and block each other indefinitely. Thread 1
can lock object A, but waits forever to get a lock for object B.
Deadlocks can usually be avoided by locking objects
in the same sequence, i.e. task 1 and task 2 lock first object B.
2b) Race Condition: Normally it抯 not
advisable to hold a lock for a long time (e.g. to access a file), because the
other thread might be able to do some meaningfull work in the meantime, after
all that抯 the reason why multithreading. However, if thread locks and reads object
A, releases the lock, does some work and then locks object A again for writing,
object A might be changed by thread 2 in the meantime and object A gets
corrupted by the write of task 1. Be aware that there are a great variety of
race conditions !
•
Use multithreading wisely (sparingly)
•
Avoids multithreading problems by using queues, etc.
•
Limit the amount of information modified by several threats as
much as possible. Often it is better to use a flag protecting the access to an
object than locking the object and its processing.
•
If possible, use Interlocked instead of Montor or
lock lock statement. Interlock has very little overhead. It allows to
change an integer value without getting interrupted. This is usually all
functionally needed if one wants to implement locking instead of using library
provided solutions.
???
???
Good code is written in such a way that the system
keeps running if an error occurs (i.e. try, catch statements). Even better code
writes information about the problem encountered (i.e. what it was and where
exactly it occurred) to a trace (error log). The user might hardly notice that
there was a problem, but the developer gets automatically all information
needed to analise the problem.
A System should react differently whether it is being
debugged or operational. If a debugger is used, execution should stop where the
problem occurred (breakpoint). If it抯 tested without a debugger running, all
problems should be written to a file (trace, error log). If the system is
operational, most problems will be traced too, however the developer might
decide not to trace some minor problems.
.NET provides generic tools for tracing, but they
don抰 fulfill the above requirements (breakpoint when in debugger, tracing when
not in debugger). This chapter describes a better solution.
For an additional tracing tool provided by ASP.NET
see chapter 6.9 ASP.NET Tracing.
Per default, a tracing information is written to the
output window when the debugger is running
Screen
File
EventLog
Special tracing classes are used for Testing (see
???) and ASP.NET (see ???).
Debug only code
[Conditional("DEBUG")]
牋牋牋牋牋?public
void MyDebugMethod ()
牋牋牋牋牋?{
牋牋牋牋牋牋牋牋?
// debugging code here...
牋牋牋牋牋?}牋
Additional debug through configuration change:
牋牋牋?
<configuration>牋牋牋?
牋牋牋牋牋牋牋??
牋牋牋牋牋?
<system.diagnostics>
牋牋牋牋牋牋牋牋牋牋牋
<switches>
牋牋牋牋牋牋牋牋?
<add name="DebugSwitch" value="1" />
牋牋牋牋牋牋牋?
</switches>
牋牋牋牋牋?
</system.diagnostics>
牋牋牋牋
</configuration>
It is a good idea to make these switches static so
that they are read in only once. After they are declared debugging code can be
written as shown below?
牋牋?public
static BooleanSwitch debug = new BooleanSwitch("DebugSwitch",
"Debug Switch");?
牋牋牋牋牋?
if(debug.Enabled)
牋牋牋牋牋?{
牋 牋牋牋牋牋牋牋?/
debugging action here...
牋牋牋牋牋?}?
???
Use the debug and release
configuration to differentiate between development and production code. No code
change should be needed to switch from debug to release code.
•
Is the code well commented and are the comments up to date ?
•
Is for every constant number a constant or enumeration defined ?
•
Are all variables defined at the proper place (as local as
possible) and with the correct modifiers ?
•
Proper detection of error conditions ?
- Is
code written to detect problems ?
- Are
exceptions caught as needed ?
- Are
problems reported to log or user ?
- If
an exception is handled locally, does the code properly rethrow exceptions for
all not handled exceptions ?
•
Nested if statements:
- Are
error conditions detected and handled first ?
- Has
every if statement an else clause ?
•
Are conditional and &&, respective or || used instead of
&, | ?
•
Is every property, non test method, etc. covered by a test method
?
•
For objects accessed by multiple threads:
- Is
proper locking used ?
- Are
deadlocks prevented ?
- Are
race conditions prevented ?
•
Is code written in the right layer (separation of user interface
/ middle layer / database access /?general library)
???
Chapters Overview