Programação

Boas práticas de programação para iniciantes

Quando estamos começando a lidar com linguagens de programação, ficamos mais preocupados em fazer nosso código funcionar do que qualquer outra coisa. Normalmente, a última coisa que prestamos atenção é se o código está legível e “bonito” se de ver… Porém, é importante começarmos a seguir algumas boas práticas de codificação desde o início. Isso irá lhe auxiliar a criar o hábito de escrever código de fácil entendimento e de manutenibilidade aprimorada. Às vezes, nós mesmos não conseguimos entender um código que fizemos a um tempo atrás.
Se você está iniciando agora, veja algumas dicas para colocar em prática.

Utilize nomes significativos

Quando escrevemos qualquer trecho de código que seja, é importante deixar claro o que é uma variável ou o que um método faz. Um dos pontos que precisamos seguir para atingirmos este objetivo é utilizarmos nomes significativos para as nossas estruturas de código. Evite nomear variáveis, métodos e classes com nomes que não tenham nada a ver com o contexto… O código deve ser simples e direto, deixando claro a sua intenção logo na primeira leitura.
Por exemplo: quando estamos iniciando nossos estudos em desenvolvimento e precisamos escrever uma estrutura de código que faça uma soma, geralmente escrevemos algo similar ao abaixo:

public int fazer_a_soma(int a, int b)
{
    return a + b;
}

Veja que temos alguns pontos que poderíamos melhorar, tornando nosso código mais legível: poderíamos simplificar o nome do método e darmos nomes mais claros para os nossos parâmetros… Nosso método, depois de corrigido (ou refatorado), poderia ficar da seguinte maneira:

public int somar(int numero1, int numero2)
{
    return numero1 + numero2;
}

Cuidado com os comentários!

Os comentários geralmente nos ajudam a explicar ou recordar de algo no código. Porém, comentários em excesso não é algo muito legal… Se você está tendo que explicar tudo que ocorre em seu código, é porque o código provavelmente está mal escrito ou bagunçado. Sendo assim, tente sempre restringir os comentários aos trechos onde realmente seja necessário. Trechos de código que sigam práticas como a utilização de nomes significativos geralmente auxiliam a restringir os comentários aos trechos onde os mesmos sejam de fato necessários.

Reaproveite o código

Se você está fazendo algo que já existe em mais de um lugar, é interessante pensar em uma forma de evitar essa duplicidade. Por isso, sempre pense em reaproveitamento de código, criando estruturas (como classes e métodos) mais abstratas e reaproveitáveis. Pensar em reaproveitamento de estruturas diminui o volume de código e torna o processo de manutenção centralizado e muito mais facilitado.

Idente seu código

A identação, que consiste nos tabs ou espaços agrupando os diferentes blocos de código, é algo essencial. Um trecho de código mal identado é terrivelmente difícil de ser lido. Por isso, atente-se sempre à identação dos trechos de código que você escrever. Lembre-se também que a maioria das IDEs e editores de texto modernos possuem atalhos que identam todo seu código de maneira rápida e automática, portanto: utilize este recurso sempre!
Veja abaixo um trecho de código com identação incorreta. Repare que o código é super complicado de ser lido, ainda mais por se tratar de um trecho de código com múltiplos blocos.

if(idade > 18) {
 System.out.println("Idade maior que 18");
} else if(idade == 18) {
System.out.println("Idade é igual a 18");
            } else { 
 System.out.println("Idade menor que 18");}

Veja agora o mesmo trecho de código corretamente identado. Veja que o código parece mais “fluído”, se tornando muito mais fácil de ser lido simplesmente por estar com a identação correta.

if(idade > 18) {
    System.out.println("Idade maior que 18");
} else if(idade == 18) {
    System.out.println("Idade é igual a 18");
} else {
    System.out.println("Idade menor que 18");
}

Mesmo sendo iniciante, é importante sempre estarmos atentos às possibilidades de como melhorar nosso código, pois isso é um ponto que o mercado em geral valoriza muito. Por mais que a maioria das empresas desejarem que seus softwares funcionem, elas também esperam que o código produzido apresente qualidade. Bons desenvolvedores estão atentos hoje às boas práticas de codificação.
Neste artigo, foram abordadas algumas práticas mais simples. Mas, caso queira se aprofundar ainda mais em pontos relacionados às boas práticas de código limpo, temos o artigo “Dicas para manter seu código limpo e legível” para que você possa melhorar cada vez mais a escrita de seus códigos.

