Demonstration: Applying the Parallel Change technique to change code in small, safe steps

The Parallel Change technique is intended to make it possible to change code in a small, save steps by first adding the new way of doing things (without breaking the old one; "expand"), then switching over to the new way ("migrate"), and finally removing the old way ("contract", i.e. make smaller). Here is an example of it applied in practice to refactor code producing a large JSON that contains a dictionary of addresses at one place and refers to them by their keys at other places. The goal is to rename the key. (We can't use simple search & replace for reasons.)



The tests typically compare the produced and the expected JSON but the expected data isn't hardcoded but is generated through helper functions, such as:


expect(data).to.deep.equal({
    ...expectAddresses({ billing: { /* .. */ }}) // this already uses the new key
    ...expectNotificationRecipients(),
    ...expectAccounts(),
    ...
});


That makes our job much easier. So here is the full process (I use "->" to indicate the change performed):

  1. (Expand) In prod code where we create the data, keep the old & add the new key: addresses["accounts_street_address"] = ... -> addresses["billing"] = addresses["accounts_street_address"] = ... => tests break
  2. In tests, modify expectAddresses() to create and thus expect both as well: addresses["accounts_street_address"] = { "street_address": expectStreetAddress(billing || all) }; -> addresses["billing"] = addresses["accounts_street_address"] = { "street_address": expectStreetAddress(billing || all) }; => tests fixed
  3. (Migrate) In prod code, use the new key when referring to the address: "address_reference_id": "accounts_street_address" -> "address_reference_id": "billing" => tests break
  4. In tests, fix expectAccounts() to expect the new key: "address_reference_id": "accounts_street_address" -> "address_reference_id": "billing" => tests fixed
  5. (Contract) In prod code, remove the old address: addresses["billing"] = addresses["accounts_street_address"] = ... -> addresses["billing"] = ... => tests break
  6. In tests, fix expectAddresses() to not expect the old address anymore: addresses["billing"] = addresses["accounts_street_address"] = ... -> addresses["billing"] = ... => tests fixed
  7. Fix two tests that access the address itself through its old name.
  8. DONE!

Tags: methodology refactoring


Copyright © 2024 Jakub Holý
Powered by Cryogen
Theme by KingMob