Your code sucks,
   let’s !x it!
   Object Calisthenics and Code readability




            Rafael Dohms
               @rdohms
Rafael Dohms




                           photo credit: Eli White
        @rdohms


 Evangelist, Speaker and
      Contributor.

Developer at WEBclusive.

Enabler at AmsterdamPHP.
http://conference.phpbenelux.eu/2013/



     25th and 26th of January

Early bird ends on Thursday the 6th!
Why does my
code suck?
Is it Readable?


Why does my
code suck?
Is it Readable?


Why does my
code suck?
        Is it Testable?
Is it Maintainable?   Is it Readable?


           Why does my
           code suck?
                      Is it Testable?
Is it Maintainable?   Is it Readable?


           Why does my
           code suck?
  Is it Reusable?     Is it Testable?
<?php                                                 Does it look like this?
$list=mysql_connect("******","*******","*****");
if(!$list)echo 'Cannot login.';
else{
    mysql_select_db("******", $list);
    $best=array("0","0","0","0","0","0");
    $id=mysql_num_rows(mysql_query("SELECT * FROM allnews"));
    $count=0;
    for($i=0;$i<6;$i++){
        while(mysql_query("SELECT language FROM allnews WHERE id=$id-$count")!="he")$count+
+;
        $best[$i]=mysql_query("SELECT id FROM allnews WHERE id=$id-$count");}
    $id=$id-$count;
    $maxdate=mktime(0,0,0,date('m'),date('d')-7,date('Y'));
    while(mysql_query("SELECT date FROM allnews WHERE id=$id-$count")>=$maxdate){
        if(mysql_query("SELECT language FROM allnews WHERE id=$id-$count")=="he"){
            $small=$best[0];
            while($i=0;$i<6;$i++){
                if(mysql_query("SELECT score FROM allnews WHERE id=
$small)"<mysql_query("SELECT score FROM allnews WHERE id=$best[i+1]"))
                         $small=$best[i+1];}
                    if(mysql_query("SELECT score FROM allnews WHERE id=
$small")<mysql_query("SELECT score FROM allnews WHERE id=$id-$count")){
                         while($i=0;$i<6;$i++){
                             if($small==$best[i])$best[i]=mysql_query("SELECT id FROM
allnews WHERE id=$id-$count");}}}}
    while($i=0;$i<6;$i++)
    echo '<a href="news-page.php?id='.$best[i].'"><div class="box '.mysql_query("SELECT
type FROM allnews WHERE id=$best[i]").'">'.mysql_query("SELECT title FROM allnews WHERE id=
$best[i]").'<div class="img" style="background-image:url(images/'.mysql_query("SELECT
image1 FROM allnews WHERE id=$best[i]").');"></div></div></a>';
    mysql_close($list);
}
?>
<?php                                                 Does it look like this?
$list=mysql_connect("******","*******","*****");
if(!$list)echo 'Cannot login.';
else{
    mysql_select_db("******", $list);
    $best=array("0","0","0","0","0","0");
    $id=mysql_num_rows(mysql_query("SELECT * FROM allnews"));
    $count=0;
    for($i=0;$i<6;$i++){
        while(mysql_query("SELECT language FROM allnews WHERE id=$id-$count")!="he")$count+
+;
        $best[$i]=mysql_query("SELECT id FROM allnews WHERE id=$id-$count");}
    $id=$id-$count;
    $maxdate=mktime(0,0,0,date('m'),date('d')-7,date('Y'));
    while(mysql_query("SELECT date FROM allnews WHERE id=$id-$count")>=$maxdate){
        if(mysql_query("SELECT language FROM allnews WHERE id=$id-$count")=="he"){
            $small=$best[0];
            while($i=0;$i<6;$i++){
                if(mysql_query("SELECT score FROM allnews WHERE id=
$small)"<mysql_query("SELECT score FROM allnews WHERE id=$best[i+1]"))
                         $small=$best[i+1];}
                    if(mysql_query("SELECT score FROM allnews WHERE id=
$small")<mysql_query("SELECT score FROM Rebecca WHERE id=$id-$count")){
                                      If allnews Black was a developer
                         while($i=0;$i<6;$i++){
                             if($small==$best[i])$best[i]=mysql_query("SELECT id FROM
allnews WHERE id=$id-$count");}}}}
    while($i=0;$i<6;$i++)
    echo '<a href="news-page.php?id='.$best[i].'"><div class="box '.mysql_query("SELECT
type FROM allnews WHERE id=$best[i]").'">'.mysql_query("SELECT title FROM allnews WHERE id=
$best[i]").'<div class="img" style="background-image:url(images/'.mysql_query("SELECT
image1 FROM allnews WHERE id=$best[i]").');"></div></div></a>';
    mysql_close($list);
}
?>
How do we fix it?
Object Calisthenics
cal·is·then·ics - noun - /ˌkaləsˈTHeniks/
     Calisthenics are a form of dynamic
      exercise consisting of a variety of
    simple, often rhythmical, movements,
    generally using minimal equipment or
                  apparatus.



Object Calisthenics
cal·is·then·ics - noun - /ˌkaləsˈTHeniks/
     Calisthenics are a form of dynamic
      exercise consisting of a variety of
    simple, often rhythmical, movements,
    generally using minimal equipment or
                  apparatus.



Object Calisthenics
                             A variety of simple, often
                          rhythmical, exercises to achieve
                           better OO and code quality
“So here’s an exercise that can help you to internalize
      principles of good object-oriented design and actually
                                       use them in real life.”

                                                -- Jeff Bay




Object Calisthenics
“So here’s an exercise that can help you to internalize
      principles of good object-oriented design and actually
                                       use them in real life.”

                                                -- Jeff Bay




Object Calisthenics
                            Important:

                      Written for JAVA

            Adaptations are needed for PHP, Ruby,
                          JS, Python
Object Calisthenics
         +
 Readability Tips
“You need to write code that minimizes the time it would


Object Calisthenics
          take someone else to understand it—even if that
                                     someone else is you.”



         +
          -- Dustin Boswell and Trevor Foucher




 Readability Tips
“You need to write code that minimizes the time it would


Object Calisthenics
          take someone else to understand it—even if that
                                     someone else is you.”



         +
          -- Dustin Boswell and Trevor Foucher




 Readability Tips
                                I’m a tip
Disclaimer:
“These are guidelines, not rules”

     * Currently adapted to PHP
OC #1
“Only one indentation
  level per method”
function validateProducts($products) {

    // Check to make sure that our valid fields are in there
    $requiredFields = array(
        'price',
        'name',
        'description',
        'type',
    );

    $valid = true;

    foreach ($products as $rawProduct) {

        $fields = array_keys($rawProduct);

        foreach ($requiredFields as $requiredField) {
            if (!in_array($requiredField, $fields)) {
                $valid = false;
            }
        }
    }

    return $valid;
}
function validateProducts($products) {

        // Check to make sure that our valid fields are in there
        $requiredFields = array(
            'price',
            'name',
            'description',
            'type',
        );

        $valid = true;

    0   foreach ($products as $rawProduct) {

            1   $fields = array_keys($rawProduct);

                foreach ($requiredFields as $requiredField) {
                  2 if (!in_array($requiredField, $fields)) {
                      3 $valid = false;
                    }
                }
        }

        return $valid;
}
function validateProducts($products) {

        // Check to make sure that our valid fields are in there
        $requiredFields = array(
            'price',
            'name',
            'description',
            'type',
        );

        $valid = true;

    0   foreach ($products as $rawProduct) {

            1   $fields = array_keys($rawProduct);

                foreach ($requiredFields as $requiredField) {
                  2 if (!in_array($requiredField, $fields)) {
                      3 $valid = false;
                    }
                }
        }

        return $valid;
}
function validateProducts($products) {

    // Check to make sure that our valid fields are in there
    $requiredFields = array(
        'price',
        'name',
        'description',
        'type',
    );

    $valid = true;

    foreach ($products as $rawProduct) {
        $validationResult = validateSingleProduct($rawProduct, $requiredFields);

        if ( ! $validationResult){
            $valid = false;
        }
    }

    return $valid;
}

function validateSingleProduct($product, $requiredFields)
{
    $valid = true;

    $fields = array_keys($rawProduct);

    foreach ($requiredFields as $requiredField) {
        if (!in_array($requiredField, $fields)) {
            $valid = false;
        }
    }
    return $valid;
}
function validateProducts($products) {

    // Check to make sure that our valid fields are in there
    $requiredFields = array(
        'price',
        'name',
        'description',
        'type',
    );

    $valid = true;

        whitespace
    foreach ($products as $rawProduct) {
        $validationResult = validateSingleProduct($rawProduct, $requiredFields);

        if ( ! $validationResult){
            $valid = false;
        }
    }

    return $valid;
}

function validateSingleProduct($product, $requiredFields)
{
    $valid = true;

    $fields = array_keys($rawProduct);

    foreach ($requiredFields as $requiredField) {
        if (!in_array($requiredField, $fields)) {
            $valid = false;
        }
    }
    return $valid;
}
function validateProducts($products) {

    // Check to make sure that our valid fields are in there
    $requiredFields = array(
        'price',
        'name',
        'description',
        'type',
    );

    $valid = true;

        whitespace
    foreach ($products as $rawProduct) {
        $validationResult = validateSingleProduct($rawProduct, $requiredFields);

        if ( ! $validationResult){
            $valid = false;
        }
    }

    return $valid;
}

function validateSingleProduct($product, $requiredFields)
{
    $valid = true;         duplicated logic
    $fields = array_keys($rawProduct);

    foreach ($requiredFields as $requiredField) {
        if (!in_array($requiredField, $fields)) {
            $valid = false;
        }
    }
    return $valid;
}
function validateProducts($products) {

      // Check to make sure that our valid fields are in there
      $requiredFields = array(
          'price',
          'name',
          'description',
          'type',
      );

      $valid = true;

    0 foreach ($products as
          whitespace
          $validationResult
                                $rawProduct) {
                                = validateSingleProduct($rawProduct, $requiredFields);

          1   if ( ! $validationResult){

              }
               2  $valid = false;

      }

      return $valid;
}

function validateSingleProduct($product, $requiredFields)
{
    $valid = true;               duplicated logic
      $fields = array_keys($rawProduct);

    0 foreach(!in_array($requiredField, $fields))
              ($requiredFields as $requiredField)     {

        1 if                                          {

          } 2
              $valid = false;

      }
      return $valid;
}
function validateProducts($products) {

      // Check to make sure that our valid fields are in there
      $requiredFields = array(
          'price',
          'name',
          'description',
          'type',
      );

      $valid = true;

    0 foreach ($products as
          $validationResult
                                $rawProduct) {
                                = validateSingleProduct($rawProduct, $requiredFields);

          1   if ( ! $validationResult){

              }
               2  $valid = false;

      }

      return $valid;
}

function validateSingleProduct($product, $requiredFields)
{
    $valid = true;

      $fields = array_keys($rawProduct);

    0 foreach(!in_array($requiredField, $fields))
              ($requiredFields as $requiredField)     {

        1 if                                          {

          } 2
              $valid = false;

      }
      return $valid;
}
function validateProducts($products) {

      // Check to make sure that our valid fields are in there
      $requiredFields = array(
          'price',
          'name',
          'description',
          'type',
      );

      $valid = true;

    0 foreach ($products as
          $validationResult
                                $rawProduct) {
                                = validateSingleProduct($rawProduct, $requiredFields);

          1   if ( ! $validationResult){

              }
               2  $valid = false;

      }

      return $valid;
}

function validateSingleProduct($product, $requiredFields)
{
    $valid = true;

      $fields = array_keys($rawProduct);

    0 foreach(!in_array($requiredField, $fields))
              ($requiredFields as $requiredField)     {

        1 if                                          {

          } 2
              $valid = false;

      }
      return $valid;
}
function validateProducts($storeData) {

    $requiredFields = array('price','name','description','type');

    foreach ($storeData['products'] as $rawProduct) {
        if ( ! validateSingleProduct($rawProduct, $requiredFields)) return false;
    }

    return true;
}

function validateSingleProduct($product, $requiredFields)
{
    $fields = array_keys($rawProduct);
    $missingFields = array_diff($requiredFields, $fields);

    return (count($missingFields) == 0);
}
function validateProducts($storeData) {

    $requiredFields = array('price','name','description','type'); cheating!
                                                            I see
    foreach ($storeData['products'] as $rawProduct) {
        if ( ! validateSingleProduct($rawProduct, $requiredFields)) return false;
    }

    return true;
}

function validateSingleProduct($product, $requiredFields)
{
    $fields = array_keys($rawProduct);
    $missingFields = array_diff($requiredFields, $fields);

    return (count($missingFields) == 0);
}
function validateProducts($storeData) {

    $requiredFields = array('price','name','description','type'); cheating!
                                                            I see
    foreach ($storeData['products'] as $rawProduct) {
        if ( ! validateSingleProduct($rawProduct, $requiredFields)) return false;
    }

    return true;    Single line IF, simple operations
}

function validateSingleProduct($product, $requiredFields)
{
    $fields = array_keys($rawProduct);
    $missingFields = array_diff($requiredFields, $fields);

    return (count($missingFields) == 0);
}
function validateProducts($storeData) {

    $requiredFields = array('price','name','description','type'); cheating!
                                                            I see
    foreach ($storeData['products'] as $rawProduct) {
        if ( ! validateSingleProduct($rawProduct, $requiredFields)) return false;
    }

    return true;    Single line IF, simple operations
}

function validateSingleProduct($product, $requiredFields)
{
    $fields = array_keys($rawProduct);
    $missingFields = array_diff($requiredFields, $fields);

    return (count($missingFields) == 0);
}
function validateProducts($storeData) {

    $requiredFields = array('price','name','description','type');

    foreach ($storeData['products'] as $rawProduct) {
        if ( ! validateSingleProduct($rawProduct, $requiredFields)) return false;
    }

    return true;    Single line IF, simple operations               return early
}

function validateSingleProduct($product, $requiredFields)
{
    $fields = array_keys($rawProduct);
    $missingFields = array_diff($requiredFields, $fields);

    return (count($missingFields) == 0);
}
function validateProducts($storeData) {

    $requiredFields = array('price','name','description','type');

    foreach ($storeData['products'] as $rawProduct) {
        if ( ! validateSingleProduct($rawProduct, $requiredFields)) return false;
    }

    return true;    Single line IF, simple operations               return early
}

function validateSingleProduct($product, $requiredFields)
{
    $fields = array_keys($rawProduct);
    $missingFields = array_diff($requiredFields, $fields);

    return (count($missingFields) == 0);
}                        C (native) functions are
                             faster then PHP
function validateProducts($storeData) {

    $requiredFields = array('price','name','description','type');

    foreach ($storeData['products'] as $rawProduct) {
        if ( ! validateSingleProduct($rawProduct, $requiredFields)) return false;
    }

    return true;
}

function validateSingleProduct($product, $requiredFields)
{
    $fields = array_keys($rawProduct);
    $missingFields = array_diff($requiredFields, $fields);

    return (count($missingFields) == 0);
}
function validateProducts($storeData) {

    $requiredFields = array('price','name','description','type');

    foreach ($storeData['products'] as $rawProduct) {
        if ( ! validateSingleProduct($rawProduct, $requiredFields)) return false;
    }

    return true;
}

function validateSingleProduct($product, $requiredFields)
{
    $fields = array_keys($rawProduct);
    $missingFields = array_diff($requiredFields, $fields);

    return (count($missingFields) == 0);
}
function validateProductList($products)
{
    $validProducts = array_filter($products, 'isValidProduct');

    return (count($products) === count($validProducts));
}




function isValidProduct($rawProduct)
{
    $requiredFields = array('price', 'name', 'description', 'type');

    $fields          = array_keys($rawProduct);
    $missingFields   = array_diff($requiredFields, $fields);

    return (count($missingFields) == 0);
}
function validateProductList($products)      faster iteration
{
    $validProducts = array_filter($products, 'isValidProduct');

    return (count($products) === count($validProducts));
}




function isValidProduct($rawProduct)
{
    $requiredFields = array('price', 'name', 'description', 'type');

    $fields          = array_keys($rawProduct);
    $missingFields   = array_diff($requiredFields, $fields);

    return (count($missingFields) == 0);
}
function validateProductList($products)      faster iteration
{
    $validProducts = array_filter($products, 'isValidProduct');

    return (count($products) === count($validProducts));
}


          reusable method
function isValidProduct($rawProduct)
{
    $requiredFields = array('price', 'name', 'description', 'type');

    $fields          = array_keys($rawProduct);
    $missingFields   = array_diff($requiredFields, $fields);

    return (count($missingFields) == 0);
}
function validateProductList($products)      faster iteration
{
    $validProducts = array_filter($products, 'isValidProduct');

    return (count($products) === count($validProducts));
}


          reusable method
function isValidProduct($rawProduct)
{
    $requiredFields = array('price', 'name', 'description', 'type');

    $fields          = array_keys($rawProduct);
    $missingFields   = array_diff($requiredFields, $fields);

    return (count($missingFields) == 0);
}


          method name matches “true” result
function validateProductList($products)      faster iteration
{
    $validProducts = array_filter($products, 'isValidProduct');

    return (count($products) === count($validProducts));
}

                                  assertable return: expected/returned
          reusable method
function isValidProduct($rawProduct)
{
    $requiredFields = array('price', 'name', 'description', 'type');

    $fields          = array_keys($rawProduct);
    $missingFields   = array_diff($requiredFields, $fields);

    return (count($missingFields) == 0);
}


          method name matches “true” result
List is more readable the plural

function validateProductList($products)      faster iteration
{
    $validProducts = array_filter($products, 'isValidProduct');

    return (count($products) === count($validProducts));
}

                                       assertable return: expected/returned
           reusable method
function isValidProduct($rawProduct)
{
    $requiredFields = array('price', 'name', 'description', 'type');

    $fields          = array_keys($rawProduct);
    $missingFields   = array_diff($requiredFields, $fields);

    return (count($missingFields) == 0);
}


           method name matches “true” result
Key Benefits


• Single Responsibility Principle (S in SOLID)

• Increases re-use
OC #2
“Do not use the ‘else’
     keyword”
public function createPost($request)
{
    $entity = new Post();

    $form = new MyForm($entity);
    $form->bind($request);

    if ($form->isValid()){

        $repository = $this->getRepository('MyBundle:Post');
        if (!$repository->exists($entity) ) {
            $repository->save($entity);
            return $this->redirect('create_ok');
        } else {
            $error = "Post Title already exists";
            return array('form' => $form, 'error' => $error);
        }

    } else {
        $error = "Invalid fields";
        return array('form' => $form, 'error' => $error);
    }

}
public function createPost($request)
{
    $entity = new Post();

    $form = new MyForm($entity);
    $form->bind($request);

    if ($form->isValid()){

        $repository = $this->getRepository('MyBundle:Post');
        if (!$repository->exists($entity) ) {
            $repository->save($entity);
            return $this->redirect('create_ok');
        } else {
            $error = "Post Title already exists";
            return array('form' => $form, 'error' => $error);
        }

    } else {
        $error = "Invalid fields";
        return array('form' => $form, 'error' => $error);
    }

}
public function createPost($request)
{
    $entity = new Post();

    $form = new MyForm($entity);
    $form->bind($request);

    if ($form->isValid()){

        $repository = $this->getRepository('MyBundle:Post');
        if (!$repository->exists($entity) ) {
            $repository->save($entity);
            return $this->redirect('create_ok');
        } else {
            $error = "Post Title already exists";
            return array('form' => $form, 'error' => $error);
        }

    } else {
        $error = "Invalid fields";
        return array('form' => $form, 'error' => $error);
    }

}
public function createPost($request)
{
    $entity = new Post();

    $form = new MyForm($entity);
    $form->bind($request);

    if ($form->isValid()){

        $repository = $this->getRepository('MyBundle:Post');
        if (!$repository->exists($entity) ) {
            $repository->save($entity);
            return $this->redirect('create_ok');
        } else {
            $error = "Post Title already exists";
            return array('form' => $form, 'error' => $error);
        }

    } else {
        $error = "Invalid fields";
        return array('form' => $form, 'error' => $error);
    }

}
public function createPost($request)
{
    $entity = new Post();

    $form = new MyForm($entity);
    $form->bind($request);

    if ($form->isValid()){

        $repository = $this->getRepository('MyBundle:Post');
        if (!$repository->exists($entity) ) {
            $repository->save($entity);
            return $this->redirect('create_ok');
        } else {
            $error = "Post Title already exists";
            return array('form' => $form, 'error' => $error);
        }

    } else {
        $error = "Invalid fields";
        return array('form' => $form, 'error' => $error);
    }

}
public function createPost($request)
{
    $entity = new Post();

    $form = new MyForm($entity);
    $form->bind($request);

    if ($form->isValid()){

        $repository = $this->getRepository('MyBundle:Post');
        if (!$repository->exists($entity) ) {
            $repository->save($entity);
            return $this->redirect('create_ok');
        } else {
            $error = "Post Title already exists";
            return array('form' => $form, 'error' => $error);
        }

    } else {
        $error = "Invalid fields";
        return array('form' => $form, 'error' => $error);
    }

}
public function createPost($request)
{
    $entity = new Post();

    $form = new MyForm($entity);
    $form->bind($request);

    if ($form->isValid()){

        $repository = $this->getRepository('MyBundle:Post');
        if (!$repository->exists($entity) ) {
            $repository->save($entity);
            return $this->redirect('create_ok');
        } else {
            $error = "Post Title already exists";
            return array('form' => $form, 'error' => $error);
        }

    } else {
        $error = "Invalid fields";
        return array('form' => $form, 'error' => $error);
    }

}
public function createPost($request)
{
    $entity = new Post();

    $form = new MyForm($entity);
    $form->bind($request);

    if ($form->isValid()){

        $repository = $this->getRepository('MyBundle:Post');
        if (!$repository->exists($entity) ) {
             $repository->save($entity);
         intermediate variable
             return $this->redirect('create_ok');
        } else {
             $error = "Post Title already exists";
             return array('form' => $form, 'error' => $error);
        }
    intermediate variable
    } else {
        $error = "Invalid fields";
        return array('form' => $form, 'error' => $error);
    }

}
public function createPost($request)
{
    $entity     = new Post();
    $repository = $this->getRepository('MyBundle:Post');

    $form = new MyForm($entity);
    $form->bind($request);

    if ( ! $form->isValid()){
        return array('form' => $form, 'error' => 'Invalid fields');
    }

    if ($repository->exists($entity)){
        return array('form' => $form, 'error' => 'Duplicate post title');
    }

    $repository->save($entity);

    return $this->redirect('create_ok');

}
public function createPost($request)
{
    $entity     = new Post();
    $repository = $this->getRepository('MyBundle:Post');

    $form = new MyForm($entity);
    $form->bind($request);

    if ( ! $form->isValid()){
        return array('form' => $form, 'error' => 'Invalid fields');
    }

    if ($repository->exists($entity)){
        return array('form' => $form, 'error' => 'Duplicate post title');
    }

    $repository->save($entity);

    return $this->redirect('create_ok');

}
public function createPost($request)
{
    $entity     = new Post();
    $repository = $this->getRepository('MyBundle:Post');

    $form = new MyForm($entity);
    $form->bind($request);
                                           removed intermediates
    if ( ! $form->isValid()){
        return array('form' => $form, 'error' => 'Invalid fields');
    }

    if ($repository->exists($entity)){
        return array('form' => $form, 'error' => 'Duplicate post title');
    }

    $repository->save($entity);

    return $this->redirect('create_ok');

}
public function createPost($request)
{
    $entity     = new Post();
    $repository = $this->getRepository('MyBundle:Post');

      $form = new MyForm($entity);
      $form->bind($request);
                                             removed intermediates
      if ( ! $form->isValid()){
          return array('form' => $form, 'error' => 'Invalid fields');
      }
    early return
      if ($repository->exists($entity)){
          return array('form' => $form, 'error' => 'Duplicate post title');
      }

      $repository->save($entity);

      return $this->redirect('create_ok');

}
Separate code
                                                              into blocks.
public function createPost($request)
{                                                             Its like using
    $entity     = new Post();
    $repository = $this->getRepository('MyBundle:Post');     Paragraphs.
      $form = new MyForm($entity);
      $form->bind($request);
                                             removed intermediates
      if ( ! $form->isValid()){
          return array('form' => $form, 'error' => 'Invalid fields');
      }
    early return
      if ($repository->exists($entity)){
          return array('form' => $form, 'error' => 'Duplicate post title');
      }

      $repository->save($entity);

      return $this->redirect('create_ok');

}
Key Benefits

• Helps avoid code duplication
• Easier to read (single true path)
• Reduces cyclomatic complexity
Ad
                           ap
                           te
                            d
       OC #3
“Wrap primitive types
    and strings”

  * if there is behavior
//...
$component->repaint(false);
//...
$component->repaint(false);
//...
$component->repaint(false);


                  unclear operation
//...
$component->repaint(false);


                  unclear operation



class UIComponent
{
//...
    public function repaint($animate = true){
            //...
    }
}
class UIComponent
{
    //...
    public function repaint( Animate $animate ){
            //...
    }
}

class Animate
{
    public $animate;

    public function __construct( $animate = true ) {
        $this->animate = $animate;}

}

//...
$component->repaint( new Animate(false) );
class UIComponent
{
    //...
    public function repaint( Animate $animate ){
            //...
    }
}

class Animate
                  This can now encapsulate all
{
                  animation related operations
    public $animate;

    public function __construct( $animate = true ) {
        $this->animate = $animate;}

}

//...
$component->repaint( new Animate(false) );
Key Benefits

• Helps identify what should be an Object
• Type Hinting
• Encapsulation of operations
Ad
                              ap
                                te
                                  d
          OC #4
   “Only one -> per line”
               or dot




* getter chain or a fluent interface
Source: CodeIgniter




$this->base_url = $this->CI->config->site_url().'/'.$this->CI->uri->segment(1).$this-
>CI->uri->slash_segment(2, 'both');




$this->base_uri = $this->CI->uri->segment(1).$this->CI->uri->slash_segment(2, 'leading');
Source: CodeIgniter




                         properties are harder to mock
$this->base_url = $this->CI->config->site_url().'/'.$this->CI->uri->segment(1).$this-
>CI->uri->slash_segment(2, 'both');




$this->base_uri = $this->CI->uri->segment(1).$this->CI->uri->slash_segment(2, 'leading');
Source: CodeIgniter




                         properties are harder to mock
$this->base_url = $this->CI->config->site_url().'/'.$this->CI->uri->segment(1).$this-
>CI->uri->slash_segment(2, 'both');


                                              no whitespace
$this->base_uri = $this->CI->uri->segment(1).$this->CI->uri->slash_segment(2, 'leading');
Source: CodeIgniter




                         properties are harder to mock
$this->base_url = $this->CI->config->site_url().'/'.$this->CI->uri->segment(1).$this-
>CI->uri->slash_segment(2, 'both');


                                              no whitespace
$this->base_uri = $this->CI->uri->segment(1).$this->CI->uri->slash_segment(2, 'leading');




                   - Underlying encapsulation problem
                   - Hard to debug and test
                   - Hard to read and understand
Source: CodeIgniter




                         properties are harder to mock
$this->base_url = $this->CI->config->site_url().'/'.$this->CI->uri->segment(1).$this-
>CI->uri->slash_segment(2, 'both');


                                              no whitespace
$this->base_uri = $this->CI->uri->segment(1).$this->CI->uri->slash_segment(2, 'leading');



                            move everything to uri object

       $this->getCI()->getUriBuilder()->getBaseUri(‘leading’);



                   - Underlying encapsulation problem
                   - Hard to debug and test
                   - Hard to read and understand
Source: Zend Framework App




$filterChain->addFilter(new Zend_Filter_Alpha())
            ->addFilter(new Zend_Filter_StringToLower());




                                                            Source: Symfony 2 Docs.
Source: Zend Framework App




    fluent interface
$filterChain->addFilter(new Zend_Filter_Alpha())
            ->addFilter(new Zend_Filter_StringToLower());




                                                            Source: Symfony 2 Docs.
Source: Zend Framework App




    fluent interface
$filterChain->addFilter(new Zend_Filter_Alpha())
            ->addFilter(new Zend_Filter_StringToLower());


      operator alignment




                                                            Source: Symfony 2 Docs.
Source: Zend Framework App




    fluent interface
$filterChain->addFilter(new Zend_Filter_Alpha())
            ->addFilter(new Zend_Filter_StringToLower());


      operator alignment




  $user = $this->get('security.context')->getToken()->getUser();




                                                            Source: Symfony 2 Docs.
Source: Zend Framework App




    fluent interface
$filterChain->addFilter(new Zend_Filter_Alpha())
            ->addFilter(new Zend_Filter_StringToLower());


      operator alignment




                                     only getters (no operations)

  $user = $this->get('security.context')->getToken()->getUser();




                                                            Source: Symfony 2 Docs.
Source: Zend Framework App




    fluent interface
$filterChain->addFilter(new Zend_Filter_Alpha())
            ->addFilter(new Zend_Filter_StringToLower());


      operator alignment




                                     only getters (no operations)

  $user = $this->get('security.context')->getToken()->getUser();




                                                            Source: Symfony 2 Docs.
Source: Zend Framework App




    fluent interface
$filterChain->addFilter(new Zend_Filter_Alpha())
            ->addFilter(new Zend_Filter_StringToLower());


      operator alignment




                                     only getters (no operations)

  $user = $this->get('security.context')->getToken()->getUser();


               where did my
             autocomplete go?

                                                            Source: Symfony 2 Docs.
Source: Zend Framework App




    fluent interface
$filterChain->addFilter(new Zend_Filter_Alpha())
            ->addFilter(new Zend_Filter_StringToLower());


      operator alignment




                                     only getters (no operations)

  $user = $this->get('security.context')->getToken()->getUser();


               where did my                  return null?
             autocomplete go?

                                                            Source: Symfony 2 Docs.
Key Benefits

• Readability
• Easier Mocking (Testing)

• Easier to Debug
• Demeter’s Law
OC #5
“Do not Abbreviate”
if($sx >= $sy) {
    if ($sx > $strSysMatImgW) {
        $ny = $strSysMatImgW * $sy / $sx;
        $nx = $strSysMatImgW;
    }

    if ($ny > $strSysMatImgH) {
        $nx = $strSysMatImgH * $sx / $sy;
        $ny = $strSysMatImgH;
    }

} else {

    if ($sy > $strSysMatImgH) {
        $nx = $strSysMatImgH * $sx / $sy;
        $ny = $strSysMatImgH;
    }

    if($nx > $strSysMatImgW) {
        $ny = $strSysMatImgW * $sy / $sx;
        $nx = $strSysMatImgW;
    }
}
?
if($sx >= $sy) {                      ?
    if ($sx > $strSysMatImgW) {
        $ny = $strSysMatImgW * $sy / $sx;
        $nx = $strSysMatImgW;
    }
                                ?
    if ($ny > $strSysMatImgH) {
        $nx = $strSysMatImgH * $sx / $sy;
        $ny = $strSysMatImgH;
    }

} else {

         ?
    if ($sy > $strSysMatImgH) {
        $nx = $strSysMatImgH * $sx / $sy;
        $ny = $strSysMatImgH;
    }

         ?
    if($nx > $strSysMatImgW) {
        $ny = $strSysMatImgW * $sy / $sx;
        $nx = $strSysMatImgW;
    }
}
Why do you abbreviate?
Why do you abbreviate?


 Its repeated many times,
        and i’m lazy.
Why do you abbreviate?


 Its repeated many times,
        and i’m lazy.
                   Underlying Problem!

   You need to transfer those operations into a separate class.
Why do you abbreviate?

function processResponseHeadersAndDefineOutput($response) { ... }
Why do you abbreviate?

function processResponseHeadersAndDefineOutput($response) { ... }




      This method name is too long to type,
                 and i’m lazy.
Why do you abbreviate?
                          more than one
                          responsibility?

function processResponseHeadersAndDefineOutput($response) { ... }




      This method name is too long to type,
                 and i’m lazy.
function getPage($data) { ... }




function startProcess() { ... }




$tr->process(“site.login”);
get from where?
function getPage($data) { ... }




function startProcess() { ... }




$tr->process(“site.login”);
get from where?
                                  Use clearer names:
function getPage($data) { ... }   fetchPage()
                                  downloadPage()




function startProcess() { ... }




$tr->process(“site.login”);
get from where?
                                  Use clearer names:
function getPage($data) { ... }   fetchPage()
                                  downloadPage()




                                  Use a thesaurus:
function startProcess() { ... }
                                  fork, create, begin, open




$tr->process(“site.login”);
get from where?
                                  Use clearer names:
function getPage($data) { ... }   fetchPage()
                                  downloadPage()




                                  Use a thesaurus:
function startProcess() { ... }
                                  fork, create, begin, open


      Table row?

$tr->process(“site.login”);
get from where?
                                  Use clearer names:
function getPage($data) { ... }   fetchPage()
                                  downloadPage()




                                  Use a thesaurus:
function startProcess() { ... }
                                  fork, create, begin, open


      Table row?
                                  Easy understanding, complete scope:
$tr->process(“site.login”);
                                  $translatorService
Key Benefits


• Clearer communication and maintainability

• Indicates underlying problems
Ad
                 ap
                     te
                     d
     OC #6
“Keep your classes
     small”
200 lines per class
 10 methods per class
15 classes per package
Increased to include
    docblocks



  200 lines per class
 10 methods per class
15 classes per package
Increased to include
    docblocks          15-20 lines per method



  200 lines per class
 10 methods per class
15 classes per package
Increased to include
    docblocks             15-20 lines per method



  200 lines per class
 10 methods per class
15 classes per package
                           read this as
                       namespace or folder
Key Benefits

• Single Responsibility
• Objective and clear methods

• Slimmer namespaces
• Avoids clunky folders
Ad
                     ap
         OC #7




                      te
                          d
 “Limit the number of
instance variables in a
    class (2 to 5)”
class MyRegistrationService
{

    protected   $userService;
    protected   $passwordService;
    protected   $logger;
    protected   $translator;
    protected   $entityManager;
    protected   $imageCropper;

    // ...
}
class MyRegistrationService
{

    protected   $userService;
    protected   $passwordService;
    protected   $logger;
    protected   $translator;
    protected   $entityManager;
    protected   $imageCropper;

    // ...
}




       Limit: 5
class MyRegistrationService
                     {

                         protected   $userService;
                         protected   $passwordService;
                         protected   $logger;
All DB interaction       protected   $translator;
   should be in          protected   $entityManager;
    userService          protected   $imageCropper;   Use an event based
                                                     system and move this
                         // ...                           to listener
                     }




                            Limit: 5
Key Benefits


• Shorter dependency list

• Easier Mocking for unit test
OC #8
“Use first class
 collections”
Doctrine:
      ArrayCollection


$collection->getIterator();

$collection->filter(...);

$collection->append(...);

$collection->map(...);
Key Benefits

• Implements collection operations
• Uses SPL interfaces

• Easier to merge collections and not worry
  about member behaviour in them
Dr
                       op
                         pe
                             d
        OC #9
“Do not use accessors
  (getter/setter)”
* Use them if you code PHP
/**
  * THIS CLASS WAS GENERATED BY THE DOCTRINE ORM. DO NOT EDIT THIS FILE.
  */
class DoctrineTestsModelsCMSCmsUserProxy
    extends DoctrineTestsModelsCMSCmsUser
    implements DoctrineORMProxyProxy
{

    public function getId()
    {
        $this->__load();
        return parent::getId();
    }

    public function getStatus()
    {
        $this->__load();
        return parent::getStatus();
    }




                                      Property get/set syntax RFC may change the game.
/**
  * THIS CLASS WAS GENERATED BY THE DOCTRINE ORM. DO NOT EDIT THIS FILE.
  */
class DoctrineTestsModelsCMSCmsUserProxy
    extends DoctrineTestsModelsCMSCmsUser
    implements DoctrineORMProxyProxy
{

    public function getId()
    {
        $this->__load();          Example: Doctrine uses getters to
        return parent::getId();     inject lazy loading operations
    }

    public function getStatus()
    {
        $this->__load();
        return parent::getStatus();
    }




                                      Property get/set syntax RFC may change the game.
Key Benefits


• Injector operations

• Encapsulation of transformations
Cr
                   ea
                    te
                        d!
   OC #10 (bonus!)
“Document your code!”
//check to see if the section above set the $overall_pref variable to void
if ($overall_pref == 'void')




// implode the revised array of selections in group three into a string
// variable so that it can be transferred to the database at the end of the
// page
$groupthree = implode($groupthree_array, "nr");
really?

//check to see if the section above set the $overall_pref variable to void
if ($overall_pref == 'void')




// implode the revised array of selections in group three into a string
// variable so that it can be transferred to the database at the end of the
// page
$groupthree = implode($groupthree_array, "nr");
really?

//check to see if the section above set the $overall_pref variable to void
if ($overall_pref == 'void')




// implode the revised array of selections in group three into a string
// variable so that it can be transferred to the database at the end of the
// page
$groupthree = implode($groupthree_array, "nr");


         Documenting because i’m doing it wrong in an unusual way
$priority = isset($event['priority']) ? $event['priority'] : 0;

if (!isset($event['event'])) {
    throw new InvalidArgumentException(...));
}

if (!isset($event['method'])) {
    $event['method'] = 'on'.preg_replace(array(
      '/(?<=b)[a-z]/ie',
      '/[^a-z0-9]/i'
    ), array('strtoupper("0")', ''), $event['event']);
}

$definition->addMethodCall(
   'addListenerService',
   array($event['event'],
   array($listenerId,
   $event['method']),
   $priority
));




                                                                  Source: Symfony2
$priority = isset($event['priority']) ? $event['priority'] : 0;

if (!isset($event['event'])) {
    throw new InvalidArgumentException(...));
}

if (!isset($event['method'])) {
    $event['method'] = 'on'.preg_replace(array(
      '/(?<=b)[a-z]/ie',
      '/[^a-z0-9]/i'
    ), array('strtoupper("0")', ''), $event['event']);
}

$definition->addMethodCall(
   'addListenerService',
   array($event['event'],
   array($listenerId,               What does this do?
   $event['method']),
   $priority
));




                                                                  Source: Symfony2
$priority = isset($event['priority']) ? $event['priority'] : 0;
                            Add a simple comment:
if   (!isset($event['event'])) {
      throw new //Strips special chars and camel cases to onXxx
                 InvalidArgumentException(...));
}

if (!isset($event['method'])) {
    $event['method'] = 'on'.preg_replace(array(
      '/(?<=b)[a-z]/ie',
      '/[^a-z0-9]/i'
    ), array('strtoupper("0")', ''), $event['event']);
}

$definition->addMethodCall(
   'addListenerService',
   array($event['event'],
   array($listenerId,                   What does this do?
   $event['method']),
   $priority
));




                                                                  Source: Symfony2
$priority = isset($event['priority']) ? $event['priority'] : 0;
                            Add a simple comment:
if   (!isset($event['event'])) {
      throw new //Strips special chars and camel cases to onXxx
                 InvalidArgumentException(...));
}

if (!isset($event['method'])) {
    $event['method'] = 'on'.preg_replace(array(
                                                    Don’t explain bad
      '/(?<=b)[a-z]/ie',                               code, fix it!
      '/[^a-z0-9]/i'
    ), array('strtoupper("0")', ''), $event['event']);
}

$definition->addMethodCall(
   'addListenerService',
   array($event['event'],
   array($listenerId,                   What does this do?
   $event['method']),
   $priority
));




                                                                  Source: Symfony2
/**
* Checks whether an element is contained in the collection.
* This is an O(n) operation, where n is the size of the collection.
*
* @todo implement caching for better performance
* @param mixed $element The element to search for.
* @return boolean TRUE if the collection contains the element, or FALSE.
*/
function contains($element);
/**
* Checks whether an element is contained in the collection.
* This is an O(n) operation, where n is the size of the collection.
*
* @todo implement caching for better performance
* @param mixed $element The element to search for.
* @return boolean TRUE if the collection contains the element, or FALSE.
*/
function contains($element);


   mark todo items so the
    changes don’t get lost
A note on cost of
       running function
/**
* Checks whether an element is contained in the collection.
* This is an O(n) operation, where n is the size of the collection.
*
* @todo implement caching for better performance
* @param mixed $element The element to search for.
* @return boolean TRUE if the collection contains the element, or FALSE.
*/
function contains($element);


   mark todo items so the
    changes don’t get lost
Do a mind dump,
                                                       then clean it up.
      A note on cost of
       running function
/**
* Checks whether an element is contained in the collection.
* This is an O(n) operation, where n is the size of the collection.
*
* @todo implement caching for better performance
* @param mixed $element The element to search for.
* @return boolean TRUE if the collection contains the element, or FALSE.
*/
function contains($element);


   mark todo items so the
    changes don’t get lost
Do a mind dump,
                                                       then clean it up.
      A note on cost of
       running function
/**
* Checks whether an element is contained in the collection.
* This is an O(n) operation, where n is the size of the collection.
*
* @todo implement caching for better performance
* @param mixed $element The element to search for.
* @return boolean TRUE if the collection contains the element, or FALSE.
*/
function contains($element);
                                                  Generate API docs
                                                 with phpDocumentor
   mark todo items so the
    changes don’t get lost
Key Benefits

• Automatic API documentation
• Transmission of “line of thought”
• Avoids confusion
Recap
•   #1 - Only one indentation level per method.

•   #2 - Do not use the ‘else’ keyword.

•   #3 - Wrap primitive types and strings.

•   #4 - Only one -> per line.

•   #5 - Do not Abbreviate.

•   #6 - Keep your classes small.

•   #7 - Limit the number of instance variables in a class (max: 5)

•   #8 - Use first class collections

•   #9 - Use accessors (getter/setter) *

•   #10 - Document your code!
Questions?




                          http://fixthatcode.com


@rdohms

http://doh.ms

http://slides.doh.ms
Recommended Links:


                    The ThoughtWorks Anthology
                    http://goo.gl/OcSNx

                    The Art of Readable Code
                    http://goo.gl/unrij


DISCLAIMER: This talk re-uses some of the examples used by Guilherme Blanco in his
original Object Calisthenic talk. These principles were studied and applied by us while we
worked together in previous jobs. The result taught us all a lesson we really want to spread
to other developers.

More Related Content

KEY
Object Calisthenics Applied to PHP
PDF
You code sucks, let's fix it
PDF
QA Fes 2016. Алексей Виноградов. Page Objects: лучше проще, да лучшe
PDF
If You Think You Can Stay Away from Functional Programming, You Are Wrong
PPT
Spring data presentation
PDF
Domain Driven Design with the F# type System -- NDC London 2013
PDF
Kiss PageObjects [01-2017]
PDF
How to Use JSON in MySQL Wrong
Object Calisthenics Applied to PHP
You code sucks, let's fix it
QA Fes 2016. Алексей Виноградов. Page Objects: лучше проще, да лучшe
If You Think You Can Stay Away from Functional Programming, You Are Wrong
Spring data presentation
Domain Driven Design with the F# type System -- NDC London 2013
Kiss PageObjects [01-2017]
How to Use JSON in MySQL Wrong

What's hot (20)

PPTX
Spring data jpa
PPTX
clean code book summary - uncle bob - English version
PDF
날로 먹는 Django admin 활용
PDF
Functional Design Patterns (DevTernity 2018)
PDF
Spring Data JPA from 0-100 in 60 minutes
PPTX
Dynamically Generate a CRUD Admin Panel with Java Annotations
PDF
Lambda Expressions in Java | Java Lambda Tutorial | Java Certification Traini...
PDF
Clean code
PDF
Jpa 잘 (하는 척) 하기
KEY
Clean code and Code Smells
PPTX
The Art of Metaprogramming in Java
PDF
Clean coding-practices
PDF
常見設計模式介紹
PDF
Your code sucks, let's fix it - PHP Master Series 2012
PDF
Spring Framework - Spring Security
PDF
Java 8 Lambda Built-in Functional Interfaces
PDF
Enterprise Tic-Tac-Toe
PPTX
Optional in Java 8
PDF
Schema Design
PPTX
Clean code slide
Spring data jpa
clean code book summary - uncle bob - English version
날로 먹는 Django admin 활용
Functional Design Patterns (DevTernity 2018)
Spring Data JPA from 0-100 in 60 minutes
Dynamically Generate a CRUD Admin Panel with Java Annotations
Lambda Expressions in Java | Java Lambda Tutorial | Java Certification Traini...
Clean code
Jpa 잘 (하는 척) 하기
Clean code and Code Smells
The Art of Metaprogramming in Java
Clean coding-practices
常見設計模式介紹
Your code sucks, let's fix it - PHP Master Series 2012
Spring Framework - Spring Security
Java 8 Lambda Built-in Functional Interfaces
Enterprise Tic-Tac-Toe
Optional in Java 8
Schema Design
Clean code slide
Ad

Viewers also liked (17)

PDF
Programming objects with android
PDF
10 commandments for better android development
PDF
Kicking the Bukkit: Anatomy of an open source meltdown
PDF
Composer The Right Way - 010PHP
PPTX
Rethinking Best Practices
PDF
Understanding PHP objects
PDF
Visual guide to selling software as a service by @prezly
PDF
We Built It, And They Didn't Come!
PDF
Clean code
PDF
Integrating React.js with PHP projects
PDF
How to Design Indexes, Really
PDF
Enterprise PHP: mappers, models and services
PDF
From development environments to production deployments with Docker, Compose,...
PDF
Enterprise PHP Architecture through Design Patterns and Modularization (Midwe...
PDF
Functional Programming Patterns (BuildStuff '14)
PDF
From Idea to Execution: Spotify's Discover Weekly
PDF
Linux Profiling at Netflix
Programming objects with android
10 commandments for better android development
Kicking the Bukkit: Anatomy of an open source meltdown
Composer The Right Way - 010PHP
Rethinking Best Practices
Understanding PHP objects
Visual guide to selling software as a service by @prezly
We Built It, And They Didn't Come!
Clean code
Integrating React.js with PHP projects
How to Design Indexes, Really
Enterprise PHP: mappers, models and services
From development environments to production deployments with Docker, Compose,...
Enterprise PHP Architecture through Design Patterns and Modularization (Midwe...
Functional Programming Patterns (BuildStuff '14)
From Idea to Execution: Spotify's Discover Weekly
Linux Profiling at Netflix
Ad

Similar to Your code sucks, let's fix it (20)

PDF
Your code sucks, let's fix it (CakeFest2012)
PDF
Your code sucks, let's fix it
PDF
Your code sucks, let's fix it - DPC UnCon
PDF
Your code sucks, let's fix it! - php|tek13
PPT
Propel sfugmd
PPTX
Open Source Search: An Analysis
PDF
PHPSpec - the only Design Tool you need - 4Developers
KEY
jQuery: Tips, tricks and hints for better development and Performance
PDF
WordPress as an application framework
PDF
Dependency Injection
KEY
Unit testing with zend framework PHPBenelux
PDF
Why Hacking WordPress Search Isn't Some Big Scary Thing
PDF
Unit testing with zend framework tek11
PDF
laravel tricks in 50minutes
PDF
50 Laravel Tricks in 50 Minutes
PPTX
Coding for Scale and Sanity
PDF
Dutch PHP Conference - PHPSpec 2 - The only Design Tool you need
PDF
The History of PHPersistence
PDF
WordCamp Portland 2018: PHP for WordPress
PPTX
Deploying Straight to Production
Your code sucks, let's fix it (CakeFest2012)
Your code sucks, let's fix it
Your code sucks, let's fix it - DPC UnCon
Your code sucks, let's fix it! - php|tek13
Propel sfugmd
Open Source Search: An Analysis
PHPSpec - the only Design Tool you need - 4Developers
jQuery: Tips, tricks and hints for better development and Performance
WordPress as an application framework
Dependency Injection
Unit testing with zend framework PHPBenelux
Why Hacking WordPress Search Isn't Some Big Scary Thing
Unit testing with zend framework tek11
laravel tricks in 50minutes
50 Laravel Tricks in 50 Minutes
Coding for Scale and Sanity
Dutch PHP Conference - PHPSpec 2 - The only Design Tool you need
The History of PHPersistence
WordCamp Portland 2018: PHP for WordPress
Deploying Straight to Production

More from Rafael Dohms (20)

PDF
The Individual Contributor Path - DPC2024
PDF
Application Metrics - IPC2023
PDF
How'd we get here? A guide to Architectural Decision Records
PDF
Architectural Decision Records - PHPConfBR
PDF
Application Metrics (with Prometheus examples)
PDF
Application metrics - Confoo 2019
PDF
Writing code you won’t hate tomorrow - PHPCE18
PDF
Application Metrics (with Prometheus examples) #PHPDD18
PDF
Application metrics with Prometheus - DPC18
PDF
“Writing code that lasts” … or writing code you won’t hate tomorrow. - PHPKonf
PDF
“Writing code that lasts” … or writing code you won’t hate tomorrow. - PHP Yo...
PDF
Writing Code That Lasts - #Magento2Seminar, Utrecht
PDF
Composer the Right Way - PHPSRB16
PDF
“Writing code that lasts” … or writing code you won’t hate tomorrow. - #PHPSRB16
PDF
Composer the Right Way - MM16NL
PDF
Composer The Right Way - PHPUGMRN
PDF
Composer the Right Way - PHPBNL16
PDF
“Writing code that lasts” … or writing code you won’t hate tomorrow.
PDF
A Journey into your Lizard Brain - PHP Conference Brasil 2015
PDF
“Writing code that lasts” … or writing code you won’t hate tomorrow.
The Individual Contributor Path - DPC2024
Application Metrics - IPC2023
How'd we get here? A guide to Architectural Decision Records
Architectural Decision Records - PHPConfBR
Application Metrics (with Prometheus examples)
Application metrics - Confoo 2019
Writing code you won’t hate tomorrow - PHPCE18
Application Metrics (with Prometheus examples) #PHPDD18
Application metrics with Prometheus - DPC18
“Writing code that lasts” … or writing code you won’t hate tomorrow. - PHPKonf
“Writing code that lasts” … or writing code you won’t hate tomorrow. - PHP Yo...
Writing Code That Lasts - #Magento2Seminar, Utrecht
Composer the Right Way - PHPSRB16
“Writing code that lasts” … or writing code you won’t hate tomorrow. - #PHPSRB16
Composer the Right Way - MM16NL
Composer The Right Way - PHPUGMRN
Composer the Right Way - PHPBNL16
“Writing code that lasts” … or writing code you won’t hate tomorrow.
A Journey into your Lizard Brain - PHP Conference Brasil 2015
“Writing code that lasts” … or writing code you won’t hate tomorrow.

Recently uploaded (20)

PDF
Getting started with AI Agents and Multi-Agent Systems
PDF
Produktkatalog für HOBO Datenlogger, Wetterstationen, Sensoren, Software und ...
PDF
A proposed approach for plagiarism detection in Myanmar Unicode text
PDF
CloudStack 4.21: First Look Webinar slides
PDF
Accessing-Finance-in-Jordan-MENA 2024 2025.pdf
PDF
The influence of sentiment analysis in enhancing early warning system model f...
PPT
Geologic Time for studying geology for geologist
PDF
How ambidextrous entrepreneurial leaders react to the artificial intelligence...
PDF
A review of recent deep learning applications in wood surface defect identifi...
PDF
UiPath Agentic Automation session 1: RPA to Agents
PDF
A contest of sentiment analysis: k-nearest neighbor versus neural network
PDF
Architecture types and enterprise applications.pdf
PPTX
AI IN MARKETING- PRESENTED BY ANWAR KABIR 1st June 2025.pptx
PPTX
Microsoft Excel 365/2024 Beginner's training
PDF
How IoT Sensor Integration in 2025 is Transforming Industries Worldwide
PPT
What is a Computer? Input Devices /output devices
PDF
Flame analysis and combustion estimation using large language and vision assi...
PDF
Enhancing plagiarism detection using data pre-processing and machine learning...
PPT
Galois Field Theory of Risk: A Perspective, Protocol, and Mathematical Backgr...
PDF
NewMind AI Weekly Chronicles – August ’25 Week III
Getting started with AI Agents and Multi-Agent Systems
Produktkatalog für HOBO Datenlogger, Wetterstationen, Sensoren, Software und ...
A proposed approach for plagiarism detection in Myanmar Unicode text
CloudStack 4.21: First Look Webinar slides
Accessing-Finance-in-Jordan-MENA 2024 2025.pdf
The influence of sentiment analysis in enhancing early warning system model f...
Geologic Time for studying geology for geologist
How ambidextrous entrepreneurial leaders react to the artificial intelligence...
A review of recent deep learning applications in wood surface defect identifi...
UiPath Agentic Automation session 1: RPA to Agents
A contest of sentiment analysis: k-nearest neighbor versus neural network
Architecture types and enterprise applications.pdf
AI IN MARKETING- PRESENTED BY ANWAR KABIR 1st June 2025.pptx
Microsoft Excel 365/2024 Beginner's training
How IoT Sensor Integration in 2025 is Transforming Industries Worldwide
What is a Computer? Input Devices /output devices
Flame analysis and combustion estimation using large language and vision assi...
Enhancing plagiarism detection using data pre-processing and machine learning...
Galois Field Theory of Risk: A Perspective, Protocol, and Mathematical Backgr...
NewMind AI Weekly Chronicles – August ’25 Week III

Your code sucks, let's fix it

  • 1. Your code sucks, let’s !x it! Object Calisthenics and Code readability Rafael Dohms @rdohms
  • 2. Rafael Dohms photo credit: Eli White @rdohms Evangelist, Speaker and Contributor. Developer at WEBclusive. Enabler at AmsterdamPHP.
  • 3. http://conference.phpbenelux.eu/2013/ 25th and 26th of January Early bird ends on Thursday the 6th!
  • 5. Is it Readable? Why does my code suck?
  • 6. Is it Readable? Why does my code suck? Is it Testable?
  • 7. Is it Maintainable? Is it Readable? Why does my code suck? Is it Testable?
  • 8. Is it Maintainable? Is it Readable? Why does my code suck? Is it Reusable? Is it Testable?
  • 9. <?php Does it look like this? $list=mysql_connect("******","*******","*****"); if(!$list)echo 'Cannot login.'; else{ mysql_select_db("******", $list); $best=array("0","0","0","0","0","0"); $id=mysql_num_rows(mysql_query("SELECT * FROM allnews")); $count=0; for($i=0;$i<6;$i++){ while(mysql_query("SELECT language FROM allnews WHERE id=$id-$count")!="he")$count+ +; $best[$i]=mysql_query("SELECT id FROM allnews WHERE id=$id-$count");} $id=$id-$count; $maxdate=mktime(0,0,0,date('m'),date('d')-7,date('Y')); while(mysql_query("SELECT date FROM allnews WHERE id=$id-$count")>=$maxdate){ if(mysql_query("SELECT language FROM allnews WHERE id=$id-$count")=="he"){ $small=$best[0]; while($i=0;$i<6;$i++){ if(mysql_query("SELECT score FROM allnews WHERE id= $small)"<mysql_query("SELECT score FROM allnews WHERE id=$best[i+1]")) $small=$best[i+1];} if(mysql_query("SELECT score FROM allnews WHERE id= $small")<mysql_query("SELECT score FROM allnews WHERE id=$id-$count")){ while($i=0;$i<6;$i++){ if($small==$best[i])$best[i]=mysql_query("SELECT id FROM allnews WHERE id=$id-$count");}}}} while($i=0;$i<6;$i++) echo '<a href="news-page.php?id='.$best[i].'"><div class="box '.mysql_query("SELECT type FROM allnews WHERE id=$best[i]").'">'.mysql_query("SELECT title FROM allnews WHERE id= $best[i]").'<div class="img" style="background-image:url(images/'.mysql_query("SELECT image1 FROM allnews WHERE id=$best[i]").');"></div></div></a>'; mysql_close($list); } ?>
  • 10. <?php Does it look like this? $list=mysql_connect("******","*******","*****"); if(!$list)echo 'Cannot login.'; else{ mysql_select_db("******", $list); $best=array("0","0","0","0","0","0"); $id=mysql_num_rows(mysql_query("SELECT * FROM allnews")); $count=0; for($i=0;$i<6;$i++){ while(mysql_query("SELECT language FROM allnews WHERE id=$id-$count")!="he")$count+ +; $best[$i]=mysql_query("SELECT id FROM allnews WHERE id=$id-$count");} $id=$id-$count; $maxdate=mktime(0,0,0,date('m'),date('d')-7,date('Y')); while(mysql_query("SELECT date FROM allnews WHERE id=$id-$count")>=$maxdate){ if(mysql_query("SELECT language FROM allnews WHERE id=$id-$count")=="he"){ $small=$best[0]; while($i=0;$i<6;$i++){ if(mysql_query("SELECT score FROM allnews WHERE id= $small)"<mysql_query("SELECT score FROM allnews WHERE id=$best[i+1]")) $small=$best[i+1];} if(mysql_query("SELECT score FROM allnews WHERE id= $small")<mysql_query("SELECT score FROM Rebecca WHERE id=$id-$count")){ If allnews Black was a developer while($i=0;$i<6;$i++){ if($small==$best[i])$best[i]=mysql_query("SELECT id FROM allnews WHERE id=$id-$count");}}}} while($i=0;$i<6;$i++) echo '<a href="news-page.php?id='.$best[i].'"><div class="box '.mysql_query("SELECT type FROM allnews WHERE id=$best[i]").'">'.mysql_query("SELECT title FROM allnews WHERE id= $best[i]").'<div class="img" style="background-image:url(images/'.mysql_query("SELECT image1 FROM allnews WHERE id=$best[i]").');"></div></div></a>'; mysql_close($list); } ?>
  • 11. How do we fix it?
  • 13. cal·is·then·ics - noun - /ˌkaləsˈTHeniks/ Calisthenics are a form of dynamic exercise consisting of a variety of simple, often rhythmical, movements, generally using minimal equipment or apparatus. Object Calisthenics
  • 14. cal·is·then·ics - noun - /ˌkaləsˈTHeniks/ Calisthenics are a form of dynamic exercise consisting of a variety of simple, often rhythmical, movements, generally using minimal equipment or apparatus. Object Calisthenics A variety of simple, often rhythmical, exercises to achieve better OO and code quality
  • 15. “So here’s an exercise that can help you to internalize principles of good object-oriented design and actually use them in real life.” -- Jeff Bay Object Calisthenics
  • 16. “So here’s an exercise that can help you to internalize principles of good object-oriented design and actually use them in real life.” -- Jeff Bay Object Calisthenics Important: Written for JAVA Adaptations are needed for PHP, Ruby, JS, Python
  • 17. Object Calisthenics + Readability Tips
  • 18. “You need to write code that minimizes the time it would Object Calisthenics take someone else to understand it—even if that someone else is you.” + -- Dustin Boswell and Trevor Foucher Readability Tips
  • 19. “You need to write code that minimizes the time it would Object Calisthenics take someone else to understand it—even if that someone else is you.” + -- Dustin Boswell and Trevor Foucher Readability Tips I’m a tip
  • 20. Disclaimer: “These are guidelines, not rules” * Currently adapted to PHP
  • 21. OC #1 “Only one indentation level per method”
  • 22. function validateProducts($products) { // Check to make sure that our valid fields are in there $requiredFields = array( 'price', 'name', 'description', 'type', ); $valid = true; foreach ($products as $rawProduct) { $fields = array_keys($rawProduct); foreach ($requiredFields as $requiredField) { if (!in_array($requiredField, $fields)) { $valid = false; } } } return $valid; }
  • 23. function validateProducts($products) { // Check to make sure that our valid fields are in there $requiredFields = array( 'price', 'name', 'description', 'type', ); $valid = true; 0 foreach ($products as $rawProduct) { 1 $fields = array_keys($rawProduct); foreach ($requiredFields as $requiredField) { 2 if (!in_array($requiredField, $fields)) { 3 $valid = false; } } } return $valid; }
  • 24. function validateProducts($products) { // Check to make sure that our valid fields are in there $requiredFields = array( 'price', 'name', 'description', 'type', ); $valid = true; 0 foreach ($products as $rawProduct) { 1 $fields = array_keys($rawProduct); foreach ($requiredFields as $requiredField) { 2 if (!in_array($requiredField, $fields)) { 3 $valid = false; } } } return $valid; }
  • 25. function validateProducts($products) { // Check to make sure that our valid fields are in there $requiredFields = array( 'price', 'name', 'description', 'type', ); $valid = true; foreach ($products as $rawProduct) { $validationResult = validateSingleProduct($rawProduct, $requiredFields); if ( ! $validationResult){ $valid = false; } } return $valid; } function validateSingleProduct($product, $requiredFields) { $valid = true; $fields = array_keys($rawProduct); foreach ($requiredFields as $requiredField) { if (!in_array($requiredField, $fields)) { $valid = false; } } return $valid; }
  • 26. function validateProducts($products) { // Check to make sure that our valid fields are in there $requiredFields = array( 'price', 'name', 'description', 'type', ); $valid = true; whitespace foreach ($products as $rawProduct) { $validationResult = validateSingleProduct($rawProduct, $requiredFields); if ( ! $validationResult){ $valid = false; } } return $valid; } function validateSingleProduct($product, $requiredFields) { $valid = true; $fields = array_keys($rawProduct); foreach ($requiredFields as $requiredField) { if (!in_array($requiredField, $fields)) { $valid = false; } } return $valid; }
  • 27. function validateProducts($products) { // Check to make sure that our valid fields are in there $requiredFields = array( 'price', 'name', 'description', 'type', ); $valid = true; whitespace foreach ($products as $rawProduct) { $validationResult = validateSingleProduct($rawProduct, $requiredFields); if ( ! $validationResult){ $valid = false; } } return $valid; } function validateSingleProduct($product, $requiredFields) { $valid = true; duplicated logic $fields = array_keys($rawProduct); foreach ($requiredFields as $requiredField) { if (!in_array($requiredField, $fields)) { $valid = false; } } return $valid; }
  • 28. function validateProducts($products) { // Check to make sure that our valid fields are in there $requiredFields = array( 'price', 'name', 'description', 'type', ); $valid = true; 0 foreach ($products as whitespace $validationResult $rawProduct) { = validateSingleProduct($rawProduct, $requiredFields); 1 if ( ! $validationResult){ } 2 $valid = false; } return $valid; } function validateSingleProduct($product, $requiredFields) { $valid = true; duplicated logic $fields = array_keys($rawProduct); 0 foreach(!in_array($requiredField, $fields)) ($requiredFields as $requiredField) { 1 if { } 2 $valid = false; } return $valid; }
  • 29. function validateProducts($products) { // Check to make sure that our valid fields are in there $requiredFields = array( 'price', 'name', 'description', 'type', ); $valid = true; 0 foreach ($products as $validationResult $rawProduct) { = validateSingleProduct($rawProduct, $requiredFields); 1 if ( ! $validationResult){ } 2 $valid = false; } return $valid; } function validateSingleProduct($product, $requiredFields) { $valid = true; $fields = array_keys($rawProduct); 0 foreach(!in_array($requiredField, $fields)) ($requiredFields as $requiredField) { 1 if { } 2 $valid = false; } return $valid; }
  • 30. function validateProducts($products) { // Check to make sure that our valid fields are in there $requiredFields = array( 'price', 'name', 'description', 'type', ); $valid = true; 0 foreach ($products as $validationResult $rawProduct) { = validateSingleProduct($rawProduct, $requiredFields); 1 if ( ! $validationResult){ } 2 $valid = false; } return $valid; } function validateSingleProduct($product, $requiredFields) { $valid = true; $fields = array_keys($rawProduct); 0 foreach(!in_array($requiredField, $fields)) ($requiredFields as $requiredField) { 1 if { } 2 $valid = false; } return $valid; }
  • 31. function validateProducts($storeData) { $requiredFields = array('price','name','description','type'); foreach ($storeData['products'] as $rawProduct) { if ( ! validateSingleProduct($rawProduct, $requiredFields)) return false; } return true; } function validateSingleProduct($product, $requiredFields) { $fields = array_keys($rawProduct); $missingFields = array_diff($requiredFields, $fields); return (count($missingFields) == 0); }
  • 32. function validateProducts($storeData) { $requiredFields = array('price','name','description','type'); cheating! I see foreach ($storeData['products'] as $rawProduct) { if ( ! validateSingleProduct($rawProduct, $requiredFields)) return false; } return true; } function validateSingleProduct($product, $requiredFields) { $fields = array_keys($rawProduct); $missingFields = array_diff($requiredFields, $fields); return (count($missingFields) == 0); }
  • 33. function validateProducts($storeData) { $requiredFields = array('price','name','description','type'); cheating! I see foreach ($storeData['products'] as $rawProduct) { if ( ! validateSingleProduct($rawProduct, $requiredFields)) return false; } return true; Single line IF, simple operations } function validateSingleProduct($product, $requiredFields) { $fields = array_keys($rawProduct); $missingFields = array_diff($requiredFields, $fields); return (count($missingFields) == 0); }
  • 34. function validateProducts($storeData) { $requiredFields = array('price','name','description','type'); cheating! I see foreach ($storeData['products'] as $rawProduct) { if ( ! validateSingleProduct($rawProduct, $requiredFields)) return false; } return true; Single line IF, simple operations } function validateSingleProduct($product, $requiredFields) { $fields = array_keys($rawProduct); $missingFields = array_diff($requiredFields, $fields); return (count($missingFields) == 0); }
  • 35. function validateProducts($storeData) { $requiredFields = array('price','name','description','type'); foreach ($storeData['products'] as $rawProduct) { if ( ! validateSingleProduct($rawProduct, $requiredFields)) return false; } return true; Single line IF, simple operations return early } function validateSingleProduct($product, $requiredFields) { $fields = array_keys($rawProduct); $missingFields = array_diff($requiredFields, $fields); return (count($missingFields) == 0); }
  • 36. function validateProducts($storeData) { $requiredFields = array('price','name','description','type'); foreach ($storeData['products'] as $rawProduct) { if ( ! validateSingleProduct($rawProduct, $requiredFields)) return false; } return true; Single line IF, simple operations return early } function validateSingleProduct($product, $requiredFields) { $fields = array_keys($rawProduct); $missingFields = array_diff($requiredFields, $fields); return (count($missingFields) == 0); } C (native) functions are faster then PHP
  • 37. function validateProducts($storeData) { $requiredFields = array('price','name','description','type'); foreach ($storeData['products'] as $rawProduct) { if ( ! validateSingleProduct($rawProduct, $requiredFields)) return false; } return true; } function validateSingleProduct($product, $requiredFields) { $fields = array_keys($rawProduct); $missingFields = array_diff($requiredFields, $fields); return (count($missingFields) == 0); }
  • 38. function validateProducts($storeData) { $requiredFields = array('price','name','description','type'); foreach ($storeData['products'] as $rawProduct) { if ( ! validateSingleProduct($rawProduct, $requiredFields)) return false; } return true; } function validateSingleProduct($product, $requiredFields) { $fields = array_keys($rawProduct); $missingFields = array_diff($requiredFields, $fields); return (count($missingFields) == 0); }
  • 39. function validateProductList($products) { $validProducts = array_filter($products, 'isValidProduct'); return (count($products) === count($validProducts)); } function isValidProduct($rawProduct) { $requiredFields = array('price', 'name', 'description', 'type'); $fields = array_keys($rawProduct); $missingFields = array_diff($requiredFields, $fields); return (count($missingFields) == 0); }
  • 40. function validateProductList($products) faster iteration { $validProducts = array_filter($products, 'isValidProduct'); return (count($products) === count($validProducts)); } function isValidProduct($rawProduct) { $requiredFields = array('price', 'name', 'description', 'type'); $fields = array_keys($rawProduct); $missingFields = array_diff($requiredFields, $fields); return (count($missingFields) == 0); }
  • 41. function validateProductList($products) faster iteration { $validProducts = array_filter($products, 'isValidProduct'); return (count($products) === count($validProducts)); } reusable method function isValidProduct($rawProduct) { $requiredFields = array('price', 'name', 'description', 'type'); $fields = array_keys($rawProduct); $missingFields = array_diff($requiredFields, $fields); return (count($missingFields) == 0); }
  • 42. function validateProductList($products) faster iteration { $validProducts = array_filter($products, 'isValidProduct'); return (count($products) === count($validProducts)); } reusable method function isValidProduct($rawProduct) { $requiredFields = array('price', 'name', 'description', 'type'); $fields = array_keys($rawProduct); $missingFields = array_diff($requiredFields, $fields); return (count($missingFields) == 0); } method name matches “true” result
  • 43. function validateProductList($products) faster iteration { $validProducts = array_filter($products, 'isValidProduct'); return (count($products) === count($validProducts)); } assertable return: expected/returned reusable method function isValidProduct($rawProduct) { $requiredFields = array('price', 'name', 'description', 'type'); $fields = array_keys($rawProduct); $missingFields = array_diff($requiredFields, $fields); return (count($missingFields) == 0); } method name matches “true” result
  • 44. List is more readable the plural function validateProductList($products) faster iteration { $validProducts = array_filter($products, 'isValidProduct'); return (count($products) === count($validProducts)); } assertable return: expected/returned reusable method function isValidProduct($rawProduct) { $requiredFields = array('price', 'name', 'description', 'type'); $fields = array_keys($rawProduct); $missingFields = array_diff($requiredFields, $fields); return (count($missingFields) == 0); } method name matches “true” result
  • 45. Key Benefits • Single Responsibility Principle (S in SOLID) • Increases re-use
  • 46. OC #2 “Do not use the ‘else’ keyword”
  • 47. public function createPost($request) { $entity = new Post(); $form = new MyForm($entity); $form->bind($request); if ($form->isValid()){ $repository = $this->getRepository('MyBundle:Post'); if (!$repository->exists($entity) ) { $repository->save($entity); return $this->redirect('create_ok'); } else { $error = "Post Title already exists"; return array('form' => $form, 'error' => $error); } } else { $error = "Invalid fields"; return array('form' => $form, 'error' => $error); } }
  • 48. public function createPost($request) { $entity = new Post(); $form = new MyForm($entity); $form->bind($request); if ($form->isValid()){ $repository = $this->getRepository('MyBundle:Post'); if (!$repository->exists($entity) ) { $repository->save($entity); return $this->redirect('create_ok'); } else { $error = "Post Title already exists"; return array('form' => $form, 'error' => $error); } } else { $error = "Invalid fields"; return array('form' => $form, 'error' => $error); } }
  • 49. public function createPost($request) { $entity = new Post(); $form = new MyForm($entity); $form->bind($request); if ($form->isValid()){ $repository = $this->getRepository('MyBundle:Post'); if (!$repository->exists($entity) ) { $repository->save($entity); return $this->redirect('create_ok'); } else { $error = "Post Title already exists"; return array('form' => $form, 'error' => $error); } } else { $error = "Invalid fields"; return array('form' => $form, 'error' => $error); } }
  • 50. public function createPost($request) { $entity = new Post(); $form = new MyForm($entity); $form->bind($request); if ($form->isValid()){ $repository = $this->getRepository('MyBundle:Post'); if (!$repository->exists($entity) ) { $repository->save($entity); return $this->redirect('create_ok'); } else { $error = "Post Title already exists"; return array('form' => $form, 'error' => $error); } } else { $error = "Invalid fields"; return array('form' => $form, 'error' => $error); } }
  • 51. public function createPost($request) { $entity = new Post(); $form = new MyForm($entity); $form->bind($request); if ($form->isValid()){ $repository = $this->getRepository('MyBundle:Post'); if (!$repository->exists($entity) ) { $repository->save($entity); return $this->redirect('create_ok'); } else { $error = "Post Title already exists"; return array('form' => $form, 'error' => $error); } } else { $error = "Invalid fields"; return array('form' => $form, 'error' => $error); } }
  • 52. public function createPost($request) { $entity = new Post(); $form = new MyForm($entity); $form->bind($request); if ($form->isValid()){ $repository = $this->getRepository('MyBundle:Post'); if (!$repository->exists($entity) ) { $repository->save($entity); return $this->redirect('create_ok'); } else { $error = "Post Title already exists"; return array('form' => $form, 'error' => $error); } } else { $error = "Invalid fields"; return array('form' => $form, 'error' => $error); } }
  • 53. public function createPost($request) { $entity = new Post(); $form = new MyForm($entity); $form->bind($request); if ($form->isValid()){ $repository = $this->getRepository('MyBundle:Post'); if (!$repository->exists($entity) ) { $repository->save($entity); return $this->redirect('create_ok'); } else { $error = "Post Title already exists"; return array('form' => $form, 'error' => $error); } } else { $error = "Invalid fields"; return array('form' => $form, 'error' => $error); } }
  • 54. public function createPost($request) { $entity = new Post(); $form = new MyForm($entity); $form->bind($request); if ($form->isValid()){ $repository = $this->getRepository('MyBundle:Post'); if (!$repository->exists($entity) ) { $repository->save($entity); intermediate variable return $this->redirect('create_ok'); } else { $error = "Post Title already exists"; return array('form' => $form, 'error' => $error); } intermediate variable } else { $error = "Invalid fields"; return array('form' => $form, 'error' => $error); } }
  • 55. public function createPost($request) { $entity = new Post(); $repository = $this->getRepository('MyBundle:Post'); $form = new MyForm($entity); $form->bind($request); if ( ! $form->isValid()){ return array('form' => $form, 'error' => 'Invalid fields'); } if ($repository->exists($entity)){ return array('form' => $form, 'error' => 'Duplicate post title'); } $repository->save($entity); return $this->redirect('create_ok'); }
  • 56. public function createPost($request) { $entity = new Post(); $repository = $this->getRepository('MyBundle:Post'); $form = new MyForm($entity); $form->bind($request); if ( ! $form->isValid()){ return array('form' => $form, 'error' => 'Invalid fields'); } if ($repository->exists($entity)){ return array('form' => $form, 'error' => 'Duplicate post title'); } $repository->save($entity); return $this->redirect('create_ok'); }
  • 57. public function createPost($request) { $entity = new Post(); $repository = $this->getRepository('MyBundle:Post'); $form = new MyForm($entity); $form->bind($request); removed intermediates if ( ! $form->isValid()){ return array('form' => $form, 'error' => 'Invalid fields'); } if ($repository->exists($entity)){ return array('form' => $form, 'error' => 'Duplicate post title'); } $repository->save($entity); return $this->redirect('create_ok'); }
  • 58. public function createPost($request) { $entity = new Post(); $repository = $this->getRepository('MyBundle:Post'); $form = new MyForm($entity); $form->bind($request); removed intermediates if ( ! $form->isValid()){ return array('form' => $form, 'error' => 'Invalid fields'); } early return if ($repository->exists($entity)){ return array('form' => $form, 'error' => 'Duplicate post title'); } $repository->save($entity); return $this->redirect('create_ok'); }
  • 59. Separate code into blocks. public function createPost($request) { Its like using $entity = new Post(); $repository = $this->getRepository('MyBundle:Post'); Paragraphs. $form = new MyForm($entity); $form->bind($request); removed intermediates if ( ! $form->isValid()){ return array('form' => $form, 'error' => 'Invalid fields'); } early return if ($repository->exists($entity)){ return array('form' => $form, 'error' => 'Duplicate post title'); } $repository->save($entity); return $this->redirect('create_ok'); }
  • 60. Key Benefits • Helps avoid code duplication • Easier to read (single true path) • Reduces cyclomatic complexity
  • 61. Ad ap te d OC #3 “Wrap primitive types and strings” * if there is behavior
  • 65. //... $component->repaint(false); unclear operation class UIComponent { //... public function repaint($animate = true){ //... } }
  • 66. class UIComponent { //... public function repaint( Animate $animate ){ //... } } class Animate { public $animate; public function __construct( $animate = true ) { $this->animate = $animate;} } //... $component->repaint( new Animate(false) );
  • 67. class UIComponent { //... public function repaint( Animate $animate ){ //... } } class Animate This can now encapsulate all { animation related operations public $animate; public function __construct( $animate = true ) { $this->animate = $animate;} } //... $component->repaint( new Animate(false) );
  • 68. Key Benefits • Helps identify what should be an Object • Type Hinting • Encapsulation of operations
  • 69. Ad ap te d OC #4 “Only one -> per line” or dot * getter chain or a fluent interface
  • 70. Source: CodeIgniter $this->base_url = $this->CI->config->site_url().'/'.$this->CI->uri->segment(1).$this- >CI->uri->slash_segment(2, 'both'); $this->base_uri = $this->CI->uri->segment(1).$this->CI->uri->slash_segment(2, 'leading');
  • 71. Source: CodeIgniter properties are harder to mock $this->base_url = $this->CI->config->site_url().'/'.$this->CI->uri->segment(1).$this- >CI->uri->slash_segment(2, 'both'); $this->base_uri = $this->CI->uri->segment(1).$this->CI->uri->slash_segment(2, 'leading');
  • 72. Source: CodeIgniter properties are harder to mock $this->base_url = $this->CI->config->site_url().'/'.$this->CI->uri->segment(1).$this- >CI->uri->slash_segment(2, 'both'); no whitespace $this->base_uri = $this->CI->uri->segment(1).$this->CI->uri->slash_segment(2, 'leading');
  • 73. Source: CodeIgniter properties are harder to mock $this->base_url = $this->CI->config->site_url().'/'.$this->CI->uri->segment(1).$this- >CI->uri->slash_segment(2, 'both'); no whitespace $this->base_uri = $this->CI->uri->segment(1).$this->CI->uri->slash_segment(2, 'leading'); - Underlying encapsulation problem - Hard to debug and test - Hard to read and understand
  • 74. Source: CodeIgniter properties are harder to mock $this->base_url = $this->CI->config->site_url().'/'.$this->CI->uri->segment(1).$this- >CI->uri->slash_segment(2, 'both'); no whitespace $this->base_uri = $this->CI->uri->segment(1).$this->CI->uri->slash_segment(2, 'leading'); move everything to uri object $this->getCI()->getUriBuilder()->getBaseUri(‘leading’); - Underlying encapsulation problem - Hard to debug and test - Hard to read and understand
  • 75. Source: Zend Framework App $filterChain->addFilter(new Zend_Filter_Alpha()) ->addFilter(new Zend_Filter_StringToLower()); Source: Symfony 2 Docs.
  • 76. Source: Zend Framework App fluent interface $filterChain->addFilter(new Zend_Filter_Alpha()) ->addFilter(new Zend_Filter_StringToLower()); Source: Symfony 2 Docs.
  • 77. Source: Zend Framework App fluent interface $filterChain->addFilter(new Zend_Filter_Alpha()) ->addFilter(new Zend_Filter_StringToLower()); operator alignment Source: Symfony 2 Docs.
  • 78. Source: Zend Framework App fluent interface $filterChain->addFilter(new Zend_Filter_Alpha()) ->addFilter(new Zend_Filter_StringToLower()); operator alignment $user = $this->get('security.context')->getToken()->getUser(); Source: Symfony 2 Docs.
  • 79. Source: Zend Framework App fluent interface $filterChain->addFilter(new Zend_Filter_Alpha()) ->addFilter(new Zend_Filter_StringToLower()); operator alignment only getters (no operations) $user = $this->get('security.context')->getToken()->getUser(); Source: Symfony 2 Docs.
  • 80. Source: Zend Framework App fluent interface $filterChain->addFilter(new Zend_Filter_Alpha()) ->addFilter(new Zend_Filter_StringToLower()); operator alignment only getters (no operations) $user = $this->get('security.context')->getToken()->getUser(); Source: Symfony 2 Docs.
  • 81. Source: Zend Framework App fluent interface $filterChain->addFilter(new Zend_Filter_Alpha()) ->addFilter(new Zend_Filter_StringToLower()); operator alignment only getters (no operations) $user = $this->get('security.context')->getToken()->getUser(); where did my autocomplete go? Source: Symfony 2 Docs.
  • 82. Source: Zend Framework App fluent interface $filterChain->addFilter(new Zend_Filter_Alpha()) ->addFilter(new Zend_Filter_StringToLower()); operator alignment only getters (no operations) $user = $this->get('security.context')->getToken()->getUser(); where did my return null? autocomplete go? Source: Symfony 2 Docs.
  • 83. Key Benefits • Readability • Easier Mocking (Testing) • Easier to Debug • Demeter’s Law
  • 84. OC #5 “Do not Abbreviate”
  • 85. if($sx >= $sy) { if ($sx > $strSysMatImgW) { $ny = $strSysMatImgW * $sy / $sx; $nx = $strSysMatImgW; } if ($ny > $strSysMatImgH) { $nx = $strSysMatImgH * $sx / $sy; $ny = $strSysMatImgH; } } else { if ($sy > $strSysMatImgH) { $nx = $strSysMatImgH * $sx / $sy; $ny = $strSysMatImgH; } if($nx > $strSysMatImgW) { $ny = $strSysMatImgW * $sy / $sx; $nx = $strSysMatImgW; } }
  • 86. ? if($sx >= $sy) { ? if ($sx > $strSysMatImgW) { $ny = $strSysMatImgW * $sy / $sx; $nx = $strSysMatImgW; } ? if ($ny > $strSysMatImgH) { $nx = $strSysMatImgH * $sx / $sy; $ny = $strSysMatImgH; } } else { ? if ($sy > $strSysMatImgH) { $nx = $strSysMatImgH * $sx / $sy; $ny = $strSysMatImgH; } ? if($nx > $strSysMatImgW) { $ny = $strSysMatImgW * $sy / $sx; $nx = $strSysMatImgW; } }
  • 87. Why do you abbreviate?
  • 88. Why do you abbreviate? Its repeated many times, and i’m lazy.
  • 89. Why do you abbreviate? Its repeated many times, and i’m lazy. Underlying Problem! You need to transfer those operations into a separate class.
  • 90. Why do you abbreviate? function processResponseHeadersAndDefineOutput($response) { ... }
  • 91. Why do you abbreviate? function processResponseHeadersAndDefineOutput($response) { ... } This method name is too long to type, and i’m lazy.
  • 92. Why do you abbreviate? more than one responsibility? function processResponseHeadersAndDefineOutput($response) { ... } This method name is too long to type, and i’m lazy.
  • 93. function getPage($data) { ... } function startProcess() { ... } $tr->process(“site.login”);
  • 94. get from where? function getPage($data) { ... } function startProcess() { ... } $tr->process(“site.login”);
  • 95. get from where? Use clearer names: function getPage($data) { ... } fetchPage() downloadPage() function startProcess() { ... } $tr->process(“site.login”);
  • 96. get from where? Use clearer names: function getPage($data) { ... } fetchPage() downloadPage() Use a thesaurus: function startProcess() { ... } fork, create, begin, open $tr->process(“site.login”);
  • 97. get from where? Use clearer names: function getPage($data) { ... } fetchPage() downloadPage() Use a thesaurus: function startProcess() { ... } fork, create, begin, open Table row? $tr->process(“site.login”);
  • 98. get from where? Use clearer names: function getPage($data) { ... } fetchPage() downloadPage() Use a thesaurus: function startProcess() { ... } fork, create, begin, open Table row? Easy understanding, complete scope: $tr->process(“site.login”); $translatorService
  • 99. Key Benefits • Clearer communication and maintainability • Indicates underlying problems
  • 100. Ad ap te d OC #6 “Keep your classes small”
  • 101. 200 lines per class 10 methods per class 15 classes per package
  • 102. Increased to include docblocks 200 lines per class 10 methods per class 15 classes per package
  • 103. Increased to include docblocks 15-20 lines per method 200 lines per class 10 methods per class 15 classes per package
  • 104. Increased to include docblocks 15-20 lines per method 200 lines per class 10 methods per class 15 classes per package read this as namespace or folder
  • 105. Key Benefits • Single Responsibility • Objective and clear methods • Slimmer namespaces • Avoids clunky folders
  • 106. Ad ap OC #7 te d “Limit the number of instance variables in a class (2 to 5)”
  • 107. class MyRegistrationService { protected $userService; protected $passwordService; protected $logger; protected $translator; protected $entityManager; protected $imageCropper; // ... }
  • 108. class MyRegistrationService { protected $userService; protected $passwordService; protected $logger; protected $translator; protected $entityManager; protected $imageCropper; // ... } Limit: 5
  • 109. class MyRegistrationService { protected $userService; protected $passwordService; protected $logger; All DB interaction protected $translator; should be in protected $entityManager; userService protected $imageCropper; Use an event based system and move this // ... to listener } Limit: 5
  • 110. Key Benefits • Shorter dependency list • Easier Mocking for unit test
  • 111. OC #8 “Use first class collections”
  • 112. Doctrine: ArrayCollection $collection->getIterator(); $collection->filter(...); $collection->append(...); $collection->map(...);
  • 113. Key Benefits • Implements collection operations • Uses SPL interfaces • Easier to merge collections and not worry about member behaviour in them
  • 114. Dr op pe d OC #9 “Do not use accessors (getter/setter)” * Use them if you code PHP
  • 115. /** * THIS CLASS WAS GENERATED BY THE DOCTRINE ORM. DO NOT EDIT THIS FILE. */ class DoctrineTestsModelsCMSCmsUserProxy extends DoctrineTestsModelsCMSCmsUser implements DoctrineORMProxyProxy { public function getId() { $this->__load(); return parent::getId(); } public function getStatus() { $this->__load(); return parent::getStatus(); } Property get/set syntax RFC may change the game.
  • 116. /** * THIS CLASS WAS GENERATED BY THE DOCTRINE ORM. DO NOT EDIT THIS FILE. */ class DoctrineTestsModelsCMSCmsUserProxy extends DoctrineTestsModelsCMSCmsUser implements DoctrineORMProxyProxy { public function getId() { $this->__load(); Example: Doctrine uses getters to return parent::getId(); inject lazy loading operations } public function getStatus() { $this->__load(); return parent::getStatus(); } Property get/set syntax RFC may change the game.
  • 117. Key Benefits • Injector operations • Encapsulation of transformations
  • 118. Cr ea te d! OC #10 (bonus!) “Document your code!”
  • 119. //check to see if the section above set the $overall_pref variable to void if ($overall_pref == 'void') // implode the revised array of selections in group three into a string // variable so that it can be transferred to the database at the end of the // page $groupthree = implode($groupthree_array, "nr");
  • 120. really? //check to see if the section above set the $overall_pref variable to void if ($overall_pref == 'void') // implode the revised array of selections in group three into a string // variable so that it can be transferred to the database at the end of the // page $groupthree = implode($groupthree_array, "nr");
  • 121. really? //check to see if the section above set the $overall_pref variable to void if ($overall_pref == 'void') // implode the revised array of selections in group three into a string // variable so that it can be transferred to the database at the end of the // page $groupthree = implode($groupthree_array, "nr"); Documenting because i’m doing it wrong in an unusual way
  • 122. $priority = isset($event['priority']) ? $event['priority'] : 0; if (!isset($event['event'])) { throw new InvalidArgumentException(...)); } if (!isset($event['method'])) { $event['method'] = 'on'.preg_replace(array( '/(?<=b)[a-z]/ie', '/[^a-z0-9]/i' ), array('strtoupper("0")', ''), $event['event']); } $definition->addMethodCall( 'addListenerService', array($event['event'], array($listenerId, $event['method']), $priority )); Source: Symfony2
  • 123. $priority = isset($event['priority']) ? $event['priority'] : 0; if (!isset($event['event'])) { throw new InvalidArgumentException(...)); } if (!isset($event['method'])) { $event['method'] = 'on'.preg_replace(array( '/(?<=b)[a-z]/ie', '/[^a-z0-9]/i' ), array('strtoupper("0")', ''), $event['event']); } $definition->addMethodCall( 'addListenerService', array($event['event'], array($listenerId, What does this do? $event['method']), $priority )); Source: Symfony2
  • 124. $priority = isset($event['priority']) ? $event['priority'] : 0; Add a simple comment: if (!isset($event['event'])) { throw new //Strips special chars and camel cases to onXxx InvalidArgumentException(...)); } if (!isset($event['method'])) { $event['method'] = 'on'.preg_replace(array( '/(?<=b)[a-z]/ie', '/[^a-z0-9]/i' ), array('strtoupper("0")', ''), $event['event']); } $definition->addMethodCall( 'addListenerService', array($event['event'], array($listenerId, What does this do? $event['method']), $priority )); Source: Symfony2
  • 125. $priority = isset($event['priority']) ? $event['priority'] : 0; Add a simple comment: if (!isset($event['event'])) { throw new //Strips special chars and camel cases to onXxx InvalidArgumentException(...)); } if (!isset($event['method'])) { $event['method'] = 'on'.preg_replace(array( Don’t explain bad '/(?<=b)[a-z]/ie', code, fix it! '/[^a-z0-9]/i' ), array('strtoupper("0")', ''), $event['event']); } $definition->addMethodCall( 'addListenerService', array($event['event'], array($listenerId, What does this do? $event['method']), $priority )); Source: Symfony2
  • 126. /** * Checks whether an element is contained in the collection. * This is an O(n) operation, where n is the size of the collection. * * @todo implement caching for better performance * @param mixed $element The element to search for. * @return boolean TRUE if the collection contains the element, or FALSE. */ function contains($element);
  • 127. /** * Checks whether an element is contained in the collection. * This is an O(n) operation, where n is the size of the collection. * * @todo implement caching for better performance * @param mixed $element The element to search for. * @return boolean TRUE if the collection contains the element, or FALSE. */ function contains($element); mark todo items so the changes don’t get lost
  • 128. A note on cost of running function /** * Checks whether an element is contained in the collection. * This is an O(n) operation, where n is the size of the collection. * * @todo implement caching for better performance * @param mixed $element The element to search for. * @return boolean TRUE if the collection contains the element, or FALSE. */ function contains($element); mark todo items so the changes don’t get lost
  • 129. Do a mind dump, then clean it up. A note on cost of running function /** * Checks whether an element is contained in the collection. * This is an O(n) operation, where n is the size of the collection. * * @todo implement caching for better performance * @param mixed $element The element to search for. * @return boolean TRUE if the collection contains the element, or FALSE. */ function contains($element); mark todo items so the changes don’t get lost
  • 130. Do a mind dump, then clean it up. A note on cost of running function /** * Checks whether an element is contained in the collection. * This is an O(n) operation, where n is the size of the collection. * * @todo implement caching for better performance * @param mixed $element The element to search for. * @return boolean TRUE if the collection contains the element, or FALSE. */ function contains($element); Generate API docs with phpDocumentor mark todo items so the changes don’t get lost
  • 131. Key Benefits • Automatic API documentation • Transmission of “line of thought” • Avoids confusion
  • 132. Recap • #1 - Only one indentation level per method. • #2 - Do not use the ‘else’ keyword. • #3 - Wrap primitive types and strings. • #4 - Only one -> per line. • #5 - Do not Abbreviate. • #6 - Keep your classes small. • #7 - Limit the number of instance variables in a class (max: 5) • #8 - Use first class collections • #9 - Use accessors (getter/setter) * • #10 - Document your code!
  • 133. Questions? http://fixthatcode.com @rdohms http://doh.ms http://slides.doh.ms
  • 134. Recommended Links: The ThoughtWorks Anthology http://goo.gl/OcSNx The Art of Readable Code http://goo.gl/unrij DISCLAIMER: This talk re-uses some of the examples used by Guilherme Blanco in his original Object Calisthenic talk. These principles were studied and applied by us while we worked together in previous jobs. The result taught us all a lesson we really want to spread to other developers.