Why Your Code Suxx
And why you should care about it ?
www.blubird.eu
Started mid 2013
Audit & consultancy
Project staffing
Project management
ABOUT BLUBIRD
Web & Mobile apps
Virtualization and cloud
Performance & optimization
Security
SMAC the picture
Security, Mobility, Apps & Cloud
Nicolas De Boose
➢ Freelancer
➢ PHP, Symfony2, (Node) Js, Css
➢ 10 yrs pro
➢ A wonderful fiancee <3
➢ @NicoDeBoose
➢ Blog: mechantblog.com
ABOUT ME
WHY THIS SUBJECT?
1. Laravel vs Symfony2 vs Zend Framework2 ? Google it!
2. “Clean code” is a subject I like
– Pull request
– Gave lots of reviews
– Read/watch Clean coders (book & website)
3. Every year, new coders must learn best practices, solid
principles, patterns, ... And it’s a good booster shot
HOW ?
1. Presentation of code that smells
2. Your opinion
3. Refactoring
//Let's go to the bar
interface Human{
public function getAge();
public function getFullName();
}
interface Drink{
public function getPrice();
}
interface Bar{
public function removeFromStock(Drink $drink);
public function hasInStock(Drink $drink);
}
class BarMan{
private $bar;
public function command(Human $human, Drink $drink){
if ($this->bar->hasInStock($drink) == true) {
if ($drink->doesNotContainAlcohol()) {
$this->prepare($human, $drink);
} else {
if ($human->getAge() >= 18){
$this->prepare($human, $drink);
} else {
$this->errorMessage = 'Too young :(';
return false;
}
}
} else {
$this->errorMessage = 'Not in stock :(';
}
return true;
}
}
class BarMan{
private $bar;
public function command(Human $human, Drink $drink){
if ($this->bar->hasInStock($drink) == true) {
if ($drink->doesNotContainAlcohol()) {
$this->prepare($human, $drink);
} else {
if ($human->getAge() >= 18){
$this->prepare($human, $drink);
} else {
$this->errorMessage = 'Too young :(';
return false;
}
}
} else {
$this->errorMessage = 'Not in stock :(';
}
return true;
}
}
Focus on this code
//In Barman::command(Human $human, Drink $drink)
if ($this->bar->hasInStock($drink) == true) {
if ($drink->doesNotContainAlcohol()) {
$this->prepare($human, $drink);
} else {
if ($human->getAge() >= 18){
$this->prepare($human, $drink);
} else {
$this->errorMessage = 'Too young :(';
return false;
}
}
} else {
$this->errorMessage = 'Not in stock :(';
return false;
}
return true;
//In Barman::command(Human $human, Drink $drink)
if ($this->bar->hasInStock($drink) == true) {
if ($drink->doesNotContainAlcohol()) {
$this->prepare($human, $drink);
} else {
if ($human->getAge() >= 18){
$this->prepare($human, $drink);
} else {
$this->errorMessage = 'Too young :(';
return false;
}
}
} else {
$this->errorMessage = 'Not in stock :(';
return false;
}
return true;
== true) == true) ...
//In Barman::command(Human $human, Drink $drink)
if ($this->bar->hasInStock($drink)) {
if ($drink->doesNotContainAlcohol()) {
$this->prepare($human, $drink);
} else {
if ($human->getAge() >= 18){
$this->prepare($human, $drink);
} else {
$this->errorMessage = 'Too young :(';
return false;
}
}
} else {
$this->errorMessage = 'Not in stock :(';
return false;
}
return true;
//In Barman::command(Human $human, Drink $drink)
if ($this->bar->hasInStock($drink)) {
if ($drink->doesNotContainAlcohol()) {
$this->prepare($human, $drink);
} else {
if ($human->getAge() >= 18){
$this->prepare($human, $drink);
} else {
$this->errorMessage = 'Too young :(';
return false;
}
}
} else {
$this->errorMessage = 'Not in stock :(';
return false;
}
return true;
Nested if’s
//In Barman::command(Human $human, Drink $drink)
if ($this->bar->hasInStock($drink)) {
if ($drink->doesNotContainAlcohol()) {
$this->prepare($human, $drink);
} else {
if ($human->getAge() >= 18){
$this->prepare($human, $drink);
} else {
$this->errorMessage = 'Too young :(';
return false;
}
}
} else {
$this->errorMessage = 'Not in stock :(';
return false;
}
return true;
//In Barman::command(Human $human, Drink $drink)
if (!$this->bar->hasInStock($drink)) {
$this->errorMessage = 'Not in stock :(';
return false;
}
if ($drink->doesNotContainAlcohol()) {
$this->prepare($human, $drink);
} else {
if ($human->getAge() >= 18){
$this->prepare($human, $drink);
} else {
$this->errorMessage = 'Too young :(';
return false;
}
}
return true;
//In Barman::command(Human $human, Drink $drink)
if (!$this->bar->hasInStock($drink)) {
$this->errorMessage = 'Not in stock :(';
return false;
}
if ($drink->doesNotContainAlcohol()) {
$this->prepare($human, $drink);
} else {
if ($human->getAge() >= 18){
$this->prepare($human, $drink);
} else {
$this->errorMessage = 'Too young :(';
return false;
}
}
return true;
call to “prepare()” here
And here + nested if
//In Barman::command(Human $human, Drink $drink)
if (!$this->bar->hasInStock($drink)) {
$this->errorMessage = 'Not in stock :(';
return false;
}
if (!$drink->doesNotContainAlcohol()
&& $human->getAge() < 18
) {
$this->errorMessage = 'Too young :(';
return false;
}
$this->prepare($human, $drink);
return true;
//In Barman::command(Human $human, Drink $drink)
if (!$this->bar->hasInStock($drink)) {
$this->errorMessage = 'Not in stock :(';
return false;
}
if (!$drink->doesNotContainAlcohol()
&& $human->getAge() < 18
) {
$this->errorMessage = 'Too young :(';
return false;
}
$this->prepare($human, $drink);
return true;
NOT doesNot… My head hurts!
//In Barman::command(Human $human, Drink $drink)
if (!$this->bar->hasInStock($drink)) {
$this->errorMessage = 'Not in stock :(';
return false;
}
if ($drink->containsAlcohol()
&& $human->getAge() < 18
) {
$this->errorMessage = 'Too young :(';
return false;
}
$this->prepare($human, $drink);
return true;
//In Barman::command(Human $human, Drink $drink)
if (!$this->bar->hasInStock($drink)) {
$this->errorMessage = 'Not in stock :(';
return false;
}
if ($drink->containsAlcohol()
&& $human->getAge() < 18
) {
$this->errorMessage = 'Too young :(';
return false;
}
$this->prepare($human, $drink);
return true;
can we be more explicit?
//In Barman::command(Human $human, Drink $drink)
if (!$this->bar->hasInStock($drink)) {
$this->errorMessage = 'Not in stock :(';
return false;
}
if ($drink->containsAlcohol()
&& $human->isYoungerThan(18)
) {
$this->errorMessage = 'Too young :(';
return false;
}
$this->prepare($human, $drink);
return true;
//In Barman::command(Human $human, Drink $drink)
if (!$this->bar->hasInStock($drink)) {
$this->errorMessage = 'Not in stock :(';
return false;
}
if ($drink->containsAlcohol()
&& $human->isYoungerThan(18)
) {
$this->errorMessage = 'Too young :(';
return false;
}
$this->prepare($human, $drink);
return true;
It’s not my job to keep the error message
It’s not my job to keep the error message
//In Barman::command(Human $human, Drink $drink)
if (!$this->bar->hasInStock($drink)) {
throw new DrinkNotInStockException($drink);
}
if ($drink->containsAlcohol()
&& $human->isYoungerThan(18)
) {
return new CommandResult(false,'Too young :(');
}
$this->prepare($human, $drink);
return true;
Solution 1: Throw an exception
Solution 2: Return a “Result” object
Solution 1: Return nothing
Solution 2: Return a “Result” object
//In Barman::command(Human $human, Drink $drink)
if (!$this->bar->hasInStock($drink)) {
throw new DrinkNotInStockException($drink);
}
if ($drink->containsAlcohol()
&& $human->isYoungerThan(18)
) {
throw new TooYoungToDrinkAlcoholException();
}
$this->prepare($human, $drink);
//In Barman::command(Human $human, Drink $drink)
if (!$this->bar->hasInStock($drink)) {
throw new DrinkNotInStockException($drink);
}
if ($drink->containsAlcohol()
&& $human->isYoungerThan(18)
) {
throw new TooYoungToDrinkAlcoholException();
}
$this->prepare($human, $drink);
What is “18”?
It’s a magic number!
//In Barman::command(Human $human, Drink $drink)
if (!$this->bar->hasInStock($drink)) {
throw new DrinkNotInStockException($drink);
}
if ($drink->containsAlcohol()
&& $human->isYoungerThan
(self::MINIMUM_AGE_FOR_ALCOHOL)
) {
throw new TooYoungToDrinkAlcoholException();
}
$this->prepare($human, $drink);
Better but not the best place
to store the constant
//In Barman::command(Human $human, Drink $drink)
if (!$this->bar->hasInStock($drink)) {
throw new DrinkNotInStockException($drink);
}
if ($drink->containsAlcohol()
&& $human->isYoungerThan
(BarLegislation::MINIMUM_AGE_FOR_ALCOHOL)
) {
throw new TooYoungToDrinkAlcoholException();
}
$this->prepare($human, $drink);
There, it’s good enough
for the moment
//In Barman::command(Human $human, Drink $drink)
if (!$this->bar->hasInStock($drink))
throw new DrinkNotInStockException($drink);
if ($drink->containsAlcohol()
&& $human->isYoungerThan
(BarLegislation::MINIMUM_AGE_FOR_ALCOHOL)
)
throw new TooYoungToDrinkAlcoholException();
$this->prepare($human, $drink);
interface Human{
// public function getAge(); => deleted
public function isYoungerThan($age); //New
public function getFullName();
}
interface Drink{
public function getPrice();
public function containsAlcohol(); //Rename
}
interface Bar{
public function removeFromStock(Drink $drink);
public function hasInStock(Drink $drink);
}
//In Barman::prepare(Human $human, Drink $drink)
$tmp = explode(" ", $human->getFullName());
$ac = "";
foreach ($tmp as $w) {
$ac .= $w[0];
}
$human->addToBill($drink);
$this->bar->removeFromStock($drink);
/* ... Prepare the drink ... */
$drink->setAcronym($ac);
SmsSender::send(
$human->getMobilePhoneNumber(),
sprintf('Drink %s ready', $ac)
);
//In Barman::prepare(Human $human, Drink $drink)
$tmp = explode(" ", $human->getFullName());
$ac = "";
foreach ($tmp as $w) {
$ac .= $w[0];
}
$human->addToBill($drink);
$this->bar->removeFromStock($drink);
/* ... Prepare the drink ... */
$drink->setAcronym($ac);
SmsSender::send(
$human->getMobilePhoneNumber(),
sprintf('Drink %s ready', $ac)
);
What does “tmp” mean?
What does “ac” mean?
//In Barman::prepare(Human $human, Drink $drink)
$nameParts = explode(" ",$human->getFullName());
$acronym = "";
foreach ($nameParts as $w) {
$acronym .= $w[0];
}
$human->addToBill($drink);
$this->bar->removeFromStock($drink);
/* ... Prepare the drink ... */
$drink->setAcronym($acronym);
SmsSender::send(
$human->getMobilePhoneNumber(),
sprintf('Drink %s ready', $acronym)
);
//In Barman::prepare(Human $human, Drink $drink)
$nameParts = explode(" ",$human->getFullName());
$acronym = "";
foreach ($nameParts as $w) {
$acronym .= $w[0];
}
$human->addToBill($drink);
$this->bar->removeFromStock($drink);
/* ... Prepare the drink ... */
$drink->setAcronym($acronym);
SmsSender::send(
$human->getMobilePhoneNumber(),
sprintf('Drink %s ready', $acronym)
);
Can we compute the
acronym elsewhere?
//In Barman::prepare(Human $human, Drink $drink)
$acronym = $this->getHumanAcronym($human);
$human->addToBill($drink);
$this->bar->removeFromStock($drink);
/* ... Prepare the drink ... */
$drink->setAcronym($acronym);
SmsSender::send(
$human->getMobilePhoneNumber(),
sprintf('Drink %s ready', $acronym)
);
That’s better,
but ...
//In Barman::prepare(Human $human, Drink $drink)
$acronym = $human->getAcronym();
$human->addToBill($drink);
$this->bar->removeFromStock($drink);
/* ... Prepare the drink ... */
$drink->setAcronym($acronym);
SmsSender::send(
$human->getMobilePhoneNumber(),
sprintf('Drink %s ready', $acronym)
);
//In Barman::prepare(Human $human, Drink $drink)
$acronym = $human->getAcronym();
$human->addToBill($drink);
$this->bar->removeFromStock($drink);
/* ... Prepare the drink ... */
$drink->setAcronym($acronym);
SmsSender::send(
$human->getMobilePhoneNumber(),
sprintf('Drink %s ready', $acronym)
);
Now let’s remove
$acronym var
//In Barman::prepare(Human $human, Drink $drink)
$human->addToBill($drink);
$this->bar->removeFromStock($drink);
/* ... Prepare the drink ... */
$drink->setAcronym($human->getAcronym());
SmsSender::send(
$human->getMobilePhoneNumber(),
sprintf('Drink %s ready',$human->getAcronym())
);
//In Barman::prepare(Human $human, Drink $drink)
$human->addToBill($drink);
$this->bar->removeFromStock($drink);
/* ... Prepare the drink ... */
$drink->setAcronym($human->getAcronym());
SmsSender::send(
$human->getMobilePhoneNumber(),
sprintf('Drink %s ready',$human->getAcronym())
);
A bill for a “simple” human?
//In Barman::prepare(Human $human, Drink $drink)
$client = new Client($human);
$client->addDrink($drink);
$this->bar->removeFromStock($drink);
/* ... Prepare the drink ... */
$drink->setAcronym($human->getAcronym());
SmsSender::send(
$human->getMobilePhoneNumber(),
sprintf('Drink %s ready',$human->getAcronym())
);
Create a “Client” class:
He knows what he’s
drinking
//In Barman::prepare(Human $human, Drink $drink)
$client = new Client($human);
$client->addDrink($drink);
$this->bar->removeFromStock($drink);
/* ... Prepare the drink ... */
$drink->setAcronym($human->getAcronym());
SmsSender::send(
$human->getMobilePhoneNumber(),
sprintf('Drink %s ready',$human->getAcronym())
);
A drink should not be
more than a drink.
//In Barman::prepare(Human $human, Drink $drink)
$client = new Client($human);
$client->addDrink($drink);
$this->bar->removeFromStock($drink);
/* ... Prepare the drink ... */
$labeledDrink = new LabeledDrink(
$drink,
$human->getAcronym());
SmsSender::send(
$human->getMobilePhoneNumber(),
sprintf('Drink %s ready',
$labeledDrink->getLabel())
);
Create a class that
symbolizes the drink
with an acronym
Take the label from the new object
//In Barman::prepare(Human $human, Drink $drink)
$client = new Client($human);
$client->addDrink($drink);
$this->bar->removeFromStock($drink);
/* ... Prepare the drink ... */
$labeledDrink = new LabeledDrink(
$drink,
$human->getAcronym());
SmsSender::send(
$human->getMobilePhoneNumber(),
sprintf('Drink %s ready',
$labeledDrink->getLabel())
);
Is there a better
place to instantiate
the object?
//In Barman::prepare(Human $human, Drink $drink)
$client = new Client($human);
$client->addDrink($drink);
$this->bar->removeFromStock($drink);
/* ... Prepare the drink ... */
$labeledDrink = new LabeledDrink(
$drink,
$human->getAcronym());
SmsSender::send(
$human->getMobilePhoneNumber(),
sprintf('Drink %s ready',
$labeledDrink->getLabel())
);
Is there a better
place to instantiate
the object?
Maybe here?
//In Barman::prepare(Human $human, Drink $drink)
$client = new Client($human);
$labeledDrink = $client->addDrink($drink);
$this->bar->removeFromStock($drink);
/* ... Prepare the drink ... */
SmsSender::send(
$human->getMobilePhoneNumber(),
sprintf('Drink %s ready',
$labeledDrink->getLabel())
);
//In Barman::prepare(Human $human, Drink $drink)
$client = new Client($human);
$labeledDrink = $client->addDrink($drink);
$this->bar->removeFromStock($drink);
/* ... Prepare the drink ... */
SmsSender::send(
$human->getMobilePhoneNumber(),
sprintf('Drink %s ready',
$labeledDrink->getLabel())
);
Not all human have a phone number
//In Barman::prepare(Human $human, Drink $drink)
$client = new Client($human);
$labeledDrink = $client->addDrink($drink);
$this->bar->removeFromStock($drink);
/* ... Prepare the drink ... */
SmsSender::send(
$client->getMobilePhoneNumber(),
sprintf('Drink %s ready',
$labeledDrink->getLabel())
);
Our clients have a phone number!
//In Barman::prepare(Human $human, Drink $drink)
$client = new Client($human);
$labeledDrink = $client->addDrink($drink);
$this->bar->removeFromStock($drink);
/* ... Prepare the drink ... */
SmsSender::send(
$client->getMobilePhoneNumber(),
sprintf('Drink %s ready',
$labeledDrink->getLabel())
);
Human becomes useless here
//In Barman::prepare(Client $client, Drink $drink)
$labeledDrink = $client->addDrink($drink);
$this->bar->removeFromStock($drink);
/* ... Prepare the drink ... */
SmsSender::send(
$client->getMobilePhoneNumber(),
sprintf('Drink %s ready',
$labeledDrink->getLabel())
);
//In Barman::prepare(Client $client, Drink $drink)
$labeledDrink = $client->addDrink($drink);
$this->bar->removeFromStock($drink);
/* ... Prepare the drink ... */
SmsSender::send(
$client->getMobilePhoneNumber(),
sprintf('Drink %s ready',
$labeledDrink->getLabel())
);
Hello ugly hard coded content!
//In Barman::prepare(Client $client, Drink $drink)
$labeledDrink = $client->addDrink($drink);
$this->bar->removeFromStock($drink);
/* ... Prepare the drink ... */
SmsSender::send(
$client->getMobilePhoneNumber(),
sprintf('Drink %s ready',
$labeledDrink->getLabel())
);
Hello ugly hard coded content!
We actually send a message here...
//In Barman::prepare(Client $client, Drink $drink)
$labeledDrink = $client->addDrink($drink);
$this->bar->removeFromStock($drink);
/* ... Prepare the drink ... */
SmsSender::send(
new DrinkIsReadyMessage($labeledDrink, $client);
);
//FYI
class DrinkIsReadyMessage implements Message{
public function getPhoneNumber(){
return $this->client->getMobilePhoneNumber();
}
public function getMessage(){/* … */}
}
//In Barman::prepare(Client $client, Drink $drink)
$labeledDrink = $client->addDrink($drink);
$this->bar->removeFromStock($drink);
/* ... Prepare the drink ... */
SmsSender::send(
new DrinkIsReadyMessage($labeledDrink, $client);
);
//In Barman::prepare(Client $client, Drink $drink)
$labeledDrink = $client->addDrink($drink);
$this->bar->removeFromStock($drink);
/* ... Prepare the drink ... */
SmsSender::send(
new DrinkIsReadyMessage($labeledDrink, $client);
);
Static is not testable!
//In Barman::prepare(Client $client, Drink $drink)
$labeledDrink = $client->addDrink($drink);
$this->bar->removeFromStock($drink);
/* ... Prepare the drink ... */
SmsSender::send(
new DrinkIsReadyMessage($labeledDrink, $client);
);
/*
Should the bar manage the sms messaging system?
*/
Static is not testable!
//In Barman::prepare(Client $client, Drink $drink)
$labeledDrink = $client->addDrink($drink);
$this->bar->removeFromStock($drink);
/* ... Prepare the drink ... */
SmsSender::send(
new DrinkIsReadyMessage($labeledDrink, $client);
);
/*
Should the bar manage the sms messaging system?
Why not, but it already manages the drinks stock.
*/
Static is not testable!
//In Barman::prepare(Client $client, Drink $drink)
$labeledDrink = $client->addDrink($drink);
$this->bar->removeFromStock($drink);
/* ... Prepare the drink ... */
SmsSender::send(
new DrinkIsReadyMessage($labeledDrink, $client);
);
/*
Should the bar manage the sms messaging system?
Why not, but it already manages the drinks stock.
Maybe the bar should be a facade?
*/
Static is not testable!
//In Barman::prepare(Client $client, Drink $drink)
$labeledDrink = $client->addDrink($drink);
$this->bar->removeFromStock($drink);
/* ... Prepare the drink ... */
$this->bar->sendMessage(
new DrinkIsReadyMessage($labeledDrink, $client);
);
Simplest solution...
//In Barman::prepare(Client $client, Drink $drink)
$labeledDrink = $client->addDrink($drink);
$this->bar->removeFromStock($drink);
/* ... Prepare the drink ... */
$this->bar->sendMessage(
new DrinkIsReadyMessage($labeledDrink, $client);
);
Simplest solution...
Voilà! Let's not over engineering for the moment :)
interface Bar{
public function removeFromStock(Drink $drink);
public function hasInStock(Drink $drink);
public function sendMessage(Message $message);//New
}
interface Client{
public function addDrink(Drink $drink);
public function getMobilePhoneNumber();
}
interface labeledDrink extends Drink{ //New class
public function getLabel();
}
interface Message{ //New interface
public function getPhoneNumber();
public function getMessage();
}
interface DrinkIsReadyMessage extends Message{} //New
class BillPrinter{ //Let's print the bill!
public function __construct(string $type) {
$this->type = $type;
}
public function renderClientBill(Client $client){
if ($this->type === 'xml') {
$this->renderAsXml($client);
} elseif ($this->type === 'html') {
$this->renderAsHtml($client);
} else {
throw new Exception('Invalid input');
}
}
public function renderAsHtml(Client $client){}
public function renderAsXml(Client $client){}
}
class BillPrinter{
public function __construct(string $type) {
$this->type = $type;
}
public function renderClientBill(Client $client){
if ($this->type === 'xml') {
$this->renderAsXml($client);
} elseif ($this->type === 'html') {
$this->renderAsHtml($client);
} else {
throw new Exception('Invalid input');
}
}
public function renderAsHtml(Client $client){}
public function renderAsXml(Client $client){}
}
Generic exception
class BillPrinter{
public function __construct(string $type) {
$this->type = $type;
}
public function renderClientBill(Client $client){
if ($this->type === 'xml') {
$this->renderAsXml($client);
} elseif ($this->type === 'html') {
$this->renderAsHtml($client);
} else {
throw new InvalidTypeException();
}
}
public function renderAsHtml(Client $client){}
public function renderAsXml(Client $client){}
}
class BillPrinter{
public function __construct(string $type) {
$this->type = $type;
}
public function renderClientBill(Client $client){
if ($this->type === 'xml') {
$this->renderAsXml($client);
} elseif ($this->type === 'html') {
$this->renderAsHtml($client);
} else {
throw new InvalidTypeException();
}
}
public function renderAsHtml(Client $client){}
public function renderAsXml(Client $client){}
}
Render an xml
Render an html
2 responsabilities
class BillPrinter{
public function __construct(string $type) {
$this->type = $type;
}
public function renderClientBill(Client $client){
if ($this->type === 'xml') {
(new XmlBillPrinter())->render($client);
} elseif ($this->type === 'html') {
$this->renderAsHtml($client);
} else {
throw new InvalidTypeException();
}
}
public function renderAsHtml(Client $client){}
}
Render an html
Delegate xml
class BillPrinter{
public function __construct(string $type) {
$this->type = $type;
}
public function renderClientBill(Client $client){
if ($this->type === 'xml') {
(new XmlBillPrinter())->render($client);
} elseif ($this->type === 'html') {
(new HtmlBillPrinter())->render($client);
} else {
throw new InvalidTypeException();
}
}
}
Delegate html
Delegate xml
class BillPrinter{
public function __construct(string $type) {
$this->type = $type;
}
public function renderClientBill(Client $client){
if ($this->type === 'xml') {
(new XmlBillPrinter())->render($client);
} elseif ($this->type === 'html') {
(new HtmlBillPrinter())->render($client);
} else {
throw new InvalidTypeException();
}
}
}
class BillPrinter{
public function __construct(string $type) {
$this->type = $type;
}
public function renderClientBill(Client $client){
if ($this->type === 'xml') {
(new XmlBillPrinter())->render($client);
} elseif ($this->type === 'html') {
(new HtmlBillPrinter())->render($client);
} else {
throw new InvalidTypeException();
}
}
}
interface BillPrinterInterface{
public function render(Client $client);
}
NB: The same interface is used
class BillPrinter{
public function __construct(string $type) {
$this->type = $type;
}
public function renderClientBill(Client $client){
if ($this->type === 'xml') {
(new XmlBillPrinter())->render($client);
} elseif ($this->type === 'html') {
(new HtmlBillPrinter())->render($client);
} else {
throw new InvalidTypeException();
}
}
} It’s not my job to find what instantiate!
I just want to render a bill!
class BillPrinter{
public function __construct(string $type) {
$this->type = $type;
}
public function renderClientBill(Client $client){
if ($this->type === 'xml') {
(new XmlBillPrinter())->render($client);
} elseif ($this->type === 'html') {
(new HtmlBillPrinter())->render($client);
} else {
throw new InvalidTypeException();
}
}
} It’s not my job to find what instantiate!
I just want to render a bill!
Delegate to a private method first...
class BillPrinter{
public function __construct(string $type) {
$this->type = $type;
}
public function renderClientBill(Client $client){
$this->getPrinterInstance()->render($client);
}
public function getPrinterInstance(){
if ($this->type === 'xml') {
return new XmlBillPrinter();
} elseif ($this->type === 'html') {
return new HtmlBillPrinter();
} else {
throw new InvalidTypeException();
}
}
}
class BillPrinter{
public function __construct(string $type) {
$this->type = $type;
}
public function renderClientBill(Client $client){
$this->getPrinterInstance()->render($client);
}
public function getPrinterInstance(){
if ($this->type === 'xml') {
return new XmlBillPrinter();
} elseif ($this->type === 'html') {
return new HtmlBillPrinter();
} else {
throw new InvalidTypeException();
}
}
}
It’s really not my job to
find what instantiate!
class BillPrinter{
public function __construct(string $type) {
$this->type = $type;
}
public function renderClientBill(Client $client){
$this->getPrinterInstance()->render($client);
}
public function getPrinterInstance(){
if ($this->type === 'xml') {
return new XmlBillPrinter();
} elseif ($this->type === 'html') {
return new HtmlBillPrinter();
} else {
throw new InvalidTypeException();
}
}
}
It’s really not my job to
find what instantiate!
It’s a factory job!
class BillPrinter{
public function __construct(string $type) {
$this->type = $type;
}
public function renderClientBill(Client $client){
BillPrinterFactory::create($this->type)
->render($client);
}
}
class BillPrinterFactory{
public static function create(string $type){
if ($type === 'xml') {
return new XmlBillPrinter();
} else //...
}
}
class BillPrinter{
public function __construct(string $type) {
$this->type = $type;
}
public function renderClientBill(Client $client){
BillPrinterFactory::create($this->type)
->render($client);
}
}
class BillPrinterFactory{
public static function create(string $type){
if ($type === 'xml') {
return new XmlBillPrinter();
} else //...
}
}
What is the purpose of this class again?
class BillPrinter{
public function __construct(string $type) {
$this->type = $type;
}
public function renderClientBill(Client $client){
BillPrinterFactory::create($this->type)
->render($client);
}
}
class BillPrinterFactory{
public static function create(string $type){
if ($type === 'xml') {
return new XmlBillPrinter();
} else //...
}
}
What is the purpose of this class again?
class BillPrinter{
public function __construct(string $type) {
$this->type = $type;
}
public function renderClientBill(Client $client){
BillPrinterFactory::create($this->type)
->render($client);
}
}
class BillPrinterFactory{
public static function create(string $type){
if ($type === 'xml') {
return new XmlBillPrinter();
} else //...
}
}
Remove the unnecessary class :)
//What about the render of the bill in HTML?
class HtmlBillPrinter
implements BillPrinterInterface{
public function render(Client $client){
foreach($client->getDrinks() as $drink){
$render .= '<div class="drink-line"';
if ($drink->containsAlcohol()) {
$render .= ' data-alcohol="1"';
}
$render.= '>'. $drink->getPrice() .'</div>';
}
return $render;
}
}
class HtmlBillPrinter
implements BillPrinterInterface{
public function render(Client $client){
foreach($client->getDrinks() as $drink){
$render .= '<div class="drink-line"';
if ($drink->containsAlcohol()) {
$render .= ' data-alcohol="1"';
}
$render.= '>'. $drink->getPrice() .'</div>';
}
return $render;
}
}
It renders lines
class HtmlBillPrinter
implements BillPrinterInterface{
public function render(Client $client){
foreach($client->getDrinks() as $drink){
$render .= '<div class="drink-line"';
if ($drink->containsAlcohol()) {
$render .= ' data-alcohol="1"';
}
$render.= '>'. $drink->getPrice() .'</div>';
}
return $render;
}
}
It manages HTML and its attributes
It renders lines
class HtmlBillPrinter
implements BillPrinterInterface{
public function render(Client $client){
foreach($client->getDrinks() as $drink){
$render .= '<div class="drink-line"';
if ($drink->containsAlcohol()) {
$render .= ' data-alcohol="1"';
}
$render.= '>'. $drink->getPrice() .'</div>';
}
return $render;
}
}
It manages HTML and its attributes
And who’s gonna escape string if necessary?
It renders lines
class HtmlBillPrinter
implements BillPrinterInterface{
public function render(Client $client){
foreach($client->getDrinks() as $drink){
$render .= '<div class="drink-line"';
if ($drink->containsAlcohol()) {
$render .= ' data-alcohol="1"';
}
$render.= '>'. $drink->getPrice() .'</div>';
}
return $render;
}
}
It manages HTML and its attributes
And who’s gonna escape string if necessary?
It renders lines
It’s bibi!
class HtmlBillPrinter
implements BillPrinterInterface{
public function render(Client $client){
foreach($client->getDrinks() as $drink){
return '<div class="drink-line"';
$div = new HtmlTag('div');
$div->addAttribute('class', 'drink-line');
if ($drink->containsAlcohol()) {
$render .= ' data-alcohol="1"';
}
$render.= '>'. $drink->getPrice() .'</div>';
}
return $render;
}
}
class HtmlBillPrinter
implements BillPrinterInterface{
public function render(Client $client){
foreach($client->getDrinks() as $drink){
$div = new HtmlTag('div');
$div->addAttribute('class', 'drink-line');
if ($drink->containsAlcohol()) {
$render .= ' data-alcohol="1"';
}
$render.= '>'. $drink->getPrice() .'</div>';
}
return $render;
}
}
class HtmlBillPrinter
implements BillPrinterInterface{
public function render(Client $client){
foreach($client->getDrinks() as $drink){
$div = new HtmlTag('div');
$div->addAttribute('class', 'drink-line');
if ($drink->containsAlcohol()) {
return ' data-alcohol="1"';
$div->addAttribute('data-alcohol', '1');
}
$render.= '>'. $drink->getPrice() .'</div>';
}
return $render;
}
}
class HtmlBillPrinter
implements BillPrinterInterface{
public function render(Client $client){
foreach($client->getDrinks() as $drink){
$div = new HtmlTag('div');
$div->addAttribute('class', 'drink-line');
if ($drink->containsAlcohol()) {
$div->addAttribute('data-alcohol', '1');
}
$render.= '>'. $drink->getPrice() .'</div>';
}
return $render;
}
}
class HtmlBillPrinter
implements BillPrinterInterface{
public function render(Client $client){
foreach($client->getDrinks() as $drink){
$div = new HtmlTag('div');
$div->addAttribute('class', 'drink-line');
if ($drink->containsAlcohol()) {
$div->addAttribute('data-alcohol', '1');
}
$render.= '>'. $drink->getPrice() .'</div>';
$div->setHtml($drink->getPrice());
return $div;
}
}
}
class HtmlBillPrinter
implements BillPrinterInterface{
public function render(Client $client){
foreach($client->getDrinks() as $drink){
$div = new HtmlTag('div');
$div->addAttribute('class', 'drink-line');
if ($drink->containsAlcohol()) {
$div->addAttribute('data-alcohol', '1');
}
$div->setHtml($drink->getPrice());
return $div;
}
}
}
It’s all about making
Your code comprehensible
WRAPPING UP
Books
Thank you ☺
Questions?
INFO@BLUBIRD.EU
+32 2 880 05 41

