You are on page 1of 13

Defensive coding in C#

What is defensive coding?

- An approach to improve software and source code, in terms of


o general quality - reducing the number of software bugs and problems =>
automated code testing;
o making the source code comprehensible - the source code should be readable and
understandable so it is approved in a code audit => clean code; the process of
restructuring code without changing its behavior is called refactoring;
o making the software behave in a predictable manner despite unexpected inputs or
user actions => validation + exception handling

1
What clean code means?

- Easy to read - If the code is easier to read, you can more readily see potential coding errors
or bugs. If the code is easier to read, you can more easily see patterns in the code, which can
lead to more refactoring. If the code is easier to read, other developers will understand your
original intent and not introduce bugs over time. Code that is easier to read is more tolerant
to quick changes made under pressure and incremental changes made over time by people
other than you, including the future version of yourself, who no longer remembers the code
- Clear intent - Clean code has a clear purpose; its structure and simple names add to that
understanding
- Simple - Clean code is simple. If the code is too complex or too elegant to be understood, it
will be too difficult to maintain. Code that is complex can also lead to more bugs over time
- Minimal - Clean coding is minimal. Each part of the code does one thing and does that thing
well, and dependencies between code are kept to a minimum as well
- Thoughtful - Most importantly, clean code is thoughtful. Code location is carefully
considered, code is organized into logical classes and into clean methods, classes are
logically organized into layers, like a user interface layer, business logic layer, data access
layer, and so on. Thought is also given to each line of code as it it is written

Clean, Testable, and Predictable Methods have the following characteristics:

- each method has a Clear Single Purpose; a method should do one thing, and it should do
that one thing well. A method with a single purpose is easier to understand. That makes it
easier to write the method without introducing bugs. It makes it easier to debug if a bug is
found, and it makes it easier to modify the code without introducing new bugs. The tests are
easier to write because they can focus on that single purpose, and a single purpose method
is predictable because you can give it a set of inputs and expect a consistent set of outputs
- has a Good Name. That name should clearly identify the purpose. Code with a clear name
has clear intent on when and why it should be used
- should contain Focused Code; all of the code in that method should be focused on this
single purpose. The code itself, then, makes the intent of the method clear, minimizing the
need for extensive comments, and it should not have any unexpected side effects; that is to
say it should not contain code that performs unexpected operations, such as modifying
global data, modifying incoming parameters, or calling unrelated methods
- should have a reasonably Short Length. Less code means fewer places for bugs and less
testing code. Plus, code that is long may suggest that the method does not have a single
purpose
- needs to be testable. You should be able to call the method from an Automated Code Test.
Using an automated code tests increases the code quality and minimizes bugs introduced
when adding additional features or during maintenance activities.
- has to return a Predictable Result. The code should not react strangely or unexpectedly with
providing invalid information or if an unforeseen condition occurs.

2
A Design Pattern is just a predefined way to build a set of code for a specific purpose. The repository
pattern includes a specific set of classes that handle the data access for your application. Each of these
classes is prefixed with the entity name and has repository as the suffix. The sole purpose of each
repository class is to hide the details of accessing the data in one place separate from the entity classes.
Each repository class contains the code to retrieve the data and the code to save the data for the entity.
The entity classes can then focus on information and operations required for the application logic, and
do not need to contain code to access the database.

Validating the method parameters

- The first step in making a method predictable is to add guards to check incoming
parameters, and only open the gates to valid input; the guards can keep out the garbage
and provide a more predictable response to invalid inputs. The guards can check for invalid,
or unexpected values, and only let in parameters that are valid. Code statements written at
the top of the method that validate the incoming parameters are appropriately called Guard
Clauses.
- minimize the number of parameters that your method has; the fewer the number of
parameters the better. There are less values to validate, less chance for introducing bugs,
and it's easier to test.
- You can also define a standard convention for your parameter order. For example, you
could order them from most important to least important. The key is to remain consistent.
Why does this matter? Well, it has less of a chance of being used incorrectly, less of a chance
for introducing bugs over time.
- By following these techniques, you can minimize problems caused by your parameters.

Automated code Testing

- Is one of the most important practice of defensive coding. It relies on creating Unit Tests
- There is no better defense for the quality of your code than a set of automated code tests
- Automated code testing involves exercising code and testing its behavior by writing more
code, so you have a set of code that tests your original code.
- Automated code tests are Structured. Tests often follow a very specific structure. One of
them is the AAA structure:
o Arrange the information that the test needs
o Act to execute the method under test
o Assert the results
- Automated code tests are Self-documented. Each test should be self-explanatory
- Automated tests are Automatic. The automation comes from a testing framework (e.g:
MSTest, that comes with Visual Studio or other testing frameworks such as NUnit).
- Automated code tests are Repeatable. One of the key points of writing automated code
tests is the ability to run them again, again, and again. Each time they are run they should
produce the same result.

