You cannot easily understand a method.
Transform the method into several smaller steps each of rawly the same scope.
Long Method
easy
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
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; } } } }
The current validate()
method can be rawly composed into three different
steps:
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.
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.