Sunday, November 20, 2016

Refactoring to Patterns: About Patterns

In my previous post in this series about the book Refactoring to Patterns by Joshua Kirievsky, I talked about the concept of refactoring, which is about improving software code without changing the software's behavior.

Today, let's take a closer look at design patterns.

What is a pattern?

The author quotes Christopher Alexander, an architect whose books A Timeless Way of Building and A Pattern Language have inspired the software pattern movement: 
Each pattern is a three-part rule, which expresses a relation between a context, a problem and a solution.

As an element in the world, each pattern is a relationship between a certain context, a certain system of forces which occurs repeatedly in that context, and a certain spatial configuration which allows these forces to resolve themselves.

As an element of language, a pattern is an instruction, which shows how this spatial configuration can be used, over and over again, to resolve the given system of forces, wherever the context makes it relevant.

The pattern is, in short, at the same time a thing, which happens in the world, and the rule which tells us how to create that thing, and when we must create it. It is both a process and a thing; both a description of a thing which is alive, and a description of the process which will generate that thing.
Software patterns appear as part of catalogs of individual patterns, and should not be viewed as stand-alone prescriptions, but rather in conjunction with other, alternative patterns.

Patterns Happy

"Patterns-happy" programmers tend to overuse patterns. They are so much in love with patterns that they apply them regardless of whether their use is justified, making code unnecessarily complex instead of simplifying it. They simply must use patterns in their code.

When learning patterns it is hard to avoid being patterns-happy. The true joy of patterns comes from using them wisely. Refactoring helps us to that by focusing our attention on removing duplication, simplifying code and making code communicate its intention. Evolving systems through refactoring makes over-engineering with patterns less likely.

There Are Many Ways to Implement a Pattern

The famous book Design Patterns by Erich Gamma et al begins the discussion of each pattern with a structure diagram. It is important to realize that this structure diagram is just an example and that there are multiple implementations of the pattern possible, depending on the need at hand. Alternative implementations are often discussed in the implementation notes. But all too often a programmer looks at the diagram and begins coding, assuming that the diagram is the way to implement the pattern.

Deviating from the standard implementation is inevitable and in fact desirable.

The evolutionary approach to software design often leads to minimalistic pattern implementations, which are simpler than classical pattern definitions, because they involve implementing only what is necessary. This is the approach used throughout this book.

Refactoring to, towards and away from Patterns

Depending on the nature of a pattern, one can refactor to the pattern, towards it and even away from it. For some patterns, like Composed Method or Template Method, one has to refactor all the way to the pattern, it does not make sense to refactor half way towards it. For other patterns, it is sometimes sufficient to improve the design by refactoring towards them even if you do not go all the way.

If after the refactoring to a pattern, you feel that your design has not improved enough, you can decide to refactor away from this pattern to another pattern.

The goal is to obtain a better design, not to implement patterns!

Do Patterns Make Code More Complex?

In general, pattern implementations ought to remove duplicate code, simplify logic, better communicate intention, and increase flexibility. Yet, people's familiarity with patterns plays a major role in how they perceive refactoring to patterns. It's better that team learn patterns rather than avoid using them, because the teams view patterns as being to complex.

On the other hand, some patterns implementation can make code unnecessarily more complex; in that case, backtracking or more refactoring is needed.

Pattern Knowledge

Patterns capture wisdom. Reusing that wisdom is extremely useful.

Knowing patterns is not enough to evolve great software, you must also know how to intelligently use patterns. Yet, if you don't study patterns, you'll lack access to important, even beautiful, design ideas.

A good way to learn patterns is to choose  great pattern books to study and then study one pattern a week in a study group. Meeting and discussing important design ideas each week is a great way to become better software designers.

Advice: only read the great books!

Up-Front Design With Patterns

The author prefers to evolve a system, refactoring to, towards or away from patterns as necessary. Up-front design with patterns has some place in a designer's toolkit, but use it rarely and most judiciously.