3
Unit Testing

- Unit tests are tests written and executed by the developer as part of the overall coding
process.
- The goal of a unit test is to isolate each unit of code in an application and verify that it
behaves as expected.
- Benefits of unit testing:
o Save time
o Find bugs faster
o Allows you to refactor safely
o Enhance your value
o Minimize interruptions
- There are two basic ways of writing Unit Tests:
o Code First - you write the code first based on the requirements of the project, or
maybe you already have existing code, and you write unit tests to test that code
o Test First - you write the tests first based on the requirements, then you write the
code to pass those tests
- Tips when creating Unit Tests:
o Start with a test for each set of Valid Inputs and verify that the correct output is
generated for those valid inputs
o Also define tests for each set of Invalid Inputs, and verify that the code
appropriately handled the invalid inputs
o There should be at least one unit test for each guard clause; that ensures that the
method is appropriately guarded and that the guard clauses are working
appropriately
o Finally, define a test for each Assumption
o Any time you find a bug or someone else finds a bug that managed to slip past your
existing test, or from some bizarre input that a user tried that you did not anticipate,
add a new test. The new test will ensure that the bug fix was successful, it will also
prevent the bug from appearing again

Defending your methods – returning predictable results

- Methods are the functions in a C# class. They are the fundamental building blocks of a C#
application. As such, they are the best place to start strengthening your code's defenses.
- There are 4 types of method results:
o returning a Value - The benefits of returning a value are that the calling code can
readily check the return value; it makes the code very easy to read and to see what's
going on here. The downside of returning a value is that the calling code needs to
ensure that it checks the return value. It could be easy for the calling code to forget,
and then it could continue with an invalid value.
o returning Exceptions - Those exceptions can provide specific messages as to what is
wrong

4
o returning Multiple Values - If returning one value is not sufficient, and throwing
exceptions is not quite right, especially in validation cases, what should be done?
How can you return both a value and more detailed messages? Well, you can return
multiple values from a method.
 One way to return multiple values from a method is to use ref parameters.
The ref keyword causes an argument to be passed by reference not by
value; that means that any change to the parameter in the method is
recognized by the calling method. – not recommended, not intuitive. Why
we need to pass an additional parameter?
 Another way is using out parameters. Out parameters are similar to ref
parameters, but they don't need to be initialized before being passed in –
not recommended, not intuitive. Why we need to pass an additional
parameter?
 Another way is using tuples. A tuple is a data structure that allows for
multiple elements. The .NET Framework tuple class does not represent a
tuple; rather it provides methods for creating tuples So, a tuple is a nice way
to be able to group a set of data and pass it out. However, it's very clumsy
to work with, and it's not intuitive at all what's being returned.
 A better way is using objects. You can define an object for return Values (eg.
OperationResult – with 2 properties, a boolean defining success or failure,
and a list of messages to pass those message back out of the method. Any
method can then return an instance of that class). So, by returning an
instance of a standardized class like OperationResult, you can have a very
consistent way that you handle validation and other types of operations
throughout your application. The nice thing is then that information could
be displayed to the user if it is something to do with the user like invalid
information that they entered, or you can log it if the information has to do
with a system type of problem.

o returning Null - The null keyword in C# represents a null reference; basically a
reference that does not refer to any object
 A method that returns a string could return a null. For example, if a method
returns a message and there is no message to return, the method could
return a null
 A method that returns a value type could instead return a nullable type. A
nullable type is a structure that contains both the value of the type and a
null value. For example, if a method returns a decimal value it could instead
return a nullable of decimal, written in shorthand with a ?. The method
could then return a null instead of 0 if the value could not be calculated.
 A method that returns an instance could return a null. The default value of
any reference type is null, and null can be used to indicate that the
reference does not refer to any object

5
- What to choose?
o Return simple Values with performing a calculation. The calling code would expect
that the result would be the result of the calculation. You can then use guard
clauses and throw exceptions if the parameters are invalid
o Return Exceptions when validating incoming parameters. Return exceptions also
when exceptional conditions occur, such as a network issue, a database error, or
other failure.
o Return Multiple Values, especially on objects such as OperationResult, when
performing general data validation. The method can return both a success/fail
indication, and a message or messages with validation error details. You can also
return multiple values when performing an operation such as the Add. The method
can return both a success/fail indication and operational status messages. Those
messages could then be logged or displayed.
o Return Nulls when returning a reference that is not set, such as the case of
returning a customer when no customer is found. Or consider using the null object
pattern. When returning a list or collection, decide whether your convention will be
to return an empty list or to return a null. Returning an empty list won't then cause
issues if later iterating over the list.

Defending your code constructs

- Local variable Declarations best practices


o Location
 A variable within a method should be declared as close to where it is needed
