Skip to ContentGo to accessibility pageKeyboard shortcuts menu
OpenStax Logo

Learning Objectives

By the end of this section, you will be able to:

  • Explain the importance of testing in software engineering
  • Describe the types of tools used by software engineers
  • Describe ways that software reuse is made possible
  • Explain the role of ethics in software engineering
  • Discuss the future of software engineering

The role of a software engineer is wide-ranging, and the factors that impact software engineers are many. There are a few areas, however, that are worth delving into deeper. These areas include software testing, refactoring, design patterns, software tools used in software development, software reuse, free and open-source software (FOSS), software engineering ethics, and legal aspects.

Testing

The purpose of testing is to verify that the software being developed delivers on the requirements that were set for the project without any unintended errors or side effects. In simple terms, it is to make sure software does what it was expected to do. Through this process of confirming that the software meets expectations, any issues that are found can be resolved to ensure the integrity of the product

Testing should happen within nearly every phase of a project, with each phase having a specific testing focus. One axiom of testing is to test early and test often. As can be seen in Figure 9.17, the earlier issues can be found when developing software, the less costly they are to fix or resolve. Issues are generally found as a result of testing.

Relative cost to fix bugs, based on time of detection graph shows rising costs starting at 2x at Requirements/architecture, Coding, Integration/component testing, System/acceptance testing, rising to 30x at Production/post-release.
Figure 9.17 The cost of finding issues rises through the life of a software development project; for example, the later a bug is found, the more it costs to fix. (attribution: Copyright Rice University, OpenStax, under CC BY 4.0 license)

Levels of Testing

Software goes through various levels of testing to ensure quality:

  • Unit testing: Individual software components (functions or methods) are tested in isolation to verify they work as intended.
  • Integration testing: Focuses on interfaces between components, ensuring they work together seamlessly.
  • System testing: Tests the entire software system as a whole, verifying it meets overall requirements.

In addition to the various levels of testing, there are other crucial testing approaches that are used to ensure that software delivers to expectations. These approaches include:

  • Acceptance testing: verifies that the software meets the expectations and requirements defined by the client or end users
  • Usability testing: evaluates how easy and intuitive the software is for users to interact with
  • Stress testing: assesses how the software performs under heavy loads or unfavorable conditions
  • Performance testing: evaluates the speed, responsiveness, and scalability of the software under various conditions
  • Security testing: identifies and addresses security vulnerabilities to protect against unauthorized access or data breaches

Some testing is done by software developers, including software engineers who write code, but a majority of testing is done by quality engineers whose primary focus is testing. End users can also participate in acceptance testing or user testing scenarios.

Concepts In Practice

How Does Microsoft Test Windows Before Release?

Microsoft uses millions of testers before deploying a new update of Windows. These testers are a part of the Windows Insider program and have access to the prerelease versions of Windows. Anyone can sign up for this program; they just need to agree to test the new version and provide Microsoft with feedback. Because testing is done on a prerelease version of the software, the testers know that the software may be unstable and will likely have issues. If you are curious about testing—or are interested in becoming a tester yourself—visit Microsoft Windows Insider or Apple’s Beta Software Program for more information.

Methodologies of Testing

There are three primary testing methodologies, and they are categorized by the knowledge the tester has (or needs to have) to conduct to the tests. The three methodologies are:

  • The manner of testing where the tester is aware of the code, so they can test that the internal structure of the item being tested works properly, is called white box testing. Other names for white box testing include glass box, clear box, and structural testing.
  • Tests based on requirements and the functionality of what is being tested without the need to focus on the code itself is called black box testing. The tester does not need to have any knowledge of the code. Other names for black box testing include input-output testing, specification-based testing, and behavioral testing.
  • The hybrid of both white box and black box testing is called gray box testing. The person who designs the test has a partial knowledge of code structure and understands the intended design of the software.

When testing at the testing system level, we distinguish between verification and validation. Testing that the software solution functions without errors is called verification. In organizations that have large development teams, verification is often done by a Quality Assurance (QA) team. Testing that the software solution conforms to the requirements and does what the user wants it to do is called validation. Both verification and validation require code execution and can happen in all levels of testing, with verification generally taking place before validation.

Testing is also distinguished by purpose into functional and nonfunctional testing. With functional testing, the functionality is tested. With nonfunctional testing, qualities such as performance, scalability, and usability are tested.

The attributes of a good test are as follows:

  • A good test has a high probability of finding an error or issue.
  • A good test is not redundant. (It does not test the same thing as another test.)
  • A good test should be neither too simple nor too complex.

Unit Testing

A crucial role in software development, the process of unit testing involves testing individual units of code, such as methods and functions, and it is usually done by developers during the development of the software or when updates are made. Software developers write scripts or code that test the functionality of a specific piece of code. Unit tests are typically added to a regression test suite, so they can be run again after each change to the source code to verify that the change did not break any existing functionality. Imagine a function that calculates the area of a rectangle. A unit test would verify that the function returns the correct area for different width and height values.

Unit testing offers several benefits:

  • Early bug detection: Unit tests help identify bugs early in the development process, when they are easier and less expensive to fix.
  • Improved code quality: The process of writing unit tests encourages developers to write clean, modular, and well-documented code.
  • Maintainability: Unit tests serve as living documentation, clarifying the intended behavior of code components and making future modifications easier.

