Extract Your Code So You Can Edit Independently

Extract a Single-purpose Component

Problem and Solution Overview

This recipe just states how to execute a solution. Read our Legacy Newsletter blog post: DevOps #4 – Edit Independently to understand the specific problem we are solving and the solution approach.

This recipe helps you extract your code to a single-purpose component that you can edit independently from other teams.

  1. Extract Data
    1. Complication: Half a Class Belongs With My Entity
  2. Extract Code
    1. Complication: My Code Calls Their Code
    2. Complication: My Code is Part of Each Method in the Class
    3. Complication: My Code Shares Locals With Theirs
    4. Complication: Half a Parameter Belongs With My Code
    5. Complication: A Test Uses Both My Code and Theirs

Basic Recipe: Extract Data I Change

The most important part is to use the customer’s names, both for the entity and the business segment that interacts with that entity. This recipe assumes your team modifies all the fields in the class. See the complication recipes if not.

  1. Identify a class whose fields your team changes.
  2. If the class has any methods other than trivial properties, extract all fields to a plain old data class.
    • Use parts 1, 3, & 5 of the Split God Class recipe to extract all fields.
    • Limit the code you extract into the helper as you want it to remain a plain old data class.
  3. Commit and merge to main.
  4. Create a namespace Entities::SomeBusinessSegment. Use a customer term for the business segment that uses this class.
    • The best way to find such a customer term is to look at your current story. This story forced you to change this code, so you know both are part of the same business segment. Use terms from the story and your Product Owner is likely to re-use the same terms in future stories that share the same business segment.
  5. Move Class the data-only class into the namespace.
  6. Rename Class to name it according to the customer’s term for that entity.
  7. Commit and merge to main.

Complication: Half a Class Belongs With My Entity

This is just like the basic recipe, except that when you use Split God Class you will extract only the fields you are changing right now. A single class may end up containing several Entities from different components.

Basic Recipe: Extract Code I Change

  1. Extract Method the code you have to change for this story.
    • Be narrow in your selection. It is better to extract multiple small chunks than to include code that might have another reason to change.
    • If you are extracting several chunks from the same method or class, use the below recipe to gather them together into a helper class.
  2. Clean up parameters and return types if necessary. Use the complication recipes below for common cases.
  3. Commit and merge to main.
  4. Create a new namespace Operations::SomeBusinessOperation. Use a customer’s term for the business operation involving this code.
    • Use your current story to find such a customer term. This story changed this code, so is related to the underlying business operation. Your Product Owner is likely to re-use the same terms in future stories that share the same business operation.
  5. Move Method the extracted method into the new namespace.
  6. Commit and merge to main.
  7. Identify any fields or entities used by this code and use the Extract Data recipe on them.
  8. (If needed) Clean up existing tests using the recipe below. Commit and merge to main.
  9. Create new unit tests for the extracted method. Place them in the same component as the code.
  10. Commit and merge to main.

Note that this recipe will often break down objects. That doesn’t require creating procedural code. You can:

  • Create functional code that operates on entities.
  • Create Tell-Don’t-Ask code that operates on entities + other TDA code objects.
  • Create OO code where the objects have shared references to entities.

Complication: My Code Calls Their Code

There are four categories of outbound calls, each with its own recipe. You might call their code:

  1. To take the next step. Use Compose With Return.
  2. To handle an eventual result. Use Take a Promise.
  3. To take an unrelated action now that I have determined some situation to be true. Use Convert to Event.
  4. To get a value for my code. Use Take a Function.

Compose With Return

  1. Extract Method on the part of their code which will be using the result of your function.
  2. Commit.
  3. Introduce Parameter Object on the input parameters for their code that come from your code.
  4. Extract Method on your code, including the line that instantiates the parameter object that you created in step 2.
    • If your code returns more than just that parameter object, consider starting over and getting more of the follow-on code to get a larger parameter object.
  5. Commit and merge to main.

Take a Promise

  1. Extract Method on the part of their code that will use your result.
  2. Commit.
  3. Create a promise before the first line of your code. Bind it to the new method you extracted.
  4. Go to the place in your code that is currently calling the method you extracted. Replace that with a call to satisfy the promise.
  5. Extract Method on your code, including the promise satisfaction, but not including the promise instantiation and binding.
  6. Commit and merge to main.

