Categorias

E se o meu time não aceitar meu código funcional?

De acordo com a Wikipedia, “programação funcional é um paradigma de programação que trata a computação como uma avaliação de funções matemáticas e que evita estados ou dados mutáveis. Ela enfatiza a aplicação de funções, em contraste da programação imperativa, que enfatiza mudanças no estado do programa”. O que isso quer dizer na prática? Isso pode significar mais um modelo para as equipes, além da programação imperativa, da programação estruturada, da programação orientada a objetos… formas diferentes de abordar um problema podem gerar atritos desnecessários no mercado do trabalho, principalmente quando estamos falando de um paradigma menos conhecido.

Apesar do conflito de visões, a programação funcional tem ganhado cada vez mais adeptos. A técnica é adotada dentro do Nubank, por exemplo. Para Bruno Rodrigues, gerente de tecnologia do banco digital, a programação funcional e sua imutabilidade são peças chaves para a internacionalização de suas operações: “a lógica do sistema financeiro de um país pode ser diferente do de outro, mas conseguimos usar a mesma base para todos (…) Caso uma peça não encaixe direito, é fácil trocá-la por outra sem perder tudo o que já foi construído”.

James Sinclair é um desenvolvedor web australiano, autor do A Skeptic’s Guide to Functional Programming With JavaScript. Em um artigo publicado na internet, ele aborda um dos temas do seu livro e explica o que fazer quando o desenvolvedor adota os paradigmas da programação funcional, mas o resto do seu time não.

Com sua autorização, traduzimos e reproduzimos o artigo na íntegra:

“Permita-me um momento e imagine o seguinte cenário. Você acabou de voltar ao trabalho depois das férias de verão. E durante o intervalo, você trabalhou duro. Você levou algum tempo para aprender programação funcional. Você tem lido artigos, devorado livros, seguido tutoriais e praticado code kata. E por alguma razão, neste verão, algo clicou. De alguma forma, as peças se encaixaram. Faz sentido. A maneira como você pensa sobre o código nunca mais será a mesma.

Agora, porém, você está de volta ao trabalho. E você está ansioso para colocar em prática o que aprendeu. Você pega uma nova tarefa do backlog que surgiu enquanto você está fora. É fantástico. É como se o código estivesse fluindo pela ponta dos dedos direto para o computador. E você sabe que o código que está escrevendo é objetivamente melhor do que o código que você usava para escrever. Você está confiante de que funciona. Tem cobertura de teste abrangente. É menos provável que exploda, porque os efeitos colaterais são cuidadosamente gerenciados. Ele lida com erros graciosamente quando as coisas dão errado. Está limpo. É elegante. É expressivo. E você está justificadamente orgulhoso.

Em seguida, você cria uma solicitação pull (PR) para que a equipe possa revisar seu trabalho. E você não está esperando aplausos. Afinal, é apenas um código. Mas você espera que talvez o sênior perceba que houve uma mudança. Este código é sólido. E comparado a como você costumava escrever, está anos-luz à frente. Portanto, você fica mais do que surpreso quando comentários críticos começam a aparecer:

  • “Isso é muito inteligente. É ilegível.
  • “Como vamos depurar isso?”
  • “Você mediu para ver se isso degrada o desempenho?”
  • “Você já considerou o efeito desta nova biblioteca no tamanho do nosso pacote?”
  • “Muita mágica acontecendo aqui.”

Alguém até escreve “como os erros são tratados aqui?” no local exato onde você escreveu uma abstração elegante para fatorar o tratamento de erros. O código lida bem com os erros. Eles simplesmente não conseguem ver.

E, por mais que tente, você não pode deixar de se sentir magoado. É como se você estivesse sob ataque. Você está confuso. O código é melhor do que o que você costumava escrever. Você tem certeza disso. Mas eles estão agindo como se você colocasse fogo em um saco de papel cheio de cocô de cachorro e pedisse que eles o mesclassem com a base de código. O que está acontecendo?

Por que todo o ódio?

Para entender o que está acontecendo, precisamos dar um passo para trás e ter um pouco de perspectiva. Você está confiante de que o código é sólido. Funciona. E isso é bom. Mas embora o código correto e bem fatorado seja bom, o sênior também tem outras preocupações. Eles estão pensando em mais do que apenas este PR isoladamente. Eles estão pensando em questões como:

  • Vamos precisar de novos módulos de treinamento, da próxima vez que integrarmos alguém à equipe?
  • O que acontece se isso for interrompido na produção e precisarmos consertar rapidamente? Posso adicionar instruções de log ou definir pontos de interrupção de depuração para obter respostas rapidamente?
  • Qual será o desempenho desse código em escala, quando for distribuído para milhares de clientes?
  • Como manteremos a estrutura e o estilo do código de nossa equipe consistentes? Queremos que todos na equipe possam entrar em qualquer parte da base de código e se sintam confiantes para realizar o trabalho. Isso criará uma área que as pessoas evitarão?
  • O que acontece quando você sai? O restante da equipe entenderá esse código e poderá atualizá-lo com segurança?

Todas essas são preocupações legítimas. Para ser justo, alguns podem ser infundados se o sênior entender seu código um pouco melhor. Mas ainda são coisas que o sênior precisa avaliar.

Ao mesmo tempo, porém, você não pode voltar. Algo em seu cérebro mudou. Você não pode escrever um código que sabe ser inferior. A maneira que você usou para escrever o código era mais propensa a erros, complexa e confusa. Você não pode mais escrever assim. Mas isso parece ser o que o sênior quer… ou pelo menos, o que eles podem enfrentar. Você está preso. O que você faz?

Escrevendo para uma audiência

