You just have to have the guidance to lead you in the direction until you can do it yourself. It is the neglect of timely repair that makes rebuilding necessary.

—Richard Whately

Refactoring


Note: This article is part of Extended SAFe Guidance, and represents official SAFe content that cannot be accessed directly from the Big Picture.


Refactoring is the activity of improving the internal structure or operation of a code or component without changing its external behavior. The goal of software development is the continuous delivery of business value to users and stakeholders. Constantly changing technology, coupled with evolving business objectives makes it difficult to maintain and constantly increase business value. Two paths to the future exist:

  • Keep adding new functionality to an existing code base toward an eventually unmaintainable “throw-away” state
  • Continuously modify the system to provide a foundation for efficiently delivering not just the current business value but future business value as well

The second choice, refactoring, is better.  With continuous refactoring, the useful life of an Enterprise’s investment in software assets can be extended as long as possible, and users can continue to experience a flow of value for years to come. Refactors enable an emergent design to ensure the system continues to meet future business needs.  Refactors are a special type of Enabler story in SAFe and, like any other Story, they must be estimable, demonstrable, and valuable, as well as accepted by the Product Owner.

Details

Figure 1 illustrates the essence of Refactoring, which is the modification of any software entity—a module, method, or program—to improve its structure or viability, without changing the external functionality.

Figure 1. Refactoring in an isolated environment for change within a larger entity

For example, refactors may accomplish such things as increases in processing speed, sourcing different internal data, or improving security concerns. Another type of refactoring involves streamlining some aspect of the code to make it more efficient, more maintainable, or more readable.

Refactoring mandates that each change is tested immediately to verify the accomplishment of the desired goal. A refactor may be broken into a series of sequential micro-refactors in order to accomplish a larger goal; each small refactor must be tested to ensure correctness. This iterative process preserves the integrity of the software at any stage.

SAFe emphasizes the importance of keeping all work visible, including refactoring. Like user value work, it must be planned for, estimated, and prioritized at all levels of the Solution.

Sources of Refactors

Refactors arise from various sources, as illustrated in Figure 2.

Figure 2. Possible sources of refactors

A refactor can be instigated by a business Feature or can be a part of larger refactoring initiative required by some new architectural Enabler. New user Stories may also require some refactoring of code. Accumulated technical debt may drive the team to refactor certain components. Some refactors may be necessitated by new Nonfunctional Requirements.

Not all refactoring efforts originate from a story. The Test-Driven Development (TDD) process encourages continuous refactoring as part of making code changes.  Developers are constantly updating their designs to better support current and upcoming requirements. Such work should be factored into the estimate of the corresponding Story (see continuous refactoring techniques described in [1], [2], [3]). Specific refactors, however, represent larger pieces of a redesign that needs to be planned and tracked as separate backlog items.

Specifying Refactors

It is important to understand what value will be achieved once the refactoring is completed. Teams may wish to use the “ . . . so that . . . ” portion of the user story voice form to foster a shared understanding of purpose and value, as Figure 3 illustrates:

Figure 3. Refactor example

Splitting Refactors

As with user stories, splitting refactors is important, as it helps sustain better development flow. Table 1 provides some useful methods for splitting refactors, and examples for each.

 

1. By User Scenario or User Story – Refactor incrementally in a story-by-story or scenario-by-scenario mode, or otherwise identify the functional areas as a basis for incrementing.
Improve DB queries and introduce data caching so that the system will run faster . . . Refactor all user management functionality . . . Catalog browsing functionality . . . Check functionality
2. By Component – First, refactor everything related to a single component, then move to the next.
Change indexing process to batch processing so that the process is at least 2 – 3 times faster for an average web page . . . For parsing (parser component) . . . For entity extraction (analyzer component) . . . For storing in the index (index component)
3. Interface / Implementation – First, create new interfaces and wrap in the old functionality, then refactor the functionality itself.
Extract all parsing parameters into an xml configuration file so that that the process can be tuned easily without changing the code . . . Install PINGID Protocol server and test with a mock identity provider . . . Read configurations from a file in any format . . . Refactor configuration functionality to support certain structure and schema validation
4. Strangle Component – Incrementally move the functionality of the component to other components; once everything is moved, delete the old code.
Replace database with custom search index so that indexing and search performance improves 10 – 20 times . . . Move index data to custom search index first . . . Move entity dictionaries
5. Inline Refactoring / Extraction – Refactor the functionality in-line where it currently is, but then extract it and encapsulate into a component, class, or method/function.
Replace current ad hoc parsing with grammar-based functionality so that changing parsing rules would become easy and without the need for coding . . . Refactor the code (as is) to use grammar notation . . . Extract all grammar-related functionality into a grammar engine
Table 1. Methods of splitting refactors

 

Establishing Acceptance Criteria

As with user stories, defining acceptance criteria for refactors helps resolve ambiguities. Figure 4 illustrates the additional specificity that comes with established acceptance criteria.

Figure 4. Example of acceptance criteria for a refactor story

Acceptance criteria can often be used as a natural basis for splitting. For example, the first step in Figure 4 might be to ‘ . . . make synchronous non-configurable batch processing with a single query to the dictionary, but without the debug logging.’ Then ‘ . . . add the capability to read batch size from the file.’ Step 3 would be ‘ . . . process items asynchronously,’ and finally ‘ . . . add debug logging functionality.’

Demonstrating Refactors

Even though refactoring is focused on the internal working of the code, as with any other story, teams demonstrate the results. From the example above, the teams might demonstrate the following:

  1. Reduced processing time on a few web pages, compared to the previous benchmark
  2. Dependency of processing time on the size of the batch, which can be configured from the file
  3. A code snippet for asynchronous processing
  4. The debug log file that captures all the operations
  5. The number of queries to dictionaries per batch (from the log file)

Adopting the Culture

Refactoring is a mandatory skill for Agile Teams and is a critical component of the Team and Technical Agility competency of the Lean Enterprise. Refactors should routinely appear on the Team Backlog and be included—along with in-line refactoring—in story estimates. A Design Community of Practice (CoP) can foster awareness and attention to refactoring techniques. Scrum Masters can help their teams learn effective approaches to specifying, estimating, and splitting refactors. Product Owners should embrace refactoring by prioritizing the work and by helping to define acceptance criteria.

A culture of test automation including TDD and Behavior-Driven Development (BDD) create a large set of tests that make refactoring easier and more reliable. Errors introduced by refactoring are caught immediately by the tests.

 


Learn More

[1] Fowler, Martin, et al. Refactoring: Improving the Design of Existing Code. Addison-Wesley, 1999.

[2] Martin, Robert. Clean Code: A Handbook of Agile Software Craftsmanship. Prentice Hall, 2008.

[3] Wake, William. Refactoring Workbook. Addison-Wesley, 2003.

 

Last update: 10 February 2021