JavaScript: você sabe o que são e por que usar Symbols?

Olá web developers!

Desde o ES2015 nós temos um novo tipo primitivo no JavaScript, os Symbols. Este tipo já existe em outras linguagens como o Ruby, onde é muito utilizado (no JavaScript seu funcionamento é um pouco diferente). Porém, no JavaScript ele ainda é pouco conhecido. Vamos conhecer melhor os Symbols e onde eles podem ser usados.

O que são Symbols?

De modo bem simples e direto, Symbol (símbolo) é um tipo primitivo que armazena um valor único. Seu propósito é esse, servir como um identificador único.

Pense neles como as suas digitais: até irmãos gêmeos idênticos possuem o mesmo DNA mas possuem digitais diferentes, significando que cada dedo é único.

Como os Symbols funcionam?

Para declarar um Symbol basta usar a função Symbol().

const meuSymbol = Symbol();

Também podemos passar para esta função um parâmetro que indica uma descrição do Symbol. Isso serve apenas para nos ajuda na hora de debugar o código, para sabermos melhor sobre do que se trata aquele Symbol. Não muda nada em sua criação.

const meuSymbol = Symbol('meu simbolo');

Como Symbols são únicos, mesmo que a gente os crie de forma igual teremos Symbols diferentes.

Symbol('meu simbolo') === Symbol('meu simbolo'); // false

Onde usar Symbols?

E por que usar Symbol? Onde seria interessante o seu uso? Vamos ver alguns casos:

Eficiência no uso de memória

Imagine que a gente tenha um objeto com uma propriedade nome:

const meuObjeto = {'nome': 'TreinaWeb'};

Sempre que chamarmos meuObjeto['nome'] estaremos criando uma String nova na memória, 'nome'. Em um sistema grande com várias chamadas a propriedades de objetos, onde a otimização do uso de memória é essencial, usar Symbols poderia ajudar, já que cada Symbol é único e, portanto, ocupa um único lugar na memória.

const nome = Symbol();
const meuObjeto = {[nome]: 'TreinaWeb'};

Agora sempre que acessarmos a propriedade nome iremos usar o Symbol que está armazenado na variável nome, nos ajudando a melhorar o consumo de memória.

Porém, a gente poderia fazer isso com Strings salvas em constantes, não é mesmo? Acessando a mesma variável também teremos acesso ao mesmo elemento na memória.

const nome = 'nome';
const meuObjeto = {[nome]: 'TreinaWeb'};

Vamos ter o mesmo resultado, mas como agora estamos usando String, um outro programador pode muito bem escrever direto meuObjeto["nome"] para acessar a propriedade, criando uma nova String na memória e jogando no lixo a melhoria que tentamos fazer.

Com Symbols o programador seria forçado a usá-los para acessar as propriedades pois, por serem únicos, não tem como criar um outro idêntico.

Sim, eu sei que o programador poderia escrever meuObjeto.nome e não estaria criando uma nova String, mas isso é só para o exemplo.

Mesmo sendo um caso de uso, não fica muito bonito ter que ficar declarando várias propriedades antes, seja por Symbols ou constantes com Strings. Mas há momentos em que realmente precisamos declarar vários valores que serão constantes, e é aí que entra algo comum em várias linguagens, Enums.

Enums

Enuns são muito usados quando precisamos definir várias constantes.

Imagine que a gente vai desenvolver um jogo e queremos criar constantes para definir as direções que o nosso personagem poderá andar.

// nosso Enum de direções
const directions = {
    RIGHT: Symbol('Right'),
    LEFT: Symbol('Left')
}

/* função a ser chamada
quando o jogador
apertar algum botão,
recebendo a tecla que
foi pressionada
*/
function onButton(key){
    /* lógica para movimentar
    o personagem */
    switch(key){
        case directions.RIGHT: x++; break;
        case directions.LEFT: x--; break;
    }
}

Com Symbols nós obrigamos o programador a utilizá-los. Se usássemos Strings ele poderia escrever em algum lugar case "RIGHT", o que criaria uma nova String. Para termos de manutenção, testes e performance, é melhor evitar ficar escrevendo valores literais pelo código.

Colisão de Nomes