A saída desse dilema é entender algo. Se você faz algum tipo de curso de redação, o primeiro conselho que eles dão é “entenda seu público”. E, goste ou não, o código não é diferente. Nós escrevemos código para um público humano.

Qualquer tolo pode escrever um código que um computador possa entender. Bons programadores escrevem códigos que humanos podem entender.

Martin Fowler, 2008, Martin Fowlerhttps://en.wikiquote.org/wiki/Martin_Fowler

Nós escrevemos nosso código para se adequar ao nosso público. E isso envolverá alguns compromissos. O que soa como escrever um código inferior. Mas não precisa ser assim. Podemos encontrar maneiras de escrever código que o tornem mais familiar para os outros, sem perder nossa confiança. A abordagem específica que você adotar mudará, dependendo do seu público. Mas quase sempre você pode encontrar uma maneira de tornar o código mais familiar para outras pessoas.

Por exemplo, suponha que você tenha escrito algum código como o seguinte:

const renderNotifications = flow(
  map(addReadableDate),
  map(addIcon),
  map(formatNotification),
  map(listify),
  wrapWithUl
);

Se você estiver familiarizado com flow() e composição de funções, esse código não lhe dará muitos problemas. Não é muito difícil descobrir o que está acontecendo. Começamos com uma lista de notificações e, para cada uma, adicionamos uma data legível e um ícone. Em seguida, formatamos cada item (de alguma forma) e o envolvemos em um elemento <li>. Por fim, envolvemos a lista com um <ul>.

Este código tem alguns aspectos que podem deixar nosso hipotético sênior nervoso, no entanto. Para começar, usar flow() oculta os parâmetros de entrada da função. Isso pode não incomodar você ou eu. Podemos descobrir o que está acontecendo porque sabemos o que flow() faz. Mas para alguém não familiarizado com flow(), isso pode ser confuso. E podemos fazer uma pequena mudança que os ajudará a se sentirem mais confortáveis:

const renderNotifications = (notifications) => pipe(
  notifications,
  map(addReadableDate),
  map(addIcon),
  map(formatNotification),
  map(listify),
  wrapWithUl
);

Se mudarmos de flow() para pipe(), a maior parte do código permanece a mesma. Mas reintroduzimos o parâmetro de entrada. E isso pode ajudar as pessoas que não estão acostumadas com flow().

Para muitas pessoas, porém, usar pipe() e flow() pode ser um problema. Eles podem simplesmente nunca ter encontrado esse tipo de coisa antes e não entender como funciona. Mas, talvez eles estejam bem em chamar métodos em objetos. Podemos reescrever nossa função para usar chamadas de método encadeadas:

const renderNotifications = (notifications) =>
  wrapWithUl(
    notifications
      .map(addReadableDate)
      .map(addIcon)
      .map(formatNotification)
      .map(listify)  
  );

Se notifications for um array, podemos alternar para a chamada de .map() e encadear as chamadas de método. Mas, a função wrapWithUl() é um pouco problemática. Não está embutido no protótipo do array, então temos que envolver a chamada de função em tudo. Isso torna a ordem menos clara. Podemos corrigir isso, porém, adicionando uma variável intersticial:

const renderNotifications = (notifications) => {
  const renderedItems = notifications
      .map(addReadableDate)
      .map(addIcon)
      .map(formatNotification)
      .map(listify);
  return wrapWithUl(renderedItems);
};

Agora, é até possível que algumas pessoas achem demais essas chamadas de método encadeadas. Nesse caso, podemos adicionar uma variável intersticial para cada linha:

const renderNotifications = (notifications) => {
  const withDates = notifications.map(addReadableDate);
  const withIcons = withDates.map(addIcon);
  const formattedItems = withIcons.map(formatNotification);
  const listItems = formatedItems.map(listify);
  return wrapWithUl(listItems);
};

Já escrevemos esse código de cinco maneiras diferentes. Mas observe o que não fizemos. Não introduzimos nenhum efeito colateral. Ainda estamos trabalhando com funções puras, cada uma fazendo uma pequena coisa. Não adicionamos nenhum estado mutável compartilhado. E como não fizemos essas coisas, ainda podemos confiar em nosso código. Ainda é fácil de testar. Ainda é ‘funcional’.

Alguém pode estar se perguntando: ‘por que não pular direto para a quarta ou quinta versão? Por que não tornar o código o mais legível possível para todos?’.

Essa é uma pergunta razoável. Na verdade, é mais do que razoável, é admirável. É bom tornar as coisas acessíveis. Mas, novamente, tudo se resume ao propósito e ao público para o qual você está escrevendo. Não começamos um artigo de física com uma recapitulação das leis da termodinâmica. Se você está escrevendo um artigo acadêmico sobre física, espera que o público os conheça. E incluí-los seria tedioso para os leitores. Isso tornaria a vida deles mais difícil, não mais fácil. E a mesma coisa vale para escrever código. Públicos diferentes irão preferir estilos diferentes. E diferentes pessoas precisarão de ajuda com diferentes aspectos do código.

Além disso, existem algumas vantagens no estilo de composição de funções. (Como em nosso primeiro e segundo exemplos). Quando trabalhamos com composição de funções, isso muda a maneira como pensamos sobre nosso código. Isso abre diferentes possibilidades de como combinamos e reutilizamos o código. Se a equipe conseguir, queremos usar esse estilo para abrir essas possibilidades. Mas nem sempre eles entendem. E tudo bem. Ainda podemos manter as principais vantagens da programação funcional. Podemos escrever código e ter certeza de que ele funciona. E com alguma sorte, o resto da equipe começará a entender com o tempo.”

Publicado originalmente como “What if the team hates my functional code?” em 18 de outubro de 2022. Traduzido e republicado com autorização do autor.