8. Golden hammer
A golden hammer is an anti-pattern defined by confidence that a particular solution is universally applicable. Examples: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.
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.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:
How many methods does
VolkswagenAmarok
have?What type should be inserted instead of the question mark in order to achieve maximum abstraction:
? someObj = new VolkswagenAmarok(); someObj.brake();
15. Accidental complexity
Unnecessary complexity is an anti-pattern in which needless complications are introduced to a solution.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! :)