Concepts In Practice

Staying in Their Lane

In the automobile industry, most new cars now include a lane detection system (LDS). This system includes code that verifies video images that are received by the automobile. The software checks to see if the vehicle crosses one of the lines painted on the road. If so, it activates a warning system to let the driver know. Units test for such a system include the following:

  • An appropriate line is recognized.
  • A warning occurs if a line appears to be crossed.
  • The warning ends after the designated period of time.
  • When the LDS system is turned off, the warning is not triggered.

Software engineers as well as testers perform these tests when the code for the LDS system is created as well as any time any updates are made to the system.

Code Coverage

Proper unit testing improves the quality of a software system and helps discover bugs early. In simple terms, a software bug is an issue or error with software programming. To find bugs, it is important to review and run the code.

The range of a unit test is typically measured in terms of code coverage, which is the measurement of the percentage of code that is activated or reviewed by the test. Common types of code coverage are statement coverage, line coverage, and path coverage. Measurement of the percentage of statements that are activated at least once when you run all tests is statement coverage. Measurement of the percentage of activated lines of source code that are tested is line coverage. Measurement of the percentage of paths through source code that you go through is path coverage. As shown in Figure 9.18, with path coverage, if you have two conditional statements, one after the other, there are typically four paths through this code:

  1. Both conditions are true.
  2. The first condition is true and the second is false.
  3. The first condition is false and the second is true.
  4. Both conditions are false.
Code path illustration of four paths: (i) x and y positive; (ii) x positive, y negative or zero; (iii) x negative or zero, y positive; (iv) x and y negative or zero.
Figure 9.18 This activity diagram illustrates how path coverage works with four possible paths: (i) x and y positive, (ii) x positive, y negative or zero, (iii) x negative or zero, y positive, and (iv) x and y negative or zero. (attribution: Copyright Rice University, OpenStax, under CC BY 4.0 license)

Path coverage is more demanding and typically requires more tests than statement or line coverage, but tests are better. You need a minimum of four tests to achieve 100% path coverage, but a minimum of two tests to achieve the same level of statement coverage.

Support for Unit Testing

Unit testing is so common that major programming languages provide some support for automated unit testing. An example is JUnit, a unit testing framework for the Java programming language, which is shown in Figure 9.19. JUnit facilitates the writing and managing of tests, and it can be run in some development environments quickly, typically via a single click. Many of the other major programming languages have similar frameworks or tools. There are also third-party software products that can be used to help with unit testing.

Screenshot of JUnit window with green arrows for tests run.
Figure 9.19 JUnit test window in NetBeans integrated development environment (IDE). You can run all tests by clicking on the green arrows. (attribution: Copyright Rice University, OpenStax, under CC BY 4.0 license; credit: NetBeans by Sun Microsystems)

Test-Driven Development

Many organizations use test-driven development (TDD), which is a process where developers write tests before they write the code. This might seem counterintuitive, but it offers several benefits:

  • Clarifies requirements: Writing tests first forces developers to think critically about the problem they are trying to solve and the expected behavior of the code. For example, if you are to write code that finds real roots of a quadratic equation and you start with unit tests, you will probably identify three cases that must be solved separately: the case when the equation has two roots, the case when the equation has a single root, and the case when the equation has no root.
  • Facilitates early bug detection: Writing tests first helps identify bugs early in the development process, when they are easier and less expensive to fix.
  • Results in improved design: The test-driven approach can help identify edge cases and scenarios that might be overlooked during traditional development, leading to a more robust design.
  • Results in improved code quality: The focus on writing clear, testable code leads to overall higher code quality.

As illustrated in Figure 9.20, in test-driven development, you write unit tests first. You then run the tests, which should fail because there is not a fully implemented function to test against. You will then run (and rerun) the tests as you develop the functions. Development continues until all of the unit tests pass.

Circular graphic: Start: 1. Write test, 2. Test fails, 3. Write code, 4. Test passes, 5. Refactor, 6. Test passes, Code complete.
Figure 9.20 In test-driven development, the developer writes the test before writing the code. Thus, when the test is first run, it should fail. (attribution: Copyright Rice University, OpenStax, under CC BY 4.0 license)

Here’s a breakdown of the steps of the test-driven development process:

  1. Write a test: The process starts with the developer or development team writing a unit test that defines the expected behavior of a specific function or code unit.
  2. Run the test (and see it fail): Run the test. Initially, the test will likely fail because the function it tests doesn’t exist yet or isn’t implemented completely.
  3. Write just enough code to make the test pass: Focus on writing the minimum amount of code necessary to make it pass the test.
  4. Run the test (and see it pass): When the code passes the test, this ensures it is fulfilling the intended functionality defined in the test.
  5. Refactor (improve code structure): Once the test passes, take a step back and refactor the code to improve readability, maintainability, and overall code structure without changing functionality. The process of restructuring source code without changing its functionality is called refactoring. We typically use refactoring to make code more readable. This can be as simple as renaming a variable or as complex as breaking the function up and splitting it across modules. Once the refactoring is complete, the unit tests should be executed again to make sure the code continues to pass.
  6. Run tests again: After refactoring, run all tests again to verify the code continues to pass and ensure no unintended side effects were introduced during refactoring.

Once you’re done with all the steps, start the cycle over: Repeat this cycle (write test, run test, write code, refactor, run test) for each unit of functionality you develop.

