On Github maxpou-slides / object-calisthenics
Maxence POUTORD
In the ThoughtWorks Anthology, Jeff Bay, list 9 rules for writing better Object Oriented code.
readability, maintainability, testability, and comprehensibility
...make your OO code great again!
public function register(Request $request): void { $username = $request->get('username'); $password = $request->get('user_password'); $confirmPassword = $request->get('confirmPassword'); if ($username) { if ($password) { if ($password === $confirmPassword) { if (mb_strlen($password) >= 8 && preg_match("regex", $password)) { // Do Something // Do Something // Do Something } } } } }
public function register(Request $request): void { $username = $request->get('username'); $password = $request->get('user_password'); $confirmPassword = $request->get('confirmPassword'); if (!$username) { return; } if (!$password) { return; } if (!$password === $confirmPassword) { return; } if (mb_strlen($password) < 8 || !preg_match("regex", $password)) { return; } // Do Something // Do Something // Do Something }
"Single Entry, Single Exit"...?
"Single Entry, Single Exit" was written for assembly languages like Fortran/COBOL
More readingYou may need to split your code into multiple functions. This is the Extract Method (cf. Martin Fowler).
//Class Command public function validateProductList(array $products): array { $validProducts[]; foreach ($products as $product) { if (!$product->price > 0 && length($product->name)) { $validProducts[] = $product; } } return $validProducts; }
//Class Command public function validateProductList(array $products): array { return array_filter($products, 'isValidProduct'); } public function isValidProduct(Product $product): boolean { if (!$product->price > 0 && length($product->name)) { return true; } return false; }
function foo($products) { if (isset($products) && count($products)) { foreach ($products as $product) { # code } } }
function foo($products) { if (isset($products) && count($products)) {foreach ($products as $product) { # code } } }
function foo($products) { foreach ($products as $product) { # code } }
public function login($username, $password) { if ($this->isValid($username, $password)) { redirect("homepage"); } else { addFlash("error", "Bad credentials"); redirect("login"); } }
public function login($username, $password) { if ($this->isValid($username, $password)) { return redirect("homepage"); } else { addFlash("error", "Bad credentials"); return redirect("login"); } }
public function login($username, $password) { // defensive approach if ($this->isValid($username, $password)) { return redirect("homepage"); } addFlash("error", "Bad credentials"); return redirect("login"); }
public function login($username, $password) { // optimistic approach if (!$this->isValid($username, $password)) { addFlash("error", "Bad credentials"); return redirect("login"); } return redirect("homepage"); }
class User { /** @var string */ private $name; /** @var string */ private $email; public function __construct(string $name, string $email) { $this->name = $name; $this->email = $email; } }//this is ok $myUser = new User("John", "john@mail.fr");//this is ok $anotherUser = new User("Max", "0811223344");//this is ok $whoeverUser = new User("Louise", "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.");//this is ok $whoeverUserBis = new User("Lucie", "打才我醫集老電了快洋,西足並良步細主!");
class Email { /** @var string */ private $email; public function __construct(string $email) { if (!filter_var($email, FILTER_VALIDATE_EMAIL) { throw new InvalidArgumentException("Invalid format!"); } $this->email = $email; } }// This is ok $myUser = new User("John", new Email("john@mail.fr"));// This is NOT ok $anotherUser = new User("Max", new Email("0811223344"));// This is NOT ok $whoeverUser = new User("Louise", new Email("Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."));// This is NOT ok $whoeverUserBis = new User("Lucie", new Email("打才我醫集老電了快洋,西足並良步細主!"));
$command = array( array( "user" => array( "id" => 42, "name" => "Maxence", "address" => "Dublin" ), "date" => "1989-04-11 07:28:47", "beers" => array( array( "ID" => "76b44e1a-a8d6-11e6-80f5-76304dec7eb7", "name" => "Tripel Karmeliet", "quantity" => 4, ), array( "ID" => "76b45220-a8d6-11e6-80f5-76304dec7eb7", "name" => "Journeyman's Pale Ale", "quantity" => 8, ), array( "ID" => "76b45356-a8d6-11e6-80f5-76304dec7eb7", "name" => "Duvel tripel hop", "quantity" => 15, ) ) ) );
foreach ($command["beers"] as $beer) { if (isset($beer["quantity"])) { $itemsCounter += $beer["quantity"]; } } // ...or $itemsCounter = $command->countItems();
class Compagny { private $name; private $employes; # code }
Where can I put filters/contains/... methods?
class Team { private $name; private $employes; # code }
'employes' collection need to be wrapped in its own class
class EmployeesCollection { private $employes; public function add(Employe $employe) {/** ... **/}; public function remove(Employe $employe) {/** ... **/}; public function count() {/** ... **/}; public function filter(Closure $p): {/** ... **/}; //... }
$objectA->getObjectB()->getObjectC();
$objectA->getThis()->getThat()->getSomethingElse()->doSomething();
What happen if getThat() return NULL?
S mean Single Responsibility Principle (SRP)
foreach ($users as $u) { // code // code // code // code // code $u->sendMail($aMail); // "$u": what does it stand for? }
Long files are hard to:
"big stuff attracts even more stuff and gets bigger"
≈ theory of the broken window
class MyWonderfulService { private $anotherService; private $logger; private $translator; private $entityManager; // code }
// Game private $score; public function setScore(int $score): void { $this->score = $score; } public function getScore(): int { return score; } // Usage $game->setScore($game->getScore() + ENEMY_DESTROYED_SCORE);
// Game public function addScore(int $delta): void { this->score += $delta; } // Usage $game->addScore(ENEMY_DESTROYED_SCORE);
class Employe { private $name; public function getName() { return $this->name; } public function setName($name) { $this->name = $name; return $this; } }
class Employe { public $name; }