Pequeños errores técnicos a tener en cuenta en cualquier prueba de programación

23-08-2023

7 min read

Alt text

Pequeños errores técnicos a tener en cuenta en cualquier prueba de programación

Una de las muchas técnicas que usan las empresas para calificar un posible candidato es la prueba técnica. No voy a ahondar en los diferentes tipos ni en cómo abordarlas. Este tuturial simplemente ilustra varios errores técnicos (algunos no tan lógicos como pueda parecer) que no sabía en su momento que podían afectar en un proceso de selección.

Note:

Hubo errores que no mostraré por no considerarlos necesarios (errores de navegación, UX, valoraciones en mi opinión subjectivas, etc).

Table of Contents

Contexto y prueba

La prueba consistía en la realización de un e-commerce de teléfonos móviles. Más allá de lo normal que podrían pedir para ello (catálogo, carrito y ficha del móvil con fotos e información técnica) se necesitaba que:

  • Los productos se obtuvieran mediante una API que ellos me ofrecían.
  • Los datos debían persistir mediante cache, localstorage, etc.

Puedes ver la prueba enviada en: https://github.com/borjamrd/ecommerce-client-borjamunoz

Error 1º: files not deleted

La prueba la realicé con create-react-app. A pesar de que ahora lo haría con vite mi primer error fue no eliminar todo el código por defecto que se genera con ese comando: los archivos CSS, el código de App.js, index.js el archivo logo.svg, etc.

Al pricipio sinceramente pensé: menuda estupidez. Pero es cierto que todo detalle ayuda y es bueno que desde el principio intentes tener la menor cantidad de código necesario posible.

Error 2º: dependencies vs devDependencies

Por lo general estaba (estaba) acostumbrado a instalar las dependencias y no darle importancia a si deben ser de desarrollo o producción.

Las dependencias de desarrollo, como es lógico, no deberían afectar directamente en producción. En mi caso incluí dependencias de testing que deberían estar en desarrollo y que si bien es cierto que operativamente hablando no me habían dado ningún problema no era una buena práctica.

1 "dependencies": {
2 "@heroicons/react": "^2.0.18",
3 "@testing-library/jest-dom": "^5.16.5",
4 "@testing-library/react": "^13.4.0",
5 "@testing-library/user-event": "^13.5.0",
6 "react": "^18.2.0",
7 "react-dom": "^18.2.0",
8 "react-router": "^6.14.1",
9 "react-router-dom": "^6.14.1",
10 "react-scripts": "5.0.1",
11 "web-vitals": "^2.1.4"
12 },
13 "devDependencies": {
14 "daisyui": "^3.2.1",
15 "eslint-plugin-react": "^7.32.2",
16 "tailwindcss": "^3.3.2"
17 }
18

Error 3º: versión dependencias.

Literamente el feedback de este apartado fue: Dependencies in package.json should have its versions fixed.

He visto que varios desarrolladores recomendaban eliminar el caret "^" para controlar las versiones exactas de las dependencias pero el que me lo tuvieran en cuenta en una prueba me recordó la necesidad de estar atento a los pequeños detalles.

Error 4º: Importaciones duplicadas.

En mi caso importé el archivo index.css en index.js y App.jsx. Al componetizar la aplicación dupliqué la importación y no corregí el error antes de enviarla.

Apóyate del uso y configuración de linters para evitar este error.

1//App.jsx
2import React, { Suspense } from "react";
3import { Route, Routes } from "react-router";
4import { BrowserRouter } from "react-router-dom";
5import Header from "./components/Header";
6import DefaultLayout from "./containers/DefaultLayout";
7import { CartProvider } from "./context/CartContext";
8import storageExpired from "./helpers/storageExpired";
9import "./index.css"; //duplicado
10import NoMatch from "./pages/NoMatch";
11
12//index.js
13
14import React from "react";
15import ReactDOM from "react-dom/client";
16import App from "./App";
17import "./index.css"; //sólo con tenerlo aquí hubiese bastado
18import reportWebVitals from "./reportWebVitals";

Error 5º: ojo con las "fragmentaciones".

"Unnecessary use of Fragment in App.jsx since the component returns a single element." ¿Cuántos de nosotros no hemos usamos un fragmento (<React.Fragment> o su forma abreviada <>) para englobar el contenido de un componente? Pues bien, ojo cuando sea un sólo elemento. Si puede hacerse con un <div> o una etiqueta semántica, mejor.

Error 6º: evita usar clear() en sessionStorage y localStorage.

En mi caso hice uso de una función que mediante setTimeout eliminaba la información almacenada en localStorage.

Procura modularizar qué quieres mantener y eliminar en localStorage y prioriza removeItem('') sobre clear()