System Testing

Whereas unit testing focuses on the various units or pieces within a software solution, system testing focuses on the complete and fully integrated software product. The overall goal of system testing is to make sure the complete software solution works on the whole as expected. This is generally a black box style of testing that focuses on making sure the software meets all the requirements that were determined and checks that any possible scenarios that could be applied to the software function play out as expected. System testing is generally done by testers rather than the end users of software, and it involves reviewing both functional and nonfunctional requirements.

System tests can be performed by testers, or they can be automated. The form of system testing where a person must run the software system, provide any input, and manually check all output is called manual testing. Repeating tests requires these efforts to be repeated by the tester. The form of testing where a program or code is executed that tests the functionality of a software system or some of its parts is called automated testing. It allows for repeating tests without requiring repeated effort on the part of the tester. An example of such a program is Selenium. Selenium is a suite of free tools that automates the testing of web applications. The easiest way to use Selenium is to have it capture the interaction between your browser and web application. The interaction is saved as a script. You can then edit the script, if needed, and replay it to check if the web application still works. Selenium also provides libraries for many popular programming languages, including Java, C#, and Python, that allow users to write programs that perform this testing.

Acceptance Testing

Also called user acceptance testing, acceptance testing is the process used to determine whether the software solution fulfills the customer’s expectations. This level of testing generally occurs after system testing and is done by the customer, client, or other end user of the software.

Acceptance tests are formalized testing that can be specified by the customer, and it tests both functional and nonfunctional requirements. To test functional requirements, acceptance tests often follow scenarios created in requirements analysis. For example, in a library information system, we can use scenario “Add new book” to create an acceptance test. During the acceptance test, the tester will fill out an input form, submit it, and check that the system contains a new book.

In the automotive industry, acceptance testing could be done by the department that requested the new features. It could also be done by dealerships or customers who own or have purchased previous versions of a given automobile. A driver could be asked to use the new features and then be surveyed to see what issues occurred.

Usability Testing

The user-centered testing method that evaluates how easy and intuitive a software system is to use is called usability testing. It goes beyond just ensuring the software functions correctly and focuses on the user interface/user experience (UI/UX) the part of computer programming that concerns how information is presented to the user and how the user can interact with a program. Usability testing is essential because software that is difficult to use can frustrate users and hinder adoption. By identifying usability issues early in the development process, developers can make improvements that lead to a more user-friendly and successful product. Usability testing typically involves recruiting a small group of users representative of the target audience. These users are asked to complete a series of tasks while a usability tester observes their actions and records their feedback. The tester pays close attention to areas such as:

  • Task completion: Can users complete the intended tasks efficiently and without errors?
  • Ease of learning: How easy is it for users to learn how to use the software?
  • User satisfaction: Are users satisfied with the overall user experience?

While acceptance testing generally happens near the end of a project, usability testing can and should happen much earlier. The focus of usability testing is to enhance user experience, and it typically emphasizes the following areas:

  • Interactivity: Are interaction mechanisms (e.g., menus, buttons) clear and consistent? Do they provide appropriate feedback to user actions?
  • Layout: Is information organized in a logical and easy-to-find way? Can users navigate the system intuitively?
  • Readability: Is text easy to read and understand? Are error messages clear and actionable?
  • Aesthetics: Is the overall look and feel of the software pleasing and consistent? Do colors, fonts, and images contribute to usability?
  • Display usage: Does the software solution use the best possible screen size and display resolution?
  • Timing: Are important features and other pieces of functionality quickly accessible?
  • Feedback support: Is useful feedback provided to end users, and can they easily go back to their work once the feedback has been read?
  • Personalization: Is the application addressing various categories of users and does it support inclusion?
  • Help: Is it easy for users to access help and other support options?
  • Accessibility: Can the software be used by people with disabilities? This includes features such as screen readers and keyboard navigation. Note that accessibility testing is a subset of usability testing that focuses specifically on the needs of users with disabilities. Here are some examples of accessibility considerations:
    • Screen reader compatibility: Can screen readers interpret the content and functionality of the software accurately?
    • Color contrast: Does the software use sufficient color contrast to be usable by people with visual impairments?
    • Keyboard navigation: Can all features of the software be accessed using only the keyboard?
    • Content features: Is blinking, scrolling, or auto content updating avoided to accommodate users with reading difficulties?

Industry Spotlight

Usability and Acceptance Testing by Car Manufacturers

Usability and acceptance testing are quite different. Let’s consider again the software for the lane departure systems (LDS) that are incorporated into automobiles these days. Acceptance testing would confirm that the lane departure system works as expected: Did the warning happen when a vehicle crossed a lane line? Did the software recognize when the vehicle departed the lane? Did the system warning start and end in an amount of time that was appropriate to the user? When the system is disabled, did the software ignore lane departures and not trigger the warnings?

With usability testing, the focus is more on whether the system met the expectations of the users interacting with it: Was the system usable? Did the user use the feature as intended? Did the user react appropriately to the warning signal? Was the signal so startling it caused the user to jerk and risk an accident? Did the user simply turn off the system because they found it annoying? The focus of usability testing is on how the solution is being used and whether it was created in the most intuitive manner