Why your code suxx

  • 1.
    Why Your CodeSuxx And why you should care about it ?
  • 2.
    www.blubird.eu Started mid 2013 Audit& consultancy Project staffing Project management ABOUT BLUBIRD Web & Mobile apps Virtualization and cloud Performance & optimization Security SMAC the picture Security, Mobility, Apps & Cloud
  • 3.
    Nicolas De Boose ➢Freelancer ➢ PHP, Symfony2, (Node) Js, Css ➢ 10 yrs pro ➢ A wonderful fiancee <3 ➢ @NicoDeBoose ➢ Blog: mechantblog.com ABOUT ME
  • 4.
    WHY THIS SUBJECT? 1.Laravel vs Symfony2 vs Zend Framework2 ? Google it! 2. “Clean code” is a subject I like – Pull request – Gave lots of reviews – Read/watch Clean coders (book & website) 3. Every year, new coders must learn best practices, solid principles, patterns, ... And it’s a good booster shot
  • 5.
    HOW ? 1. Presentationof code that smells 2. Your opinion 3. Refactoring
  • 6.
    //Let's go tothe bar interface Human{ public function getAge(); public function getFullName(); } interface Drink{ public function getPrice(); } interface Bar{ public function removeFromStock(Drink $drink); public function hasInStock(Drink $drink); }
  • 7.
    class BarMan{ private $bar; publicfunction command(Human $human, Drink $drink){ if ($this->bar->hasInStock($drink) == true) { if ($drink->doesNotContainAlcohol()) { $this->prepare($human, $drink); } else { if ($human->getAge() >= 18){ $this->prepare($human, $drink); } else { $this->errorMessage = 'Too young :('; return false; } } } else { $this->errorMessage = 'Not in stock :('; } return true; } }
  • 8.
    class BarMan{ private $bar; publicfunction command(Human $human, Drink $drink){ if ($this->bar->hasInStock($drink) == true) { if ($drink->doesNotContainAlcohol()) { $this->prepare($human, $drink); } else { if ($human->getAge() >= 18){ $this->prepare($human, $drink); } else { $this->errorMessage = 'Too young :('; return false; } } } else { $this->errorMessage = 'Not in stock :('; } return true; } } Focus on this code
  • 9.
    //In Barman::command(Human $human,Drink $drink) if ($this->bar->hasInStock($drink) == true) { if ($drink->doesNotContainAlcohol()) { $this->prepare($human, $drink); } else { if ($human->getAge() >= 18){ $this->prepare($human, $drink); } else { $this->errorMessage = 'Too young :('; return false; } } } else { $this->errorMessage = 'Not in stock :('; return false; } return true;
  • 10.
    //In Barman::command(Human $human,Drink $drink) if ($this->bar->hasInStock($drink) == true) { if ($drink->doesNotContainAlcohol()) { $this->prepare($human, $drink); } else { if ($human->getAge() >= 18){ $this->prepare($human, $drink); } else { $this->errorMessage = 'Too young :('; return false; } } } else { $this->errorMessage = 'Not in stock :('; return false; } return true; == true) == true) ...
  • 11.
    //In Barman::command(Human $human,Drink $drink) if ($this->bar->hasInStock($drink)) { if ($drink->doesNotContainAlcohol()) { $this->prepare($human, $drink); } else { if ($human->getAge() >= 18){ $this->prepare($human, $drink); } else { $this->errorMessage = 'Too young :('; return false; } } } else { $this->errorMessage = 'Not in stock :('; return false; } return true;
  • 12.
    //In Barman::command(Human $human,Drink $drink) if ($this->bar->hasInStock($drink)) { if ($drink->doesNotContainAlcohol()) { $this->prepare($human, $drink); } else { if ($human->getAge() >= 18){ $this->prepare($human, $drink); } else { $this->errorMessage = 'Too young :('; return false; } } } else { $this->errorMessage = 'Not in stock :('; return false; } return true; Nested if’s
  • 13.
    //In Barman::command(Human $human,Drink $drink) if ($this->bar->hasInStock($drink)) { if ($drink->doesNotContainAlcohol()) { $this->prepare($human, $drink); } else { if ($human->getAge() >= 18){ $this->prepare($human, $drink); } else { $this->errorMessage = 'Too young :('; return false; } } } else { $this->errorMessage = 'Not in stock :('; return false; } return true;
  • 14.
    //In Barman::command(Human $human,Drink $drink) if (!$this->bar->hasInStock($drink)) { $this->errorMessage = 'Not in stock :('; return false; } if ($drink->doesNotContainAlcohol()) { $this->prepare($human, $drink); } else { if ($human->getAge() >= 18){ $this->prepare($human, $drink); } else { $this->errorMessage = 'Too young :('; return false; } } return true;
  • 15.
    //In Barman::command(Human $human,Drink $drink) if (!$this->bar->hasInStock($drink)) { $this->errorMessage = 'Not in stock :('; return false; } if ($drink->doesNotContainAlcohol()) { $this->prepare($human, $drink); } else { if ($human->getAge() >= 18){ $this->prepare($human, $drink); } else { $this->errorMessage = 'Too young :('; return false; } } return true; call to “prepare()” here And here + nested if
  • 16.
    //In Barman::command(Human $human,Drink $drink) if (!$this->bar->hasInStock($drink)) { $this->errorMessage = 'Not in stock :('; return false; } if (!$drink->doesNotContainAlcohol() && $human->getAge() < 18 ) { $this->errorMessage = 'Too young :('; return false; } $this->prepare($human, $drink); return true;
  • 17.
    //In Barman::command(Human $human,Drink $drink) if (!$this->bar->hasInStock($drink)) { $this->errorMessage = 'Not in stock :('; return false; } if (!$drink->doesNotContainAlcohol() && $human->getAge() < 18 ) { $this->errorMessage = 'Too young :('; return false; } $this->prepare($human, $drink); return true; NOT doesNot… My head hurts!
  • 18.
    //In Barman::command(Human $human,Drink $drink) if (!$this->bar->hasInStock($drink)) { $this->errorMessage = 'Not in stock :('; return false; } if ($drink->containsAlcohol() && $human->getAge() < 18 ) { $this->errorMessage = 'Too young :('; return false; } $this->prepare($human, $drink); return true;
  • 19.
    //In Barman::command(Human $human,Drink $drink) if (!$this->bar->hasInStock($drink)) { $this->errorMessage = 'Not in stock :('; return false; } if ($drink->containsAlcohol() && $human->getAge() < 18 ) { $this->errorMessage = 'Too young :('; return false; } $this->prepare($human, $drink); return true; can we be more explicit?
  • 20.
    //In Barman::command(Human $human,Drink $drink) if (!$this->bar->hasInStock($drink)) { $this->errorMessage = 'Not in stock :('; return false; } if ($drink->containsAlcohol() && $human->isYoungerThan(18) ) { $this->errorMessage = 'Too young :('; return false; } $this->prepare($human, $drink); return true;
  • 21.
    //In Barman::command(Human $human,Drink $drink) if (!$this->bar->hasInStock($drink)) { $this->errorMessage = 'Not in stock :('; return false; } if ($drink->containsAlcohol() && $human->isYoungerThan(18) ) { $this->errorMessage = 'Too young :('; return false; } $this->prepare($human, $drink); return true; It’s not my job to keep the error message It’s not my job to keep the error message
  • 22.
    //In Barman::command(Human $human,Drink $drink) if (!$this->bar->hasInStock($drink)) { throw new DrinkNotInStockException($drink); } if ($drink->containsAlcohol() && $human->isYoungerThan(18) ) { return new CommandResult(false,'Too young :('); } $this->prepare($human, $drink); return true; Solution 1: Throw an exception Solution 2: Return a “Result” object Solution 1: Return nothing Solution 2: Return a “Result” object
  • 23.
    //In Barman::command(Human $human,Drink $drink) if (!$this->bar->hasInStock($drink)) { throw new DrinkNotInStockException($drink); } if ($drink->containsAlcohol() && $human->isYoungerThan(18) ) { throw new TooYoungToDrinkAlcoholException(); } $this->prepare($human, $drink);
  • 24.
    //In Barman::command(Human $human,Drink $drink) if (!$this->bar->hasInStock($drink)) { throw new DrinkNotInStockException($drink); } if ($drink->containsAlcohol() && $human->isYoungerThan(18) ) { throw new TooYoungToDrinkAlcoholException(); } $this->prepare($human, $drink); What is “18”? It’s a magic number!
  • 25.
    //In Barman::command(Human $human,Drink $drink) if (!$this->bar->hasInStock($drink)) { throw new DrinkNotInStockException($drink); } if ($drink->containsAlcohol() && $human->isYoungerThan (self::MINIMUM_AGE_FOR_ALCOHOL) ) { throw new TooYoungToDrinkAlcoholException(); } $this->prepare($human, $drink); Better but not the best place to store the constant
  • 26.
    //In Barman::command(Human $human,Drink $drink) if (!$this->bar->hasInStock($drink)) { throw new DrinkNotInStockException($drink); } if ($drink->containsAlcohol() && $human->isYoungerThan (BarLegislation::MINIMUM_AGE_FOR_ALCOHOL) ) { throw new TooYoungToDrinkAlcoholException(); } $this->prepare($human, $drink); There, it’s good enough for the moment
  • 27.
    //In Barman::command(Human $human,Drink $drink) if (!$this->bar->hasInStock($drink)) throw new DrinkNotInStockException($drink); if ($drink->containsAlcohol() && $human->isYoungerThan (BarLegislation::MINIMUM_AGE_FOR_ALCOHOL) ) throw new TooYoungToDrinkAlcoholException(); $this->prepare($human, $drink);
  • 28.
    interface Human{ // publicfunction getAge(); => deleted public function isYoungerThan($age); //New public function getFullName(); } interface Drink{ public function getPrice(); public function containsAlcohol(); //Rename } interface Bar{ public function removeFromStock(Drink $drink); public function hasInStock(Drink $drink); }
  • 29.
    //In Barman::prepare(Human $human,Drink $drink) $tmp = explode(" ", $human->getFullName()); $ac = ""; foreach ($tmp as $w) { $ac .= $w[0]; } $human->addToBill($drink); $this->bar->removeFromStock($drink); /* ... Prepare the drink ... */ $drink->setAcronym($ac); SmsSender::send( $human->getMobilePhoneNumber(), sprintf('Drink %s ready', $ac) );
  • 30.
    //In Barman::prepare(Human $human,Drink $drink) $tmp = explode(" ", $human->getFullName()); $ac = ""; foreach ($tmp as $w) { $ac .= $w[0]; } $human->addToBill($drink); $this->bar->removeFromStock($drink); /* ... Prepare the drink ... */ $drink->setAcronym($ac); SmsSender::send( $human->getMobilePhoneNumber(), sprintf('Drink %s ready', $ac) ); What does “tmp” mean? What does “ac” mean?
  • 31.
    //In Barman::prepare(Human $human,Drink $drink) $nameParts = explode(" ",$human->getFullName()); $acronym = ""; foreach ($nameParts as $w) { $acronym .= $w[0]; } $human->addToBill($drink); $this->bar->removeFromStock($drink); /* ... Prepare the drink ... */ $drink->setAcronym($acronym); SmsSender::send( $human->getMobilePhoneNumber(), sprintf('Drink %s ready', $acronym) );
  • 32.
    //In Barman::prepare(Human $human,Drink $drink) $nameParts = explode(" ",$human->getFullName()); $acronym = ""; foreach ($nameParts as $w) { $acronym .= $w[0]; } $human->addToBill($drink); $this->bar->removeFromStock($drink); /* ... Prepare the drink ... */ $drink->setAcronym($acronym); SmsSender::send( $human->getMobilePhoneNumber(), sprintf('Drink %s ready', $acronym) ); Can we compute the acronym elsewhere?
  • 33.
    //In Barman::prepare(Human $human,Drink $drink) $acronym = $this->getHumanAcronym($human); $human->addToBill($drink); $this->bar->removeFromStock($drink); /* ... Prepare the drink ... */ $drink->setAcronym($acronym); SmsSender::send( $human->getMobilePhoneNumber(), sprintf('Drink %s ready', $acronym) ); That’s better, but ...
  • 34.
    //In Barman::prepare(Human $human,Drink $drink) $acronym = $human->getAcronym(); $human->addToBill($drink); $this->bar->removeFromStock($drink); /* ... Prepare the drink ... */ $drink->setAcronym($acronym); SmsSender::send( $human->getMobilePhoneNumber(), sprintf('Drink %s ready', $acronym) );
  • 35.
    //In Barman::prepare(Human $human,Drink $drink) $acronym = $human->getAcronym(); $human->addToBill($drink); $this->bar->removeFromStock($drink); /* ... Prepare the drink ... */ $drink->setAcronym($acronym); SmsSender::send( $human->getMobilePhoneNumber(), sprintf('Drink %s ready', $acronym) ); Now let’s remove $acronym var
  • 36.
    //In Barman::prepare(Human $human,Drink $drink) $human->addToBill($drink); $this->bar->removeFromStock($drink); /* ... Prepare the drink ... */ $drink->setAcronym($human->getAcronym()); SmsSender::send( $human->getMobilePhoneNumber(), sprintf('Drink %s ready',$human->getAcronym()) );
  • 37.
    //In Barman::prepare(Human $human,Drink $drink) $human->addToBill($drink); $this->bar->removeFromStock($drink); /* ... Prepare the drink ... */ $drink->setAcronym($human->getAcronym()); SmsSender::send( $human->getMobilePhoneNumber(), sprintf('Drink %s ready',$human->getAcronym()) ); A bill for a “simple” human?
  • 38.
    //In Barman::prepare(Human $human,Drink $drink) $client = new Client($human); $client->addDrink($drink); $this->bar->removeFromStock($drink); /* ... Prepare the drink ... */ $drink->setAcronym($human->getAcronym()); SmsSender::send( $human->getMobilePhoneNumber(), sprintf('Drink %s ready',$human->getAcronym()) ); Create a “Client” class: He knows what he’s drinking
  • 39.
    //In Barman::prepare(Human $human,Drink $drink) $client = new Client($human); $client->addDrink($drink); $this->bar->removeFromStock($drink); /* ... Prepare the drink ... */ $drink->setAcronym($human->getAcronym()); SmsSender::send( $human->getMobilePhoneNumber(), sprintf('Drink %s ready',$human->getAcronym()) ); A drink should not be more than a drink.
  • 40.
    //In Barman::prepare(Human $human,Drink $drink) $client = new Client($human); $client->addDrink($drink); $this->bar->removeFromStock($drink); /* ... Prepare the drink ... */ $labeledDrink = new LabeledDrink( $drink, $human->getAcronym()); SmsSender::send( $human->getMobilePhoneNumber(), sprintf('Drink %s ready', $labeledDrink->getLabel()) ); Create a class that symbolizes the drink with an acronym Take the label from the new object
  • 41.
    //In Barman::prepare(Human $human,Drink $drink) $client = new Client($human); $client->addDrink($drink); $this->bar->removeFromStock($drink); /* ... Prepare the drink ... */ $labeledDrink = new LabeledDrink( $drink, $human->getAcronym()); SmsSender::send( $human->getMobilePhoneNumber(), sprintf('Drink %s ready', $labeledDrink->getLabel()) ); Is there a better place to instantiate the object?
  • 42.
    //In Barman::prepare(Human $human,Drink $drink) $client = new Client($human); $client->addDrink($drink); $this->bar->removeFromStock($drink); /* ... Prepare the drink ... */ $labeledDrink = new LabeledDrink( $drink, $human->getAcronym()); SmsSender::send( $human->getMobilePhoneNumber(), sprintf('Drink %s ready', $labeledDrink->getLabel()) ); Is there a better place to instantiate the object? Maybe here?
  • 43.
    //In Barman::prepare(Human $human,Drink $drink) $client = new Client($human); $labeledDrink = $client->addDrink($drink); $this->bar->removeFromStock($drink); /* ... Prepare the drink ... */ SmsSender::send( $human->getMobilePhoneNumber(), sprintf('Drink %s ready', $labeledDrink->getLabel()) );
  • 44.
    //In Barman::prepare(Human $human,Drink $drink) $client = new Client($human); $labeledDrink = $client->addDrink($drink); $this->bar->removeFromStock($drink); /* ... Prepare the drink ... */ SmsSender::send( $human->getMobilePhoneNumber(), sprintf('Drink %s ready', $labeledDrink->getLabel()) ); Not all human have a phone number
  • 45.
    //In Barman::prepare(Human $human,Drink $drink) $client = new Client($human); $labeledDrink = $client->addDrink($drink); $this->bar->removeFromStock($drink); /* ... Prepare the drink ... */ SmsSender::send( $client->getMobilePhoneNumber(), sprintf('Drink %s ready', $labeledDrink->getLabel()) ); Our clients have a phone number!
  • 46.
    //In Barman::prepare(Human $human,Drink $drink) $client = new Client($human); $labeledDrink = $client->addDrink($drink); $this->bar->removeFromStock($drink); /* ... Prepare the drink ... */ SmsSender::send( $client->getMobilePhoneNumber(), sprintf('Drink %s ready', $labeledDrink->getLabel()) ); Human becomes useless here
  • 47.
    //In Barman::prepare(Client $client,Drink $drink) $labeledDrink = $client->addDrink($drink); $this->bar->removeFromStock($drink); /* ... Prepare the drink ... */ SmsSender::send( $client->getMobilePhoneNumber(), sprintf('Drink %s ready', $labeledDrink->getLabel()) );
  • 48.
    //In Barman::prepare(Client $client,Drink $drink) $labeledDrink = $client->addDrink($drink); $this->bar->removeFromStock($drink); /* ... Prepare the drink ... */ SmsSender::send( $client->getMobilePhoneNumber(), sprintf('Drink %s ready', $labeledDrink->getLabel()) ); Hello ugly hard coded content!
  • 49.
    //In Barman::prepare(Client $client,Drink $drink) $labeledDrink = $client->addDrink($drink); $this->bar->removeFromStock($drink); /* ... Prepare the drink ... */ SmsSender::send( $client->getMobilePhoneNumber(), sprintf('Drink %s ready', $labeledDrink->getLabel()) ); Hello ugly hard coded content! We actually send a message here...
  • 50.
    //In Barman::prepare(Client $client,Drink $drink) $labeledDrink = $client->addDrink($drink); $this->bar->removeFromStock($drink); /* ... Prepare the drink ... */ SmsSender::send( new DrinkIsReadyMessage($labeledDrink, $client); ); //FYI class DrinkIsReadyMessage implements Message{ public function getPhoneNumber(){ return $this->client->getMobilePhoneNumber(); } public function getMessage(){/* … */} }
  • 51.
    //In Barman::prepare(Client $client,Drink $drink) $labeledDrink = $client->addDrink($drink); $this->bar->removeFromStock($drink); /* ... Prepare the drink ... */ SmsSender::send( new DrinkIsReadyMessage($labeledDrink, $client); );
  • 52.
    //In Barman::prepare(Client $client,Drink $drink) $labeledDrink = $client->addDrink($drink); $this->bar->removeFromStock($drink); /* ... Prepare the drink ... */ SmsSender::send( new DrinkIsReadyMessage($labeledDrink, $client); ); Static is not testable!
  • 53.
    //In Barman::prepare(Client $client,Drink $drink) $labeledDrink = $client->addDrink($drink); $this->bar->removeFromStock($drink); /* ... Prepare the drink ... */ SmsSender::send( new DrinkIsReadyMessage($labeledDrink, $client); ); /* Should the bar manage the sms messaging system? */ Static is not testable!
  • 54.
    //In Barman::prepare(Client $client,Drink $drink) $labeledDrink = $client->addDrink($drink); $this->bar->removeFromStock($drink); /* ... Prepare the drink ... */ SmsSender::send( new DrinkIsReadyMessage($labeledDrink, $client); ); /* Should the bar manage the sms messaging system? Why not, but it already manages the drinks stock. */ Static is not testable!
  • 55.
    //In Barman::prepare(Client $client,Drink $drink) $labeledDrink = $client->addDrink($drink); $this->bar->removeFromStock($drink); /* ... Prepare the drink ... */ SmsSender::send( new DrinkIsReadyMessage($labeledDrink, $client); ); /* Should the bar manage the sms messaging system? Why not, but it already manages the drinks stock. Maybe the bar should be a facade? */ Static is not testable!
  • 56.
    //In Barman::prepare(Client $client,Drink $drink) $labeledDrink = $client->addDrink($drink); $this->bar->removeFromStock($drink); /* ... Prepare the drink ... */ $this->bar->sendMessage( new DrinkIsReadyMessage($labeledDrink, $client); ); Simplest solution...
  • 57.
    //In Barman::prepare(Client $client,Drink $drink) $labeledDrink = $client->addDrink($drink); $this->bar->removeFromStock($drink); /* ... Prepare the drink ... */ $this->bar->sendMessage( new DrinkIsReadyMessage($labeledDrink, $client); ); Simplest solution... Voilà! Let's not over engineering for the moment :)
  • 58.
    interface Bar{ public functionremoveFromStock(Drink $drink); public function hasInStock(Drink $drink); public function sendMessage(Message $message);//New } interface Client{ public function addDrink(Drink $drink); public function getMobilePhoneNumber(); } interface labeledDrink extends Drink{ //New class public function getLabel(); } interface Message{ //New interface public function getPhoneNumber(); public function getMessage(); } interface DrinkIsReadyMessage extends Message{} //New
  • 59.
    class BillPrinter{ //Let'sprint the bill! public function __construct(string $type) { $this->type = $type; } public function renderClientBill(Client $client){ if ($this->type === 'xml') { $this->renderAsXml($client); } elseif ($this->type === 'html') { $this->renderAsHtml($client); } else { throw new Exception('Invalid input'); } } public function renderAsHtml(Client $client){} public function renderAsXml(Client $client){} }
  • 60.
    class BillPrinter{ public function__construct(string $type) { $this->type = $type; } public function renderClientBill(Client $client){ if ($this->type === 'xml') { $this->renderAsXml($client); } elseif ($this->type === 'html') { $this->renderAsHtml($client); } else { throw new Exception('Invalid input'); } } public function renderAsHtml(Client $client){} public function renderAsXml(Client $client){} } Generic exception
  • 61.
    class BillPrinter{ public function__construct(string $type) { $this->type = $type; } public function renderClientBill(Client $client){ if ($this->type === 'xml') { $this->renderAsXml($client); } elseif ($this->type === 'html') { $this->renderAsHtml($client); } else { throw new InvalidTypeException(); } } public function renderAsHtml(Client $client){} public function renderAsXml(Client $client){} }
  • 62.
    class BillPrinter{ public function__construct(string $type) { $this->type = $type; } public function renderClientBill(Client $client){ if ($this->type === 'xml') { $this->renderAsXml($client); } elseif ($this->type === 'html') { $this->renderAsHtml($client); } else { throw new InvalidTypeException(); } } public function renderAsHtml(Client $client){} public function renderAsXml(Client $client){} } Render an xml Render an html 2 responsabilities
  • 63.
    class BillPrinter{ public function__construct(string $type) { $this->type = $type; } public function renderClientBill(Client $client){ if ($this->type === 'xml') { (new XmlBillPrinter())->render($client); } elseif ($this->type === 'html') { $this->renderAsHtml($client); } else { throw new InvalidTypeException(); } } public function renderAsHtml(Client $client){} } Render an html Delegate xml
  • 64.
    class BillPrinter{ public function__construct(string $type) { $this->type = $type; } public function renderClientBill(Client $client){ if ($this->type === 'xml') { (new XmlBillPrinter())->render($client); } elseif ($this->type === 'html') { (new HtmlBillPrinter())->render($client); } else { throw new InvalidTypeException(); } } } Delegate html Delegate xml
  • 65.
    class BillPrinter{ public function__construct(string $type) { $this->type = $type; } public function renderClientBill(Client $client){ if ($this->type === 'xml') { (new XmlBillPrinter())->render($client); } elseif ($this->type === 'html') { (new HtmlBillPrinter())->render($client); } else { throw new InvalidTypeException(); } } }
  • 66.
    class BillPrinter{ public function__construct(string $type) { $this->type = $type; } public function renderClientBill(Client $client){ if ($this->type === 'xml') { (new XmlBillPrinter())->render($client); } elseif ($this->type === 'html') { (new HtmlBillPrinter())->render($client); } else { throw new InvalidTypeException(); } } } interface BillPrinterInterface{ public function render(Client $client); } NB: The same interface is used
  • 67.
    class BillPrinter{ public function__construct(string $type) { $this->type = $type; } public function renderClientBill(Client $client){ if ($this->type === 'xml') { (new XmlBillPrinter())->render($client); } elseif ($this->type === 'html') { (new HtmlBillPrinter())->render($client); } else { throw new InvalidTypeException(); } } } It’s not my job to find what instantiate! I just want to render a bill!
  • 68.
    class BillPrinter{ public function__construct(string $type) { $this->type = $type; } public function renderClientBill(Client $client){ if ($this->type === 'xml') { (new XmlBillPrinter())->render($client); } elseif ($this->type === 'html') { (new HtmlBillPrinter())->render($client); } else { throw new InvalidTypeException(); } } } It’s not my job to find what instantiate! I just want to render a bill! Delegate to a private method first...
  • 69.
    class BillPrinter{ public function__construct(string $type) { $this->type = $type; } public function renderClientBill(Client $client){ $this->getPrinterInstance()->render($client); } public function getPrinterInstance(){ if ($this->type === 'xml') { return new XmlBillPrinter(); } elseif ($this->type === 'html') { return new HtmlBillPrinter(); } else { throw new InvalidTypeException(); } } }
  • 70.
    class BillPrinter{ public function__construct(string $type) { $this->type = $type; } public function renderClientBill(Client $client){ $this->getPrinterInstance()->render($client); } public function getPrinterInstance(){ if ($this->type === 'xml') { return new XmlBillPrinter(); } elseif ($this->type === 'html') { return new HtmlBillPrinter(); } else { throw new InvalidTypeException(); } } } It’s really not my job to find what instantiate!
  • 71.
    class BillPrinter{ public function__construct(string $type) { $this->type = $type; } public function renderClientBill(Client $client){ $this->getPrinterInstance()->render($client); } public function getPrinterInstance(){ if ($this->type === 'xml') { return new XmlBillPrinter(); } elseif ($this->type === 'html') { return new HtmlBillPrinter(); } else { throw new InvalidTypeException(); } } } It’s really not my job to find what instantiate! It’s a factory job!
  • 72.
    class BillPrinter{ public function__construct(string $type) { $this->type = $type; } public function renderClientBill(Client $client){ BillPrinterFactory::create($this->type) ->render($client); } } class BillPrinterFactory{ public static function create(string $type){ if ($type === 'xml') { return new XmlBillPrinter(); } else //... } }
  • 73.
    class BillPrinter{ public function__construct(string $type) { $this->type = $type; } public function renderClientBill(Client $client){ BillPrinterFactory::create($this->type) ->render($client); } } class BillPrinterFactory{ public static function create(string $type){ if ($type === 'xml') { return new XmlBillPrinter(); } else //... } } What is the purpose of this class again?
  • 74.
    class BillPrinter{ public function__construct(string $type) { $this->type = $type; } public function renderClientBill(Client $client){ BillPrinterFactory::create($this->type) ->render($client); } } class BillPrinterFactory{ public static function create(string $type){ if ($type === 'xml') { return new XmlBillPrinter(); } else //... } } What is the purpose of this class again?
  • 75.
    class BillPrinter{ public function__construct(string $type) { $this->type = $type; } public function renderClientBill(Client $client){ BillPrinterFactory::create($this->type) ->render($client); } } class BillPrinterFactory{ public static function create(string $type){ if ($type === 'xml') { return new XmlBillPrinter(); } else //... } } Remove the unnecessary class :)
  • 76.
    //What about therender of the bill in HTML? class HtmlBillPrinter implements BillPrinterInterface{ public function render(Client $client){ foreach($client->getDrinks() as $drink){ $render .= '<div class="drink-line"'; if ($drink->containsAlcohol()) { $render .= ' data-alcohol="1"'; } $render.= '>'. $drink->getPrice() .'</div>'; } return $render; } }
  • 77.
    class HtmlBillPrinter implements BillPrinterInterface{ publicfunction render(Client $client){ foreach($client->getDrinks() as $drink){ $render .= '<div class="drink-line"'; if ($drink->containsAlcohol()) { $render .= ' data-alcohol="1"'; } $render.= '>'. $drink->getPrice() .'</div>'; } return $render; } } It renders lines
  • 78.
    class HtmlBillPrinter implements BillPrinterInterface{ publicfunction render(Client $client){ foreach($client->getDrinks() as $drink){ $render .= '<div class="drink-line"'; if ($drink->containsAlcohol()) { $render .= ' data-alcohol="1"'; } $render.= '>'. $drink->getPrice() .'</div>'; } return $render; } } It manages HTML and its attributes It renders lines
  • 79.
    class HtmlBillPrinter implements BillPrinterInterface{ publicfunction render(Client $client){ foreach($client->getDrinks() as $drink){ $render .= '<div class="drink-line"'; if ($drink->containsAlcohol()) { $render .= ' data-alcohol="1"'; } $render.= '>'. $drink->getPrice() .'</div>'; } return $render; } } It manages HTML and its attributes And who’s gonna escape string if necessary? It renders lines
  • 80.
    class HtmlBillPrinter implements BillPrinterInterface{ publicfunction render(Client $client){ foreach($client->getDrinks() as $drink){ $render .= '<div class="drink-line"'; if ($drink->containsAlcohol()) { $render .= ' data-alcohol="1"'; } $render.= '>'. $drink->getPrice() .'</div>'; } return $render; } } It manages HTML and its attributes And who’s gonna escape string if necessary? It renders lines It’s bibi!
  • 81.
    class HtmlBillPrinter implements BillPrinterInterface{ publicfunction render(Client $client){ foreach($client->getDrinks() as $drink){ return '<div class="drink-line"'; $div = new HtmlTag('div'); $div->addAttribute('class', 'drink-line'); if ($drink->containsAlcohol()) { $render .= ' data-alcohol="1"'; } $render.= '>'. $drink->getPrice() .'</div>'; } return $render; } }
  • 82.
    class HtmlBillPrinter implements BillPrinterInterface{ publicfunction render(Client $client){ foreach($client->getDrinks() as $drink){ $div = new HtmlTag('div'); $div->addAttribute('class', 'drink-line'); if ($drink->containsAlcohol()) { $render .= ' data-alcohol="1"'; } $render.= '>'. $drink->getPrice() .'</div>'; } return $render; } }
  • 83.
    class HtmlBillPrinter implements BillPrinterInterface{ publicfunction render(Client $client){ foreach($client->getDrinks() as $drink){ $div = new HtmlTag('div'); $div->addAttribute('class', 'drink-line'); if ($drink->containsAlcohol()) { return ' data-alcohol="1"'; $div->addAttribute('data-alcohol', '1'); } $render.= '>'. $drink->getPrice() .'</div>'; } return $render; } }
  • 84.
    class HtmlBillPrinter implements BillPrinterInterface{ publicfunction render(Client $client){ foreach($client->getDrinks() as $drink){ $div = new HtmlTag('div'); $div->addAttribute('class', 'drink-line'); if ($drink->containsAlcohol()) { $div->addAttribute('data-alcohol', '1'); } $render.= '>'. $drink->getPrice() .'</div>'; } return $render; } }
  • 85.
    class HtmlBillPrinter implements BillPrinterInterface{ publicfunction render(Client $client){ foreach($client->getDrinks() as $drink){ $div = new HtmlTag('div'); $div->addAttribute('class', 'drink-line'); if ($drink->containsAlcohol()) { $div->addAttribute('data-alcohol', '1'); } $render.= '>'. $drink->getPrice() .'</div>'; $div->setHtml($drink->getPrice()); return $div; } } }
  • 86.
    class HtmlBillPrinter implements BillPrinterInterface{ publicfunction render(Client $client){ foreach($client->getDrinks() as $drink){ $div = new HtmlTag('div'); $div->addAttribute('class', 'drink-line'); if ($drink->containsAlcohol()) { $div->addAttribute('data-alcohol', '1'); } $div->setHtml($drink->getPrice()); return $div; } } }
  • 87.
    It’s all aboutmaking Your code comprehensible WRAPPING UP
  • 88.
  • 89.
  • 90.