Initial Situation

You cannot easily understand a method.

Goal

Transform the method into several smaller steps each of rawly the same scope.

Improves

Long Method   

Difficulty

easy

Pros/Cons

Simplifies a method by splitting it up into small chunks rawly of the same scope.
Better communicates what a method does through its name.
Sometimes makes debugging difficult if logic is spread across many small methods.
Sometimes leads to an excessive number of small methods (consider the Extract Class refactoring in this case)
Setup Continuous Code Quality Management in minutes.

Refactoring Basics

Related Refactorings

Compose Method

Overview

Compose Method is a very basic pattern that should be in everyone’s refactoring arsenal. A composing method is nothing more than a method that calls out to other methods. It is best applied when all called methods have rawly the same level of detail.

Refactoring towards a composing method often involves extracting code from the original method. If you have a hard time naming the extracted methods. This is a good indication that the chunk of code you were about to extract was too big, try to find a smaller chunk in such a case. Often in longer methods, certain sections are already labeled with a comment; these sections can frequently be extracted into new methods.

If you find yourself with too many small private methods in your class after applying this refactoring, you might want to apply Extract Class next.

Guidelines

  • Smaller is preferable
  • Remove duplication: When refactoring to a composing method, try to reduce code duplication.
  • Communicate intention: Name all variables, methods, parameters so they clearly communicate their purpose.
  • Simplify
  • Same level of detail: All called methods should have the same level of detail. For example, calling a simple getter and a method performing a heavy computation is not ideal.

Example: Splitting up a Validator

Original Class

This class is a slighly adapted class from a real-world project. It performs validation of an image according to criteria which are passed as a parameter:

class ImageValidator
{
    public function validate($value, Constraint $constraint)
    {
        if (null === $value || '' === $value) {
            return;
        }

        if (null === $constraint->minWidth && null === $constraint->maxWidth) {
            return;
        }

        $size = @getimagesize($value);
        if (empty($size) || ($size[0] === 0) || ($size[1] === 0)) {
            $this->context->addViolation($constraint->sizeNotDetectedMessage);

            return;
        }

        $width  = $size[0];
        $height = $size[1];

        if ($constraint->minWidth) {
            if (!ctype_digit((string) $constraint->minWidth)) {
                throw new ConstraintDefinitionException(sprintf('"%s" is not a valid minimum width', $constraint->minWidth));
            }

            if ($width < $constraint->minWidth) {
                $this->context->addViolation($constraint->minWidthMessage, array(
                    '{{ width }}'    => $width,
                    '{{ min_width }}' => $constraint->minWidth
                ));

                return;
            }
        }

        if ($constraint->maxWidth) {
            if (!ctype_digit((string) $constraint->maxWidth)) {
                throw new ConstraintDefinitionException(sprintf('"%s" is not a valid maximum width', $constraint->maxWidth));
            }

            if ($width > $constraint->maxWidth) {
                $this->context->addViolation($constraint->maxWidthMessage, array(
                    '{{ width }}'    => $width,
                    '{{ max_width }}' => $constraint->maxWidth
                ));

                return;
            }
        }
    }
}

Creating a Composing Method

The current validate() method can be rawly composed into three different steps:

  1. Validating the input value’s type
  2. Validating the constraint
  3. Validating the image’s size
class ImageValidator
{
    public function validate($value, Constraint $constraint)
    {
        if ( ! $this->isInputTypeValid($value)) {
            return;
        }

        if ( ! $this->isConstraintValid($constraint)) {
            return;
        }

        $this->validateImageSize($value, $constraint);
    }

    private function isInputValid($value)
    {
        return $value !== null && $value !== '';
    }

    private function isConstraintValid(Constraint $constraint)
    {
        return $constraint->minWidth !== null || $constraint->maxWidth !== null;
    }

    private function validateImageSize($value, Constraint $constraint)
    {
        $size = @getimagesize($value);
        if (empty($size) || ($size[0] === 0) || ($size[1] === 0)) {
            $this->context->addViolation($constraint->sizeNotDetectedMessage);

            return;
        }

        // ...
    }
}

The validateImageSize is still quite big. We could go ahead an re-apply the Compose Method refactoring to it as well to split it up further.

How to use the Checklist?

Checklists present a proven order of necessary refactoring steps and are especially helpful for beginners.

If you feel comfortable with a refactoring, there is no need to follow each of the steps literally; you can often perform multiple steps at once or skip some.

We suggest to try the recommended order at least once; this will often provide new insights - even for seasoned developers.