Showing posts with label refactoring. Show all posts
Showing posts with label refactoring. Show all posts

Tuesday, December 6, 2011

I See Dead Code

I recently removed a lot of dead code from our code base at work. There were several similar directories, dead files, and unused code within live files.

All this unused code was a major nuisance for over a year. It would cause grep to show too much information -- most of it meaningless -- and it would even cause your IDE to jump to the old unused class file, rather than the one you actually wanted.
People were afraid to remove this code for fear of breaking things. After all, it was tied to our main shared library that could cripple over 200 of our websites at once. But enough was enough. I decided to embark on removing it. I have a lot of experience with removing dead code but rarely this high of a risk and with so much of it. I had only one day to figure it out, delete it, and test it. Otherwise, it would have to have be rolled back, for who knows how long, until anyone would attempt to remove it again.

Before removing code like this, I ask myself if it's good code that we might want to start using soon - or even right now under a configuration option. If it's useful code I wouldn't just delete it. Rather, I'd extract it out into classes that could become part of our library.

In my case it really was all just useless code. Some programmer from long ago had been a little too happy to copy and paste code to new files, leaving the old dead files lying around.

Some other code was living in live files but was effectively in a "dead block" because it would only happen under a condition that would never happen. I decided to remove this as well because such code will only confuse programmers every time they see it. In fact, I was about to spend time converting it to work with our new platform. What a waste of time that would have been! Fortunately, dead code like this is often easy to determine as dead, because you go to refactor it and can't figure out how to get it to run under that condition from the user interface. This is why it's we shouldn't rely solely on unit tests.

A coworker suggested that I keep the condition in the code at first, replacing the body of useless code with code to log a message such as "This condition should never happen." This way we could monitor the logs once it was deployed for a while, and really make sure the code wasn't being used by anything.

The easiest dead code to spot is commented-out code. Go ahead and remove this any time you see this. It will only sit there forever, no one will ever uncomment it, and if they really do need it that's what source control is for.

For those times that it's hard to tell if code is dead or not, the main tool I used to figure it out is grep. More specifically I use ack-grep. The key is to trust your grepping abilities and be extremely thorough. If you can prove that certain code isn't being referenced via other files, that's a huge step toward having the confidence to remove it.

Of course, it's not always that easy. Sometimes the code is referenced but that's okay; replacing it to use the proper files instead is certainly worth the effort.

Once you have removed the code, and all is working in your development sandbox, it's time to deploy it. It really isn't much different than writing new code or refactoring existing live code. Any time you commit code it could cause other things to break. This is why we do regression testing.

I highly recommend that you wait to push this kind of change out when there's no other important code that needs to be deployed too. If the "new" code is found to not be in a working state, then your code removal attempt will be the first source of blame and you will likely have to rollback and risk never getting to try again -- even if it wasn't the problem.

So go ahead and remove that dead code the next time you see it. You'll thank yourself later and so will your coworkers.

Friday, September 16, 2011

Refactoring with Vim Macros

Automation is what computers do best but we often forget to take advantage of this fact. I don't know about you but I love to take shortcuts when it gets me to the same place. In practice though, it often takes a little extra work up front, but the small amount of effort comprises the majority of the end result.

There are some refactoring commands that you may wish to repeat in the Vim editor but don't want to make a permanent mapping for because they're only needed temporarily for the current file. And a complicated regular expression would take you too long to get right - especially with Vim's non-standard regex engine - and you may not feel it safe to run even if you did manage to figure it out.

Fortunately, Vim also supports macros. Knowing just the Vim commands that you already know, plus a couple of commands to record and play them, you can create powerful macros with very little effort.

To record a macro in Vim, type qq to begin recording, followed by the series of commands to record, then "q" to stop recording. You then type @q to execute the recorded macro.

Let's start with a very simple example. Say you have a file with a bunch of lines:

    line1
    line2
    line3
    line4
    ...

and you want to change the first letter of each line to be a capital letter. You might normally do this all manually by going to the beginning of each line and typing ~ (tilde in vim toggles the case of the character under the cursor). Then you might type j repeatedly to scroll down to each line and then type . to repeat the command. I've seen people do stuff like this on lines with 50+ lines and they just sit there like a robot typing the same thing over and over. Well, it doesn't have to be that way. Instead, you could just go to line1 and type:

    qq0~j<ESC>q

That will run the command on just the current line number and position you to the next line so you can easily run the macro. Note that the 0 (zero) I added to the macro is there to jump to the beginning of the line so we always only capitalize the first letter. Now you're ready to repeat the macro on the rest of the lines by typing:

    3@q

The '3' is for the number of times to repeat the macro.

As another example say you need to edit an PHP file that has a bunch of ugly, yet similar, lines like:

    if (@$FOO) someFunction($foo);

and you want to refactor these lines to be like:

    if (isset($foo) && !empty($foo)) {
      someFunction($foo);
    }


to do this, you could type the following (Don't worry if you don't understand it yet, I'll break it down in a minute):

  0f@xyeiisset(<ESC>f)i) && !empty(<ESC>pa)<ESC>la {<ENTER><ESC>o}<ESC>

so now all you need to do to this record it is add two q's to the beginning and one q to the end, like so:

    qq0f@xyeiisset(<ESC>f)i) && !empty(<ESC>pa)<ESC>la {<ENTER><ESC>o}<ESC>q

Now you can put your cursor anywhere on a line containing the if statement you want to refactor and type @q and it will run the macro. To repeat the macro, go to another line and type @@.

Okay, let's break down the command. It may need to be tweaked depending on your Vim setup, but on my set up it works like this:

    qq begins recording
    0 jumps to the beginning of the line
    f@ jumps to the @ sign.
    x deletes the @ sign
    ye yanks from the cursor to the end of the word (copies $foo)
    i puts you in INSERT mode
    isset( inserts the text "isset("
    <ESC> escapes you back to normal mode
    f) jumps to to the closing parenthesis
    i) && !empty( inserts the text ") && !empty("
    <ESC> escapes you back to normal mode
    p pastes the text "$foo"
    a) inserts the closing parenthesis
    <ESC> escapes you back to normal mode
    la moves your cursor passed the ")" and into insert mode
    { inserts the opening brace
    <ENTER< inserts a newline, pushing "somefunc($foo)" to the next line
    <ESC> escapes you back to normal mode
    o jumps your cursor to the next line and puts you in insert mode
    } inserts a closing brace (which should auto-indent depending on your setup)
    <ESC> escapes you back to normal mode
    q ends recording

This may seem intimidating on first glance and make you think it's more trouble than it's worth but the beauty of Vim is how quickly you can type commands. You just need to try it a few times to get the hang of it, trust me.

In the above examples I used qq to begin recording. This is just a convention for single macros. You can actually store multiple macros at the same time by storing them in different registers. For example qa begins recording to the register a, which you can then run with @a. You could then create another macro with qb and run with @b, and so on.

You can even run a macro on a specific range of lines, say 20 to 30, by doing:

    :20,30norm! @q

Similarly, you could visually select just the lines you want to run the macro on and then type"

    :norm @q

If you remember this simple strategy of recording each series of commands you plan to repeat, then you're golden. If you're going to have to type the commands either way, then recording them is very little effort compared to manually typing the commands over and over.

Followers