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.
notion image
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
notion image

Patterns are abstractions of experiences

Anti patterns are more difficult to draft, but rapidly lead to sharing of experiences
notion image
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
 
notion image

Development Anti pattern

notion image

🍝 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
notion image

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!
notion image
notion image
 

👻 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 Orderabout 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.

Mini Antipattern ( Is coming..)

notion image
 

© 2021 - 2024 · Khanh Tran · 顔エンジン · tran_khanh@outlook.com