Skip to main content

1. Introdução ao Clean Code aplicado ao Pyspark

O termo Clean Code representa um conjunto de técnicas, independentes de linguagem de programação, que tem a finalidade de guiar a escrita de softwares para uma interpretação legível por humanos. Embora os códigos fontes sejam interpretados/compilados por máquinas, são os humanos até o presente momento que dão manutenção neles.

A proposta deste conceito, em que a preocupação está em desenvolver código de fácil entendimento, tem também a finalidade de facilitar a manutenção ou a correção de bugs.

O  conceito  foi  amplamente  difundido  no  livro  “Clean  Code:  A  Handbook  of  Agile Software  Craftsmanship”, escrito  por  Robert Cecil Martin, programador  desde a  década de 1970. Com toda a experiência acumulada em vários projetos, ele desenvolveu este livro que pode-se resumir como um compilado de boas práticas. Este livro faz parte de listas dos melhores livros  de  programação  e  está  nos  top  10  livros  mais  vendidos  na  Amazon  dos  EUA.  A importância de um código bem feito é enfatizado neste livro, como por exemplo mencionando que a proporção de tempo gasto lendo um código comparado o tempo de escrita é maior do que 10:1.

Além de facilitar a compreensão de quem irá atuar no código em um futuro, é o tipo de legado  e  reconhecimento  que  o  programador  quer  deixar.  Assim  como  um  escritor  será reconhecido por suas obras, o programado será reconhecido e julgado pelos seus códigos.

2. Nomes Significativos com relação ao Clean Code

Os  nomes  estão  por  toda  parte  no  software.  Temos  de  nomear  as  nossas  variáveis, funções, bibliotecas, dataframes, arquivos de origem e os diretórios que os contém. Porque nós nomeamos  com  frequência, temos  de  fazê-lo  bem, porém podemos  nos  utilizar de  simples regras para criar bons nomes.

Usar nomes que revelam a intenção é sempre uma boa saída, pois todas as pessoas que porventura forem ler o seu código, com clean code, ficarão mais felizes, incluindo nós mesmos.

O nome de uma variável, função ou dataframe deve responder a algumas perguntas, como para que existe, o que faz e como é usado. Se o nome precisa de um comentário, já não revela a sua intenção.

No  PySpark,  as  convenções  de  nomenclatura  das  bibliotecas  são  um  tanto  quanto confusas,  o  que  torna  a  coisa  um  pouco  inconsistente,  no  entanto  existem  padrões recomendados. Os módulos novos e pacotes, incluindo framework de terceiros, necessitam ser gravados com esses padrões, porém quando uma biblioteca existente possui um estilo diferente, a consistência interna é preferida.

Os seguintes estilos de nomenclatura são mais comumente distinguidos:

Por  padronização,  os  códigos  PySpark  preferencialmente  seguem  o  padrão  PEP  8  de nomenclatura, pórem, deve se saber qual o alinhamento da equipe para o uso de padrões.

Os seguintes estilos do PEP 8 de nomenclatura seguem abaixo:

  • Nome  de  variável/dataframe:  minúsculas_com_sublinhados  por  extenso  e  que representem a real intenção
Clean Code

Nome de classe: CamelCase – Cada palavra iniciando por maiúscula e deve ser um substantivo que represente a abstração do problema.

clean code_

Nome de função: Deve estar em minúscula separados por sublinhados e ser um verbo que descreva a ação requerida pelo problema.

É recomendável não utilizar os caracteres ‘l’ (letra minúscula ele), ‘O’ (letra maiúscula oh) ou ‘I’ (letra maiúscula i) como nomes de variáveis de caractere único pois em algumas fontes esses caracteres são indistinguíveis dos números um e zero.

Os  módulos  e  os  pacotes  devem  possuir  nomes  curtos  e  em  letras  minúsculas.  Os sublinhados devem ser utilizados no nome do módulo se forem melhorar a legibilidade, já para os pacotes o seu uso não é recomendado.

Os  nomes  das  funções  devem  estar  em  letra  minúscula,  com  palavras  separadas  por sublinhados, para melhorar a legibilidade, os nomes das variáveis seguem a mesma convenção. Caso o nome de um argumento de uma função entrar em conflito com uma palavra-chave reservada, geralmente é melhor anexar um único sublinhado à direita ao invés de usar uma abreviação ou corrupção ortográfica. Assim, class_ é melhor do que clss. (Melhor evitar esse tipo de situação utilizando um sinônimo)

Ao se escolher os nomes em um código devemos ter em mente a clareza e nunca utilizar coloquialismos ou gírias. Expressar o significado da ação é o mais recomendado a se fazer seguindo as regras descritas mais acima escolhendo uma palavra para um conceito abstrato e ficando com ela.

