├── .gitattributes ├── .travis-build.php ├── .travis.yml ├── LICENSE └── README.md /.gitattributes: -------------------------------------------------------------------------------- 1 | *.md linguist-documentation=false 2 | *.md linguist-language=PHP 3 | -------------------------------------------------------------------------------- /.travis-build.php: -------------------------------------------------------------------------------- 1 | setFlags(SplFileObject::DROP_NEW_LINE); 6 | 7 | $cliRedBackground = "\033[37;41m"; 8 | $cliReset = "\033[0m"; 9 | $exitStatus = 0; 10 | 11 | $indentationSteps = 3; 12 | $manIndex = 0; 13 | $linesWithSpaces = []; 14 | $tableOfContentsStarted = null; 15 | $currentTableOfContentsChapters = []; 16 | $chaptersFound = []; 17 | foreach ($readMeFile as $lineNumber => $line) { 18 | if (preg_match('/\s$/', $line)) { 19 | $linesWithSpaces[] = sprintf('%5s: %s', 1 + $lineNumber, $line); 20 | } 21 | if (preg_match('/^(?##+)\s(?.+)/', $line, $matches)) { 22 | if (null === $tableOfContentsStarted) { 23 | $tableOfContentsStarted = true; 24 | continue; 25 | } 26 | $tableOfContentsStarted = false; 27 | 28 | $chaptersFound[] = sprintf('%s [%s](#%s)', 29 | strlen($matches['depth']) === 2 30 | ? sprintf(' %s.', ++$manIndex) 31 | : ' *' 32 | , 33 | $matches['title'], 34 | preg_replace(['/ /', '/[^-\w]+/'], ['-', ''], strtolower($matches['title'])) 35 | ); 36 | } 37 | if ($tableOfContentsStarted === true && isset($line[0])) { 38 | $currentTableOfContentsChapters[] = $line; 39 | } 40 | } 41 | 42 | if (count($linesWithSpaces)) { 43 | fwrite(STDERR, sprintf("${cliRedBackground}The following lines end with a space character:${cliReset}\n%s\n\n", 44 | implode(PHP_EOL, $linesWithSpaces) 45 | )); 46 | $exitStatus = 1; 47 | } 48 | 49 | $currentTableOfContentsChaptersFilename = __DIR__ . '/current-chapters'; 50 | $chaptersFoundFilename = __DIR__ . '/chapters-found'; 51 | 52 | file_put_contents($currentTableOfContentsChaptersFilename, implode(PHP_EOL, $currentTableOfContentsChapters)); 53 | file_put_contents($chaptersFoundFilename, implode(PHP_EOL, $chaptersFound)); 54 | 55 | $tableOfContentsDiff = shell_exec(sprintf('diff --unified %s %s', 56 | escapeshellarg($currentTableOfContentsChaptersFilename), 57 | escapeshellarg($chaptersFoundFilename) 58 | )); 59 | 60 | @ unlink($currentTableOfContentsChaptersFilename); 61 | @ unlink($chaptersFoundFilename); 62 | 63 | if (!empty($tableOfContentsDiff)) { 64 | fwrite(STDERR, sprintf("${cliRedBackground}The table of contents is not aligned:${cliReset}\n%s\n\n", 65 | $tableOfContentsDiff 66 | )); 67 | $exitStatus = 1; 68 | } 69 | 70 | exit($exitStatus); 71 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | 3 | sudo: false 4 | 5 | php: 6 | - nightly 7 | 8 | script: php .travis-build.php 9 | 10 | notifications: 11 | email: false 12 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Ryan McDermott 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Clean Code PHP 2 | Traducción al español de [Clean Code PHP](https://github.com/jupeter/clean-code-php) realizado por [Jupeter](https://github.com/jupeter/). 3 | Si encuentras un error de ortografía, de redacción o de traducción; no dudes en hacer un PR. 4 | 5 | ## Tabla de contenidos 6 | 7 | 1. [Introducción](#introducción) 8 | 2. [Variables](#variables) 9 | * [Usar variables que tengan significado y sean pronunciables](#usar-variables-que-tengan-significado-y-sean-pronunciables) 10 | * [Usar el mismo vocabulario para el mismo tipo de variable](#usar-el-mismo-vocabulario-para-el-mismo-tipo-de-variable) 11 | * [Usar nombres que puedan ser buscados (parte 1)](#usar-nombres-que-puedan-ser-buscados-parte-1) 12 | * [Usar nombres que puedan ser buscados (parte 2)](#usar-nombres-que-puedan-ser-buscados-parte-2) 13 | * [Usar variables explicativas](#usar-variables-explicativas) 14 | * [Evitar anidación profunda usando return tempranamente (parte 1)](#evitar-anidación-profunda-usando-return-tempranamente-parte-1) 15 | * [Evitar anidación profunda usando return tempranamente (parte 2)](#evitar-anidación-profunda-usando-return-tempranamente-parte-2) 16 | * [Evitar mapas mentales](#evitar-mapas-mentales) 17 | * [No agregar contexto innecesario](#no-agregar-contexto-innecesario) 18 | * [Usar argumentos por defecto en lugar de cortocircuitos o condicionales](#usar-argumentos-por-defecto-en-lugar-de-cortocircuitos-o-condicionales) 19 | 3. [Funciones](#funciones) 20 | * [Argumentos de la función (idealmente 2 o menos)](#argumentos-de-la-función-idealmente-2-o-menos) 21 | * [Las funciones deben hacer una cosa](#las-funciones-deben-hacer-una-cosa) 22 | * [Los nombres de las funciones deben indicar lo que hacen](#los-nombres-de-las-funciones-deben-indicar-lo-que-hacen) 23 | * [Las funciones deben tener sólo un nivel de abstracción](#las-funciones-deben-tener-sólo-un-nivel-de-abstracción) 24 | * [No usar banderas como parámetros de funciones](#no-usar-banderas-como-parámetros-de-funciones) 25 | * [Evitar efectos secundarios](#evitar-efectos-secundarios) 26 | * [No escribir funciones globales](#no-escribir-funciones-globales) 27 | * [No usar el patrón Singleton](#no-usar-el-patrón-singleton) 28 | * [Encapsular condicionales](#encapsular-condicionales) 29 | * [Evitar condicionales negativos](#evitar-condicionales-negativos) 30 | * [Evitar condicionales](#evitar-condicionales) 31 | * [Evitar revisión de tipo (parte 1)](#evitar-revisión-de-tipo-parte-1) 32 | * [Evitar revisión de tipo (parte 2)](#evitar-revisión-de-tipo-parte-2) 33 | * [Quitar código muerto](#quitar-código-muerto) 34 | 4. [Objetos y estructuras de datos](#objetos-y-estructuras-de-datos) 35 | * [Usar encapsulación de objetos](#usar-encapsulación-de-objetos) 36 | * [Hacer que los objetos tengan partes private/protected](#hacer-que-los-objetos-tengan-partes-privateprotected) 37 | 5. [Clases](#clases) 38 | * [Preferir composición antes que herencia](#preferir-composición-antes-que-herencia) 39 | * [Evitar interfaces fluidas](#evitar-interfaces-fluidas) 40 | 6. [SOLID](#solid) 41 | * [Principio de responsabilidad única](#principio-de-responsabilidad-única) 42 | * [Principio de abierto/cerrado](#principio-de-abiertocerrado) 43 | * [Principio de la sustitución de Liskov](#principio-de-la-sustitución-de-liskov) 44 | * [Principio de segregación de la interfaz](#principio-de-segregación-de-la-interfaz) 45 | * [Principio de la inversión de dependencia](#principio-de-la-inversión-de-dependencia) 46 | 7. [No te repitas](#no-te-repitas) 47 | 8. [Traducciones](#traducciones) 48 | 49 | ## Introducción 50 | 51 | Adaptación para PHP de los principios de ingeniería de software descritos por Robert C. Martin en su libro 52 | [*Clean Code*](https://www.amazon.com/Clean-Code-Handbook-Software-Craftsmanship/dp/0132350882). Esta no es una guía de estilo. Es una guía para producir software que sea legible, reutilizable y refactorizable en PHP. 53 | 54 | No todos los principios deben ser seguidos estrictamente, e incluso unos pocos serán aceptados totalmente. Estos son una referencia y nada más, pero han sido desarrollados tras los años de experiencia del autor de *Clean Code*. 55 | 56 | Inspirado por [clean-code-javascript](https://github.com/ryanmcdermott/clean-code-javascript) 57 | 58 | A pesar de que muchos desarrolladores aún utilizan PHP 5, la mayoría de los ejemplos en este artículo funcionan sólo en PHP 7.1+. 59 | 60 | ## Variables 61 | 62 | ### Usar variables que tengan significado y sean pronunciables 63 | 64 | **Mal:** 65 | 66 | ```php 67 | $ymdstr = $moment->format('y-m-d'); 68 | ``` 69 | 70 | **Bien:** 71 | 72 | ```php 73 | $currentDate = $moment->format('y-m-d'); 74 | ``` 75 | 76 | **[⬆Volver](#tabla-de-contenidos)** 77 | 78 | ### Usar el mismo vocabulario para el mismo tipo de variable 79 | 80 | **Mal:** 81 | 82 | ```php 83 | getUserInfo(); 84 | getUserData(); 85 | getUserRecord(); 86 | getUserProfile(); 87 | ``` 88 | 89 | **Bien:** 90 | 91 | ```php 92 | getUser(); 93 | ``` 94 | 95 | **[⬆Volver](#tabla-de-contenidos)** 96 | 97 | ### Usar nombres que puedan ser buscados (parte 1) 98 | 99 | Leerás más código del que puedas escribir. Es importante que el código que escribimos sea legible y que pueda ser buscado. Dañamos a nuestros lectores al *no* escribir nombres de variables que tengan significado para entender nuestro programa. Crea nombres que puedan ser buscados. 100 | 101 | **Mal:** 102 | 103 | ```php 104 | // ¿Qué diablos significa 448? 105 | $result = $serializer->serialize($data, 448); 106 | ``` 107 | 108 | **Bien:** 109 | 110 | ```php 111 | $json = $serializer->serialize($data, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE); 112 | ``` 113 | 114 | ### Usar nombres que puedan ser buscados (parte 2) 115 | 116 | **Mal:** 117 | 118 | ```php 119 | // ¿Qué diablos significa 4? 120 | if ($user->access & 4) { 121 | // ... 122 | } 123 | ``` 124 | 125 | **Bien:** 126 | 127 | ```php 128 | class User 129 | { 130 | const ACCESS_READ = 1; 131 | const ACCESS_CREATE = 2; 132 |    const ACCESS_UPDATE = 4; 133 | const ACCESS_DELETE = 8; 134 | } 135 | 136 | if ($user->access & User::ACCESS_UPDATE) { 137 | // Editar ... 138 | } 139 | ``` 140 | 141 | **[⬆Volver](#tabla-de-contenidos)** 142 | 143 | ### Usar variables explicativas 144 | 145 | **Mal:** 146 | 147 | ```php 148 | $address = 'One Infinite Loop, Cupertino 95014'; 149 | $cityZipCodeRegex = '/^[^,]+,\s*(.+?)\s*(\d{5})$/'; 150 | preg_match($cityZipCodeRegex, $address, $matches); 151 | 152 | saveCityZipCode($matches[1], $matches[2]); 153 | ``` 154 | 155 | **Nada mal:** 156 | 157 | Está mejor, pero todavía es muy dependiente de la expresión regular. 158 | 159 | ```php 160 | $address = 'One Infinite Loop, Cupertino 95014'; 161 | $cityZipCodeRegex = '/^[^,]+,\s*(.+?)\s*(\d{5})$/'; 162 | preg_match($cityZipCodeRegex, $address, $matches); 163 | 164 | [, $city, $zipCode] = $matches; 165 | saveCityZipCode($city, $zipCode); 166 | ``` 167 | 168 | **Bien:** 169 | 170 | Disminuye la dependencia a la expresión regular al nombrar subpatrones. 171 | 172 | ```php 173 | $address = 'One Infinite Loop, Cupertino 95014'; 174 | $cityZipCodeRegex = '/^[^,]+,\s*(?<city>.+?)\s*(?<zipCode>\d{5})$/'; 175 | preg_match($cityZipCodeRegex, $address, $matches); 176 | 177 | saveCityZipCode($matches['city'], $matches['zipCode']); 178 | ``` 179 | 180 | **[⬆Volver](#tabla-de-contenidos)** 181 | 182 | ### Evitar anidación profunda usando return tempranamente (parte 1) 183 | 184 | Muchas declaraciones if else pueden hacer tu código difícil de seguir. Explicito es mejor que implícito. 185 | 186 | **Mal:** 187 | 188 | ```php 189 | function isShopOpen($day): bool 190 | { 191 | if ($day) { 192 | if (is_string($day)) { 193 | $day = strtolower($day); 194 | if ($day === 'friday') { 195 | return true; 196 | } elseif ($day === 'saturday') { 197 | return true; 198 | } elseif ($day === 'sunday') { 199 | return true; 200 | } else { 201 | return false; 202 | } 203 | } else { 204 | return false; 205 | } 206 | } else { 207 | return false; 208 | } 209 | } 210 | ``` 211 | 212 | **Bien:** 213 | 214 | ```php 215 | function isShopOpen(string $day): bool 216 | { 217 | if (empty($day)) { 218 | return false; 219 | } 220 | 221 | $openingDays = [ 222 | 'friday', 'saturday', 'sunday' 223 | ]; 224 | 225 | return in_array(strtolower($day), $openingDays, true); 226 | } 227 | ``` 228 | 229 | **[⬆Volver](#tabla-de-contenidos)** 230 | 231 | ### Evitar anidación profunda usando return tempranamente (parte 2) 232 | 233 | **Mal:** 234 | 235 | ```php 236 | function fibonacci(int $n) 237 | { 238 | if ($n < 50) { 239 | if ($n !== 0) { 240 | if ($n !== 1) { 241 | return fibonacci($n - 1) + fibonacci($n - 2); 242 | } else { 243 | return 1; 244 | } 245 | } else { 246 | return 0; 247 | } 248 | } else { 249 | return 'Not supported'; 250 | } 251 | } 252 | ``` 253 | 254 | **Bien:** 255 | 256 | ```php 257 | function fibonacci(int $n): int 258 | { 259 | if ($n === 0 || $n === 1) { 260 | return $n; 261 | } 262 | 263 | if ($n > 50) { 264 | throw new \Exception('Not supported'); 265 | } 266 | 267 | return fibonacci($n - 1) + fibonacci($n - 2); 268 | } 269 | ``` 270 | 271 | **[⬆Volver](#tabla-de-contenidos)** 272 | 273 | ### Evitar mapas mentales 274 | 275 | No fuerces al lector de tu código a traducir lo que significa una variable. 276 | Explicito es mejor que implícito. 277 | 278 | **Mal:** 279 | 280 | ```php 281 | $l = ['Austin', 'New York', 'San Francisco']; 282 | 283 | for ($i = 0; $i < count($l); $i++) { 284 | $li = $l[$i]; 285 | doStuff(); 286 | doSomeOtherStuff(); 287 | // ... 288 | // ... 289 | // ... 290 | // Espera, ¿Para qué era `$li`? 291 | dispatch($li); 292 | } 293 | ``` 294 | 295 | **Bien:** 296 | 297 | ```php 298 | $locations = ['Austin', 'New York', 'San Francisco']; 299 | 300 | foreach ($locations as $location) { 301 | doStuff(); 302 | doSomeOtherStuff(); 303 | // ... 304 | // ... 305 | // ... 306 | dispatch($location); 307 | } 308 | ``` 309 | 310 | **[⬆Volver](#tabla-de-contenidos)** 311 | 312 | ### No agregar contexto innecesario 313 | 314 | Si el nombre de tu clase/objeto te dice algo, no lo repitas en el nombre del atributo. 315 | 316 | 317 | **Mal:** 318 | 319 | ```php 320 | class Car 321 | { 322 | public $carMake; 323 | public $carModel; 324 | public $carColor; 325 | 326 | //... 327 | } 328 | ``` 329 | 330 | **Bien:** 331 | 332 | ```php 333 | class Car 334 | { 335 | public $make; 336 | public $model; 337 | public $color; 338 | 339 | //... 340 | } 341 | ``` 342 | 343 | **[⬆Volver](#tabla-de-contenidos)** 344 | 345 | ### Usar argumentos por defecto en lugar de cortocircuitos o condicionales 346 | 347 | **Nada bien:** 348 | 349 | No está bien porque `$breweryName` puede ser `NULL`. 350 | 351 | ```php 352 | function createMicrobrewery($breweryName = 'Hipster Brew Co.'): void 353 | { 354 |    // ... 355 | } 356 | ``` 357 | 358 | **Nada mal:** 359 | 360 | Está opción es más entendible que la versión anterior, pero es mejor controlar el valor de la variable. 361 | 362 | ```php 363 | function createMicrobrewery($name = null): void 364 | { 365 |    $breweryName = $name ?: 'Hipster Brew Co.'; 366 | // ... 367 | } 368 | ``` 369 | 370 | **Bien:** 371 | 372 | Si tienes instalado PHP 7+, entonces puedas usar [implicación de tipos](http://php.net/manual/en/functions.arguments.php#functions.arguments.type-declaration) y así asegurarte de que `$breweryName` no será `NULL`. 373 | 374 | ```php 375 | function createMicrobrewery(string $breweryName = 'Hipster Brew Co.'): void 376 | { 377 |    // ... 378 | } 379 | ``` 380 | 381 | **[⬆Volver](#tabla-de-contenidos)** 382 | 383 | ## Funciones 384 | 385 | ### Argumentos de la función (idealmente 2 o menos) 386 | 387 | Limitar la cantidad de parámetros de una función es increíblemente importante porque la hace más fácil de probar. Teniendo más de tres lleva a una explosión de combinaciones que tendrás que probar, argumento por argumento. 388 | 389 | El caso ideal es cero argumentos. Uno o dos argumentos están bien, pero tres deben ser evitados. Algo más debe ser dicho. Usualmente, si tienes más de dos argumentos significa que estás intentando hacer demasiado en la función. En los casos en que no, la mayoría del tiempo un objeto de alto nivel será suficiente como argumento. 390 | 391 | **Mal:** 392 | 393 | ```php 394 | function createMenu(string $title, string $body, string $buttonText, bool $cancellable): void 395 | { 396 | // ... 397 | } 398 | ``` 399 | 400 | **Bien:** 401 | 402 | ```php 403 | class MenuConfig 404 | { 405 | public $title; 406 | public $body; 407 | public $buttonText; 408 | public $cancellable = false; 409 | } 410 | 411 | $config = new MenuConfig(); 412 | $config->title = 'Foo'; 413 | $config->body = 'Bar'; 414 | $config->buttonText = 'Baz'; 415 | $config->cancellable = true; 416 | 417 | function createMenu(MenuConfig $config): void 418 | { 419 | // ... 420 | } 421 | ``` 422 | 423 | **[⬆Volver](#tabla-de-contenidos)** 424 | 425 | ### Las funciones deben hacer una cosa 426 | 427 | Esta es por lejos una de las más importantes reglas en ingeniería de software. Cuando las funciones hacen más de una cosa, se vuelven difíciles de hacer, probar y razonar sobre ellas. Cuando puedes aislar una función en una sola acción, ellas pueden ser refactorizadas con facilidad y tu código será mucho más limpio de leer. Si esta es la única regla que sigas de esta guía, estarás por sobre muchos desarrolladores. 428 | 429 | **Mal:** 430 | ```php 431 | function emailClients(array $clients): void 432 | { 433 | foreach ($clients as $client) { 434 | $clientRecord = $db->find($client); 435 | if ($clientRecord->isActive()) { 436 | email($client); 437 | } 438 | } 439 | } 440 | ``` 441 | 442 | **Bien:** 443 | 444 | ```php 445 | function emailClients(array $clients): void 446 | { 447 | $activeClients = activeClients($clients); 448 | array_walk($activeClients, 'email'); 449 | } 450 | 451 | function activeClients(array $clients): array 452 | { 453 | return array_filter($clients, 'isClientActive'); 454 | } 455 | 456 | function isClientActive(int $client): bool 457 | { 458 | $clientRecord = $db->find($client); 459 | 460 | return $clientRecord->isActive(); 461 | } 462 | ``` 463 | 464 | **[⬆Volver](#tabla-de-contenidos)** 465 | 466 | ### Los nombres de las funciones deben indicar lo que hacen 467 | 468 | **Mal:** 469 | 470 | ```php 471 | class Email 472 | { 473 | //... 474 | 475 | public function handle(): void 476 | { 477 | mail($this->to, $this->subject, $this->body); 478 | } 479 | } 480 | 481 | $message = new Email(...); 482 | // ¿Qué es esto? ¿Un manejador para los mensajes? ¿Ahora escribimos en un archivo? 483 | $message->handle(); 484 | ``` 485 | 486 | **Bien:** 487 | 488 | ```php 489 | class Email 490 | { 491 | //... 492 | 493 | public function send(): void 494 | { 495 | mail($this->to, $this->subject, $this->body); 496 | } 497 | } 498 | 499 | $message = new Email(...); 500 | // Limpio y obvio 501 | $message->send(); 502 | ``` 503 | 504 | **[⬆Volver](#tabla-de-contenidos)** 505 | 506 | ### Las funciones deben tener sólo un nivel de abstracción 507 | 508 | Cuando tienes más de un nivel de abstracción usualmente es porque tu función está haciendo demasiado. Separarlas en funciones lleva a la reutilización y facilita las pruebas. 509 | 510 | **Mal:** 511 | 512 | ```php 513 | function parseBetterJSAlternative(string $code): void 514 | { 515 | $regexes = [ 516 | // ... 517 | ]; 518 | 519 | $statements = explode(' ', $code); 520 | $tokens = []; 521 | foreach ($regexes as $regex) { 522 | foreach ($statements as $statement) { 523 | // ... 524 | } 525 | } 526 | 527 | $ast = []; 528 | foreach ($tokens as $token) { 529 | // lex... 530 | } 531 | 532 | foreach ($ast as $node) { 533 | // convertir... 534 | } 535 | } 536 | ``` 537 | 538 | **También mal:** 539 | 540 | Hemos separado algunas de las funcionalidades, pero la función `parseBetterJSAlternative()` todavía es muy compleja e imposible de probar. 541 | 542 | ```php 543 | function tokenize(string $code): array 544 | { 545 | $regexes = [ 546 | // ... 547 | ]; 548 | 549 | $statements = explode(' ', $code); 550 | $tokens = []; 551 | foreach ($regexes as $regex) { 552 | foreach ($statements as $statement) { 553 | $tokens[] = /* ... */; 554 | } 555 | } 556 | 557 | return $tokens; 558 | } 559 | 560 | function lexer(array $tokens): array 561 | { 562 | $ast = []; 563 | foreach ($tokens as $token) { 564 | $ast[] = /* ... */; 565 | } 566 | 567 | return $ast; 568 | } 569 | 570 | function parseBetterJSAlternative(string $code): void 571 | { 572 | $tokens = tokenize($code); 573 | $ast = lexer($tokens); 574 | foreach ($ast as $node) { 575 | // convertir... 576 | } 577 | } 578 | ``` 579 | 580 | **Bien:** 581 | 582 | Lo mejor es sacar las dependencias de la función `parseBetterJSAlternative()`. 583 | 584 | ```php 585 | class Tokenizer 586 | { 587 | public function tokenize(string $code): array 588 | { 589 | $regexes = [ 590 | // ... 591 | ]; 592 | 593 | $statements = explode(' ', $code); 594 | $tokens = []; 595 | foreach ($regexes as $regex) { 596 | foreach ($statements as $statement) { 597 | $tokens[] = /* ... */; 598 | } 599 | } 600 | 601 | return $tokens; 602 | } 603 | } 604 | 605 | class Lexer 606 | { 607 | public function lexify(array $tokens): array 608 | { 609 | $ast = []; 610 | foreach ($tokens as $token) { 611 | $ast[] = /* ... */; 612 | } 613 | 614 | return $ast; 615 | } 616 | } 617 | 618 | class BetterJSAlternative 619 | { 620 | private $tokenizer; 621 | private $lexer; 622 | 623 | public function __construct(Tokenizer $tokenizer, Lexer $lexer) 624 | { 625 | $this->tokenizer = $tokenizer; 626 | $this->lexer = $lexer; 627 | } 628 | 629 | public function parse(string $code): void 630 | { 631 | $tokens = $this->tokenizer->tokenize($code); 632 | $ast = $this->lexer->lexify($tokens); 633 | foreach ($ast as $node) { 634 | // convertir... 635 | } 636 | } 637 | } 638 | ``` 639 | 640 | **[⬆Volver](#tabla-de-contenidos)** 641 | 642 | ### No usar banderas como parámetros de funciones 643 | 644 | Las banderas le dicen al usuario que la función hace más de una cosa. Las funciones deben hacer sólo una. Divide tus funciones si ellas siguen diferentes caminos basados en un valor booleano. 645 | 646 | **Mal:** 647 | 648 | ```php 649 | function createFile(string $name, bool $temp = false): void 650 | { 651 | if ($temp) { 652 | touch('./temp/'.$name); 653 | } else { 654 | touch($name); 655 | } 656 | } 657 | ``` 658 | 659 | **Bien:** 660 | 661 | ```php 662 | function createFile(string $name): void 663 | { 664 | touch($name); 665 | } 666 | 667 | function createTempFile(string $name): void 668 | { 669 | touch('./temp/'.$name); 670 | } 671 | ``` 672 | 673 | **[⬆Volver](#tabla-de-contenidos)** 674 | 675 | ### Evitar efectos secundarios 676 | 677 | Una función produce un efecto secundario si hace algo más que tomar un valor y devolver otros. Un efecto secundario puede ser escribir en un archivo, modificar alguna variable global, o accidentalmente darle todo tu dinero a un extraño. 678 | 679 | Ahora, ocasionalmente necesitaras los efectos secundarios en un programa. Como los ejemplos anteriores, necesitarás escribir en un archivo. Lo que quieres hacer en esos casos es centralizar donde realizarlos. No tengas muchas funciones y clases que escriban un archivo en particular. Ten un servicio que lo haga. Uno y sólo uno. 680 | 681 | El punto principal es evitar trampas comunes como compartir estados entre objetos sin alguna estructura, usar tipos de datos mutables que puedan ser escritos por cualquiera, y no centralizar donde el efecto paralelo ocurre. Si puedes hacerlo, serás más feliz que la vasta mayoría de los demás programadores. 682 | 683 | **Mal:** 684 | 685 | ```php 686 | // Variable global referenciada por la siguiente función. 687 | // Si tenemos otra función que use el mismo nombre, ahora será un arreglo y podría romperla. 688 | $name = 'Ryan McDermott'; 689 | 690 | function splitIntoFirstAndLastName(): void 691 | { 692 | global $name; 693 | 694 | $name = explode(' ', $name); 695 | } 696 | 697 | splitIntoFirstAndLastName(); 698 | 699 | var_dump($name); // ['Ryan', 'McDermott']; 700 | ``` 701 | 702 | **Bien:** 703 | 704 | ```php 705 | function splitIntoFirstAndLastName(string $name): array 706 | { 707 | return explode(' ', $name); 708 | } 709 | 710 | $name = 'Ryan McDermott'; 711 | $newName = splitIntoFirstAndLastName($name); 712 | 713 | var_dump($name); // 'Ryan McDermott'; 714 | var_dump($newName); // ['Ryan', 'McDermott']; 715 | ``` 716 | 717 | **[⬆Volver](#tabla-de-contenidos)** 718 | 719 | ### No escribir funciones globales 720 | 721 | Contaminar los globales es una mala práctica en muchos lenguajes porque puedes chocar con otra librería y el usuario de tu API podría no enterarse hasta obtener una excepción en producción. Pensemos en un ejemplo: qué pasaría si esperabas tener un arreglo de configuración. Podrías escribir una función global como `config()`, pero podría chocar con otra librería que haya intentado hacer lo mismo. 722 | 723 | **Mal:** 724 | 725 | ```php 726 | function config(): array 727 | { 728 | return [ 729 | 'foo' => 'bar', 730 | ] 731 | } 732 | ``` 733 | 734 | **Bien:** 735 | 736 | ```php 737 | class Configuration 738 | { 739 | private $configuration = []; 740 | 741 | public function __construct(array $configuration) 742 | { 743 | $this->configuration = $configuration; 744 | } 745 | 746 | public function get(string $key): ?string 747 | { 748 | return isset($this->configuration[$key]) ? $this->configuration[$key] : null; 749 | } 750 | } 751 | ``` 752 | 753 | Crea la variable `$configuration` con una instancia de la clase `Configuration` 754 | 755 | ```php 756 | $configuration = new Configuration([ 757 | 'foo' => 'bar', 758 | ]); 759 | ``` 760 | 761 | Y ahora puedes usar una instancia de la clase `Configuration` en tu aplicación. 762 | 763 | **[⬆Volver](#tabla-de-contenidos)** 764 | 765 | ### No usar el patrón Singleton 766 | 767 | Singleton es un [anti-patrón](https://en.wikipedia.org/wiki/Singleton_pattern). Citando a Brian Button: 768 | 1. Son usados generalmente como una **instancia global**, ¿Por qué eso es malo? Porque **escondes las dependencias** de tu aplicación en tu código, en lugar de exponerlas mediante interfaces. Hacer algo global para evitar pasarlo es una [hediondez de código](https://en.wikipedia.org/wiki/Code_smell). 769 | 2. Violan el [principio de la responsabilidad única](#principio-de-responsabilidad-única): en virtud del hecho de que **ellos controlan su propia creación y ciclo de vida**. 770 | 3. Inherentemente causan que el código esté estrechamente [acoplado](https://en.wikipedia.org/wiki/Coupling_%28computer_programming%29). Esto hace que muchas veces sean **difíciles de probar**. 771 | 4. Llevan su estado al ciclo de vida de la aplicación. Otro golpe a las pruebas porque **puedes terminar con una situación donde las pruebas necesitan ser ordenadas** lo cual es un gran no para las pruebas unitarias. ¿Por qué? Porque cada prueba unitaria debe hacerse independiente de la otra. 772 | 773 | [Misko Hevery](http://misko.hevery.com/about/) ha realizado unas reflexiones interesantes sobre el [origen del problema](http://misko.hevery.com/2008/08/25/root-cause-of-singletons/). 774 | 775 | **Mal:** 776 | 777 | ```php 778 | class DBConnection 779 | { 780 | private static $instance; 781 | 782 | private function __construct(string $dsn) 783 | { 784 | // ... 785 | } 786 | 787 | public static function getInstance(): DBConnection 788 | { 789 | if (self::$instance === null) { 790 | self::$instance = new self(); 791 | } 792 | 793 | return self::$instance; 794 | } 795 | 796 | // ... 797 | } 798 | 799 | $singleton = DBConnection::getInstance(); 800 | ``` 801 | 802 | **Bien:** 803 | 804 | ```php 805 | class DBConnection 806 | { 807 | public function __construct(string $dsn) 808 | { 809 | // ... 810 | } 811 | 812 | // ... 813 | } 814 | ``` 815 | Crea una instancia de la clase `DBConnection` y configúrala con [DSN](http://php.net/manual/en/pdo.construct.php#refsect1-pdo.construct-parameters). 816 | 817 | ```php 818 | $connection = new DBConnection($dsn); 819 | ``` 820 | 821 | Y ahora debes usar la instancia de `DBConnection` en tu aplicación. 822 | 823 | **[⬆Volver](#tabla-de-contenidos)** 824 | 825 | ### Encapsular condicionales 826 | 827 | **Mal:** 828 | 829 | ```php 830 | if ($article->state === 'published') { 831 | // ... 832 | } 833 | ``` 834 | 835 | **Bien:** 836 | 837 | ```php 838 | if ($article->isPublished()) { 839 | // ... 840 | } 841 | ``` 842 | 843 | **[⬆Volver](#tabla-de-contenidos)** 844 | 845 | ### Evitar condicionales negativos 846 | 847 | **Mal:** 848 | 849 | ```php 850 | function isDOMNodeNotPresent(\DOMNode $node): bool 851 | { 852 | // ... 853 | } 854 | 855 | if (!isDOMNodeNotPresent($node)) 856 | { 857 | // ... 858 | } 859 | ``` 860 | 861 | **Bien:** 862 | 863 | ```php 864 | function isDOMNodePresent(\DOMNode $node): bool 865 | { 866 | // ... 867 | } 868 | 869 | if (isDOMNodePresent($node)) { 870 | // ... 871 | } 872 | ``` 873 | 874 | **[⬆Volver](#tabla-de-contenidos)** 875 | 876 | ### Evitar condicionales 877 | 878 | Esta parece ser una tarea imposible. Al escuchar esto por primera vez, la mayoría de la gente dice, "¿cómo se supone que haré algo sin una declaración `if`?" La respuesta es que la mayoría de las veces puedes usar polimorfismo para lograr el mismo resultado. 879 | 880 | La segunda pregunta usualmente es, "bien, eso es genial, ¿pero cómo puedo hacerlo?" La respuesta es un concepto de código limpio que ya hemos aprendido: una función debe hacer sólo una cosa. Cuando tienes clases y funciones que usan declaraciones `if`, estás diciéndole al usuario que tu función hace más de una cosa. Recuerda, hacer sólo una cosa. 881 | 882 | **Mal:** 883 | 884 | ```php 885 | class Airplane 886 | { 887 | // ... 888 | 889 | public function getCruisingAltitude(): int 890 | { 891 | switch ($this->type) { 892 | case '777': 893 | return $this->getMaxAltitude() - $this->getPassengerCount(); 894 | case 'Air Force One': 895 | return $this->getMaxAltitude(); 896 | case 'Cessna': 897 | return $this->getMaxAltitude() - $this->getFuelExpenditure(); 898 | } 899 | } 900 | } 901 | ``` 902 | 903 | **Bien:** 904 | 905 | ```php 906 | interface Airplane 907 | { 908 | // ... 909 | 910 | public function getCruisingAltitude(): int; 911 | } 912 | 913 | class Boeing777 implements Airplane 914 | { 915 | // ... 916 | 917 | public function getCruisingAltitude(): int 918 | { 919 | return $this->getMaxAltitude() - $this->getPassengerCount(); 920 | } 921 | } 922 | 923 | class AirForceOne implements Airplane 924 | { 925 | // ... 926 | 927 | public function getCruisingAltitude(): int 928 | { 929 | return $this->getMaxAltitude(); 930 | } 931 | } 932 | 933 | class Cessna implements Airplane 934 | { 935 | // ... 936 | 937 | public function getCruisingAltitude(): int 938 | { 939 | return $this->getMaxAltitude() - $this->getFuelExpenditure(); 940 | } 941 | } 942 | ``` 943 | 944 | **[⬆Volver](#tabla-de-contenidos)** 945 | 946 | ### Evitar revisión de tipo (parte 1) 947 | 948 | PHP es un lenguaje no tipado, lo que quiere decir que tus funciones pueden tener cualquier tipo de argumento. 949 | Algunas veces habrás sentido esta libertad y te habrás tentado a hacer revisión de tipo en tus funciones. Hay muchas maneras de evitar tener que hacerlo. 950 | Lo primero es considerar la consistencia de las APIs. 951 | 952 | **Mal:** 953 | 954 | ```php 955 | function travelToTexas($vehicle): void 956 | { 957 | if ($vehicle instanceof Bicycle) { 958 | $vehicle->pedalTo(new Location('texas')); 959 | } elseif ($vehicle instanceof Car) { 960 | $vehicle->driveTo(new Location('texas')); 961 | } 962 | } 963 | ``` 964 | 965 | **Bien:** 966 | 967 | ```php 968 | function travelToTexas(Traveler $vehicle): void 969 | { 970 | $vehicle->travelTo(new Location('texas')); 971 | } 972 | ``` 973 | 974 | **[⬆Volver](#tabla-de-contenidos)** 975 | 976 | ### Evitar revisión de tipo (parte 2) 977 | 978 | Si estás trabajando con valores básicos primitivos como cadenas, enteros y arreglos; y estás usando PHP 7+ y no puedes usar polimorfismo pero aún necesitas realizar una revisión de tipo entonces puedes considerar [declaración de tipo](http://php.net/manual/en/functions.arguments.php#functions.arguments.type-declaration) o modo estricto. Esto provee tipado estático sobre la sintaxis estándar de PHP. 979 | El problema con hacer la revisión de tipo manualmente es que requerirá escribir texto adicional que dará una falsa "seguridad de tipado" que no compensará la perdida de legibilidad. Mantén tu código PHP limpio, escribe buenas pruebas, y realiza buenas revisiones. 980 | Dicho de otra forma, realiza todo lo que se ha recomendado pero usando la declaración de tipo estricto en PHP o el modo estricto. 981 | 982 | **Mal:** 983 | 984 | ```php 985 | function combine($val1, $val2): int 986 | { 987 | if (!is_numeric($val1) || !is_numeric($val2)) { 988 | throw new \Exception('Must be of type Number'); 989 | } 990 | 991 | return $val1 + $val2; 992 | } 993 | ``` 994 | 995 | **Bien:** 996 | 997 | ```php 998 | function combine(int $val1, int $val2): int 999 | { 1000 | return $val1 + $val2; 1001 | } 1002 | ``` 1003 | 1004 | **[⬆Volver](#tabla-de-contenidos)** 1005 | 1006 | ### Quitar código muerto 1007 | 1008 | El código muerto es tan malo como el código duplicado. No hay motivos para mantenerlo en tu código fuente. Si no está siendo llamado, ¡deshazte del! Siempre estará a salvo en tu versión histórica si aún lo necesitas. 1009 | 1010 | **Mal:** 1011 | 1012 | ```php 1013 | function oldRequestModule(string $url): void 1014 | { 1015 | // ... 1016 | } 1017 | 1018 | function newRequestModule(string $url): void 1019 | { 1020 | // ... 1021 | } 1022 | 1023 | $request = newRequestModule($requestUrl); 1024 | inventoryTracker('apples', $request, 'www.inventory-awesome.io'); 1025 | ``` 1026 | 1027 | **Bien:** 1028 | 1029 | ```php 1030 | function requestModule(string $url): void 1031 | { 1032 | // ... 1033 | } 1034 | 1035 | $request = requestModule($requestUrl); 1036 | inventoryTracker('apples', $request, 'www.inventory-awesome.io'); 1037 | ``` 1038 | 1039 | **[⬆Volver](#tabla-de-contenidos)** 1040 | 1041 | 1042 | ## Objetos y estructuras de datos 1043 | 1044 | ### Usar encapsulación de objetos 1045 | 1046 | En PHP puedes establecer métodos como `public`, `protected` y `private`. 1047 | Al utilizarlos, puedes controlar las modificaciones a las propiedades de un objeto. 1048 | 1049 | * Cuando quieras hacer algo más que obtener una propiedad de un objeto, no tienes que revisar y cambiar cada método de acceso en tu código fuente. 1050 | * Agrega una validación simple cuando haces `set`. 1051 | * Encapsula la representación interna. 1052 | * Facilita agregar registro y manejo de errores al obtener y colocar. 1053 | * Puedes sobrescribir la funcionalidad por defecto al heredar la clase. 1054 | * Puedes hacer carga diferida de las propiedades del objeto, por ejemplo, al obtenerlos desde un servidor. 1055 | 1056 | Adicionalmente, esto es parte del [principio abierto/cerrado](#principio-de-abiertocerrado). 1057 | 1058 | **Mal:** 1059 | 1060 | ```php 1061 | class BankAccount 1062 | { 1063 | public $balance = 1000; 1064 | } 1065 | 1066 | $bankAccount = new BankAccount(); 1067 | 1068 | // Comprar zapatos... 1069 | $bankAccount->balance -= 100; 1070 | ``` 1071 | 1072 | **Bien:** 1073 | 1074 | ```php 1075 | class BankAccount 1076 | { 1077 | private $balance; 1078 | 1079 | public function __construct(int $balance = 1000) 1080 | { 1081 | $this->balance = $balance; 1082 | } 1083 | 1084 | public function withdraw(int $amount): void 1085 | { 1086 | if ($amount > $this->balance) { 1087 | throw new \Exception('Amount greater than available balance.'); 1088 | } 1089 | 1090 | $this->balance -= $amount; 1091 | } 1092 | 1093 | public function deposit(int $amount): void 1094 | { 1095 | $this->balance += $amount; 1096 | } 1097 | 1098 | public function getBalance(): int 1099 | { 1100 | return $this->balance; 1101 | } 1102 | } 1103 | 1104 | $bankAccount = new BankAccount(); 1105 | 1106 | // Comprar zapatos... 1107 | $bankAccount->withdraw($shoesPrice); 1108 | 1109 | // Obtener saldo 1110 | $balance = $bankAccount->getBalance(); 1111 | ``` 1112 | 1113 | **[⬆Volver](#tabla-de-contenidos)** 1114 | 1115 | ### Hacer que los objetos tengan partes private/protected 1116 | 1117 | * Los métodos y propiedades configuradas como `public` son las más expuestas a los cambios, porque algún código externo puede fácilmente modificarlos pero no tienes control sobre qué es lo que se está modificando en verdad. **Modificaciones en clases son peligrosas para usuarios de una clase.** 1118 | * El modificador `protected` es tan peligroso como el `public`, porque están disponible en el ámbito de cualquier clase hija. Efectivamente esto significa que la diferencia entre public y protected es el mecanismo de acceso, pero la encapsulación garantiza que siga siendo la misma. **Modificaciones en clases son peligrosas para todas las clases descendientes.** 1119 | * El modificador `private` garantiza que el código es **peligroso de modificar sólo en los límites de una clase en particular** (estarás asegurado ante modificaciones y no tendrás un [efecto Jenga](http://www.urbandictionary.com/define.php?term=Jengaphobia&defid=2494196)). 1120 | 1121 | Por lo tanto, usar `private` por defecto y `public/protected` cuando quieras proveer acceso a clases externas. 1122 | 1123 | Para más información, puedes leer el [siguiente articulo](http://fabien.potencier.org/pragmatism-over-theory-protected-vs-private.html) escrito por [Fabien Potencier](https://github.com/fabpot). 1124 | 1125 | **Mal:** 1126 | 1127 | ```php 1128 | class Employee 1129 | { 1130 | public $name; 1131 | 1132 | public function __construct(string $name) 1133 | { 1134 | $this->name = $name; 1135 | } 1136 | } 1137 | 1138 | $employee = new Employee('John Doe'); 1139 | echo 'Employee name: '.$employee->name; // Nombre del empleado: John Doe 1140 | ``` 1141 | 1142 | **Bien:** 1143 | 1144 | ```php 1145 | class Employee 1146 | { 1147 | private $name; 1148 | 1149 | public function __construct(string $name) 1150 | { 1151 | $this->name = $name; 1152 | } 1153 | 1154 | public function getName(): string 1155 | { 1156 | return $this->name; 1157 | } 1158 | } 1159 | 1160 | $employee = new Employee('John Doe'); 1161 | echo 'Employee name: '.$employee->getName(); // Nombre del empleado: John Doe 1162 | ``` 1163 | 1164 | **[⬆Volver](#tabla-de-contenidos)** 1165 | 1166 | ## Clases 1167 | 1168 | ### Preferir composición antes que herencia 1169 | 1170 | Como se ha dicho en [*Design Patterns*](https://en.wikipedia.org/wiki/Design_Patterns) de the Gang of Four, debes preferir composición sobre herencia cuando puedas hacerlo. Hay muchas buenas razones para usar herencia y muchas buenas razones para usar composición. El punto principal de esta máxima es que si tu mente instintivamente va hacia herencia, trata de pensar si la composición modela tu problema de mejor manera. En algunos casos si es posible. 1171 | 1172 | Debes preguntarte entonces, "¿cuándo debo usar herencia?" Eso depende del problema que tengas entre manos, pero hay una lista decente que indica cuando tiene más sentido usar herencia en lugar de composición. 1173 | 1174 | 1. Herencia representa una relación "es-un" y no una relación "tiene-un". (Humano->Animal vs. Usuario->DetallesDeUsuario). 1175 | 2. Puedes reutilizar código desde las clases padres. (Humanos pueden moverse como todos los animales). 1176 | 3. Puedes querer hacer cambios globales en las clases derivadas al cambiar una clase padre. (Cambiar el consumo de calorías de todos los animales cuando se mueven) 1177 | 1178 | **Mal:** 1179 | 1180 | ```php 1181 | class Employee 1182 | { 1183 | private $name; 1184 | private $email; 1185 | 1186 | public function __construct(string $name, string $email) 1187 | { 1188 | $this->name = $name; 1189 | $this->email = $email; 1190 | } 1191 | 1192 | // ... 1193 | } 1194 | 1195 | // Está mal porque los empleados "tienen" información de impuestos. 1196 | // EmployeeTaxData no es un tipo de Employee 1197 | 1198 | class EmployeeTaxData extends Employee 1199 | { 1200 | private $ssn; 1201 | private $salary; 1202 | 1203 | public function __construct(string $name, string $email, string $ssn, string $salary) 1204 | { 1205 | parent::__construct($name, $email); 1206 | 1207 | $this->ssn = $ssn; 1208 | $this->salary = $salary; 1209 | } 1210 | 1211 | // ... 1212 | } 1213 | ``` 1214 | 1215 | **Bien:** 1216 | 1217 | ```php 1218 | class EmployeeTaxData 1219 | { 1220 | private $ssn; 1221 | private $salary; 1222 | 1223 | public function __construct(string $ssn, string $salary) 1224 | { 1225 | $this->ssn = $ssn; 1226 | $this->salary = $salary; 1227 | } 1228 | 1229 | // ... 1230 | } 1231 | 1232 | class Employee 1233 | { 1234 | private $name; 1235 | private $email; 1236 | private $taxData; 1237 | 1238 | public function __construct(string $name, string $email) 1239 | { 1240 | $this->name = $name; 1241 | $this->email = $email; 1242 | } 1243 | 1244 | public function setTaxData(string $ssn, string $salary) 1245 | { 1246 | $this->taxData = new EmployeeTaxData($ssn, $salary); 1247 | } 1248 | 1249 | // ... 1250 | } 1251 | ``` 1252 | 1253 | **[⬆Volver](#tabla-de-contenidos)** 1254 | 1255 | ### Evitar interfaces fluidas 1256 | 1257 | Una [interfaz fluida](https://en.wikipedia.org/wiki/Fluent_interface) es una API orientada a objetos que ayuda a mejorar la legibilidad del código fuente al usar [encadenamientos de métodos](https://en.wikipedia.org/wiki/Method_chaining). 1258 | 1259 | Pueden haber algunos contextos, frecuentemente los constructores de objetos, donde este patrón reduce la cantidad de texto del código (por ejemplo el [Constructor de Simulación de PHPUnit](https://phpunit.de/manual/current/en/test-doubles.html) o [Constructor de Consultas Doctrine](http://docs.doctrine-project.org/projects/doctrine-dbal/en/latest/reference/query-builder.html)), pero a menudo esto viene con algunos costos: 1260 | 1261 | 1. Rompe la [encapsulación](https://en.wikipedia.org/wiki/Encapsulation_%28object-oriented_programming%29) 1262 | 2. Rompe los [decoradores](https://en.wikipedia.org/wiki/Decorator_pattern) 1263 | 3. Es difícil de [simular](https://en.wikipedia.org/wiki/Mock_object) en un conjunto de pruebas. 1264 | 4. Dificulta la lectura de los diffs de commits. 1265 | 1266 | Para más información, puedes leer el [articulo completo](https://ocramius.github.io/blog/fluent-interfaces-are-evil/) escrito por [Marco Pivetta](https://github.com/Ocramius). 1267 | 1268 | **Mal:** 1269 | 1270 | ```php 1271 | class Car 1272 | { 1273 | private $make = 'Honda'; 1274 | private $model = 'Accord'; 1275 | private $color = 'white'; 1276 | 1277 | public function setMake(string $make): self 1278 | { 1279 | $this->make = $make; 1280 | 1281 | // Observación: Retornará esto para encadenar. 1282 | return $this; 1283 | } 1284 | 1285 | public function setModel(string $model): self 1286 | { 1287 | $this->model = $model; 1288 | 1289 | // Observación: Retornará esto para encadenar. 1290 | return $this; 1291 | } 1292 | 1293 | public function setColor(string $color): self 1294 | { 1295 | $this->color = $color; 1296 | 1297 | // Observación: Retornará esto para encadenar. 1298 | return $this; 1299 | } 1300 | 1301 | public function dump(): void 1302 | { 1303 | var_dump($this->make, $this->model, $this->color); 1304 | } 1305 | } 1306 | 1307 | $car = (new Car()) 1308 | ->setColor('pink') 1309 | ->setMake('Ford') 1310 | ->setModel('F-150') 1311 | ->dump(); 1312 | ``` 1313 | 1314 | **Bien:** 1315 | 1316 | ```php 1317 | class Car 1318 | { 1319 | private $make = 'Honda'; 1320 | private $model = 'Accord'; 1321 | private $color = 'white'; 1322 | 1323 | public function setMake(string $make): void 1324 | { 1325 | $this->make = $make; 1326 | } 1327 | 1328 | public function setModel(string $model): void 1329 | { 1330 | $this->model = $model; 1331 | } 1332 | 1333 | public function setColor(string $color): void 1334 | { 1335 | $this->color = $color; 1336 | } 1337 | 1338 | public function dump(): void 1339 | { 1340 | var_dump($this->make, $this->model, $this->color); 1341 | } 1342 | } 1343 | 1344 | $car = new Car(); 1345 | $car->setColor('pink'); 1346 | $car->setMake('Ford'); 1347 | $car->setModel('F-150'); 1348 | $car->dump(); 1349 | ``` 1350 | 1351 | **[⬆Volver](#tabla-de-contenidos)** 1352 | 1353 | ## SOLID 1354 | 1355 | **SOLID** es un acrónimo mnemotécnico creado por Michael Feathers para los primeros cinco principios nombrados por Robert Martin, lo que significa los cinco principios básicos de la programación y diseño orientado a objetos. 1356 | 1357 | * [S: Principio de responsabilidad única (SRP)](#principio-de-responsabilidad-única) 1358 | * [O: Principio de abierto/cerrado (OCP)](#principio-de-abiertocerrado) 1359 | * [L: Principio de la sustitución de Liskov (LSP)](#principio-de-la-sustitución-de-liskov) 1360 | * [I: Principio de la segregación de la interfaz (ISP)](#principio-de-segregación-de-la-interfaz) 1361 | * [D: Principio de la inversión de dependencia (DIP)](#principio-de-la-inversión-de-dependencia) 1362 | 1363 | ### Principio de responsabilidad única 1364 | 1365 | Como se ha dicho en Clean Code, "No debe haber nunca más de un motivo para que una clase cambie". Es tentador empaquetar una clase con muchas funcionalidades, como si sólo pudieras llevar una maleta en tu vuelo. El problema es que esa clase no será conceptualmente cohesiva y te dará muchas razones para cambiarla. Minimizar la cantidad de veces que necesitas realizar cambios a una clase es importante. Es importante porque hay demasiadas funcionalidades en una sola clase y modificas una parte de ella, será difícil entender cómo afectará a otros módulos que dependan de ella en tu código fuente. 1366 | 1367 | **Mal:** 1368 | 1369 | ```php 1370 | class UserSettings 1371 | { 1372 | private $user; 1373 | 1374 | public function __construct(User $user) 1375 | { 1376 | $this->user = $user; 1377 | } 1378 | 1379 | public function changeSettings(array $settings): void 1380 | { 1381 | if ($this->verifyCredentials()) { 1382 | // ... 1383 | } 1384 | } 1385 | 1386 | private function verifyCredentials(): bool 1387 | { 1388 | // ... 1389 | } 1390 | } 1391 | ``` 1392 | 1393 | **Bien:** 1394 | 1395 | ```php 1396 | class UserAuth 1397 | { 1398 | private $user; 1399 | 1400 | public function __construct(User $user) 1401 | { 1402 | $this->user = $user; 1403 | } 1404 | 1405 | public function verifyCredentials(): bool 1406 | { 1407 | // ... 1408 | } 1409 | } 1410 | 1411 | class UserSettings 1412 | { 1413 | private $user; 1414 | private $auth; 1415 | 1416 | public function __construct(User $user) 1417 | { 1418 | $this->user = $user; 1419 | $this->auth = new UserAuth($user); 1420 | } 1421 | 1422 | public function changeSettings(array $settings): void 1423 | { 1424 | if ($this->auth->verifyCredentials()) { 1425 | // ... 1426 | } 1427 | } 1428 | } 1429 | ``` 1430 | 1431 | **[⬆Volver](#tabla-de-contenidos)** 1432 | 1433 | ### Principio de abierto/cerrado 1434 | 1435 | Como dijo Bertrand Meyer, "las entidades de software (clases, módulos, funciones, etc) deben ser abiertas para ser extendidas, pero cerradas para modificarlas." ¿Qué significa esto? Este principio establece básicamente que puedes permitir a los usuarios agregar nuevas funcionalidades pero sin cambiar el código existente. 1436 | 1437 | **Mal:** 1438 | 1439 | ```php 1440 | abstract class Adapter 1441 | { 1442 | protected $name; 1443 | 1444 | public function getName(): string 1445 | { 1446 | return $this->name; 1447 | } 1448 | } 1449 | 1450 | class AjaxAdapter extends Adapter 1451 | { 1452 | public function __construct() 1453 | { 1454 | parent::__construct(); 1455 | 1456 | $this->name = 'ajaxAdapter'; 1457 | } 1458 | } 1459 | 1460 | class NodeAdapter extends Adapter 1461 | { 1462 | public function __construct() 1463 | { 1464 | parent::__construct(); 1465 | 1466 | $this->name = 'nodeAdapter'; 1467 | } 1468 | } 1469 | 1470 | class HttpRequester 1471 | { 1472 | private $adapter; 1473 | 1474 | public function __construct(Adapter $adapter) 1475 | { 1476 | $this->adapter = $adapter; 1477 | } 1478 | 1479 | public function fetch(string $url): Promise 1480 | { 1481 | $adapterName = $this->adapter->getName(); 1482 | 1483 | if ($adapterName === 'ajaxAdapter') { 1484 | return $this->makeAjaxCall($url); 1485 | } elseif ($adapterName === 'httpNodeAdapter') { 1486 | return $this->makeHttpCall($url); 1487 | } 1488 | } 1489 | 1490 | private function makeAjaxCall(string $url): Promise 1491 | { 1492 | // Solicitar y retornar una promesa 1493 | } 1494 | 1495 | private function makeHttpCall(string $url): Promise 1496 | { 1497 | // Solicitar y retornar una promesa 1498 | } 1499 | } 1500 | ``` 1501 | 1502 | **Bien:** 1503 | 1504 | ```php 1505 | interface Adapter 1506 | { 1507 | public function request(string $url): Promise; 1508 | } 1509 | 1510 | class AjaxAdapter implements Adapter 1511 | { 1512 | public function request(string $url): Promise 1513 | { 1514 | // Solicitar y retornar una promesa 1515 | } 1516 | } 1517 | 1518 | class NodeAdapter implements Adapter 1519 | { 1520 | public function request(string $url): Promise 1521 | { 1522 | // Solicitar y retornar una promesa 1523 | } 1524 | } 1525 | 1526 | class HttpRequester 1527 | { 1528 | private $adapter; 1529 | 1530 | public function __construct(Adapter $adapter) 1531 | { 1532 | $this->adapter = $adapter; 1533 | } 1534 | 1535 | public function fetch(string $url): Promise 1536 | { 1537 | return $this->adapter->request($url); 1538 | } 1539 | } 1540 | ``` 1541 | 1542 | **[⬆Volver](#tabla-de-contenidos)** 1543 | 1544 | ### Principio de la sustitución de Liskov 1545 | 1546 | Este es un término aterrador para un concepto muy simple. Se define formalmente como "Si S es un subtipo de T, entonces los objetos de tipo T pueden ser reemplazados con objetos de tipo S (es decir, objetos de tipo S pueden sustituir objetos de tipo T) sin alterar alguna propiedad deseable de ese programa (exactitud, tarea realizada, etc)." Esa es una definición aún más aterradora. 1547 | 1548 | La mejor explicación para esto es que si tienes una clase padre y una clase hija, entonces la clase padre y la clase hija pueden intercambiarse sin obtener resultados incorrectos. Esto puede ser confuso, así que veamos el clásico ejemplo del Cuadrado-Rectángulo. Matemáticamente, un cuadrado es un Rectángulo, pero si tu modelo está usando la relación "es-un" por herencia, rápidamente estarás en problemas. 1549 | 1550 | **Mal:** 1551 | 1552 | ```php 1553 | class Rectangle 1554 | { 1555 | protected $width = 0; 1556 | protected $height = 0; 1557 | 1558 | public function render(int $area): void 1559 | { 1560 | // ... 1561 | } 1562 | 1563 | public function setWidth(int $width): void 1564 | { 1565 | $this->width = $width; 1566 | } 1567 | 1568 | public function setHeight(int $height): void 1569 | { 1570 | $this->height = $height; 1571 | } 1572 | 1573 | public function getArea(): int 1574 | { 1575 | return $this->width * $this->height; 1576 | } 1577 | } 1578 | 1579 | class Square extends Rectangle 1580 | { 1581 | public function setWidth(int $width): void 1582 | { 1583 | $this->width = $this->height = $width; 1584 | } 1585 | 1586 | public function setHeight(int $height): void 1587 | { 1588 | $this->width = $this->height = $height; 1589 | } 1590 | } 1591 | 1592 | /** 1593 | * @param Rectangle[] $rectangles 1594 | */ 1595 | function renderLargeRectangles(array $rectangles): void 1596 | { 1597 | foreach ($rectangles as $rectangle) { 1598 | $rectangle->setWidth(4); 1599 | $rectangle->setHeight(5); 1600 | $area = $rectangle->getArea(); // MAL: Retornará 25 para cuadrados y debería ser 20 1601 | $rectangle->render($area); 1602 | } 1603 | } 1604 | 1605 | $rectangles = [new Rectangle(), new Rectangle(), new Square()]; 1606 | renderLargeRectangles($rectangles); 1607 | ``` 1608 | 1609 | **Bien:** 1610 | 1611 | ```php 1612 | abstract class Shape 1613 | { 1614 | protected $width = 0; 1615 | protected $height = 0; 1616 | 1617 | abstract public function getArea(): int; 1618 | 1619 | public function render(int $area): void 1620 | { 1621 | // ... 1622 | } 1623 | } 1624 | 1625 | class Rectangle extends Shape 1626 | { 1627 | public function setWidth(int $width): void 1628 | { 1629 | $this->width = $width; 1630 | } 1631 | 1632 | public function setHeight(int $height): void 1633 | { 1634 | $this->height = $height; 1635 | } 1636 | 1637 | public function getArea(): int 1638 | { 1639 | return $this->width * $this->height; 1640 | } 1641 | } 1642 | 1643 | class Square extends Shape 1644 | { 1645 | private $length = 0; 1646 | 1647 | public function setLength(int $length): void 1648 | { 1649 | $this->length = $length; 1650 | } 1651 | 1652 | public function getArea(): int 1653 | { 1654 | return pow($this->length, 2); 1655 | } 1656 | } 1657 | 1658 | /** 1659 | * @param Rectangle[] $rectangles 1660 | */ 1661 | function renderLargeRectangles(array $rectangles): void 1662 | { 1663 | foreach ($rectangles as $rectangle) { 1664 | if ($rectangle instanceof Square) { 1665 | $rectangle->setLength(5); 1666 | } elseif ($rectangle instanceof Rectangle) { 1667 | $rectangle->setWidth(4); 1668 | $rectangle->setHeight(5); 1669 | } 1670 | 1671 | $area = $rectangle->getArea(); 1672 | $rectangle->render($area); 1673 | } 1674 | } 1675 | 1676 | $shapes = [new Rectangle(), new Rectangle(), new Square()]; 1677 | renderLargeRectangles($shapes); 1678 | ``` 1679 | 1680 | **[⬆Volver](#tabla-de-contenidos)** 1681 | 1682 | ### Principio de segregación de la interfaz 1683 | 1684 | Este principio establece que "Los clientes no deben forzar la dependencia sobre interfaces que no utilizan". 1685 | 1686 | Un buen ejemplo a considerar para demostrar este principio son las clases que requieren objetos de configuración grandes. No requerir clientes con una gran cantidad de opciones de configuración es beneficioso, porque la mayoría del tiempo no necesitará todas las configuraciones. Hacerlas opcional ayuda a prevenir una "interfaz pesada". 1687 | 1688 | **Mal:** 1689 | 1690 | ```php 1691 | interface Employee 1692 | { 1693 | public function work(): void; 1694 | 1695 | public function eat(): void; 1696 | } 1697 | 1698 | class Human implements Employee 1699 | { 1700 | public function work(): void 1701 | { 1702 | // ....trabajando 1703 | } 1704 | 1705 | public function eat(): void 1706 | { 1707 | // ...... comiendo en la hora de almuerzo 1708 | } 1709 | } 1710 | 1711 | class Robot implements Employee 1712 | { 1713 | public function work(): void 1714 | { 1715 | //.... trabajando mucho más 1716 | } 1717 | 1718 | public function eat(): void 1719 | { 1720 | //.... los robots no pueden comer, pero debe implementarse este método 1721 | } 1722 | } 1723 | ``` 1724 | 1725 | **Bien:** 1726 | 1727 | No todos los trabajadores son empleados, pero cada empleado es un trabajador. 1728 | 1729 | ```php 1730 | interface Workable 1731 | { 1732 | public function work(): void; 1733 | } 1734 | 1735 | interface Feedable 1736 | { 1737 | public function eat(): void; 1738 | } 1739 | 1740 | interface Employee extends Feedable, Workable 1741 | { 1742 | } 1743 | 1744 | class Human implements Employee 1745 | { 1746 | public function work(): void 1747 | { 1748 | // ....trabajando 1749 | } 1750 | 1751 | public function eat(): void 1752 | { 1753 | //.... comiendo en la hora de almuerzo 1754 | } 1755 | } 1756 | 1757 | // Los robots solo pueden trabajar 1758 | class Robot implements Workable 1759 | { 1760 | public function work(): void 1761 | { 1762 | // ....trabajando 1763 | } 1764 | } 1765 | ``` 1766 | 1767 | **[⬆Volver](#tabla-de-contenidos)** 1768 | 1769 | ### Principio de la inversión de dependencia 1770 | 1771 | Este principio establece dos cosas esenciales: 1772 | 1. Los módulos de alto nivel no deben depender de los de bajo nivel. Ambos deben depender de abstracciones. 1773 | 2. Las abstracciones no deben depender de detalles. Los detalles deben depender de abstracciones. 1774 | 1775 | Esto puede ser difícil de entender al comienzo, pero si has trabajado con frameworks de PHP (como Symfony), habrás visto alguna implementación de este principio en forma de Inyección de Dependencia (DI). Cuando no hay conceptos idénticos, este principio mantiene en conocimiento a los módulos de alto nivel sobre los módulos de bajo nivel y los configura. 1776 | Esto se logra mediante la inyección de dependencia. Un gran beneficio de esto es la reducción del acoplamiento entre módulos. El acoplamiento es un patrón de desarrollo muy malo porque hace que el código sea difícil de refactorizar. 1777 | 1778 | **Mal:** 1779 | 1780 | ```php 1781 | class Employee 1782 | { 1783 | public function work(): void 1784 | { 1785 | // ....trabajando 1786 | } 1787 | } 1788 | 1789 | class Robot extends Employee 1790 | { 1791 | public function work(): void 1792 | { 1793 | //.... trabajando mucho más 1794 | } 1795 | } 1796 | 1797 | class Manager 1798 | { 1799 | private $employee; 1800 | 1801 | public function __construct(Employee $employee) 1802 | { 1803 | $this->employee = $employee; 1804 | } 1805 | 1806 | public function manage(): void 1807 | { 1808 | $this->employee->work(); 1809 | } 1810 | } 1811 | ``` 1812 | 1813 | **Bien:** 1814 | 1815 | ```php 1816 | interface Employee 1817 | { 1818 | public function work(): void; 1819 | } 1820 | 1821 | class Human implements Employee 1822 | { 1823 | public function work(): void 1824 | { 1825 | // ....trabajando 1826 | } 1827 | } 1828 | 1829 | class Robot implements Employee 1830 | { 1831 | public function work(): void 1832 | { 1833 | //.... trabajando mucho más 1834 | } 1835 | } 1836 | 1837 | class Manager 1838 | { 1839 | private $employee; 1840 | 1841 | public function __construct(Employee $employee) 1842 | { 1843 | $this->employee = $employee; 1844 | } 1845 | 1846 | public function manage(): void 1847 | { 1848 | $this->employee->work(); 1849 | } 1850 | } 1851 | ``` 1852 | 1853 | **[⬆Volver](#tabla-de-contenidos)** 1854 | 1855 | ## No te repitas 1856 | 1857 | Intenta observar el principio [no te repitas.](https://en.wikipedia.org/wiki/Don%27t_repeat_yourself) 1858 | 1859 | Haz tu mejor esfuerzo en evitar código duplicado. Duplicar código está mal porque significa que hay más de un lugar para modificar algo si necesitas cambiar alguna lógica. 1860 | 1861 | Imagina si tienes un restaurant y mantienes seguimiento de tu inventario: todos tus tomates, cebollas, ajos, especias, etc. Si tienes múltiples listas que mantienen esto, entonces tienes que actualizarlas cuando sirves un plato con tomates en él. Si solo tienes una lista, ¡Hay un solo lugar que actualizar! 1862 | 1863 | A menudo tienes código duplicado porque tienes dos o más cosas ligeramente diferentes, que comparten mucho en común, pero sus diferencias te fuerzan a tener dos o más funciones separadas haciendo mucho de lo mismo. Remover código duplicado significa crear una abstracción que puedan manejar diferentes conjuntos de cosas en una función/modulo/clase. 1864 | 1865 | Lograr una correcta abstracción es crítico, esto porque deberás seguir los principios SOLID dispuestos en la sección [clases](#clases). Malas abstracciones pueden ser peor que el código duplicado, ¡así que ten cuidado! Dicho esto, si puedes hacer buenas abstracciones, ¡Hazlo! No te repitas, de otra manera te encontrarás actualizando muchos lugares cuando necesites cambiar una cosa. 1866 | 1867 | **Mal:** 1868 | 1869 | ```php 1870 | function showDeveloperList(array $developers): void 1871 | { 1872 | foreach ($developers as $developer) { 1873 | $expectedSalary = $developer->calculateExpectedSalary(); 1874 | $experience = $developer->getExperience(); 1875 | $githubLink = $developer->getGithubLink(); 1876 | $data = [ 1877 | $expectedSalary, 1878 | $experience, 1879 | $githubLink 1880 | ]; 1881 | 1882 | render($data); 1883 | } 1884 | } 1885 | 1886 | function showManagerList(array $managers): void 1887 | { 1888 | foreach ($managers as $manager) { 1889 | $expectedSalary = $manager->calculateExpectedSalary(); 1890 | $experience = $manager->getExperience(); 1891 | $githubLink = $manager->getGithubLink(); 1892 | $data = [ 1893 | $expectedSalary, 1894 | $experience, 1895 | $githubLink 1896 | ]; 1897 | 1898 | render($data); 1899 | } 1900 | } 1901 | ``` 1902 | 1903 | **Bien:** 1904 | 1905 | ```php 1906 | function showList(array $employees): void 1907 | { 1908 | foreach ($employees as $employee) { 1909 | $expectedSalary = $employee->calculateExpectedSalary(); 1910 | $experience = $employee->getExperience(); 1911 | $githubLink = $employee->getGithubLink(); 1912 | $data = [ 1913 | $expectedSalary, 1914 | $experience, 1915 | $githubLink 1916 | ]; 1917 | 1918 | render($data); 1919 | } 1920 | } 1921 | ``` 1922 | 1923 | **Muy bien:** 1924 | 1925 | Es mejor usar una versión compacta del código. 1926 | 1927 | ```php 1928 | function showList(array $employees): void 1929 | { 1930 | foreach ($employees as $employee) { 1931 | render([ 1932 | $employee->calculateExpectedSalary(), 1933 | $employee->getExperience(), 1934 | $employee->getGithubLink() 1935 | ]); 1936 | } 1937 | } 1938 | ``` 1939 | 1940 | **[⬆Volver](#tabla-de-contenidos)** 1941 | 1942 | ## Traducciones 1943 | 1944 | Disponible en muchos otros idiomas: 1945 | 1946 | * :cn: **Chino:** 1947 | * [php-cpm/clean-code-php](https://github.com/php-cpm/clean-code-php) 1948 | * :ru: **Ruso:** 1949 | * [peter-gribanov/clean-code-php](https://github.com/peter-gribanov/clean-code-php) 1950 | * :brazil: **Portugues:** 1951 | * [fabioars/clean-code-php](https://github.com/fabioars/clean-code-php) 1952 | * [jeanjar/clean-code-php](https://github.com/jeanjar/clean-code-php/tree/pt-br) 1953 | * :thailand: **Tailandes:** 1954 | * [panuwizzle/clean-code-php](https://github.com/panuwizzle/clean-code-php) 1955 | 1956 | **[⬆Volver](#tabla-de-contenidos)** 1957 | --------------------------------------------------------------------------------