Executando verificação de segurança...
1

Código simples é melhor do que código espertinho

Você sabe por quê programadores não gostam de temas claros? Porque luz atrai insetos!... Insetos... BUGS... Sacou?... Desculpa.

Recentemente, eu criei meu blog (https://andre.pro), e decidi que queria a capacidade de trocar o tema de cores entre claro e escuro. Então, aproveitei a deixa para treinar um pouquinho de Web Components, e criei um pequeno componente para essa tarefa.

O resultado ficou simples e funcional, mas uma coisa estava me incomodando neste trecho de código aqui:

// theme-changer-component.js

export default class ThemeChanger extends HTMLElement {

    ...

    createDOMTree() {
        const lang = this.getAttribute('lang');

        const lightThemeButton = document.createElement('button');
        lightThemeButton.classList.add('light-theme-button');
        lightThemeButton.type = 'button';
        lightThemeButton.innerText = 'A';
        lightThemeButton.title =
            lang === 'pt-BR' ? 'Tema Claro' : 'Light Theme';

        this.lightThemeButton.addEventListener(
            'click',
            this.handleLightThemeButtonClick.bind(this)
        );

        const darkThemeButton = document.createElement('button');
        darkThemeButton.classList.add('dark-theme-button');
        darkThemeButton.type = 'button';
        darkThemeButton.innerText = 'A';
        darkThemeButton.title =
            lang === 'pt-BR' ? 'Tema Escuro' : 'Dark Theme';

        this.darkThemeButton.addEventListener(
            'click',
            this.handleDarkThemeButtonClick.bind(this)
        );

        const lightThemeButtonContainer = document.createElement('div');
        lightThemeButtonContainer.classList.add('icon-container');
        lightThemeButtonContainer.append(lightThemeButton);

        const darkThemeButtonContainer = document.createElement('div');
        darkThemeButtonContainer.classList.add('icon-container');
        darkThemeButtonContainer.append(darkThemeButton);

        const wrapper = document.createElement('div');
        wrapper.ariaHidden = true;
        wrapper.classList.add('wrapper');
        wrapper.append(lightThemeButtonContainer);
        wrapper.append(darkThemeButtonContainer);

        return wrapper;
    }

    ...

}

Eu não conseguia parar de pensar: "Olha a quantidade de vezes que estou criando um elemento manualmente, depois setando atributos manualmente! Tem que haver um jeito mais conciso de fazer isso."

Então achei que era uma boa ideia escrever um pequeno utilitário para criar elementos HTML:

// createElement.js

export const a = new Proxy({}, {
    get: (target, name) => {
        if (!target[name])
            target[name] = (
                properties => createElement(name, properties)
            );
        return target[name];
    },
});

function createElement(name, properties) {
    const element = document.createElement(name);

    for (let key in properties) {
        if (key === 'children') {
            element.append(...properties[key])
            continue;
        }
        element[key] = properties[key];
    }

    return element;
}

Este utilitário permite criar elementos mais ou menos assim:

// theme-changer-component.js

import { a } from './createElement.js';

export default class ThemeChanger extends HTMLElement {

    ...

    createDOMTree() {

        ...

        // antes
        const lightThemeButton = document.createElement('button');
        lightThemeButton.classList.add('light-theme-button');
        lightThemeButton.type = 'button';
        lightThemeButton.title = lang === 'pt-BR' ? 'Tema Claro' : 'Light Theme';
        lightThemeButton.innerText = 'A';

        // depois
        const lightThemeButton = a.button({
            classList: ['light-theme-button'],
            type: 'button',
            title: lang === 'pt-BR' ? 'Tema Claro' : 'Light Theme',
            innerText: 'A',
        });

        ...

    }

    ...

}

Interessante! Esse código parece ter ficado bom e precisei digitar um pouco menos quando comparado à API nativa do DOM.

Uau! Essa ideia foi realmente muito esperta!

Agora, ao reescrever o código aplicando este utilitário, o resultado fica assim:

// theme-changer-component.js

import { a } from './createElement.js';

export default class ThemeChanger extends HTMLElement {

    ...

    createDOMTree() {
        const lang = this.getAttribute('lang');

        this.lightThemeButton = a.button({
            classList: ['light-theme-button'],
            type: 'button',
            innerText: 'A',
            title: lang === 'pt-BR' ? 'Tema Claro' : 'Light Theme',
            onclick: this.handleLightThemeButtonClick.bind(this),
        });

        this.darkThemeButton = a.button({
            classList: ['dark-theme-button'],
            type: 'button',
            innerText: 'A',
            title: lang === 'pt-BR' ? 'Tema Escuro' : 'Dark Theme',
            onclick: this.handleDarkThemeButtonClick.bind(this),
        });

        this.lightThemeButtonContainer = a.div({
            classList: ['icon-container'],
            children: [this.lightThemeButton],
        });

        this.darkThemeButtonContainer = a.div({
            classList: ['icon-container'],
            children: [this.darkThemeButton],
        });

        const wrapper = a.div({
            ariaHidden: true,
            classList: ['wrapper'],
            children: [
                this.lightThemeButtonContainer,
                this.darkThemeButtonContainer
            ],
        });

        return wrapper;
    }

    ...

}

Que vantagens obtemos com essa nova implementação? Bom, consigo pensar somente em 2 coisas:

  1. Digitamos um pouquinho menos de código, mas isso por si só não tem muito valor e a diferença também não é muito grande
  2. O código talvez esteja mais fácil de ler. Mas a forma como o código foi escrito anteriormente era tão difícil de ler a ponto de justificar essa mudança? Acredito que não

Mas e se irmos um pouquinho mais além? Vamos tentar aninhar a criação dos elementos e avaliar estes dois pontos novamente:

// theme-changer-component.js

import { a } from './createElement.js';

export default class ThemeChanger extends HTMLElement {

    ...

    createDOMTree() {
        const lang = this.getAttribute('lang');
    
        const wrapper = a.div({
            ariaHidden: true,
            classList: ['wrapper'],
            children: [
                a.div({
                    classList: ['icon-container'],
                    children: [
                        a.button({
                            classList: ['light-theme-button'],
                            type: 'button',
                            innerText: 'A',
                            title: lang === 'pt-BR' ? 'Tema Claro' : 'Light Theme',
                            onclick: this.handleLightThemeButtonClick.bind(this),
                        }),
                    ],
                }),
                a.div({
                    classList: ['icon-container'],
                    children: [
                        a.button({
                            classList: ['dark-theme-button'],
                            type: 'button',
                            innerText: 'A',
                            title: lang === 'pt-BR' ? 'Tema Escuro' : 'Dark Theme',
                            onclick: this.handleDarkThemeButtonClick.bind(this),
                        }),
                    ],
                }),
            ],
        });
    
        return wrapper;
    }

    ...

}

Agora, o código parece menos legível, além de termos múltiplos níveis de indentação. E mesmo que alguém ache isso mais legível, se resume a um gosto pessoal e, além do mais, ainda precisamos falar sobre as desvantagens do novo código:

  1. Uma pessoa lendo o novo código vai ter uma maior carga cognitiva por ter de entender o funcionamento da nova função a ao invés de ter de lidar somente com a API nativa do DOM
  2. Criar bibliotecas e utilitários dessa forma demanda uma grande quantidade de testes para evitar a introdução de novos bugs. Se eu avançar muito na funcionalidade do utilitário, é mais fácil simplesmente utilizar alguma bilbioteca já pronta, como o React.
  3. E a maior desvantagem de todas: Perdemos quase todo o suporte da IDE: O novo código nos faz perder autocomplete, detecção de erros e toda e qualquer ferramenta que trabalhe analisando as API do DOM. Tudo o que conseguimos da IDE agora são sugestões fora de contexto.

código bom
Com a versão original do código a IDE faz sugestões baseadas no contexto

código ruim
Com a nova versão do código a IDE faz sugestões sem contexto e que não fazem sentido

Inicialmente, a ideia de criar um utilitário para criar elementos HTML pareceu ser muito boa, mas após um pouco de análise, chegamos à conclusão que a tentativa de economizar código trouxe mais prejuízos do que benefícios.

Qual lição fica disso? Simples: Não tente reinventar a roda e não crie códigos "espertinhos" 😉

Carregando publicação patrocinada...
1

Publicação sensacional André! Eu adoto muito isso no TabNews e sempre vou pelo caminho mais simples e que fique mais fácil refatorar para que no futuro o código e todo o sistema me diga qual a melhor hora e forma mais correta de se fazer.

Uau! Essa ideia foi realmente muito esperta!

Isso aqui é um sinal muito claro que há uma alta probabilidade de problemas futuros não esperados surgirem.

1

Muitas vezes menos é mais.
Isso me fez lembrar o Zen do Python.
Para ver isso é só digitar import this no interpretador Python.
Ele diz:

Beautiful is better than ugly.
Explicit is better than implici
t. Simple is better than complex.
Complex is better than complicated.
Flat is better than nested.
Sparse is better than dense.
Readability counts.
Special cases aren't special enough to break the rules.
Although practicality beats purity.
Errors should never pass silently.
Unless explicitly silenced.
In the face of ambiguity, refuse the temptation to guess.
There should be one-- and preferably only one --obvious way to do it.
Although that way may not be obvious at first unless you're Dutch.
Now is better than never.
Although never is often better than *right* now.
If the implementation is hard to explain, it's a bad idea.
If the implementation is easy to explain, it may be a good idea.
Namespaces are one honking great idea -- let's do more of those!

Não é javascript mas são os mesmos princípios