CodeGym/Java Blog/Design Patterns in Java/What are anti-patterns? Let's look at some examples (Part...
Konstantin
Level 36
Odesa

What are anti-patterns? Let's look at some examples (Part 2)

Published in the Design Patterns in Java group
members
What are anti-patterns? Let's look at some examples (Part 1) Today we continue our review of the most popular anti-patterns. If you missed the first part, here it is. What are anti-patterns? Let's look at some examples (Part 2) - 1So, design patterns are best practices. In other words, they are examples of good, time-tested ways of solving specific problems. In turn, anti-patterns are their exact opposite, in the sense that they are patterns of pitfalls or mistakes when solving various problems (evil patterns). Let's proceed to the next software development anti-pattern.

8. Golden hammer

A golden hammer is an anti-pattern defined by confidence that a particular solution is universally applicable. Examples:
  1. After encountering a problem and finding a pattern for the perfect solution, a programmer tries to stick this pattern everywhere, applying it to current and all future projects, instead of looking for the appropriate solutions for specific cases.

  2. Some developers once created their own variant of a cache for a specific situation (because nothing else was suitable). Later, on the next project which involved no special cache logic, they used their variant again instead of using ready-made libraries (for example, Ehcache). The result was a bunch of bugs and incompatibilities, as well as a lot of wasted time and fried nerves.

    Anybody can fall for this anti-pattern. If you're a beginner, you may not be knowledgeable about design patterns. This may lead you to try to solve all problems in the one way you have mastered. If we're talking about professionals, then we call this professional deformation or nerdview. You have your own preferred design patterns, and instead of using the right one, you use your favorite, assuming that a good fit in the past guarantees the same result in the future.

    This pitfall can produce very sad results — from a bad, unstable, and difficult to maintain implementation to a complete failure of the project. Just as there is no one pill for all diseases, there is no one design pattern for all occasions.

9. Premature optimization

Premature optimization is an anti-pattern whose name speaks for itself.
"Programmers spend a huge amount of time thinking and worrying about non-critical places in the code and trying to optimize them, which only negatively affects subsequent debugging and support. We should generally forget about optimization in, say, 97% of cases. Moreover, premature optimization is the root of all evil. That said, we must pay all attention to the remaining 3%." — Donald Knuth
For example, prematurely adding indexes to a database. Why is that bad? Well, it is bad in that, indexes are stored as a binary tree. As a result, each time a new value is added and deleted, the tree will be recalculated, and this consumes resources and time. Therefore, indexes should be added only when there is an urgent need (if you have a large amount of data and queries take too long) and only for the most important fields (the fields that are most frequently queried).

10. Spaghetti code

Spaghetti code is an anti-pattern defined by code that is poorly structured, confusing, and difficult to understand, containing all kinds of branching, such as wrapping exceptions, conditions, and loops. Previously, the goto operator was this anti-pattern's main ally. Goto statements are not really used any longer, which happily eliminates a number of associated difficulties and problems.
public boolean someDifficultMethod(List<String> XMLAttrList) {
           ...
   int prefix = stringPool.getPrefixForQName(elementType);
   int elementURI;
   try {
       if (prefix == -1) {
        ...
           if (elementURI != -1) {
               stringPool.setURIForQName(...);
           }
       } else {
        ...
           if (elementURI == -1) {
           ...
           }
       }
   } catch (Exception e) {
       return false;
   }
   if (attrIndex != -1) {
       int index = attrList.getFirstAttr(attrIndex);
       while (index != -1) {
           int attName = attrList.getAttrName(index);
           if (!stringPool.equalNames(...)){
           ...
               if (attPrefix != namespacesPrefix) {
                   if (attPrefix == -1) {
                    ...
                   } else {
                       if (uri == -1) {
                       ...
                       }
                       stringPool.setURIForQName(attName, uri);
                   ...
                   }
                   if (elementDepth >= 0) {
                   ...
                   }
                   elementDepth++;
                   if (elementDepth == fElementTypeStack.length) {
                   ...
                   }
               ...
                   return contentSpecType == fCHILDRENSymbol;
               }
           }
       }
   }
}
It looks awful, doesn't it? Unfortunately, this is the most common anti-pattern :( Even the person who writes such code won't be able to understand it in the future. Other developers who see the code will think, "Well, if it works, then okay — it's better not to touch it". Often, a method is initially simple and very transparent, but as new requirements are added, the method is gradually saddled with more and more conditional statements, turning it into a monstrosity like this. If such a method appears, you need to refactor it either completely or at least the most confusing parts. Typically, when scheduling a project, time is allocated for refactoring, for example, 30% of the sprint time is for refactoring and tests. Of course, this assumes that there isn't any rush (but when does that ever happen). You can find a good example of spaghetti code and its refactoring here.

