Chapter 10. Refactoring

WHAT'S IN THIS CHAPTER?

  • Renaming classes, types, methods, functions, and variables

  • Reorganizing code and restructuring classes

  • Modernizing your code

Despite our best efforts, class and data structures don't always endure. New features, requirements, operating system evolution, and improvements elsewhere in your application may cause you to revisit the design of your existing class and data architecture. You may need to reorganize the hierarchy of your classes, migrate functionality from one class to another, take advantage of new language features, or simply rename a variable so that it more accurately reflects its purpose. These kinds of changes are referred to as refactoring your code.

Making structural changes to your code using the editor and the search and replace tools can often be tedious, time consuming, and error prone. In the days before refactoring, I would often rename or relocate an instance variable by simply changing its declaration, and then hunting down and correcting all of the compilation errors that resulted — not the most productive way to spend an afternoon.

Fortunately, Xcode now provides a refactoring tool that helps you make many common structural changes to your application, quickly and succinctly. It uses the Code Sense database and an inherent knowledge of C and Objective-C syntax to intelligently rename symbols, reorganize classes, and streamline code. This chapter describes the kinds of changes the refactoring tool performs and the steps required.

Refactoring is formally defined as a change to the internal structure of your program that doesn't change its external, or functional, behavior. The reality is that any change, no matter how careful, can have unintended consequences. The section "Refactoring Pitfalls" discusses some of these potential problems, how to address them, and some best practices.

REFACTORING WORKFLOW

To refactor something in your project, follow these general steps:

  1. Select the symbol or code fragment to refactor.

  2. Choose the Edit

    REFACTORING WORKFLOW
  3. Select the type of refactoring to perform, provide whatever additional information is required, and choose options.

  4. Preview the refactoring.

  5. Commit the refactoring.

The process begins by selecting the specific symbol name or code fragment in the editor that represents what you want to refactor. Once selected, choose the Edit

REFACTORING WORKFLOW
FIGURE 10-1

Figure 10-1. FIGURE 10-1

The refactor tool determines which refactoring methods, called transformations, can be applied to the selected text. Select your desired transformation from the pop-up menu on the left, as shown in Figure 10-1. What transformations are available will depend entirely on the nature and context of the selection. Xcode offers to rename or create a superclass if a class name is selected. A selected block of code could be extracted or possibly modernized.

Warning

Refactoring uses the Code Sense index. This means that Code Sense must be enabled, up-to-date, and it must be able to make sense of your code. As a general rule, don't refactor code that won't compile — especially code that contains syntax errors that might prevent Code Sense from interpreting its meaning. See Chapter 7 if you need to turn Code Sense on or reindex your project.

Once the transformation is chosen, supply whatever additional information, if any, the transformation requires. For a rename transformation, you'll need to supply the symbol's new name. Other transformations might require several pieces of information.

Some transformations present options, typically used to control what additional, non-code changes are made. For example, renaming a class presents two options: one will also rename KVC references to that class found in Interface Builder documents and Core Data models. The other option will automatically rename the source files that contain the class's definition so that they continue to match the class name.

After you've prepared the transformation, start the process by clicking the Preview button. The process scans your entire project and prepares a list of proposed changes. At this point no changes have actually been made. You can preview the changes using the differences browser, as shown in Figure 10-2. It may also present a list of warnings that should be considered.

FIGURE 10-2

Figure 10-2. FIGURE 10-2

You can elect which of the changes you're going to allow the refactoring tool to make using the check boxes next to each file in the list. Uncheck a file, and Xcode will not make any of the proposed changes contained therein.

You also have the option of an added safety net, in the form of a project snapshot. Checking the Snapshot option makes a snapshot of your entire project before making any of the proposed changes. This also has the advantage of being able to selectively reverse unwanted changes on a change-by-change basis. See Chapter 11 for more about snapshots.

Once you've defined and previewed the transformation, click the Apply button to commit the changes. When finished, the Refactor window closes and returns you to your (now modified) project.

C AND OBJECTIVE-C TRANSFORMATIONS

As of this writing, the refactoring tool only refactors C and Objective-C source code; C++ and Java programmers are just out of luck. The next few sections describe the specific transformations that the refactoring tool provides, along with some examples.

Rename

