Best Practice
&
Clean Code
Alessandro Cappellozza
12/10/2019
"Il mio codice è così bello e scritto bene che non ha commenti ma solo complimenti"
eppak
SOLID
Single responsibility principle
Ogni elemento di un programma (classe, metodo, variabile) deve avere una sola responsabilità, e che tale responsabilità debba essere interamente incapsulata dall'elemento stesso. Tutti i servizi offerti dall'elemento dovrebbero essere strettamente allineati a tale responsabilità.
class User {
protected function formatResponse($data) {
}
protected function validateUser($user) {
}
protected function loadFromDB($id) {
}
}
Open/closed principle
Afferma che le entità (classi, moduli, funzioni, ecc.) software dovrebbero essere aperte all'estensione, ma chiuse alle modifiche; in maniera tale che un'entità possa permettere che il suo comportamento sia modificato senza alterare il suo codice sorgente.
Ereditarietà dell'implementazione
Ereditarietà dell'interfaccia
class LoginService {
protected function login($user) {
if ($user istanceof Admin) {
$this->authenticateAdmin($user);
} else if ($user istanceof user) {
$this->authenticateNormal($user);
}
}
}
interface ILogin {
public function authenticateUser($user);
}
class UserLogin implements ILogin {
public function authenticateUser($user) {
}
}
class AdminLogin implements ILogin {
public function authenticateUser($user) {
}
}
class LoginService {
protected function login($user) {
$this->authenticate($user);
}
}
Liskov substitution principle
"Se q(x) è una proprietà che si può dimostrare essere valida per oggetti x di tipo T, allora q(y) deve essere valida per oggetti y di tipo S dove S è un sottotipo di T."
interface IRepository {
public function load($id);
}
class Userimplements IRepository {
public function load($id) {
return DB_table('users')->where('user_id', '=', $id);
}
}
class Admin implements IRepository {
public function load($id) {
return FileSystem::getData(id);
}
}
Interface segregation principle
Un client non dovrebbe dipendere da metodi che non usa, e che pertanto è preferibile che le interfacce siano molte, specifiche e piccole (composte da pochi metodi) piuttosto che poche, generali e grandi.
class Driver {
public function operate(Car $car) {
$car->getFuel();
$cat->shiftGear();
$car->steer();
}
}
interface ICar {
public function getFuel();
public function shiftGear();
public function steer();
}
class Car implements ICar {
private $gasoline = 0;
public function getFuel() { return $gasoline; }
public function shiftGear() {}
public function steer() {}
}
class ElecticCar implements ICar {
public function getFuel() { return null; }
public function shiftGear() {}
public function steer() {}
}
Dependency inversion principle
I moduli di alto livello non devono dipendere da quelli di basso livello. Entrambi devono dipendere da astrazioni; Le astrazioni non devono dipendere dai dettagli; sono i dettagli che dipendono dalle astrazioni.
interface IConnection {
public function connect();
}
class DBConnection implements IConnection {
public function connect() { }
}
class PasswordReset implements IConnection {
private $db;
function __constructor(IConnection $conn) {
$this->db = $conn;
}
}
Code Smell
- Codice duplicato, uguale o pressoché uguale, in diverse sezioni di codice (viola il principio don't repeat yourself).
- Metodo troppo lungo.
- Classe troppo grande.
-
Lista di parametri troppo lunga (per metodi o funzioni).
-
Feature envy o data envy ("invidia dei dati"): una classe che usa massicciamente i servizi o i dati di un'altra.
-
Costanti magiche: valori letterali (numeri, stringhe) che appaiono direttamente ("cablati") nel codice.
-
Espressioni complesse di basso livello (per esempio aritmetiche, di manipolazione di stringhe, ...).
-
What comments ("commenti cosa"): commenti che spiegano cosa fa una certa porzione di codice (sintomo che il codice non è sufficientemente chiaro di per sé).
- Nomi oscuri: nomi e identificatori (di variabili, attributi, classi, ...) che non chiariscono il significato inteso dell'entità corrispondente.
- Nomi inconsistenti: insiemi di nomi e identificatori inconsistenti fra loro (per esempio, uso inconsistente di maiuscole e minuscole).
- Dead code ("codice morto"): porzioni di codice che non sono usate (e non vengono cancellate), contribuendo al costo di manutenzione del codice senza produrre alcun beneficio.
-
Generalità speculativa: codice scritto in una forma più generale del necessario per poter essere eventualmente applicato in futuro in contesti più ampi.
L'extreme programming ha una specifica regola contro questa pratica, "You Aren't Gonna Need It" ("non ne avrai bisogno"): "implementa sempre le cose quando ne hai effettivamente bisogno, mai quando prevedi di averne bisogno".[5]
Shotgun Surgery
Shotgun Surgery resembles Divergent Change but is actually the opposite smell. Divergent Change is when many changes are made to a single class. Shotgun Surgery refers to when a single change is made to multiple classes simultaneously.
A single responsibility has been split up among a large number of classes. This can happen after overzealous application of Divergent Change.
Divergent Change
Divergent Change resembles Shotgun Surgery but is actually the opposite smell. Divergent Change is when many changes are made to a single class. Shotgun Surgery refers to when a single change is made to multiple classes simultaneously.
You find yourself having to change many unrelated methods when you make changes to a class. For example, when adding a new product type you have to change the methods for finding, displaying, and ordering products.
Data Clumps
Sometimes different parts of the code contain identical groups of variables (such as parameters for connecting to a database). These clumps should be turned into their own classes.
Refused Bequest
If a subclass uses only some of the methods and properties inherited from its parents, the hierarchy is off-kilter. The unneeded methods may simply go unused or be redefined and give off exceptions.
Someone was motivated to create inheritance between classes only by the desire to reuse the code in a superclass. But the superclass and subclass are completely different.
The Tell, Don’t Ask
You should endeavor to tell objects what you want them to do; do not ask them questions about their state, make a decision, and then tell them what to do.
Anemic Model
In object-oriented programming, and especially in Domain-Driven Design, objects are said to be anemic if they have state but lack behavior. Some kinds of objects, such as Data Transfer Objects (DTOs), are expected to simply be a collection of data.
Object Calisthenics
A way to implement and apply SOLID design principles, Design Patterns and improve code quality in our software projects.
1 - Only one level of indentation per method
class InsertionSort {
public function sort(array $array) {
// 1 level
for ($i = 0; $i < count($array); $i++) {
// 2 level
$x = $array[$i];
for ($j = $i - 1; $j >= 0; $j--) {
// 3 level
if ($array[$j] > $x) {
$array[$j + 1] = $array[$j];
$array[$j] = $x;
}
}
}
return $array;
}
}
class InsertionSort {
public function sort(array $array) {
for ($i = 0; $i < count($array); $i++) {
$array = $this->insert($array, $i);
}
return $array;
}
protected function insert(array $array, $i) {
$x = $array[$i];
for ($j = $i - 1; $j >= 0; $j--) {
$array = $this->swap($array, $x, $j);
}
return $array;
}
protected function swap(array $array, $x, $j) {
if ($array[$j] > $x) {
$array[$j + 1] = $array[$j];
$array[$j] = $x;
}
return $array;
}
}
2 - Do not use “else” keyword
class HomeController
{
protected function indexAction()
{
if ($this->security->isGranted('ADMIN')) {
$view = 'admin/index.html.twig';
} else {
$view = 'home/access_denied.html.twig';
}
return $this->render($view);
}
}
class HomeController
{
protected function indexAction()
{
if ($this->security->isGranted('ADMIN')) {
return $this->render('admin/index.html.twig');
}
return $this->render('home/access_denied.html.twig');
}
}
3 - Wrap primitive types and strings
if (filter_var($email, FILTER_VALIDATE_EMAIL) === false) {
throw new InvalidEmailException;
}
$emailDnsServiceProvider->verfiy($email);
class Email {
public function __construct(string $email) {
// validate email
}
}
$emailDnsServiceProvider->verify(new Email($email));
4 - First class collections
class Bucket {
protected $products = [];
// many other things...
}
class Bucket {
/** @var ProductCollection */
protected $products;
// many other things...
}
class ProductCollection implements Iterator { // ... }
5 - Only one object operator per line
This is the direct example of the Law of Demeter, only talk to your friends, where you should never use more than one object operator. The exception exists for Fluent Interface for example, which is another approach and doesn’t belong to this rule.
Applicazione legge di Demetra
6 - Don’t abbreviate
function delTmpFiles($p) { ... }
function sort(array $a) {
for ($i = 0; $i < count($a); $i++) { ... }
}
function deleteTemporaryFiles($path) { ... }
function sort(array $array) {
for ($index = 0; $index < count($array); $index++) { ... }
}
7 - Keep classes small
The original rule says we must have not more than 50 lines per class, which in PHP is really easy to reach even with a small entities, due to code styles and PHPDoc comments. But anyway, what we should do it’s to keep them as small as possible. The aim here is also to avoid the God Object anti-pattern
8 - No classes with more than two instance variables
We don’t even need an example here, the rule is so extreme and simple,no more than two instance variable per class, this ensure you’re respecting the High Cohesion and the Single Responsibility Principle. If it contains more than two variables, then maybe your class it’s doing too much
9 - No getter/setters
class Car {
protected $speed;
protected $people;
public function getSpeed() { ... }
public function setSpeed($speed) { ... }
public function setPeople($people) { ... }
public function getPeople() { ... }
}
class Car {
protected $speed;
protected $people;
public function onBoardPerson() { ... }
public function dropOffPerson()
{
if ($this->people === 0) {
throw new CarEmptyException;
}
$this->people--;
}
public function accelerate($speed)
{
$this->speed += $speed;
}
public function decelerate($speed) { ... }
}
Bibliografia
Clean Code
By Alessandro Cappellozza
Clean Code
- 889