Funciones
Argumentos de función (idealmente 2 o menos)
Limitar la cantidad de parámetros en las funciones es extremadamente importante porque facilita la prueba de las mismas. Tener más de tres parámetros conduce a una explosión combinatoria donde debes probar innumerables casos con cada argumento por separado.
El caso ideal es utilizar uno o dos argumentos, y se debe evitar tres si es posible. Cualquier cantidad superior debe consolidarse. Generalmente, si necesitas más de dos argumentos, la función está intentando hacer demasiado. En casos donde no sea así, un objeto de nivel superior suele ser suficiente como argumento.
Dado que JavaScript permite crear objetos al vuelo sin necesidad de estructuras de clase complejas, puedes usar un objeto cuando requieras múltiples argumentos.
Para clarificar las propiedades esperadas por la función, utiliza la sintaxis de desestructuración ES2015/ES6. Esto ofrece varias ventajas:
- Al revisar la firma de la función, las propiedades utilizadas son inmediatamente evidentes.
- Permite simular parámetros nombrados.
- La desestructuración también clona los valores primitivos específicos del objeto recibido como parámetro. Esto ayuda a prevenir efectos secundarios. Nota: los objetos y arrays desestructurados NO se clonan.
- Los linters pueden advertir sobre propiedades no utilizadas, lo cual es imposible sin desestructuración.
Malo:
function createMenu(title, body, buttonText, cancellable) {
// ...
}
createMenu("Foo", "Bar", "Baz", true);Bueno:
function createMenu({ title, body, buttonText, cancellable }) {
// ...
}
createMenu({
title: "Foo",
body: "Bar",
buttonText: "Baz",
cancellable: true
});Las funciones deben hacer una sola cosa
Esta es la regla más importante en ingeniería de software. Cuando las funciones realizan múltiples tareas, se dificulta su composición, prueba y comprensión. Al aislar cada función en una acción única, se facilita la refactorización y mejora la legibilidad. Si solo aplicas este principio, estarás por encima de muchos desarrolladores.
Malo:
function emailClients(clients) {
clients.forEach(client => {
const clientRecord = database.lookup(client);
if (clientRecord.isActive()) {
email(client);
}
});
}Bueno:
function emailActiveClients(clients) {
clients.filter(isActiveClient).forEach(email);
}
function isActiveClient(client) {
const clientRecord = database.lookup(client);
return clientRecord.isActive();
}Los nombres de función deben indicar su acción
Malo:
function addToDate(date, month) {
// ...
}
const date = new Date();
// It's hard to tell from the function name what is added
addToDate(date, 1);Bueno:
function addMonthToDate(month, date) {
// ...
}
const date = new Date();
addMonthToDate(1, date);Las funciones deben tener un solo nivel de abstracción
Múltiples niveles de abstracción indican que la función está sobrecargada. Dividir funciones mejora la reutilización y prueba del código.
Malo:
function parseBetterJSAlternative(code) {
const REGEXES = [
// ...
];
const statements = code.split(" ");
const tokens = [];
REGEXES.forEach(REGEX => {
statements.forEach(statement => {
// ...
});
});
const ast = [];
tokens.forEach(token => {
// lex...
});
ast.forEach(node => {
// parse...
});
}Bueno:
function parseBetterJSAlternative(code) {
const tokens = tokenize(code);
const syntaxTree = parse(tokens);
syntaxTree.forEach(node => {
// parse...
});
}
function tokenize(code) {
const REGEXES = [
// ...
];
const statements = code.split(" ");
const tokens = [];
REGEXES.forEach(REGEX => {
statements.forEach(statement => {
tokens.push(/* ... */);
});
});
return tokens;
}
function parse(tokens) {
const syntaxTree = [];
tokens.forEach(token => {
syntaxTree.push(/* ... */);
});
return syntaxTree;
}Elimina código duplicado
Evita a toda costa la duplicación de código. Esto genera múltiples puntos de modificación cuando necesitas cambiar lógica.
Imagina gestionar inventario en un restaurante: si tienes múltiples listas de tomates, deberás actualizar todas al servir un platillo. Con una sola lista, solo hay un lugar para modificar.
La duplicación suele surgir cuando elementos similares tienen diferencias que obligan a funciones separadas. Eliminarla requiere crear una abstracción que unifique estas variantes.
La correcta abstracción es crucial: sigue los principios SOLID de la sección Clases. Las abstracciones deficientes son peores que el código duplicado. Si logras una buena abstracción, ¡aplícala! La repetición te obligará a actualizar múltiples lugares ante cambios.
Malo:
function showDeveloperList(developers) {
developers.forEach(developer => {
const expectedSalary = developer.calculateExpectedSalary();
const experience = developer.getExperience();
const githubLink = developer.getGithubLink();
const data = {
expectedSalary,
experience,
githubLink
};
render(data);
});
}
function showManagerList(managers) {
managers.forEach(manager => {
const expectedSalary = manager.calculateExpectedSalary();
const experience = manager.getExperience();
const portfolio = manager.getMBAProjects();
const data = {
expectedSalary,
experience,
portfolio
};
render(data);
});
}Bueno:
function showEmployeeList(employees) {
employees.forEach(employee => {
const expectedSalary = employee.calculateExpectedSalary();
const experience = employee.getExperience();
const data = {
expectedSalary,
experience
};
switch (employee.type) {
case "manager":
data.portfolio = employee.getMBAProjects();
break;
case "developer":
data.githubLink = employee.getGithubLink();
break;
}
render(data);
});
}Establece objetos predeterminados con Object.assign
Malo:
const menuConfig = {
title: null,
body: "Bar",
buttonText: null,
cancellable: true
};
function createMenu(config) {
config.title = config.title || "Foo";
config.body = config.body || "Bar";
config.buttonText = config.buttonText || "Baz";
config.cancellable =
config.cancellable !== undefined ? config.cancellable : true;
}
createMenu(menuConfig);Bueno:
const menuConfig = {
title: "Order",
// User did not include 'body' key
buttonText: "Send",
cancellable: true
};
function createMenu(config) {
let finalConfig = Object.assign(
{
title: "Foo",
body: "Bar",
buttonText: "Baz",
cancellable: true
},
config
);
return finalConfig
// config now equals: {title: "Order", body: "Bar", buttonText: "Send", cancellable: true}
// ...
}
createMenu(menuConfig);No uses flags como parámetros
Las flags indican que la función realiza múltiples acciones. Divide funciones que sigan diferentes flujos basados en booleanos.
Malo:
function createFile(name, temp) {
if (temp) {
fs.create(`./temp/${name}`);
} else {
fs.create(name);
}
}Bueno:
function createFile(name) {
fs.create(name);
}
function createTempFile(name) {
createFile(`./temp/${name}`);
}Evita efectos secundarios (parte 1)
Una función produce efectos secundarios si realiza cualquier acción adicional a recibir un valor y devolver otro(s). Esto incluye escribir archivos, modificar variables globales o enviar electrónicamente todo tu dinero a un desconocido.
A veces los efectos secundarios son necesarios. Centraliza estas operaciones en un único servicio. Evita múltiples funciones modificando el mismo recurso.
Evita errores comunes: estados compartidos sin estructura, tipos mutables accesibles globalmente, y efectos secundarios dispersos. Centraliza estas operaciones para mejorar tu código.
Malo:
// Global variable referenced by following function.
// If we had another function that used this name, now it'd be an array and it could break it.
let name = "Ryan McDermott";
function splitIntoFirstAndLastName() {
name = name.split(" ");
}
splitIntoFirstAndLastName();
console.log(name); // ['Ryan', 'McDermott'];Bueno:
function splitIntoFirstAndLastName(name) {
return name.split(" ");
}
const name = "Ryan McDermott";
const newName = splitIntoFirstAndLastName(name);
console.log(name); // 'Ryan McDermott';
console.log(newName); // ['Ryan', 'McDermott'];Evita efectos secundarios (parte 2)
En JavaScript, objetos y arrays son mutables. Modificarlos como parámetros puede causar errores. Por ejemplo, si una función modifica un carrito de compras, afecta a otros componentes que lo usen.
Supón que el usuario compra mientras agrega un ítem accidentalmente. Sin clonar el carrito, la transacción incluirá el ítem no deseado durante reintentos de red.
Solución: clonar el carrito antes de modificarlo. Esto aisla los cambios. Consideraciones:
- Raramente se necesita modificar objetos directamente
- Usa bibliotecas como Immutable.js para clonar eficientemente.
Malo:
Bueno:
No escribas en funciones globales
const addItemToCart = (cart, item) => {
cart.push({ item, date: Date.now() });
};Contaminar el ámbito global (ej: modificar Array.prototype) genera conflictos. Prefiere clases ES6 para extender funcionalidades.
const addItemToCart = (cart, item) => {
return [...cart, { item, date: Date.now() }];
};Malo:
Bueno:
Prefiere programación funcional sobre imperativa
Array.prototype.diff = function diff(comparisonArray) {
const hash = new Set(comparisonArray);
return this.filter(elem => !hash.has(elem));
};JavaScript tiene elementos funcionales. Favorece este estilo para código más limpio y testeable.
class SuperArray extends Array {
diff(comparisonArray) {
const hash = new Set(comparisonArray);
return this.filter(elem => !hash.has(elem));
}
}Malo:
Bueno:
Encapsula condicionales
const programmerOutput = [
{
name: "Uncle Bobby",
linesOfCode: 500
},
{
name: "Suzie Q",
linesOfCode: 1500
},
{
name: "Jimmy Gosling",
linesOfCode: 150
},
{
name: "Gracie Hopper",
linesOfCode: 1000
}
];
let totalOutput = 0;
for (let i = 0; i < programmerOutput.length; i++) {
totalOutput += programmerOutput[i].linesOfCode;
}Malo:
const programmerOutput = [
{
name: "Uncle Bobby",
linesOfCode: 500
},
{
name: "Suzie Q",
linesOfCode: 1500
},
{
name: "Jimmy Gosling",
linesOfCode: 150
},
{
name: "Gracie Hopper",
linesOfCode: 1000
}
];
const totalOutput = programmerOutput.reduce(
(totalLines, output) => totalLines + output.linesOfCode,
0
);Bueno:
⬆ Volver al inicio
Evita condicionales negativas
if (fsm.state === "fetching" && isEmpty(listNode)) {
// ...
}Malo:
function shouldShowSpinner(fsm, listNode) {
return fsm.state === "fetching" && isEmpty(listNode);
}
if (shouldShowSpinner(fsmInstance, listNodeInstance)) {
// ...
}Bueno:
⬆ Volver al inicio
Evita condicionales
function isDOMNodeNotPresent(node) {
// ...
}
if (!isDOMNodeNotPresent(node)) {
// ...
}Usa polimorfismo para eliminar condicionales. Las funciones con if indican múltiples responsabilidades. Recuerda: haz solo una cosa.
function isDOMNodePresent(node) {
// ...
}
if (isDOMNodePresent(node)) {
// ...
}Malo:
Bueno:
Evita verificación de tipos (parte 1)
class Airplane {
// ...
getCruisingAltitude() {
switch (this.type) {
case "777":
return this.getMaxAltitude() - this.getPassengerCount();
case "Air Force One":
return this.getMaxAltitude();
case "Cessna":
return this.getMaxAltitude() - this.getFuelExpenditure();
}
}
}JavaScript es dinámico, pero la verificación explícita de tipos suele ser innecesaria. Diseña APIs consistentes para evitarla.
class Airplane {
// ...
}
class Boeing777 extends Airplane {
// ...
getCruisingAltitude() {
return this.getMaxAltitude() - this.getPassengerCount();
}
}
class AirForceOne extends Airplane {
// ...
getCruisingAltitude() {
return this.getMaxAltitude();
}
}
class Cessna extends Airplane {
// ...
getCruisingAltitude() {
return this.getMaxAltitude() - this.getFuelExpenditure();
}
}Malo:
Bueno:
Evita verificación de tipos (parte 2)
function travelToTexas(vehicle) {
if (vehicle instanceof Bicycle) {
vehicle.pedal(this.currentLocation, new Location("texas"));
} else if (vehicle instanceof Car) {
vehicle.drive(this.currentLocation, new Location("texas"));
}
}Para valores primitivos que requieren tipado, considera TypeScript. La verificación manual reduce legibilidad sin ofrecer seguridad real.
function travelToTexas(vehicle) {
vehicle.move(this.currentLocation, new Location("texas"));
}Malo:
Bueno:
No sobreoptimices
function combine(val1, val2) {
if (
(typeof val1 === "number" && typeof val2 === "number") ||
(typeof val1 === "string" && typeof val2 === "string")
) {
return val1 + val2;
}
throw new Error("Must be of type String or Number");
}Los navegadores modernos optimizan automáticamente. Enfócate solo en cuellos de botella verificados. Consulta recursos especializados.
function combine(val1, val2) {
return val1 + val2;
}Malo:
Bueno:
Elimina código muerto
// On old browsers, each iteration with uncached `list.length` would be costly
// because of `list.length` recomputation. In modern browsers, this is optimized.
for (let i = 0, len = list.length; i < len; i++) {
// ...
}El código no utilizado es tan dañino como el duplicado. Si no se ejecuta, elimínalo. Tu historial de versiones lo conservará si es necesario.
for (let i = 0; i < list.length; i++) {
// ...
}Malo:
Bueno:
function oldRequestModule(url) {
// ...
}
function newRequestModule(url) {
// ...
}
const req = newRequestModule;
inventoryTracker("apples", req, "www.inventory-awesome.io");function newRequestModule(url) {
// ...
}
const req = newRequestModule;
inventoryTracker("apples", req, "www.inventory-awesome.io");