The rename transformation is the simplest and most flexible. It will rename any of the following:

  • Global, instance, parameter, and automatic variable names

  • Function names

  • Typdef, structure tag, and enum names

  • Class names

  • Method signatures

To rename a symbol, follow these steps:

  1. Select a symbol name or at least one token of an Objective-C message name.

  2. Choose the Edit

    Rename
  3. Select the Rename transformation.

  4. Supply a replacement name and choose any additional option.

All of the rename transformations occur within the symbol's scope. For example, renaming an integer variable from c to i, as shown in Figure 10-3, only renames references that refer to the selected variable. Refactor is not confused by the unichar c variable, even though it is nested within the block of code where int c is declared. In this respect, refactoring is more intelligent than the Edit All in Scope command described in the "Editing Symbol Names" section of Chapter 7. Edit All in Scope only analyzes the textual context, and would have renamed both c variables to i.

FIGURE 10-3

Figure 10-3. FIGURE 10-3

Renaming method signatures is particularly accurate and flexible. For example, when renaming an Objective-C method name, Xcode picks up the entire method signature and asks that you supply a replacement. You can change any, or all, of the method name tokens and the refactoring tool will make the appropriate changes in the code. In the example shown in Figure 10-4, the method scheduleDate:time:room: is being renamed to scheduleStartingAt:duration:room:. The replacement method name must match the number and order of the original arguments.

FIGURE 10-4

Figure 10-4. FIGURE 10-4

When renaming a class, you are given the additional option of renaming any source files with the same name. By tradition, the files that define the Hue class are named Hue.h and Hue.m. Renaming the Hue class to Color would also rename the files to Color.h and Color.m, and update any #include "Hue.h" and #import "Hue.m" statements to match.

The Rename Related KVC/Core Data Members option extends the refactoring beyond the scope of your source code. It scours NIB documents and Core Data models for any references to the class, method, or variable being remained. This option will update class inheritance, member names, outlet connections, actions, and bindings. If you have any NIB document or Core Data models that refer to your class, I highly recommend checking this option.

Extract

Reusable, modular code is the hallmark of efficient programming. Often, however, code that could be organized into its own method or function is originally embedded in a larger function — often because you don't realize its utility until later. Take the situation shown in Listing 10-1. Here, the programmer is writing a new rescheduleStartingAt:duration: method, when she realizes that the loop she is about to write is identical to the one in scheduleStartingAt:duration:inRoom:.

Example 10-1. Repetitious code

- (void)scheduleStartingAt:(NSDate*)date
                  duration:(NSTimeInterval)minutes
                    inRoom:(RoomIdentifier)roomID
{
    start = date;
    duration = minutes*60.0;
    room = roomID;
    NSEnumerator *e = [participants objectEnumerator];
    Person *participant;
    while ( (participant=[e nextObject])!=nil ) {
        [participant updateCalendarEvent:self];
    }
}

- (void)rescheduleStartingAt:(NSDate*)date
                    duration:(NSTimeInterval)minutes
{
    start = date;
    duration = minutes*60.0;
    NSEnumerator *e =
}

Ideally, the loop that sends update events to all of the meeting participants should be isolated in its own method. Then, rescheduleStartingAt:duration: and scheduleStartingAt:duration:inRoom: can reuse the logic. This transformation is called an extraction.

To extract a block of code, follow these steps:

  1. Select the block of code you want to reuse.

  2. Choose the Edit

    Repetitious code
  3. Choose the Extract transformation.

  4. Decide whether you want the code to extract into a C function or an Objective-C method.

  5. Supply the name of the new function or method.

Figure 10-5 illustrates our programmer exacting the enumeration loop in Listing 10-1 into its own method.

The extract transformation lifts a local fragment of code and puts it into its own Objective-C method or C function (your choice). In the example shown in Figure 10-5, our programmer is extracting the enumeration loop into a new method named -(void)updateParticipants. The original code is replaced with a message that invokes the new method. After the extraction, the rescheduleStartingAt:duration: can now be completed with a single [self updateParticipants]; statement.

FIGURE 10-5

Figure 10-5. FIGURE 10-5

Note

The formatting of extracted code can be a bit erratic. After extraction, select the new method and reformat it by typing Control+I.