1const storageExpired = () => {
2 let hours = 1;
3 let now = new Date().getTime();
4 let setupTime = localStorage.getItem("setupTime");
5 if (setupTime == null) {
6 localStorage.setItem("setupTime", now);
7 } else {
8 if (now - setupTime > hours * 60 * 60 * 1000) {
9 localStorage.clear(); // Aquí sería recomendable remover todos aquellos elementos que querramos eliminar en vez de usar el genérico clear().
10 localStorage.setItem("setupTime", now);
11 }
12 }
13};
14
15export default storageExpired;

Error 7º: nombra bien las funciones y métodos.

El código que te muestro a continuación sirve para señalar varios errores. Comencemos con el primero:

Dentro de CartContext hice uso del método addToCart para actualizar un producto. Inicialmente lo creé para añadir un producto al carrito pero me di cuenta que era mejor en un futuro poder modificar el estado y añadirlo/quitarlo. Pues bien, no modifiqué el nombre para especificar que era actualizar y no añadir.

Si tu función modifica, indícalo, si tu función elimina o añade, indícalo.

1import React, { createContext, useState } from "react";
2
3export const CartContext = createContext();
4
5export const CartProvider = ({ children }) => {
6 //hooks, etc.
7 const addToCart = (product) => {
8 if (Array.isArray(product)) {
9 updateProducts([...products, product]);
10 } else if (typeof product === "object" && product !== null) {
11 updateProducts([...products, product]);
12 } else {
13 console.error("addToCart: Product must be an array or an object");
14 }
15 };
16
17 const emptyCart = () => {
18 updateProducts([]);
19 };
20
21 const updateProducts = (products) => {
22 setProducts(products);
23 localStorage.setItem("products", JSON.stringify(products));
24 };
25 const cartContextValue = {
26 products,
27 addToCart,
28 setProducts,
29 emptyCart,
30 };
31
32 return (
33 <CartContext.Provider value={cartContextValue}>
34 {children}
35 </CartContext.Provider>
36 );
37};

En el caso de la actualización del producto en el CartContext aglutiné toda la lógica que engloba lo que se puede hacer con un producto y expuse un método que debería ser privado.

1const cartContextValue = {
2 products,
3 addToCart,
4 setProducts, //expuesto
5 emptyCart,
6};
7
8//cambiar por
9const cartContextValue = {
10 products,
11 addToCart,
12 emptyCart,
13};

De esta forma utilizo internamente el método updateProducts, que a su vez es utilizado por los métodos addToCart y emptyCart. Esto hace que el diseño sea más coherente al mantener ciertos métodos como "privados" en el ámbito del contexto.

Error 9º: elimina las variables, métodos y funciones no usadas.

Muchas veces con las prisas, con la intención de implementar funcionalidades, podemos caer en la verbosidad de introducir variables, funciones y métodos que realmente no necesitamos.

Apóyate mucho en el uso de linters para ver las detectar este problema. En mi caso fallé al implementar un abortController para poder abortar las peticiones a la API que no tenía validez práctica para el caso que necesitaba implementar.

1const fetchData = () => {
2 const abortController = new AbortController();
3 setController(abortController);
4 fetch(url, {
5 method,
6 body: JSON.stringify(body),
7 headers: {
8 "Content-Type": "application/json",
9 },
10 signal: abortController.signal,
11 })
12 .then((resp) => {
13 if (resp?.status === 500) {
14 return setError("error 500");
15 }
16 return resp.json();
17 })
18 .then((json) => {
19 saveData(json);
20 })
21 .catch((error) => {
22 console.log(error);
23 if (error.name === "AbortError") {
24 console.log("Cancel request");
25 } else {
26 setError(error);
27 }
28 })
29 .finally(() => setLoading(false));
30 return () => abortController.abort();
31};

Error 10º: antes de escribir, piensa y abstrae.

Este es quizá el más importante.

En cuanto comencé el desarrollo de la aplicación lo primero que implementé fue un hook personalizado useFetchProducts de forma que pudiese hacer GET de los productos, manejar los errores y mostrar visualmente un spinner.

Hasta aquí todo normal, el problema vino cuando tenía que realizar la petición POST para recibir los datos de un artículo en particular.

No me servía tal y como tenía el hook y realizar modificaciones con el poco tiempo que me quedaba era arriesgado. Así que básicamente lo copié y realicé los ajustes sobre el nuevo hook useFetchProduct.

El problema no es la repeción del código en si mismo (que también) si no la falta de abstracción de los requisitos antes de ponerme a implementar funcionalidades. Si hubiese dedicado 5 minutos a pensar con un esquema cómo resolver técnicamente cada uno de los requisitos esto no me habría pasado.

Espero que te haya ayudado. Ten en cuenta que mi nivel sigue siendo junior así que agradecería que cualquier sugerencia, corrección o recomendación no dudes en decírmelo.