» Refactoring: A quick overview

Refactoring: A quick overview

Hi! As promised, here I am again with another “basic concepts for coding lovers” thing. Before proceeding, I’d like to say thank you to all the people that read my last post and gave me feedback, because you help me very much during my learning process. :)

Well, after this short but certainly emotive declaration, we’re getting into the matter. Today we’ll learn about refactor. The knowledge for this post proceeds from this book, this site and this site, then I applied my own experience to filter relevant data and make a very basic and summarized guide. Don’t expect to find here all the code smells or refactor techniques that exist, because it is not the purpose of this post.

If you really want to learn how to refactor, you should absolutely read “Refactoring: Improving the Design of Existing Code”, by Martin Fowler (and follow him everywhere). Also I’d like to make some altruistic advertising for these guys, who are regularly making courses and dojos to spread the word of divinity code, amen.

Index


Why to refactor

“If it ain’t broke, don’t fix it”. The program may not be broken, but it does hurt. (M.F.)

I’ve heard this words so many times, and they’ve always upset me. I think they’re the most irritating words along with “It has always been done this way”. So what.

When a code is dirty, it becomes a nightmare for all the programmers working with it, because it is difficult to understand, adverse to changes, and hides bugs like hell.

Clean code, instead, is made to be read by humans and to help them doing their work, either applying changes or finding bugs. A good code is also reusable.

Refactoring is like arranging a chaotic garage. Imagine it, so full of paint buckets, broken toys and scrap, that you can’t even park your car in it. So you roll up your sleeves and start hanging the tools on the wall and getting rid of unused stuff until everything is clean and tidy.

Now you can use your garage and the objects that are on it, you can quickly find the hammer in the wall and the hose in the storage boxes. Besides, you got empty space to add new features, like parking a car or setting up a worbench. You even recovered forgotten toys so you won’t need to buy new ones for your kids.

And most important, now everyone, including you, loves that garage and enjoys working on it, and will probably make an effort to keep it clean because a rag thrown in the middle of your tidy garage would be immediately spotted.

So yeah, a chaotic garage may still be working as a garage, but eveyone prefers your zen garage.


Tips

There are some tips to apply when refactoring.

Code must work

First of all, code must work correctly before starting the process. Otherwise you won’t know if a failure is due to a known bug or it is a side effect of your refactor.

First refactor, then add

This rule is explained by the metaphor of two hats, by Kent Beck:

Whenever you are programming, you can wear one of these two hats:
The add hat allows you to add new functionalities and to write tests.
The refactoring hat is dedicated to restructure the code and run the tests.

IMG_20160813_174929

You can only wear one hat at a time, and you should always be conscious of what hat you are wearing at the moment. Meaning, do not start refactoring if you have not finished adding new logic, and do not stop refactoring whenever you think of a new feature. Just write it down and finish the refactor, then come back to it.

Be always supported by tests

You should create a test suite with which you feel enought comfortable and confident to proceed with a refactor. The tests must be written while wearing the add hat, and ideally generated with TDD, which is a design technique I would like to write about in the future. (Meanwhile you can read this).

Your tests must cover all the parts of the code that will be affected (and potentially broken) by your refactor, either directly or collaterally. Once you have them, start the refactor and throw all the tests each time you take a step. This way you monitor the changes on the code and immediately know if your last change broke your code or not.

Baby steps

Don’t make huge changes at once. Try to go little by little, making steps as small as possible, and always throwing tests after each step. This will minimize the area affected by your last change and it will become easier for you to spot the bug in case tests fail.

This does not mean that you can’t make big changes to your code. It means that big changes must be splitted into small changes and be regularly tested during the refactoring process.

 


Bad smells

If it stinks, change it (M. F.)

Code smells are symptoms that makes us suspect that our code needs a refactor treatment. So what we’re about to do is learn how to recognize code smells, and later we will speak about how to deal with them.

Speaking with a colleague recently, I was asked if a code smell is a “problem” on the code. Well, I wouldn’t say it is exactly a problem, but more like a warning. Sometimes we even introduce them on purpose to highlight something. So don’t start mass killing bad smells: you have to be reflexive and proceed wisely.


Long method / Large class

A very long method or a large class is probably taking  way more responsibilities than it should, but the worst thing about them is that they are completely unmanageable.

There are several opinions about how long should a method be  to be considered “too long”. Usually, 10 lines or the space that you can see in your IDE without scrolling.

For classes, it’s hard to say. Probably that file with 500 lines of code is stinking like a rotten fish.

No shortcuts: The best way to know if a method or a class is too big, is to inspect it and determine if it is rightly in charge of all those functions. After doing this, check the code itself, because it may contain several smells that are making it so long, like duplicated or unused code.


Data clumps

Those are groups of fields that are related and dedicated to the same function. You’d possibly recognize them because of their names:

This set of fields is obviously dedicated to manage the race of the kitten.

Sometimes you could find data clumps preceded by comments or separated somehow from the rest of the code, which should make your spider sense tingle:

Eugh.


Switch statements

Not only switches, also eternal if/elses and similar structures are considered smells, because this is not how object-orientation works. We’ll see an example later.


Refused bequest

This happens when a subclass does not use all the methods inherited from its father. It is a symptom of that either the class is incorrectly being considered a subtype of that superclass, or the superclass is hanging methods that would better belong to another interface:

