Software engineers spend a significant part of their career lives finding and fixing bugs. Better quality control, in the form of defect prevention could free up software engineers’ time for more productive work. This could in turn significantly reduce software maintenance costs.
According to Leffingwell and Widrig (2012) (1), the objective of professional software development is to deliver fit for purpose software solutions that are reasonably free of bugs or defects, and that meet the expectations of users, within budgetary constraints. Such quality software applications are often attributed to how well the requirements are understood and how well the implementation conforms to a thorough design.
It is important for professional software developers to have a good understanding of the objectives of the development process tasks and how quality control can be embedded and intentionally applied from the onset. This ensures that problems and defects are detected and resolved early to avoid propagating them to the implementation phase, therefore, minimising the defects in the delivered product.
The success of an application, and possibly of the organization, can rest on the quality of the software products. Software defects lead to costly rework, schedule overruns, decreased quality and lower customer satisfaction. This article provides experience-based practices and key concepts adapted from referenced literature, which can be used by any organization to improve the software development process.
This article looks at each of the four tasks considered to be at the core of professional software development. With reference to literature, an understanding of these tasks and their objectives is explained. The goal of this article is to share techniques that can be directly applied by software personnel to improve their products and avoid costly mistakes and oversights.
Software specification is the process of comprehensively defining what the software application will do. Depending on the methodology employed (agile or waterfall) the level of formality and the stage at which the task is executed will vary. Ultimately, the specification should include software requirements as a complete and accurate account from a user’s perspective. There should be no room for making assumptions when these requirements are then transformed to programs.
The author of a software requirements specification would ideally be a hybrid person with excellent communication skills, a good understanding of the business or process, and familiar enough with the technology to know its capabilities and limitations. Depending on the nature of the software, the requirements can be written in simple natural language for ease of understanding between non-technical stakeholders and the project team. The advantage of natural language is that it is unconstrained, easy to use and requires no additional training.
For applications where accuracy is paramount, such as safety-critical or high-reliability applications, these requirements can be further specified using formal languages that are rooted in mathematical modelling to eliminate any ambiguities and allow for correctness proof.
The purpose of a software specification is to document the intended objectives of the software under development. The specification becomes the basis of agreement between the stakeholders and the developers. If it is complete and clear it saves developers’ time by eliminating the need to go back and forth to clarify requirements with stakeholders. It also forms the basis for user acceptance testing where the software is validated for completeness and accuracy. It therefore needs to be written in such a manner that it is precise, clear, easy to understand, and meaningful to a wider audience.
Dr. Roland Petrasch (2), gives a detailed analysis that emphasizes the concept of embedding quality control at requirements specification. In his analysis, software quality is effectively measured when the characteristics relevant for quality are precisely defined with the requirements. A requirements specification would not be complete without specifying the appropriate attributes that can be checked against for quality inspection.
Because of time constraints, most software projects struggle to complete within the budgeted time. It is therefore important to prioritise software requirements at the time of specification and agree to the priorities with the stakeholders. These priorities help project managers with planning and scheduling to ensure all mandatory requirements are implemented first, and the ‘nice to haves’ can be done if time permits.
For purposes of quality control, it is essential that third parties review the software specification before it is baselined for ensuing development tasks. It is therefore important to identify the right people from the technical and functional domain that can review and sign it off. There needs to be at least one user representative to verify that it adequately captures the purpose of the system. Testing and solution design representatives are also vital on the review team to ensure that it is fit for purpose as a base for implementation and for testing.
The following are some of the validation checks that can be carried out for the software requirements specification:
- Ensure that all requirements derived from analysis have been specified.
- Ensure that the level of detail is consistent and appropriate to eliminate ambiguities.
- Ensure that each requirement is unique and is uniquely identified.
- Ensure that each requirement is within the scope of the project.
- Ensure that every requirement is an actual requirement that is feasible.
- Ensure that the requirements as stated are testable and measurable. All quality attribute goals explicitly documented and quantified, with the acceptable exceptions specified
- Ensure that performance requirements have been captured and are complete, and consistent with the higher-level requirements.
- Ensure that every requirement is appropriately categorized according to priority.
Any review issues identified should be appropriately addressed until all parties are satisfied that the spec is fit for purpose. It is worthwhile to spend time at this stage to get it right. It may also be necessary to have a formal checklist that that the review team could use as a guideline to ensure a rigorous and consistent approach to the review process. Once agreed, the other tasks that depend on the specification details can be started with confidence.
Software design is the process that transforms software requirements into a blueprint for constructing the solution. It moves the focus from the problem domain to a solution domain by addressing how to fulfil the software requirements.
Software design is an iterative process that starts with a holistic view of the software, recursively breaks it down to design models that are complete with their relevant data structures, architecture, interfaces, and program components. It is a technical decision-making process that requires creative skill, past experience, sound technical knowledge, and an overall commitment to software quality. A poor design leads to a poor product.
The design process could be seen as the application of software theory to product development. It should indicate a systematic and rigorous approach to building systems that accurately fulfill user requirements including quality attributes such as efficiency and reliability. The concepts in this section have been adapted from Helm, Johnson and Vlissides (1994) (3), and past experiences and reflection on these concepts.
The rationale behind software design is to make it easy for implementers to create and maintain the software application. A good design should identify the program components to be implemented and their associated data structures as drawn from recognizable data patterns. It should also lead to interfaces that illustrate in simple terms, the connections between components and with the external environment.
A good software design leads to improved software quality and developer productivity. It lays a good foundation upon which the product is built and identifies the technologies and approaches that best satisfy the system requirements. If the design is meticulous, then conforming to it on implementation increases the likelihood of good quality software.
A good design is also helpful for documentation and to communicate details about the product for purposes of future maintenance or review. If the design specification is maintained up to date with software changes, it gives future developers the ability and option to understand the application without trying to figure it out from the code.
The specific approaches and techniques used for software design vary depending on the criticality and complexity of the system being developed. For example, designing a website for your local club, designing a retail web application, and designing air traffic control software are all very different in terms of formality and what you need to formally document.
The methodology that is used to build the system also determines the approach to design. For example, in the traditional waterfall methodology, the design is completed and signed off before moving on to implementation.
In the more iterative and incremental methodologies, the initial design is based on known requirements at the time. This design evolves as additional features are added to the product.
For purposes of embedding quality control in the development process, the overall design should be reviewed and agreed upon, before coding starts. Typically the review panel would be made up of the design authority and other technical people with the relevant technical skills to adequately evaluate suitability of the proposed solution.
The following are some checks that can be carried out ensure the design is fit for purpose:
- The design should be complete and accurate. Check that the design implements all of the requirements specified including non-functional requirements such as performance and availability. Ensure that the design logic is accurate, and the appropriate technology is used.
- Check that the design has been decomposed to the appropriate level at which the next task may commence. This depends on the development approach. If solution implementers are expected to derive detailed designs then it has to be at a level where they can identify the components and further design as required. Detailed designs should contain details for the individual components logic, interfaces and data structures.
- Check that the design conforms to applicable standards. Ensure that the proposed security implementation, user interfaces, and data structures are compliant with organisational policy or industry specific standards.
- Check that low level design components are consistent with the high-level design and can be traced back to the requirements.
- Ensure that the level of detail for all low-level components is consistent. For example, if pseudocode is used, then it has to be consistent in its level of detail across all components.
- Check if all the data structures have been derived and can be traced back to the requirements. All the defined data must be used and correct default values specified if required.
- Check if all interfaces are clear and well defined. The data types and sizes at each interface should be compliant with external system formats.
- Check for duplicated components. Reuse existing logic wherever possible
The above list is not exhaustive. More checks can be done depending on the software being developed. It takes experience, attention to detail, and an inclination for software quality to review a technical design effectively. Because the design lays the blueprint for the actual software product, it is important that it is done rigorously to minimise software defects and improve programmers’ productivity.
Design reviews are not just red tape designed to tick some boxes on the software process. The amount of time taken to get them right does pay back on the rework that would otherwise be required if defects were to be picked up later in the process. Coding should only start when all design issues have been addressed and all stakeholders are happy with the proposed solution.
Coding, also known as programming, is the construction of computer programs. According to Downey (2015) (4), a program is a sequence of instructions that specifies how to perform a computation. The computer programs, also known as source code, are the deliverables that represent the actual product of a software project. A good design specification is of no use if the software application does not work. Because of this significance, there is a general misconception that software development is all about programming.
The formal software development process starts with the formulation of a problem (problem analysis and requirements specification), finding a solution (software design), and expressing the solution (coding). Each of the steps detailed above are critical for the production of good quality code that meets user requirements.
Programming languages are used for coding. There are many different languages available and the choice of language used is subject to many considerations, such as existing technology, system requirements, suitability to task, or individual preference. Ideally, the programming language best suited for the task at hand will be proposed as part of the solution design.
Professional programming requires expertise in programming languages, knowledge of the application domain and a good understanding of core computing theory, which is vital in formulating effective algorithms. Programmers also contribute to planning through knowledgeable estimation. Testing, technical documenting, and debugging, are some of the skills necessary for a professional programmer. Success in programming will come through improved education and experience (C.A.R Hoare (IEEE Software)) (5).
The coding style of the implementer has a direct impact on the overall quality of the program. The onus is upon the implementer not only to conform to the solution design but to also consciously pursue high quality standards in writing code. Jones and Bonsignour (2012) (6) elaborate this concept.
Good quality software and code does not happen by accident. It happens as a result of consistent and passionate focus by the software implementation team to meet quality goals. The following concepts, adapted from Diomidis Spinelli’s (7) rules for writing quality code, present some guidelines for adopting an attitude that promises quality professional code:
- Keep things simple
Keep your code logic simple and to the point. Do not over-engineer your solution by doing more than required. Keep your code blocks short. Each function or procedure should be designed to accomplish a single objective. Code that is simply written is easier to maintain and tends to be less prone to errors because, the more complex the code is, the more difficult the bugs are to find and the more likely there are to be hidden bugs.
- Know and follow the established style guide.
There is no need to worry about your personal style of coding. Know the organisation’s development standard and follow it. Be consistent with the established conventions for naming and structuring code blocks. Use white spaces appropriately and consistently to achieve code that is readable. Be sure to adopt the conventions of the framework in which you are programming. Adhering to coding standards result in greater consistency and uniformity of delivered code regardless of programmer style. Dr. Roland Petrasch (2), in his paper, also emphasizes the importance of standards in driving the quality of a software product.
- Do not reinvent the wheel
The goal for professional software development is to develop quality software as quickly as possible. Redesigning a solution that already exists defeats this productivity goal. As a developer, learn what API functionality is available in your programming framework, and also what is available through mature, widely adopted third-party libraries. That code has already been proven to be stable and correct. So for productivity and quality’s sake, resist the temptation to rewrite code for functionality that already exists.
- Unit test your work as you go.
Develop your programs in incremental stages and unit test as you go. This approach simplifies testing by allowing you to catch errors early, close to their source. This approach of integrating testing with development ensures that every single unit works correctly, independently from others, giving you a high level of confidence that the whole system will work as expected when the units are integrated. If unit testing is done properly, later testing phases will be more successful. There is a difference, however, between casual, ad-hoc unit testing based on knowledge of the problem, and structured, repeatable unit testing based on the requirements of the system. Beck (2003) (8) illustrates this concept through examples of how coding logic can be driven by a testing mindset.
In most software projects, the only reliable documentation available to programmers is the code itself, McConnell (2004) (9). With the evolution of software, requirements specifications and design documents can go out of date, but the source code is always up to date. Consequently, it is imperative that the source code be of the highest possible quality. Peer reviews by other programmers should be used to check and enforce the following quality attributes – the following attributes have been adapted from Microsoft’s Solution Development Fundamentals.
At the program level, this is a measure of how often the results of a program are correct. This is verified by checking the conceptual correctness of algorithms, and general programming mistakes such as logic errors. These checks need an eye for detail to expose issues such as division by zero that could arise from uninitialized variables.
Program robustness is the ability of a system to cope with errors during execution and to cope with erroneous input (wiki). Robust programming requires code to handle unexpected situations by terminating gracefully and displaying appropriate error messages. Code reviewers need to verify that contingencies are planned for and handled gracefully in the code logic.
The application’s user interfaces must be designed with the end user in mind so that they provide a positive overall user experience. Regardless of other issues, users can reject an application if they find it difficult to use.
Ensure that screen designs confirm to established user interface standards for a consistent look and feel. Input flows should be logical and intuitive to maximize ease of use. Also check that appropriate control types such as checkboxes, selection lists or radio groups are used to improve the clarity, cohesiveness and completeness of a program’s user interface.
Software applications do not work in isolation but have to interconnect with other external systems. Interoperability is an application’s ability to interact and exchange information with other systems. Check that the standards for communication protocols, security policy, interfaces and data formats are adhered to. The data formats used within the application should be compatible with the data formats of the external systems that it interacts with.
Maintainability is the ease with which a program can be modified by its present or future developers in order to make improvements or customizations, fix defects, or adapt it to new environments. Check that the code logic is simple and clear for future developers to understand.
Reviewers should watch out for hard coded values, excessive component dependencies, and direct communication between components on different layers of the application’s architecture. References to framework APIs or public libraries should be made in such a way that future upgrades to the APIs or libraries would have minimum impact on the software.
A program’s efficiency is a measure of its run time performance. Run time performance is influenced by the system resources a program consumes such as processor time, memory space, physical disk access, and network bandwidth: the less, the better. Peer review checks should look out for poor management of resources, for example memory leaks and failing to clean up temporary files. Programs should also be designed to minimize physical disk access
Testing is often the most misunderstood task in the software development process. In many cases where the software is riddled with defects, the problem is often attributed to poor testing. It is not uncommon for user acceptance testing to identify defects that could be attributed to misunderstood or missed user requirements. At that point it would be too late to resolve them before delivery leading to the common ‘known defects’, on the delivered software.
According to Dustin (2002) (10), to be most effective, the testing effort must be integrated into the software-development process as a whole. Isolating the testing effort to the end of the software life cycle is a common mistake that must be avoided.
An effective software testing programme constitutes a lot more tasks than the actual mechanics of testing. Successful testing for large projects includes tasks such as test planning, formulating the appropriate test strategy for the specific software being developed, preparation of appropriate test data and environments, and documentation of test procedures and results.
It is important that a representative of the test team is involved in the requirements phase, and also informed of all requirements changes. It is in this phase that a thorough understanding of the system and its requirements can be obtained, (10). This understanding is essential for testers to determine the goals of the testing effort, approaches to the test strategy, and considerations related to data, environments, and the software itself.
The preparation of user acceptance test cases is based on the user requirements to ensure completeness and correctness. In addition, having a member of the test team at the requirements phase would also help to ascertain the testability of the specified requirements.
Test planning must take place as early as possible in the software life cycle. Early planning allows for testing schedules and budgets to be estimated, approved, and incorporated into the overall software development project plan.
The knowledge of what constitutes a successful testing programme is typically gained and improved through experience. A successful testing team typically has a mixture of technical and domain experts, as well as a structured and precise division of roles and responsibilities. For example, a test team member with domain expertise would be most effective at the requirements phase while a technical expert would be effective for test case and test data preparation.
Clearly defining and aligning the objective of the test programme to the purposes of the software can achieve the effectiveness in software testing. It is crucial that the test strategy aims to achieve adequate test coverage to appropriately verify and validate the application to ascertain its fitness for purpose and to minimise defects.
Some of the typical testing objectives are:
- To validate the correctness of the solution – test each component against design specification. This can be achieved through unit testing.
- To validate end-to-end application functionality – test that the components have successfully integrated with each other. This can be achieved through system integration testing.
- To verify solution completeness, test if all functional requirements are met. This is achieved through user acceptance testing.
- To validate the performance of the application – test the application’s performance against the performance attributes specified. This is usually achieved by use of automated tools that simulate full-load environments.
- To cover all the application paths – Test all the possible scenarios and then test some more. Coverage testing starts with unit testing where the objective is to test that every line of code is executed and behaves as expected. At the system testing level, creating test cases that cover all possible input or processing scenarios achieves this goal.
The ultimate objective of an effective testing program is to find defects so they can be resolved. It is therefore paramount that there is an effective process for documenting and managing these defects and also that any rework to fix the defects is reviewed and properly tested to minimise the introduction of further defects. System testers need to be knowledgeable about the system to enable them find real defects. It is not uncommon for defects to be raised due to invalid test scenarios created by testers’ lack of process knowledge.
Building quality software applications is not something that happens by accident. Quality assurance is a combined effort for the whole software development team that needs to be consciously pursued and applied from the onset.
At the organization level, explicitly including and formalising quality control tasks can help create a culture of pursuing software quality by establishing a rigorous software development process. It is necessary to tailor the process according to the software being developed so that the developers will buy into the concept rather than see it as red tape that hinders progress. This can lead to resistance and / or ineffective application.
“Quality is not a fixed or universal property of software; it depends on the context and goals of its stakeholders. Even if you get it right and complete first time, you can be sure that it will become invalid over time. So the only solution is continuous quality control”, Wagner (2013) (11). Effective quality control measures need to be continuously evaluated with respect to a product’s properties and the organization’s quality goals.
At a personal level, the pursuit of quality software products should give personal satisfaction from developing products that work. It is also beneficial for professional maturity as it implies that you are passionate about your work therefore can be trusted with bigger responsibilities. For anyone seeking advancement in a software development career, it is worthwhile to learn and adopt the skills that are essential for quality software development. A quality mentality can also reduce the stress levels that are associated with the job.
Software professionals would not have to spend so much time working on defects and maintaining existing applications. This time could be put to more productive use like innovating solutions that make the process more productive. Furthermore, this would save the organisation money and improve its reputation.
- Managing Software Requirements: A Use Case Approach, by Dean Leffingwell and Don Widrig (2012)
- The Definition of ‘Software Quality’: A Practical Approach by Dr. Roland Petrasch (1999)
- Design Patterns: Elements of Reusable Object-Oriented Software By Richard Helm, Ralph Johnson, and John Vlissides (1994)
- Think Python: How to think like a computer scientist, Allen B. Downey (2015)
- Programming: Sorcery or science? C.A.R. Hoare (IEEE Software), 1(2): 5–16, April 1984
- The Economics of Software Quality, by Capers Jones and Olivier Bonsignour (2012)
- Code Quality: The Open Source Perspective by Diomidis Spinellis (2006)
- Test Driven Development by Kent Beck (2003)
- Code Complete: A Practical Handbook of Software Construction 2nd Edition by Steve McConnell (2004)
- Effective Software Testing: 50 Specific Ways to Improve Your Testing by Elfriede Dustin (2002)
- Software Product Quality Control by Stefan Wagner (2013).