The Insurance Institute for Highway Safety found that 49% of users turned the LDS off during their normal commuting. More important, it was found that 54% turned it on if it used tactile warnings compared to only 46% turning it on if it used an audible alert. From a usability testing standpoint, this shows that it is better to focus on a tactile warning over an audible one if the goal is to get the most usage. Because LDS has been shown to decrease the chance of fatal accidents, even with low usage, it can still help save lives.

Software Engineering Tools

Software developers use many tools that facilitate development; some of them are used daily while others only occasionally, but they all play an important role in software engineering process.

Compiler

A compiler is a program that converts source code into a syntax or format that a computer can execute. Programming languages such as C and C++ compile into machine code, which consists of instructions for a specific processor. Other programming languages such as Java compile into machine independent code. For example, Java program code is generally compiled into Java bytecode. This machine independent code is then executed on a Java Virtual Machine.

Debugger

A debugger is a program that assists in detecting and correcting of bugs. It typically helps in finding issues in code by enabling features such as the ability to halt a program while it’s running so that variable values can be displayed and even changed. Debuggers can also run a program line by line to see what each line of code is doing. More advanced features involve halting a program on a specified condition and checking for deadlock and memory leaks. Figure 9.21 shows the NetBeans debugger stopped on line 6 of a program. In the lower part of the screen, the software developer can see the values of the variables currently used by the program.

Screenshot of a debugger tool from NetBeans.
Figure 9.21 In this debugger tool from NetBeans, the program stopped at line 6, creating an opportunity for the developer to choose what to do next. The lower window displays the values of variables currently used by the program. (rendered in NetBeans by Sun Microsystems; attribution: Copyright Rice University, OpenStax, under CC BY 4.0 license)

Profiler

A profiler is a program that performs dynamic program analysis that can be used to optimize or otherwise streamline code. It collects data such as frequency and duration of method calls and presents it to the software developer who can use it to propose code optimizations. The process of data gathering is called profiling, and it is typically performed when the performance of the software system does not meet specified criteria.

Integrated Development Environment (IDE)

Software engineers typically write source code in an IDE such as IntelliJ, Microsoft Visual Studio, or VS Code. An IDE is a software application that facilitates software development by combining several tools that developers use such as an editor, compiler, debugger, and profiler. Some IDEs only support a specific programming language, such as IDLE for Python, and some support multiple programming languages, such as Microsoft Visual Studio, which supports C++, C#, Visual Basic, and J#.

The best IDEs provide features that facilitate writing and maintaining source code. These features can span from simple features such as syntax highlighting to sophisticated ones such as static analysis of source code. Syntax highlighting displays different parts of the source code in different colors, which facilitates reading. Figure 9.22 shows an IDE highlighting Java code.

Screenshot of code highlighted in different colors in a window.
Figure 9.22 Syntax highlighting is a feature of IDEs that helps developers read code. Here, Java source code is being highlighted by the NetBeans IDE. (rendered in NetBeans by Sun Microsystems; attribution: Copyright Rice University, OpenStax, under CC BY 4.0 license)

The cost of IDEs can range from free to thousands of dollars. IDEs such as Eclipse, NetBeans, and Microsoft Visual Studio Code are free. Microsoft Visual Studio and IntelliJ IDEA have a fee associated, although there are free editions of each available. Generally, the cost of an IDE is offset by the increase in productivity that a developer gains from its use.

Version Control System

A quite common tool used in software development is a version control system, which stores the history of changes to source code and facilitates collaboration of multiple developers. The most popular version control system today is Git, which is often used with a common hub, such as GitHub or Bitbucket.

Git stores the project source code written by a developer in a repository that is typically on the developer’s machine. Developers can push their code to a project repository for integration purposes. The main features of Git are as follows:

  • It tracks changes in your source code.
  • It enables reverting the changes.
  • It facilitates collaborative development.

GitHub facilitates code sharing among developers by allowing developers to modify source code on their own machine, commit changes to a Git repository on their machine, and then push changes from their machine to GitHub so that other developers can access them.

Many open-source projects are hosted on GitHub that allow people to download their source code and contribute. Figure 9.23 shows a standard GitHub page, in this case from the JavaScript library called the jQuery project.

Screenshot of a GitHub window.
Figure 9.23 GitHub is a version control system that facilitates code sharing among developers. (credit: GitHub, CC0 1.0)

Bug Tracking System

Another common software development tool is a bug tracking system, which aids in the tracking and resolution of fixing issues with software. Bug tracking software stores information about issues that have been reported and their resolution.

A common workflow with the bug tracking system is as follows: when a customer or a tester reports a bug, a new record in the bug tracking system is created. Then, the project manager or any other team member assigns the bug to a developer. The bug report must provide instructions as to how the bug manifests and may be fixed so that the assigned developer can reproduce it and decide how to proceed. Here’s a general breakdown of how bug tracking works:

  • If the bug cannot be reproduced or if it is not a bug but a request for a new feature, it will be rejected.
  • If the report describes a bug that was already reported, it will be marked as a duplicate.
  • If the fix is scheduled for a next release, it will be marked as deferred.

When the developer fixes the bug, the code should be retested to confirm that the bug has truly been resolved. Figure 9.24 illustrates a common bug reporting and tracking flow.

