• No results found

The purpose of this chapter is to conclude this report with a brief summary of the results obtained, good practices when migrating from C into C++, and future work. Subsection 8.1 summarizes the result of the project. Subsection 8.2 discusses future work followed by good practices in migrating a C legacy code to C++ in Subsection 8.3.

8.1 Results

In this project, a redesign of the image sensor’s camera driver was developed. In this redesign, different OOD techniques such as design patterns and SOLID principles were applied. Applying these techniques helps to divide and structure the functionalities of the camera driver into smaller and more decoupled classes. This arrangement improves the modularity of the driver. A prototype implementation of this redesign was provided in C++.

Code duplications, function tables, and branches were removed in the implementation of the redesign by using the concept of polymorphism and interfaces. These options were not possible in the original C implementation due to the limitation of the C language. In addition, C++’s memory management and exception handling techniques were applied in the implementation of the redesign. These techniques help to efficiently manage memory leaks and debug errors more easily.

This report shows that the prototype implementation meets the functional requirements for the main use cases. The main use cases implementation were tested both on the testing simulation environment (Devbench) and on the testing environment that involves real hardware (Testbench). The result shows that the implementation fulfills the main functional requirements of the camera driver. Moreover, the result demonstrates the integrability of the C++ implementation with its C code client and other soft-ware components that are implemented in C.

For the non-functional requirements, the redesign avoids cyclic dependencies between classes or layers of the driver and is flexible for the extendibility of new camera sensors or relays in the future. The developers only need to add a concrete class for the new sensor or relay and implement its specific features - the other features can be easily extended from one of the existing sensors or relays. In addition, the size of the driver codebase (total number of lines of code) is reduced in the C++ implementation.

This reduction improves the maintainability and readability of the driver’s code with insignificant per-formance differences from its original implementation. Finally, a short guideline reference document to migrate a C component to C++ is added as an appendix to the confidential version of this report.

8.2 Future work

The main use case features and properties of the camera driver are realized, but there are some features that are good to be incorporated in the realization. The following features are recommended as possible future work.

a. Redesign some of the provided external interfaces of the camera driver: There are unused function parameters in some of the provided interfaces. For instance, the client uses only one interface to initialize all hardware components of the driver except the sensor. This leads the interface to incorporate many parameters that are not required by all hardware types. This can be avoided by refactoring this single interface into separate interfaces for each of the hardware

40

initializations so that the interface only contains parameters that are required to initialize that specific hardware.

b. Convert non-DDF dependencies into DDF: Software components at ASML communicate through a DDF interface standard. However, it is found that the client of the camera driver depends directly on a C header file. This is an undesired dependency and requires some work-around when we migrate the C code into C++. We need a C++ wrapper, as shown in Figure 29, for each of the non-DDF dependencies, which increases unwanted overhead. Hence, con-verting the non-DDF dependencies into DDF is recommended to avoid such a workaround.

Figure 29: C++ wrapper for non-DDF dependencies

c. Make the prototype implementation of the camera driver into a production-ready im-plementation: The prototype implementation is meant to migrate the main use case of the driver and all of its features such as diagnostics use case are not migrated. To make this proto-type implementation production-ready, the following steps need to be done:

• Implement features of the codebase that have not been migrated.

• Perform thorough testing.

• Implement new features of the camera driver that were added throughout the course of the project.

8.3 Good practices on migrating C codebase into C++

There are multiple stages in migrating the C legacy code of a software driver into its corresponding C++ version, starting from understanding the driver codebase, redesigning the driver, implementing the redesigned prototype, and migrating available tests. Along with these processes, good experiences were gained that can be used for future references. The main good practices obtained during this project are presented in the following subsections.

41 8.3.1. Understand the codebase thoroughly and extract the main use cases

Understanding the codebase in detail mainly concerns extracting the main use cases and identifying the main non-functional requirements for the redesign of a driver.

Extracting the main functional use cases

Before rushing to the legacy code of a driver, it is a good idea to understand the driver's domain. This helps to grasp the bigger picture of the system and comprehend how the software components com-municate and work together. Once the domain is grasped, the next step is to dive deeply into the legacy code. This is because the driver's behavior and functionalities live within the sources in addition to its documentation. Understanding the legacy code well is crucial for extracting the main use case function-alities and identifying the key non-functional requirements. Extracting these key use cases not only helps to thoroughly understand the current design and behavior of the driver, but it also helps to plan the migration process very well.

Redesigning the driver

After extracting and understanding the main use cases of the driver, the next process is to redesign the driver. It is impossible to incorporate and achieve all the quality attributes of the driver in the redesign because the redesign solution might have a negative or positive impact on a certain quality attribute.

Hence, the redesign should mainly focus on a few non-functional requirements that have to be addressed during the project. In addition to the legacy code, analyzing the shortcomings of an existing design document, if it exists, is helpful to easily identify the key non-functional requirements. It is good to follow object-oriented design standards in the redesigning process and apply OOD techniques such as design patterns and SOLID principles [6] as much as needed.

8.3.2. Prioritize the use cases and migrate step by step

The logical order and prioritization of the extracted uses cases have to be put in place before starting implementation. Knowing the size and logical order of these use cases helps to estimate the time it takes for each of them and build the minimum end-to-end functionalities of the driver. It is also important to identify the development approach that has to be followed during the implementation of the use cases, i.e., either develop one use case and apply its functional test before implementing the next use case or develop all use cases first and apply the functional tests later.

Even though choosing what approach to follow depends on the behavior and context of a driver, expe-rience from this project shows that applying an iterative process is a good practice. Implementing one use case and testing its functionality before moving to the next step helps to debug easily, reassess, and adjust the estimated plan. It also gives you more insights along the way that can provide an idea to improve the redesign as soon as possible. Besides, use cases of a driver have different sizes and opment times. This project's experience also shows that driver initialization use case takes more devel-opment time than other use cases. This is because, in an embedded software environment, driver ini-tialization has many operations and validations as it is the basic precondition for the primary operations of a driver.

8.3.3. Have a C++ reference guideline at an early stage of the migration

After going through the legacy code of a driver and understanding the nature of its codebase, it is good practice to have a short strategy guideline on what type of C++ techniques should be used during mi-gration into C++ on aspects such as on loops, containers, memory management, and exception handling.

This does not only improve the efficiency and fasten the development time but also helps to have con-sistency in the new migrated code. For instance, it is good to know when to apply smart pointers because

42

if we use them by default everywhere without caution, they will have a negative impact on performance.

Similarly, overusing the try-catch pair on every function implementation is not a good practice as it makes the code not clean and readable. For more tips and guidelines, refer to Appendix A of the confi-dential version of this document.

8.3.4. Use C++ STL libraries over company-specific C macros

C++ STL contains a family of functions and classes that are related to iterators, algorithms, and con-tainers. These libraries are efficient, type-safe, and valuable to write clean as well as readable code. In a driver legacy code, company-specific C macros are used that are less efficient and error prone than the STL. Hence, when we migrate a legacy code into C++, replacing these company-specific C macros using their corresponding C++ STL is better.

43