Showing posts with label Engineering Practices. Show all posts
Showing posts with label Engineering Practices. Show all posts

Monday, April 6, 2020

Software Development Best Practices

SOLID PRINCIPLES

S - Single Responsibility Principle

An element of code (such as module, class or function) should do one primary thing and nothing else. That is, do not build Swiss army knife code elements. For example, a function that changes the case of a string should not trim it as well.

O - Open/Closed Principle

Do not alter the foundational code (such as base classes and common functions) but rather compose or extend them. Modules, classes and functions should be open for extension but closed for modification. The initial functionality should not be changed but may be extended via composition or inheritance.

L - Liskov Substitution Principle

Program to interfaces. Your business logic should be programmed against class interfaces (not concrete implementation of classes). A subclass should have "is a" relationship with the base class. Wherever you can use a base class, you should be able to use a derived class.

I - Interface Segregation Principle

Interfaces should be minimal and tailored to the function they need to perform. Minimize the number of methods in a given interface. Instead, expose multiple smaller interfaces. Clients can choose to implement just the interfaces they require.

D - Dependency Inversion Principle

Do not use concrete implementations directly. High level modules should not depend on lower level modules. Constructors for higher level modules should accept abstractions (interfaces) of lower level modules. At runtime, Dependency Container injects a concrete instance of the abstraction into the constructor.

SEPARATION OF CONCERNS

Similar to Single Responsibility Principle in SOLID but applied at a macro level (Systems, Modules, Sub-Systems). For example, a payment processing module should only perform payment processing and nothing else (i.e. it should not calculate invoices or bills).

DRY - DON'T REPEAT YOURSELF

Do not implement the same logic twice. Put it in a common module, library, class or function and use it everywhere.

Factor out common code into helper classes, methods and components. This makes the testing effort more concise and helps track down bugs to a single place in code instead of several repeating code sections.

MINIMIZE THE CODE YOU WRITE

Minimize the amount of code you have to write to solve a problem. The less code there is, the lesser the chance of buggy code. Code Minimization goes beyond DRY in that you may opt to look at alternate design approaches that minimize the amount of code you have to write to begin with. Remember code is a liability! The less code you have to solve the problem, the better. Examples:

  • Use a well established library or software package to solve the problem instead of writing code to do the same.
  • Use declarative code instead of imperative code. For example in .Net, instead of using for loops use LINQ.
  • Instead of using switch/case statements to select a value or action use a table driven approach to look up a value or execute an action (a database table or an in-memory data structure should do the trick).

YAGNI - YOU AREN'T GONNA NEED IT

