DEV Community

Cover image for Funções (Código Limpo: Que Bruxaria é Essa?!?! - Parte 3)
ananopaisdojavascript
ananopaisdojavascript

Posted on

Funções (Código Limpo: Que Bruxaria é Essa?!?! - Parte 3)

Argumentos da função (o ideal é que sejam 2 ou menos)

Limitar a quantidade de parâmetros de uma função é incrivelmente importante porque a deixa mais fácil de ser testada. Três ou mais argumentos causam uma combinação explosiva na qual você precisa testar toneladas de casos distintos com cada argumento separado.

Um ou dois argumentos é o ideal e, se possível, evite um terceiro argumento. Mais do que isso deve ser consolidado. Se o normal para você é usar mais de dois parâmetros, então a sua função está tentando fazer demais. Nos casos nos quais é algo inevitável, um objeto de alto nível será suficiente como um parâmetro.

Como o JavaScript permite que você escreva objetos em movimento, sem um monte de classes padrão, você pode usar um objeto se precisa de muitos argumentos.

Para fazer com que as propriedades esperadas pela função sejam óbvias, você pode usar a sintaxe de desestruturação do ES2015/ES6. A desestruturação tem algumas vantagens:

  • Quando alguém olha a assinatura da função, fica imediatamente claro ver quais propriedades estão sendo usadas.
  • Pode ser usada para estimular parâmetros nomeados.
  • A desestruturação também clona os valores primitivos especificados do objeto argumento passado dentro da função, o que ajuda a prevenir efeitos colaterais. Observação: objetos e vetores que são desestruturados do objeto argumento NÃO são clonados.
  • Linters podem avisar você das propriedades inutilizadas, o que seria impossível sem a desestruturação.

Não é recomendável:

function createMenu(title, body, buttonText, cancellable) {
  // ...
}

createMenu("Foo", "Bar", "Baz", true);
Enter fullscreen mode Exit fullscreen mode

É recomendável:

function createMenu({ title, body, buttonText, cancellable }) {
  // ...
}

createMenu({
  title: "Foo",
  body: "Bar",
  buttonText: "Baz",
  cancellable: true
});
Enter fullscreen mode Exit fullscreen mode

Funções devem apresentar apenas uma única utilidade

Esta é, de longe, a regra mais importante na engenharia de software. Quando funções apresentam mais de uma utilidade, são mais difíceis de compor, testar e explicar. Quando você restringe a função a uma única ação, pode ser refatorada com facilidade e seu código ficará com uma leitura mais limpa. Se você assimilar apenas essa regra, estará na frente de muitos desenvolvedores.

Não é recomendável:

function emailClients(clients) {
  clients.forEach(client => {
    const clientRecord = database.lookup(client);
    if (clientRecord.isActive()) {
      email(client);
    }
  });
}
Enter fullscreen mode Exit fullscreen mode

É recomendável:

function emailActiveClients(clients) {
  clients.filter(isActiveClient).forEach(email);
}

function isActiveClient(client) {
  const clientRecord = database.lookup(client);
  return clientRecord.isActive();
}
Enter fullscreen mode Exit fullscreen mode

Os nomes de funções devem dizer o que fazem

Não é recomendável:

function addToDate(date, month) {
  // ...
}

const date = new Date();

// It's hard to tell from the function name what is added
addToDate(date, 1);
Enter fullscreen mode Exit fullscreen mode

É recomendável:

function addMonthToDate(month, date) {
  // ...
}

const date = new Date();
addMonthToDate(1, date);
Enter fullscreen mode Exit fullscreen mode

Funções devem apresentar somente um único nível de abstração

Quando sua função apresenta mais de um nível de abstração, normalmente está fazendo demais. A divisão das funções leva a uma reutilização e testes mais fáceis.

Não é recomendável:

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...
  });
}
Enter fullscreen mode Exit fullscreen mode

É recomendável:

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;
}
Enter fullscreen mode Exit fullscreen mode

Remova código duplicado

Esforce-se para evitar código duplicado. O código duplicado é ruim porque significa que há mais de um lugar para alterar algo se você precisa modificar alguma lógica.