Bug tracking chart: New, Assigned, Opened (Fixed, Duplicated, Rejected, Deferred), Retested, Verified, Closed. If reopened leads to Assigned.
Figure 9.24 The Jira issue tracking system shows a common workflow of bug tracking systems. When a bug is detected, a new record is created. After the bug has gone through the process of being assigned and fixed, and the fix has been verified, the record closes. (attribution: Copyright Rice University, OpenStax, under CC BY 4.0 license)

Figure 9.25 shows a bug tracking tool—the jQuery bug tracking tool—that is available on GitHub.

Screenshot of jQuery bug tracker on GitHub.
Figure 9.25 The jQuery bug tracker on GitHub keeps track of how many issues are pending (“86 Open”) and which have been resolved (“602 Close”). (credit: GitHub, CC0 1.0)

Industry Spotlight

Bugs Not Caught

In January of 2022, the San Francisco Unified School District rolled out a new employee information system, which included the payroll for over 10,000 employees. Unfortunately, the development team did not do a thorough job of testing this system, and it contained a number of bugs when it was launched. As a result, hundreds of teachers and staff members did not, even after several months, receive paychecks from the system.

Because the system was unable to process payroll, hundreds of employees were unable to pay their rent, medical bills, and other expenses. In this case, the delivery of a flawed system harmed people.

This is an example where a system’s product did not adhere to the highest standards and failed to be tested to a level that would avoid an issue that directly harmed many of the employees who relied on the system. While the Chief Technology Officer issued apologies and assigned 100% of the project staff to get the issues resolved, the reality is—and ethically speaking—more time should have been allotted to test the system prior to it replacing the old system.

We typically think about all of the ways that technology and systems can help people. It is also important to acknowledge there are risks also. Bugs in software can have a significant impact on the people affected. This reinforces the importance of testing and validating a system before we release it.

Software Reuse

When constructing software, developers commonly use libraries and frameworks, which consist of code that was written by other developers. Libraries typically provide some functions, which you can call in your code, and frameworks typically provide application skeletons, which can call your code. For example, you can have a library that provides encryption functionality and a framework that facilitates the development of web applications. Both libraries and frameworks are common in software development, and it is common to combine them. Libraries can be static, which are linked to your code at compile time, or dynamic, which are linked to your code when it executes. Because static libraries are linked at compile time, you must recompile the program when the library is modified. On the other hand, a dynamic library is in a separate file, and thus it can evolve independently of your code.

Some programming languages, such as Java, provide ready-to-use code as part of a language library. That code may be invoked via an application programming interface (API) and it involves implementation of common data structures and operations on them. Software applications may also leverage external APIs that allow developers to access functionality or data. For example, the United States Postal Service (USPS) provides an API that can be used to get shipping rates, track packages, schedule package pickups, and more. There is no need for a developer to write new code to do this, but rather they can tap into the USPS API instead.

It might seem that we do not need to write code anymore because we can assemble applications from existing components, but, unfortunately, it does not work this way. One of the reasons is that we usually want to customize functionality, and another reason is that even if an existing component is a good fit for a project, we are often not allowed to use it due to legal reasons. Each component is published under legal terms called licenses, and if we want to use it, we must follow these terms. For example, components published under the GNU General Public License require software that uses them to be published under the same license, which is usually not acceptable in commercial environments.

Technology in Everyday Life

Libraries on Your Computer

Code reuse and the use of libraries is extremely common. In fact, if you look at the files on a Microsoft Windows computer, you will find that a plethora of libraries are used by the Windows operating system itself. The files that have an extension of .DLL are library files where DLL stands for dynamic-link library.

On a computer running Microsoft Windows, do a search in Windows File Explorer using “*.dll” to view a list of dynamic-link libraries. Notice how many there are and the dates they were updated. Many of the Windows operating system files are DLL files that get updated at a different frequency as compared to the operating system. The names of some of the files will give you an idea of what the code within them likely does. You can, of course, attempt a similar search for a computer running on Apple’s operating system.

Patterns

A pattern is a high-level concept that supports the idea of reuse and provides reusable solutions to problems often encountered when building software. Patterns are not backed up by any theory; they relate to solutions that were observed in practice at various levels of abstraction and proved successful. In general, patterns are discussed in terms of their meaning, their intent, and the benefits of using them across many software engineering areas. Patterns are typically organized hierarchically in pattern catalogs using descriptive pattern templates. Pattern languages may be used to describe the compatibility between various patterns and help weave them together whenever applicable.

Software engineers are likely to encounter two broad categories of patterns depending on whether they are designing a software architecture model or mapping it to a corresponding implementation architecture tailored to the platform or environment the solution is meant to be deployed onto. Patterns used to model software architectures include architectural style, architectural patterns, and design patterns, an architectural model, or an implementation of such.

As you learned earlier in the chapter, an architectural style is a transformation that is imposed on the design of an entire system. The intent is to establish a structure for all components of a system under development. Examples of architectural styles for distributed systems include the object management architecture (OMA), service-oriented architecture (SOA), multitier architecture, and peer-to-peer decentralized architecture. Note that multiple architectural styles may be combined to create a hybrid architecture style. An architectural pattern is part of a category of patterns that focus on the architecture of software. They tend to be more abstract and focused on improving issues such as deployment, availability, maintainability, performance, scalability, security, and testing. A design pattern is a reusable solution to a design problem that software engineers repeatedly encounter while architecting and designing software systems. For example, the design pattern Singleton suggests how to restrict the number of class instances to one. This problem commonly occurs in software systems whenever a class represents a thing or concept that has a single instance. A more complex design pattern is Builder, which allows a software engineer to create an object in steps and is typically used when creation is a complex process.