11. Magic numbers

Magic numbers is an anti-pattern in which all kinds of constants are used in a program without any explanation of their purpose or meaning. That is, they are generally poorly named or in extreme cases, there is no comment explaining what the comments are or why. Like spaghetti code, this is one of the most common anti-patterns. Someone who didn't write the code may or may not have a clue about the magic numbers or how they work (and in time, the author himself won't be able to explain them). As a result, changing or removing a number causes the code to magically stop working all together. For example, 36 and 73. To combat this anti-pattern, I recommend a code review. Your code needs to be looked at by developers who are not involved in the relevant sections of the code. Their eyes will be fresh and they will have questions: what's this and why did you do that? And of course, you need to use explanatory names or leave comments.

12. Copy-and-paste programming

Copy-and-paste programming is an anti-pattern in which someone else's code is thoughtlessly copied and pasted, possibly resulting in unanticipated side effects. For example, copying and pasting methods with mathematical calculations or complex algorithms that we do not fully understand. It may work for our particular case, but in some other circumstances it could lead to trouble. Suppose I need a method to determine the maximum number in an array. Rummaging around the Internet, I found this solution:
public static int max(int[] array) {
   int max = 0;
   for(int i = 0; i < array.length; i++) {
       if (Math.abs(array[i]) > max){
           max = array[i];
       }
   }
   return max;
}
We get an array with the numbers 3, 6, 1, 4, and 2, and the method returns 6. Great, let's keep it! But later we get an array consisting of 2.5, -7, 2, and 3, and then our result is -7. And this result is no good. The problem here is that Math.abs() returns the absolute value. Ignorance of this leads to disaster, but only in certain situations. Without an in-depth understanding of the solution, there are many cases you won't be able to verify. Copied code may also go beyond the application's internal structure, both stylistically and on a more fundamental, architectural level. Such code will be more difficult to read and maintain. And of course, we mustn't forget that straight up copying someone else's code is a special kind of plagiarism. In those instances when a programmer does not fully understand what he or she is doing and decides to take someone else's supposedly working solution, not only does this demonstrate a lack of perseverance, but also these actions are a detriment to the team, the project, and sometimes the whole company (so copy and paste carefully).

13. Reinventing the wheel

Reinventing the wheel is an anti-pattern, also sometimes known as reinventing the square wheel. In essence, this template is the opposite of the copy-and-paste anti-pattern considered above. In this anti-pattern, the developer implements his or her own solution for a problem for which solutions already exist. Sometimes these existing solutions are better than what the programmer invents. Most often, this leads only to lost time and lower productivity: the programmer may not find a solution at all or may find a solution that is far from the best. That said, we can't rule out the possibility of creating an independent solution, because doing that is a direct road to copy-and-paste programming. The programmer should be guided by the specific programming tasks that arise in order to solve them competently, whether by using ready-made solutions or by creating custom solutions. Very often, the reason for using this anti-pattern is simply haste. The result is a shallow analysis of (search for) ready-made solutions. Reinventing the square wheel is a case where the anti-pattern under consideration has a negative outcome. That is, the project requires a custom solution, and the developer creates it, but badly. At the same time, a good option does already exist and others are using it successfully. Bottom line: a huge amount of time is lost. First, we create something that doesn't work. Then we try to refactor it, and finally we replace it with something that already existed. An example is implementing your own custom cache when plenty of implementations already exist. No matter how talented you are as a programmer, you should remember that reinventing a square wheel is at the very least a waste of time. And, as you know, time is the most valuable resource.

14. Yo-yo problem

The yo-yo problem is an anti-pattern in which the application's structure is overly complicated due to excessive fragmentation (for example, an excessively subdivided inheritance chain). The "yo-yo problem" arises when you need to understand a program whose inheritance hierarchy is long and complex, creating deeply nested method calls. As a result, programmers need to navigate between many different classes and methods in order to inspect the behavior of the program. The name of this anti-pattern comes from the name of the toy. As an example, let's look at the following inheritance chain: We have a Technology interface:
public interface Technology {
   void turnOn();
}
The Transport interface inherits it:
public interface Transport extends Technology {
   boolean fillUp();
}
And then we have another interface, GroundTransport:
public interface GroundTransportation extends Transport {
   void startMove();
   void brake();
}
And from there, we derive an abstract Car class:
public abstract class Car implements GroundTransportation {
   @Override
   public boolean fillUp() {
       /* some implementation */
       return true;
   }
   @Override
   public void turnOn() {
       /* some implementation */
   }
   public boolean openTheDoor() {
       /* some implementation */
       return true;
   }
   public abstract void fixCar();
}
Next is the abstract Volkswagen class:
public abstract class Volkswagen extends Car {
   @Override
   public void startMove() {
       /* some implementation */
   }
   @Override
   public void brake() {
       /* some implementation */
   }
}
And finally, a specific model:
public class VolkswagenAmarok extends Volkswagen {
   @Override
   public void fixCar(){
       /* some implementation */
   }
}
This chain forces us to hunt for answers to questions like:
  1. How many methods does VolkswagenAmarok have?

  2. What type should be inserted instead of the question mark in order to achieve maximum abstraction:

    ? someObj = new VolkswagenAmarok();
           someObj.brake();