Imagine a seguinte situação: você comanda um restaurante e monitora o seu inventário: todos os seus tomates, cebolas, alho, pimentas, etc. Se você tem várias listas para mantê-lo, todas deverão estar atualizadas sempre que você servir uma refeição com tomates, por exemplo. Se você tem uma única lista, haverá apenas um lugar para atualização!

Às vezes seu código está duplicado porque você apresenta duas ou mais funcionalidades ligeiramente distintas, que dividem muito em comum, porém essas diferenças lhe forçam a ter duas ou mais funções separadas que fazem mais do que as mesmas utilidades. A remoção do código duplicado significa criar uma abstração que pode lidar esse conjunto de funcionalidades diferentes com uma única função/módulo/classe.

Obter a abstração correta é crucial e é por isso que você deve seguir o princípios de SOLID que estão na seção Classes. Abstrações ruins podem ser piores que código duplicado, portanto tenha cuidado! Dito isso, se você pode criar uma boa abstração, vá em frente! Não se repita, do contrário você se encontrará na situação de atualizar muitos lugares toda vez que você quiser apenas alterar uma única coisa.

Não é recomendável:

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);
  });
}
Enter fullscreen mode Exit fullscreen mode

É recomendável:

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);
  });
}
Enter fullscreen mode Exit fullscreen mode

Configure objetos padrão em Object.assign

Não é recomendável:

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);
Enter fullscreen mode Exit fullscreen mode

É recomendável:

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);
Enter fullscreen mode Exit fullscreen mode

Não use flags como parâmetros de função

Flags dizem ao seu usuário que a função tem mais de um uso. Funções devem ter apenas uma única utilidade. Divida suas funções se seguem padrões diferentes de código com base em um valor booleano.

Não é recomendável:

function createFile(name, temp) {
  if (temp) {
    fs.create(`./temp/${name}`);
  } else {
    fs.create(name);
  }
}
Enter fullscreen mode Exit fullscreen mode

É recomendável:

function createFile(name) {
  fs.create(name);
}

function createTempFile(name) {
  createFile(`./temp/${name}`);
}
Enter fullscreen mode Exit fullscreen mode

Evite os Efeitos Colaterais (Parte 1)

Uma função produz um efeito colateral se tem mais de um uso além de obter um valor e retorna outro(s) valor(es). Um efeito colateral pode ser a gravação em um arquivo, a modificação de alguma variável global ou a transferência acidental de todo o seu dinheiro a um estranho.

Agora, se o seu programa precisa apresentar efeitos colaterais de vez em quando. Assim como o exemplo anterior, talvez você precise fazer gravação em um arquivo. O que você quer fazer é centralizar em um local o que você está fazendo. Não tenha várias funções e classes que gravem em um arquivo em particular. Tenha apenas um serviço para fazê-lo. Primeiro e único.

O ponto principal é evitar armadilhas comuns como compartilhar estados entre objetos sem estrutura alguma, usar tipos de dados mutáveis que podem ser escritos por qualquer coisa e não centralizar onde acontecem seus efeitos colaterais. Se você tomar essas atitudes, será mais feliz do que a vasta maioria dos programadores.

Não é recomendável:

// Variável global fazendo referência à função a seguir.
/*
Se temos outra função que usa esse nome, agora seria um
vetor e poderia quebrá-lo.
*/

let name = "Ryan McDermott";

function splitIntoFirstAndLastName() {
  name = name.split(" ");
}

splitIntoFirstAndLastName();

console.log(name); // ['Ryan', 'McDermott'];
Enter fullscreen mode Exit fullscreen mode

É recomendável:

function splitIntoFirstAndLastName(name) {
  return name.split(" ");
}

const name = "Ryan McDermott";
const newName = splitIntoFirstAndLastName(name);

console.log(name); // 'Ryan McDermott';
console.log(newName); // ['Ryan', 'McDermott'];
Enter fullscreen mode Exit fullscreen mode

Evite Efeitos Colaterais (Parte 2)

No JavaScript, alguns valores não se modificam (são imutáveis) e alguns se modificam (são mutáveis). Objetos e vetores são dois tipos de valores mutáveis. Portanto é importante manejá-los com cuidado quando são passados como parâmetros para uma função. Uma função JavaScript pode mudar as propriedades de um objeto de alterar os conteúdos de um vetor, o que poderia facilmente causar bugs em toda parte.

