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 an activity to improve a code or component’s internal structure or operation without changing its external behavior. The goal of software development is the continuous delivery of business value to users and stakeholders. Constantly changing technology and evolving business objectives make maintaining and continuously increasing business value difficult. Two paths to the future exist:

  1. Keep adding new functionality to an existing code base toward an eventually unmaintainable “throw-away” state.
  2. Continuously modify the system to provide a foundation for efficiently delivering current and future business value.

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. Users can continue to experience a flow of value for years to come. Refactors enable an emergent design, ensuring the system continues to meet future business needs. Refactors are a particular type of Enabler story in SAFe, and, like any other Story, they must be estimable, verifiable, and valuable, as well as accepted by the Product Owner.

Details

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

Figure 1. Refactoring in an isolated environment for change within a larger entity
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 aspects of the code to make it more efficient, maintainable, or readable.

Refactoring requires that each change is tested immediately to verify the accomplishment of the desired goal. A refactor may be broken into sequential micro-refactors to accomplish a more important purpose. 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, refactoring must be planned for, estimated, and prioritized.

Sources of Refactors

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

Figure 2. Possible sources of refactors
Figure 2. Possible sources of refactors

A refactor can be provoked by a business Feature or a part of a larger refactoring initiative required by some new architectural enabler. New user stories may also require some refactoring of code. Technical debt may drive the team to refactor specific components. Some may be necessitated by new Nonfunctional Requirements (NFRs)

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 support current and upcoming requirements better. 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 more significant pieces of a redesign that needs to be planned and tracked as separate backlog items.

Specifying Refactors

Understanding what value will be achieved once the refactoring is completed is essential. Teams may wish to apply the user story voice form to foster a shared understanding of purpose and value, as Figure 3 illustrates:

Figure 3. Refactor example
Figure 3. Refactor example

Splitting Refactors

As with user stories, splitting refactors is essential, as it helps sustain better development flow. Table 1 provides some practical 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, and Check functionality.
2. By Component – Refactor everything related to a single component, then move to the next.
Change the indexing process to batch processing so that the process is at least 2 – 3 times faster than 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, wrap in the old functionality, and then refactor the functionality.
Extract all parsing parameters into an XML configuration file so 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 specific structure and schema validation
4. Strangle Component – Incrementally move the component’s functionality to other components; once everything is moved, delete the old code.
Replace the database with a custom search index so indexing and search performance improve 10 – 20 times. Move index data to the custom search index first. Move entity dictionaries.
5. Inline Refactoring / Extraction – Refactor the functionality inline where it currently is, but then extract it and encapsulate it in a component, class, or method/function.
Replace current ad hoc parsing with grammar-based functionality so that changing parsing rules would become easy without coding. Refactor the code (as is) to use grammar notation. Extract all grammar-related functionality into a grammar engine.

Table 1. Methods of splitting refactor stories

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 refactoring story
Figure 4. Example of acceptance criteria for a refactoring story

Acceptance criteria can often be used as a 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 the batch size from the file.’ Step 3 would be the 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 show 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 a Refactoring Culture

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

A test automation culture, including TDD and Behavior-Driven Development (BDD), creates a large set of tests that make refactoring more accessible and 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: 24 February 2023