-

Ao estudar diferentes linguagens de programação ao longo dos anos, fui atraído pela família Lisp devido à sua simplicidade e poder. Lisp, criado por John McCarthy em 1958, é conhecido por suas características distintas, como a notação de código como listas de dados (daí o nome LISt Processing) e sua capacidade de manipular código como uma estrutura de dados. Emacs Lisp e Common Lisp são dois dos muitos dialetos que evoluíram a partir do Lisp original, cada um com suas próprias peculiaridades e casos de uso.

-

Minha jornada com Lisp levou-me ao Clojure pela conveniência de rodar na JVM, uma vez que Clojure é um moderno dialeto Lisp que roda na Java Virtual Machine (JVM). Optei pelo Clojure porque já tinha uma base sólida em Java, e Clojure oferece interoperabilidade sem emenda com código Java. Esta transição também foi influenciada pela minha crescente frustração com algumas complexidades encontradas na programação orientada a objetos (OOP), especialmente em Java.

-

Ao longo dos anos, percebi que a construção de hierarquias de classes complexas muitas vezes introduz uma complexidade desnecessária quando se trata de programação orientada a objetos (POO/OOP). Esse tipo de complexidade, especialmente em linguagens como Java, advém não dos desafios inerentes ao problema a ser resolvido, mas das metodologias e tecnologias escolhidas para o desenvolvimento. Para ilustrar, considere o processo de montar um modelo de avião a partir de um kit de modelagem.

-

O objetivo final é simples: construir um modelo que se pareça com um avião. No entanto, se o kit contém peças que se encaixam de maneiras específicas e exige um conjunto complexo de instruções para cada passo, o processo se torna desnecessariamente complicado. Isso se compara à maneira como a programação orientada a objetos pode obrigar nós, os desenvolvedores a seguir protocolos rígidos e a usar padrões que não necessariamente se alinham com as necessidades simples do problema, resultando em complexidade acidental e aumento do esforço de desenvolvimento.

-

Tal como a montagem de um modelo de avião poderia ser simplificada com um design mais intuitivo e menos peças, a programação poderia beneficiar-se de abordagens que reduzam a rigidez e a complexidade não essencial.

-

Este exemplo tenta capturar a ideia de que complexidades adicionais podem surgir não por causa do problema em si, mas devido às ferramentas e métodos escolhidos para abordá-lo. Essa complexidade se manifesta através de práticas comuns que podem complicar o código desnecessariamente. Por exemplo, a proliferação de POJOs (Plain Old Java Objects) e códigos boilerplate é notória.

-

Os POJOs, simples em uso, podem levar ao excesso de classes que servem principalmente para armazenar e recuperar dados sem conter métodos significativos. O código boilerplate, repetido em diversas partes do aplicativo, inclui a implementação frequente de métodos como get e set, hashCode, equals e toString, que, apesar de necessários, podem obscurecer a lógica principal do código e aumentar a carga de manutenção.

+

Table of Contents

+
    +
  1. Introdução
  2. +
  3. Descobrindo Clojure: Uma Ponte entre Lisp e Java
  4. +
  5. Manutenibilidade
  6. +
  7. Transparência referencial
  8. +
  9. Problemas de Reutilização
  10. +
  11. Pattern Null Object
  12. +
  13. Pattern Singleton
  14. +
  15. Pattern Observer
  16. +
+

Introdução

+

Ao estudar diferentes linguagens de programação, fui atraído pela família Lisp devido à sua simplicidade e poder. Lisp, criado por John McCarthy em 1958, é conhecido pela notação de código como listas de dados e pela capacidade de manipular código como uma estrutura de dados. Emacs Lisp e Common Lisp são dois dos muitos dialetos que evoluíram a partir do Lisp original, cada um com suas próprias peculiaridades e casos de uso. Mas o que torna o Lisp tão atraente em comparação com linguagens mais modernas?

+

O Lisp continua a ser atraente em comparação com linguagens modernas principalmente notação prefixada e a natureza homoicônica (onde o código é tratado como uma lista de dados que pode ser manipulada pelo próprio programa) permitem uma flexibilidade incrível na macro expansão e manipulação de código. Isso torna o Lisp excepcionalmente poderoso para escrever código compacto e expressivo que pode modificar sua própria estrutura, o que é mais difícil em linguagens mais rígidas e menos dinâmicas.

+