Furthermore, a design pattern imposes a transformation on the design of an architecture of a given architectural pattern. Therefore, architectural and design patterns may be used in conjunction with an architectural style to shape the overall structure of a system. For example, Model-View-Controller (MVC) is a typical architectural pattern that is used to design the software architecture of a multitier system. If the multitier system provides a scoreboard, that feature will leverage a Singleton design pattern to avoid confusion and ensure the existence of a single scoreboard.

There are many design patterns described in software development literature. The first book collecting design patterns, Design Patterns by Gamma, Helm, Johnson, and Vlissides, was written in 1995 and continues to be available today. More patterns have been identified and shared since that time. You can find many listings of commonly used design patterns online such as the one available on the tutorialspoint website.

Each design pattern provides a solution to a problem in a particular context. Thus, design patterns are not universal solutions as they almost always come with disadvantages, and it depends on the context whether the advantages outweigh the disadvantages, which is why it is important to not only be familiar with design patterns, but also to understand their advantages and disadvantages so that you can assess their benefits and drawbacks in a specific context. Design patterns can be applied when you design a software system, or they can be introduced later on in the process of refactoring. They are rarely applied in their pure form as they are described in literature. The more common practice is to tailor them according to the context in which they will be used. It is also common that the code base of a software system may implement several design patterns, which contributes to the superior quality of the system.

Refactoring

As has been stated earlier, refactoring is the process of restructuring source code without changing its functionality. It is not specific to reuse, though refactoring can involve reuse. Typically, refactoring consists of making a series of elementary changes called micro-refactorings, such as:

  • Rename an attribute, which changes the name of an attribute to a new one that better corresponds to its purpose.
  • Rename a method, which changes the name of a method to a new one that better corresponds to its purpose.
  • Extract a class, which moves a part of an existing class into a new class.
  • Extract a method, which moves a fragment of code into a new method.
  • Inline a method, which replaces a method call by its body. This is typically used only for very short methods.
  • Move a method, which moves a method to a more appropriate class.
  • Move a field, which moves a field to a more appropriate class.
  • Remove dead code, which removes code that never executes.
  • Replace a literal with a symbolic constant, which replaces a magic value with a named constant.
  • Change method parameters, which adds or removes method parameters.
  • Substitute an algorithm, which replaces one algorithm with another. This is typically done to improve performance of the software system.

You can apply these micro-refactorings manually, but it is more common to perform them in an IDE. Modern IDEs provide support for some refactoring, and you can often apply a selected refactoring by activating an appropriate menu item, as shown in Figure 9.26.

Screenshot of NetBeans IDE. Open window is highlighting Refactor, which opens to window with Rename highlighted.
Figure 9.26 In the NetBeans IDE, the action of renaming a method falls under the Refactor menu item. (rendered in NetBeans by Sun Microsystems; attribution: Copyright Rice University, OpenStax, under CC BY 4.0 license)

Returning to the concept of reuse, one of the benefits of refactoring is that functions, classes, and other chunks of code can be extracted from other parts of the code. These can then be reused by placing them in libraries, by incorporating them into a framework to be used by other developers, or by constructing an interface to access them.

Software Licensing

When software engineers design and develop software for an organization, that software and all its code generally belong to the organization. This means the source code and functionality cannot be used by others without permission. This permission is typically controlled by a software license. The type of license can be determined by those who created the software or commissioned its creation. Two of the most common categories for defining software reuse are closed source and open source. Proprietary, or closed-source software, is a form of licensing that prohibits access to underlying source code. This type of software is typically developed for organizations or as software to be sold. It can be used to protect software that might contain proprietary information or intellectual property within the code. Free and open-source (FOSS), or open-source software, is a form of software licensing that provides access to the underlying code and generally allows the code to be reused and modified

It is important to read the license terms of any software code used. It is also important to make sure that, if software is to be shared, it is clear as to what license will be applied to both the use of the software and its code. If a license is not provided with code, then the code is generally protected under copyright rules.

Free and Open-Source Software (FOSS)

Free software (sometimes called libre software) is software distributed under the terms that provide users with the following four freedoms:

  1. the freedom to run the software for any purpose
  2. the freedom to study how the software works and modify it
  3. the freedom to redistribute the software
  4. the freedom to improve the software and release the improvements to the public

The freedom to study how the software works and modify it and the freedom to improve the software can hardly be fulfilled without source code, and so these freedoms more or less imply that source code of the software must be available.

Free here refers to liberty, not price (you may be required to pay for free software). Software that is distributed gratis is referred to as freeware, and it rarely comes with source code. Open-source software is software distributed under a license that grants users the right to use, study, modify, and distribute the software and its source code. It does not mean that software must be gratis, although in many cases it is the case. Today, it is common that open-source projects provide their source code via a public repository on the Internet that allows anybody to contribute.

The benefits of FOSS are as follows:

  • Improved quality and reliability: Community involvement in open-source projects can lead to better quality and more reliable software due to a wider range of users and developers identifying and fixing bugs.
  • Lower development costs: Open-source software can benefit from community contributions, reducing development costs for companies and organizations.
  • Innovation: Openness and collaboration can accelerate innovation as developers can build upon existing open-source code.

