Keep Your Code DRY

Summary

Don’t Repeat Yourself (DRY) - avoid writing the same logic more than once.

Every time you copy and paste code, flick yourself in the eye. This is a great disincentive to doing it again but over time may cause blindness.

Details

If the same logic is required more than once then it should not be duplicated; it should instead, be extracted to a well named class or method.

This will be both easier to read and easier to maintain because a change will only be required in one place should the logic need to change.

Bad

  1. class Foo {
  2. private int status;
  3. private boolean approved;
  4. foo() {
  5. if (status == 12 || approved) {
  6. doFoo();
  7. }
  8. }
  9. bar() {
  10. if (status == 12 || approved) {
  11. doBar();
  12. }
  13. }
  14. }

Better

  1. class Foo {
  2. private final static int PRE_APPROVED = 12;
  3. private int status;
  4. private boolean approved;
  5. foo() {
  6. if (isApproved()) {
  7. doFoo();
  8. }
  9. }
  10. bar() {
  11. if (isApproved()) {
  12. doBar();
  13. }
  14. }
  15. private isApproved() {
  16. return status == PRE_APPROVED || approved;
  17. }
  18. }

Things are a little trickier when we have similar but not identical logic.

Although it is quick and easy, the worst thing we can do is copy and paste.

Terrible

  1. public void doSomething(List<Widget> widgets) {
  2. for (Widget widget : widgets) {
  3. reportExistence(widget);
  4. if (widget.snortles() > 0) {
  5. reportDeviance(widget);
  6. performSideEffect(widget);
  7. }
  8. }
  9. }
  10. public void doSomethingSimilar(List<Widget> widgets) {
  11. for (Widget widget : widgets) {
  12. reportExistence(widget);
  13. if (widget.snortles() > 0) {
  14. reportDeviance(widget);
  15. performDifferentSideEffect(widget);
  16. }
  17. }
  18. }

This seemed quick and easy now, but is the start of a codebase that will suck time each time we try to understand or change it.

A straightforward but very limited approach to re-use code is to introduce Boolean flags.

Not great

  1. public void doSomething(List<Widget> widgets) {
  2. doThings(widgets, false);
  3. }
  4. public void doSomethingSimilar(List<Widget> widgets) {
  5. doThings(widgets, true);
  6. }
  7. private void doThings(List<Widget> widgets, boolean doDifferentSideEffect) {
  8. for (Widget widget : widgets) {
  9. reportExistence(widget);
  10. if (widget.snortles() > 0) {
  11. reportDeviance(widget);
  12. if (doDifferentSideEffect) {
  13. performDifferentSideEffect(widget);
  14. } else {
  15. performSideEffect(widget);
  16. }
  17. }
  18. }
  19. }

This is ugly and gets worse as the number of possibilities increases.

A much more scalable approach is to use the Strategy pattern.

If we introduce an interface:

  1. interface WidgetAction {
  2. void apply(Widget widget);
  3. }

Then we can use it as follows:

Better

  1. public void doSomething(List<Widget> widgets) {
  2. doThings(widgets, performSideEffect());
  3. }
  4. public void doSomethingSimilar(List<Widget> widgets) {
  5. doThings(widgets, performDifferentSideEffect());
  6. }
  7. private WidgetAction performSideEffect() {
  8. return new WidgetAction() {
  9. @Override
  10. public void apply(Widget widget) {
  11. performSideEffect(widget);
  12. }
  13. };
  14. }
  15. private WidgetAction performDifferentSideEffect() {
  16. return new WidgetAction() {
  17. @Override
  18. public void apply(Widget widget) {
  19. performDifferentSideEffect(widget);
  20. }
  21. };
  22. }
  23. private void doThings(List<Widget> widgets, WidgetAction action ) {
  24. for (Widget widget : widgets) {
  25. reportExistence(widget);
  26. if (widget.snortles() > 0) {
  27. reportDeviance(widget);
  28. action.apply(widget);
  29. }
  30. }
  31. }

The Java 7 version is quite verbose due to the anonymous inner class boiler plate.

Arguably, Boolean flags might be preferable for very simple cases such as this but, if we extract the logic in performSideEffect and performDifferentSideEffect methods into top-level classes implementing WidgetAction, then the Strategy version becomes compelling.

In Java 8, there is little question that the Strategy pattern is preferable in even the simplest of cases.

Better with Java 8

  1. public void doSomething(List<Widget> widgets) {
  2. doThings(widgets, widget -> performSideEffect(widget));
  3. }
  4. public void doSomethingSimilar(List<Widget> widgets) {
  5. doThings(widgets, widget -> performDifferentSideEffect(widget));
  6. }
  7. private void doThings(List<Widget> widgets, Consumer<Widget> action ) {
  8. for (Widget widget : widgets) {
  9. reportExistence(widget);
  10. if (widget.snortles() > 0) {
  11. reportDeviance(widget);
  12. action.accept(widget);
  13. }
  14. }
  15. }

We do not need to introduce our own interface - the built-in Consumer<T> is enough. We should consider introducing one if the doThings method were exposed publicly or if the logic in performSideEffect was complex enough to pull into a top-level class.

The loop might also be converted to a pipeline.

As a pipeline

  1. private void doThings(List<Widget> widgets, Consumer<Widget> action ) {
  2. widgets
  3. .stream()
  4. .peek(widget -> reportExistence(widget))
  5. .filter(widget -> widget.snortles() > 0)
  6. .peek(widget -> reportDeviance(widget))
  7. .forEach(action);
  8. }