IMG_20160813_201728


Shotgun surgery

When making a single change requires that you perform many small changes to many different classes. This could mean that your classes are coupled to each other.

IMG_20160812_172313


Comments

A code that needs a lot of explanatory comments is therefore an incomprehensible code. And, as we said before, the code must be written for human readers. Ideally, it should be read as prose.  So instead of:

Prefer this:

Zero comments, 100% understandable.


Duplicated code

If you need a functionality again, reuse it. Please stop copypasting.

Sometimes you may duplicate code on purpose because you are not sure where should that code be encapsulated, or because you really don’t know how will it look like when you finish the feature. In that case, wait until it is repeated three times, then start considering encapsulation. This technique is know as The Rule of Three.


Lazy class, dead code and speculative generality

Different names to call the same thing: unused code. If a piece of code is not enought meaningful or you are not using it or you don’t need it right now, get rid of it.

Don’t write code for future eventualities, like “yeah, well, let me create this interface called Poisonous just in case someday I want to add snakes to my game ’cause ya know”.

As my C teacher used to say, “good programmers are lazy programmers”.


Feature envy

When a method or class is continously accessing the data of another object, it would probably be happier having that data within it.


Middle man

A class whose only purpose consists in delegating to another class:

This class has no reason for existence.

Refactorings

Ok, so now that we’re done with code smells, it’s time to get into refactorings. I must warn you that refactoring is not an exact science. Each scene needs a personalized analysis and specific treatment. So I’m providing the tools, but yours is the duty of guessing which tool suits better your situation.


Extract method

Replace a piece of code by a method call:

This is useful not only to lighten up the code, but also to make it more meaningful, because if you gave a good name to the extracted method, you know what’s going on there just reading its call.

There’s a similar result when using extract variable.

Then, in the line of extract variable, we have Replace magic number with symbolic constant, which is used to hide a hardcoded number behind a constant that has a self-explanatory name:


Decompose conditional

This technique consists in replacing complex conditionals into understandable method calls, like this:

IMG_20160814_164645


Extract class

Although the procedure is similar to extract method, there is an extra benefit, because this refactor allows you to disengage a functionality from a class that was not responsible of it.


Extract superclass / interface

Create shared superclasses and interfaces for classes that have common fields and methods.


Replace method with method object

When it is not possible to extract method because you have plenty of local variables that are mixed together, extract the method to a class and transform these vars to fields:


Move method

Move method and move field consist in moving a method or a field from one class to another. This could be a solution for feature envies.


Hide delegate / Remove middle man

If we take back our example:

MiddleCat is hiding kitten’s methods, meaning, a client that uses this class will never know about Kitten‘s methods because it will be forced to access them through MiddleCat.

This technique, called hide delegate, is useful and used in other cases, but not in this one.

Remove middle man deletes the pointless intermediate class, exposing the final methods directly to the client. In this example MiddleCat is beseeching to die, so we should apply Remove middle man for mercy.


Encapsulate field

Convert a public field to private and provide public methods to work with it.

The most interesting example of this refactor happens when we are working with collections, because it is kind of reckless allowing everyone to play with such a delicate data structure. So instead of exposing it, make it private and create methods for specific operations:

This way you have better control over how your collections (and all kind of fields) are used from the outside.


Hide method

Convert a public method to private or protected.

When programming a new class, I use to set the minimum visibility to its methods, and then relax this as external objects needs to access them. This way I make sure that there’s no indecorous access to my shy resources.


Consolidate conditional expression

Both consolidatre conditional expression and consolidate duplicated conditional fragments are reoganization of conditionals.

If several conditionals lead to the same result, unify them and write the result only once:

If all the conditionals share the same code, move it outside the conditional so everyone can reuse it without duplication:


Replace conditional with polymorphism

This is my favourite one. Imagine you have a conditional such as:

The main problem of this structure is that you will have to add cases each time you want to implement the caress for an animal, and as the game grows, this piece of code will become unmaintainable.

Polymorphism consist in creating classes with common interfaces, and delegating in these classes the responsibility of implementation. So for the example above, we first create classes for each branch of the switch, and a shared interface:

And then we replace the whole conditional by a method call:

IMG_20160814_203449Lovely, elegant and fluffy as a stuffed kitten.


Pull ups and push downs

We say pull up when a field, a method or a constructor is moved from a subclass to a superclass, and push up when a field or a method is moved back from a superclass to a subclass.

 

Conclusion

Knowledge and, especially, practical experience, are the best way to learn. But after all, refactor is a matter of hunches.

In my next post I’ll be speaking about design patterns, which are just a deeper level on the eternal wisdom of refactoring.

4 Comments on Refactoring: A quick overview

  1. Joan
    September 22, 2016 at 9:55 am (12 months ago)

    Muy buen trabajo, enhorabuena. Con ganas de que lleguen nuevas entregas.

    Reply
    • nyandev
      December 10, 2016 at 10:06 am (9 months ago)

      Gracias Joan!

      Reply
  2. Pedro
    August 16, 2016 at 6:50 pm (1 year ago)

    Great Job!!!

    Reply
    • nyandev
      December 10, 2016 at 10:07 am (9 months ago)

      Thanks Pedro :)

      Reply

Leave a Reply