When extracting code, Xcode analyzes the scope of all of the variable references within the code block. Any variable that isn't in scope when the code is relocated to its own method (or function) is converted into a parameter. In the previous example, both the participants and self instance variables are still accessible in the new updateParticipants method. If you choose to extract this code into a C function, or the loop refers to any automatic variables declared earlier in the method, those references will be converted into parameters, as shown in Figure 10-6.

FIGURE 10-6

Figure 10-6. FIGURE 10-6

The function notify_participants(...) in Figure 10-6 has no object context, so the collection of participants and the receiving object were converted to parameters.

Encapsulate

The encapsulate transformation creates getter and setter methods for class instance variables. Figure 10-7 shows the tasks variable being encapsulated.

FIGURE 10-7

Figure 10-7. FIGURE 10-7

To encapsulate an instance variable, follow these steps:

  1. Select the name of the instance variable.

  2. Choose the Edit

    FIGURE 10-7
  3. Choose the Encapsulate transformation.

  4. Optionally edit the getter and setter method names. Xcode generates a matched pair of KVC-compliant method names, but you can change them if you want.

The encapsulate transformation generates a getter and setter method for the instance variable and then replaces any existing references to the variable with messages (that is, ivar with [object ivar] and ivar=... with [object setIvar:...]).

As of this writing, a number of unfortunate limitations keep the encapsulate transformation from being truly useful, except in a narrow set of circumstances. For the specifics, see the "Encapsulate" portion of the "Refactoring Pitfalls" section, later in this chapter.

Because of these issues, I recommend using the encapsulate transformation only when:

  • You are not using Objective-C 2.0.

  • You are using managed memory (not garbage collection).

  • You have just added an instance variable and want to create getter and setter methods for it, but have not yet used the variable in any code. If you've already used the variable in your code, you will need to carefully review the memory management of the object afterwards.

  • Your property does not need to be thread-safe.

In any other circumstances, I'd recommend using Objective-C @property and @synthesize directives instead. See the "Updating a Project to Objective-C 2.0" section later in this chapter.

Create Superclass

The create superclass transformation creates a new class definition and inserts it between an existing class and its original superclass. This is most useful when you need to create a more abstract superclass for an existing class whose functionality needs to be partitioned. To create a superclass, follow these steps:

  1. Select the name of the existing class.

  2. Choose the Edit

    Create Superclass
  3. Choose the Create Superclass transformation.

  4. Supply a new class name and choose whether the new class should be defined in the existing file or defined in a new set of source files with the same name.

Xcode changes the inheritance of the existing class so that it is now a subclass of the new class, which is a subclass of the original superclass. If you asked Xcode to generate new class files, those files are added to the project and appropriate #import directives are inserted into the original files, as shown in Figure 10-8.

FIGURE 10-8

Figure 10-8. FIGURE 10-8

In the hypothetical project management program, you decide that you need an intermediate class between the very abstract Event class and the very specific Meeting class. The create superclass transformation quickly defines a new ScheduledMeeting class, changes the superclass of the Meeting class, and then creates and adds all the necessary project files.

Move Up and Move Down

After applying a create superclass transformation, or simply creating a new subclass, you may want to migrate properties or functionality from a class to its superclass or vice versa. That's what the move up and move down transformations do.

To move an instance variable or method to a superclass or one of its subclasses, follow these steps:

  1. Select the instance variable declaration or method name.

  2. Choose the Edit

    Move Up and Move Down
  3. Choose either the Move Up or Move Down transformation.

  4. Select the desired options:

    1. If moving an instance variable up, you may also elect to move related methods — any method that refers to the variable — to the superclass.

    2. If moving down, the Refactor tool presents a list of the immediate subclasses of the class; choose which subclasses will get a copy of the variable or method.

Using the project management example again, the start and duration properties in the Meeting class need to be migrated to the new ScheduledEvent superclass, as shown in Figure 10-9.

FIGURE 10-9

Figure 10-9. FIGURE 10-9

This transformation is rarely without consequences; you must migrate the instance variables start and duration, along with any related methods, one at a time. This will also require that some of the methods be recoded, and you'll probably want to create an abstract scheduleStartingAt:duration: method in the new superclass. You could do this using two transformations: extract the code that sets the schedule in scheduleStartingAt:duration:inRoom: into its own method, then perform a move up transformation to migrate it to the ScheduledEvent superclass.

