Next: , Previous: Evaluation, Up: Programming Aids


2.4 Refactoring

Expressions within functions can be automatically “refactored” into their own sub-functions by using the erl-refactor-subfunction command (C-c C-d f). The command takes the text of the expression, determines which variables it needs from the original function, and then generates the new function and puts it on the kill ring for insertion by hand (with yank, C-y). The original function is rewritten with a call to the subfunction where the refactored expression used to be.

For example, suppose we want to refactor the following function:

     eval_expression(S) ->
         case parse_expr(S) of
             {ok, Parse} ->
                 case catch erl_eval:exprs(Parse, []) of
                     {value, V, _} ->
                         {ok, flatten(io_lib:format("~p", [V]))};
                     {'EXIT', Reason} ->
                         {error, Reason}
                 end;
             {error, {_, erl_parse, Err}} ->
                 {error, Err}
         end.

In this example we will take the inner case expression and move it into a new function called try_evaluation. We do this by setting the Emacs region (using the mouse or C-SPC) from the word case until the end of the word end – marking exactly one whole expression. We then enter C-c C-d f to refactor, and when prompted for the function name we respond with “try_evaluation”. The original function is then rewritten to:

     eval_expression(S) ->
         case parse_expr(S) of
            {ok, Parse} ->
                try_evaluation(Parse);
            {error, {_, erl_parse, Err}} ->
                {error, Err}
         end.

And at the front of the kill ring we have the new function definition, which can be pasted into the buffer wherever we want. The actual definition we get is:

     try_evaluation(Parse) ->
         case catch erl_eval:exprs(Parse, []) of
            {value, V, _} ->
                {ok, flatten(io_lib:format("~p", [V]))};
            {'EXIT', Reason} ->
                {error, Reason}
         end.

Important note: This command is not a “pure” refactoring, because although it will import variables from the parent function into the subfunction, it will not export new bindings created in the subfunction back to the parent. However, if you follow good programming practice and never “export” variables from inner expressions, this is not a problem. An example of bad code that will not refactor correctly is this if expression:

     if A < B -> X = true;
        B > A -> X = false
     end,
     foo(X)

This is in poor style – a variable created inside the if is used by code at an outer level of nesting. To work with refactoring, and to be in better style, it should be rewritten like this:

     X = if A < B -> true;
            B > A -> false
         end,
     foo(X)