Some projects that were developed as free and/or open-source software can be categorized as follows:

  • Operating systems
    • Linux: An open-source operating system, which is distributed in so-called distributions. The distributions are referred to by name such as Debian, Fedora, Ubuntu, RedHat, and SUSE, and they can be commercial (paid) or community-driven (gratis). The Linux kernel is licensed under GNU General Public License, version 2, which makes it a free and open-source software.
    • Android: A free and open-source operating system developed by a consortium sponsored by Google. It is based on the Linux kernel and other open-source software, and it aims primarily to touchscreen mobile devices, such as smartphones and tablets. Android is the most common operating system (not only on mobile phones).
  • Development tools
    • NetBeans IDE: An open-source IDE that started as a student project with the goal of creating an IDE for the Java programming language. Now it belongs to Apache Software Foundation and also supports other programming languages, such as PHP, C, C++, and JavaScript.
    • Eclipse IDE: An open-source integrated development environment that started at IBM and is now managed by the Eclipse Foundation.
    • GNU Compiler Collection (GCC): A free and open-source compiler (or more precisely a suite of compilers) that supports various programming languages, hardware platforms, and operating systems. It is the standard compiler included in many Linux distributions.
  • Web browsers
    • Google Chrome: A no-cost web browser developed by Google. Although most source code of Google Chrome comes from free and open-source software, it is licensed as proprietary freeware. It is available on Windows, Linux, Android, macOS, and iOS.
    • Mozilla Firefox: A free and open-source web browser developed by Mozilla Foundation. It is available on Windows, Linux, macOS, and Android.
  • Programming languages
    • OpenJDK: A free and open-source implementation of the Java Platform, Standard Edition. Many other distributions of the Java Platform, such as Amazon Corretto, are based on OpenJDK.
    • C#: A modern cross-platform programming language initially developed by Microsoft. Both the C# language and the current (as of 2014) Microsoft C# compiler are open source.
    • Python: A modern programming language is that is completely open source, freely usable and distributable, even for commercial use, making it extremely popular with businesses around the world.
  • Application servers
    • Payara Server: An open-source application server, with paid support options.

While some open-source software is entirely free, FOSS projects can have successful business models. It may seem that open-source software development does not bring any benefits to software publishing companies, but community involvement leads to better quality and higher reliability of the software and lower cost of development. Many software companies that moved to open-source development adequately modified their business model and instead of selling software, they charge the customer for providing additional services. For example, Payara Server is an open-source application server for Jakarta Enterprise Edition applications. The server is gratis, but companies that use Payara Server for mission-critical applications can pay for support, which allows them to consult a team of software engineers at any time. In addition, they receive monthly releases with bug fixes and security alerts. Many companies that embrace open-source development focus on providing value-added services, such as technical support, training, or custom development, around the core open-source product.

It is important for companies and developers to pay attention to the following:

  • Software licensing considerations: Understanding software licenses is crucial for both software users and developers. While many people accept End User Licensing Agreements (EULAs) when installing software on their personal computers and devices, it is important that software engineers understand that these are legal agreements. As such, when using software to build solutions, it is important to understand what the licensing allows.
  • Using open-source software: Software developers should be aware of the license terms associated with any FOSS they use to ensure compliance.
  • Distributing software: Additionally, when distributing software, it is important to make sure that an included license defines how you or the organization that owns the software wants it used and protected. Choose a license that aligns with your project’s goals and how you want your software to be used and distributed.

FOSS is a powerful development model that fosters collaboration, innovation, and cost-effective software creation. While security considerations should also be factored in, open-source software plays a vital role in the tech industry.

Software Engineering Ethics and Legal Aspects

Software engineers often spend a lot of time creating code and solutions to solve problems. While they might be the ones writing the code, that does not mean the code or the knowledge that they gained while writing the code belongs to them.

For example, if a software engineer worked for an auto manufacturer, they might be tasked with designing and creating a futuristic product that detects the eye movement of a driver to verify they are looking forward while driving. The software might present an alert if the driver’s attention wanders from the road for longer than a given period. Generally, it would be considered unethical for this same developer to use what they learned while working for the auto company to develop their own software product that uses the same code to detect eye movement. While the software engineer might have written the code, they wrote it for the auto company, so it would be unethical to use the same code in an independent product without the auto manufacturer’s permission.

Software engineers should abide by a code of ethics that guides the work that they do and the products that they produce. At a personal level, ethics for software engineers involve the following guiding principles:

  • Do not use other people’s data or code for financial gain.
  • Do not leverage other people’s proprietary information as part of a commercial project.
  • Do not use or hide the use of other people’s data or programs as part of your own projects.
  • Do not violate the privacy of others.
  • Do not gain wrongful access to a system for financial gain.
  • Do not create or propagate computer viruses or worms.
  • Do not create or use programs that promote discrimination or harassment of any kind.