Warning

Like encapsulate, the move up and move down transformations know nothing about Objective-C 2.0 properties. If you move an instance variable up or down, you'll have to manually relocate any @property and @synthesize directives.

Modernize Loop

Objective-C introduces the much-appreciated fast enumeration syntax. Fast enumeration provides a succinct syntax for processing the objects in a collection that's both fast and robust. Prior to Objective-C 2.0, programmers used a variety of for and while loop patterns. The modernize loop transformation attempts to convert a legacy for or while loop into its equivalent using fast enumeration syntax.

To modernize a single loop, follow these steps:

  1. Select an entire for or while statement, including the statement or block of code that it controls. Do not include any statements before or after the loop, even if they include loop initialization.

  2. Choose the Edit

    Modernize Loop
  3. Choose the Modernize Loop transformation.

The process begins by selecting the entire for loop statement and block, as shown in Figure 10-10.

FIGURE 10-10

Figure 10-10. FIGURE 10-10

Loop modernization only transforms for loops that meet these criteria:

  • The loop iterates through the contents of an NSArray.

  • Uses the message [collection objectAtIndex:index] to obtain each object.

  • Starts with the first object in the collection.

  • Each object is processed in order, without skipping objects (this does not preclude exiting the loop early using a break statement).

  • Each iteration processes a single object, without trying to access any other objects in the collection at the same time.

When the modernize loop transformation updates a while loop, it replaces the traditional NSEnumerator pattern shown in Figure 10-11 with its modern equivalent.

FIGURE 10-11

Figure 10-11. FIGURE 10-11

Loop modernization only transforms while loops that meet these criteria:

  • The while loop iterates through the objects in a collection using an NSEnumerator object.

  • Uses the message [enumerator nextObject] to obtain each object.

  • Starts with the first object in the collection.

  • Processes each object, in order, without skipping objects (this does not preclude exiting the loop early using a break statement).

The second and third points mean that you can't use refactoring to modernize a loop that uses -reverseObjectEnumerator.

Note

An often-overlooked feature of fast enumeration is that the modern NSEnumerator class also conforms to NSFastEnumeration. The implication is that any enumerator object can be treated as the collection object in a fast enumeration statement. Consider the following code:

NSEnumerator* e = [collection reverseObjectEnumerator];
for ( id obj in e ) {
...
}

The enumerator acts as a proxy for its collection. The statement loops through the objects returned by the enumerator. This is particularly useful with enumerators that return a subset of a collection or process objects in a different order, like -[NSDictionary keysSortedByValueUsingSelector:].

It should be noted that this trick does not improve the performance of your application; a fast enumeration using an enumerator object isn't any faster than using the enumerator object directly. In other words, you don't get the "fast" part of fast enumeration; you just get the convenience of a concise syntax.

UPDATING A PROJECT TO OBJECTIVE-C 2.0

The Edit

UPDATING A PROJECT TO OBJECTIVE-C 2.0
  • Modernize Loops

  • Convert instance variables to properties

To update your entire project to Objective-C 2.0, follow these steps:

  1. Choose the Edit

    UPDATING A PROJECT TO OBJECTIVE-C 2.0
  2. Select either, or both, of the Modernize loops or Use properties transformations.

The modernize loops transformation is the same one described in the previous section, just applied globally to every Objective-C source file in your project.

The use properties transformation converts simple instance variables to Objective-C 2.0 properties. Is does this by

  • Adding a @property directive for each instance variable.

  • Adding a @synthesize directive for each instance variable that does not already have KVC-compliant getter and setter methods with the same name.

REFACTORING PITFALLS

As mentioned at the beginning of the chapter, any change to an application — no matter how seemingly inconsequential — can occasionally have profound effects on its behavior. Although the refactoring logic is quite intelligent, it isn't omniscient. It makes mechanical replacements that are reasonably expected to be functionally equivalent, but may not be.

