Key points of good software development


I’ve been writing this post for a long time now but here it is. I wanted to write some notes on some of the key points of software development for myself and for others also. This is not a complete all knowing guide but only a staring point for anyone who is interested to have a check list on things in software development. I will probably update this in the future but for now this edition will suffice. I has it’s problems but It’s ok for the moment :).


Good software development

Key Points of good software development

Things to take into consideration with Design.

Programming methods and concepts.


Loose and Tight Coupling.

Loose Coupling.

Tight Coupling.







Cohesive (or Single Responsibility Principle)




Model View Controller – MVC (Design Pattern)




Design Patterns.

Anti Patterns.


Design, Analysis and Architecture.

Key points of software design.

Design principles.

Principle #1: The Open-Closed Principle (OCP)

Principle #2: The Don’t Repeat Yourself Principle (DRY)

Principle #3: The Single Responsibility Principle (SRP)

Principle #4: The Liskov Substitution Principle (LSP)

Principle #5: Interface Segregation Principle (ISP)

Principle #6: Dependency Inversion Principle (DIP)


Functionalities, requirements and analysis.

Key points of requirements.

What is a requirement?.

Requirements list

Use case.

Summary – Use Cases.

Requirements change.


Class Diagrams.

Big applications and problems.

Solving big problems.


Use case diagrams.

Use cases reflect usage, features reflect functionality.

Domain Analysis.



First step – Functionality.

Questions to ask.


Iterating, Testing and Contracts.

Writing test scenarios.

What makes a good test case.

Software Testing Techniques and Methods.

Types of performance testing:

A sample testing cycle.

Programming by contract


The Lifecycle of software development


Good software development

Key Points of good software development

–          Does what the customer wants it to do, the application is stable, it is upgradable

–          The software is stable and works no matter what the customer might do

–          No code duplicates

–          Each object controls its own behavior

–          Extendable code with solid and flexible design

–          Usage of design patterns and principles

–          Keeping object loosely coupled

–          Your code open for extension but closed for modification. Makes code reusable.

–          Object oriented analysis and design (OOA&D) provides a way to produce well-designed applications that satisfy both the customer and the programmer.

–          Find the parts of your application that change often, and try and separate them from the parts of your application that don’t change.

–          Building an application that works well but is poorly designed satisfies the customer but will leave you with possible problems that will take time and energy to fix.

–          One of the best ways to see if software is well-designed is to try and change it.

–          If your software is hard to change, there’s probably something you can improve about the design.


  • Software must satisfy the customer. The software must do what the customer wants it to do.
  • Software is well-designed, well-coded, and easy to maintain, reuse, and extend.

Things to take into consideration with Design

  • Make sure that the application works like it should before you dive into applying design patterns or trying to do any real restructuring of how the application is put together. Too much design before basic functionality is done can be work wasted. Potentially a lot of design will change as you add new functionality.
  • A functional and flexible design, allows you to employ design patterns to improve your design further, and make your application easier to reuse.
  • Begin a project by figuring out what the customer wants.
  • Once basic functionalities are in place, work on refining the design so it’s flexible.
  • Use Object Oriented principles like encapsulation and delegation to build applications that are flexible.

Programming methods and concepts


“In computer science, an object is a location in memory having a value and referenced by an identifier. An object can be a variable, function, or data structure. In the object-oriented programming paradigm, “object,” refers to a particular instance of a class where the object can be a combination of variables, functions, and data structures. In relational Database management an object can be a table or column, or an association between data and a database entity (such as relating a person’s age to a specific person).” –

Objects are need to be particular about their jobs. Each object must do its job, and only its job, to the best of its ability. Object should not be used to do something that isn’t its true purpose.

  1. Objects should do what their names indicate.
    1. Should have specific tasks to its wanted application. Example: A bus can move and stop but not handle passenger requests(well unless it’s a very smart with an AI J)
    2. Each object should represent a single concept.
      1. Sample: An object of a bus, does not need to represent a fast bus or a flying bus object.
      2. Avoid unused properties.
        1. If you have empty or null properties think if they have a better purpose somewhere else.

Loose and Tight Coupling

Loose Coupling means reducing dependencies of a class that use a different class directly. In tight coupling, classes and objects are dependent on one another. In general, tight coupling is usually bad because it reduces flexibility and re-usability of code and it makes changes much more difficult and impedes testability etc.

Loose Coupling

“Loose coupling is a design goal that seeks to reduce the inter-dependencies between components of a system with the goal of reducing the risk that changes in one component will require changes in any other component. Loose coupling is a much more generic concept intended to increase the flexibility of a system, make it more maintainable, and makes the entire framework more “stable”. “

Tight Coupling

“A Tightly Coupled Object is an object that needs to know quite a bit about other objects and are usually highly dependent on each other’s interfaces. Changing one object in a tightly coupled application often requires changes to a number of other objects. In a small application we can easily identify the changes and there is less chance to miss anything. But in large applications these inter-dependencies are not always known by every programmer or there is a chance of overlooking changes. But each set of loosely coupled objects are not dependent on each other.”


Encapsulation allows you to hide the inner workings of your application’s parts by making it clearer what each part does.

Anytime you see duplicate code, look for a way to encapsulate it. Encapsulate the parts of your application that might vary away from the parts that will stay the same. Breaking up the different parts of your application, you can change one part without having to change all the other parts.

Places to apply:

  • Entire set of properties
    • Protecting data in your class from other parts of the application
    • Behaviors
      • When you break a behavior out from a class, you can change the behavior without the class having to change as well. So if you changed how properties were stored, you wouldn’t have to change your class at all, because the properties are encapsulated away from the class.