Como Symbols são únicos, é impossível haver colisão de nomes.

Imagine que a gente tenha um módulo JavaScript com um objeto. Esse objeto possui uma propriedade contador que foi criada com Symbol e temos uma função que incrementa o nosso contador.

const contador = Symbol();
export const meuObjeto = {
    [contador]: 1,
    incrementar(){
        meuObjeto[contador]++;
    }
};

Podemos importar nosso objeto e executar o método incrementar(). Porém, mesmo se criarmos uma outra variável contador com Symbol(), não estaremos sobrescrevendo o contador do módulo. Como todo Symbol é único, nossa variável contador de um módulo é diferente da variável contador do outro módulo.

import { meuObjeto } from 'meuArquivo';

meuObjeto.incrementar(); // podemos incrementar

const contador = Symbol();
meuObjeto[contador] = 55; // esta propriedade será outra

Assim evitamos colisões de nomes acidentalmente. Isso pode acontecer caso você utilize duas bibliotecas que armazenam algo no escopo global.

Outro uso interessante para isso é poder acrescentar algo a um objeto já existente em um código, como uma biblioteca. Se você simplesmente alterar algo em um objeto de uma biblioteca, corre o risco de sobrescrever algo importante deste objeto e fazer a biblioteca parar de funcionar corretamente. Mesmo que você escolha um nome que não esteja em uso, há o risco de lançarem uma atualização utilizando exatamente este nome de propriedade.

Há bibliotecas que criam variáveis com "@@" ou "__" como prefixo de nomes de propriedades que não devem ser usados. Com Symbols não há como haver esse perigo de algo ser acessado ou sobrescrito sem querer, pois ao criar seu Symbol ele será único.

Separar propriedades do objeto

Quando criamos propriedades em objetos usando Symbols podemos dizer que estas propriedades são “escondidas”. Onde isso poderia ser interessante?

Imagine que a gente possua os seguintes objetos com algumas informações:

const minhaLista = [
    {nome: 'maçã', total: 15, ativo: true},
    {nome: 'banana', total: 28, ativo: true}
];

Object.keys(minhaLista[0]); // ['nome', 'total', 'ativo']

Agora pense que a gente queira jogar os campos “nome” e “total” em uma tabela usando um código que gera uma tabela automaticamente. Cada coluna da tabela deve ser uma propriedade do objeto, então basta pegar um dos objetos e passar para Object.keys() que receberemos um Array com o nome das propriedades. Porém isso nos trará o campo ativo junto, que é um campo que não queremos.

Se criarmos esse campo ativo com Symbols ele não será retornado.

const ativo = Symbol();

const minhaLista = [
    {nome: 'maçã', total: 15, [ativo]: true},
    {nome: 'banana', total: 28, [ativo]: true}
];

Object.keys(minhaLista[0]); // ['nome', 'total']

Esse é um exemplo bem simples, mas que pode ser muito interessante caso você esteja trabalhando com metaprogramação. Para obter uma lista com os Symbols podemos usar a função Object.getOwnPropertySymbols().

Erros sobre Symbols

Propriedades Privadas? NÃO!

Há um certo engano sobre Symbols. Como eles são únicos, algumas pessoas pensam que podemos tentar usá-los para evitar que algo seja acessado.

Imagine que a gente crie um objeto dentro de um módulo. Esse objeto possui uma propriedade criada com um Symbol.

const contador = Symbol();
export const meuObjeto = {
    [contador]: 1
};

/*aqui conseguimos
acessar a propriedade
criada com o Symbol
*/
meuObjeto[contador];

Como o Symbol não foi exportado, não conseguiremos acessar a propriedade de fora do módulo (será mesmo?).

import { meuObjeto } from 'meuArquivo';

meuObjeto[?????]; // não tem como acessar

Mas lembre-se que podemos obter os Symbols de um objeto, então há sim como conseguirmos acessar as propriedades “ocultas”.

import { meuObjeto } from 'meuArquivo';

const meuSymbol = Object.getOwnPropertySymbols(meuObjeto)[0];
meuObjeto[meuSymbol]; // conseguimos pegar o Symbol e acessar a propriedade que armazena o contador

Devido ao fato de podermos recuperar Symbols, eles não garantem propriedades privadas. Há outros meios para se fazer isso.

É impossível haver conflito de Symbols? Nem sempre

