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.