as possible – means that there is less code to look at in order to understand
the purpose and use of the variable; clarifies the intent and minimizez the
change of future errors
o Naming
 Use good clear Naming for your variables
 Use consistent names
 use CamelCase to distinguish local variable names from properties or
methods. CamelCase implies that the first word of a variable is lowercase
and every other word is uppercase
o Initialization
 Where possible, Initialize variables at the point of declaration. Declaring a
variable in one place, assigning it further down, and using it even further
down opens up the opportunity for errors both now and in maintenance
o var keyword
 use var especially when the type is already clearly defined on the right-hand
side. Use var when it makes sense and does not detract from readability
 The var keyword is also useful when dealing with complex return types such
as with LINQ, because you don't have to figure out exactly what's being
returned, especially in cases where you don't really care what's being
returned, you just want to iterate through the result

6
- If Statements best practices
o Use braces
 When you have an if block with one statement, the best practice is to either
add braces or put the code on one line
 Create the shortest, least syntactically noisy construct that is unambiguous
to anyone reading it quickly and maintaining it over time
o Else
 Consider adding an ‘else’ clause
o Think positive
 It is often easier to think of conditionals in the Positive. For example, that a
value is null or a success field is true, as opposed to that value is not null or
not true.
 The recommendation is, when feasible, define the condition in the positive
o Nested if stataments
 If statements within if statements within if statements quickly become hard
to read and therefore make the intent difficult to discern; they also make
the code more prone to error and more difficult to maintain, and the code is
harder to test because each if statement adds more possible paths through
the code.
 If you have if statements within if statements within if statements, consider
refactoring some of the logic into separate methods.
 By refactoring nested if statements, your code will be cleaner, clearer, and
easier to test.

- Switch Statements best practices


o Order
 Put the most common cases first
 If there are more common cases, it may make sense to put the cases in
numerical order. The important point is to put the cases in a logical
sequence so it is easy to glean the purpose of the switch statement and the
paths it defines
o Default case
 It is always a good idea to use a default case. The default case will execute if
there is no match to the other case statements. The default case should
either throw an exception, like is done here, or take some default action
such as log the issue and then break
 By always having a default case in a switch statement, your switch
statement will be more predictable over time
o Simple Actions
 The code in each statement should be a Simple Action.
 If the case is complex, then the case should call a method that performs the
complex action.

7
 By including only simple actions, the entire switch block will be a reasonable
size; this makes it easier to understand and modify as necessary over time.
- Enums best practices - An enumerated type defined with the Enum keyword in C# is a type
of data that allows each member to be described in words. Enums should be used whenever
you have a discrete and possibly disjoint set of values that you want to express in words
o Avoid magic numbers
 Magic numbers are numeric values used directly in the code. The use of
unnamed magic numbers in code obscures the developer's intent, increases
opportunities for subtle errors, and makes maintenance and enhancements
more difficult
 When those magic numbers are defined with a related set of values, they
can instead be given meaningful names using an Enum
o Nesting
 Usually it's best to define an enum directly within a namespace, so that all
classes in the namespace can access it more conveniently. Having it directly
in the namespace also limits potential naming collisions with class member
names
 Do not nest enums within the class body!
o IsDefined
 It might be good practice to instead determine whether the value is a valid
enum before switching on it. One way to do that is with IsDefined. IsDefined
returns an indication of whether the value exists in a specified enum
 However, it is normally recommended that IsDefined not be used. Why?
Because Is uses reflection, making a call to IsDefined a very expensive call in
terms of performance and CPU
o TryParse
 The TryParse method converts the string representation of the name or the
numeric value to its equivalent enumerated value. The return value of the
TryParse method is a Boolean indicating success or failure
o Conclusions: Use enums instead of magic numbers; don't nest an enum inside a
class; and use TryParse instead of IsDefined.

- Casting best practices - Casting is the process of changing information from one type to
another
o Cast carefully
 Be carefull at Blind Casting because it casts without first confirming that the
cast will be valid. (eg: ((Button)sender).Text = “Processing”; ). Check the
value first
 One way to check the value is to use the as operator. Eg:
Button button = sender as Button;
Using the as keyword is called a Safe Cast. If the cast fails, the code
won't throw an exception, rather it will assign a null value, so you can then
check for that null value. The as guarantees that no matter what happens
the code won't crash

8
 When using as, always use an if to check for the null

Conclusions - Defending your code constructs

To protect your code constructs from unexpected errors and botched changes over time, we identified
three key considerations:

- Minimize coding defects by following Best Practice guidelines


- Minimize functional defects through Clarity of Intent
- Maximize Predictability with code constructs that can't easily fail as the code changes over
time.

Asserts, errors, and exceptions

