It is motivated by Code smells
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;
The two loops can be rewritten as the single function:
int calcAverage (int* Array_of_4)
{
int sum = 0;
for (int i = 0; i < 4; i++)
{
sum += Array_of_4[i];
}
return sum/4;
}
Using the above function will give source code that has no loop duplication:
extern int array1[];
extern int array2[];
int average1 = calcAverage(array1);
int average2 = calcAverage(array2);
A number of different algorithms have been proposed to detect duplicate code. For example:
2. Long Method/Function/Procedure that has grown too large
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 _daysOverdrawn
field to remain on the account class, because that will vary with individual accounts.
11
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 _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.
14
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
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();
The method
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.
Now the String
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;
}
This should provide output similar to:
125185 and 89188225
9084 and 9441
42 and 42
Note 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(...);
}
}
A user of the class is expected to implement a subclass like this:
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();
}
}
A preferable interface looks like this:
abstract class ReportGenerator {
public Report CreateReport() {
Tabulate();
// Generate the general report object
// ...
return new Report(...);
}
protected abstract void Tabulate();
}
An implementation would override this class like this:
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 the Person
class states that a Person can.
So, in fact, the class Person
could better be named FreePerson
. If that were the case, then the idea that class 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;
}
Note that although a name (e.g. 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;
}
This still leaves the original intent of the pattern mostly
un-addressed (i.e. there is no syntax for accessing the constants
unqualified). However, since Java 5, consider using static import[2] to achieve the same goal:
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