The Design Process
So we’ve looked at requirements, now we’re onto looking at the next stage of the software engineering life cycle - design.
Design is the process of deciding how to implement the client’s requirements and coming up with a resulting design plan. This occurs at different levels a high level view will address the structure of the system and later lower level views will focus on the design of individual units.
Designing software is an iterative process of modelling, where different decompositions are tried; analysis, where the architecture is assessed; documentation, where architectural decisions are recorded; and review where the design is compared against the requirements. The final outcome is a software architecture document (SAD) which describes the architecture.
Choosing the Right Design
The process of designing a system isn’t simple, there are many possible permutations of a working design, there are a lot of possibilities that have to be accounted for, and there are quality attributes of designs that have to be considered such as how easy the system is to scale, maintain, and add-to. There are also external factors such as libraries, open standards, and regulation to consider.
Utilising Existing Solutions
To reduce the time spent designing it’s possible to leverage existing material. Many designs can adapt solutions from similar problems. Sometimes it’s possible to clone and tweak a solutions other times there are already reference models, models that suggest how to decompose a system.
For times when there aren’t a reference model it’s possible to leverage existing software architectural style. An element of good design is selecting the correct architectural style.
Elements of Good Design
A good design will utilise many of the following elements.
- Existing Design Patterns - Well understood generic solutions
- These are common for common tasks such as designing customers databases, tasks such as calculating age, etc.
- Design Conventions - Collection of advice that taken together promotes a good practice
- Such as placing navigation on the left or top of the page on the web or using Retrofit with RxJava in Android
- Design Principles
- Such as allowing undo in Android and hiding the majority of options in an overflow menu
Modelling Architecture
Models of suggested architecture:
- Helps us to understand the system better
- Helps us to determine the reuse from other systems and how reusable the proposed system would be
- Provide a blueprint for system construction
- Helps us to reason about how the system might evolve
- Helps us to analyse dependencies
- Helps us to understand the risks of a system
Decompositions
Some problems have no existing solutions so the solution must be decomposed into modules. A modular system describes a system where each system activity is performed by exactly one unit of the system (no activity is defined twice) and the inputs and outputs of the unit are defined. A unit is well defined when the interface of the software accurately describes its external behaviour (the unit’s behaviour isn’t defined elsewhere).
There are a number of popular types of decomposition.
Functional Decomposition
Produces modules based on function. Functions listed in the requirements specification are split into modules which are further divided into lower level functions. Described by which functions call each other.
Featured Oriented Decomposition
Assigns features to modules. High level designs describe the system as a service with a collection of features. Low level designs describe each feature and identifies interactions between features.
Data-Oriented Decomposition
Describes how data will be divided into modules. High level design describes conceptual data structures. Low level designs describes the distribution of data between modules.
Process Orientated Decomposition
Partitions the system into concurrent process. The design identifies the main task of the system and allocates them to processes and describes how the tasks coordinate with each other.
Event Orientated Decomposition
Focuses on the events the system must handle and divides the responsibility for events between modules. A high level design describes the systems expected input events and a low level design decomposes the system into states and describes the how events trigger transitions between states
Object Oriented Decomposition
Divides objects between modules. High level designs describe object types and describes how they are related to each other. Low level designs describe the objects attributes and operations.
Component Based Engineering
Component based software development is a method of making software by assembling existing components, self contained software with interfaces that could be developed as a distinct entity. Component based engineering allows rapid development of software by reducing the need to develop components by using integration and reducing the need to maintain components by using component replacement. At the present moment this is more of a goal than a reality.
Architectural Views
The architecture of a system can be displayed in a number of views. Each focusing on a different aspect of the design and therefore providing a different context for understanding the architecture.
Decomposition View
The decomposition view portrays the system as programmable units in a typically hierarchical fashion.
Dependencies View
The dependencies view shows dependencies between software units and is useful for assessing the impact of making changes to a module
Generalisation View
The generalisation view shows software units that are generalisations or specialisations of one another. Useful for designing abstract or extendable units.
Implementation View
Maps units to source files to help developers identify the files associated with a specific unit.
Execution View
The execution view shows the runtime structure of a system in terms of components and connectors where each component is a distinct entity that’s executing and each connector is a communication mechanism such as a communication channel, data repository, or remote procedure call.
Deployment View
Maps runtime entities such as a components and connectors to resources such as processors, data stores, and networks. This is helpful for analysing the non-functional elements of a design such as performance and security.
Work Assignment View
Divides the system design into work tasks that are assigned to teams. Helps project managers allocate resources and track progress.
Quality Attributes
Modifiability
Having a design that is easy to change is important. When change occurs units can be categorised in two ways, ones that are directly affected and ones that are indirectly affected.
Managing Change
When change occurs units that are classified as ‘directly affected’ responsibilities need to change and indirectly affects units do not need to have their responsibilities changed but their implementation needs to change.
Minimising the Impact: Directly Affected
Minimising the impact of change for directly affected units focuses on clustering anticipated changes. Encapsulating areas that are likely to change in a software unit helps limit the amount that needs to be changed to that area. Keeping units cohesive increases the chances that a change to the systems’ responsibilities is confined to a few units assigned those responsibilities. Keeping units general makes it less likely the unit needs to be drastically modified and more likely that just the unit’s output needs to be altered.
Minimising the Impact: Indirectly Affected
Minimising the impact on indirectly affected units focuses on reducing the dependencies of units. Lowering the coupling of units reduces the likelihood that a change to one unit will ripple to other units. If units interact only through interfaces changes to one unit will not spread beyond the unit’s boundaries unless the interface changes. A unit modified to provide new data or services can add a new interface without changing any of the units existing interfaces.
Self Managing Software
The idea of self managing software is that the software dynamically changes its behaviour based on its environment or its own performance. For example, changing web servers that are queried based on load, scaling servers up or down based on load, or moving processes between processors to balance processor load. There are challenges with monitoring non-functional requirements (security for instance) but performance can be measured using attributes such as: response time, throughput, and load. Performance can then be managed by changing resource allocation to a different algorithm: first come/first served, priority, or earliest deadline for instance, or by reducing demand.
Security
Security is divided into immunity, the system’s ability to prevent an attack, and resilience, the system’s ability to recover from an attack. Immunity can be achieved by including security features and minimising exploitable areas. Resilience can be achieved by using an architecture that segments functionality to limit the area of functionality that is exposed to an attacker.
Reliability
Reliability describes a system’s ability to correctly perform its function. A system is made more reliable by being able to tolerate or prevent human error. This can be achieved by passively detecting faults when the occur (incorrect input), actively checking for symptoms of faults and restoring those conditions, and handling exceptional circumstances in a way that the system returns to a normal state.
Fault recovery is another technique for making a system reliable. Techniques for recovering from a fault include:
- Undoing Transactions - Managing actions as a single transaction, or with a transaction log, allowing the actions to be reversed if a fault occurs at any point
- Rollbacks - Storing the system state at a particular point allowing the system to restore that version if a fault occurs
- Backup - The system replaces faulty unit with a backup
- Correct and Continue - The system detects the error, fixes it, and continues
- Report - The system returns to a previous state and reports the error to a unit responsible for handling an error
N-Version programming was typically thought to be a way of preventing faults occurring in software. N many different design teams produce designs at different times. The chance of a fault occurring in all copies of the design was thought to be very low. However designers typically design in similar ways using similar or identical patterns, principles, and paradigms reducing the effectiveness of the technique.
Robustness
Robustness is similar to reliability but refers to the system’s ability to handle faults from external agents, either the environment or in another unit. It works on the principle of mutual suspicion where each unit of software assumes the other is faulty.
Tactics for recovery include: offering a reduced service, triggering an exception, or aborting a transaction.
Usability
Usability, we discussed previously, is how easy a user finds it to operate a system. User interfaces should be in their own software unit
Business Goals
Business goals refer to the quality attributes a software needs to consider related to the cost and time spent on a project. There are several tensions
- Buy vs. Build
- Initial Development vs. Maintenance Costs
- New vs. Known Technologies
Collaborative Design
The design of software usually happens in teams and as a result there are a number of decisions that need to be made about how the group works. For example who designs what parts of the system, how the group should document the system, and how the group should coordinate the integration of units.
Distributed development is a particular challenge.