class Program {

public class Account {

private decimal accountBalance = 500.00m;

public decimal CheckBalance() {

return accountBalance;



static void Main() {

Account myAccount = new Account();

decimal myBalance = myAccount.CheckBalance();

/* This Main method can check the balance via the public

               * “CheckBalance” method provided by the “Account” class

               * but it cannot manipulate the value of “accountBalance” */



” –


The act of one object forwarding an operation to another object, to be performed on behalf of the first object.

Delegation is when an object needs to perform a certain task, and instead of doing that task directly, it asks another object to handle the task (or sometimes just a part of the task).

Delegation makes your code more reusable. It also lets each object worry about its own functionality, rather than spreading the code that handles a single object’s behavior all throughout your application

Delegation lets each object worry about equality (or some other task) on its own. This means your objects are more independent of each other, or more loosely coupled. Loosely coupled objects can be taken from one app and easily reused in another, because they’re not tightly tied to other objects’ code.

Loosely coupled is when the objects in your application each have a specific job to do, and they do only that job. So the functionality of your app is spread out over lots of well-defined objects, which each do a single task really well.

Loosely coupled applications are usually more flexible, and easy to change. Since each object is pretty independent of the other objects, you can make a change to one object’s behavior without having to change all the rest of your objects. So adding new features or functionality becomes a lot easier.

Sample in Java:

class A {

void foo() {

// “this” also known under the names “current”, “me” and “self” in other languages;


void bar() {




class B {

private A a; // delegation link

public B(A a)


this.a = a;


void foo() {; // call foo() on the a-instance


void bar() {




a = new A();

b = new B(a); // establish delegation between two objects

” –

Singlecast” delegates (C#)

delegate void Notifier(string sender);  // Normal method signature with the keyword delegate

Notifier greetMe;                       // Delegate variable

void HowAreYou(string sender) {

Console.WriteLine(“How are you, ” + sender + ‘?’);


greetMe = new Notifier(HowAreYou);

“ –

Multicast delegates (C#)

void HowAreYou(string sender) {

Console.WriteLine(“How are you, ” + sender + ‘?’);


void HowAreYouToday(string sender) {

Console.WriteLine(“How are you today, ” + sender + ‘?’);


Notifier greetMe;

greetMe = new Notifier(HowAreYou);

greetMe += new Notifier(HowAreYouToday);

greetMe(“Leonardo”);                      // “How are you, Leonardo?”

// “How are you today, Leonardo?”

greetMe -= new Notifier(HowAreYou);

greetMe(“Pereira”);                   // “How are you today, Pereira?”

” –


Abstract classes are placeholders for actual implementation classes. The abstract class defines behavior, and the subclasses implement that behavior.

An abstract class defines some basic behavior, but it’s really the subclasses of the abstract class that add the implementation of those behaviors.

Whenever you find common behavior in two or more places, look to abstract that behavior into a class, and then reuse that behavior in the common classes. Helps to avoid duplicate code.

public class Animal extends LivingThing


private Location loc;

private double energyReserves;

public boolean isHungry() {

return energyReserves < 2.5;


public void eat(Food food) {

// Consume food

energyReserves += food.getCalories();


public void moveTo(Location location) {

// Move to new location

this.loc = location;



thePig = new Animal();

theCow = new Animal();

if (thePig.isHungry()) {;


if (theCow.isHungry()) {;



” –


An interface contains definitions for a group of related functionalities that a class or a struct can implement.

By using interfaces, you can, for example, include behavior from multiple sources in a class.

Interfaces can contain methods, properties, events, indexers, or any combination of those four member types.

You can write code that interacts directly with a subclass, or you can write code that interacts with the interface. When you run into a choice like this, you should always favor coding to the interface, not the implementation. It makes your software easier to extend. By coding to an interface, your code will work with all of the interface’s subclasses—even ones that haven’t been created yet.

This it adds flexibility to your app. Instead of your code being able to work with only one specific subclass you’re able to work with the more generic, the interface. That means that your code will work with any subclass of the interface, and even subclasses that haven’t even been designed yet.

interface ISampleInterface


void SampleMethod();


class ImplementationClass : ISampleInterface


// Explicit interface member implementation:

void ISampleInterface.SampleMethod()


// Method implementation.


static void Main()


// Declare an interface instance.

ISampleInterface obj = new ImplementationClass();

// Call the member.




” –


The main issue is to prevent duplicate code. Encapsulation also helps you protect your classes from unnecessary changes.

Anytime you have behavior in an application that you think is likely to change, you want to move that behavior away from parts of your application that probably won’t change very frequently. In other words, you should always try to encapsulate what varies.

“class Program {

public class Account {

private decimal accountBalance = 500.00m;

public decimal CheckBalance() {

return accountBalance;



static void Main() {

Account myAccount = new Account();

decimal myBalance = myAccount.CheckBalance();

/* This Main method can check the balance via the public

               * “CheckBalance” method provided by the “Account” class

               * but it cannot manipulate the value of “accountBalance” */



” –


A constant in software development is CHANGE. Software that isn’t well designed falls apart at the first sign of change, but great software can change easily.

The easiest way to make your software resilient to change is to make sure each class has only one reason to change. In other words, you’re minimizing the chances that a class is going to have to change by reducing the number of things in that class that can cause it to change.

When you see a class that has more than one reason to change, it is probably trying to do too many things. See if you can break up the functionality into multiple classes, where each individual class does only one thing—and therefore has only one reason to change.

Cohesive (or Single Responsibility Principle)

A cohesive class does one thing really well and does not try to do or be something else.

The more cohesive your classes are, the higher the cohesion of your software.

Look through the methods of your classes-do they all relate to the name of your class? If you have a method that looks out of place, it might belong in another class. Cohesive classes are focused on specific tasks.

Cohesion measures the degree of connectivity among the elements of a single module, class, or object. The higher the cohesion of your software is, the more well-defined and related the responsibilities of each individual class in your application. Each class has a very specific set of closely related actions it performs.

In other words cohesion tells how easy it is to change your application

Cohesion focuses on how you’ve constructed each individual class, object, and package of your software. If each class does just a few things that are all grouped together, then it’s probably a highly cohesive piece of software. But if you have one class doing all sorts of things that aren’t that closely related, you’ve probably got low cohesion.

A highly cohesive software is loosely coupled

In almost every situation, the more cohesive your software is, the looser the coupling between classes.

The higher the cohesion in your application, the better defined each object’s job is. And the better defined an object (and its job) is, the easier it is to pull that object out of one context, and have the object do the same job in another context.


Collections provide a more flexible way to work with groups of objects. Unlike arrays, the group of objects you work with can grow and shrink dynamically as the needs of the application change. For some collections, you can assign a key to any object that you put into the collection so that you can quickly retrieve the object by using the key.

When you have a set of properties that vary across your objects, use a collection, like a Map, to store those properties dynamically.

You’ll remove lots of methods from your classes, and avoid having to change your code when new properties are added to your app.


“Inheritance enables you to create new classes that reuse, extend, and modify the behavior that is defined in other classes. The class whose members are inherited is called the base class, and the class that inherits those members is called the derived class. Inheritance is transitive. If ClassC is derived from ClassB, and ClassB is derived from ClassA, ClassC inherits the members declared in ClassB and ClassA.

A derived class is a specialization of the base class. For example, if you have a base class Animal, you might have one derived class that is named Mammal and another derived class that is named Reptile. A Mammalis an Animal, and a Reptile is an Animal, but each derived class represents different specializations of the base class. “



  • Encapsulate what varies.
  • Code to an interface rather than to an implementation.
  • Each class in your application should have only one reason to change.
  • Classes are about behavior and functionality.

Model View Controller – MVC (Design Pattern)

“Remember you’re technically minded and close to the code. MVC to you is as clear as day, but saying to the business ‘Model, View, Contoller’ could give them the impression that you are suffering from some form tourette syndrome. MVC won’t mean much to the business even after you define them in relation to the code. To get the business to understand why this is the answer and least of all what it is, can be more of a task than expected in my experience. Even some fellow developers have difficulty understanding this on occasion.

To get the listener to understand what MVC is and why it works what I have tried in the pass is to apply MVC to a different industries where the listeners have had more involvement. An example that has worked for me in the past in a comparison to the property or even the vehicles. Most people have had dealing’s with builders, carpenters, plumbers, electricians or have watched the flood of property shows on the TV. This experience is a good platform to use and to explain why separation such as MVC works. I know you’re probably thinking that won’t work as it’s not the same as in software, but remember you’re not trying to train the business to become developers or have an in depth understanding of MVC, simply explaining to them that separation in production is required and that’s what an MVC structure offers.

To give an example of how you could describe this I have very briefly explained how separation works in property. Keep in mind this is focused on using the system not developing which could be a completely different angle of explanation.


The view in MVC is the presentation layer. This is what the end user of a product will see and interact with. A system can have multiple views of all different types ranging from command line output to rendered HTML. The view doesn’t consist of business logic in most clear designs. The interface is fit for purpose and is the area of interaction. Therefore you could simply output HTML for consumers to interact with or output SOAP/XML for businesses to interact with. Both use the same business logic behind the system otherwise known as the models and controllers.

In the world of property you could think of the view as the interior of a property or the outer layer of a property that the inhabitants interact with. The interior can be customised for purpose and the same property can have many different types of tenants. For example a property of a particular design could contain residential dwellings. The same internal space could easily be used as office space, where although in the same property has a different purpose. However the property structure is the same. Therefore the environment in which the users interact does not interfere with the structure of the building.


The controller is where the magic happens and defines the business application logic. This could be where the user has sent a response from the view, then this response is used to process the internal workings of the request and processes the response back to the user. Taking a typical response where a user has requested to buy a book. The controller has the user id, payment details, shipping address and item choice. These elements are then processed through the business logic to complete a purchase. The data is passed through the system into the model layer and eventually after the entire request satisfies the business definitions, the order is constructed and the user receives their item.

If we compare this to a property, we could compare the ordering of a book online to turning on a light switch. A tenant will flick the switch to on just like ordering a book. The switch itself is an element in the view layer which sends the request to the controller just like clicking a checkout button on a web site. The business logic in this case is what the electrician installed and are embedded within the property designs. The switch is flicked, which completes the circuit. Electricity runs through all the wires including the fuse box straight through to the light bulb. Just like the user receiving a book, in this case the tenant receives light. The whole process behind the scenes involving the electricity cabling is not visible to the the tenant. They simply interact with the switch within the space and from there the controller handles the request.


The models in MVC are the bottom most layer and handle the core logic of the system. In most cases this could be seen as the layer that interacts with the data source. In systems using MVC, the controller will pass information to the model in order to store and retrieve data. Following on from the example above controller definition, this is where the order details are stored. Additional data such as stock levels, physical location of product of the book amongst many things are all stored here. If that was the last book in stock ordered, the next request for this item may check if it’s available and disallow the order as the item is no longer available.

Sticking with out example of turning on a light switch, this level in our structure could be the electricity supply. When the tenant flicks the switch, the internal circuit must request electricity to power the request which is similar when the user requested data from the database, as in data is needed to process a request. If the dwelling isn’t connected to an electric supply, it cannot complete the process. Business benefits from using MVC

After you get the message across explaining what MVC is, you will then have to see what benefits can be obtained from it. I’m not going to go into a huge amount of detail here are I’m sure you can apply benefits more accurately which are directly related to you actual situation. To list just some of the common benefits of an MVC based system here are a few examples:

  • Different skill levels can work on different system levels. For example designers can work on the interface (View) with very little development knowledge and developers can work on the business logic (Controller) with very little concern for the design level. Then they simply integrate together on completion.
  • As a result of the above separation projects can be managed easier and quicker. The designer can start the interfaces before the developer and vice versa. This development process can be parallel as opposed to being sequential therefore reducing development time.
  • Easy to have multiple view types using the same business logic.
  • Clear route through the system. You clearly know where there different levels of the system are. With a clear route of the system, logic can be shared and improved. This has added security benefits as you clearly know the permitted route from the data to the user and can have clear security checks along the route.
  • Each layer is responsible for itself. (Relates to point 1) This means that you can have clean file structure which can be maintained and managed much easier and quicker than a tightly couple system where you may have lots of duplicate logic.
  • Having a clear structure means development will be more transparent which should result in reduced development time, maintenance problems and release cycles if applied properly.

Design Patterns

Design patterns help you recognize and implement GOOD solutions to common problems. Design patterns are proven solutions to particular types of problems, and help you structure your own applications in ways that are easier to understand, more maintainable, and more flexible.

Anti Patterns

Anti patterns are about recognizing and avoiding BAD solutions to common problems. Anti-patterns are the reverse of design patterns: they are common BAD solutions to problems. These dangerous pitfalls should be recognized and avoided.


Refactoring changes the internal structure of your code WITHOUT affecting your code’s behavior. Refactoring is done to increase the cleanness, flexibility, and extensibility of your code, and usually is related to a specific improvement in your design.

Design, Analysis and Architecture

Key points of software design

  • Mistakes are bound to happen, learn to let go and look forward how to improve the design
  • Do not be afraid to let bad design die
  • Be ready to look at your code more than once, even after it is ready
  • Design is iterative and you have to be willing to change your own designs, as well as those that you inherit from other programmers.
  • Most good designs come from analysis of bad designs. Never be afraid to make mistakes and then change things around.
  • Great software is usually about being good enough. In other words there is no need to try to write the “perfect software”. Be realistic to the circumstances that limit or free your development and design decisions. If your software works, the customer is happy, and you’ve done your best to make sure things are designed well, then it just might be time to move on to the next project.

Design principles

A design principle is a basic tool or technique that can be applied to designing or writing code to make that code more maintainable, flexible, or extensible.

Principle #1: The Open-Closed Principle (OCP)

Open-Closed Principle is about allowing change, but doing it without requiring you to modify existing code: Classes should be open for extension, and closed for modification.

Closed for medication:

Make sure that nobody can change your class’s code, and you’ve made that particular piece of behavior closed for modification. In other words, nobody can change the behavior, because you’ve locked it up in a class that you’re sure won’t change.

Open for extension:

Let others subclass your class, and then they can override your method to work like they want it to. So even though they didn’t mess with your working code, you still left your class open for extension.

OCP is about flexibility, and goes beyond just inheritance.

OCP is not just about inheritance and what it offers you. It is also about that anytime you write working code, you want to do your best to make sure that code stays working… and that means not letting other people change that code. In situations where you want to allow change rather than just diving into your code and making a bunch of changes, the OCP lets you extend your working code, without changing that code. So another way of achieving this is composition.

Other ways to use OCP is to provide access or functionality on private methods in your class. You’re extending the behavior of the private methods, without changing them. So anytime your code is closed for modification but open for extension, you’re using the OCP.

public class Logger


    IMessageLogger _messageLogger;


    public Logger(IMessageLogger messageLogger)


        _messageLogger = messageLogger;



    public void Log(string message)







public interface IMessageLogger


    void Log(string message);




public class ConsoleLogger : IMessageLogger


    public void Log(string message)







public class PrinterLogger : IMessageLogger


    public void Log(string message)


        // Code to send message to printer



Principle #2: The Don’t Repeat Yourself Principle (DRY)

Don’t Repeat Yourself: Avoid duplicate code by abstracting out things that are common and placing those things in a single location. DRY is really about ONE requirement in ONE place. When you’re trying to avoid duplicate code, you’re really trying to make sure that you only implement each feature and requirement in your application one single time.

DRY is about avoiding duplicate code, but it’s also about doing it in a way that won’t create more problems down the line. Rather than just tossing code that appears more than once into a single class, you need to make sure each piece of information and behavior in your system has a single, clear place where it exists. That way, your system always knows exactly where to go when it needs that information or behavior.

Whether you’re writing requirements, developing use cases, or coding, you want to be sure that you don’t duplicate things in your system. A requirement should be implemented one time, use cases shouldn’t have overlap, and your code shouldn’t repeat itself.

Principle #3: The Single Responsibility Principle (SRP)

The SRP is all about responsibility, and which objects in your system do what. You want each object that you design to have just one responsibility to focus on—and when something about that responsibility changes, you’ll know exactly where to look to make those changes in your code. Each of your objects has only one reason to change.

DRY and SRP are related, and often appear together. DRY is about putting a piece of functionality in a single place, such as a class; SRP is about making sure that a class does only one thing, and that it does it well.

In good applications, one class does one thing, and does it well, and no other classes share that behavior. Cohesion is actually just another name for the SRP. If you’re writing highly cohesive software, then that means that you’re correctly applying the SRP.

public class OxygenMeter


    public double OxygenSaturation { get; set; }


    public void ReadOxygenLevel()


        using (MeterStream ms = new MeterStream(“O2”))


            int raw = ms.ReadByte();

            OxygenSaturation = (double)raw / 255 * 100;






public class OxygenSaturationChecker


    public bool OxygenLow(OxygenMeter meter)


        return meter.OxygenSaturation <= 75;





public class OxygenAlerter


    public void ShowLowOxygenAlert(OxygenMeter meter)


        Console.WriteLine(“Oxygen low ({0:F1}%)”, meter.OxygenSaturation);



Principle #4: The Liskov Substitution Principle (LSP)

Subtypes must be substitutable for their base types.

The LSP is all about well-designed inheritance. When you inherit from a base class, you must be able to substitute your subclass for that base class without things going wrong. Otherwise, you’ve used inheritance incorrectly! Make sure that new derived classes are extending the base classes without changing their behavior.

“ The LSP applies to inheritance hierarchies. It specifies that you should design your classes so that client dependencies can be substituted with subclasses without the client knowing about the change. All subclasses must, therefore, operate the same manner as their base classes. The specific functionality of the subclass may be different but must conform to the expected behaviour of the base class. To be a true behavioural subtype, the subclass must not only implement the base class’s methods and properties but also conform to its implied behaviour. This requires compliance with several rules.

The first rule is that there should be contravariance between parameters of the base class’s methods and the matching parameters in subclasses. This means that the parameters in subclasses must either be the same types as those in the base class or must be less restrictive. Similarly, there must be covariance between method return values in the base class and its subclasses. This specifies that the subclass’ return types must be the same as, or more restrictive than, the base class’ return types.

The next rule concerns preconditions and postconditions. A precondition of a class is a rule that must be in place before an action can be taken. For example, before calling a method that reads from a database you may need to satisfy the precondition that the database connection is open. Postconditions describe the state of objects after a process is completed. For example, it may be assumed that the database connection is closed after executing a SQL statement. The LSP states that the preconditions of a base class must not be strengthened by a subclass and that postconditions cannot be weakened in subclasses.

Next the LSP considers invariants. An invariant describes a condition of a process that is true before the process begins and remains true afterwards. For example, a class may include a method that reads text from a file. If the method handles the opening and closing of the file, an invariant may be that the file is not open before the call or afterwards. To comply with the LSP, the invariants of a base class must not be changed by a subclass.

The next rule is the history constraint. By their nature, subclasses include all of the methods and properties of their superclasses. They may also add further members. The history constraint says that new or modified members should not modify the state of an object in a manner that would not be permitted by the base class. For example, if the base class represents an object with a fixed size, the subclass should not permit this size to be modified.

The final LSP rule specifies that a subclass should not throw exceptions that are not thrown by the base class unless they are subtypes of exceptions that may be thrown by the base class.

The above rules cannot be controlled by the compiler or limited by object-oriented programming languages. Instead, you must carefully consider the design of class hierarchies and of types that may be subclassed in the future. Failing to do so risks the creation of subclasses that break rules and create bugs in types that are dependent upon them.

One common indication of non-compliance with the LSP is when a client class checks the type of its dependencies. This may be by reading a property of an object that artificially describes its type or by using reflection to obtain the type. Often a switch statement will be used to perform a different action according to the type of the dependency. This additional complexity also violates the Open / Closed Principle (OCP), as the client class will need to be modified as further subclasses are introduced. ” –

public class Project


    public Collection<ProjectFile> AllFiles { get; set; }

    public Collection<WriteableFile> WriteableFiles { get; set; }


    public void LoadAllFiles()


        foreach (ProjectFile file in AllFiles)






    public void SaveAllWriteableFiles()


        foreach (WriteableFile file in WriteableFiles)








public class ProjectFile


    public string FilePath { get; set; }


    public byte[] FileData { get; set; }


    public void LoadFileData()


        // Retrieve FileData from disk





public class WriteableFile : ProjectFile


    public void SaveFileData()


        // Write FileData to disk



Alternatives to inheritance


Delegating functionality to another class. Delegation is when you hand over the responsibility for a particular task to another class or method. Delegation is best used when you want to use another class’s functionality, as is, without changing that behavior at all. If you need to use functionality in another class, but you don’t want to change that functionality, consider using delegation instead of inheritance.

In delegation, the behavior of the object you’re delegating behavior to never changes.

Delegation is the simple yet powerful concept of handing a task over to another part of the program. In object-oriented programming it is used to describe the situation where one object assigns a task to another object, known as the delegate. This mechanism is sometimes referred to as aggregation, consultation or forwarding (when a wrapper object doesn’t pass itself to the wrapped object[4]).

“Delegation is dependent upon dynamic binding, as it requires that a given method call can invoke different segments of code at runtime. It is used throughout Mac OS X (and its predecessor NeXTStep) as a means of customizing the behavior of program components.[5] It enables implementations such as making use of a single OS-provided class to manage windows because the class takes a delegate that is program-specific and can override default behavior as needed. For instance, when the user clicks the close box, the window manager sends the delegate a windowShouldClose: call, and the delegate can delay the closing of the window if there is unsaved data represented by the window’s contents.

It has been argued that delegation may in some cases be preferred for inheritance to make program code more readable and understandable.” –

class A {
  void foo() {
    // "this" also known under the names "current", "me" and "self" in other languages;

  void bar() {

class B {
  private A a; // delegation link

  public B(A a)
    this.a = a;

  void foo() {; // call foo() on the a-instance

  void bar() {

a = new A();
b = new B(a); // establish delegation between two objects


Assemble behaviors from other classes. When you need to have more than one single behavior to choose from. Composition is most powerful when you want to use behavior defined in an interface, and then choose from a variety of implementations of that interface, at both compile time and run time.

In composition, the object composed of other behaviors owns those behaviors. When the object is destroyed, so are all of its behaviors. The behaviors in a composition do not exist outside of the composition itself.

// Composition

class Car




    // Car is the owner of carburetor.

    // Carburetor is created when Car is created,

    // it is destroyed when Car is destroyed.

    Carburetor carb;





Aggregation is when one class is used as part of another class, but still exists outside of that other class.

An aggregate object is one which contains other objects. For example, an Airplane class would contain Engine, Wing, Tail, Crew objects. Sometimes the class aggregation corresponds to physical containment in the model (like the airplane). But sometimes it is more abstract (e.g. Club and Members).

When you should use composition, and when you should use aggregation? Does the object whose behavior I want to use exist outside of the object that uses its behavior? If the object does make sense existing on its own, then you should use aggregation; if not, then go with composition.

// Aggregation

class Pond



   std::vector<Duck*> ducks;

}; –

“Aggregation differs from ordinary composition in that it does not imply ownership. In composition, when the owning object is destroyed, so are the contained objects. In aggregation, this is not necessarily true. For example, a university owns various departments (e.g., chemistry), and each department has a number of professors. If the university closes, the departments will no longer exist, but the professors in those departments will continue to exist. Therefore, a University can be seen as a composition of departments, whereas departments have an aggregation of professors. In addition, a Professor could work in more than one department, but a department could not be part of more than one university.” –

class Professor;

class Department
 // Aggregation
 Professor* members[5];

class University

 std::vector< Department > faculty;

 // Composition (must limit to 20)
 faculty.push_back( Department(...) );
 faculty.push_back( Department(...) );
}; -

Alternatives to inheritance – Summary


Delegate behavior to another class when you don’t want to change the behavior, but it’s not your object’s responsibility to implement that behavior on its own.


You can reuse behavior from one or more classes, and in particular from a family of classes, with composition. Your object completely owns the composed objects, and they do not exist outside of their usage in your object.


When you want the benefits of composition, but you’re using behavior from an object that does exist outside of your object, use aggregation.

If you favor delegation, composition, and aggregation over inheritance, your software will usually be more flexible, and easier to maintain, extend, and reuse.

Principle #5: Interface Segregation Principle (ISP)

“The Interface Segregation Principle (ISP) states that clients should not be forced to depend upon interfaces that they do not use. When we have non-cohesive interfaces, the ISP guides us to create multiple, smaller, cohesive interfaces. The original class implements each such interface. Client code can then refer to the class using the smaller interface without knowing that other members exist.

When you apply the ISP, class and their dependencies communicate using tightly-focussed interfaces, minimising dependencies on unused members and reducing coupling accordingly. Smaller interfaces are easier to implement, improving flexibility and the possibility of reuse. As fewer classes share interfaces, the number of changes that are required in response to an interface modification is lowered. This increases robustness.” –

public interface IEmailable


    string Name { get; set; }

    string EmailAddress { get; set; }


 public interface IDiallable


    string Telephone { get; set; }


 public class Contact : IEmailable, IDiallable


    public string Name { get; set; }

    public string Address { get; set; }

    public string EmailAddress { get; set; }

    public string Telephone { get; set; }


 public class MobileEngineer : IDiallable


    public string Name { get; set; }

    public string Vehicle { get; set; }

    public string Telephone { get; set; }


 public class Emailer


    public void SendMessage(IEmailable target, string subject, string body)


        // Code to send email, using target’s email address and name



public class Dialler


    public void MakeCall(IDiallable target)


        // Code to dial telephone number of target


} –


Principle #6: Dependency Inversion Principle (DIP)

“The Dependency Inversion Principle (DIP) states that high level modules should not depend upon low level modules. Both should depend upon abstractions. Secondly, abstractions should not depend upon details. Details should depend upon abstractions.

The idea of high level and low level modules categorises classes in a hierarchical manner. High level modules or classes are those that deal with larger sets of functionality. At the highest level they are the classes that implement business rules within the overall design of a solution. Low level modules deal with more detailed operations. At the lowest level they may deal with writing information to databases or passing messages to the operating system. Of course, there are many levels between the highest and the lowest. The DIP applies to any area where dependencies between classes exist.

Applying the DIP resolves these problems by removing direct dependencies between classes. Instead, higher level classes refer to their dependencies using abstractions, such as interfaces or abstract classes. The lower level classes implement the interfaces, or inherit from the abstract classes. This allows new dependencies to be substituted without impact. Furthermore, changes to lower levels should not cascade upwards as long as they do not involve changing the abstraction.

The effect of the DIP is that classes are loosely coupled. This increases the robustness of the software and improves flexibility. The separation of high level classes from their dependencies raises the possibility of reuse of these larger areas of functionality. Without the DIP, only the lowest level classes may be easily reusable.” –

public interface ITransferSource


    void RemoveFunds(decimal value);


 public interface ITransferDestination


    void AddFunds(decimal value);


 public class BankAccount : ITransferSource, ITransferDestination


    public string AccountNumber { get; set; }


    public decimal Balance { get; set; }


    public void AddFunds(decimal value)


        Balance += value;


     public void RemoveFunds(decimal value)


        Balance -= value;



 public class TransferManager


    public ITransferSource Source { get; set; }

     public ITransferDestination Destination { get; set; }

  public decimal Value { get; set; }


    public void Transfer()





} –



  • When you find code that violates the LSP, consider using delegation, composition, or aggregation to use behavior from other classes without resorting to inheritance.
  • If you need behavior from another class but don’t need to change or modify that behavior, you can simply delegate to that class to use the desired behavior.
  • Composition lets you choose a behavior from a family of behaviors, often via several implementations of an interface.
  • When you use composition, the composing object owns the behaviors it uses, and they stop existing as soon as the composing object does.
  • Aggregation allows you to use behaviors from another class without limiting the lifetime to those behaviors.
  • Aggregated behaviors continue to exist even after the aggregating object is destroyed.
  • Gather requirements about the functionality
  • Figure out what the functionality should really do(think outside what the client told you, look at things differently and into the future)
  • Get additional information from the client and make sure you have understood the client correctly by going through WHAT YOU have understood. You may have to do this more than once, perhaps several times.
  • Build the right functionality. Make sure that the client is on the same page about the needed functionality and requirements.

Functionalities, requirements and analysis

Key points of requirements

What is a requirement?

A requirement is usually a single thing, and you can test that thing to make sure you’ve actually fulfilled the requirement. It’s a specific thing your system has to do to work correctly. A requirement is a singular need detailing what a particular product or service should be or do.

A “system” is the complete app or project you’re working on. A system has a lot of things that it needs to do. What a client comes up with is part of what the system “does”. Remember, the customer decides when a system works correctly. So if you leave out a requirement, or even if they forget to mention something to you, the system isn’t working correctly!

Pay attention to what the system needs to do; you can figure out how the system will do those things later.

Requirements list

When creating a requirements list you need to understand how things will be used.

You’ve got to ask the customer questions to figure out what they want before you can determine exactly what the system should do. Begin by finding out what your customer wants and expects, and what they think the system you’re building for them should do. Then, you can begin to think beyond what your customers asked for and anticipate their needs, even before they realize they have a problem.

Remember, most people expect things to work even if problems occur. So you’ve got to anticipate what might go wrong, and add requirements to take care of those problems as well. A good set of requirements goes beyond just what your customers tell you, and makes sure that the system works, even in unusual or unexpected circumstances.

The system is everything needed to meet a customer’s goals.

You’ve got to make sure your application works like the customer wants it to—even if that’s not how you would use the application. That means you’ve got to really understand what the system has to do, and how your customers are going to use it.

In fact, the only way to ensure you get to your client a working, successful application is to know the system even better than they do, and to understand exactly what it needs to do. You can then anticipate problems, and hopefully solve them before your client ever knows something could have gone wrong.

Use case

A use case describes what your system does to accomplish a particular customer goal. Or what people call the steps that a system takes to make something happen.

“In software and systems engineering, a use case is a list of steps, typically defining interactions between a role (known in Unified Modeling Language (UML) as an “actor”) and a system, to achieve a goal. The actor can be a human or an external system.” –

Use cases are all about the “what.” What a particular goal needs to do. Not how, for the moment.

A single use case focuses on a single goal. What needs to be done for a single goal to successfully complete. In other words avoid having more than one outcome for a single use case.

The customer goal is the point of the use case: what do all these steps need to make happen? The focus is on the client. The system has to help that customer accomplish their goal.

A use case is a technique for capturing the potential requirements of a new system or software change. Each use case provides one or more scenarios that convey how the system should interact with the end user or another system to achieve a specific goal.

Key points for good User Cases

  • Every use case must have a clear value to the system. If the use case doesn’t help the customer achieve their goal, then the use case isn’t of much use.
  • Every use case must have a definite starting and stopping point. Something must begin the process, and then there must be a condition that indicates that the process is complete.
  • Every use case is started off by an external initiator, outside of the system. Sometimes that initiator is a person, but it could be anything outside of the system.

One of the key points about a use case is that it is focused on accomplishing one particular goal. If your system does more than one thing then you’ll need more than one use case.

Use cases are meant to help you understand what a system should do—and often to explain the system to others (like the customer or your boss). If your use case focuses on specific code- level details, it’s not going to be useful to anyone but a programmer. As a general rule, your use cases should use simple, everyday language. If you’re using lots of programming terms, or technical jargon, your use case is probably getting too detailed to be that useful. You will never write great software if you can’t deliver an app that does what the customer wants it to do. Use cases are a tool to help you figure that out—and then you’re ready to write code to actually implement the system your use case describes.

Summary – Use Cases

  • Your system must work in the real world, plan and test for when things go wrong.
  • Requirements are things your system must do to work correctly.
  • Good requirements ensure your system works like your customers expect.
  • Make sure your requirements cover all the steps in the use cases for your system.
  • Use your use cases to find out about things your customers forgot to tell you.
  • Your use cases will reveal any incomplete or missing requirements that you might have to add to your system.
  • Your initial requirements usually come from your customer.
  • To make sure you have a good set of requirements, you should develop use cases for your system.
  • Use cases detail exactly what your system should do.
  • A use case has a single goal, but can have multiple paths to reach that goal.
  • A good use case has a starting and stopping condition, an external initiator, and clear value to the user.
  • A use case is simply a story about how your system works.
  • You will have at least one use case for each goal that your system must accomplish.
  • After your use cases are complete, you can refine and add to your requirements.
  • A requirements list that makes all your use cases possible is a good set of requirements.
  • Your system must work in the real world, not just when everything goes as you expect it to.
  • When things go wrong, your system must have alternate paths to reach the system’s goals.

Requirements change

Requirements always change. With good use cases you can usually change your software quickly to adjust to those new requirements.

If a use case is confusing to you, you can simply rewrite it. There are tons of different ways that people write use cases, but the important thing is that it makes sense to you, your team, and the people you have to explain it to.

Alternate Paths and Scenarios

An alternate path is one or more steps that a use case has that are optional, or provide alternate ways to work through the use case. Alternate paths can be additional steps added to the main path, or provide steps that allow you to get to the goal in a totally different way than parts of the main path.

You can have alternate paths that provide additional steps, and multiple ways to get from the starting condition to the ending condition.

A complete path through a use case, from the first step to the last, is called a scenario. Most use cases have several different scenarios, but they always share the same user goal.

Any time you change your use case, you need to go back and check your requirements. Remember to keep things as simple as you can; there’s no need to add complexity if you don’t need it.

Key points to requirement changes

  • Requirements will always change as a project progresses.
  • When requirements change, your system has to evolve to handle the new requirements.
  • When your system needs to work in a new or different way, begin by updating your use case.
  • A scenario is a single path through a use case, from start to finish.
  • A single use case can have multiple scenarios, as long as each scenario has the same customer goal.
  • Alternate paths can be steps that occur only some of the time, or provide completely different paths through parts of a use case.
  • If a step is optional in how a system works, or a step provides an alternate path through a system, use numbered sub- steps, like 3.1, 4.1, and 5.1, or 2.1.1, 2.2.1, and 2.3.1.
  • Try to avoid duplicate code. It’s a maintenance nightmare, and usually points to problems in how you’ve designed your system.


Analysis helps you ensure your system works in a real-world context.

Key points to analysis

  • Figuring out potential problems.
  • Plan a solution.

There is usually more than one solution to a problem, there is usually more than one way to write that solution in a use case.

Write your use cases in a way that makes sense to you, your boss, and your customers. Analysis and your use cases let you show customers, managers, and other developers how your system works in a real world context.

Each use case should detail one particular user goal.

Looking at the nouns (and verbs) in your use case to figure out classes and methods is called textual analysis.

Remember, you need classes only for the parts of the system you have to represent.

A good use case clearly and accurately explains

What a system does, in language that’s easily understood. With a good use case complete, textual analysis is a quick and easy way to figure out the classes in your system.

Textual analysis

Textual analysis tells you what to focus on, not just what classes you should create.

Think about how the classes you do have can support the behavior your use case describes.

Nouns and Verbs

The point is that the nouns are what you should focus on.

The verbs in your use case are (usually) the methods of the objects in your system.

You’ve already seen how the nouns in your use case usually are a good starting point for figuring out what classes you might need in your system. If you look at the verbs in your use case, you can usually figure out what methods you’ll need for the objects that those classes represent: nouns are candidates for classes… not every noun will be a class.


  • Analysis helps you ensure that your software works in the real world context, and not just in a perfect environment.
  • Use cases are meant to be understood by you, your managers, your customers, and other programmers.
  • You should write your use cases in whatever format makes them most usable to you and the other people who are looking at them.
  • A good use case precisely lays out what a system does, but does not indicate how the system accomplishes that task.
  • Each use case should focus on only one customer goal. If you have multiple goals, you will need to write multiple use cases.

Class Diagrams

Class diagrams give you an easy way to show your system and its code constructs as an overall “big picture”. A class diagram describes the structure of a system by showing the system’s classes their attributes, operations (or methods), and the relationships among objects.

  • The attributes in a class diagram usually map to the member variables of your classes.
  • The operations in a class diagram usually represent the methods of your classes.

Class diagrams leave lots of detail out, such as class constructors, some type information, and the purpose of operations on your classes.

Textual analysis helps you translate a use case into code-level classes, attributes, and operations.

The nouns of a use case are candidates for classes in your system, and the verbs are candidates for methods on your system’s classes.

Big applications and problems

Solving big problems

You solve big problems the same way you solve small problems.

The best way to look at a big problem is to see it as lots of individual pieces of functionality. You can treat each of those pieces as an individual problem to solve, and apply the things you already know.

You can solve a big problem by breaking it into lots of functional pieces, and then working on each of those pieces individually.

Two things to take into consideration with big problems are:

  • Encapsulation: The more you encapsulate things, the easier it will be for you to break a large app up into different pieces of functionality.
  • Interfaces: By coding to an interface, you reduce dependencies between different parts of your application.

The best way to get good requirements is to understand what a system is supposed to do.

  • If you know what each small piece of your app’s functionality should do, then it’s easy to combine those parts into a big app that does what it’s supposed to do.

Analysis helps you ensure your system works in a real-world context.

  • Analysis is even more important with large software and in most cases, you start by analyzing individual pieces of functionality, and then analyzing the interaction of those pieces.

Great software is easy to change and extend, and does what the customer wants it to do

  • This doesn’t change with bigger problems. In fact, the higher the cohesion of your app, the more independent each piece of functionality is, and the easier it is to work on those pieces one at a time.
  • Commonality: What things are similar? Helps you determine what is important in your system. What you need to worry about.
  • Variability: What things are different? Helps you determine what is not important in your system. What your system is not like. Tells you what to NOT worry about.

Things to find out in your system:


A feature is just a high-level description of something a system needs to do. You usually get features from talking to your customers (or listening in on their conversations).

You can take one feature, and come up with several different requirements that you can use to satisfy that feature. Figuring out a system’s features is a great way to start to get a handle on your requirements.

Get features from the customer, and then figure out the requirements you need to implement those features.

Use case diagrams

Use cases don’t always help you see the big picture. When you start to write use cases, you’re really getting into a lot of detail about what the system should do. The problem is that can cause you to lose sight of the big picture.

So even though you could start writing use cases, that probably won’t help you figure out exactly what you’re trying to build, from the big-picture point of view. When you’re working on a system, it’s a good idea to defer details as long as you can. You won’t get caught up in the little things when you should be working on the big things.

Even though use cases might be a little too focused on the details for where we are in designing the system right now, you still need to have a good understanding of what your system needs to do. So you need a way to focus on the big picture, and figure out what your system should do, while still avoiding getting into too much detail.

When you need to know what a system does, but don’t want to get into all the detail that use cases require you can use Use Case Diagrams.

Use case diagrams are the blueprints for your system.

The focus here is on the big picture. That use case diagram may seem vague, but they help you keep your eye on the fundamental things that your system must do.

Use your feature list to make sure your use case diagram is complete. Once you have your features and a use case diagram, you can make sure you’re building a system that will do everything it needs to. Take your use case diagram, and make sure that all the use cases you listed will cover all the features you got from the customer. Then you’ll know that your diagram—the blueprints for your system—is complete, and you can start building the system.

Use a feature or requirement list to capture the BIG THINGS that your system needs to do.

Once you’ve got your features and requirements mapped out, you need to get a basic idea of how the system is going to be put together. Use cases are often too detailed, so a use case diagram can help you see what a system is like from far above, a blueprint for your application.

Draw a use case diagram to show what your system IS without getting into unnecessary detail.

Use cases reflect usage, features reflect functionality.

When writing use cases, we’re dealing with just the interactions between actors and a system. We’re just talking about the ways that your system is used (which is where the term “use case” came from).

The features in your system reflect your system’s functionality. Your system must do those things in order for the use cases to actually work, even though the functionality isn’t always an explicit part of any particular use case.

A use case may depend upon a feature to function, but the feature may not actually be part of the steps in the use case itself.

Use cases are requirements for how people and things (actors) interact with your system, and features are requirements about things that your system must do. They’re related, but they are not the same. Still, to implement a system’s use cases, you’re going to need the functionality in the system’s features. That’s why you should always be able to map your features to the use cases that they enable and are used by.

Your customer—and her customers—only interact with your system through the use cases. So if a feature doesn’t at least indirectly make a use case possible, you’re customer really isn’t going to see a benefit. If you think you’ve got a feature that doesn’t really affect how your system is used or performs, talk it over with the customer, but don’t be afraid to cut something if it’s not going to improve your system.

The features in your system are what the system does, and are not always reflected in your use cases, which show how the system is used. Features and use cases work together, but they are not the same thing.

Domain Analysis

Domain analysis lets you check your designs, and still help you speak the customer’s language. Domain analysis, and just means that we’re describing a problem using terms the customer will understand.

The process of identifying, collecting, organizing, and representing the relevant information of a domain, based upon the study of existing systems and their development histories, knowledge captured from domain experts, underlying theory, and emerging technology within a domain.

Domain analysis helps you avoid building parts of a system that aren’t your job to build.


  • The best way to look at a big problem is to view it as a collection of smaller problems.
  • Just like in small projects, start working on big projects by gathering features and requirements.
  • Features are usually “big” things that a system does, but also can be used interchangeably with the term requirement.
  • Use cases are detail-oriented; use case diagrams are focused more on the big picture.
  • Your use case diagram should account for all the features in your system.
  • Domain analysis is representing a system in language that the customer will understand.
  • Commonality and variability give you points of comparison between a new system and things you already know about.
  • An actor is anything that interacts with your system, but isn’t part of the system.


It’s not enough to just figure out the individual pieces of a big problem. You also need to know about how those pieces fit together, and which ones might be more important than others; that way, you’ll know what you should work on first.

Architecture is your design structure, and highlights the most important parts of your app, and the relationships between those parts.

Architecture is the organizational structure of a system, including its decomposition into parts, their connectivity, interaction mechanisms, and the guiding principles and decisions that you use in the design of a system.

First step – Functionality

The first step is to make sure an application does what it’s supposed to do. In small projects, we use a requirements list to write down functionality; in big projects, we’ve are using a feature list to figure those things out: features are about functionality, they focus on what the system has to do, not on what principles or patterns you use to build the system.

We need to figure out which pieces are the most important. Those are the pieces we want to focus on first.

What are the most important features? It’s your job to figure out which features you think are the most important, and then in what order you’d work on those things.

The things in your application that are really important are architecturally significant, and you should focus on them FIRST.

Architecture isn’t just about the relationships between parts of your app; it’s also about figuring out which parts are the most important, so you can start building those parts first.

Questions to ask

  • Is it(the feature) part of the essence of the system?
    • Is the feature really core to what a system actually is. Can you imagine the system without a feature? If not, then you’ve probably found a feature that is part of the essence of a system. When you’re looking at a feature, ask yourself: “If this feature wasn’t implemented, would the system still really be what it’s supposed to be?” If the answer is no, you’ve found yourself an “essence feature.” The essence of a system is what it is at its most basic level. In other words, if you stripped away all the bells and whistles, all the “neat” things that marketing threw in, and all the cool ideas you had, what would the system really be about? That’s the essence of a system.
    • What does it mean?
      • If you’re not sure what the description of a particular feature really means, it’s probably pretty important that you pay attention to that feature. Anytime you’re unsure about what something is, it could take lots of time, or create problems with the rest of the system. Spend time on these features early, rather than late.
      • How do I do it?
        • Another place to focus your attention early on is on features that seem really hard to implement, or are totally new programming tasks for you. If you have no idea how you’re going to tackle a particular problem, you better spend some time up front looking at that feature, so it doesn’t create lots of problems down the road.

If you do not know what something means then you can simply go back to find out what is going on. Get more details and information.

The reason that these features are architecturally significant is that they all introduce risk to your project. It doesn’t matter which one you start with—as long as you are working towards reducing the risk in succeeding. The point here is to reduce risk, not to argue over which key feature you should start with first. You can start with ANY of these, as long as you’re focused on building what you’re supposed to be building.

Focus on one feature at a time to reduce risk in your project Don’t get distracted with features that won’t help reduce risk.

Find out the key features, minimize risks then you can go back to add in more details to your planning, such as using use cases.

Good design will always reduce risk.

OOA&D is all about code—it’s about writing great software, every time. But the way you get to good code isn’t always by sitting down and writing it right away. Sometimes the best way to write great code is to hold off on writing code as long as you can. Plan, organize, architect, understand requirements, reduce risks… all these make the job of actually writing your code very simple.

Customers don’t pay you for great code, they pay you for great software. Great software is more than just great code.

Great code is well-designed, and generally functions like it’s supposed to. But great software not only is well- designed, it comes in on time and does what the customer really wants it to do.

That’s what architecture is about: reducing the risks of you delivering your software late, or having it not work like the customer wants it to. Our key feature list, class diagrams, and those partially done classes all help make sure we’re not just developing great code, but that we’re developing great software.


  • Architecture helps you turn all your diagrams, plans, and feature lists into a well-ordered application.
  • The features in your system that are most important to the project are architecturally significant.
  • Focus on features that are the essence of your system, which you’re unsure about the meaning of, or unclear about how to implement first.
  • Everything you do in the architectural stages of a project should reduce the risks of your project failing.
  • If you don’t need all the detail of a use case, writing a scenario detailing how your software could be used can help you gather requirements quickly.
  • When you’re not sure what a feature is, you should ask the customer, and then try and generalize the answers you get into a good understanding of the feature.
  • Use commonality analysis to build software solutions that are flexible.
  • Customers are a lot more interested in software that does what they want, and comes in on time, than they are in code that you think is cool/great.

Iterating, Testing and Contracts

You can write good software iteratively. Work on the big picture, and then iterate over pieces of the application until it’s complete.

How to choose on which piece to focus on?

You can choose to focus on specific features of the application. This approach is all about taking one piece of functionality that the customer wants, and working on that functionality until it’s complete.

  • You can choose to focus on – Feature driven development: That is when you pick a specific feature in your app, and plan, analyze, and develop that feature to completion.
    • When you’re using feature driven development, you work on a single feature at a time, and then iterate, knocking off features one at a time until you’ve finished up the functionality of an application.
    • Feature driven development is more granular.
      • Works well when you have a lot of different features that don’t interconnect a whole lot.
      • Allows you to show the customer working code faster.
      • Is very functionality-driven. You’re not going to forget about any features using feature driven development.
      • Works particularly well on systems with lots of disconnected pieces of functionality.
      • Or – Use case driven development: when you pick a scenario through a use case, and write code to support that complete scenario through the use case.
        • With use case driven development, you work on completing a single scenario through a use case. Then you take another scenario and work through it, until all of the use case’s scenarios are complete. Then you iterate to the next use case, until all your use cases are working. With use case driven development, you work from the use case diagram, which lists the different use cases in your system.
        • Use case driven development is more “big picture”.
          • Works well when your app has lots of processes and scenarios rather than individual pieces of functionality.
          • Allows you to show the customer bigger pieces of functionality at each stage of development.
          • Is very user-centric. You’ll code for all the different ways a user can use your system with use case driven development.
          • Works particularly well on transactional systems, where the system is largely defined by lengthy, complicated processes.

Both approaches to iterating are driven by good requirements. Because requirements come from the customer, both approaches focus on delivering what the customer wants.

Your customers want to see something that makes sense to them. Feature driven development allows you to create faster results for your customer to see something concrete. Test scenarios are also a way to show to the customer how things works.

Writing test scenarios

Test cases don’t have to be very complex; they just provide a way to show your customer that the functionality in your classes is working correctly.

You should test your software for every possible usage you can think of. Be creative! Don’t forget to test for incorrect usage of the software, too. You’ll catch errors early, and make your customers very happy.

Good software development is about mixing a lot of different methods and ways of doing things based on the situation and what is required of the software. You might start with a use case (use case driven development), and then choose just a small feature in that use case to start working on (which is really a form of feature driven development). Finally, you might use tests to figure out how to implement that feature (feature driven development).

You want to keep your tests simple, and have them test just a small piece of functionality at a time. If you start testing multiple things at once, it’s hard to tell what might have caused a particular test to fail. You may need a lot more tests, but keep each one focused on a very specific piece of functionality. each test really focuses on a single piece of functionality. That might involve one method, or several methods.

Test driven development focuses on getting the behavior of your classes right.

Design decisions are always a tradeoff. Design choices have both positive and negative effects. The smart thing to do is to reevaluate your design decisions and make sure they are working. Be courageous in changing your design and implementation for the best possible end result.

Iteration is really the key point here. Lots of design decisions look great at one stage of your development, but then turn out to be a problem as you get deeper into a particular part of your app. So once you make a decision, stick with it, and iterate deeper into your application. As long as your design is working, and you’re able to use good OO principles and apply design patterns, you’re in good shape. If you start running into trouble with a decision, though, don’t ever be afraid to change designs and rework things.

You always have to make a choice, even if you’re not 100% sure if it’s the right one. It’s always better to take your best guess, and see how things work out, rather than spend endless hours debating one choice or another. That’s called analysis paralysis, and it’s a sure way to not get anything done. It’s much better to start down one path, even if you’re not totally sure it’s the right one, and get some work done, than to not make a choice at all.

Good software is built iteratively. Analyze, design, and then iterate again, working on smaller and smaller parts of your app. Each time you iterate, reevaluate your design decisions, and don’t be afraid to CHANGE something if it makes sense for your design.

What makes a good test case

  • Each test case should have an ID and a name
    • The names of your test cases should describe what is being tested. Test names with nothing but a number at the end aren’t nearly as helpful as names like testProperty() or testCreation().
    • Each test case should have one specific thing that it tests.
      • Each of your test cases should be atomic: each should test only one piece of functionality at time. This allows you to isolate exactly what piece of functionality might not be working in your application.
      • Each test case should have an input you supply.
        • You’re going to give the test case a value, or a set of values, that it uses as the test data. This data is usually then used to execute some specific piece of functionality or behavior.
        • Each test case should have an output that you expect.
          • Given your input, what should the program, class, or method output? You’ll compare the actual output of the program with your expected output, and if they match, then you’ve got a successful test, and your software works.

Anatomy of a Test Case

Fields in test cases:

  • Test case id
  • Requirement # / Section
  • Objective [What is to be verified? ]
  • Assumptions & Prerequisites
  • Test Procedure Steps to be executed
  • Test data: Variables and their values
  • Expected result
  • Actual result
  • Status: Pass/Fail
  • Comments
  • Functional Test Cases
  • Performace Test Cases
  • Security Test Cases
  • Integration Test Cases
  • Positive Test Cases
  • Negative Test Cases
  • Database Test Cases
  • Acceptance Test Cases
  • Usability Test Cases

Types Of Test cases

Software Testing Techniques and Methods

White box testing:

White box testing is done by the Developers. This requires knowledge of the internal coding of the software.

White box testing is concerned with testing the implementation of the program. The intent of this testing is not to exercise all the different input or output conditions, but to exercise different programming structures and data structures used in the program. It is commonly called structural testing.

White box testing mainly applicable to lower levels of testing: Unit testing and Integration Testing.

Implementation knowledge is required for white box testing.

Black box testing:

Black box testing is done by the professional testing team. This does not require knowledge of internal coding of the application. Testing the application against the functionality of the application without the knowledge of internal coding of the software.

In Black box testing the structure of the program is not considered. Test cases are decided solely on the basis of the requirements or specification of the program or module.

Black box testing mainly applicable to higher levels of testing: Acceptance Testing and System Testing.

Implementation knowledge is not required for black box testing.

Gray box testing:

A combination of Black Box and White Box testing methodologies: testing a piece of software against its specification but using some knowledge of its internal workings. It can be performed by either development or testing teams. In gray box testing tester applies a limited no of test cases to the internal working of the software under test. In remaining part of gray box testing one takes a black box approach in applying inputs to the software under test and observing the outputs.

Unit testing:

The first test in the development process is the unit test. Testing of individual software components or modules. Typically done by the programmer and not by testers, as it requires detailed knowledge of the internal program design and code. Unit test depends upon the language on which the project is developed.

Integration testing:

Testing in which modules are combined and tested as a group. Modules are typically code modules, individual applications, client and server applications on a network, etc. Integration Testing follows unit testing and precedes system testing.

Regression testing:

Testing the application as a whole for the modification in any module or functionality. Such testing ensures reported product defects have been corrected for each new release and that no new quality problems were introduced in the maintenance process.

Usability testing:

Application flow is tested, Can new user understand the application easily, Proper help documented whenever user stuck at any point. Basically system navigation is checked in this testing.

Performance testing:

Functional testing conducted to evaluate the compliance of a system or component with specified performance requirements. It is usually conducted by the performance engineer. Performance testing will determine whether or not their software meets speed, scalability and stability requirements under expected workload.

Types of performance testing:

Load testing- Load testing is the simplest form of performance testing .Load testing is a generic term covering Performance Testing and Stress Testing. Testing technique that puts demand on a system or device and measures its response. It is usually conducted by the performance engineers.

Stress testing- Stress testing is normally used to understand the upper limits of capacity within the system Testing technique which evaluates a system or component at or beyond the limits of its specified requirements to determine the load under which it fails and how. It is usually conducted by the performance engineer.


Stress testing involves testing an application under extreme workloads to see how it handles high traffic or data processing .The objective is to identify breaking point of an application.

Volume testing- testing which confirms that any values that may become large over time (such as accumulated counts, logs, and data files), can be accommodated by the program and will not cause the program to stop working or degrade its operation in any manner. It is usually conducted by the performance engineer.

Endurance testing- It is also known as soak testing. Type of testing which checks for memory leaks or other problems that may occur with prolonged execution. It is usually performed by performance engineers.

Scalability testing– The objective of scalability testing is to determine the software application’s effectiveness in “scaling up” to support an increase in user load. It helps plan capacity addition to your software system.

Spike testing- tests the software’s reaction to sudden large spikes in the load generated by users i.e.  Spike testing is done by suddenly increasing the number of, or load generated by, users by a very large amount and observing the behavior of the system. The goal is to determine whether performance will suffer, the system will fail, or it will be able to handle dramatic changes in load.

Recovery testing:

Testing technique which evaluates how well a system recovers from crashes, hardware failures, or other catastrophic problems. It is performed by the testing teams.

Security Testing-:

A process to determine that an information system protects data and maintains functionality as intended. It can be performed by testing teams or by specialized security-testing companies. Testing how well the system protects against unauthorized internal or external access. Checked if system, database is safe from external attacks.

Conformance testing:

Verifying implementation conformance to industry standards. Producing tests for the behavior of an implementation to be sure it provides the portability, interoperability, and/or compatibility a standard defines.

Smoke testing:

Testing technique which examines all the basic components of a software system to ensure that they work properly. Typically, smoke testing is conducted by the testing team, immediately after a software build is made .

Compatibility testing:

Testing to ensure compatibility of an application or Web site with different browsers, OSs, and hardware platforms. Compatibility testing can be performed manually or can be driven by an automated functional or regression test suite.

System testing:

Testing conducted on a complete, integrated system to evaluate the system’s compliance with its specified requirements. System testing falls within the scope of black box testing, and as such, should require no knowledge of the inner design of the code or logic.

Alpha testing:

Type of testing a software product or system conducted at the developer’s site. Usually it is performed by the end user. Testing is done at the end of development.

Beta testing:

Testing typically done by end-users or others. Final testing before releasing application for commercial purpose.

Acceptance testing:

Testing to verify a product meets customer specified requirements. A customer usually does this type of testing on a product that is developed externally.

Comparison testing:

Comparison of product strengths and weaknesses with previous versions or other similar products.

Sanity testing:

Testing to determine if a new software version is performing well enough to accept it for a major testing effort. If application is crashing for initial use then system is not stable enough for further testing and build or application is assigned to fix.

Ad-hoc Testing:

Testing performed without planning and documentation – the tester tries to ‘break’ the system by randomly trying the system’s functionality. It is performed by the testing teams.

“ –

Install/uninstall testing – Tested for full, partial, or upgrade install/uninstall processes on different operating systems under different hardware, software environment.

“Internationalization and localization

The general ability of software to be internationalized and localized can be automatically tested without actual translation, by using pseudolocalization. It will verify that the application still works, even after it has been translated into a new language or adapted for a new culture (such as different currencies or time zones).” –

A sample testing cycle

“Although variations exist between organizations, there is a typical cycle for testing.[45] The sample below is common among organizations employing the Waterfall development model. The same practices are commonly found in other development models, but might not be as clear or explicit.

  • Requirements analysis: Testing should begin in the requirements phase of the software development life cycle. During the design phase, testers work to determine what aspects of a design are testable and with what parameters those tests work.
  • Test planning: Test strategy, test plan, testbed creation. Since many activities will be carried out during testing, a plan is needed.
  • Test development: Test procedures, test scenarios, test cases, test datasets, test scripts to use in testing software.
  • Test execution: Testers execute the software based on the plans and test documents then report any errors found to the development team.
  • Test reporting: Once testing is completed, testers generate metrics and make final reports on their test effort and whether or not the software tested is ready for release.
  • Test result analysis: Or Defect Analysis, is done by the development team usually along with the client, in order to decide what defects should be assigned, fixed, rejected (i.e. found software working properly) or deferred to be dealt with later.
  • Defect Retesting: Once a defect has been dealt with by the development team, it is retested by the testing team. AKA Resolution testing.
  • Regression testing: It is common to have a small test program built of a subset of tests, for each integration of new, modified, or fixed software, in order to ensure that the latest delivery has not ruined anything, and that the software product as a whole is still working correctly.
  • Test Closure: Once the test meets the exit criteria, the activities such as capturing the key outputs, lessons learned, results, logs, documents related to the project are archived and used as a reference for future projects.”

Programming by contract

When you’re writing software, you’re also creating a contract between that software and the people that use it. The contract details how the software will work when certain actions are taken—like requesting a non-existent property in an object.

If the customer wants an action to result in different behavior, then you’re changing the contract. So if your framework should throw an exception when a non-existent property is queried, that’s fine; it just means that the contract between the frame users and the framework has changed.

When you program by contract, you and your software’s users are agreeing that your software will behave in a certain way. Programming by contract is really all about trust.

When you return null, you’re trusting programmers to be able to deal with null return values. Programmers are basically saying that they’ve coded things well enough that they won’t ask for non-existent properties, so their code just doesn’t worry about getting null values back from an object.

But what happens if you don’t think your code will be used correctly? Or if you think that certain actions are such a bad idea that you don’t want to let users deal with them in their own way? In these cases, you may want to consider defensive programming. Instead of returning a null you throw an exception that forced the user of your code to deal with the situation.

When you are programming by contract, you’re working with client code to agree on how you’ll handle problem situations. When you’re programming defensively, you’re making sure the client gets a “safe” response, no matter what the client wants to have happen.


  • The first step in writing good software is to make sure your application works like the customer expects and wants it to.
  • Customers don’t usually care about diagrams and lists; they want to see your software actually do something.
  • Use case driven development focuses on one scenario in a use case in your application at a time.
  • In use case driven development, you focus on a single scenario at a time, but you also usually code all the scenarios in a single use case before moving on to any other scenarios, in other use cases.
  • Feature driven development allows you to code a complete feature before moving on to anything else.
  • You can choose to work on either big or small features in feature-driven development, as long as you take each feature one at a time.
  • Software development is always iterative. You look at the big picture, and then iterate down to smaller pieces of functionality.
  • You have to do analysis and design at each step of your development cycle, including when you start working on a new feature or use case.
  • Tests allow you to make sure your software doesn’t have any bugs, and let you prove to your customer that your software works.
  • A good test case only tests one specific piece of functionality.
  • Test cases may involve only one, or several, methods in a single class, or may involve multiple classes.
  • Test driven development is based on the idea that you write your tests first, and then develop software that passes those tests. The result is fully functional, working software.
  • Programming by contract assumes both sides in a transaction understand what actions generate what behavior, and will abide by that contract.
  • Methods usually return null or unchecked exceptions when errors occur in programming by contract environments.
  • Defensive programming looks for things to go wrong, and tests extensively to avoid problem situations.
  • Methods usually return “empty” objects or throw checked exceptions in defensive programming environments.

The Lifecycle of software development

Feature List

Figure out what your app is supposed to do at a high level. Feature lists are all about understanding what your software is supposed to do.

Use Case Diagrams

Nail down the big processes that your app performs, and any external forces that are involved. Use case diagrams let you start thinking about how your software will be used, without getting into a bunch of unnecessary details.

Break Up the Problem

Break your application up into modules of functionality, and then decide on an order in which to tackle each of your modules.


Figure out the individual requirements for each module, and make sure those fit in with the big picture.

Domain Analysis

Figure out how your use cases map to objects in your app, and make sure your customer is on the same page as you are.

Preliminary Design

Fill in details about your objects, define relationships between the objects, and apply principles and patterns.


Write code, test it, and make sure it works. Do this for each behavior, each feature, each use case, each problem, until you’re done.


You’re done! Release your software.

OOA&D is about having lots of options. There is never one right way to solve a problem, so the more options you have, the better chance you’ll find a good solution to every problem.



Head First Object-Oriented Analysis & Design – Book