Many of these pitfalls can be avoided by refactoring your application defensively and with the assumption that any transformation could introduce a bug. Try to follow these best practices when refactoring:

  1. Your project should build successfully and function correctly before beginning any refactoring.

  2. Make use of the refactoring preview; diligently review each proposed change before committing the transformation. Disable any changes that aren't appropriate.

  3. Always make a project snapshot. They are easy to discard later.

  4. After the transformation, make sure the project still builds. Fix any compilation errors that appear.

  5. If you have any unit tests defined, run them.

Each transformation carries some specific pitfalls. The next few sections highlight common issues that you're likely to encounter when refactoring.

Rename

  • Renaming a method only renames the occurrences of that method defined by a single class. If that method overrides an inherited method, the inherited method in the superclass is not renamed. If you need to rename an inherited method, you must rename the same method in all other classes.

  • Conversely, renaming a method could change it from a unique method to one that now overrides a method in its superclass.

Encapsulate

  • Encapsulate only generates getter and setter methods. It does not produce @property or @synthesize directives. If you're using Objective-C 2.0, just write @property or @synthesize directives instead of trying to use encapsulate.

  • The getter and setter methods assume that you are using traditional managed memory, not garbage collection. Though this will work in a garbage collection environment, it still generates more code than is optimal. Again, @property or @synthesize directives are a better solution. The @synthesize directive automatically adapts to your chosen memory management model.

  • Existing memory management is overlooked, which can create over-retain and over-release issues. For example, encapsulating the ivar variable will replace the two statements [ivar release]; ivar=nil; with the statements [[self ivar] release]; [self setIvar:nil];. This results in ivar being released twice: once in the code and again in the setter.

  • The visibility of the original instance variable remains unchanged. If it were @public before the transformation, it's still public afterwards. After the transformation, change the visibility of the instance variable to @protected or @private.

  • The setter method generated is not thread-safe.

Move Up

  • Moving an instance variable to its superclass does not migrate its @property or @synthesize directives.

  • The visibility of a variable is not retained. A @private variable could become @protected or @public.

  • If you migrate related methods, you could migrate methods that now refer to properties defined only in the subclass. This requires either migrating those other properties as well, or redesigning the methods.

  • The refactoring tool warns you if the superclass already has a variable or method with the same name, but it won't prevent you from applying the transformation. The result will be a class with duplicate methods.

  • You can only migrate a variable or method to its immediate superclass.

Move Down

  • Moving an instance variable to one of its subclasses does not migrate its @property or @synthesize directives.

  • The visibility of a variable is not retained. A @private variable could become @protected or @public.

  • The refactoring tool warns you if any of the subclasses already have a variable or method with the same name, but it won't prevent you from applying the transformation. The result will be classes with duplicate methods.

  • You can only migrate a variable or method to one or more of its immediate subclasses.

Modernize Loop

  • The modernize loop transformation is generally safe, because it simply refuses to be applied unless the control loop meets its rather strict prerequisites. It is still possible, however, to trick the refactoring tool into transforming a loop that contains abnormal behavior hidden in preprocessor macros.

Use Properties

  • The use properties transformation may produce @property directives whose attributes (that is, assign, retain, copy, nonatomic) do not agree with the implementation of the existing getter or setter methods. Review each new @property directive and confirm that your getter and setter fulfill the property contract, correcting one or the other as appropriate.

  • The transformation will not create @synthesize directives for properties that already have KVC getter and setter methods, even if the getter and setter methods use generic patterns. You may want to delete simplistic getter and setter methods before converting your project and let the transformation insert the more modern @synthesize directives.

  • Unlike the encapsulate transformation, use properties will not replace direct references to instance variables (ivar=...) with property "dot" syntax (self.ivar=...) or getter and setter messages ([self setIvar:...]). You will need to search your code for direct instance variable references and determine which should be replaced with property accessors.

  • No attempt is made to reduce the visibility of the existing instance variables. After the transformation, consider making the instance variables @private. This will also help identify direct references to the variable outside the class.

SUMMARY

Changing the structure of your application isn't always as simple as moving a definition or performing a global search and replace. The refactoring tool makes short work of many common code changes, quickly and as intelligently as possible. This doesn't relieve you of the need to confirm the veracity of those changes, but it does relieve you of much of the tedium involved in making them.

Before every transformation, the refactoring tool gives you the option of making a snapshot of your project. The next chapter explains what snapshots are and how to use them in other circumstances.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset