Beruflich Dokumente
Kultur Dokumente
)AntiPatterns(
: Submitted by
Indranil Nandy
MTech, 2007
Roll no. : 06CS6010
Definition of AntiPatterns:
AntiPatterns are related to Design Patterns in that Design Patterns provide
solutions to recurring problems, while AntiPatterns are ”a literary form that describes
a commonly occurring solution to a problem that generates decidedly negative
consequences”
Design Patterns consist of a problem and a solution, while AntiPatterns consist
of two solutions – one which is problematic and one which is more beneficial, the
refactored solution.
AntiPatterns are useful in several ways:
• They provide a common vocabulary for known dysfunctional
software designs and solutions, as every AntiPattern has a short and
descriptive name like The Blob, Poltergeist or Golden Hammer.
• They help detecting problems in the code, the architecture and the
management of software projects.
• They describe both, preventive measures as well as refactored
solutions, which can save software projects in trouble.
ViewPoints:
Architectural AntiPatterns are important for the software architect. They focus on
”common problems in system structure, their consequences, and solutions”.
Management AntiPatterns are relevant for the software project manager. They
depict ”common problems and solutions due to the software organization”.
Root Causes:
Root Causes, also referred to as the ”seven deadly sins”, are software
development mistakes which lead to negative consequences like cost overruns or the
cancellation of projects. The following Root Causes can be identified:
Haste leads to compromises in software quality. Due to tight schedules and deadlines,
important activities such as unit testing and documenting source code are often
neglected in favor of ”getting things done”.
Apathy is even worse, in that it’s understood by the developers that something needs
to be improved, but they are unwilling to actually do it. This attitude of not caring
about solving known problems can have very negative consequences for the software
project.
Sloth is characterized by decisions which always favor the most simple ”solution” or
answer to a problem. The consequence is improper and obscure software design,
which ultimately forces the developers to perform system discovery (trying to find out
how the software works) instead of writing or improving code.
Object Level This is the finest-grained software-design level. At this level, the software
developer is concerned with classes, objects and their respective signatures and
implementations.
Micro-Architecture Level This level assembles a set of objects and combines them (often
using Design Patterns), creating a low-scale software architecture.
Framework Level This level is mainly concerned with macro-level architectures, which
usually involve multiple micro-architectures. The goal is to create reusable, flexible and
understandable software frameworks.
Application Level This level is concerned with the issues involving application design. The
main goal is to implement a set of functional and non-functional requirements in order to
deliver a complete application. This level often leverages one or more frameworks.
System Level This level comprises multiple applications which need to properly interact with
each other. Important issues here are life-cycle management, system evolution and especially
application compatibility issues.
Enterprise Level This is the largest architectural scale within a company and usually
involves multiple heterogeneous systems, each consisting of a set of interacting systems.
Global Level This is the largest software-design level across enterprise borders. It includes
multiple formal and de facto standards which are used by several companies, and are often
designed by consortiums or standardization bodies.
Spaghetti Code
AntiPattern Name: Spaghetti Code
Most Applicable Scale: Application
Refactored Solution Name: Software Refactoring, Code Cleanup
Refactored Solution Type: Software
Root Causes: Ignorance, Sloth
Unbalanced Forces: Management of Complexity, Change
Description:
The Spaghetti Code AntiPattern is probably the most well-known and the most
widespread AntiPattern. In the early days of computing and programming, when the
design of software systems was not very well understood and often done in an ad-hoc
style, Spaghetti Code was pervasive. But even today, in the age of object-oriented
programming (OOP) and high-level languages, the pattern still persists.
Symptoms of this AntiPattern include, but are not limited to, a) confusing,
inconsistent or barely existing code structure, b) excessive usage of global variables,
and c) the usage of GOTOs. The result is unmaintainable code, which is very hard to
read, understand and reuse.
The programmer who wrote this Java code, did not use polymorphism
properly. The classes MMSPhysComp, MMSLogComp, MMSPhysLink most
probably are (or should be) subclasses of the common parent-class MMSNode. A
more concise and more readable solution would be something like:
attribs = ( (MMSNode) obj ) . getAttribs( ) ;
The built-in polymorphism of the programming language will automatically
and transparently choose and call the respectivemethod. The code not only becomes
shorter and probably more efficient, but more importantly it becomes a lot more
readable.
Refactored Solution:
In general, the refactored solution for the Spaghetti Code AntiPattern is code
cleanup or refactoring. This comprises a multitude of activities such as renaming
classes, methods, and variables for more consistency, removing dead code, enforcing
coding standards, refactoring the code in order to be reusable.
In short, regular code cleanups should be performed during development. To
retrospectively clean up and ship a software system after months or even years of
unstructured development is definitely doomed to fail.
Cut-and-Paste Programming
AntiPattern Name: Cut-and-Paste Programming
Also Known As: Clipboard Coding, Software Cloning, Software Propagation
Most Applicable Scale: Application
Refactored Solution Name: Black Box Reuse
Refactored Solution Type: Software
Root Causes: Sloth
Unbalanced Forces: Management of Resources, Technology Transfer
Description:
Cut-and-Paste Programming is a form of code reuse and commonly practiced
in software development.However, it is also a major source of bugs and
maintainability problems. In Cut-and-Paste Programming, several similar code
fragments co-exist in the same program. These fragments usually come from code
which is copied multiple times by the developers. Each instance of such a cloned code
is then adapted to the local environment in which it is used. This bears multiple
problems:
• First, the same bugs often reoccur although they have been fixed locally multiple
times. This is the case because each instance of the copied code fragment must be
found and fixed individually. In addition to that, each code fragment needs a slightly
different bugfix, which makes software maintenance a very time consuming and error-
prone task.
• The code bloat produced by Cut-and-Paste Programming has no benefit in terms of
additional features or better code quality. On the contrary, the code becomes a lot
harder to read and understand.
• Code inspections, reviews and walkthroughs become unnecessarily long and
complicated, simply because more code needs to be analyzed. Consider this small
example from Eclipse 3.0 (an open source software development platform):
From the class SimpleLookupTable:
Both methods are identical barring the renaming of variables, thus they are good
candidates for refactoring.
Refactored Solution:
Using such tools, developers can easily find duplicated code fragments in their
projects. Manual search for code clones would be impractical for smaller projects and
next to impossible for large software projects with several million lines of code.
Refactoring is the next logical step, after code clones have been identified.
Fowler defines Refactoring as ”the process of changing a software system in such a
way that it does not alter the external behavior of the code yet improves its internal
structure.”. For the purposes of clone removal, the duplicated code is factored out to a
common function, method, or class which is then used instead of the duplicated code.
This ensures that bugs must only be fixed in one place and only once.
Now, we will discuss briefly about some other antipatterns :
More generally, "smoke and mirrors" may refer to any sort of presentation by which
the audience is intended to be deceived, such as an attempt to fool a prospective client
into thinking that one has capabilities necessary to deliver a product in question.
Software bloat:
Software bloat (or resource hog) is a term used in both a neutral and disparaging
sense, to describe the tendency of newer computer programs to be larger, or to use
larger amounts of system resources (mass storage space, processing power or
memory) than older versions of the same programs, without concomitant benefits
being provided to end users.
The latter is often blamed either on the prioritization of marketing and "headline
feature-set" over quality and focus, or the need to be perceived as adding new
functionality in the software market, which for many products relies upon the
existence of regular enhanced versions to be sold within the existing user base.
It is also used more generally to describe programs which appear to be using more
system resources than necessary. Software exhibiting these tendencies is referred to as
bloatware, resource hog or, less commonly, fatware.
General design anti-patterns
Abstraction Inversion:
Examples
The term was popularised in Brian Foote and Joseph Yoder's 1999 paper of the same
name, which defines the term thus:
Big Ball of Mud systems were usually developed over a period of time with different
individuals working on various pieces and parts. Expediency plays a major role.
Systems developed by people with no formal computer architecture or programming
training often fall into this pattern.
Foote and Yoder do not universally condemn Big Ball of Mud programming, pointing
out that this pattern is most prevalent because it works — at least for the moment.
However, programs of this pattern become maintenance nightmares.
Programmers in control of a big ball of mud project are strongly encouraged to study
it and to understand what it accomplishes and use this as a loose basis for a formal set
of requirements for the new well architected system that would be developed to
replace the former. Technology shifts (client-server to web-based, file-based to
database-based, etc.) can provide good reasons to start over from scratch.
Race Hazard:
Electronics
A typical example of a race condition may occur in a system of logic gates, where
inputs vary. If a particular output depends on the state of the inputs, it may only be
defined for steady-state signals. As the inputs change state, a finite delay will occur
before the output changes, due to the physical nature of the electronic system. For a
brief period, the output may change to an unwanted state before settling back to the
designed state. Certain systems can tolerate such glitches, but if for example this
output signal functions as a clock for further systems that contain memory, the system
can rapidly depart from its designed behavior (in effect, the temporary glitch becomes
permanent).
For example, consider a two input AND gate fed with a logic signal X on input A and
its negation, NOT X, on input B. In theory, the output (X AND NOT X) should never
be high. However, if changes in the value of X take longer to propagate to input B
than to input A then when X changes from false to true, a brief period will ensue
during which both inputs are true, and so the gate's output will also be true.
Proper design techniques (e.g. Karnaugh maps—note, the Karnaugh map article
includes a concrete example of a race condition and how to eliminate it) encourage
designers to recognise and eliminate race conditions before they cause problems.
As well as these problems, logic gates can enter metastable states, which create
further problems for circuit designers.
Types
Static race conditions
These are caused when a signal and its complement are combined together.
Dynamic race conditions
These result in multiple transitions when only one is intended. They are due to
interaction between gates (Dynamic race conditions can be eliminated by
using not more than two levels of gating).
Essential race conditions
These are caused when an input has two transitions in less than the total
feedback propagation time. Sometimes they are cured using inductive delay-
line elements to effectively increase the time duration of an input signal.
Computing
Let us assume that two threads T1 and T2 each want to increment the value of a
global integer by one. Ideally, the following sequence of operations would take place:
1. Integer i = 0;
2. T1 reads the value of i from memory into a register : 0
3. T1 increments the value of i in the register: (register contents) + 1 = 1
4. T1 stores the value of the register in memory : 1
5. T2 reads the value of i from memory into a register : 1
6. T2 increments the value of i in the register: (register contents) + 1 = 2
7. T2 stores the value of the register in memory : 2
8. Integer i = 2
In the case shown above, the final value of i is 2, as expected. However, if the two
threads run simultaneously without locking or synchronization, the outcome of the
operation could be wrong. The alternative sequence of operations below demonstrates
this scenario:
1. Integer i = 0;
2. T1 reads the value of i from memory into a register : 0
3. T2 reads the value of i from memory into a register : 0
4. T1 increments the value of i in the register: (register contents) + 1 = 1
5. T2 increments the value of i in the register: (register contents) + 1 = 1
6. T1 stores the value of the register in memory : 1
7. T2 stores the value of the register in memory : 1
8. Integer i = 1
The final value of i is 1 instead of the expected result of 2. This occurs because the
increment operations of the second case are non-atomic. Atomic operations are those
that cannot be interrupted while accessing some resource, such as a memory location.
In the first case, T1 was not interrupted while accessing the variable i, so its operation
was atomic.
global integer A = 0;
0
0
0
RX
RX
2
RX
RX
4
4
Now consider this chain of events, which might occur next:
File systems
In file systems, two or more programs may "collide" in their attempts to modify or
access a file, which could result in data corruption. File locking provides a
commonly-used solution. A more cumbersome remedy involves reorganizing the
system in such a way that one unique process (running a daemon or the like) has
exclusive access to the file, and all other processes that need to access the data in that
file do so only via interprocess communication with that one process (which of course
requires synchronization at the process level).
A different form of race hazard exists in file systems where unrelated programs may
affect each other by suddenly using up available resources such as disk space (or
memory, or processor cycles). Software not carefully designed to anticipate and
handle this rare situation may then become quite fragile and unpredictable. Such a risk
may be overlooked for a long time in a system that seems very reliable. But
eventually enough data may accumulate or enough other software may be added to
critically destabilize many parts of a system. Probably the best known example of this
occurred with the near-loss of the Mars Rover "Spirit" not long after landing, but this
is a commonly overlooked hazard in many computer systems. A solution is for
software to request and reserve all the resources it will need before beginning a task;
if this request fails then the task is postponed, avoiding the many points where failure
could have occurred. (Alternately, each of those points can be equipped with error
handling, or the success of the entire task can be verified before proceeding
afterwards.) A more common but incorrect approach is to simply verify that enough
disk space (for example) is available before starting a task; this is not adequate
because in complex systems the actions of other running programs can be
unpredictable.
Networking
In networking, consider a distributed chat network like IRC, where a user acquires
channel-operator privileges in any channel he starts. If two users on different servers,
on different ends of the same network, try to start the same-named channel at the
same time, each user's respective server will grant channel-operator privileges to each
user, since neither server will yet have received the other server's signal that it has
allocated that channel.
In this case of a race condition, the concept of the "shared resource" covers the state
of the network (what channels exist, as well as what users started them and therefore
have what privileges), which each server can freely change as long as it signals the
other servers on the network about the changes so that they can update their
conception of the state of the network. However, the latency across the network
makes possible the kind of race condition described. In this case, heading off race
conditions by imposing a form of control over access to the shared resource—say,
appointing one server to control who holds what privileges—would mean turning the
distributed network into a centralized one (at least for that one part of the network
operation). Where users find such a solution unacceptable, a pragmatic solution can
have the system 1) recognize when a race condition has occurred; and 2) repair the ill
effects.
Magic PushButton:
A better way to do this is to refactor the business logic (in this example storing the
filename to the registry) into a separate class.
type
TPreferences = class
private
FFilename: string;
procedure SetFilename(const Value: string);
public
property Filename:string read FFilename write SetFilename;
procedure Load;
procedure Save;
end;
and call this class Save method from the Click handler:
God Object:
The basic idea behind structured programming is that a big problem is broken down
into many smaller problems (divide and conquer) and solutions are created for each of
them. If you are able to solve all of the small problems, you have solved the big
problem as a whole. Therefore there is only one object about which an object needs to
know everything: itself. And there is only one problem an object needs to solve: its
own.
God object-based code does not follow this approach. Instead, much of a program's
overall functionality is coded into a single object. Because this object holds so much
data and has so many methods, its role in the program becomes God-like (all-
encompassing).
Instead of objects communicating amongst themselves directly, the other objects rely
on the God object. Because the God object is referenced by so much of the other code,
maintenance becomes more difficult than it otherwise would.
More generally, the yo-yo problem can also refer to any situation where a person must
keep flipping between different sources of information in order to understand a
concept.
Programming anti-patterns
Blind Faith:
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 harddisk
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. Another common name for Blind Faith is
"God oriented programming" or "Divine orientation".
Blind faith programming can also be used as a challenge to test programming skills.
• it is not evident when reading the program which globals serve as arguments,
• programmers can forget to set a value before invoking,
• the state can change in a moment between setting and invoking, particularly
when programming with threading,
• the global variables may be corrupted by being unintentionally used to pass
values to more than one subroutine.
• it makes the use of recursion much more awkward.
Some older programming languages make this style hard to avoid: this is the only way
of passing arguments to a paragraph in BASIC (using GOSUB) or COBOL.
Ravioli Code:
Some consider ravioli code to be a good design methodology, especially when the
components used are modular, highly interchangeable, well-encapsulated, and
providing well-defined interfaces and behavior. Others consider ravioli code to be an
anti-pattern. In poorly-designed object-oriented systems, with deep inheritance
hierarchies and multiple layers of virtual functions overriding each other, it can
become very difficult to discern (without use of a debugger) exactly what the
behavior of the program is, as it is often unclear how virtual function calls are
resolved.
Conclusion:
References:
http://www.hermann-uwe.de/files/antipatterns.pdf
http://www.eclipse.org
http://www.ioccc.org