Se você criar Symbols como mostrado nesse post (utilizando a função Symbol()), realmente você terá um Symbol único. Mas há outra forma de criá-los.

Symbol('a') === Symbol('a'); // false
Symbol.for('a') === Symbol.for('a'); // true

Symbol.for() é um outro meio de se criar Symbols, mas ele os cria em um tipo de registro global. Então se você criar algo com Symbol.for() e passar um mesmo valor pelo parâmetro tanto em um Service Worker e um iFrame, por exemplo, teremos exatamente o mesmo Symbol.

Então, já que a ideia dos Symbols é serem únicos, prefira evitar o uso de Symbol.for().

Conclusão

A função de Symbols é apenas uma: servir como um identificador único.

Então podemos usá-los muito bem com objetos onde usaríamos alguma String para criar certas propriedades (como os Enums), utilizá-los para inserir metadados ou acrescentar algo em um objeto já existente sem o perigo de sobrescrever algo que possa ser importante para seu funcionamento.

Conceitos de linguagens funcionais: o que é currying?

O paradigma funcional e suas vertentes vêm ganhando cada vez mais destaque no mercado de maneira geral. Os benefícios das linguagens funcionais que vimos neste artigo justificam essa adoção mais intensa de soluções e linguagens que suportem este paradigma por parte do mercado.
Porém, junto com esta popularização, começam a surgir uma variedade de termos que antes eram incomuns e soam como algo extremamente complexo, como currying, pattern matching e monads, por exemplo. E, por muitas vezes, estes termos acabam por assustar quem está tendo os primeiros contatos com o mundo do paradigma funcional.
Nesta série de artigos, vamos começar a ilustrar alguns destes termos esquisitos. Iremos iniciar falando um pouco sobre currying.

Antes de tudo: quem foi Haskell Curry?

Toda essa história começa com um matemático norte-americano: Haskell Curry. Haskell inicialmente iniciou seus estudos superiores em medicina na Universidade de Harvard em 1916, mas decidiu estudar matemática no meio do caminho. Ainda trabalhou no campo de engenharia elétrica no MIT e logo após retornou à Harvard para obter uma pós-graduação em Física. Logo após, ainda obteve o título de Ph.D. na área de lógica combinatória, também pela Universidade de Harvard. Curry ainda trabalhou no projeto ENIAC (o primeiro computador digital do mundo) após a Segunda Guerra Mundial.