Do not implement code that is not immediately needed now even if you think you might need it in the future (you probably won't or if you do, you will need something entirely different).

Don't anticipate features and try to adapt your design or code to the features that are not necessary to complete the stories at hand. The chances are that either the requirements will change over time or the features you anticipate are never really needed. Do the simplest thing that will work.

USE DESIGN PATTERNS

When writing code check if there is a well-known design pattern that can be used to make implementation more robust and maintainable. Design Pattern Examples

ORGANIZE YOUR CODE WELL

Use polymorphism (abstract class + derived classes) to implement specific behaviors instead of conditionals sprinkled all over the code to handle differing behaviors. Use a single switch to instantiate the polymorphic derived class of the desired behavior and have the controlling logic invoke the behavior in a general fashion that is applicable to the entire family of the derived polymorphic classes.

Keep methods (functions) short and single-purposed. Complex methods should be no longer than 20 lines. Simpler methods should be no longer than 10 lines.

Do not pass too many parameters into a method. When several parameters must be passed into a method, create a request object that contains the parameters and pass the request object into the method.

Name the methods purposefully. The name should clearly reflect what the method does. A comment for the method should ideally be unnecessary. Write code that reads like prose. Comments should not be necessary when reading the code. Refactor logic into smaller methods with descriptive names. When reading the main method, the reader should be able deduce the purpose of the helper methods being called and understand what the main method is trying to accomplish without reading the implementation of the helper methods.

Do not enter who changed the code or the what the ticket or issue number was in code comments. Such information belongs in the source control check-in comments.

GET HELP FROM THE COMPILER

Use strongly type variables and constructs (generics, for example) to detect problems as early as possible (at compile time instead of runtime). Use enumerations instead of string values. Use named constants instead of string literals. Pay attention to compiler warnings.

TESTABILITY

Write testable code

Structure and implement your code so that it is easily testable. This mind set will naturally help you write code that is loosely coupled and minimizes inter-dependencies. The loose coupling is essential for testing a class or a component in isolation as is necessary when writing Unit Tests.

Write automated tests

By all means write automated Unit Tests. Better yet, take the Test Driven Development (TDD) route. That is, devise and write your tests before you start developing. TDD will greatly help in the design of the system in addition to the more obvious benefit of having a more complete set of automated tests. Having good automated unit tests allow us to refactor our code confidently.

Integrate often - Fail fast

Don't sit on pending changes. Check them in right away to find any incompatible changes. It is better to fail fast and correct the problem earlier in the cycle than to sit on a change and having to figure out why things don't work due to a change you made last week.

REFACTORING

Groom (clean up) code without changing the underlying functionality often. Refactoring is essential for long term maintenance of the code and to facilitate future enhancements.

Over the time, the code will naturally decay. You will see violations of DRY, Minimize Code and even YAGNI principles. It is imperative to take time to periodically refactor the code to ensure its maintainability over time. Remove and discard dead code. Such code only adds noise to your code base. The lower the noise-to-signal (code) ratio, the better.

Tuesday, June 23, 2015

Using Moq and Unity together for testing

Moq lets you generate class (code) with prescribed behavior.
  • Moq needs an interface
  • Moq api is used to set the behavior of the generated class
Unity lets you resolve an interface to a concrete implementation.
  • Register an interface with the desired concrete implementation
  • The concrete implementation can be an instance of class generated by Moq for testing purposes
  • In production, the interface is configured to resolve to real implementation classes

ASP.Net MVC: Using Unity IOC with MVC

1. Install Unity using Nuget

Unity bootstrapper for ASP.NET MVC
(This will install all dependencies as well)

2. MVC Unity adds the following files in App_Start

UnityMvcActivator.cs - Provides the bootstrapping for integrating Unity with ASP.NET MVC. The Start() and Shutdown() methods are automatically called when MVC application starts and stops (uses WebActivatorEx to do this automatically).

UnityConfig.cs - This is where you register the type mappings.
The registration is done in RegisterTypes() method. It can be done with code here or can be loaded from web.config.

Sunday, June 7, 2015

Using Moq for Unit Testing


What is it?

  • Generate objects (instances) based on interfaces
  • Declaratively define the behavior of generated object
  • Use Nuget to add Moq to your project

Example

  • We want to unit test the invoice class.
  • We want to "fix" the behavior of shipping and tax calculation classes so that we can truly exercise and test the functionality of invoice class in isolation

Code to Test


public class Invoice
    {
        IShippingCalc _shippingCalc;
        ITaxCalc _taxCalc;
        decimal _subTotal;
        string _state;

        public Invoice(decimal subTotal, string state, IShippingCalc shippingCalc,
            ITaxCalc taxCalc)
        {
            this._state = state;
            this._shippingCalc = shippingCalc;
            this._taxCalc = taxCalc;
            this._subTotal = subTotal;
        }
        public decimal TotalDue
        {
            get
            {
                return _subTotal + _shippingCalc.Calc(_state) + _taxCalc.CalcTax(_state, _subTotal);
            }
        }
    }

    public interface IShippingCalc
    {
        decimal Calc(string state);
    }

    public interface ITaxCalc
    {
        decimal CalcTax(string state, decimal taxableAmount);
    }

Unit Test


  • We create "mocks" for IShippingCalc and ITaxCalc using Moq
  • We define the behavior of these mocked objects so that we can verify the behavior of the invoice class

public void BasicInvoiceTest()
{
var shippingMoq = new Mock<IShippingCalc>();
shippingMoq.Setup(
m => m.Calc(
It.Is<string>(state => 
state.Equals("ca", StringComparison.InvariantCultureIgnoreCase)))).Returns(5);
shippingMoq.Setup(
  m => m.Calc(
  It.Is<string>(state =>
  !state.Equals("ca", StringComparison.InvariantCultureIgnoreCase)))).Returns(10);

var taxMoq = new Mock<ITaxCalc>();
taxMoq.Setup(   // no sales tax for other states
m => m.CalcTax(It.IsAny<String>(), It.IsAny<decimal>()))
.Returns<string, decimal>((s, d) => 0);

taxMoq.Setup(   // 8% sales tax for CA
m => m.CalcTax(It.IsIn("ca", "CA", "Ca", "ca"), It.IsAny<decimal>()))
.Returns((string state, decimal taxableAmount) => taxableAmount * .08M);

var subTotal = 100;
var invoiceCA = new Invoice(subTotal, "ca", shippingMoq.Object, taxMoq.Object);
Assert.AreEqual((100 + 8 + 5), invoiceCA.TotalDue, "subTotal + Sales Tax + $5 shipping");

var invoiceOther = new Invoice(subTotal, "ma", shippingMoq.Object, taxMoq.Object);
Assert.AreEqual((100 + 0 + 10), invoiceOther.TotalDue, "subTotal + NO Tax + $10 shipping");
}

Sunday, November 24, 2013

Agile Coding Principles and Best Practices

YAGNI - You aren't going to need it
Don't anticipate features and try to adapt your design or code to the features that are not necessary to complete the stories at hand. The chances are that either the requirements will change over time or the features you anticipate are never really needed. Do the simplest thing that will work.

DRY - Don't repeat yourself
Factor out common code into helper classes, methods and components. This makes the testing effort more concise and helps track down bugs to a single place in code instead of several repeating code sections.

Minimize Code
Minimize the amount of code you have to write to solve a problem. The less code there is, the lesser the chance of buggy code. Code Minimization goes beyond DRY in that you may opt to look at alternate design approaches that minimize the amount of code you have to write to begin with. Remember code is a liability! The less code you have to solve the problem, the better!

  • Use declarative code instead of imperative code. For example in .Net, instead of using for loops use LINQ
  • Instead of using switch/case statements to select a value or action use a table driven approach to look up a value or execute an action (a database table or an in-memory data structure should do the trick).



Organize your code well
Use polymorphism (abstract class + derived classes) to implement specific behaviors instead of conditionals sprinkled all over the code to handle differing behaviors. Use a single switch to instantiate the polymorphic derived class of the desired behavior and have the controlling logic invoke the behavior in a general fashion that is applicable to the entire family of the derived polymorphic classes.

Keep methods (functions) short and single-purposed. Complex methods should be no longer than 20 lines. Simpler methods should be no longer than 10 lines.

Do not pass too many parameters into a method. When several parameters must be passed into a method, create a request object that contains the parameters and pass the request object into the method.

Name the methods purposefully. The name should clearly reflect what the method does. A comment for the method should ideally be unnecessary. Write code that reads like prose. Comments should not be necessary when reading the code. Refactor logic into smaller methods with descriptive names. When reading the main method, the reader should be able deduce the purpose of the helper methods being called and understand what the main method is trying to accomplish without reading the implementation of the helper methods.

Do not enter who changed the code or the what the ticket or issue number was in code comments. Such information belongs in the source control check-in comments.


Take advantage of the compiler
Use strongly type variables and constructs (generics, for example) to detect problems as early as possible (at compile time instead of runtime). Use enumerations instead of string values. Use named constants instead of string literals. Pay attention to compiler warnings.


Write testable code
Structure and implement your code so that it is easily testable. This mind set will naturally help you write code that is loosely coupled and minimizes interdependencies. The loose coupling is essential for testing a class or a component in isolation as is necessary when writing Unit Tests. 

Write automated tests
By all means write automated Unit Tests. Better yet, take the Test Driven Development (TDD) route. That is, devise and write your tests before you start developing. TDD will greatly help in the design of the system in addition to the more obvious benefit of having a more complete set of automated tests. Having good automated unit tests allow us to refactor our code confidently.

Refactor, Refactor, Refactor
Over the time, the code will naturally decay. You will see violations of DRY, Minimize Code and even YAGNI principles. It is imperative to take time to periodically refactor the code to ensure its maintainability over time. Remove and discard dead code. Such code only adds noise to your code base. The lower the noise-to-signal (code) ratio, the better.

Integrate often - Fail fast
Don't sit on pending changes. Check them in right away to find any incompatible changes. It is better to fail fast and correct the problem earlier in the cycle than to sit on a change and having to figure out why things don't work due to a change you made last week.

Wednesday, August 28, 2013

Using the Scientific Method and Bisection for Debugging Software

Scientific Method

  1. Study available data (test results, and program text with a critical eye). Ask: what could have gone wrong?
  2. Form a hypothesis as to why the program did not behave it should have based on the analysis of available data
  3. Design and run a repeatable experiment (write a test)
  4. The experiment must have the potential to refute the hypothesis - If the experiment is not capable of proving the hypothesis incorrect, it is of no use!
  5. The expected (correct) result of the experiment must be established before the test
  6. Run the experiment and check if it meets the expectation

Debugging

  • Approach debugging systematically
  • The objective is to reduce the search space - i.e. amount of the source code and data we need to look at to find the source of problem
  • Use bisection (as used in binary search) to reduce the search space. Start around the middle of search space (source code and/or data).
  • Establish in which section of the bisected search space does the root cause of the problem lie
  • Repeat the bisection process in the selected section
  • By using this approach we reduce the search space into half with each attempt thus getting to root cause in a systematic, and efficient manner

What to look for


  1. Order of arguments passed to a method
  2. Initialization of variables, especially the ones used in a loop. Did you intend to initialize these inside or outside the loop?
  3. Comparison of objects and values. Did you intend the reference (address) or value?
  4. Are multiple variables pointing to the same object and perhaps changing the object.
  5. Look for side-effects. Is the method being called changing the arguments passed in?
  6. When copying objects, did you intend to make shallow or deep copy of the object?
  7. Do not exclusively rely on the code comments. Read the code.
  8. Reconsider the assumptions. Are you looking at the correct part or version of the code? Are you using the right data to reproduce the problem?
  9. Walk away and take a fresh look later
  10. Don't rush to fix the bug. Look closely at the nature of the bug. Is is spread among other portions of the code. Does fixing this bug introduce other bugs? Are you adding a lot of code to fix the bug? Should you first clean up and refactor?

Saturday, August 10, 2013

Git

What is it?

  • Version control system
  • Distributed - Every one has a full local repository and full history, can avoid network latency when committing changes
  • Distributed repositories are synchronized using pulls and pushes
  • Branching is really quick in Git
  • Branch can stay at local repository or can be pushed to the remote Git

Installer

Setup Commands

# to help on a git command
git help <commandname>

# set user name and email
git config --global user.name "Your Name Here"
git config --global user.email "your_email@example.com"

# who we are user.name, user.email
git config -l

Repository Commands

# cloning a repository from a remote server
git clone <url>

# create new (empty) repository in the current directory
git init

Check-in/Commit Commands

# information about our local copy, is there anything to commit?
# what branch are we on?
git status

# pull vs fetch
# git fetch updates your remote-tracking branches under refs/remotes/<remote>/.
#    Does not change any of your own local branches under refs/heads
# git pull does a git fetch followed by a git merge
#     git pull brings a local branch (refs/heads) up-to-date with its remote version,
#    while also updating other remote-tracking branches.

# get latest
git fetch
git fetch origin
git fetch origin/master

# To add new file to repo
# Add existing file to staging area (must be added to staging area before commit)
git add <filename>

# Add all files
git add .

# diff of working copy
git diff

# diff of copy in staging area
git diff --cached
git diff --staged  # same as --cached

# commit changes that were added to staging area only
# commit is done to local repository only
# each commit gets its own version number - which is the hash of repository
#  first 8 digits of hash are shown
git commit -m "comment"

# add all changes to the staging area and commit to repository
git commit -a

# add all changes to the staging area and commit to repository with a comment

git commit -a -m "comment"

# clears the staging area
git reset

# revert all changes
# clears the staging area and undo local changes in the working copy
git reset --hard

#revert to certain commit
git reset --hard AEFXH

#revert to head of remote branch
git reset --hard origin/master

# shows history
git log

# deleting file from working tree and source control
git rm <filename>
git commit -a -m "A file was deleted"
git push origin branchname

# deleting file from source control only (leave in working tree)
git rm -cached <filename>
git commit -a -m "A file was deleted"

git push origin branchname

# removing a folder and all its contents
git rm -r <folder>

Interacting with an existing Remote Git Server

# add remote server called origin
git remote add origin <https://-connection-path>
# verify
git remote -v
git remote show origin

# to remove a remote server

git remote remove origin

# to push changes to remote server

git push origin master

# if push fails, someone has made changes we need to fetch and apply

# fetch changes by others; origin is the name of the remote
# fetch, rebase, and push if on the same branch
# do a fetch and rebase (resolve conflicts) then push INSTEAD of a pull
#   easier to change your own code to merge
git fetch origin

# applies to local copy i.e. updates the working copy from the master branch of origin remote
# undoes (rewind) our changes, gets remote changes, then applies our changes
git rebase origin/master

# send to the origin

git push origin

# if cannot rebase due to merge conflict, both versions are available to resolve the conflict
# manually
git add <filename>
git rebase origin/master -- continue

git push origin

Creating a new Remote Git Server

# In this example, we are using directory on a USB drive a  proxy for remote server
# to push changes to different server or another disk repository
# commits all day, then push at end of day push
# On the USB drive, create a directory and create a bare Git repos
# Back up to Flash drive
git init --bare

# create bare repository directory on the Server
git init bare shared

# In local git repo, add a remote
# Flash drive or another Git server are called remotes
# can have multiple remotes to push/pull
# from your machine, tell Git about the remote
git remote add myusbback F:\gitproj

# to push changes
# master is the name of the branch on the usb
# in git must have a branch; master is created by default; but myusbback was bare
git push myusbback master

#subsequent push (master was previously created, no need to specify now)
git push myusbback

Branches

# what branch are we on


git status

# shows list of local branches; the current branch has an asterisk; example, * master
git branch

# shows list of branches, local and remote; 
# the current branch has an asterisk; example, * master
git branch -a

# can create a new branch from any branch
# switch to the base branch
# create a new branch
# declare a new branch
git branch <anewbranch>

# switch to a new branch
git checkout branchname

# pushing a branch; origin is the alias of the remote repos
#  first time must specify branch name

git push origin branchname

# push all branches (specify name if want only a specific branch to be pushed)

git push origin

# pull between branches
# git pull does a git fetch followed by a git merge
# pull tries to apply changes on top our already changed files
# won't be able to always push because in wrong order
git pull
#  reapply your work on top of the incoming changes
git pull --rebase

# pull with rebase
git pull --rebase <remote> <branch>

equivalent to

git fetch <remote>
git rebase <remote>/<branch>


# to delete a branch, switch to a different branch and then delete
#  branch must have been merged to another before delete
git branch -d branchname

# force delete with prior merge from local repository using capital D
git branch -D branchname

# To remove branch from remote (notice the colon before the branch name)
git push origin :branchname
or
git push <remote_name> --delete <branch_name>

# to merge, switch to destination branch
git checkout master

# merge changes from master to branchname
git merge branchname
# update the remote (origin)
git push origin

Tuesday, January 18, 2011

Dimensions of Software Testing

As Agile developers, we are most accustomed to writing Unit Tests for our code. There is a lot more to software testing than Unit Testing alone. In order to release a robust software product, we need to devise and implement tests to cover other dimensions of software as well. Testing can be can be sliced in various ways. The following are several different ways to view software testing.

Purpose
  1. Correctness
  2. Scalability
  3. Performance
  4. Load (Stress)

Granularity
  1. Unit
  2. Functional
  3. Integration

Tier
  1. User Interface
  2. Business
  3. Service
  4. API
  5. Database
 
Proof
  1. Positive
  2. Negative

Environment
  1. Operating System
  2. Database Server
  3. Application Server
  4. Web Server
  5. Browser
  6. Runtime, Virtual Machine

 Scope
  1. Smoke
  2. Regression
  3. Full

Deployment
  1. New
  2. Existing (Upgrade)
  3. Various Platforms

Three things all Units Tests must do

Unit Tests verify correctness of a discrete piece of code. Regardless of the application domain and the unit test tools utilized, there are three things a well-written unit test must do:

  1. Create and verify prerequisite state for the action to be tested
  2. Execute the action to be tested
  3. Verify post-action state

Example
Let's say we have a system that allows warehouse employees to ship products. A ShippingOrder is an object in the system that contains the products to be shipped. Our business rule states that a ShippingOrder can only be processed (shipped) if all products in the ShippingOrder are in stock. In other words, we want to verify that either all products in a ShippingOrder are shipped or none of the products in the ShippingOrder are shipped based on stock availability. Our Unit Test will verify this behavior of the ShippingOrder.

1. Create and verify prerequisite state
Before a test is executed, we want to ensure that the environment in which a test is about to be executed is known and verified. It is important to stress that it is not sufficient to bring the test environment into a known state but also to explicitly verify the state before execution of the action. This is typically done using ASSERTs provided by the Unit Testing framework. In our example, we have at least three stock level conditions:
S_1. All Products in the ShippingOrder are in stock
S_2. None of the Products in the ShippingOrder are in stock
S_3. Some the products in the ShippingOrder are in stock
We have identified three states of product stock levels and hence we will be executing at least three tests. Before each of the three tests is executed, the corresponding state of the stock levels of products in the ShippingOrder must be established and verified by our test setup code. Our test code will create three distinct ShippingOrder objects corresponding to these states. Lets call these objects SO_1, SO_2, and SO_3. The test setup code must also verify that these three ShippingOrder objects are in Pending state.


2. Execute the test
Our test is going to verify that a ShippingOrder can only transition to a Shipped state when our business rules are satisfied. We will be writing at least 3 actions:

T_1. Use SO_1. Try to transition ShippingOrder to a Shipped state.
T_2. Use SO_2. Try to transition ShippingOrder to a Shipped state.
T_3. Use SO_3. Try to transition ShippingOrder to a Shipped state.

3. Verify post-action State
Once the test action is executed, we verify the post-action state of SO_1, SO_2, and SO_3 using ASSERTs provided by the Unit Testing framework:
V_1. Verify SO_1 is in Shipped state
V_2. Verify SO_2 is in Pending state
V_3. Verify SO_3 is in Pending state

Friday, January 14, 2011

Role of Software Architect in an Agile Environment

Is the role of Software Architect still relevant in an Agile development shop? Given that Agile methodologies shun big up front design and take design-as-you-go approach, should there be a person or persons designated to look after the system architecture? The answer is unequivocally yes for any system of any significant size. For any significant system, there are key technology decisions that need to be made up front before development can even begin.  For example, whether the system is going to be a desktop application or a web application? Will it be hosted on Windows or UNIX? Which programming language and class libraries to use? Key technology and architectural approaches must be vetted out carefully before coding can begin. Once coding begins, some architectural oversight has to be in place to ensure the system follows the planned architectural pattern. There is still plenty to do during the development Sprints.  However, in an Agile environment, a Software Architect must adapt his or her approach towards creating, implementing and overseeing system architecture:
  1. Live in the moment! Defer decisions to the last responsible moment. Let the architecture emerge. Only decide what needs to be decided right now. Set the direction and let the developers fill in the rest. To maintain oversight, pair program with developers especially in key areas where security, performance and scalability aspects of the application are at stake. In addition, review unit and functional tests.
  2. Consider yourself part of the development team. To garner any respect or following from the development team, an Architect must be an excellent technologist. Absolutely write some production code. This is in addition to developing proof of concepts, prototypes, design patterns, and perhaps some base classes to kick start the development. At the outset of a new system (or subsystem) development, an Architect must validate the approach by creating a skeletal version of the application that hits all key technologies that will be utilized and simulate all application tiers that would be deployed in the production system.
  3. Communicate in the language of the developers (e.g. class diagrams, code examples, whiteboard, not PowerPoint slides). High level artifacts do not resonate well with developers who are focused on what needs to be done right now to complete stories in the current Sprint. Maintain a development WIKI where engineers can document significant design decisions. For one, this makes engineers think harder about the design decision they are making and perhaps just as importantly provides some continuity as different Agile teams work on the same feature over time.
  4. On top of all of the above, an Architect must have the temperament and patience to listen and balance the needs of all stakeholders. An Architect must be able to effectively communicate the system design, at different levels of detail, to internal (e.g. sales, marketing, documentation, executive team) and external stakeholders (e.g. users, customer leadership team, customer IT department) and explain how the system design meets everyone's business goals.

Source Code Branching for Release Management

1. When features targeted for a particular release are code complete, a RELEASE branch is created. All fixes and changes including service packs for the release are made in this branch. The system is built and released from this branch.

2. Changes for future versions of the system can continue to be made in the MAIN line while fixes are being made in a RELEASE branch to service live customers. Any fixes made in the RELEASE branch that are applicable to the code in the MAIN line can be merged from the RELEASE branch to the MAIN line.














Writing effective stories for a Scrum Sprint

A Story is a brief narration of a software requirement that describes desired functionality in terms of business value it brings to the Customer. A story is best expressed in terms of a Customer Goal. For example, compare the following two different ways of expressing the same requirement:

Form 1: Implement a query system to lookup inactive customers for a region.

Form 2: As a Regional Director, I would like to periodically find out which customers have been inactive for more than 30 days.

It can be argued that Form 2 is a substantially better way to express a requirement than Form 1. For instance, in Form 2 the team is introduced to the target user, a Regional Director. Given this information, the User Interface can be tailored to the particular needs of a person with computer skills of a typical executive. Furthermore, Form 2 states that the Regional Director is really only interested in finding out about customers that have been inactive for more than 30 days. This information allows the team to develop a query system that has just enough functionality to meet the Customer Goal. Any additional functionality can be added to the query system later as Customer requirements dictate.

Given only the information in Form 1, a team can easily embark on development of an all inclusive query system that has features that are never used or valued by the Customer.

Writing Unit Tests using Test Driven Development (TDD)

Say a developer needs to write a new class called Person. A developer may proceed as follows:
  1. Create class Person and define all its public properties and methods with { throw new NotImplementedException; } as the body.
  2. Create a Unit Test class called PersonTest and write [Tests] to exercise the public behavior and state of Person class.
  3. Run the tests. All tests should fail.
  4. Continue implementation of the Person class until all tests pass.
When the above approach and naming strategy (classnameTest) is followed for Unit Tests, it is easier for developers to quickly locate, exercise and expand Unit Tests for a class when the functionality of that class is enhanced.