Convert to Event

  1. Extract Method their code. Give it an Honest Name based on what it does.
  2. Commit.
  3. Instantiate an event at the top of the function or in the class. Name it applesauce.
  4. Go to the place in your code that calls their code. Convert it to an event bind + dispatch as follows.
    1. Immediately before the call to their method, paste applesauce += () => .
    2. Immediately after the call to their method, paste applesauce.?Invoke();
  5. Rename applesauce to a name that describes the situation your code knows to be true.
  6. Commit and merge to main.

Take a Function

  1. Extract Method the part of their code that creates the value you need.
  2. Commit.
  3. The line before you call their method, paste const applesauce = TheMethod;
  4. Copy their method name and paste it over the TheMethod in the line you just pasted.
  5. Copy applesauce and paste it over their method name in the invocation.
  6. Commit.
  7. Introduce Parameter on the applesauce variable.
  8. Rename applesauce to a useful name from the perspective of your code.
    • Describe what it provides you, but not how it creates, gets, or loads that value.
  9. Commit and merge to main.

Complication: My Code is Part of Each Method in the Class

You are going to extract a helper class and put the helper class into your component.

  1. Extract Method each piece of your code. Make each a private method. Commit each one independently.
  2. Use the other recipes in this section to remove any calls from your code to their code.
  3. Remove any field access from each of your private methods.
    1. If the field is used mostly by your code then it should be part of an entity. Perform Extract Data on it. Commit.
    2. Otherwise search for writes to the field. If there are no writes, perform Introduce Parameter on the field. Commit.
    3. If there are writes, then continue.
    4. Encapsulate one write with a lambda. Convert someField = foo to (foo) => { someField = foo; }(foo).
    5. Commit.
    6. Introduce Parameter on the lambda (not including the evaluation).
    7. Commit.
    8. Copy the evaluation of the lambda at the old write site. Paste it over each other write usage in the method.
    9. Commit.
    10. Perform steps D-I with read usages.
      • It’ll be the same except that in step 4 you convert foo = someField to foo = () => { someField }().
  4. Split Class to extract just your helper methods to a new class. That class should have no fields.
  5. Commit.
  6. Use this helper class as the target in the remainder of the Extract Code basic recipe you are implementing.

Complication: My Code Shares Locals With Theirs

  1. Extract Method on your code.
  2. Commit.
  3. Introduce Parameter Object on your class for the shared locals.
    • If you feel that some variables are more tightly related to each other, then put those in their own parameter object.
  4. Commit.
  5. At the call site to your code, Introduce Variable on the parameter object.
  6. If the Extract Method resulted in any writes back to local variables at the call site, change them to assign the value from the parameter object rather than from an out param or return value.
  7. Commit.
  8. Remove any unused return values or out params from your method.
  9. Commit.
  10. Consider which of the parameter objects should be Entities. Perform Extract Data on them.

Complication: Half a Parameter Object Belongs With My Code

  1. If the parameter exposes any fields that you use, Encapsulate Field on each one to wrap it with a property.
  2. Commit and merge to main.
  3. Use Extract Data to create an Entity from the portion of the parameter class that you use.
    • Each property will change from doing a direct field access to dereferencing the helper and accessing one of its fields. That encapsulates the extraction so that no one else’s code needs to change.
  4. Add a property on the original parameter object that exposes the Entity.
  5. Commit.
  6. If your code uses methods on the parameter object, use the Split God Class recipe to break off just the part you change.
    • Consider using Extract Code on the part of the parameter object you change, splitting it off from any Entities.
  7. Commit and merge to main.
  8. At this point, your only possible access to the original parameter object is to properties you created in step 1. If there are no such references, skip to step 14.
  9. Pick one reference. Inline this usage of the property (not all usages of the property).
  10. Introduce Parameter on the entity, as accessed by property from the old parameter object.
    • For example, convert param.Entity.Foo to new_param.Foo, where new_param is an entity that your caller is setting to param.Entity.
  11. Commit.
  12. Go to all other property references. Inline each one and then convert it to use the new Entity parameter.
  13. Commit.
  14. Clean up any unused parameters. Your initial parameter object may or may not be unused, depending on what you extracted from it.

Complication: A Test Uses Both My Code and Theirs

You are going to split this test into two tests. One calls just your code and is part of your component. The other tests whatever is left and does not result in your code executing at all. That is the only variant you need to maintain: no test that executes someone else’s code executes any of your code.

Use Parts 2 & 3 of the Squeezing Juice out of Over-ripe Tests recipe to split the test. Treat your code as the “common code” in that recipe.