The production of software through a progression of disciplined and controlled steps. As the need for complex, real-time, and life-critical software proliferates, there is a demand for an effective software development process that guarantees the correctness and quality of the software produced. To meet this challenge, the computer science community has developed a process called software engineering that aims to build software that meets all the specifications for its intended use. The term engineering refers to software being produced with the objective of delivering a successful final product created with as much integrity as a building or a road built by a civil engineer. For this reason, it is necessary that software engineers have a full understanding of the intended design and are certain of its correctness and reliability of operation in a specific operating environment. In other words, software engineers should aim to develop the counterpart of the civil engineer’s blueprints to guarantee the functionality, correctness, and behavior of the required software. However, whereas civil engineers have been constructing roads, buildings, and bridges for centuries, software has been built only since the early 1940s. For this reason, software engineering is an evolving discipline for manufacturing software systems that, in a typical modern configuration, have many interacting software and hardware components for accomplishing specific tasks and include, as an essential component of the final systems, documentation to substantiate the needs of the systems and how they operate (Fig. 1). See also: Software
Software engineering process
Several techniques have been defined for developing software systems, some of which are among the most complex and difficult products ever built. As mentioned earlier, software engineering, following the lead of its most mature siblings (civil and hydraulic engineering), has adopted and adapted the strategy of “divide and conquer” to make the development of software and its ever-increasing complexity more manageable. Therefore, to better accomplish this task, the software engineering process is divided into phases or developmental stages: requirements elicitation, design, coding, testing, debugging, deployment, and maintenance. The definition of these phases, their order, and their interactions are known as the software development life-cycle model. Currently, there are different models for, or approaches to, the software development process. Among the most popular ones are the Waterfall, prototype, incremental, iterative, spiral, Rapid Application Development, and Agile models. In general, during the software development process, the output of each phase serves as the input to the next one. Although there may be some reiteration among some consecutive phases, the aim is that, as a result of this progressive developmental process, the final product is a correctly working system that satisfies all the requirements and the users’ needs and, at the same time, is easy to understand and maintain.
The purpose of the requirements elicitation phase is to record and document the customer or system requirements of the perceived system and its operating constraints. It aims to answer this question: What is the system supposed to do? A typical requirements document includes the following: a product overview; a list of functional and nonfunctional characteristics; an explanation of system performance; user interface specifications; development specifications; details about the operating and maintenance environment for the system; a high-level conceptual model of the system; error-handling specifications; a description of potential enhancements to the system; and other auxiliary information such as a user’s manual, glossary, and index. This requirements elicitation phase is generally accomplished by using a combination of techniques and tools, such as interviews, questionnaires, checklists, brainstorming, workshops, focus groups, and the like. This process can become difficult because of miscommunication among the customer, the business, the requirements analyst (who acts as a go-between for the customer and the developer), and the developer. Factors that may play a major role in miscommunication among the parties during information gathering are imprecision as a result of a lack of expressiveness or understanding by the customer as to what the system needs to accomplish; the customer’s assumption that the requirements analyst or the developer already understands the requirements in detail without much clarification; the customer’s unfamiliarity with the technology; or the developer’s unfamiliarity with the customer’s business operations, data flow, and volatility of requirements. The outcome or deliverable product of this phase is a document called the software requirements specification (SRS). The requirements elicitation process is critical, as the outcome and acceptance of the final system depends on the accuracy of the SRS. This is because each function in the system must be mapped to a system requirement for traceability, and vice versa. Incompleteness of the SRS can cause potential weakness in the process, resulting in a partial picture of the needed system.
The design phase helps to answer this question: How is the system to be implemented? It uses the SRS document as its input and delivers as its output the functional specification document, technical design document, implementation plan, performance analysis, and test plan.
The design phase maps the system requirements to an architecture that defines the components of the system, their interfaces, and their behaviors using modeling tools, some of which are briefly described later on. The design documents provide detailed information about programming languages, environments, system architecture, algorithms, and data structures used to implement the system. During this phase, the designers also refer to cost estimation models such as the constructive cost model (COCOMO). COCOMO uses a basic regression formula that includes historical, current, and future project data as input parameters. The effort estimation is equally important. Methods such as the analysis effort method are used to estimate the duration of the project.
Over the years, software engineers have developed different methodologies and notations to express system requirements that try to mimic, to some degree, the blueprints used by architects, civil engineers, and electricians. As such, their notation is aimed at highlighting what is important to them, while minimizing the other aspects of the design. However, regardless of the notation methodology used, the overall objective is to convey a clear understanding of how the pieces fit together before considering how they will be implemented. The main principles used are those of abstraction, modularity, information hiding, and coupling and cohesion.
Abstraction is concerned with the identification of key objects and functionality that might be reused throughout the system. Closely associated with abstraction is the principle of modularity. A module is a simple unit of the system that accomplishes a particular subtask, has a well-defined interface, and can be independently tested. It is the totality and the interplay of these modules that allow a particular functionality of the overall system to be achieved. Modules can be thought of as “black boxes” in which the internal details are hidden from other modules. However, modules need to interact, and therefore each one of them has a “public” component that allows communication with the others. In the composition of each module, or even larger units, it is necessary to take into account the relationships of its internal components, or cohesiveness. We can think of cohesiveness as a measure of how well all the components are working together for a common goal. A pragmatic approach for determining whether a module is performing a single task is to try to describe its functionality through a single sentence that contains a single subject, verb, and object. If this is not possible, then the module is performing more than one task and needs to be rewritten. A tighter relationship, or high cohesiveness, in a module is desirable because it makes the module's functionality easier to understand, test, and document.
In addition to considering how the internal components of a module interact to accomplish a task, it is crucial to consider how the different modules interact with one another (coupling). Coupling can be thought of as a measure of the strength of the linkages between modules, based on the amount and type of information that they exchange—that is, how closely connected the modules are. Minimally coupled or loosely coupled modules are independent of each other. In other words, a minimal amount of required information is exchanged between the modules. In this regard, minimal coupling is a very desirable goal in software design. The notions of coupling and cohesion are related. “Low coupling” generally correlates well with high cohesion, and vice versa. Low coupling is regarded as an indicator of good design. If high cohesion among modules also exists, then the desirable goals of high readability and maintainability of any individual system software component, as well as of the overall system, will be achieved. See also: Algorithm; Computer programming; Data structure; Modeling languages; Programming language
The coding phase of the software development life cycle is concerned with implementing the software components that will satisfy the system requirements as stated in the SRS document using one or more suitable programming languages. The coding of the system may involve the use of either a high- or a low-level language. High-level languages that may be used for writing appropriate code can be of different types, such as functional, declarative, imperative, or object oriented. Low-level languages that can be used are machine or assembly languages. For pragmatic reasons, most systems are generally developed using a combination of high-level languages; however, when fast performance or minimum size of executable code (footprint) is required, the use of a low-level language—or a high-level language that allows direct interaction with the hardware—is desirable. As a result of this programming process, the deliverable for this phase is a working version of the envisioned system. The coding phase considers the essential issues of quality, performance, and debugging. See also: Language theory; Object-oriented programming
Although the testing phase can be viewed as an independent phase of the software development life cycle, it is highly integrated with the coding phase. This depends, in part, on the testing approaches used, as described later in this section, and the fact that programmers continually test their modules. The main objective of the testing phase is to examine the working version of the system to determine whether it meets the system requirements as specified in the SRS document. It aims to find where the system fails to meet these specifications as the result of an error, a bug, or an oversight related to requirement implementation. All functionalities of the system must be mapped to a particular set of requirements, and vice versa. This is necessary not only for coding, but also for all life-cycle phases and their deliverables. See also: Software testing
The software testing process is often divided into subphases. The first subphase is unit testing of the software developed by a single programmer. The second subphase is integration testing, in which all units are combined and tested as a single group and the test cases are developed directly from the SRS document. System testing can be done in either a top-down or bottom-up fashion. In top-down testing, high-level routines are implemented and tested first and then used as a testing environment for the lower-level routines. This is called the test harness. Testing the system in a bottom-up fashion proceeds by first developing low-level routines and testing them, then progressively combining these routines and testing them as parts of larger and larger program units. In practice, when both methods are used concurrently, the process is called sandwich testing. Final acceptance of the system is done by its intended users. When the new system is intended to replace an existing one, both systems are run in parallel until the user is satisfied with the new system’s performance. The final acceptance of a system is generally preceded by a walk-through and inspection testing in which users and developers drill the system rigorously to examine how it functions.
In Fig. 2, the different types of testing are categorized into functional and non-functional testing. Smoke testing is an important type of testing as it may save time for the testers. It checks to ensure that no critical functionality is broken at the initial stage. If the testers find a defect, then they can reject the build and send it back to the coding team. On the other hand, stress testing is an important step in the non-functional testing category in which the system is stressed beyond its load specification to see if it fails.
Software systems are dynamic: they frequently change during and after deployment because of added functionalities, new operating environments, repaired errors, and other factors. Maintenance occurs after deployment, and may sometimes cost more than all the other software life-cycle phases combined. It consists of three activities: adaptation, correction, and enhancement. Enhancement is the process of adding new functionality to a system. This is usually done at the request of system users. This activity requires a full life cycle of its own because it demands requirements, design, implementation, and testing. Studies have shown that about half of all maintenance involves enhancements. Adaptive maintenance is the process of changing a system to adapt it to a new operating environment; for example, moving a system from the Windows operating system to the Linux operating system. Adaptive maintenance has been found to account for about a quarter of all maintenance effort. Corrective maintenance is the process of fixing errors in a system after its release and accounts for about 20% of all maintenance effort. Because software systems change frequently, it is necessary to manage and control these changes through a well-structured software configuration management process. This activity consists primarily of tracking versions of life-cycle objects and monitoring the changes and the relationships among them. Typical configuration management activities also include handling and processing change requests and keeping records of these activities. See also: Operating system
Software engineering tools
Because of the inherent complexity of the development process, software engineers have introduced various tools to facilitate and monitor the phases of the software life cycle. These are known as the computer-aided software engineering (CASE) tools. Some CASE tools are used specifically for software development life cycle (SDLC) phases, whereas others are used for back-end activities of the SDLC. The back-end activities include documentation, timeline tracking, budget tracking, and more. Examples of phase-specific CASE tools include Microsoft Visio for planning, Doxygen for documentation, CaseComplete for requirements management, Visible Analyst for design, Integrated development environments (IDEs) such as Microsoft Visual Studio for implementation, and testing tools such as SOAPTest. Another phase-specific CASE tool is the Unified Modeling Language (UML), which provides a standard way of visualizing the design of the system independent of the programming language used for its implementation (Fig. 1).
Software development life cycles
The phases of the software engineering process are generic phases of any software development life cycle; however, because of the differences in the types of systems created and their implementation, more focused life-cycle models have been created for various systems under development.
The Waterfall model is shown in Fig. 3. In this model, the requirements are finalized early in the cycle, allowing for fewer miscommunications and the completion of the project in a timely manner. On the other hand, this characteristic can also be seen as a disadvantage because it is difficult to introduce new requirements at later phases of the development process. The model does not by nature lend itself well to progressive enhancement and incremental planning.
In a prototype model, the developers create a prototype of the application based on a limited version of the user requirements (Fig. 4). It is basically a hollow shell showing some of the basic features and functionality of the system. A critical drawback of this model is that, from the users' perspective, the prototype may be seen as the final product and some of the original requirements may now seem not to be needed. However, after using the prototype for a while, some of the requirements that were not considered necessary may become desirable or the user may have new requirements that were not initially considered. Because of this problem, the prototype may sometimes have to be redesigned to add new functionality; this, in turn, increases the development cost.
The Agile software development life cycle is a group of software development methods in which requirements and solutions evolve through collaboration between developers and customers. One advantage of this model is that it allows for continuous improvement of the software because changes can be implemented in small increments over a short period of time. Thus, the Agile method encourages a rapid and flexible response to changes and a timely delivery. Some of the Agile principles include customer satisfaction because of rapid delivery of useful software, flexibility to change requirements late in development, and close collaboration between users and developers. The working software in this model can be considered the principal measure of progress and adaptation to changing circumstances. See also: Agile methods in software engineering
Scrum is an iterative and incremental Agile software development framework for managing product development. The new or changed requirements cannot be easily addressed in a traditional predictive or planned manner. This model takes into account that customers can change their minds and allows them to implement product changes using a developmental increment called a sprint that typically takes about three weeks to produce. The customer and the developer agree on a set of features from the product backlog (sprint backlog) that still need to be implemented during a sprint.