Code Smells
1. Duplicate Code
The following are some of the ways in which two code sequences can be duplicates of each other:
- character-for-character identical
- character-for-character identical with white space characters and comments being ignored
- token-for-token identical
- token-for-token identical with occasional variation (i.e., insertion/deletion/modification of tokens)
- functionally identica
There are a number of reasons why duplicate code may be created, including:
- Copy and paste programming, in which a section of code is copied "because it works". In most cases this operation involves slight modifications in the cloned code such as renaming variables or inserting/deleting code.
- Functionality that is very similar to that in another part of a program is required and a developer independently writes code that is very similar to what exists elsewhere.
- Plagiarism, where code is simply copied without permission or attribution
Code duplication is generally considered a mark of poor or lazy programming style. Good coding style is generally associated with code reuse. It may be slightly faster to develop by duplicating code, because the developer need not concern himself with how the code is already used or how it may be used in the future. The difficulty is that original development is only a small fraction of a product's life cycle, and with code duplication the maintenance costs are much higher. Some of the specific problems include:
- Code bulk affects comprehension: Code duplication frequently creates long, repeated sections of code that differ in only a few lines or characters. The length of such routines can make it difficult to quickly understand them. This is in contrast to the "best practice" of code decomposition.
- Purpose masking: The repetition of largely identical code sections can conceal how they differ from one another, and therefore, what the specific purpose of each code section is. Often, the only difference is in a parameter value. The best practice in such cases is a reusable subroutine.
- Update anomalies: Duplicate code contradicts a fundamental principle of database theory that applies here: Avoid redundancy. Non-observance incurs update anomalies, which increase maintenance costs, in that any modification to a redundant piece of code must be made for each duplicate separately. At best, coding and testing time are multiplied by the number of duplications. At worst, some locations may be missed, and for example bugs thought to be fixed may persist in duplicated locations for months or years. The best practice here is a code library.
- File size: Unless external lossless compression is applied, the file will take up more space on the computer.
- Consider the following code snippet for calculating the average of an array of integers
-
extern int array1[]; extern int array2[]; int sum1 = 0; int sum2 = 0; int average1 = 0; int average2 = 0; for (int i = 0; i < 4; i++) { sum1 += array1[i]; } average1 = sum1/4; for (int i = 0; i < 4; i++) { sum2 += array2[i]; } average2 = sum2/4;
int calcAverage (int* Array_of_4) { int sum = 0; for (int i = 0; i < 4; i++) { sum += Array_of_4[i]; } return sum/4; }
extern int array1[]; extern int array2[]; int average1 = calcAverage(array1); int average2 = calcAverage(array2);
- Baker's algorithm.[2]
- Rabin–Karp string search algorithm.
- Using Abstract Syntax Trees.[3]
- Visual clone detection.[4]
3. Large class: a class that has grown too large. See God object.
In object-oriented programming, a god object is an object that knows too much or does too much. The god object is an example of an anti-pattern.
The basic idea behind object-oriented programming is that a big problem is broken down into several smaller problems (a divide and conquer strategy) and solutions are created for each of them. Once the small problems have been solved, the big problem as a whole has been solved. Therefore there is only one object about which an object needs to know everything: itself. Likewise, there is only one set of problems an object needs to solve: its own.
God object–based code does not follow this approach. Instead, most of a program's overall functionality is coded into a single "all-knowing" object, which maintains most of the information about the entire program and provides most of the methods for manipulating this data. Because this object holds so much data and requires so many methods, its role in the program becomes god-like (all-encompassing). Instead of program objects communicating amongst themselves directly, the other objects within the program rely on the god object for most of their information and interaction. Since the god object is referenced by so much of the other code, maintenance becomes more difficult than it would in a more evenly divided programming design.
A god object is the object-oriented analogue of failing to use subroutines in procedural programming languages, or of using far too many global variables to store state information.
While creating a god object is typically considered bad programming practice, this technique is occasionally used for tight programming environments (such as microcontrollers), where the slight performance increase and centralization of control is more important than maintainability and programming elegance
4. Too many parameters: a long list of parameters in a procedure or function make readability and code quality worse.
5. Feature envy: a class that uses methods of another class excessively.
A classic smell is a method that seems more
interested in a class other than the one it actually is in. The most
common focus of the envy is the data. We’ve lost count of the times
we’ve seen a method that invokes half-a-dozen getting methods on another
object to calculate some value. Fortunately the cure is obvious, the
method clearly wants to be elsewhere, so you use Move Method to get it there. Sometimes only part of the method suffers from envy; in that case use Extract Method on the jealous bit and Move Method to give it a dream home.
03
Of
course not all cases are cut-and-dried. Often a method uses features of
several classes, so which one should it live with? The heuristic we use
is to determine which class has most of the data and put the method
with that data. This step is often made easier if Extract Method is used to break the method into pieces that go into different places.
for tomorrow!!
http://sourcemaking.com/refactoring/move-method
A method is, or will be, using or used by more features of another class than the class on which it is defined.
02
Create
a new method with a similar body in the class it uses most. Either turn
the old method into a simple delegation, or remove it altogether.
Motivation
03
Moving
methods is the bread and butter of refactoring. I move methods when
classes have too much behavior or when classes are collaborating too
much and are too highly coupled. By moving methods around, I can make
the classes simpler and they end up being a more crisp implementation of
a set of responsibilities.
04
I
usually look through the methods on a class to find a method that seems
to reference another object more than the object it lives on. A good
time to do this is after I have moved some fields. Once I see a likely
method to move, I take a look at the methods that call it, the methods
it calls, and any redefining methods in the hierarchy. I assess whether
to go ahead on the basis of the object with which the method seems to
have more interaction.
05
It’s
not always an easy decision to make. If I am not sure whether to move a
method, I go on to look at other methods. Moving other methods often
makes the decision easier. Sometimes the decision still is hard to make.
Actually it is then no big deal. If it is difficult to make the
decision, it probably does not matter that much. Then I choose according
to instinct; after all, I can always change it again later.
Mechanics
06- Examine all features used by the source method that are defined on
the source class. Consider whether they also should be moved.
If a feature is used only by the method you are about to move, you might as well move it, too. If the feature is used by other methods, consider moving them as well. Sometimes it is easier to move a clutch of methods than to move them one at a time.
- Check the sub- and superclasses of the source class for other declarations of the method.
If there are any other declarations, you may not be able to make the move, unless the polymorphism can also be expressed on the target.
- Declare the method in the target class.
You may choose to use a different name, one that makes more sense in the target class.
- Copy the code from the source method to the target. Adjust the method to make it work in its new home.
If the method uses its source, you need to determine how to reference the source object from the target method. If there is no mechanism in the target class, pass the source object reference to the new method as a parameter. If the method includes exception handlers, decide which class should logically handle the exception. If the source class should be responsible, leave the handlers behind.
- Compile the target class.
- Determine how to reference the correct target object from the source.
There may be an existing field or method that will give you the target. If not, see whether you can easily create a method that will do so. Failing that, you need to create a new field in the source that can store the target. This may be a permanent change, but you can also make it temporarily until you have refactored enough to remove it.
- Turn the source method into a delegating method.
- Compile and test.
- Decide whether to remove the source method or retain it as a delegating method.
Leaving the source as a delegating method is easier if you have many references.
- If you remove the source method, replace all the references with references to the target method.
You can compile and test after changing each reference, although it is usually easier to change all references with one search and replace.
- Compile and test.
Example
07
An account class illustrates this refactoring:
08 class Account...
double overdraftCharge() {
if (_type.isPremium()) {
double result = 10;
if (_daysOverdrawn > 7) result += (_daysOverdrawn - 7) * 0.85;
return result;
}
else return _daysOverdrawn * 1.75;
}
double bankCharge() {
double result = 4.5;
if (_daysOverdrawn > 0) result += overdraftCharge();
return result;
}
private AccountType _type;
private int _daysOverdrawn;
09
Let’s
imagine that there are going to be several new account types, each of
which has its own rule for calculating the overdraft charge. So I want
to move the overdraft charge method over to the account type.
10
The
first step is to look at the features that the overdraftCharge method
uses and consider whether it is worth moving a batch of methods
together. In this case I need the
11_daysOverdrawn
field to remain on the account class, because that will vary with individual accounts.
Next I copy the method body over to the account type and get it to fit.
12 class AccountType...
double overdraftCharge(int daysOverdrawn) {
if (isPremium()) {
double result = 10;
if (daysOverdrawn > 7) result += (daysOverdrawn - 7) * 0.85;
return result;
}
else return daysOverdrawn * 1.75;
}
13
In this case fitting means removing the
14_type
from uses of features of the account type, and doing something about
the features of account that I still need. When I need to use a feature
of the source class I can do one of four things: (1) move this feature
to the target class as well, (2) create or use a reference from the
target class to the source, (3) pass the 0source object as a parameter
to the method, (4) if the feature is a variable, pass it in as
a parameter.
In this case I passed the variable as a parameter.
15
Once the method fits and compiles in the target class, I can replace the source method body with a simple delegation:
16 class Account...
double overdraftCharge() {
return _type.overdraftCharge(_daysOverdrawn);
}
17
At this point I can compile and test.
18
I
can leave things like this, or I can remove the method in the source
class. To remove the method I need to find all callers of the method and
redirect them to call the method in account type:
19 class Account...
double bankCharge() {
double result = 4.5;
if (_daysOverdrawn > 0) result += _type.overdraftCharge(_daysOverdrawn);
return result;
}
20
Once
I’ve replaced all the callers, I can remove the method declaration in
account. I can compile and test after each removal, or do them in a
batch. If the method isn’t private, I need to look for other classes
that use this method. In a strongly typed language, the compilation
after removal of the source declaration finds anything I missed.
21
In
this case the method referred only to a single field, so I could just
pass this field in as a variable. If the method called another method on
the account, I wouldn’t have been able to do that. In those cases I
need to pass in the source object:
22 class AccountType...
double overdraftCharge(Account account) {
if (isPremium()) {
double result = 10;
if (account.getDaysOverdrawn() > 7)
result += (account.getDaysOverdrawn() - 7) * 0.85;
return result;
}
else return account.getDaysOverdrawn() * 1.75;
}
23
I
also pass in the source object if I need several features of the class,
although if there are too many, further refactoring is needed.
Typically I need to decompose and move some pieces back.
http://sourcemaking.com/refactoring/extract-method
6. Inappropriate intimacy: a class that has dependencies on implementation details of another class.
Sometimes classes become far too intimate and spend
too much time delving in each others’private parts. We may not be
prudes when it comes to people, but we think our classes should follow
strict, puritan rules.
02
Overintimate classes need to be broken up as lovers were in ancient days. Use Move Method and Move Field to separate the pieces to reduce the intimacy. See whether you can arrange a Change Bidirectional Association to Unidirectional. If the classes do have common interests, use Extract Class to put the commonality in a safe place and make honest classes of them. Or use Hide Delegate to let another class act as go-between.
03
Inheritance
often can lead to overintimacy. Subclasses are always going to know
more about their parents than their parents would like them to know. If
it’s time to leave home, apply Replace Delegation with Inheritance.
7. Refused bequest: a class that overrides a method of a base class in such a way that the contract of the base class is not honored by the derived class. See Liskov substitution principle.
All the time we design a program module and we create some class hierarchies. Then we extend some classes creating some derived classes.
We must make sure that the new derived classes just extend without replacing the functionality of old classes. Otherwise the new classes can produce undesired effects when they are used in existing program modules.
Likov's Substitution Principle states that if a program module is using a Base class, then the reference to the Base class can be replaced with a Derived class without affecting the functionality of the program module.
Intent
Derived types must be completely substitutable for their base types.Example
Below is the classic example for which the Likov's Substitution Principle is violated. In the example 2 classes are used: Rectangle and Square. Let's assume that the Rectangle object is used somewhere in the application. We extend the application and add the Square class. The square class is returned by a factory pattern, based on some conditions and we don't know the exact what type of object will be returned. But we know it's a Rectangle. We get the rectangle object, set the width to 5 and height to 10 and get the area. For a rectangle with width 5 and height 10 the area should be 50. Instead the result will be 100// Violation of Likov's Substitution Principle class Rectangle { protected int m_width; protected int m_height; public void setWidth(int width){ m_width = width; } public void setHeight(int height){ m_height = height; } public int getWidth(){ return m_width; } public int getHeight(){ return m_height; } public int getArea(){ return m_width * m_height; } } class Square extends Rectangle { public void setWidth(int width){ m_width = width; m_height = width; } public void setHeight(int height){ m_width = height; m_height = height; } } class LspTest { private static Rectangle getNewRectangle() { // it can be an object returned by some factory ... return new Square(); } public static void main (String args[]) { Rectangle r = LspTest.getNewRectangle(); r.setWidth(5); r.setHeight(10); // user knows that r it's a rectangle. // It assumes that he's able to set the width and height as for the base class System.out.println(r.getArea()); // now he's surprised to see that the area is 100 instead of 50. } } |
This principle is just an extension of the Open Close Principle and it means that we must make sure that new derived classes are extending the base classes without changing their behavior.
Immutable Objects example
A classic example of an immutable object is an instance of the Java
String
class.String s = "ABC"; s.toLowerCase();
toLowerCase()
will not change the data "ABC" that s
contains. Instead, a new String object is instantiated and given the
data "abc" during its construction. A reference to this String object is
returned by the toLowerCase()
method. To make the String s
contain the data "abc", a different approach is needed.s = s.toLowerCase();
s
references a new String object that contains "abc". There is nothing in the syntax of the declaration
of the class String that enforces it as immutable; rather, none of the
String class's methods ever affect the data that a String object
contains, thus making it immutable.By default, fields and local variables are mutable. They can be made immutable using the
final
keyword.int i = 42; i = 43; // OK final int j = 42; j = 43; // does not compile
8. Lazy class / Freeloader: a class that does too little.
A superclass and subclass are not very different.
02
Merge them together.
Motivation
03
If
you have been working for a while with a class hierarchy, it can easily
become too tangled for its own good. Refactoring the hierarchy often
involves pushing methods and fields up and down the hierarchy. After
you’ve done this you can well find you have a subclass that isn’t adding
any value, so you need to merge the classes together.
Mechanics
04- Choose which class is going to be removed: the superclass or the subclasses.
- Use Pull Up Field and Pull Up Method or Push Down Method and Push Down Field to move all the behavior and data of the removed class to the class with which it is being merged.
- Compile and test with each move.
- Adjust references to the class that will be removed to use the merged class. This will affect variable declarations, parameter types, and constructors.
- Remove the empty class.
- Compile and test.
9. Contrived complexity: forced usage of overly complicated design patterns where simpler design would suffice.
10. Excessively long identifiers: in particular, the use of naming conventions to provide disambiguation that should be implicit in the software architecture
11. Excessively short identifiers: the name of a variable should reflect its function unless it's obvious.
12. Excessive use of literals: these should be coded as named constants, to improve readability and to avoid programming errors. Additionally, literals can and should be externalized into resource files/scripts where possible, to facilitate localization of software if it is intended to be deployed in different regions.
13. Ubercallback: a callback that is trying to do everything
The following code in C demonstrates the use of callbacks to display two numbers.
#include <stdio.h> #include <stdlib.h> /* The calling function takes a single callback as a parameter. */ void PrintTwoNumbers(int (*numberSource)(void)) { printf("%d and %d\n", numberSource(), numberSource()); } /* A possible callback */ int overNineThousand(void) { return (rand() % 1000) + 9001; } /* Another possible callback. */ int meaningOfLife(void) { return 42; } /* Here we call PrintTwoNumbers() with three different callbacks. */ int main(void) { PrintTwoNumbers(&rand); PrintTwoNumbers(&overNineThousand); PrintTwoNumbers(&meaningOfLife); return 0; }
125185 and 89188225 9084 and 9441 42 and 42Note how this is different from simply passing the output of the callback function to the calling function, PrintTwoNumbers() - rather than printing the same value twice, the PrintTwoNumbers calls the callback as many times as it requires. This is one of the two main advantages of callbacks.
14. Anti-pattern
Object-oriented design
- Anemic Domain Model: The use of the domain model without any business logic. The domain model's objects cannot guarantee their correctness at any moment, because their validation and mutation logic is placed somewhere outside (most likely in multiple places).
- BaseBean: Inheriting functionality from a utility class rather than delegating to it, For example, an order is not a vector, although an order may contain a vector of line items.A class should not inherit from another class simply because the parent class contains functionality needed in the subclass. Instead, delegation (has-a relationship) should be used to obtain the business logic or data structure that is required.In technical terms, this case warrants composition over inheritance
- Call super: Requiring subclasses to call a superclass's overridden method
Example
Suppose there is a class for generating a report about the inventory of a video rental store. Each particular store has a different way of tabulating the videos currently available, but the algorithm for generating the final report is the same for all stores. A framework that uses the call super anti-pattern may provide the following abstract class (in C#):
abstract class ReportGenerator { public virtual Report CreateReport() { // Generate the general report object // ... return new Report(...); } }
class ConcreteReportGenerator : ReportGenerator { public override Report CreateReport() { // Tabulate data in the store-specific way // ... // Design of this class requires the parent CreateReport() function to be called at the // end of the overridden function. But note this line could easily be left out, or the // returned report could be further modified after the call, violating the class design // and possibly also the company-wide report format. return base.CreateReport(); } }
abstract class ReportGenerator { public Report CreateReport() { Tabulate(); // Generate the general report object // ... return new Report(...); } protected abstract void Tabulate(); }
class ConcreteReportGenerator : ReportGenerator { protected override void Tabulate() { // Tabulate data in the store-specific way // ... } }
- Circle-ellipse problem: Subtyping variable-types on the basis of value-subtypes
Challenge the premise of the problem
While at first glance it may seem obvious that a Circle is-an Ellipse, consider the following alternate representation of essentially the same problem, stated in terms of Java code.
class Person { void walkNorth( int meters ) {...} // No failure or exception allowed void walkEast( int meters ) {...} // No failure or exception allowed }
Now, a prisoner is obviously a person. So we could logically create a sub-class:
class Prisoner extends Person { void walkNorth( int meters ) {...} // No failure or exception allowed void walkEast( int meters ) {...} // No failure or exception allowed }
Just as obviously, this leads us into trouble, since a prisoner is not free to move an arbitrary distance in any direction, yet the contract of thePerson
class states that a Person can.
So, in fact, the classPerson
could better be namedFreePerson
. If that were the case, then the idea thatclass Prisoner extends FreePerson
is clearly wrong.
By analogy, then, a Circle, is not an Ellipse, because it lacks the same degrees of freedom as an Ellipse.
This strongly suggests that inheritance should never be used when the sub-class restricts the freedom implicit in the base class, but should only be used when the sub-class adds extra detail to the concept represented by the base class as in 'Monkey' is-a 'Animal'.
- Circular dependency: Introducing unnecessary direct or indirect mutual dependencies between objects or software modules
Circular dependencies can cause many unwanted effects in software programs. Most problematic from a software design point of view is the tight coupling of the mutually dependent modules which reduces or makes impossible the separate re-use of a single module.
Circular dependencies can cause a domino effect when a small local change in one module spreads into other modules and has unwanted global effects (program errors, compile errors). Circular dependencies can also result in infinite recursions or other unexpected failures.
Circular dependencies may also cause memory leaks by preventing certain very primitive automatic garbage collectors (those that use reference counting) from deallocating unused objects.
In very large software designs, software engineers may lose the context and inadvertently introduce circular dependencies. There are tools to analyze software and find unwanted circular dependencies[1][2].
Circular dependencies are often introduced by inexperienced programmers who need to implement some kind of callback functionality. Experienced programmers avoid such unnecessary circular dependencies by applying design patterns like the observer pattern.
Implementation of circular dependencies in C/C++ can be a bit tricky, because any class or structure definition must be placed above its usage in the same file. A circular dependency between classes A and B will thus both require the definition of A to be placed above B, and the definition of B to be placed above A, which of course is impossible. A forward declaration trick is therefore needed to accomplish this.
The following example illustrates how this is done.
a.h:
#ifndef A_H #define A_H class B; //forward declaration class A { public: B* b; }; #endif //A_H
b.h:
#ifndef B_H #define B_H class A; //forward declaration class B { public: A* a; }; #endif //B_H
main.cpp:
#include "a.h" #include "b.h" int main() { A a; B b; a.b = &b; b.a = &a; }
A
) can be declared multiple times, such as in forward declarations, it can only be defined once (the One Definition Rule).- Constant interface: Using interfaces to define constants
Example
public interface Constants { public static final double PI = 3.14159; public static final double PLANCK_CONSTANT = 6.62606896e-34; } public class Calculations implements Constants { public double getReducedPlanckConstant() { return PLANCK_CONSTANT / (2 * PI); } }
Alternatives
Many of the pitfalls of the anti-pattern can be avoided by converting the constants interface to a proper class with no instances:
public final class Constants { private Constants() { // restrict instantiation } public static final double PI = 3.14159; public static final double PLANCK_CONSTANT = 6.62606896e-34; }
import static Constants.PLANCK_CONSTANT; import static Constants.PI; public class Calculations { public double getReducedPlanckConstant() { return PLANCK_CONSTANT / (2 * PI); } }
- God object: Concentrating too many functions in a single part of the design (class)
In object-oriented programming, a god object is an object that knows too much or does too much. The god object is an example of an anti-pattern.
The basic idea behind object-oriented programming is that a big problem is broken down into several smaller problems (a divide and conquer strategy) and solutions are created for each of them. Once the small problems have been solved, the big problem as a whole has been solved. Therefore there is only one object about which an object needs to know everything: itself. Likewise, there is only one set of problems an object needs to solve: its own.
God object–based code does not follow this approach. Instead, most of a program's overall functionality is coded into a single "all-knowing" object, which maintains most of the information about the entire program and provides most of the methods for manipulating this data. Because this object holds so much data and requires so many methods, its role in the program becomes god-like (all-encompassing). Instead of program objects communicating amongst themselves directly, the other objects within the program rely on the god object for most of their information and interaction. Since the god object is referenced by so much of the other code, maintenance becomes more difficult than it would in a more evenly divided programming design.
A god object is the object-oriented analogue of failing to use subroutines in procedural programming languages, or of using far too many global variables to store state information.
While creating a god object is typically considered bad programming practice, this technique is occasionally used for tight programming environments (such as microcontrollers), where the slight performance increase and centralization of control is more important than maintainability and programming elegance - Object cesspool: Reusing objects whose state does not conform to the (possibly implicit) contract for re-use
When writing an object pool, the programmer has to be careful to make sure the state of the objects returned to the pool is reset back to a sensible state for the next use of the object. If this is not observed, the object will often be in some state that was unexpected by the client program and may cause the client program to fail. The pool is responsible for resetting the objects, not the clients. Object pools full of objects with dangerously stale state are sometimes called object cesspools and regarded as an anti-pattern.
The presence of stale state is not always an issue; it becomes dangerous when the presence of stale state causes the object to behave differently. For example, an object that represents authentication details may break if the "successfully authenticated" flag is not reset before it is passed out, since it will indicate that a user is correctly authenticated (possibly as someone else) when they haven't yet attempted to authenticate. However, it will work just fine if you fail to reset some value only used for debugging, such as the identity of the last authentication server used.
Inadequate resetting of objects may also cause an information leak. If an object contains confidential data (e.g. a user's credit card numbers) that isn't cleared before the object is passed to a new client, a malicious or buggy client may disclose the data to an unauthorized party.
If the pool is used by multiple threads, it may need means to prevent parallel threads from grabbing and trying to reuse the same object in parallel. This is not necessary if the pooled objects are immutable or otherwise thread-safe.
- Object orgy: Failing to properly encapsulate objects permitting unrestricted access to their internals
Absence of data encapsulation. The data members may be public (or might as well be because they are exposed to the world through getters and setters), or the objects may hold references to each other. In any case the objects do more tweaking of the other objects data than their own, with ensuing confusion about who did what to whom when.
Have you ever attempted to disassemble a mechanical stopwatch? While most aren't too bad, many share one interesting trait: upon removing the back cover, tiny springs, gears, cogs, plates, shafts, sprockets, ratchets and dozens of unidentifiable pieces literally fly out. This sensation of hopelessness is matched only by a programmer assigned to make changes to a program designed to work exactly one way and to never be changed. Building anew is far more likely than ever picking the pieces up "off the floor" - Poltergeists: Objects whose sole purpose is to pass information to another object
The consequences of an object orgy are essentially a loss of the benefits of encapsulation:
Unrestricted access makes it hard for the reader to reason about the behaviour of an object. This is because direct access to its internal state means any other part of the system can manipulate it, increasing the amount of code to be examined, and opening the door to future abuse.
As a consequence of the difficulty of reasoning, design by contract is effectively impossible.
If much code takes advantage of the lack of encapsulation, the result is a scarcely maintainable maze of interactions, commonly known as a rat's nest or spaghetti code.
The original design is obscured by the excessively broad interfaces to objects.
The broad interfaces make it harder to re-implement a class without disturbing the rest of the system. This is especially hard when the clients of the class are developed by a different team or organisation. - Sequential coupling: A class that requires its methods to be called in a particular order
Methods whose name starts with Init, Begin, Start, etc. may indicate the existence of sequential coupling.Using a car as an analogy, if the user steps on the gas without first starting the engine, the car does not crash, fail, or throw an exception - it simply fails to accelerate.Sequential coupling can be refactored with the Template method pattern to overcome the problems posed by the usage of this anti-pattern.[1] - Yo-yo problem: A structure (e.g., of inheritance) that is hard to understand due to excessive fragmentation
In computer science, the yo-yo problem is an anti-pattern that occurs when a programmer has to read and understand a program whose inheritance graph is so long and complicated that the programmer has to keep flipping between many different class definitions in order to follow the control flow of the program. It often happens in object-oriented programming. The term comes from comparing the bouncing attention of the programmer to the up-down movement of a toy yo-yo.
Programming
- Accidental complexity: Introducing unnecessary complexity into a solution
Accidental complexity is complexity that arises in computer programs or their development process which is non-essential to the problem to be solved. While essential complexity is inherent and unavoidable, accidental complexity is caused by the approach chosen to solve the problem.[1]
While sometimes accidental complexity can be due to mistakes such as ineffective planning, or low priority placed on a project, some accidental complexity always occurs as the side effect of solving any problem. For example, the complexity caused by out of memory errors is an accidental complexity to most programs that occurs because one decided to use a computer to solve the problem.
- Action at a distance: Unexpected interaction between widely separated parts of a system
Action at a distance is an anti-pattern (a recognized common error) in which behavior in one part of a program varies wildly based on difficult or impossible to identify operations in another part of the program. The way to avoid the problems associated with action at a distance are a proper design which avoids global variables and alters data in a controlled and local manner, or usage of a pure functional programming style with referential transparency.
Bugs due to "action at a distance" may arise because a program component is doing something at the wrong time, or affecting something it should not. It is very difficult, however, to track down which component is responsible. Side effects from innocent actions can put the program in an unknown state, so local data is not necessarily local. The solution in this particular scenario is to define which components should be interacting with which others. A proper design that accurately defines the interface between parts of a program, and that avoids shared states, can largely eliminate problems caused by action at a distance.
This example, from the Perl programming language, demonstrates an especially serious case of action at a distance:
Array indices normally begin at 0 because the value of$[
is normally 0; if you set$[
to 1, then arrays start at 1, which makes Fortran programmers happy, and so we see examples like this in theperl(3)
man page:
foreach $num ($[ .. $#entry) { print " $num\t'",$entry[$num],"'\n"; }
$[
to 17 to have arrays start at some random number such as 17 or 4 instead of at 0 or 1. This was a great way to sabotage module authors.
Fortunately, sanity prevailed. These features are now recognized to have been mistakes. The perl5-porters mailing list now has a catchphrase for such features: they're called "action at a distance". The principle is that a declaration in one part of the program shouldn't drastically and invisibly alter the behavior of some other part of the program.
Proper object oriented programming involves design principles that avoid action at a distance. The Law of Demeter states that an object should only interact with other objects near itself. Should action in a distant part of the system be required then it should be implemented by propagating a message. Proper design severely limits occurrences of action at a distance, contributing to maintainable programs. Pressure to create an object orgy results from poor interface design, perhaps taking the form of a God object, not implementing true objects, or failing to heed the Law of Demeter.
referential transparency:
As an example, let's use two functions, one which is referentially opaque, and the other which is referentially transparent:
globalValue = 0; integer function rq(integer x) begin globalValue = globalValue + 1; return x + globalValue; end integer function rt(integer x) begin return x + 1; end
The functionrt
is referentially transparent, which means thatrt(x) = rt(y)
ifx = y
. For instance,rt(6) = 6 + 1 = 7, rt(4) = 4 + 1 = 5
, and so on. However, we can't say any such thing forrq
because it uses a global variable which it modifies - Blind faith: Lack of checking of (a) the correctness of a bug fix or (b) the result of a subroutine
In computer programming blind faith (also known as blind programming or blind coding) is a situation whereby a programmer develops a solution or fixes a computer bug and deploys it without ever testing their creation. The programmer in this situation has blind faith in their own abilities.
Another form of blind faith is when a programmer calls a subroutine without checking the result. E.g.: A programmer calls a subroutine to save user-data on the hard disk without checking whether the operation was successful or not. In this case the programmer has blind faith in the subroutine always performing what the programmer intends to accomplish.
Blind faith is an example of an Anti-pattern. Other common names for blind faith include "God oriented programming" and "divine orientation".
- Boat anchor: Retaining a part of a system that no longer has any use
The term boat anchor is also used in software development to refer to an unused piece of code[2] that is left in a system's code base, typically for the reason "In case we need it later".
This is an example of an anti-pattern and therefore can cause many problems for people attempting to maintain the program that contains the obsolete code. The key problem comes from the fact that programmers will have a hard time differentiating between obsolete code which doesn't do anything and working code which does. For example, a programmer may be looking into a bug with the program's input handling system, so they search through the code looking for code that links into the input handling API. Obviously if the programmer comes across obsolete input handling code they may well start editing and debugging it, wasting valuable time before they realise that the code that they're working with is never executed and therefore not part of the problem they're trying to solve. Other problems include longer compile times and the risk that programmers may accidentally link working code into the defunct code, inadvertently resurrecting it. The correct solution for dealing with boat anchors in source code is to remove them from the code base and to place them in a separate location so that they can be referred to if necessary, but will not be compiled or be mistaken as "working" code. (For example deleting them, knowing they are stored in the projects's source control)
- Busy waiting: Consuming CPU while waiting for something to happen, usually by repeated checking instead of messaging
In software engineering, busy-waiting or spinning is a technique in which a process repeatedly checks to see if a condition is true, such as whether keyboard input is available, or if a lock is available. Spinning can also be used to generate an arbitrary time delay, a technique that was necessary on systems that lacked a method of waiting a specific length of time. On modern computers with widely differing processor speeds, spinning as a time delay technique often produces unpredictable results unless code is implemented to determine how quickly the processor can execute a "do nothing" loop.
The following C code examples illustrate two threads that share a global integer i. The first thread uses busy-waiting to check for a change in the value of i:
#include <stdio.h> #include <pthread.h> #include <unistd.h> #include <stdlib.h> volatile int i = 0; /* i is global, so it is visible to all functions. It's also marked volatile, because it may change in a way which is not predictable by the compiler, here from a different thread. */ /* f1 uses a spinlock to wait for i to change from 0. */ static void *f1(void *p) { while (i==0) { /* do nothing - just keep checking over and over */ } printf("i's value has changed to %d.\n", i); return NULL; } static void *f2(void *p) { sleep(60); /* sleep for 60 seconds */ i = 99; printf("t2 has changed the value of i to %d.\n", i); return NULL; } int main() { int rc; pthread_t t1, t2; rc = pthread_create(&t1, NULL, f1, NULL); if (rc != 0) { fprintf(stderr,"pthread f1 failed\n"); return EXIT_FAILURE; } rc = pthread_create(&t2, NULL, f2, NULL); if (rc != 0) { fprintf(stderr,"pthread f2 failed\n"); return EXIT_FAILURE; } pthread_join(t1, NULL); pthread_join(t2, NULL); puts("All pthreads finished."); return 0; }
In low-level programming, busy-waits may actually be desirable. It may not be desirable or practical to implement interrupt-driven processing for every hardware device, particularly those that are seldom accessed. Sometimes it is necessary to write some sort of control data to hardware and then fetch device status resulting from the write operation, status that may not become valid until a number of machine cycles have elapsed following the write. The programmer could call an operating system delay function, but doing so may consume more time than would be expended in spinning for a few clock cycles waiting for the device to return its status. - Caching failure: Forgetting to reset an error flag when an error has been corrected
- Cargo cult programming: Using patterns and methods without understanding why
- Coding by exception: Adding new code to handle each special case as it is recognized
- Error hiding: Catching an error message before it can be shown to the user and either showing nothing or showing a meaningless message
- Hard code: Embedding assumptions about the environment of a system in its implementation
- Lava flow: Retaining undesirable (redundant or low-quality) code because removing it is too expensive or has unpredictable consequences[5][6]
- Loop-switch sequence: Encoding a set of sequential steps using a switch within a loop statement
- Magic numbers: Including unexplained numbers in algorithms
- Magic strings: Including literal strings in code, for comparisons, as event types etc.
- Repeating yourself: Writing code which contains repetitive patterns and substrings over again; avoid with once and only once (abstraction principle)
- Soft code: Storing business logic in configuration files rather than source code[7]
- Spaghetti code: Programs whose structure is barely comprehensible, especially because of misuse of code structures
- Shotgun surgery: Developer adds features to an application codebase which span a multiplicity of implementors or implementations in a single change.
No comments:
Post a Comment