Your code sucks, let's fix it - PHP Master Series 2012
- 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.
- 8. <?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);
}
?>
- 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 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);
}
?>
- 12. 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
- 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
A variety of simple, often
rhythmical, exercises to achieve
better OO and code quality
- 14. “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
- 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
Important:
PHP != JAVA
Adaptations will be done
- 17. “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
- 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
I’m a tip
- 21. 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;
}
- 22. 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;
}
- 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;
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;
}
- 25. 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;
}
- 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; duplicated logic
$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;
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;
}
- 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
$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;
}
- 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($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);
}
- 31. 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);
}
- 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; Single line IF, simple operations
}
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');
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);
}
- 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);
} C (native) functions are
faster then PHP
- 36. 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);
}
- 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 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);
}
- 39. 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);
}
- 40. 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);
}
- 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);
}
method name matches “true” result
- 42. 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
- 43. 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
- 46. 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);
}
}
- 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);
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);
}
}
- 54. 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');
}
- 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);
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');
}
- 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');
}
early return
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);
Alternate solution:
return $this->redirect('create_ok'); Use Exceptions
}
- 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);
Alternate solution:
return $this->redirect('create_ok'); Use Exceptions
}
- 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
- 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”
* 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.
- 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;
}
}
- 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.
- 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
- 100. Ad
ap
te
d
OC #6
“Keep your classes
small”
- 101. 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
- 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 behavior in them
- 114. Dr
op
pe
d
OC #9
“Do not use getters/
setters”
* 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 accessor (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 accessor (get/set) syntax RFC may change the game.
- 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
- 132. Recap
• #1 - Only one indentation level per method.
• #2 - Do not use the ‘else’ keyword.
• #3 - Wrap primitive types and string, if it has behavior.
• #4 - Only one -> per line, if not getter or fluent.
• #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 getter/setter
• #10 - Document your code!
- 133. Questions?
http://fixthatcode.com
@rdohms
http://doh.ms http://joind.in/7941
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.