Um exemplo dessa situação é utilizar as palavras buscar, recuperar e obter como funções equivalentes de diferentes classes, não dá para lembrar facilmente qual nome de função combina com  qual  classe.  Podemos  passar  muitas  horas  tentando  nos  lembrar  qual  pessoa  escreveu determinada função ou classe ou navegando por cabeçalhos e códigos anteriores só porque não fomos claros na nossa intenção ao nomear os objetos.

Podemos também utilizar termos de ciência da computação em geral para nomear os objetos, afinal de contas os códigos são lidos por programadores, porém não é aconselhável escrever todos os nomes do domínio do problema em uma variável ou em uma função por exemplo pois é desagradável ter que recorrer ao cliente perguntando o que cada nome significa quando já se conhece o conceito por um nome diferente.

Existem alguns nomes que são significativos por si, porém a maioria não é, as vezes é necessário  contextualizar o leitor, uma maneira  de fazer  isso  é prefixar  o nome. Variáveis chamadas primeiro_nome, ultimo_nome, numero_casa, rua, cidade, estado e codigo_postal quando juntas deixam claro que fazem parte de um endereço, porém se alguma delas estivesse sozinha  em  uma  função  seria  interessante  prefixá-las.  Usamos  como  exemplo  nesse  caso endereco_primeiro_nome, endereco_rultimo_nome,… e assim por diante, tornando elas parte de uma estrutura maior.

O mais difícil de se escolher bons nomes é que requer boas habilidades descritivas e uma ampla formação cultural. Essa é uma questão de ensino e não uma questão técnica, de negócios ou de gerenciamento. Como resultado, muitas pessoas não aprendem a fazê-lo muito bem.

Em geral as pessoas têm medo de renomear os objetos por medo da oposição de outros desenvolvedores, na maioria das vezes não memorizamos os nomes das classes e funções pois usamos ferramentas modernas para lidar com detalhes como esses e possamos nos concentrar no código e nas sentenças a desenvolver. Ao nomearmos ou renomearmos um objeto seguindo o clean code podemos surpreender a nós mesmos e a todos com uma melhoria no código como um todo e valerá a pena a curto, médio e longo prazo.

3. Formatação

•A Afinidade Conceitual, a Distância Vertical, e o Encadeamento de Expressões

A  linguagem  PySpark  contém  alguns  atributos  bastante  utlizados  em  códigos  em produção, que acompanham a lógica de tratamento de dados em um fluxo tradicional, entre eles o encadeamento de expressões.  Por exemplo, imagine que fosse necessário modificar um dataframe a partir da seleção de colunas originais, filtrando-as por valores pré-determinados, com a adição de novas colunas, e ao final promovendo-se o join com outros dataframes, removendo-se também colunas que foram usadas temporariamente para o processo, como no exemplo a seguir:

À  primeira  vista,  este  recurso  da  linguagem  favorece  a  aplicação  de  dois  conceitos importantes em Clean Code:

  • A finidade Conceitual: grupos de funções de operações similares ou afins tendem a se manter juntos. Neste caso, a afinidade gira em torno da diversas alterações promovidas em um único dataframe.
  • Distância Vertical: conceitos que são intimamente relacionados devem ser mantidos a uma distância vertical pequena. Neste exemplo, estamos promovendo alterações que possuem um encadeamento lógico bastante bem definido e com a ordenação correta para o efeito desejado.

Embora este exemplo seja razoavelmente simples, é comum encontrar o encadeamento de inúmeras funções aplicadas a uma única transformação e que, dependendo do seu tamanho e  complexidade,  dificultam  a  busca,  entendimento  e  a  manutenção  através  de  novas alterações.  Além  disso,  é  importante  notar  que  os  comportamentos  individuais  das funcionalidade encadeadas trazem contextos diferentes (seleção de colunas, filtragem, criação de colunas, joins, e deleção de colunas).

Se no futuro houver a necessidade de alteração ou inclusão de novos parâmetros de filtragem,  ou  criação  de  novas  colunas  e  joins  com  outros  dataframes,  existem  grandes chances de que a ligação lógica entre cada contexto seja quebrada. Por este motivo uma abordagem mais adequada seria a aplicação de conceitos de responsabilidade única, ou de maneira similar, de isolamento dos contextos:

Nesta  versão  estamos  realizando  a  seleção  de  colunas  e  filtragem  dos  registros necessários no primeiro passo, a criação de colunas no segundo passo e o join com outros dataframes, junto com a deleção de colunas, no terceiro passo. Desta forma, em sistemas mais complexos, seria possível promover alterações ou manutenções paralelas por mais de um desenvolvedor, mesmo em contextos diferentes, desde que guiados pela mesma especificação, com a garantia de funcionamento da solução final.

