"Código limpio" en Laravel: seis consejos prácticos con ejemplos
- Publicado el 26 agosto, 2024
- Palabras: 2585
El código limpio es algo a lo que aspiran todos los desarrolladores, ¿no? Pero, ¿qué significa REALMENTE? En este tutorial, veremos seis consejos prácticos para escribir código limpio en Laravel. El objetivo general aquí es enviar código que sea fácil de entender y mantener para los futuros desarrolladores (incluidos nosotros mismos). Entonces, ¿cuáles son los principios principales aplicados a los proyectos de Laravel?

#Consejo 1. Nombrar cosas: significado y convenciones
Hay un viejo dicho de Phil Karlton:
"En informática solo hay dos cosas difíciles: invalidar la memoria caché y nombrar cosas".
Sí, a menudo nos resulta difícil entender el código de alguien porque las variables, funciones, clases y métodos están mal redactados.
Cuesta tiempo y esfuerzo entender lo que hace realmente el código.
Veamos un código de ejemplo:
public function create()
{
$aid = request('aid', setting('default.account'));
$std = request('std', Date::now()->firstOfMonth()->toDateString());
$etd = request('etd', Date::now()->endOfMonth()->toDateString());
$acc = Account::find($acc_id);
$c = $acc->currency;
$tl = $this->getTransactions($acc, $std, $etd);
$ob = $this->getOpeningBalance($acc, $std);
return view('banking.reconciliations.create', compact('acc', 'c', 'ob', 'tl'));
}
¿Qué te parece? ¿Puedes entender rápidamente cuáles son las variables?
{{ $c->symbol }} {{ $ob }}
¿Qué es $c? ¿Qué es $ob? No está claro, ¿verdad?
Y ahora, veamos las variables con nombres apropiados:
public function create()
{
$accountId = request('account_id', setting('default.account'));
$startedAt = request('started_at', Date::now()->firstOfMonth()->toDateString());
$endedAt = request('ended_at', Date::now()->endOfMonth()->toDateString());
$account = Account::find($accountId);
$currency = $account->currency;
$transactions = $this->getTransactions($account, $startedAt, $endedAt);
$openingBalance = $this->getOpeningBalance($account, $startedAt);
return view('banking.reconciliations.create', compact('account', 'currency', 'openingBalance', 'transactions'));
}
y en la vista
{{ $currency->symbol }} {{ $openingBalance }}
Ahora es mucho más fácil entender qué hace el código y el propósito de cada variable.
Esto también se aplica a las funciones y clases. Utilice nombres significativos que describan lo que hace la función o de qué es responsable la clase.
No tengas miedo de utilizar nombres largos, siempre que sean descriptivos. Es mejor tener un nombre descriptivo largo que uno corto y críptico.
Los caracteres son gratis, pero la capacidad cerebral no. Por ejemplo:
public function getTransactionsForUserWithCurrency(Account $account, string $started_at, string $ended_at)
{
//
}
En vez de:
public function transactions($a, $std, $etd)
{
// code here
}
Si se nombran de forma clara, cualquiera puede entender fácilmente lo que hace la función sin tener que mirar su implementación o documentación.
Por supuesto, también debes seguir las convenciones para nombrar. Ayudan a las personas a comprender tu código más rápido y evitan confusiones mientras trabajan en equipo. Algunos de los recursos que puedes usar:
- Convenciones de PHP
- Estándares PSR (Laravel sigue PSR-4, PSR-7, PSR-12)
- Convenciones generales
Hay muchos estilos diferentes por idioma o marco de trabajo disponibles para que usted elija. Lo más importante es ser coherente con las convenciones de nombres.
#Consejo 2. Principio de responsabilidad única: escriba código que haga UNA sola cosa
Si una clase o un método son responsables de muchas operaciones, los futuros desarrolladores pueden perderse y les resultará difícil realizar tareas de mantenimiento o corregir errores.
Veamos un ejemplo:
class UserController {
public function store(Request $request)
{
$this->validate($request, [
'name' => ['required'],
'email' => ['required','email'],
'password' => ['required'],
'country' => ['required'],
'city' => ['required'],
'address' => ['required'],
]);
$formatName = explode(' ', $request->input('name'));
$fullAddress = $request->input('address') . ', ' . $request->input('city') . ', ' . $request->input('country');
$user = User::create([
'first_name' => $formatName[0],
'last_name' => $formatName[1],
'email' => $request->input('email'),
'password' => bcrypt($request->input('password')),
'full_address' => $fullAddress,
'address' => $request->input('address'),
'country' => $request->input('country'),
'city' => $request->input('city'),
]);
$user->notify(new OnboardingEmail());
$user->balanceAccount()->create([
'balance' => 0,
]);
return redirect()->route('home');
}
}
Este código hace demasiadas cosas a la vez:
- Validación
- Formato del nombre
- Creación de un usuario
- Envío de un correo electrónico
- Creación de una cuenta de saldo
Intentemos refactorizarlo:
class UserController {
public function store(Request $request)
{
$this->validateRequest($request);
$user = $this->createUser($request);
$this->sendOnboardingEmail($user);
$this->createBalanceAccount($user);
return redirect()->route('home');
}
private function validateRequest(Request $request): void
{
$this->validate($request, [
'name' => ['required'],
'email' => ['required','email'],
'password' => ['required'],
'country' => ['required'],
'city' => ['required'],
'address' => ['required'],
]);
}
private function createUser(Request $request): User
{
$formatName = $this->formatName($request->input('name'));
return User::create([
'first_name' => $formatName[0],
'last_name' => $formatName[1],
'email' => $request->input('email'),
'password' => bcrypt($request->input('password')),
'full_address' => $this->createFullAddress($request),
'address' => $request->input('address'),
'country' => $request->input('country'),
'city' => $request->input('city'),
]);
}
private function formatName(string $name): array
{
return explode(' ', $request->input('name'));
}
private function createFullAddress(Request $request): string
{
return $request->input('address') . ', ' . $request->input('city') . ', ' . $request->input('country');
}
private function sendOnboardingEmail(User $user): void
{
$user->notify(new OnboardingEmail());
}
private function createBalanceAccount(User $user): void
{
$user->balanceAccount()->create([
'balance' => 0,
]);
}
}
Ahora, el código se divide en métodos más pequeños, cada uno responsable de una sola acción.
De esta manera, es más fácil entender lo que hace cada método. También es más fácil probar y depurar funciones por separado.
Por supuesto, este es un ejemplo simple y la creación de métodos privados en los controladores no es tan común. En aplicaciones del mundo real, tendrá acciones más complejas y creará clases de servicio/acción independientes. Aun así, el principio es el mismo: divida su código en métodos más pequeños, cada uno responsable de una sola acción.
#Consejo 3. Principio DRY: refactorizar código repetitivo
El principio DRY (Don't Repeat Yourself) alienta a los desarrolladores a reducir la repetición en el código.
Si el mismo código se repite dos veces, tendrá dos lugares para editar en caso de cambios o correcciones futuras. Existe un mayor riesgo de olvidarse de corregir uno de esos lugares.
Aquí hay un ejemplo en una aplicación de Laravel, que muestra un fragmento de código incorrecto que viola el principio DRY y un fragmento correcto que refactoriza el código para cumplir con DRY.
Fragmento de código incorrecto (antes de la refactorización DRY)
Consideremos un escenario en el que necesita actualizar el perfil de un usuario y el perfil de un administrador en dos controladores diferentes, pero la lógica es casi idéntica.
app/Http/Controllers/UserController.php
public function updateUserProfile(Request $request, $id)
{
$user = User::find($id);
$user->name = $request->input('name');
$user->email = $request->input('email');
$user->address = $request->input('address');
$user->phone = $request->input('phone');
$user->save();
return redirect()->back()->with('success', 'User profile updated successfully!');
}
app/Http/Controllers/AdminController.php
public function updateAdminProfile(Request $request, $id)
{
$admin = Admin::find($id);
$admin->name = $request->input('name');
$admin->email = $request->input('email');
$admin->address = $request->input('address');
$admin->phone = $request->input('phone');
$admin->save();
return redirect()->back()->with('success', 'Admin profile updated successfully!');
}
El problema: si necesita cambiar la forma en que se actualizan los perfiles (por ejemplo, agregar un nuevo campo o modificar la validación), deberá actualizar ambos métodos por separado, lo que aumenta el riesgo de errores.
Para aplicar el principio DRY, puede refactorizar el código moviendo la lógica repetida a un método reutilizable o Trait. En este caso, elegimos un Trait:
app/Traits/UpdatesProfiles.php
namespace App\Traits;
use Illuminate\Http\Request;
trait UpdatesProfiles
{
public function updateProfile(Request $request, $model)
{
$model->name = $request->input('name');
$model->email = $request->input('email');
$model->address = $request->input('address');
$model->phone = $request->input('phone');
$model->save();
return redirect()->back()->with('success', 'Profile updated successfully!');
}
}
Nuestros controladores ahora pueden usar este Trait:
app/Http/Controllers/UserController.php
namespace App\Http\Controllers;
use App\Models\User;
use Illuminate\Http\Request;
use App\Traits\UpdatesProfiles;
class UserController extends Controller
{
use UpdatesProfiles;
public function updateUserProfile(Request $request, $id)
{
$user = User::find($id);
return $this->updateProfile($request, $user);
}
}
app/Http/Controllers/AdminController.php
namespace App\Http\Controllers;
use App\Models\Admin;
use Illuminate\Http\Request;
use App\Traits\UpdatesProfiles;
class AdminController extends Controller
{
use UpdatesProfiles;
public function updateAdminProfile(Request $request, $id)
{
$admin = Admin::find($id);
return $this->updateProfile($request, $admin);
}
}
Ahora, cualquier cambio en la lógica de actualización de perfiles debe realizarse en un solo lugar.
Además, claridad Los controladores son más claros y están más enfocados en sus responsabilidades específicas, delegando la funcionalidad común a un rasgo.
Consejo 4. Estilo del código: formato y sangría
Este consejo resulta útil cuando trabajas en equipo. ¿Cuántas veces te has encontrado con código con formato y sangría inconsistentes? ¡Es una pesadilla! Sin mencionar que las diferencias de Git están llenas de cambios innecesarios.
Entonces, ¿cómo puedes evitarlo? ¡Siguiendo una guía de estilo coherente, por supuesto!
Laravel tiene su propia guía de estilo, pero también puedes usar otras populares como PSR-2 y PSR-12 o incluso crear la tuya propia.
Pero, ¿cómo hacemos cumplir esto? Bueno, esa es la parte difícil... Hay un factor humano: primero, todos los miembros del equipo deben estar de acuerdo en usarla.
En la actualidad, puedes forzar el uso de Laravel Pint (¡ahora viene en Laravel de manera predeterminada!) para formatear automáticamente tu código. Aquí te mostramos cómo puedes usarlo:
./vendor/bin/pint
Una vez hecho esto, todos los archivos tendrán un estilo coherente. No habrá más discusiones sobre posiciones, corchetes o espacios. ¡Todos estarán contentos!
¡Ah, y por supuesto, no más diferencias de Git con cambios innecesarios!
Consejo 5. Early Returns
Un breve resumen.
Una ganancia rápida para su código puede ser lo que se denomina Early Returns. Veamos un código antes de refactorizarlo:
if ($this->panel) {
if ($this->deployment) {
$this->deployment->addNewMessage('Generation Processing...' . PHP_EOL);
$this->panel->load([
'cruds',
]);
$service = new PanelService($this->panel);
foreach ($this->panel->panelFiles()->where('path', 'like', '%database/migrations%')->get() as $file) {
$service->deleteFile($file);
}
// ...
} else {
return;
}
} else {
return;
}
Y ahora compárelo con el código refactorizado:
if (! $this->panel || ! $this->deployment) {
return;
}
$this->deployment->addNewMessage('Generation Processing...'.PHP_EOL);
$this->panel->load([
'cruds',
]);
$service = new PanelService($this->panel);
foreach ($this->panel->panelFiles()->where('path', 'like', '%database/migrations%')->get() as $file) {
$service->deleteFile($file);
}
// ...
El código hace lo mismo pero con retornos anticipados. Evitar la anidación de sentencias if hace que el código sea más claro y fácil de leer.
Consejo 6. Evite los números y cadenas mágicos
A menudo dejamos números mágicos o cadenas en nuestro código, como estos:
public function getTransactions()
{
return Transaction::where('account_id', 1)->get();
}
// Or
public function getTransactions()
{
return Transaction::where('status', 'approved')->get();
}
Y esto está bien a corto plazo, pero ¿qué pasa si alguien comete un error tipográfico?
O, si necesitas cambiar el valor, debes buscar en todo el código base y reemplazarlo.
¿Qué puedes hacer entonces? Puedes usar constantes o Enums:
class Transaction {
const ACCOUNT_ID = 1;
const STATUS_APPROVED = 'approved';
public function getTransactions()
{
return Transaction::where('account_id', Transaction::ACCOUNT_ID)->get();
}
// Or
public function getTransactions()
{
return Transaction::where('status', Transaction::STATUS_APPROVED)->get();
}
}
De esta manera, puedes cambiar fácilmente el valor en un solo lugar y evitar errores tipográficos.
Por supuesto, también puedes crear clases independientes para las enumeraciones, pero la idea principal es evitar números mágicos y cadenas en tu código.
#Consejo final
Puede que esto suene raro en este artículo de este sitio web, pero tienes que usar Laravel. No, lo digo en serio: usa Laravel DE VERDAD, no luches contra el Framework.
Eric Barnes y Matt Stauffer hablaron sobre esto en un video. Eric le preguntó a Matt cuál es el mayor error que ve que cometen los desarrolladores de Laravel y la respuesta de Matt fue:
“Creer que sabes más que el Framework.”
Y esto es realmente cierto. Si empiezas a luchar contra el Framework y a reinventar la rueda con código personalizado, perderás muchas de sus integraciones y características.
Como resultado, es posible que (más adelante) tengas que escribir mucho código repetitivo que se puede hacer con una sola línea en Laravel.
Por lo tanto, nuestro consejo final es aprender más características de Laravel y usarlas. Tu código será más fácil de mantener y actualizar con el tiempo.
Fuente: Laravel Daily
Inicia la conversación
Hazte miembro de Antonio Jenaro para comenzar a comentar.
Regístrate ahora¿Ya estás registrado? Inicia sesión