Imagine que existe uma função que aceita um vetor como parâmetro que representa um carrinho de compras. Se a função faz uma mudança no vetor do carrinho de compras - ao identificar um item para compra, por exemplo - então qualquer outra função que use o mesmo vetor cart será afetado por essa inclusão. O que pode ser bom e ruim. Vamos imaginar uma situação ruim:

O usuário clica no botão Comprar que chama uma função purchase que dispara uma requisição de rede e manda o vetor cartpara o servidor. Por causa de uma conexão de rede ruim, a função purchase continua tentando fazer a requisição. Agora, e se nesse meio tempo o usuário clica por acidente no botão Adicionar ao Carrinho de um produto que não quer na realidade antes que a requisição de rede comece? Se essa situação acontece e a requisição se reinicia, aquela função de compra enviará o item incluído por acidente porque o vetor cart foi modificado.

Um boa solução para a função addItemCart seria sempre clonar o cart, editá-lo e retornar o clone. Essa solução garante que as funções que ainda estão usando o antigo carrinho de compra não sejam afetadas pelas mudanças.

Duas condições dessa abordagem precisam ser mencionadas:

Talvez existam casos nos quais você realmente queira modificar o objeto de entrada, porém quando adota essa prática de programação, descubrirá que esses casos são bem raros. A maioria das coisas podem ser refatoradas para não apresentarem efeitos colaterais!
A clonagem de grandes objetos pode ser bem cara quanto ao desempenho. Felizmente, não é um grande problema na prática porque há ótimas bibliotecas que permitem com que essa abordagem de programação seja rápida e não tão intensa, em termos de memória, que se você clonasse manualmente objetos e vetores.

Não é recomendável:

const addItemToCart = (cart, item) => {
  cart.push({ item, date: Date.now() });
};
Enter fullscreen mode Exit fullscreen mode

É recomendável:

const addItemToCart = (cart, item) => {
  return [...cart, { item, date: Date.now() }];
};
Enter fullscreen mode Exit fullscreen mode

Não escreva funções globais

Elementos globais poluidores é uma prática ruim no JavaScript porque você poderia entrar em conflito com outra biblioteca e o usuário da sua API não saberia de nada até que obtivesse uma exceção em produção. Pensando em um exemplo: e se você quisesse estender um método de vetor nativo do JavaScript para obter um método diff que pudesse mostrar a diferença entre dois vetores? Você poderia escrever sua nova função para Array.prototype porém poderia entrar em conflito com outra biblioteca que tentasse fazer a mesma coisa. E se outra biblioteca estivesse usando diff somente para encontrar a diferença entre o primeiro e o último elementos do vetor? É por essas e outras que seria muito melhor usar apenas as classes ES2015/ES6 e simplesmente estender o global Array.

Não é recomendável:

Array.prototype.diff = function diff(comparisonArray) {
  const hash = new Set(comparisonArray);
  return this.filter(elem => !hash.has(elem));
};
Enter fullscreen mode Exit fullscreen mode

É recomendável:

class SuperArray extends Array {
  diff(comparisonArray) {
    const hash = new Set(comparisonArray);
    return this.filter(elem => !hash.has(elem));
  }
}
Enter fullscreen mode Exit fullscreen mode

Prefira a programação funcional à programação imperativa

O JavaScript não é uma linguagem funcional do mesmo modo que o Haskell, porém tem um sabor funcional. Linguagens funcionais podem ser mais limpas e fáceis de serem testadas. Prefira esse estilo de programação sempre que puder.

Não é recomendável:

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;
}
Enter fullscreen mode Exit fullscreen mode

É recomendável:

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
);
Enter fullscreen mode Exit fullscreen mode

Encapsule condicionais

Não é recomendável:

if (fsm.state === "fetching" && isEmpty(listNode)) {
  // ...
}
Enter fullscreen mode Exit fullscreen mode

É recomendável:

function shouldShowSpinner(fsm, listNode) {
  return fsm.state === "fetching" && isEmpty(listNode);
}