Saturday, November 19, 2016

Refactoring to Patterns: About Refactoring

I am reading this pretty old (2004) book by Joshua Kirievsky called Refactoring to Patterns, which I would like to study deeper and share what I learn with others.

The book, as suggested by its title, combines two key concepts in software development: Refactoring and Design Patterns. In fact, the book argues that great software designs are better understood and learnt not as stand-alone masterpieces of software craftsmanship, but by studying how they emerge through refactoring.

What is Refactoring?

Refactoring is behavior-preserving transformation, "a change made to the internal structure of software to make it easier to understand and cheaper to modify without changing its observable behavior." [Martin Fowler]

Refactoring involves
  • removing duplication
  • simplifying complex logic
  • and clarifying unclear code.
To refactor safely and with courage, you need a set of automated tests that you can run quickly to confirm that your code still works.

Refactoring should be done in small steps that take seconds or minutes.

It's better to refactor continuously, rather than in phases. If you see code that needs improvement, improve it. If you need to implement an important feature by tomorrow, first finish the feature and refactor later. Refactoring must co-exist harmoniously with business priorities.

Refactoring Motivations

  • Make it easier to add new code: when adding a new feature, we can use two approaches, none of which is right or wrong:
    • either code it quickly without regard to how well it fits with an existing design, refactor later;
    • or modify the existing design so it can easily and gracefully accommodate the new feature.
  • Improve the design of existing code: sniff constantly for code smells and remove smells immediately (or soon) after finding them. This is a great hygienic habit. It can also lead to greater job enjoyment.
  • Gain a better understanding of code: If some code is not clear, it's an odor that needs to be removed by refactoring, not by deodorizing the code with a comment.
  • Make coding less annoying: We often refactor simply to make code less annoying to work with.

Many Eyes

To get best refactoring results, you'll want the help of many eyes, which is one of the reasons for the practices of pair-programming and collective code ownership.

Human-Readable Code

Good code 
  • reads like spoken language
  • separates important code from distracting code
"Any fool can write code that a computer can understand. Good programmers write code that humans can understand." [Martin Fowler]

Keeping Code Clean

Refactoring is a lot like cleaning your room. The worse the mess becomes, the harder it is to clean and the less you want to clean it. One giant cleanup is not enough, you must practice continuous hygiene. To keep code clean, we must continuously:
  • remove duplication
  • simplify code
  • clarify code
Do not tolerate messes in code, and do not backslide into bad habits.

Clean code -> Better design -> Faster development -> Happy customers and programmers 

Small Steps

Take very small steps and keep the unit tests green! They should not stay red for more than a few minutes.

Design Debt

Design debt is a much better metaphor to communicate with management than the technical language of refactoring. It occurs when you do not consistently do 3 things:
  1. Remove duplication
  2. Simplify your code
  3. Clarify your code's intent
When do you pay back the debt? In financial terms, when you don't pay your debt, you incur late fees. If you don't pay your late fees, you incur higher late fees, and so on. Compound interest kicks in, and going out of debt becomes an impossible dream. So it is with design debt.

Evolving a New Architecture 

Evolutionary design suggest that you:
  • Form one team
  • Drive the framework from application needs
  • Continuously improve applications and the framework by refactoring

Composite and Test-Driven Refactorings

Composite refactorings are high-level refactorings composed of low-level refactorings. Between applying low-level refactorings you run unit tests.

Test-driven refactorings involove applying TDD to produce replacement code and then swap out old code for new code (while retaining and rerunning the old code's tests).
When it's impossible to evolve a design through composite refactorings, test-driven refactorings can be used to produce a better design. Substitute Algorithm is a good example of a test-driven refactoring.

Benefits of composite refactorings:
  • They describe an overall path for a refactoring sequence
  • They suggest non-obvious design directions
  • They provide insights into implementing patterns