$30 off During Our Annual Pro Sale. View Details »

Anti-Patterns Found in Your Object-Oriented Codebase

Anti-Patterns Found in Your Object-Oriented Codebase

If you ask some developers, following a functional programming paradigm will solve all your problems. Others will tell you OOP is the way to go. The reality is there are good things found in all paradigms. There are also bad things. This talk will cover examples of the most egregious anti-patterns found in almost every object-oriented codebase ever written. We will talk about what they are, why they are made, and how you can avoid them. Walking out of the room you should be able to spot problems in your own code and the code of your peers that you can fix tomorrow to make your codebase a better place.

Jeff Carouth

October 21, 2015
Tweet

More Decks by Jeff Carouth

Other Decks in Programming

Transcript

  1. PRESENTED BY
    JEFF CAROUTH
    @jcarouth
    Anti-Patterns Found in your
    Object oriented codebase

    View Slide

  2. Today we'll look at several
    patterns found in object-
    oriented codebases
    which may be considered
    undesirable.

    View Slide

  3. anti pattern
    A common solution to a recurring problem
    which might seem like an appropriate response
    but often brings more consequences than
    benefits.

    View Slide

  4. null reference

    View Slide

  5. null
    Word of the day
    adjective
    1. the absence of a known value

    2. empty

    View Slide

  6. $object = null;
    $object->doSomethingMagical();
    Pop Quiz
    1. What does the following code snippet output?
    a) An Elephpant

    b) null

    c) Parse error: syntax error, unexpected '::' (T_PAAMAYIM_NEKUDOTAYIM)

    d) Fatal error: Call to a member function doSomethingMagical() on null

    e) All of the above

    f) None of the above

    g) I do not know

    View Slide

  7. ! php null.php
    Fatal error: Call to a member function doSomethingMagical() on null
    in /Users/jcarouth/null.php on line 3
    Call Stack:
    0.0006 227176 1. {main}() /Users/jcarouth/null.php:0

    View Slide

  8. Right now you're
    thinking: but I would
    never assign null to an
    variable and try to use it
    like it's an object.

    View Slide

  9. class UserRepository
    {
    public function findByEmail($email)
    {
    $statement = $this->db->prepare(
    'SELECT * FROM users WHERE email = :email'
    );
    $statement->execute(['email' => $email]);
    if ($statement->rowCount() === 0) {
    return null;
    }
    return new User($statement->fetch());
    }
    }

    View Slide

  10. class
    {
    }
    if ($statement->rowCount() === 0) {
    return null;
    }

    View Slide

  11. Why does it matter?
    $userRepository = new UserRepository(new PDO(/*...*/));
    $email = '[email protected]';
    $user = $userRepository->findByEmail($email);
    if (null === $user) {
    header("HTTP/1.1 404 Not Found");
    exit;
    }
    //do something magical with the $user object

    View Slide

  12. Why does it matter?
    $userRepository
    $email
    $user
    if
    }
    //do something magical with the $user object
    if (null === $user) {
    header("HTTP/1.1 404 Not Found");
    exit;
    }

    View Slide

  13. Why does it matter?
    $userRepository
    $email
    $user
    if
    }
    //do something magical with the $user object
    $email = 'doesnotexist';
    $user = $userRepository->findByEmail($email);

    View Slide

  14. Why does it matter?
    $userRepository
    $email
    $user
    if
    }
    //do something magical with the $user object
    $email = 'doesnotexist';
    $user = null;

    View Slide

  15. Semantic Method Names
    class UserRepository
    {
    /**
    * @return User|null
    */
    public function findByEmail($email)
    {
    /*...*/
    }
    }

    View Slide

  16. Semantic Method Names
    class UserRepository
    {
    /**
    * @return User|null
    */
    public function findByEmailOrNullIfNotFound($email)
    {
    /*...*/
    }
    }

    View Slide

  17. Alternative #1: Throw Exceptions
    Instead of returning a null you can use built-in
    exception handling to properly handle the situation
    for you. Object-oriented languages have exception
    handling to get away from the imperative style
    error handling.

    View Slide

  18. class ResourceNotFoundException extends \Exception { }
    class UserRepository
    {
    public function findByEmail($email)
    {
    $statement = $this->db->prepare(
    'SELECT * FROM users WHERE email = :email'
    );
    $statement->execute(['email' => $email]);
    if ($statement->rowCount() === 0) {
    throw new ResourceNotFoundException();
    }
    return new User($statement->fetch());
    }
    }

    View Slide

  19. class
    class
    {
    }
    if ($statement->rowCount() === 0) {
    throw new ResourceNotFoundException();
    }

    View Slide

  20. How does that change the client code?
    $userRepository = new UserRepository(new PDO(/*...*/));
    $email = '[email protected]';
    $user = $userRepository->findByEmail($email);
    if (null === $user) {
    header("HTTP/1.1 404 Not Found");
    exit;
    }
    //do something magical with the $user object

    View Slide

  21. How does that change the client code?
    try {
    $userRepository = new UserRepository(new PDO(/*...*/));
    $email = '[email protected]';
    $user = $userRepository->findByEmail($email);
    $user->doSomethingMagical();
    } catch (ResourceNotFoundException $e) {
    header("HTTP/1.1 404 Not Found");
    exit;
    }

    View Slide

  22. Using exceptions allows
    you to let the caller or
    other upstream callers
    handle the situation
    appropriately.

    View Slide

  23. How does that change the client code?
    try {
    $userRepository = new UserRepository(new PDO(/*...*/));
    $email = '[email protected]';
    $user = $userRepository->findByEmail($email);
    $user->doSomethingMagical();
    } catch (ResourceNotFoundException $e) {
    header("HTTP/1.1 404 Not Found");
    exit;
    }

    View Slide

  24. Alternative #2: Return a Null Object
    The Null Object pattern is sometimes useful when
    you can use an object which implements neutral
    behavior in place of another object.

    View Slide

  25. Using a Null User to Represent Guests
    class User
    {
    public function isRegistered()
    {
    return true;
    }
    }
    class NullUser
    {
    public function isRegistered()
    {
    return false;
    }
    }

    View Slide

  26. Using a Null User to Represent Guests
    class UserRepository
    {
    public function findByEmail($email)
    {
    $statement = $this->db->prepare(
    'SELECT * FROM users WHERE email = :email'
    );
    $statement->execute(['email' => $email]);
    if ($statement->rowCount() === 0) {
    return new NullUser();
    }
    return new User($statement->fetch());
    }
    }

    View Slide

  27. utility classes

    View Slide

  28. There's nothing inherently
    wrong with utility classes.
    They just aren't OOP.

    View Slide

  29. class StatTracker
    {
    public function __construct($trackerFile)
    {
    $this->trackerFile = $trackerFile;
    }
    public function trackEvent($event, $value)
    {
    $data = json_encode(
    ['event' => $event, 'value' => $value]
    );
    FileUtils::write($this->trackerFile, $data);
    }
    }

    View Slide

  30. class
    {
    [
    )
    }
    FileUtils::write($this->trackerFile, $data);

    View Slide

  31. class FileUtils
    {
    public static function write($file, $content)
    {
    $fileHandle = fopen($file, 'a');
    fwrite($fileHandle, $content);
    fclose($fileHandle);
    }
    }

    View Slide

  32. class FileUtils
    {
    public static function write($file, $content)
    {
    $fileHandle = fopen($file, 'a');
    fwrite($fileHandle, $content);
    fclose($fileHandle);
    }
    }
    $statTracker = new StatTracker('/path/to/stats.txt');
    $statTracker->trackEvent('used a util class', 'jcarouth');

    View Slide

  33. What's the problem then?
    Right now, nothing. Unless you're worried about
    testability. It's a minor inconvenience but you'll
    have to have a real filesystem because of the
    static method call.

    View Slide

  34. Alternative: Create an Object to deal
    with your FileSystem abstraction
    FileUtility::write()
    becomes

    (new FileManager())->write()

    View Slide

  35. class FileManager
    {
    public function __construct($file)
    {
    $this->path = $file;
    }
    public function write($content)
    {
    fwrite($this->handle, $content);
    }
    public function open($mode = 'r')
    {
    $this->handle = fopen($this->file, $mode);
    return $this;
    }
    public function close($handle)
    {
    fclose($this->handle);
    }
    }

    View Slide

  36. class StatTracker
    {
    public function __construct($trackerFile)
    {
    $this->trackerFile = $trackerFile;
    }
    public function trackEvent($event, $value)
    {
    $data = json_encode(
    ['event' => $event, 'value' => $value]
    );
    $fileManager = new FileManager($this->trackerFile);
    $fileManager->open('a')->write($data);
    }
    }

    View Slide

  37. class FileManager
    {
    public function __construct($file)
    {
    $this->path = $file;
    }
    public function write($content)
    {
    fwrite($this->handle, $content);
    }
    public function open($mode = 'r')
    {
    $this->handle = fopen($this->file, $mode);
    return $this;
    }
    public function close($handle)
    {
    fclose($this->handle);
    }
    }

    View Slide

  38. class TextFile
    {
    protected $path;
    public function __construct($file)
    {
    $this->file = $file;
    }
    public function write($content)
    {
    $file = $this->open('a');
    fwrite($file, $content);
    $this->close($file);
    }
    protected function open($mode = 'r') { /** snip **/ }
    protected function close($handle) { /** snip **/ }
    }

    View Slide

  39. (new FileManager('/path/to/file.txt'))
    ->open('a')
    ->write('i know details');
    (new TextFile('/path/to/file.txt'))
    ->write('better');

    View Slide

  40. class StatTracker
    {
    public function __construct(TextFile $file)
    {
    $this->trackerFile = $file;
    }
    public function trackEvent($event, $value)
    {
    $data = json_encode(
    ['event' => $event, 'value' => $value]
    );
    $this->trackerFile->write($data);
    }
    }

    View Slide

  41. class
    {
    [
    )
    }
    public function __construct(TextFile $file)
    $this->trackerFile->write($data);

    View Slide

  42. class
    {
    [
    )
    }
    $statTracker = new StatTracker(new TextFile('/path/to/stats.txt'));
    $statTracker->trackEvent('made a real object', 'jcarouth');

    View Slide

  43. Fear of Adding Classes

    View Slide

  44. The manifestation of this
    anti pattern is in classes
    that have large
    complexity due to poor
    abstraction.

    View Slide

  45. class DataExport
    {
    public function __construct($type, $fileName = "report")
    {
    $this->fileName = $fileName;
    $this->outputType = strtolower($type);
    switch ($this->outputType) {
    case 'csv':
    case 'txt':
    $this->contentType = 'application/csv';
    break;
    case 'xls':
    $this->contentType = 'application/vnd.ms-excel';
    break;
    default:
    throw new Exception('Unsupported export type');
    }
    }
    }

    View Slide

  46. class DataExport
    {
    public function outputFromArray($data)
    {
    $firstRow = current($data);
    $columns = array_keys($firstRow);
    switch ($this->outputType) {
    case 'csv':
    case 'txt':
    //create text body
    break;
    case 'xls':
    //build xls file
    break;
    }
    }
    }

    View Slide

  47. Alternative: Extract objects from the
    switch statements

    View Slide

  48. class DataExport
    {
    public function outputFromArray($data)
    {
    $firstRow = current($data);
    $columns = array_keys($firstRow);
    switch ($this->outputType) {
    case 'csv':
    case 'txt':
    //create text body
    break;
    case 'xls':
    //build xls file
    break;
    }
    }
    }

    View Slide

  49. interface FileExport
    {
    public function outputFromArray($data);
    }
    class CsvExportFile implements FileExport
    {
    public function __construct($name)
    {
    $this->name = $name;
    }
    public function outputFromArray($data)
    {
    //create the text body
    }
    }
    class XlsExportFile implements FileExport {}

    View Slide

  50. class FileExportFactory
    {
    public static function create($type, $name)
    {
    switch (strtolower($type)) {
    case 'csv':
    case 'txt':
    return new CsvExportFile($name);
    break;
    case 'xls':
    return new XlsExportFile($name);
    break;
    }
    }
    }

    View Slide

  51. exposing getters and
    setters

    View Slide

  52. Getters and setters are
    not always wrong, but
    often they lead to other
    problems.

    View Slide

  53. class Product
    {
    protected $id;
    protected $price;
    public function __construct($id, Price $price)
    {
    $this->id = $id;
    $this->price = $price;
    }
    public function getPrice()
    {
    return $this->price;
    }
    public function setPrice(Price $price)
    {
    $this->price = $price;
    }
    }

    View Slide

  54. class Price
    {
    public $value;
    public $currency;
    public function __construct($value, $currency)
    {
    $this->value = $value;
    $this->currency = $currency;
    }
    }

    View Slide

  55. How do you reduce the
    price of a Product? Let's
    say we want to put a
    particular object on sale
    for 20% off.

    View Slide

  56. $product = new Product('12345', new Price(9999, 'USD'));
    $salePrice = new Price(
    intval(round($product->getPrice()->value * 0.80, 0)),
    $product->getPrice()->currency
    );
    $product->setPrice($salePrice);

    View Slide

  57. Wouldn't it be cool if I
    could just tell a product
    to reduce its price by 20
    percent? Why can't I?

    View Slide

  58. class Product
    {
    public function price()
    {
    return $this->price;
    }
    public function reducePriceByPercentage($salePercent)
    {
    $multiplier = 1 - ($salePercent / 100.00);
    $this->price = new Price(
    intval(
    round(
    $product->getPrice()->value * $multiplier,
    0
    )
    ),
    $this->price->value
    );
    }
    }

    View Slide

  59. $product = new Product('12345', new Price(9999, 'USD'));
    $product->reducePriceByPercentage(20);

    View Slide

  60. recap

    View Slide

  61. null references can be
    problematic for client
    code and in an object-
    oriented world can be
    mitigated with use of
    exceptions or null objects

    View Slide

  62. Purely utility objects are
    sometimes damaging to
    object-oriented
    codebases and should be
    extracted to proper
    abstractions when
    possible

    View Slide

  63. Do not be afraid to think
    about places where you
    are adding complexity by
    avoiding adding classes.
    Classes are not the
    enemy of object-oriented
    code.

    View Slide

  64. Exposing internals of
    objects can leak details
    and increase surface area
    of objects. Codifying the
    behaviors you expect as
    the interface avoids this
    situation entirely.

    View Slide

  65. Thank You
    @jcarouth

    View Slide