Por exemplo, a introdução de dialetos Lisp como shcl, uma implementação de um shell script em Lisp que descobri recentemente, demonstra como essas características podem ser úteis em ambientes modernos. Além do tratamento de exceções com blocos try/catch, ele suporta recursos como logging avançado, gerenciamento automático de memória através de garbage collection e uma interface para manipular ambientes de execução de forma mais controlada. Essas funcionalidades enriquecem o shell, transformando scripts simples em aplicações robustas e menos propensas a erros.. Por exemplo:

+
1(try
+2  (command-that-may-fail)
+3  (catch 'error e
+4    (println "Erro capturado: " e)))
+

Este exemplo ilustra como o Lisp, através de suas extensões e dialetos, se adapta e continua relevante, trazendo suas características poderosas para novos domínios e desafios na programação contemporânea.

+

Descobrindo Clojure: Uma Ponte entre Lisp e Java

+

Minha jornada com Lisp levou-me ao Clojure, um moderno dialeto que roda na Java Virtual Machine (JVM). Escolhi o Clojure por causa da interoperabilidade com código Java, já que eu tinha uma base sólida em Java. Esta transição foi também influenciada pela frustração com as complexidades da programação orientada a objetos (OOP) em Java.

+

Percebi que a construção de hierarquias de classes complexas frequentemente introduz uma complexidade desnecessária na OOP. Essa complexidade, especialmente em Java, surge mais das metodologias e tecnologias escolhidas do que dos desafios inerentes ao problema a ser resolvido. Por exemplo, montar um modelo de avião a partir de um kit pode ser simplificado com um design mais intuitivo e menos peças, semelhante à forma como a programação pode se beneficiar de abordagens que reduzem a rigidez e a complexidade não essencial.

+

Complexidades adicionais surgem não por causa do problema em si, mas devido às ferramentas e métodos escolhidos. Isso se manifesta através de práticas que complicam desnecessariamente o código, como a proliferação de POJOs (Plain Old Java Objects) e códigos boilerplate. POJOs, embora simples em uso, podem resultar em excesso de classes que servem principalmente para armazenar e recuperar dados. O código boilerplate, repetido em várias partes do aplicativo, inclui a implementação frequente de métodos como get e set, hashCode, equals e toString, que obscurecem a lógica principal do código e aumentam a carga de manutenção.

Considere o exemplo clássico abaixo na classe Employee abaixo, em Java, que exemplifica essa questão:

 1public class Employee {
  2    private String name;
@@ -204,15 +221,14 @@ 

Java e Clojure

20 21(defn set-employee-department [employee new-department] 22 (assoc employee :department new-department)) -

Aqui, defrecord cria uma estrutura de dados com campos nomeados, que também gera funções para acessar e modificar esses campos, promovendo a imutabilidade. Isso reduz a probabilidade de erros comuns em programas, como alterações de estado não intencionais, e simplifica o entendimento, teste e manutenção do código.

-

Essa abordagem minimiza a cerimônia e o boilerplate típicos da OOP em Java, focando mais nos dados e comportamentos reais do que na estrutura de classes. Ou seja, reduz a complexidade estrutural e foca em resolver o problema da forma mais transparente e segura possível. Além disso o defrecord também implementa automaticamente interfaces para serialização e outras funcionalidades, oferecendo mais do que apenas uma simplificação do acesso aos dados.

-

E aqui é essencial reconhecer que muito depende das escolhas e habilidades do desenvolvedor. No entanto, dentro de um consenso geral, a própria linguagem, juntamente com seus frameworks, bibliotecas e a filosofia subjacente da POO, tendem a conduzir os desenvolvedores por esse caminho de complexidade aumentada. Java, como uma linguagem projetada com uma forte inclinação para a POO, encoraja a criação de extensas hierarquias de classes e o uso de padrões de design que, embora úteis em muitos contextos, podem também adicionar camadas de complexidade que não são diretamente pertinentes à lógica de negócios em questão.

-

Os frameworks Java, como Spring e Hibernate, por exemplo, oferecem poderosas ferramentas de abstração que simplificam o desenvolvimento em muitos aspectos, mas que também podem levar a um código altamente acoplado e difícil de gerenciar se não forem usados com discernimento. Os POJOs e o código boilerplate, como já discutido, são exemplos claros de como as práticas comuns em Java podem contribuir para o aumento considerável dessa complexidade.

+