Previous: Name Parameters, Up: Refactoring

#### 5.9.3 Extract Subprogram

This refactoring is used to move some code from one place to a separate subprogram. The goal is to simplify the original subprogram, by moving part of its code elsewhere.

Here is an example from the "Refactoring" book. The refactoring will take place in the body of the package `pkg.adb`, but the spec is needed so that you can compile the source code (a preliminary step mandatory before you can refactor the code).

         pragma Ada_05;

         with Ada.Containers.Indefinite_Doubly_Linked_Lists;
         with Ada.Strings.Unbounded;

         package Pkg is

            type Order is tagged null record;
            function Get_Amount (Self : Order) return Integer;

            package Order_Listsis new
               Ada.Containers.Indefinite_Doubly_Linked_Lists (Order);

            type Invoice is tagged record
               Orders : Order_Lists.List;
               Name   : Ada.Strings.Unbounded.Unbounded_String;
            end record;

            procedure Print_Owing (Self : Invoice);

         end Pkg;

The initial implementation for this code is given by the following code:

         pragma Ada_05;
         with Ada.Strings.Unbounded;  use Ada.Strings.Unbounded;
         with Ada.Text_IO;            use Ada.Text_IO;

         package body Pkg is
            use Order_Lists;

            ----------------
            -- Get_Amount --
            ----------------

            function Get_Amount (Self : Order) return Integer is
            begin
               return 0;
            end Get_Amount;

            -----------------
            -- Print_Owing --
            -----------------

            procedure Print_Owing (Self : Invoice) is
               E : Order_Lists.Cursor := First (Self.Orders);
               Outstanding : Natural := 0;
               Each : Order;
            begin
               --  <<< line 30
               --  Print Banner

               Put_Line ("********************************");
               Put_Line ("***** Customer Owes         ****");
               Put_Line ("********************************");  --  << line 35

               --  Calculate Outstanding

               while Has_Element (E) loop
                  Each := Element (E);
                  Outstanding := Outstanding + Each.Get_Amount;
                  Next (E);
               end loop;

               --  Print Details

               Put_Line ("Name: " & To_String (Self.Name));
               Put_Line ("Outstanding:" & Outstanding'Img);
            end Print_Owing;
         end Pkg;

The procedure `Print_Owing` is too long and does several independent actions. We will perform a series of three successive refactoring steps to extract the code and move it elsewhere.

The first is the code that prints the banner. Moving it is easy, since this code does not depend on any context. We could just do a copy-paste, but then we would have to create the new subprogram. Instead, we select lines 30 to 35, and then select the contextual menu `Refactoring/Extract Subprogram`. GPS will then automatically change `Print_Owing` and create a new procedure `Print_Banner` (the name is specified by the user, GPS does not try to guess it). Also, since the chunk of code that is extracted starts with a comment, GPS automatically uses that comment as the documentation for the new subprogram. Here is part of the resulting file:

         package body Pkg is

            procedure Print_Banner;
            --  Print Banner

            ------------------
            -- Print_Banner --
            ------------------

            procedure Print_Banner is
            begin
               Put_Line ("********************************");
               Put_Line ("***** Customer Owes         ****");
               Put_Line ("********************************");
            end Print_Banner;

            ... (code not shown)

            procedure Print_Owing (Self : Invoice) is
               E : Order_Lists.Cursor := First (Self.Orders);
               Outstanding : Natural := 0;
               Each : Order;
            begin
               Print_Banner;

               --  Calculate Outstanding

               while Has_Element (E) loop
                  Each := Element (E);
                  Outstanding := Outstanding + Each.Get_Amount;
                  Next (E);
               end loop;

               --  Print Details   <<< line  54

               Put_Line ("Name: " & To_String (Self.Name));
               Put_Line ("Outstanding:" & Outstanding'Img);  --  line 57
            end Print_Owing;

         end Pkg;

A more interesting example is when we want to extract the code to print the details of the invoice. This code depends on one local variable and the parameter to Print_Owing. When we select lines 54 to 57 and extract it into a new `Print_Details` subprogram, we get the following result. GPS automatically decides which variables to extract, and whether they should become parameters of the new subprogram, or local variables. In the former case, it will also automatically decide whether to create `"in"`, `"out"` or `"in out"` parameters. If there is a single `"out"` parameter, it will automatically create a function rather than a procedure.

GPS will use, for the parameters, the same name that was used for the local variable. Very often, it will make sense to recompile the new version of the source, and then apply the `Rename Entity` refactoring to have more specific names for the parameters, or the `Name Parameters` refactoring so that the call to the new method uses named parameters to further clarify the code.

            ... code not shown

            procedure Print_Details
              (Self : Invoice'Class;
               Outstanding : Natural);
            --  Print Details

            -------------------
            -- Print_Details --
            -------------------

            procedure Print_Details
              (Self : Invoice'Class;
               Outstanding : Natural)
            is
            begin
               Put_Line ("Name: " & To_String (Self.Name));
               Put_Line ("Outstanding:" & Outstanding'Img);
            end Print_Details;

            procedure Print_Owing (Self : Invoice) is
               E : Order_Lists.Cursor := First (Self.Orders);
               Outstanding : Natural := 0;
               Each : Order;
            begin
               Print_Banner;

               --  Calculate Outstanding

               while Has_Element (E) loop
                  Each := Element (E);
                  Outstanding := Outstanding + Each.Get_Amount;
                  Next (E);
               end loop;

               Print_Details (Self, Outstanding);
            end Print_Owing;

Finally, we want to extract the code that computes the outstanding amount. When this code is moved, the variables `E` and `Each` become useless in `Print_Owing` and are moved into the new subprogram (which we will call `Get_Outstanding`. Here is the result of that last refactoring (the initial selection should include the blank lines before and after the code, to keep the resulting `Print_Owing` simpler). GPS will automatically ignore those blank lines.

            ... code not shown

            procedure Get_Outstanding (Outstanding : in out Natural);
            --  Calculate Outstanding

            ---------------------
            -- Get_Outstanding --
            ---------------------

            procedure Get_Outstanding (Outstanding : in out Natural) is
               E : Order_Lists.Cursor := First (Self.Orders);
               Each : Order;
            begin
               while Has_Element (E) loop
                  Each := Element (E);
                  Outstanding := Outstanding + Each.Get_Amount;
                  Next (E);
               end loop;
            end Get_Outstanding;

            procedure Print_Owing (Self : Invoice) is
               Outstanding : Natural := 0;
            begin
               Print_Banner;
               Get_Outstanding (Outstanding);
               Print_Details (Self, Outstanding);
            end Print_Owing;

Note that the final version of `Print_Owing` is not perfect. For instance, passing the initial value 0 to `Get_Outstanding` is useless, and in fact that should probably be a function with no parameter. But GPS already saves a lot of time and manipulation.

Finally, a word of caution: this refactoring does not check that you are giving a valid input. For instance, if the text you select includes a `declare` block, you should always include the full block, not just a part of it (or select text between `begin` and `end`). Likewise, GPS does not expect you to select any part of the variable declarations, just the code.