if (shouldShowSpinner(fsmInstance, listNodeInstance)) {
  // ...
}
Enter fullscreen mode Exit fullscreen mode

Evite condicionais negativas

Não é recomendável:

function isDOMNodeNotPresent(node) {
  // ...
}

if (!isDOMNodeNotPresent(node)) {
  // ...
}
Enter fullscreen mode Exit fullscreen mode

É recomendável:

function isDOMNodePresent(node) {
  // ...
}

if (isDOMNodePresent(node)) {
  // ...
}
Enter fullscreen mode Exit fullscreen mode

Evite condicionais

Parece ser uma tarefa impossível. Muita gente, ao escutar esse conselho, pergunta: "Como eu devo fazer qualquer coisa sem o if?!". A resposta é que você pode usar polimorfismo para alcançar o mesmo resultado em muitos casos. A segunda questão é: "Tudo bem, é legal, mas porque eu teria que fazer isso?!". A resposta vem de um conceito de código limpo que já aprendemos: uma função deve apresentar uma única utilidade. Quando você tem classes e funções com if, você está dizendo ao usuário que sua função tem mais de um uso. Lembre-se: apenas tenha uma única utilidade.

Não é recomendável:

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();
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

É recomendável:

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();
  }
}
Enter fullscreen mode Exit fullscreen mode

Evite verificação de tipos (Parte 1)

O JavaScript não tem tipo, o significa que suas funções podem levar qualquer tipo de argumento. Às vezes você pode ser bicado por essa liberdade toda e torna-se tentador verificar tipos em suas funções. Há várias formas de evitar essa atitude. O primeiro ponto a ser considerado são APIs consistentes.

Não é recomendável:

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"));
  }
}
Enter fullscreen mode Exit fullscreen mode

É recomendável:

function travelToTexas(vehicle) {
  vehicle.move(this.currentLocation, new Location("texas"));
}
Enter fullscreen mode Exit fullscreen mode

Evite verificação de tipos (Parte 2)

Se você está trabalhando com valores primitivos básicos como cadeias de caracteres e números inteiros, você deve considerar o uso do TypeScript. É uma excelente alternativa ao JavaScript regular, uma vez que fornece tipos estáticos em cima da sintaxe padrão do JavaScript. O problema como a verificação normal do JavaScript é que desempenhá-la bem exige muita verbosidade extra de modo que a falsa sensação de "tipos seguros" obtida por você não compensa a perda de facilidade de leitura. Mantenha seu código JavaScript limpo, escreva bons testes e tenha boas revisões de código. Do contrário, faça tudo isso mas com TypeScript (como eu já disse, é uma ótima alternativa!)

Não é recomendável:

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");
}
Enter fullscreen mode Exit fullscreen mode

É recomendável:

function combine(val1, val2) {
  return val1 + val2;
}
Enter fullscreen mode Exit fullscreen mode

Não exagere na otimização

Navegadores modernos executam um monte de otimizações por debaixo dos panos. Muitas vezes se você os otimiza está apenas perdendo seu tempo. Há bons recursos para verificar onde a otimização está ausente. Mire-as neste meio tempo até que estejam resolvidas se puderem.

Não é recomendável:

for (let i = 0, len = list.length; i < len; i++) {
  // ...
}
Enter fullscreen mode Exit fullscreen mode

É recomendável:

for (let i = 0; i < list.length; i++) {
  // ...
}
Enter fullscreen mode Exit fullscreen mode

Remova código morto

Código morto é tão ruim quanto código duplicado. Não há razão para mantê-lo na sua base de códigos. Se não é chamado, livre-se dele! Esse código ainda estará seguro no histórico da sua versão se ainda for necessário.

Não é recomendável:

function oldRequestModule(url) {
  // ...
}

function newRequestModule(url) {
  // ...
}

const req = newRequestModule;
inventoryTracker("apples", req, "www.inventory-awesome.io");
Enter fullscreen mode Exit fullscreen mode

É recomendável:

function newRequestModule(url) {
  // ...
}

const req = newRequestModule;
inventoryTracker("apples", req, "www.inventory-awesome.io");
Enter fullscreen mode Exit fullscreen mode

E aí? Gostaram? Até a próxima tradução! 🤗

Discussion (0)