Anti pattern ký
date
May 30, 2022
slug
antipattern-learning
status
Published
tags
programming
design theory
leadership
operation
summary
Good software structure is essential for system extension and maintenance. Software development is a chaotic activity, therefore the implemented structure of systems tends to stray from the planned structure as determined by architecture, analysis, and design
type
Post
What is Anti-pattern ( and why should I care)?
Anti pattern are negative solutions that present more problems than they address. They are a natural extension to design-pattern, bridge the gap between architectural concepts and real world implementation. Understanding antipattern provides the knowledge to prevent or recover from them.
Why are Antipattern so pervasive?
Given a particular context, one has options. Make something up, or use a “Pattern” that has worked before. This pattern is an Antipattern if it produces more problems than it solves.
AntiPatterns are based on a rhetorical structure that differs from simple patterns. AntiPatterns begin with a compelling, problematic solution. From this solution, a discussion of the root causes records how the problematic solution is the result of incorrectly resolving the forces for a specific underlying set of problems within its context.
This convergence from a concrete situation to the more abstract underlying forces is a key component in communicating an understanding of how and why the problem exists. This abstraction is composed of symptoms and as consequences, similar to the context and forces of a design pattern, but which clearly documents the implications of the problematic solution.
The documented symptoms can then be critical cues used in the diagnosis and recognition of a specific problematic solution, or, AntiPattern. Finally, once an AntiPattern has been correctly identified, it’s refactored solution can be used to obtain a better convergence of the underlying forces to lead you to a better understanding of the problem and an effective method of resolving the problematic solution
Patterns are abstractions of experiences
Anti patterns are more difficult to draft, but rapidly lead to sharing of experiences
Where patterns generally assume a specific context comprised of few, if any, pre-existing concerns which affect the design of a system, experience has demonstrated that situations with legacy and existing problems are much more commonplace in practice.
In contrast to Design Patterns, AntiPatterns, start with an existing “negative solution” or legacy approach, such as may be the result of a manager or developer’s ignorance or inexperience in solving a particular type of problem, or perhaps having applied a perfectly good design pattern in a wrong context.
The AntiPattern then amplifies the negative solution in a way that helps organizations recognize and understand a problematic situation, its symptoms, and its ultimate consequences.
The AntiPattern then presents a common solution that refactors the system to maximize benefits and minimize consequences.
Yesterday’s hot solution can become today’s Anti-Pattern
Development Anti pattern
🍝 Spaghetti code
Spaghettis is an documented piece of software source code that cannot be extended or modified without extreme difficulty dude to its convoluted structure.
- Un-structured code is a liability
- Well structured code is an assess
Common Symptoms:
- quick demonstrate on code that became operational
- ‘Lone Ranger’ programmer?
- Obsolete or scanty documentation
- 50% of maintenance spent on system rediscovery
- Hesitant programmer syndrome
- More likely to break it than extend it
- Easier to just rewire it
- Cannot be reused
- System software and COST packages can’t be upgraded
- performance continue be optimized
- User work arounds
OOP symptoms:
- Many object methods with no parameters.
- Suspicions class or global variables
- interwind and unforeseen relationships between objects
- process-oriented method, object with process-oriented names.
- OO advantage lost - inheritance cannot be used to extend the system, polymorphism not effective either.
OOP symptoms and strategy: 🧑💼Reform the software process
- Refactoring as your program (Beck 1996)
- Incremental development
- refactoring to move structure
- incremental test
- iterate
- Use programming discipline (Humphrey 1995)
- keep track of defects (metrics)
- learn to avoid programming defects
- Use Architecture-Centred development (Booch 1996)
- define enforceable system boundaries
- use design pattern to document software
OOP symptoms and treatments:
- Refactor to generalize: create an abstract superclass:
- Refactor to specialize: Simplify conditionals
- Refactor to combine: capture aggregations and components
References: Spaghetti Code (sourcemaking.com)
The Blob ( or 🧔♀️God class)
Root Causes: Sloth, rush
OOP symptoms:
- Single class with many attribute and operations. ( >60 attributes)
- Controller class with simple, data object classes
- Lack of OO design
- A migrated legacy design
Use case: Reeo
From last year we had a MVP project and must get it done in 😨 5 week. Struggles in early weeks with a ton of business things. We had no time to build a neatly product. So we have no choice to accepted Anti pattern Blob. After first release, we wanted to add more features and nightmare was coming!
Main file: blob-example (github.com)
👻 Poltergeists ( the opposite of the 🧔♀️God class)
Root Causes: Sloth, Ignorance
OOP symptoms:
- Redundant navigation paths.
- Transient associations.
- Stateless classes.
- Temporary, short-duration objects and classes.
- Excessive complexity
- Unstable analysis and design models.
- Poor system performance
- Lack of system extensibility
OOP symptoms and treatments:
- Refactor to eliminate irrelevant classes:
- Delete external classes
- Delete classes with no domain relevance
- Refactor to eliminate transient “data classes”
- Refactor to eliminate “Operation classes”
- Refactor other classes with short life cycles or few responsibilities
- Mote into collaborating classed
- Regroup into cohesive larger classes
Examples:
Engineer manager
public class EngineerManager
{
private Delivery delivery;
public EngineerManager(Delivery delivery)
{
this.delivery= delivery;
}
public void DeliveryWebFeature()
{
delivery.deliveryWebFeature();
}
public void DeliveryMobileFeature()
{
delivery.deliveryMobileFeature();
}
}
The “EngineerManager” class delegates its work to another class, the “Delivery ” class, and so there is little point in having a separate “EngineerManager” object.
To get rid of the EngineerManager class in your code, you should use the Delivery class in places where you use the EngineerManager class. Once you have identified and change all those places, remove the poltergeist class.
Order controller from:
<?php
class OrderController
{
public function payOrder($orderId)
{
if(isset($_POST['Order'])) {
$order = Order::findOrFail($orderId);
$invoice = new Invoice();
$invoice->setOrder($order);
$invoice->setUser(Auth::user());
if($invoice->save()) {
$email = new Email();
$email->setSubject('Invoice for order#' . $order->id);
// ...
if($email->send()) {
$this->redirectTo('orders');
} else {
// ... fail
}
} else {
// ... fail
}
}
}
}
I can bet that you have written this sort of code, or surely have met it in some application. The code doesn’t look very readable with all these nested conditionals. So, we decided to refactor it and replace the payment process into the object and then call a method on it. The idea looks great and SOLID: our object will have a single responsibility - payment process.
<?php
class InvoicePaymentHandler
{
public function make(array $data)
{
$order = Order::findOrFail($orderId);
$invoice = new Invoice();
$invoice->setOrder($order);
$invoice->setUser(Auth::user());
if($invoice->save()) {
$email = new Email();
$email->setSubject('Invoice for order#' . $order->id);
...
return $email->send();
}
return false;
}
}
And the controller now looks very nice and clear. We have removed all this messy code behind the
make
method call. Now the controller follows Tell, Don’t Ask Principle. It tells the instance of InvoicePaymentHandler
to make an invoice, and behind the scenes, this instance validates data from the request, creates an invoice record in the database and on success sends an email with payment details.<?php
class OrderController
{
public function payOrder($orderId)
{
if($_POST['Order']) {
$invoicePayment = new InvoicePaymentHandler();
if($invoicePayment->make($_POST['Order'])) {
$this->redirectTo('orders');
}
$errors = $invoicePayment->getErrors();
$this->redirectBack($errors);
}
}
}
So, what’s wrong here? Take a look at the 👻poltergeist description and then at the
InvoicePaymentHandler
class. This class doesn’t carry any internal state and its only responsibility is to trigger methods on the other objects. As a result, we have added an unneeded layer of abstraction in the whole application architecture, just to replace some code into another place. We think that we followed Single Responsibility Principle and Tell, Don’t Ask Principle, but instead, we have an object-oriented container for some procedural code.InvoicePaymentHandler
class doesn’t play any solid role in the application, it is used only as a container for some code and this code is hardcoded there, so there is no way to reuse InvoicePaymentHandler
in the system. - 😨Now we have extra code to maintain and test.
- 🤔It is often hard to read and understand code with a poltergeist, because at first, we need to find out what poltergeist does and then mentally replace it to see the real code flow.
To remove a 👻poltergeist, delete the class and insert its functionality in the invoked class:
<?php
class OrderController
{
public function payOrder($orderId)
{
if($_POST['Order']) {
$order = Order::findOrFail($orderId);
try {
$order->makeInvoice(
$_POST['Order'], Auth::user()
);
} catch(InvoiceCreationException $e) {
$this->redirectBack(
$e->getMessage()
);
}
$this->redirectTo('orders');
}
}
}
We have delegated the process of invoice creation to
Order
class. Previously we had to ask Order
about its state (total products and their price), now Order
itself can create an invoice according to its own state. Then we can trigger an event and send an email. There is no more need in InvoicePaymentHandler
class. We have also removed the process of payment creation out of the controller and now can reuse it in another place of our application.Poltergeist vs Commands At first sight, poltergeist objects looks very similar to Command Pattern. The key difference is that commands are often more generic and can contain some state to be reused. On the opposite poltergeist objects are often special purpose objects with a single method, they exist to make some noise in the system and then they disappear. You can treat poltergeists as crunches that help to construct or initialize other objects. Poltergeists represent a static action, but commands represent a configurable action.