Erik Onstenk is a lead software architect at Kulicke & Soffa Netherlands.

Corné van de Pol is a consultant at Alten Netherlands and the tech lead of the refactoring project at Kulicke & Soffa.

18 August

To maintain the speed and quality of machine software, Kulicke & Soffa and Alten have started a refactoring project parallel to regular development. Their efforts are already starting to pay off.

Founded in 1951, Kulicke & Soffa (K&S) is a leading provider of semiconductor and electronic assembly equipment. In the Netherlands, it develops pick-and-place systems. Placing components on a PCB, these machines are used for surface mounting technology (SMT) and advanced packaging applications.

The development of the system software started over a decade ago. At some point in time, alternative flows were introduced in the codebase to support product variations. As time went by, more variations and versions were required, and alternative flows started to accumulate and spread over a growing number of software components.

Nowadays, multiple product-oriented teams are working simultaneously with different release schedules, often making changes in the same software components. The shared nature of the code makes it difficult to ascertain the impact of changes on different products, increasing the required amount of (automated) test time and potentially even the number of bugs. Due to product-specific alternative code flows, the size of the codebase a team works with becomes bigger and more complex as well. Finally, introducing new variations is hard because many alternative flows, scattered over the codebase, need to be changed or extended.

KS refactoring changes
Changes per product team
KS refactoring codebase
Codebase before refactoring (top) and after (bottom)

Strategies

To maintain speed and quality, we’ve started a refactoring project parallel to regular development. The aim is to separate product-specific from generic code. We do this by moving the product-specific code out of the existing software components and putting it together. After all specific code has been extracted from a component, only shared code remains.

An important constraint is that no dependencies are allowed from shared to product-specific code and between one product-specific part and another. Correctly managing dependencies guarantees that changes to product-specific code can never impact other products. As a side effect, products can also be compiled and tested separately. By only compiling and testing those products that have changed, delivery lead time can be reduced significantly.

We process one software component at a time. For each component, we follow one of four strategies: nothing, make-shared, move or split. The strategy of choice is determined by the nature of the code.

With generic code that can be shared between multiple products, we apply the nothing strategy. Code is generic when it doesn’t contain product-specific terminology and/or alternative flows. Alternative flows manifest themselves through conditional logic such as switch cases and if statements, either directly on product type or indirectly on product-specific properties. While most of the ‘direct’ conditional logic can be found rather easily, ‘indirect’ conditional logic is sometimes harder to locate because terminology may differ for each area of the code.

The make-shared strategy is chosen for software components that remain in the shared code part but contain product-specific terminology or alternative flows for different products. To be more resilient for product-specific changes and prevent code duplication, the components are made generic.

When code is specific for one product and that product’s domain, the entire software component is marked for a move to the product-specific part. When moving a component, no incoming dependencies from the shared code should remain. To avoid illegal dependencies, we need to address the dependent component(s) first.

We apply a split when a software component contains product-specific code of multiple products. Refactoring the code such that the product-specific code is separated makes this usually a more complex strategy.

KS refactoring move
Dependency before a move (top) and illegal dependency after a move (bottom)
KS refactoring split
An application that starts the machine software, before and after being split. Before, multiple startup steps are created by the ‘main’ class and injected into the ‘StartupSteps’ (top). Some steps contain conditional logic because the behavior either differs or is completely skipped for a specific product. To split ‘Startup,’ two product-specific executables are introduced where each ‘main’ only creates the steps applicable for that product (bottom). This removes the need for product-specific if statements and makes it possible to move the product-specific code. The code that remains in the ‘StartupC’ library is generic and shared functionality. All dependency arrows crossing the boundaries between the executables and the library point to the shared library.

Efforts

The first step in executing the project has been implementing the infrastructure to support the moving of code to product-specific parts. This infrastructure is now in place. To get the maximum value, we also need to improve the build and solely build the parts that have changed. For example, if a changeset only contains modifications in product A, there’s no need to compile and test product B. We’ve started development of this ‘build what has changed’ and expect to deploy it soon.

The software components with the highest benefit are addressed first. The benefit is determined by the number of changes for different products in a component versus the expected cost of the planned action. Currently, we’ve moved the first components, others have been split and more are in progress.

Software components that hardly ever change aren’t worth the investment. Despite the possible presence of product-specific code, warranting a make-shared or split, such a component will undergo nothing. For some of the components requiring a split, we need to do a redesign instead because the design isn’t fit for upcoming functionality.

With the project still in its early stages, we see our efforts are starting to pay off. The developers are eagerly awaiting more progress. We do realize that during the refactoring, we shouldn’t lose sight of the objective, which is improving quality and efficiency.

Edited by Nieke Roos