•Operações lógicas encadeadas

Em  alguma  ocasiões,  durante  os  processos  de  consolidação  de  dados  é  comum  nos depararmos com a necessidade de gerar resultados a partir da comparação de múltiplos dados e em combinações diversas. Por exemplo, se estivermos lidando com o contexto de contas bancárias e relacionamento de pessoas físicas com instituições financeiras, talvez se torne necessário classificar clientes em função de tipo de conta (corrente, poupança, salário), status de titularidade (titular, co-titular, avalista, responsável, procurador, representante), situação de bloqueio  (juridicial,  administrativo,  cadastral),  uso  da  conta  (ativa,  paralisada,  inativa, encerrada, reativada, inativa excluída), etc. Nestes casos é comum a criação de lógicas que combinam estes e ainda outras informações pertinentes em longas cadeias que são de difícil entendimento e manutenção.

Para facilitar a explicação, vamos usar um exemplo mais simples.

Considerando que as expressões em PySpark são sempre delimitadas por parênteses, quando se necessita agrupar operações lógicas também são exigidos parênteses. Isso faz com que a leitura e entendimento fique ainda mais dificultado.

De maneira análoga ao exemplo do encadeamento de expressões no caso anterior, o ideal seria separar os filtros e condições “intermediários”, que recebem, também, nomes que têm as qualidades esperadas no Clean Code:

A expressão F.when agora pode ser compreendida imediatamente, e qualquer alteração nos conceitos de ‘delivery_date_passed’ ou ‘is_delivered’, por exemplo, podem ser feitas de maneira independente, com risco mínimo de afetar as outras condições e filtros, permitindo a evolução do código.

•Expressões em Múltiplas Linhas

O PySpark é, na verdade, uma API Python para o Spark, embora o suporte da ferramenta permita também o desenvolvimento em Java e Scala. Por ter sido desenvolvido suportando diversas linguagens, no que tange ao Python, é preciso atenção quando do uso de expressões em múltiplas linhas, que exigem ou o uso de quebras explícitas, ou o uso de parênteses para separação.

A fim de  evitar o  uso  de quebras explícitas  ou o  uso  excessivo de  parênteses  para separação e agrupamento, que dificultam a leitura e em caso de esquecimento podem causar diversos problemas, as recomendações de Clean Code para estes casos é o de separar as expressões por um conjunto dedicado de parênteses:

4. Conclusões ao Clean Code

O código limpo não é escrito apenas seguindo um conjunto de regras, princípios ou padrões, mais do que isso ele requer bom senso e trabalho duro através de muita prática para que possamos detalhar e formalizar cada linha afim de que possam ser lidos de uma forma mais fluida tanto por outro programador como pela máquina. Embora existam regras que se aplicam em vários cenários, sempre é necessário alinhar as boas práticas com o time de desenvolvimento.

Através do uso de código limpo é fácil perceber que embora se traduzam em conceitos técnicos, em médio e longo termos, para a empresa, muito mais do que valor de negócio, pode significar sua sobrevivência e longevidade. As empresas que não conseguiram manter seus produtos atrativos,  com  a  inclusão  de  recursos  seguindo as tendências  e necessidades de mercado, são a melhor prova disso.

Como destacado na introdução, a proporção entre leitura e escrita de código é de 10:1, entende-se que a necessidade de investir tempo na escrita poderá facilitar e otimizar o tempo de leitura.

Gostou do artigo? Continue navegando no Semantix Lab e veja muito mais artigos!

REFERÊNCIAS
  • Amazon  Best  Sellers:  Best  Computer  Programming. 2021.  Disponível  em: <https://www.amazon.com/Best-Sellers-Books-Computer-Programming/zgbs/books/3839/>. Acesso em: 20 dez. 2021
  • Palantir Technologies. This is a guide to PySpark code style presenting common situations and the associated best practices based on the most frequent recurring topics across   the   PySpark   repos   we’ve   encountered.  2021.   Disponível   em: <https://github.com/palantir/pyspark-style-guide/>. Acesso em: 02 dez. 2021
  • Martin(2008)  Robert  C.  Martin.  Clean  Code  –  A  Handbook  of  Agile  Software Craftsmanship. Prentice Hall
Autores:
– Alan Mattos Neves Pinto: Analista de Dados Sr. – Responsável pelo desenvolvimento, implantação e manutenção dos pipelines de dados
Anderson Luiz Nichele: Engenheiro de dados – Profissional com mais de 10 anos de experiência na área de TI

Leave a Reply