It's difficult to quickly answer such questions — it requires us to take a look and investigate, and it's easy to get confused. And what if the hierarchy is much larger, longer, and more complicated, with all sorts of overloads and overrides? The structure we would have would be obscured due to excessive fragmentation. The best solution would be to reduce the unnecessary divisions. In our case, we would leave Technology → Car → VolkswagenAmarok.

15. Accidental complexity

Unnecessary complexity is an anti-pattern in which needless complications are introduced to a solution.
"Any fool can write code that a computer can understand. Good programmers write code that humans can understand." — Martin Fowler
So what is complexity? It can be defined as the degree of difficulty with which each operation is performed in the program. As a rule, complexity can be divided into two types. The first kind of complexity is the number of functions that a system has. It can be reduced in only one way — by removing some function. The existing methods need to be monitored. A method should be removed if it is no longer used or is still used but without bringing any value. What's more, you need to assess how all the methods in the application are used, in order to understand where investments would be worthwhile (a lot of code reuse) and what you can say no to. The second type of complexity is unnecessary complexity. It can be cured only through a professional approach. Instead of doing something "cool" (young developers are not the only ones susceptible to this disease), you need to think about how to do it as simply as possible, because the best solution is always simple. For example, suppose we have small related tables with descriptions of some entities, such as a user: What are anti-patterns? Let's look at some examples (Part 2) - 3So, we have the id of the user, the id of the language in which the description is made, and the description itself. Similarly, we have auxiliary descriptors for the cars, files, plans, and customers tables. Then what would it look like to insert new values into such tables?
public void createDescriptionForElement(ServiceType type, Long languageId, Long serviceId, String description)throws Exception {
   switch (type){
       case CAR:
           jdbcTemplate.update(CREATE_RELATION_WITH_CAR, languageId, serviceId, description);
       case USER:
           jdbcTemplate.update(CREATE_RELATION_WITH_USER, languageId, serviceId, description);
       case FILE:
           jdbcTemplate.update(CREATE_RELATION_WITH_FILE, languageId, serviceId, description);
       case PLAN:
           jdbcTemplate.update(CREATE_RELATION_WITH_PLAN, languageId, serviceId, description);
       case CUSTOMER:
           jdbcTemplate.update(CREATE_RELATION_WITH_CUSTOMER, languageId, serviceId, description);
       default:
           throw new Exception();
   }
}
And accordingly, we have this enum:
public enum ServiceType {
   CAR(),
   USER(),
   FILE(),
   PLAN(),
   CUSTOMER()
}
Everything seems to be simple and good... But what about the other methods? Indeed, they will also all have a bunch of switch statements and a bunch of almost identical database queries, which in turn will greatly complicate and bloat our class. How could all this be made easier? Let's upgrade our enum a bit:
@Getter
@AllArgsConstructor
public enum ServiceType {
   CAR("cars_descriptions", "car_id"),
   USER("users_descriptions", "user_id"),
   FILE("files_descriptions", "file_id"),
   PLAN("plans_descriptions", "plan_id"),
   CUSTOMER("customers_descriptions", "customer_id");
   private String tableName;
   private String columnName;
}
Now each type has the names of its table's original fields. As a result, the method for creating a description becomes:
private static final String CREATE_RELATION_WITH_SERVICE = "INSERT INTO %s(language_id, %s, description) VALUES (?, ?, ?)";
public void createDescriptionForElement(ServiceType type, Long languageId, Long serviceId, String description) {
   jdbcTemplate.update(String.format(CREATE_RELATION_WITH_SERVICE, type.getTableName(), type.getColumnName()), languageId, serviceId, description);
   }
Convenient, simple, and compact, don't you think? The indication of a good developer is not even how often he or she uses patterns, but rather how often he or she avoids anti-patterns. Ignorance is the worst enemy, because you need to know your enemies by sight. Well, that's all I have for today. Thank you, everyone! :)
Comments (1)
  • Popular
  • New
  • Old
You must be signed in to leave a comment
Bartek Moń
Level 2 , Koszalin, Poland
26 August 2020, 11:33
Awesome, thanks a lot mate! 👍👍👍