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
Nome de classe: CamelCase – Cada palavra iniciando por maiúscula e deve ser um substantivo que represente a abstração do problema.
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