Mejores prácticas en PHP utilizadas en Tiendanube.
Este documento es una referencia para los desarrolladores de Tiendanube y para la comunidad de PHP en general. Tiene como objetivo ayudar y guiar a los mismos a escribir código limpio y de alta calidad.
Tabla de Contenidos
Introducción
En esta guía recopilamos varios principios de la Ingeniería de Software introducidos en diferentes libros y experiencias propias que creemos que son buenas prácticas.
No todos los principios deben seguirse estrictamente y puede que no estés de acuerdo con todos ellos, si es así, eres bienvenido a enviar tu Pull Request.
Hay que tener en cuenta que todos de los ejemplos en este artículo funcionan en versiones superiores de PHP 7.0.
Inspirado en clean-code-dotnet.
¿Por qué seguir buenas prácticas?
Como desarrolladores, a veces nos sentimos tentados a escribir código de cierta manera que sea conveniente para terminar rápidamente sin tener en cuenta las buenas prácticas. Posteriormente esto dificulta las revisiones y las pruebas de código. Podríamos decir que estamos codificando y, al hacerlo, hacemos más difícil para otros decodificar nuestro trabajo. Sin embargo, nuestro objetivo como desarrollador debe ser que nuestro código sea reutilizable, legible y mantenible. Y eso algunas veces requiere un esfuerzo extra.
Clean Code
Naming
Evitar usar nombres complicados
Un buen nombre permite que los desarrolladores entiendan fácilmente el código. El nombre debe reflejar lo que hace y dar contexto.Mal:
$ymdstr = $moment->format('y-m-d');
Bien:
$currentDate = $moment->format('y-m-d');
Usar notación Camel Case
Usar la notación CamelCase para variables, clases, métodos y funciones.
Mal:
$customerphone = '1134971828';
public function calculate_product_price(int $productid, int $quantity)
{
// some logic
}
Bien:
$customerPhone = '1134971828';
public function calculateProductPrice(int $productId, int $quantity)
{
// some logic
}
Variables
Evitar valores mágicos
Valores mágicos se refiere a los valores constantes que se especifican directamente dentro del código. Con frecuencia, dichos valores terminan duplicados dentro del sistema y se convierten en una fuente común de errores cuando se realizan cambios.
Mal:
class Payment
{
public function isPending()
{
if ($this->status === "pending")
{
// lógica aquí
}
}
}
Bien:
class Payment
{
const STATUS_PENDING = 'pending';
public function isPending()
{
if ($this->status === self::STATUS_PENDING)
{
// lógica aquí
}
}
}
De esta manera solo tenemos que cambiar en un único lugar centralizado.`
Usar variables explicativas
No fuerce al lector del código a traducir lo que significa la variable. Explícito es mejor que implícito.
Mal:
$address = 'Avenida Rivadavia 1678, 1406';
$cityZipCodeRegex = '/^[^,]+,\s*(.+?)\s*(\d{4})$/';
preg_match($cityZipCodeRegex, $address, $matches);
saveCityZipCode($matches[1], $matches[2]);
No tan mal:
$address = 'Avenida Rivadavia 1678, 1406';
$cityZipCodeRegex = '/^[^,]+,\s*(.+?)\s*(\d{4})$/';
preg_match($cityZipCodeRegex, $address, $matches);
[, $street, $zipCode] = $matches;
saveCityZipCode($street, $zipCode);
Bien: Al utilizar subpatrones es más fácil entender la expresión regular.
$address = 'Avenida Rivadavia 1678, 1406';
$cityZipCodeRegex = '/^[^,]+,\s*(?<street>.+?)\s*(?<zipCode>\d{4})$/';
preg_match($cityZipCodeRegex, $address, $matches);
saveCityZipCode($matches['street'], $matches['zipCode']);
Evitar anidación profunda.
Debemos minimizar al máximo los niveles de indentación. Muchas declaraciones if-else anidadas pueden hacer tu código difícil de seguir.
Mal
function isShopOpen($day): bool
{
if ($day) {
if (is_string($day)) {
$day = strtolower($day);
if ($day === 'friday') {
return true;
} elseif ($day === 'saturday') {
return true;
} elseif ($day === 'sunday') {
return true;
} else {
return false;
}
} else {
return false;
}
} else {
return false;
}
}
Bien
function isShopOpen(string $day): bool
{
if (empty($day)) {
return false;
}
$openingDays = [
'friday', 'saturday', 'sunday'
];
return in_array(strtolower($day), $openingDays, true);
}
Evitar utilizar sentencia else.
La sentencia else
puede dificultar la lectura del código si la sentencia if
contiene demasiado código.
Mal
if ($product->hasStock()) {
// mucho código...
} else {
return false;
}
Bien
Para evitar utilizar la sentencia else
podemos utilizar Cláusulas de Guarda.
Las cláusulas de guarda es una técnica sencilla que consiste en negar la condición de la consulta y eliminar la sentencia else
.
if (!$product->hasStock()) {
return false;
}
// código...
No agregar contexto innecesario
Si el nombre de tu clase/objeto te dice algo, no lo repitas en el nombre del atributo.
Mal:
class Product
{
private $productPrice;
private $productStock;
private $productDescription;
//...
}
Bien:
class Product
{
private $price;
private $stock;
private $description;
//...
}
Métodos y Funciones
Los siguiente consejos aplican tanto a métodos como a funciones.
Declaración de tipos en parámetros y retornos
Desde PHP 7 podemos utilizar la declaración de tipos para los argumentos de entrada y salida de nuestras funciones y métodos.
Mal:
function sum($val1, $val2)
{
return $val1 + $val2;
}
Bien:
function sum(int $val1, int $val2): int
{
return $val1 + $val2;
}
Argumentos de la función (idealmente no más de 2)
El número ideal de argumentos es cero, después uno (monádico) y después dos (diádico). Siempre debes evitar desarrollar funciones o métodos con tres o más argumentos (poliádico). Más de dos argumentos significa que el método esta haciendo demasiadas cosas. Para los casos en donde se necesiten más de dos argumentos, es mejor utilizar un objeto como argumento.
Mal:
function createProduct(string $title, string $description, float $price, int $stock): void
{
// ...
}
Bien:
class ProductConfig
{
private string $title;
private string $description;
private float $price;
private int $stock;
public function __construct(string $title, string $description, float $price, int $stock)
{
$this->title = $title;
$this->description = $description;
$this->price = $price;
$this->stock = $stock;
}
}
function createProduct(ProductConfig $config): void
{
// ...
}
$config = new ProductConfig('foo', 'bar', 10.0, 100);
createProduct($config);
Las funciones deben hacer una cosa
Cuando las funciones hacen más de una acción, se vuelven difíciles de razonar y probar. Cuando puedes aislar una función en una sola acción, ellas pueden ser refactorizadas con facilidad y el código será mucho más fácil de leer y reutilizar.
Mal:
function emailClients(array $clients): void
{
foreach ($clients as $client) {
$clientRecord = $db->find($client);
if ($clientRecord->isActive()) {
email($client);
}
}
}
Bien:
function emailClients(array $clients): void
{
$activeClients = activeClients($clients);
array_walk($activeClients, 'email');
}
function activeClients(array $clients): array
{
return array_filter($clients, 'isClientActive');
}
function isClientActive(int $client): bool
{
$clientRecord = $db->find($client);
return $clientRecord->isActive();
}
Tamaño reducido
La primera regla de las funciones es que deben ser de tamaño reducido. La segunda regla es que deben ser todavía más reducidos. La experiencia cuenta que las funciones con más de 20 líneas de código aproximadamente, esconden más de una acción.
Los nombres de las funciones y métodos deben indicar lo que hacen
Mal:
class Email
{
//...
public function handle(): void
{
mail($this->to, $this->subject, $this->body);
}
}
$message = new Email(...);
// ¿Qué es esto? ¿Un "manejador" para los mensajes? 🤔
$message->handle();
Bien:
class Email
{
//...
public function send(): void
{
mail($this->to, $this->subject, $this->body);
}
}
$message = new Email(...);
// Limpio y obvio
$message->send();
Las funciones deben tener sólo un nivel de abstracción
Cuando existe más de un nivel de abstracción usualmente es porque la función está haciendo demasiado. Dividirlas en funciones más pequeñas lleva a la reutilización de las mismas y facilita las pruebas.
Mal:
function parseBetterJSAlternative(string $code): void
{
$regexes = [
// ...
];
$statements = explode(' ', $code);
$tokens = [];
foreach ($regexes as $regex) {
foreach ($statements as $statement) {
// ...
}
}
$ast = [];
foreach ($tokens as $token) {
// lex...
}
foreach ($ast as $node) {
// convertir...
}
}
También mal:
Hemos separado algunas de las funcionalidades, pero la función parseBetterJSAlternative() todavía es muy compleja e imposible de probar.
function tokenize(string $code): array
{
$regexes = [
// ...
];
$statements = explode(' ', $code);
$tokens = [];
foreach ($regexes as $regex) {
foreach ($statements as $statement) {
$tokens[] = /* ... */;
}
}
return $tokens;
}
function lexer(array $tokens): array
{
$ast = [];
foreach ($tokens as $token) {
$ast[] = /* ... */;
}
return $ast;
}
function parseBetterJSAlternative(string $code): void
{
$tokens = tokenize($code);
$ast = lexer($tokens);
foreach ($ast as $node) {
// convertir...
}
}
Bien:
Lo mejor es sacar las dependencias de la función parseBetterJSAlternative()
.
class Tokenizer
{
public function tokenize(string $code): array
{
$regexes = [
// ...
];
$statements = explode(' ', $code);
$tokens = [];
foreach ($regexes as $regex) {
foreach ($statements as $statement) {
$tokens[] = /* ... */;
}
}
return $tokens;
}
}
class Lexer
{
public function lexify(array $tokens): array
{
$ast = [];
foreach ($tokens as $token) {
$ast[] = /* ... */;
}
return $ast;
}
}
class BetterJSAlternative
{
private $tokenizer;
private $lexer;
public function __construct(Tokenizer $tokenizer, Lexer $lexer)
{
$this->tokenizer = $tokenizer;
$this->lexer = $lexer;
}
public function parse(string $code): void
{
$tokens = $this->tokenizer->tokenize($code);
$ast = $this->lexer->lexify($tokens);
foreach ($ast as $node) {
// convertir...
}
}
}
No usar flags como parámetros de funciones
Las banderas le dicen al usuario que la función hace más de una cosa. Se debe dividir las funciones si siguen diferentes caminos basados en un valor booleano.
Mal:
function createFile(string $name, bool $temp = false): void
{
if ($temp) {
touch('./temp/'.$name);
} else {
touch($name);
}
}
Bien:
function createFile(string $name): void
{
touch($name);
}
function createTempFile(string $name): void
{
touch('./temp/'.$name);
}
Evitar efectos secundarios
El punto principal es evitar compartir estados entre objetos sin alguna estructura, usar tipos de datos mutables que puedan ser escritos por cualquiera, y no centralizar donde el efectos ocurren.
Mal:
function createUser(string $name, string $email, string $password)
{
$user = new User($name, $email, $password);
$user->create();
$message = new Email($email);
$message->send();
}
Bien:
function createUser(string $name, string $email, string $password)
{
$user = new User($name, $email, $password);
$user->create();
}
function sendEmail($email)
{
$message = new Email($email);
$message->send();
}
Evitar retornar null
Al retornar null
podríamos estar obligando al cliente de la función o método a realizar una validación adicional.
Basta con que falte la comprobación de null
para que la aplicación pierda el control.
Si siente la tentación de devolver null
, pruebe con lanzar una excepción, devolver una estructura vacía o un objeto vacío (Null Object Pattern).
Mal:
function getItems()
{
//...
return null;
}
$items = getItems();
if ($items !== null) {
foreach ($items as $item) {
// ...
}
}
Bien:
function getItems()
{
//...
return [];
}
$items = getItems();
foreach ($items as $item) {
// ...
}
Condicionales
Evitar condicionales
Al tener declaraciones if
dentro de nuestras funciones, estamos diciendo que nuestra función puede hacer más de una cosa.
Una de varias soluciones es utilizar polimorfismo para evitar condicionales.
Mal:
class Airplane
{
public function getCruisingAltitude(): int
{
switch ($this->type) {
case '777':
return $this->getMaxAltitude() - $this->getPassengerCount();
case 'Air Force One':
return $this->getMaxAltitude();
case 'Cessna':
return $this->getMaxAltitude() - $this->getFuelExpenditure();
}
}
}
Bien:
interface Airplane
{
public function getCruisingAltitude(): int;
}
class Boeing777 implements Airplane
{
public function getCruisingAltitude(): int
{
return $this->getMaxAltitude() - $this->getPassengerCount();
}
}
class AirForceOne implements Airplane
{
public function getCruisingAltitude(): int
{
return $this->getMaxAltitude();
}
}
class Cessna implements Airplane
{
public function getCruisingAltitude(): int
{
return $this->getMaxAltitude() - $this->getFuelExpenditure();
}
}
Evitar condicionales negativos
Mal:
if (!haveNotPromotions($product))
{
// ...
}
Bien:
if (havePromotions($product))
{
// ...
}
Evitar verificación de tipo
Hacer verificaciones de tipos da una falsa "seguridad de tipado" además de afectar la legibilidad del código.
Mal:
function travelToTexas($vehicle): void
{
if ($vehicle instanceof Bicycle) {
$vehicle->pedalTo(new Location('texas'));
} elseif ($vehicle instanceof Car) {
$vehicle->driveTo(new Location('texas'));
}
}
Bien:
function travelToTexas(Traveler $vehicle): void
{
$vehicle->travelTo(new Location('texas'));
}
Evitar revisión de tipos primitivos
El punto anterior también aplica a los tipos primitivos.
Mal:
function combine($val1, $val2): int
{
if (!is_numeric($val1) || !is_numeric($val2)) {
throw new \Exception('Must be of type Number');
}
return $val1 + $val2;
}
Bien:
function combine(int $val1, int $val2): int
{
return $val1 + $val2;
}
Encapsular condicionales
Encapsula condiciones dentro de métodos o funciones brinda un unico punto de modificación, mayor cohesión y promueve la reutilización.
Mal:
if ($payment->status === 'pending') {
// ...
}
Bien:
if ($payment->isPending()) {
// ...
}
Clases y Objetos
Clases finales por defecto
Al momento de pensar y crear una clase debemos partir de la premisa que son clases finales. De esta forma promovemos la idea de que deben funcionar como una unidad del sistema. Permitiendo mayor entendimiento, mantenimiento y reutilización de la clase.
La visibilidad de los objetos se debe ir abriendo según evolucione el código o en casos justificados.
No tan bien:
class Employee
{
// código...
}
Bien:
final class Employee
{
// código...
}
Propiedades privadas por defecto
La visibilidad de las propiedades debe ser private
por defecto. Ya que el objeto no debe revelar nada de si mismo,
excepto lo que sea necesario para que otras partes del sistema interactúen con él.
Mal:
final class Product
{
public float $price = 1000.00;
}
Bien:
final class Product
{
private float $price = 1000.00;
Constructor sobre setters públicos
Siempre debemos esconder los detalles de implementación de los objetos. Para eso, debemos construir los objetos por medio de su constructor y no por setters públicos.
Mal:
final class Product
{
private string $description;
private float $price;
public function setDescription(string $value): void
{
$this->description = $value;
}
public function setPrice(float $value): void
{
$this->price = $value;
}
}
$product = new Product();
$product->setDescription('Foo');
$product->setPrice(1000.00);
Bien:
final class Product
{
private string $description;
private float $price;
public function __construct(string $description, float $price)
{
$this->description = $description;
$this->price = $price;
}
}
$product = new Product('Foo', 1000.00);
O también:
Note que los setters son private
.
Esta forma es útil cuando deseas agregar lógica al momento de establecer una propiedad.
final class Product
{
private string $description;
private float $price;
public function __construct(string $description, float $price)
{
$this->setDescription($description);
$this->setPrice($price);
}
private function setDescription(string $value): void
{
$this->description = $value;
}
private function setPrice(float $value): void
{
$this->price = $value;
}
}
$product = new Product('Foo', 1000.00);
Preferir la composición sobre la herencia
Composición sobre herencia es un principio de diseño que permite una mayor flexibilidad. Es más natural construir clases de dominio a partir de varios componentes que tratar de encontrar puntos en común entre ellos y crear un árbol genealógico, como ocurre con la herencia. La composición también proporciona un dominio del negocio más estable a largo plazo, ya que es menos propenso a las peculiaridades que puedan llegar a tener las clases hijas de hijas de hijas...
Mal:
class Vehicle
{
public function move()
{
echo "Move the car";
}
}
class Car extends Vehicle
{
public function accelarate()
{
$this->move();
}
}
$car = new Car();
$car->accelarate();
En este ejemplo existe un acoplamiento muy estrecho entre las clases Vehicle
y Car
.
Si algo cambia en la clase Vehicle
, específicamente en el método move()
, la clase Car
puede romperse fácilmente
ya que la superclase Vehicle
no tiene idea de para qué la utilizan las clases hijas.
Bien:
interface MoveVehicle
{
public function move();
}
final class Vehicle implements MoveVehicle
{
public function move()
{
echo "Move the car";
}
}
final class Car
{
private MoveVehicle $vehicle;
public function __construct(MoveVehicle $vehicle)
{
$this->vehicle = $vehicle;
}
public function accelarate()
{
$this->vehicle->move();
}
}
$vehicle = new Vehicle();
$car = new Car($vehicle);
$car->accelarate();
Ahora nuestra clase Car
se compone de cualquier clase que implemente la interfaz. Esto brinda una gran flexibilidad y
rompe con el acoplamiento estrecho que teníamos en la herencia.
Principios de Diseño de Software
¿Qué es el Diseño de Software?
Los principios de diseño son un conjunto de diseños y buenas prácticas que se emplean en OOD y OOP (diseño y programación orientada a objetos).
Robert C. Martin dice que la mayoría de nosotros usamos lenguajes orientados a objetos sin saber por qué, y sin saber cómo obtener el beneficio máximo de ellos. Por este motivo es que surgen estos patrones de diseño.
Aplicar estos principios nos ayudaran, entre otras cosas, a crear un código que sea más legible, simple, reusable, escalable y fácil de mantener. Es decir, nos ayudaran a evitar los problemas con los que nos solemos encontrar a medida que nuestro código va creciendo.
Algunos de ellos son:
SOLID
¿Qué es SOLID?
SOLID es un acrónimo donde cada letra representa un principio del diseño orientado a objetos. Los cinco principios están muy relacionados entre si y nos brindan distintas técnicas para crear código de alta calidad.
Principio de responsabilidad única (SRP)
El Principio de Responsabilidad Única (SRP) es la S del Principio SOLID y nos dice que una clase debe tener un único motivo por el cual debe ser modificada.
Mal:
class Page
{
private string $title;
public function title(): string
{
return $this->title;
}
public function formatJson()
{
return json_encode($this->title());
}
}
Si necesitamos agregar otro tipo de formato debemos modificar la clase para agregar otro método. Eso está bien para una clase tan simple como esta, pero si la clase tuviera más propiedades, el formato sería más complejo de cambiar.
Bien:
class Page
{
private string $title;
public function title(): string
{
return $this->title;
}
}
class JsonPageFormatter
{
public function format(Page $page)
{
return json_encode($page->title());
}
}
Hacer esto significa que si quisiéramos crear un formato XML, simplemente podríamos agregar una clase llamada XmlPageFormatter
y escribir un código simple para generar XML. Ahora solo tenemos una razón para cambiar la clase Page
.
Principio abierto/cerrado (OCP)
Las entidades de software (clases, módulos, funciones, etc) deben ser abiertas para ser extendidas, pero cerradas para modificarlas. Este principio establece que puedes permitir a tus colegas desarrolladores agregar nuevas funcionalidades pero sin cambiar el código existente.
Parece complicado pero gracias a las interfaces y abstracciones se convierte en algo muy simple.
Mal:
class Customer
{
public function pay(float $total, CreditCardPayment $paymentMethod)
{
$paymentMethod->execute($total);
}
}
class CreditCardPayment
{
public function execute(float $total)
{
// lógica para pagar con tarjeta de crédito...
}
}
$customer = new Customer();
$customer->pay(1000.00, new CreditCardPayment());
De esta forma, el cliente solamente puede pagar con tarjeta de crédito. Si quisiéramos agregar un nuevo método de pago
deberíamos modificar el método pay()
.
Bien:
interface PaymentMethod
{
public function execute(float $total);
}
class Customer
{
public function pay(float $total, PaymentMethod $paymentMethod)
{
$paymentMethod->execute($total);
}
}
class CreditCardPayment implements PaymentMethod
{
public function execute(float $total)
{
// lógica para pagar con tarjeta de crédito...
}
}
class CashPayment implements PaymentMethod
{
public function execute(float $total)
{
// lógica para pagar con dinero...
}
}
$customer = new Customer();
$customer->pay(1000.00, new CreditCardPayment());
// o
$customer->pay(1000.00, new CashPayment());
Gracias a la interfaz PaymentMethod
, el método pay()
quedo cerrado para modificaciones pero abierto a nuevas formas de pago.
Principio de sustitución de Liskov (LSP)
El principio de Liskov establece que, dada una clase padre y una clase hija, pueden intercambiarse sin obtener resultados incorrectos.
Mal:
class Rectangle
{
// ...
public function setWidth(int $width): void
{
$this->width = $width;
}
public function setHeight(int $height): void
{
$this->height = $height;
}
public function getArea(): int
{
return $this->width * $this->height;
}
}
class Square extends Rectangle
{
//...
public function setWidth(int $width): void
{
$this->width = $this->height = $width;
}
public function setHeight(int $height): void
{
$this->width = $this->height = $height;
}
}
Debido a que Square
es diferente a Rectangle
, se necesita anular parte del código para permitir
que un cuadrado exista correctamente.
Anular mucho código en clases para adaptarse a situaciones específicas puede provocar problemas de mantenimiento.
Bien:
interface Quadrilateral {
public function setHeight(int $height);
public function setWidth(int $width);
public function getArea();
}
class Rectangle implements Quadrilateral
{
// ...
}
class Square implements Quadrilateral
{
// ...
}
La conclusión aquí es que si se anulando gran parte del código, entonces tal vez la arquitectura no sea la correcta y se debería pensar en el principio de sustitución de Liskov.
Principio de la segregación de la interfaz (ISP)
El principio de segregación de la interfaz dice que un cliente solo debe conocer los métodos que van a utilizar y no aquellos que no utilizará.
Mal:
interface Worker
{
public function takeBreak();
public function code();
public function callToClient();
public function attendMeetings();
public function getPaid();
}
class Manager implements Worker
{
// ...
public function code()
{
return false;
}
}
class Developer implements Worker
{
// ...
public function callToClient()
{
echo "I'll ask my manager.";
}
}
En el ejemplo anterior, la interfaz Worker
abarca demasiadas funcionalidades obligando a implementar
el método callToClient()
en la clase Developer
o devolver falso en el método code()
para Manager
ya que los managers no desarrollan.
Bien:
Al aplicar ISP las interfaces quedan divididas en varias especificas.
interface Worker
{
public function takeBreak();
public function getPaid();
}
interface Coder
{
public function code();
}
interface ClientFacer
{
public function callToClient();
public function attendMeetings();
}
class Developer implements Worker, Coder
{
// ...
}
class Manager implements Worker, ClientFacer
{
// ...
}
Principio de la inversión de dependencias (DIP)
El principio de la inversión de dependencias consiste en que las clases no deben depender de clases concretas, sino que deben depender de abstracciones.
Mal:
class MySqlConnection
{
public function connect() {}
}
class PageLoader
{
private MySqlConnection $dbConnection;
public function __construct(MySqlConnection $dbConnection)
{
$this->dbConnection = $dbConnection;
}
}
Bien:
interface DbConnectionInterface
{
public function connect();
}
class MySqlConnection implements DbConnectionInterface
{
public function connect() {}
}
class PageLoader
{
private DbConnectionInterface $dbConnection;
public function __construct(DbConnectionInterface $dbConnection)
{
$this->dbConnection = $dbConnection;
}
}
Otro ejemplo de este principio lo vimos en la sección de [Composición sobre Herencia]
Principio DRY
Principio DRY: Don’t Repeat Yourself
Según este principio toda pieza de información nunca debería ser duplicada debido a que la duplicación incrementa la dificultad en los cambios y evolución posterior, puede perjudicar la claridad y crear un espacio para posibles inconsistencias.
A menudo se tiene código duplicado porque existen dos o más cosas ligeramente diferentes que comparten mucho en común, pero sus diferencias fuerzan a tener dos o más funciones separadas haciendo mas de lo mismo. Remover código duplicado significa crear una abstracción que puedan manejar diferentes conjuntos de cosas en una función/método/clase.
Mal:
function showDeveloperList(array $developers): void
{
foreach ($developers as $developer) {
$expectedSalary = $developer->calculateExpectedSalary();
$experience = $developer->getExperience();
$githubLink = $developer->getGithubLink();
$data = [
$expectedSalary,
$experience,
$githubLink
];
render($data);
}
}
function showManagerList(array $managers): void
{
foreach ($managers as $manager) {
$expectedSalary = $manager->calculateExpectedSalary();
$experience = $manager->getExperience();
$githubLink = $manager->getGithubLink();
$data = [
$expectedSalary,
$experience,
$githubLink
];
render($data);
}
}
Bien:
function showList(array $employees): void
{
foreach ($employees as $employee) {
$expectedSalary = $employee->calculateExpectedSalary();
$experience = $employee->getExperience();
$githubLink = $employee->getGithubLink();
$data = [
$expectedSalary,
$experience,
$githubLink
];
render($data);
}
}
Mejor:
Mejor aún si se eliminan variables temporales.
function showList(array $employees): void
{
foreach ($employees as $employee) {
render([
$employee->calculateExpectedSalary(),
$employee->getExperience(),
$employee->getGithubLink()
]);
}
}
Principio Tell don't ask
¿Qué es el principio Tell don't ask?
El principio Tell Don’t Ask dice que no se debe preguntar a un objeto sobre su estado para luego realizar una acción. Por el contrario, debe realizar la acción directamente.
Mal:
$order = new Order();
if ($order->hasFreeShipping()) {
$order->shippingCost = 0;
}
Bien:
$order = new Order();
$order->updateShippingPrice();
class Order
{
public function updateShippingPrice()
{
if ($this->hasFreeShipping()) {
$this->shippingCost = 0;
}
// ...
}
}
Testing
WIP