From "Refactoring"
🎧 Listen to Summary
Free 10-min PreviewRefactoring Variables for Clarity and Immutability
Key Insight
Variables often have multiple responsibilities, particularly when assigned several times within a method, excluding loop or collecting variables (like those calculating sums or string concatenations). If a variable holds the result of a complex calculation for later reference and is reassigned, it indicates more than one responsibility, causing confusion for readers. The 'Split Variable (240)' refactoring addresses this by replacing a single variable with multiple variables, each handling a distinct responsibility. This is achieved by renaming the variable at its declaration and first assignment, then changing subsequent references up to the next assignment, repeating this process until all distinct responsibilities are separated, often declaring new variables as immutable. An example involves an 'acc' variable for calculating distance travelled by a haggis, which is split into 'primaryAcceleration' and 'secondaryAcceleration' due to its two responsibilities. Another case involves an input parameter like 'inputValue' in a 'discount' function being split into 'originalInputValue' and 'result' to separate input value from calculation result.
Mutable data is a significant source of software problems, causing awkward coupling and hard-to-spot knock-on effects. A key strategy to minimize mutable data's scope is to remove variables that can be easily calculated (derived variables). Replacing a mutable derived variable with a query (a function that calculates its value on demand) clarifies the data's meaning and prevents corruption if source data changes and the derived variable is not consistently updated. This approach is highly effective unless the source data for the calculation is immutable and the result can also be forced to be immutable. Transformation operations creating new immutable data structures are reasonable to keep, showing a duality with objects wrapping data structures with calculated properties.
To replace a derived variable with a query, first identify all update points for the variable. If necessary, use 'Split Variable (240)' to separate each point of update. Then, create a function that calculates the variable's value. Crucially, use 'Introduce Assertion (302)' to verify that the existing variable and the new calculation yield identical results whenever the variable is used, potentially using 'Encapsulate Variable (132)' to provide a home for the assertion. After testing confirms the assertion holds, replace all reads of the variable with calls to the new calculation function. Finally, apply 'Remove Dead Code (237)' to the original variable's declaration and updates. An example is a 'ProductionPlan' where a mutable '_production' accumulator, modified by 'applyAdjustment', is replaced by a 'calculatedProduction' getter that sums '_adjustments', initially using an assertion like 'assert(this._production === this.calculatedProduction)' for safe transition. If the initial value isn't zero, 'Split Variable (240)' can separate an '_initialProduction' from a '_productionAccumulator' before applying the query refactoring.
📚 Continue Your Learning Journey — No Payment Required
Access the complete Refactoring summary with audio narration, key takeaways, and actionable insights from Martin Fowler, Kent Beck.