- Asserts provide issue notifications to the developer when building, testing, and maintaining
code.
- Errors in this context are anticipated issues and exceptions. These include user entry errors,
invalid or missing data such as from a database, code construct issues such as a switch
statement with no matching case, system issues such as file not found. These issues can all
be anticipated and handled defensively.
- Exceptions refer to unexpected issues and unhandled exceptions, things such as a dropped
internet connection, unexpected database error, or a system problem such as out of disk
space

Error prevention and handling

- Good error and exception handling techniques are key to a strong defensive coding strategy
- There is no one size fits all approach to error prevention and handling. Some of the
techniques covered include:
o Using appropriate controls. Prevent invalid user entry through appropriate user
interface controls, and leverage control binding features for validation where
feasible.
o Where possible, use code to prevent exceptions. Null check to prevent possible
exceptions.

o Ensure that the code confirms the state of Invariants, such as global or class-level
data. In most cases, Invariants are checked using Debug.Assert, but note that the
Assert methods are only executed when running within the Debug configuration.

9
o Validate the parameters of your methods using guard clauses. If those guard clauses throw
exceptions, like these do here, add a try catch block around the code that calls the method.

o Catch anticipated exceptions from code constructs. For example, this switch statement has a
default, so if the paymentTypeOption is not credit card or Paypal, the switch will fall through to the
default condition and throw an exception.

o Catch anticipated exceptions from system operations. The example that we looked at was an email
example, so we have a try block around some code here that you could imagine sends an email, and
the catch block will catch any anticipated exceptions from the emailing operation.

o Finally, add a Global Exception Handler as appropriate for your type of application.

Handling Anticipated Exceptions – best practices

o Don't let an anticipated exception propagate up to the Global Exception Handler.


o Rather, catch the error locally, near to where the exception was generated.
o Ensure that caught exceptions are actually handled; don't catch an exception unless you plan to
have some code in the catch block that will actually do something with it.
o Catch Specific Exceptions. So if a guard clause throws an ArgumentException, catch only the
ArgumentException, not the general exception. This allows unexpected errors to then be caught
by the Global Exception Handler.
o Log exceptions. Use a log to write information about the exception and the state of the system
before the exception occurred. This log provides a starting point for researching the cause of the

10
error. The only exception to this practice is when validating user entry. Often, user entry errors
do not need to be logged.
o Don't put try catch blocks around every bit of code; only add exception handling around
conditions where you anticipate an exception and plan to do something about it.
o Use throw, not throw ex, to propagate an exception. If the code needs to perform some action
such as logging, yet still notify the calling code of an exception, you can catch the exception,
perform the action, and then re-throw to propagate the exception of the call stack.

What is legacy code?


o Code developed with older technologies
o Code inherited from an older version of the application
o Code inherited from someone else
o Code that is no longer under development, only patched, and code that has an
excessive amount of technical debt.

Modifying Legacy code

- Evaluate assumptions - Before you actually modify the legacy code, look at each method
that you have to change and evaluate its assumptions. What are the valid and invalid inputs,
what assumptions are made regarding Invariants, that is global or class-level data, what are
the possible return values
- Write automated code tests to test those assumptions. The tests then provide the
documentation of the method's assumptions, because who knows where the
documentation for this legacy code is if there was any, or if it is still accurate. If it's not
possible to write an automated code test, say for example that the code is entirely behind a
click event, then perform the minimum possible refactor in order to write a test.
- Ensure that the tests pass - These tests should cover each of the code's assumptions
defined earlier
- Refactor - When you have the basic legacy code under test, then you can start to refactor
into appropriate classes and clean methods. As you refactor, ensure that your tests still pass.
So this may be a cycle;
- Make the change - Once you have the code refactored as you like, then make your
requested change. That change may require additional unit tests or changes to the unit test
depending on the scope of the change that was originally requested. Once the change is
complete ensure that your tests still pass.

11
Fixing bugs in lgacy code
- Evaluate the bug
- Write an automated code test that reproduces the bug. This may not be possible if the code
is behind event handlers. If that is the case, then refactoring is required as per the prior
slide. And depending on your project, the bug, and the schedule, you may not be able to do
that refactoring. And if this bug is in code that is not modified often, it may not be worth the
time. But if this legacy code is still being updated frequently, then it may be worth it.
- Test fails - The automated code tests that you wrote to reproduce the bug should of course
fail; that's because they're reproducing the bug.
- Fix the issue - You should then fix the issue and rerun the tests so that they pass. At that
point, you can take a moment to revisit the assumptions and assertions that caused the
issue to begin with. Should the existing code be modified further to prevent similar types of
problems? Should this part of the code be refactored to make it clearer? If there is
refactoring done, ensure that the tests still pass.
- When working with legacy code, clean up the code you are working on, refactoring where
feasible, and adding automated code tests where possible.

12
13

You might also like