Codigo limpio en Javascript

Hace algunos años, cuando comencé a programar, leí en internet acerca del famoso libro de Robert C. Martin. Muchos lo recomiendan y lo usan como una biblia para la programación, mientras que otros mencionan que muchos de sus conceptos son antiguos y no vale tanto la pena leer el libro.

Después me topé con un maravilloso repositorio, que adapta algunos de los principios de este libro para JavaScript.

Aquí dejaré algunos de los conceptos que me gustaron y en mi opinión son muy importantes y que muchos desarrolladores olvidan o simplemente no han pensado en ello.

Variables

Nombres que puedan ser buscados

Muchos desarrolladores tenemos la mala costumbre de escribir directamente valores numericos en nuestro código. Esto tiende a ser una mala practica ya que puede llevar a inconsistencies y confuciones.

Algunos de los casos en los que podemos hacer uso de este principio son los siguientes:

const FIVE_MB = 1024 * 1024 * 5
const IVA = 0.16
const MILISECONDS_PER_DAY = 86400000

De esta manera podemos utilizar estos valores sin que haya alguna confusión.

function getTotal(subtotal) {
  const total = subtotal * IVA
  return total
}

if (size > FIVE_MB) {
  console.log('The size of the file is too big!')
}

El nombre de las variables debe explicar lo que son

Muchas veces no usamos los nombres correctos para lo que usamos, o simplemente no hacemos variables que pueden hacer el código mas entendible. Veamos el siguiente código.

const stringArray = 'fetchedData["Sales", "Jose Guardiola"]next'
const [matchedArray] = stringArray.match(/\[(.*?)\]/)

const moduleHeader = `${JSON.parse(matchedArray)[1]}: ${
  JSON.parse(matchedArray)[0]
}` // José Guardiola: Sales

Esté codigo funciona correctamente, pero si alguien viene a hacer algún cambio, quizas le cueste un poco mas de trabajo entender lo que esta pasando.

Podemos usar destructuring assignment con el metodo estático de parse, para asignar variables y entender mejor lo que estamos haciendo.

const stringArray = 'fetchedData["Sales", "Jose Guardiola"]next'

const [matchedArray] = stringArray.match(/\[(.*?)\]/)
const [userPosition, userName] = JSON.parse(matchedArray)

const moduleHeader = `${userName}: ${userPosition}`

console.log('module header: ', moduleHeader) // Jose Guardiola: Sales

Funciones

Las funciones deben hacer solo una cosa.

Este concepto me tomo algo de tiempo comprenderlo en un principio, ya que cuando empece a programar, me preguntaba ¿Porqué debería de separar todas las funciones, si puedo tener solo una que haga lo que quiero? Así ya no me voy a tener que estar moviendo de lugar todo el tiempo. Claro estaba, que no tenía la experiencia necesaria para darme cuenta que al momento de querer hacer algún cambio o de testearla, sería muy complicado incluso para mi poder entender el código después de haberlo escrito meses atras.

Se menciona que este es por mucho la regla más importante dentro de la ingeniería de software y estoy de acuerdo con ello. Evita que la proxima persona que tenga que refactorizar o cambiar su código no te odie (o evita no odiar a tu yo del pasado).

Supongamos que tenemos una función que manda correo a los clientes con facturas pendientes por pagar.

const buildBillingMail = ({ ...opts }) => {
  // Mi codigo que crea el billing mail
}

const sendMail = (user, content, opts) => {
  // Mi codigo para mandar el correo
}

const availableClient = client.isPending && client.isActive

const filterPendingClients = (client) => availableClient

const pendingClients = (clients) => clients.filter(filterPendingClients)

const sendBillingMail = (clients) => {
  const clientsToMail = pendingClients(clients)
  for (const client of clientsToMail) {
    const mailContent = buildBillingMail({ ...opts })
    sendMail(client, mailContent)
  }
}

sendBillingMail()

Este tipo de acciones requieren mucha funcionalidad y tienden a ser muy grandes. Puede que llegue un momento en el que exista tanta funcionalidad, que a cualquiera se le dificulte entender el código aún y estando documentado. Lo mejor siempre será separar las funciones, ya que podemos reutilizar algunas para otro caso, o simplemente que se nos solicite cambiar algo en el filtrado o cualquier cosa.

Elimina el código duplicado

Definitivamente, la mayoría de nosotros hemos oído que tener código duplicado es perjudicial y, sin duda, lo es. Pero, ¿qué pasa si es absolutamente necesario tener código duplicado debido a ciertos cambios en nuestras acciones? La respuesta a esto radica en algunos principios/conceptos dentro de la programación que son extremadamente importantes. Muchos de estos principios son mencionados en los famosos principios SOLID, los cuales hablaré un poco más adelante.

En resumen, a través de una buena abstracción , podemos evitar muchos de estos problemas.

Usa objetos por default con “spread” operator

En el repositorio utilizan Object.assign(), el cual es otra manera correcta en la que puedes settear valores por default, pero ya estamos en el 2023 y aunque no sea una mala practica o que este incorrecto, tenemos que actualizarnos.

Imaginemos que nos solicitan un nuevo “endpoint” para crear nuevas compañias, Todas tendran por default algunos datos, a no ser que lo editen desde el front.

const defaultCompanyAttributes = {
  name: 'Temporary Company',
  numberOfUsers: 20,
  isActive: true,
  includeIntegrations: false,
  city: null,
  phone: null,
  logo: '/default',
  color: 'blue',
  validated: false,
};

const createCompany = (newCompany) => {
  const companyToCreate = { ...defaultCompanyAttributes, ...newCompany };
  console.log('created company: ', companyToCreate);
  /* created company:  { name: 'Peps Company', 
	    numberOfUsers: 100, 
	    isActive: true, 
	    includeIntegrations: false, 
	    city: 'Monterrey', 
	    phone: null, 
	    logo: '/default', 
	    color: 'blue', 
	    validated: true, 
	    numberOfBranches: 10, 
	    customFields: true }
		};
	*/

createCompany({
  name: 'Peps Company',
  city: 'Monterrey',
  numberOfUsers: 100,
  validated: true,
  numberOfBranches: 10,
  customFields: true,
});

Si no utilizaramos objetos con valores predeterminados y algunos de los campos fueran obligatorios, terminariamos utilizando bastantes fallbacks de esta manera.

createCompany({
  name: newCompany.name || 'Temporary Company',
  numberOfUsers: newCompany.numberOfUsers || 20,
})

Lo anterior no está mal, simplemente puede llegar a complicarse en algún punto, al ir agregando más atributos.

Evita efectos secundarios

Los efectos secundarios son comunes en muchos proyectos, pero según el libro, deberían evitarse en la medida de lo posible y solo utilizarse cuidadosamente en casos específicos.

No entraré mucho en detalle, pero al hablar de esto, me viene a la mente el concepto de "funciones puras". Este concepto es clave dentro del paradigma de programación funcional (aquí hay más información al respecto).

¿Qué significa evitar los efectos secundarios? Básicamente, no modificar ninguna información que no sea parte de la función. En lugar de cambiar un objeto globalmente, es mejor hacer una "copia profunda" del objeto, modificar la copia y devolverla. No queremos alterar variables fuera de nuestra función.