Haskell Curry (Gleb.svechnikov [CC BY-SA 4.0 (https://creativecommons.org/licenses/by-sa/4.0)])

A foto acima pertence a Gleb.svechnikov (CC BY-SA 4.0 (https://creativecommons.org/licenses/by-sa/4.0))

Curry fez um trabalho fantástico na área de lógica combinatória, área esta que era sua grande paixão. Seu trabalho matemático envolvendo lógica e análise combinatória inspirou e norteou uma série de princípios na área da computação, principalmente no que diz respeito justamente ao paradigma funcional e no cálculo-lambda (uma das bases do paradigma funcional). Além disso, Curry fundamentou um conceito importantíssimo para o paradigma funcional, conceito este que, em sua homenagem, recebeu seu sobrenome: currying. Ainda temos uma outra homenagem a Curry: a famosa linguagem funcional Haskell, que recebeu esse nome em homenagem ao matemático precursor dos conceitos de programação funcional.

O que é o currying em linguagens funcionais?

A definição clássica de currying diz o seguinte:

Dada uma função f do tipo f: (X x Y) → Z, a técnica de currying pode transformar a expressão em curry(f): → (Y → Z)

Calma! Não se desespere com a notação matemática. Ela é mais simples do que parece. 🙂
Vamos relembrar um conceito importante sobre funções matemáticas: elas não podem receber mais do que um parâmetro. Por definição, funções matemáticas só recebem um único parâmetro por vez.
Se levarmos esta situação para o ambiente de programação, criar métodos que recebem um único parâmetro por vez pode ser um complicador. Mas, se quisermos seguir o paradigma funcional, precisamos tentar seguir estas regras… Como podemos criar funções que recebam um único parâmetro por vez? Como poderíamos, por exemplo, criar uma função para multiplicar dois números recebendo um único parâmetro?
Vamos adotar o JavaScript para ilustrar esta situação. Se fôssemos escrever essa função de maneira “normal”, poderíamos ter o seguinte código:

function multiplicar(n1, n2) {
    return n1 * n2;
}
// ...
console.log(multiplicar(2, 2)); // A saída deverá ser "4"

Nossa função está recebendo dois parâmetros, e precisamos que ela receba um único parâmetro. Nós podemos obter esse resultado se montarmos uma composição de funções: podemos ter a primeira função recebendo n1, sendo que esta função retorna uma outra função que recebe n2! Nosso código poderia ficar da seguinte maneira:

function multiplicar(n1) {
    return function(n2) {
        return n1 * n2;
    }
}
// ...
const funcaoCurrying = multiplicar(2);
console.log(funcaoCurrying(2)); // A saída deverá ser "4"
// OU...
console.log(multiplicar(2)(2)); // A saída deverá ser "4"

Veja que temos funções que recebem um único parâmetro por vez, e estas funções podem retornar outras funções, formando uma cadeia de composição de funções. É exatamente o que a notação curry(f): → (Y → Z) na notação matemática que vimos no começo deste bloco diz: nós podemos ter uma função chamada curry gerada pelo encadeamento das funções Y e Z. No final, todas essas funções são funções que chamamos de “unárias”, pois elas lidam somente com um único parâmetro por vez.
O código acima ficaria muito mais elegante se utilizássemos arrow functions (ou funções-lambda):

const multiplicar = (n1) => (n2) => n1 * n2;
// ...
console.log(multiplicar(2)(2)); // A saída deverá ser "4"

Com o trecho acima, temos um código simples, elegante, funcional e utilizando a técnica de currying! 😉

Quais as vantagens obtidas através da utilização do currying?

Para mim, particularmente, nós temos uma série de vantagens interessantes com o currying:

  • Reaproveitamento de código. Utilizando currying, podemos criar uma série de funções genéricas que, quando combinadas, podem chegar à funcionalidade que desejamos, aplicando-se o conceito clássico de composição com maestria;
  • Criação de métodos que podem receber múltiplos parâmetros de maneira “emulada” em linguagens puramente funcionais: algumas linguagens funcionais deixam o conceito de currying implícito: basta você criar uma função que receba mais de um parâmetro que, internamente, ela será decomposta para múltiplas funções aplicando-se a técnica de currying. Porém, outras linguagens, como o Haskell, não aceitam essa abordagem de nenhuma maneira. Estas linguagens são conhecidas como linguagens puramente funcionais ou linguagens funcionais puras. Nestes casos, se quisermos criar métodos que recebam mais do que um parâmetro (o que é bem comum), nós precisaremos recorrer ao currying inevitavelmente;
  • A utilização do currying produz código elegante e conciso, mesmo em linguagens não funcionais. Currying não é um conceito que é aplicável somente em linguagens funcionais. Você pode aplicar a técnica de currying em linguagens que não são majoritariamente funcionais, como Java e C#, produzindo código legível e de fácil manutenção em praticamente qualquer linguagem;
  • No final, temos funções unárias. Funções unárias – as funções que recebem um único parâmetro – são mais legíveis e mais fáceis de serem manuteídas, pois geralmente são menores e possuem menos pontos de entrada (já que possuem um único parâmetro), o que reduz os possíveis pontos de efeito colateral durante uma alteração no código;
  • Você provavelmente já utiliza a técnica de currying. Se você analisar as APIs de map/reduce/filter do JavaScript, o LINQ do C# ou a stream API do Java, você verá que as APIs já trabalham com o conceito de funções que recebem um único parâmetro, ou mesmo funções que recebem funções como argumentos. Boa parte dos métodos destas APIs já utiliza conceitos de linguagens funcionais ,como o currying. Estas APIs só conseguem ser tão genéricas justamente por causa desse conceito de composição de funções que é natural ao conceito de currying.

Ainda temos muitos outros termos para vermos!

A programação funcional é de fato um mundo cheio de termos e jargões que podem soar esquisitos no início, mas que geralmente são mais simples do que parecem (como o conceito de currying). Ainda temos vários outros termos para serem explorados, como monads e partial function applications. Nos próximos posts, iremos continuar a desbravar estes termos esquisitos e que colocam medo por várias vezes em nós; mas que, no final, são mais simples do que parecem.
Até o próximo post! 😉

JUNTE-SE A MAIS DE 150.000 PROGRAMADORES