The Association for Computing Machinery (ACM) and IEEE-CS established the Committee on Professional Ethics (COPE), which published a Software Engineering Code of Ethics and Professional Practice. This code of ethics states that software engineers shall commit themselves to making the analysis, specification, design, development, testing, and maintenance of software a beneficial and respected profession in accordance with their commitment to the health, safety, and welfare of the public. The Code goes on to define eight principles that all software engineers are required to follow to possibly avoid legal consequences:

  1. Responsibility should be taken for the work done.
  2. Actions should be in the best interest of a client or employer.
  3. Products and updates should be created in a manner that adheres to the highest professional standards.
  4. Integrity should be applied in actions taken.
  5. An ethical approach to managing software development and updates should be maintained and promoted.
  6. The integrity of what it means to be a software engineer should be maintained.
  7. Software engineers should be supportive of peers and colleagues in a manner that is fair.
  8. Learning should be a continual endeavor to the betterment of the software engineer’s own profession.

Think It Through

Applying the Concepts of Ethics

As a software engineer, you will deal with the constant change of technology and the constant need to understand a variety of business scenarios to build solutions. Clearly, you cannot be expected to know everything.

Consider two hypothetical scenarios: In the first, suppose that a developer, when faced with a task they did not know how to do, spent days avoiding the rest of the team as they researched and read books to try to learn the new technology. They did not tell anyone they did not know the technology.

In the second, imagine a developer was faced with the issue of needing to code for a widget for the team to use. The issue was shared online in developer forums such as StackOverlow and CodeProject and the developer asked others to provide code to do what the widget was expected to do. While the developer did not understand what the code did, he copied what he was given and presented it to the team as his own without saying where it came from.

In both situations, how did the concepts of ethics apply?

Road Ahead for Software Engineering

People have vastly different opinions as to what software engineering will be like twenty or thirty years into the future. One day, revolutionary discovery or invention may change our society in a radical way—or not. Nobody can predict when or what specific scientific breakthroughs will happen, and discoveries can shape our future in ways that are hard to imagine today. Based on our experience of the past twenty or more years, however, we can make some predictions about where software engineering may be in 2040.

Evolution of Programming

We can, for example, predict that the evolution of current programming languages will be driven by changes in hardware and that the tools developers use will improve. But with the evolution of AI and machine learning, will software developers become obsolete? Under current circumstances, the answer is no. AI can, however, help with some developer’s tasks, such as testing, debugging, and refactoring. While we can expect that intelligent bots will be members of development teams, they will not fully replace software developers.

Future of Software Development

Now let’s discuss three questions about the future of software development:

  1. What programming languages will we use? We can expect that current programming languages will evolve, perhaps even become obsolete, by 2040, but it is hard to imagine that languages like Java, Python, C, C#, and C++ will disappear entirely because there is so much code written in these programming languages.
  2. Can we get to the point where all software is written, and we do not need any software development? Absolutely not in the near future. We are not at the end nor in the middle of a technology revolution—we are at the beginning. The importance of software in society will grow, and software systems will become more sophisticated. For example, with the Internet of Things (IoT), we will connect thousands and thousands of smart devices to the Internet, and they all will need software.
  3. Does it make sense to learn programming today? Absolutely. With the increased use of smart, connected devices, the integration of technology is getting more rather than less prevalent in almost all facets of life, and technology continues to offer an advantage to businesses to operate quicker, better, and more effectively than the competition. Given this, software development will become more important.

Additionally, electronics (including computer hardware) and the way computer programming is applied continue to evolve. In the early 2000s, mobile computing and Wi-Fi connectivity changed the way computing happened. In the past decade, connectivity continued to evolve, and many new types of smart devices needed new code. Over the next few decades, changes in the core of computers, such as the creation of quantum coprocessors, have the potential to revolutionize software development yet again.

In 2021, the U.S. Bureau of Labor Statistics predicted that over the next decade, the number software engineering jobs in the United States will increase by 25%8. This is, in part, because technology continues to infiltrate our daily lives. For example, many grocery chains are updating their shopping carts to include sensors that record all of the items you add to the basket area. As this automation continues and expands, it will require a lot of software developers. The current push for autonomous vehicles will be accomplished as a result of sensors and a large number of software engineers creating hundreds of thousands of lines of code. It will take years of testing, debugging, and refactoring before autonomous vehicles are as commonly accepted as a mobile phone is today. It will be software engineers who make that happen.

Footnotes

  • 8Bureau of Labor Statistics, U.S. Department of Labor, Occupational Outlook Handbook, Software Developers, Quality Assurance Analysts, and Testers, at https://www.bls.gov/ooh/computer-and-information-technology/software-developers.htm (visited June 04, 2023).
Citation/Attribution

This book may not be used in the training of large language models or otherwise be ingested into large language models or generative AI offerings without OpenStax's permission.

Want to cite, share, or modify this book? This book uses the Creative Commons Attribution License and you must attribute OpenStax.

Attribution information
  • If you are redistributing all or part of this book in a print format, then you must include on every physical page the following attribution:
    Access for free at https://openstax.org/books/introduction-computer-science/pages/1-introduction
  • If you are redistributing all or part of this book in a digital format, then you must include on every digital page view the following attribution:
    Access for free at https://openstax.org/books/introduction-computer-science/pages/1-introduction
Citation information

© Oct 29, 2024 OpenStax. Textbook content produced by OpenStax is licensed under a Creative Commons Attribution License . The OpenStax name, OpenStax logo, OpenStax book covers, OpenStax CNX name, and OpenStax CNX logo are not subject to the Creative Commons license and may not be reproduced without the prior and express written consent of Rice University.