M. David Green

Existence is a byproduct of semantics

Refactoring Javascript: Replacing a Local Variable With a Function or Method

Replacing Local Variables That Add No Semantic Value

You may find a local variable that adds no semantic value, or that gets its value from a simple expression that might be useful to recalculate at different times as the conditions change. Eliminating variables like these in favor of function calls can improve the readability and flexibility of your code.

For example

Given an Employee constructor with start_data and end_date attributes:

function Employee(data) {
    var start_date = data.start_date || null,
        end_date = data.end_date || new Date().getFillYear(),
        years_employed = (start_date) ? end_date - start_date : 0;
    this.years_employed = years_employed;
}

Which might be used this way:

var employee = new Employee({"start_date":2009, "end_date":2012});
employee.years_employed; // 3

The local years_employed variable is being used to calculate a value that may change depending on the conditions passed in. Its only purpose so far in this constructor is to carry the value of the person’s years employed to the years_employed property of Employee instances, but it may be a useful value to recalculate with different start and end dates.

One hint that this might be a good local variable to replace with a new method is the fact that the name of the variable is very similar to, or in this case the same as, the name of the property that will receive the result. That tells you that the variable is not adding any semantic value.

A Jasmine test to make sure this is working might look like this:

describe("an employee", function() {
    var employee;
    it("should have a years_employed value", function() {
        employee = new Employee({"start_date":2009, "end_date":2012});
        expect(employee.years_employed).toEqual(3);
    });
    it("should have no years_employed value with no start date", function() {
        employee = new Employee({"end_date":2012});
        expect(employee.years_employed).toEqual(0);
    });
});

Considerations:

In this case, the expression used to calculate years_employed is one that may need to be calculated again based on changing circumstances. The local variable could be replaced with a call to a separate method of the same name that performs the operation.

When replacing any local variables, you need to be careful to make sure that any other code in the local scope where this variable was originally declared does not rely on it. When you are finished, you should find you have replaced any references to it with references to the property where the value is now being stored.

The refactored code will probably be a little longer than the original code, depending on how complex the expression used to calculate the original local variable was. But you may find that having the expression available as a new method suggests more productive ways that method can be enhanced to make it more useful in other circumstances.

The highlighted code above could be refactored into:

function Employee(data) {
    this.years_employed = years_employed();
    function years_employed() {
        var start_date = data.start_date || null,
        end_date = data.end_date || new Date().getFillYear();
        return (start_date) ? end_date - start_date : 0;
    }
}

By following these steps:

  1. Identify a local variable which carries a value that is calculated in a very simple expression that might change its value depending on the circumstances.
  2. If you are using test-driven development, this is when you would define your first test parameters with an expected return value, and create your first test.
  3. Copy the code for the expression that generates the value of this local variable to a new function or method with the same name as the local variable.
  4. Search your code for calls to the original local variable, replace them with calls to the new function or method, and test to make sure things still work as expected.
  5. Delete the local variable and test again to make sure nothing broke as a result of the refactor.

A refactor like this may make the overall code a little longer, but it will likely be more readable. Local variables originally carried on the constructor may be eliminated in favor of temporary local variables on a private method within that constructor.

It would also be possible to add this new method to the prototype of the constructor separately, after defining the constructor. This would reduce the memory impact of the new method, since each instance would reference a single shared method instead of maintaining individual copies of it. However, this would change the scope of the method, making it public.

If you find it easier to work with longer constructors that keep shared relationships together and private, just be aware of the additional memory usage involved in carrying a copy of this new method in every instance of the constructor. In practical terms, it may turn out not to be an issue in your case. It will be easier to identify whether it is or not once the code is properly organized, and this decision is trivial to refactor out again if it turns out to be an issue.