Garrafas e princípios. Como melhor organizar CSS.
You can also read it in english.
Algo realmente curioso em relação a vários dos consagrados conceitos, princípios e boas práticas de programação é que eles parecem simplesmente desaparecer, ou ao menos soar distante, quando o software em questão é o cliente web, vulgo front-end.
Pouquíssimo projetos front-end que vi na minha vida tinham o mínimo de dignidade lógica na maneira como o código era organizado. A maioria era simplesmente uma amontoado de código altamente interdependente com responsabilidades muito mal delimitadas. Uma verdadeira bagunça da qual nenhuma das letrinhas do SOLID passava sequer perto.
No meio daquela turva e misteriosa representação do que seria o cliente web, surge o que talvez seja a razão maior do desespero de quem tem de manter e evoluir aquela bagunça. As folhas de estilo. Sim, o CSS. Já conheci programadores que tinham real pavor de CSS. Entre diversas outras manifestações de carinho, já ouvi:
“Uma b****. Muito fácil de fazer m****.” ou “CSS é como um Lego, se você começa montando errado, fica difícil trocar as peças iniciais depois que a m***** tá feita.”, ou simplesmente “Confuso pra c******.”
Alguns colocam a culpa na sua natureza global. Outros atentam contra o que julgam ser um universo de símbolos e relações indecifráveis. Outros ainda, apenas reclamam, num tom que mistura desespero e mendicância, que não conseguem colocar o botão exatamente no lugar que o designer gostaria.
Ela pode ter uma estrutura diferente, ter sintaxe diferente, ela pode sequer ser considerada uma linguagem de programação. Porém, vos garanto, ela é totalmente passível à aplicação de conceitos, princípios e boas práticas, assim como nas linguagem de programação.
Antes de pensar em tecnologia, pense em princípios.
Dito isso, sugiro a seguir que, ao invés de tentar domar seu CSS com nomes de classes estranhas que misturam traços duplos e underlines, ou explorar lógicas obscuras que oras tratam classes como layout, oras como página, oras como tema, ou ainda cometer o sacrilégio de levar suas classes para dentro do JavaScript, pratique tão somente um dos princípios listados no acrônimo SOLID, o “O” de Open-Closed.
Software entities should be open for extension, but closed for modification; that is, such an entity can allow its behaviour to be extended without modifying its source code.
wikipedia.org
Esse é o conceito Open-Closed. Organize seus objetos de tal maneira que possam ter seu comportamento estendido sem que seja preciso alterá-los.
Definindo objetos.
Uma maneira fácil de entender isso na prática é imaginar uma garrafa. Sim, uma garrafa. Nesse momento, você talvez tenha imaginado uma garrafa de refrigerante, uma garrafa de cerveja, uma garrafa de vinho, ou uma garrafa vazia. Porém, perceba que eu citei apenas uma garrafa e não o seu conteúdo. Uma garrafa aceita uma infinidade de líquidos diferentes em seu interior. Eu posso enchê-la com refrigerante, cerveja ou vinho sem que eu precise alterar a garrafa. A garrafa é um belo exemplo de objeto que respeita o princípio Open-Closed.
Agora você deve estar se perguntando: Como diabos uma garrafa vai me proteger do inferno que pode virar minhas folhas de estilo? A seguir, vou repetir o exemplo acima em forma de código. Classes CSS, mais especificamente.
<div class=”bottle”>
<div class=”bottle-content”></div>
</div>
O que vemos é uma garrafa vazia. Uma vez que a garrafa é um objeto, ela tem sua própria folha de estilo: bottle.css.
Especializando objetos.
Surge então a demanda por uma garrafa de vinho. E agora? Como continuar organizando meu código? Crio uma classe chamada bottle-content-wine e a acrescento à <div> que contém a classe bottle-content? Crio um tema chamado bottle-wine para a garrafa e o acrescento à <div> que contém a classe bottle? Veja, todas essas sugestões estariam violando o princípio Open- Closed porque estariam modificando o código da garrafa. A maneira mais apropriada de tratar a garrafa de vinho é criar um outro objeto no seu sistema que faça uso da garrafa sem precisar modificá-la. Observe:
<div class=”wine-bottle”>
<div class=”bottle”>
<div class=”bottle-content”></div>
</div>
</div>
Agora, além da folha de estilo bottle.css, você também tem a folha de estilo wine-bottle. Esta última, por sua vez, pode estender todos os elementos da garrafa como bem desejar. Por exemplo:
.wine-bottle .bottle-content { background-color: red; }
Feito!
Ignore, para fins didáticos, a possibilidade de termos, claro, diversas possibilidades de cores para o vinho. O importante a ser notado aqui é que o objeto garrafa não sofreu qualquer alteração e, onde quer que eu coloque uma garrafa no meu sistema, esta terá sempre, e absolutamente sempre, as mesmas características de uma garrafa, ou bottle. Isso deverá ser verdadeiro também para uma garrafa de vinho e qualquer outro objeto que você criar.
Perceba também que, ao seguir o princípio Open-Closed, você automaticamente dosa a especificidade de seus seletores na medida exata das suas necessidades. Isso é fundamental para que tanto a bottle quanto a wine-bottle continuem abertas para extensão. Se você super especificar seus seletores, pode acabar por impedir a extensão de alguma característica do objeto e, portanto, violar o princípio.
Variações de um mesmo objeto.
Nesse momento você pode estar pensando: mas se surgir a demanda por uma garrafa pequena ou grande, devo então criar novos objetos chamados small-bottle e large-bottle?
Não. Nesse caso estaríamos contemplando características da própria garrafa e não características do contexto na qual ela estaria inserida. Ao surgir tal demanda, a abordagem seria:
<div class=”bottle bottle-small”>
<div class=”bottle-content”></div>
</div>
Note que bottle-small exerce o papel de modificador da bottle, tornando-a pequena. Ou seja, deve ir para a folha de estilo bottle.css. Lá deverá estar tudo que se refere à bottle, incluindo suas possíveis variações. Essa folha de estilo não deverá conter nada que não esteja relacionado diretamente com a garrafa. A garrafa não deverá conhecer qualquer característica que não seja sua.
Se criássemos uma classe bottle-wine e a colocássemos dentro da folha de estilo bottle.css, estaríamos fazendo com que a garrafa tivesse conhecimento sobre o contexto ao qual foi anexada. Dado que o interior de uma garrafa pode ser preenchido por uma infinidade de conteúdos diferentes, você estaria correndo o risco de inflar o objeto garrafa com características que não deveriam estar ali. Com o passar do tempo, a consequência disso poderia ser um objeto demasiadamente complexo e difícil de manter.
Nome de classe, namespace e folha de estilo.
A fim de evitar uma possível confusão entre classes que indicam um objeto e classes que desempenham papel de modificadores, três simples regras relacionadas à anatomia do markup devem ser respeitadas:
<div class=”application”>
<div class=”application-topbar”>
<div class=”application-actions-container”>
<div
class=”application-action application-action-close”>
</div>
<div
class=”application-action application-action-minimize”
</div>
<div
class=”application-action application-action-maximize”>
</div>
</div>
<div class=”application-title-container”></div>
</div>
<div class=”application-content-container”></div>
</div>
- O elemento raiz no markup do objeto deverá ter a classe que dá nome ao objeto, application no exemplo acima. Ela funciona como um namespace e deverá prefixar todas as demais classes desse objeto.
- A classe que dá nome ao objeto dá nome também à folha de estilo que contém esta e todas as demais classes do objeto.
- As classes que desempenham papel de modificadores nunca são usadas sozinhas. application-action-close não causa efeito algum quando usada sozinha, apenas quando usada em conjunto com application-action.
Na prática
Curioso para ver esse conceito aplicado a um projeto real? Confira um dos meus projetos open source chamado Glorious Demo. Como comentei anteriormente, cada uma das folhas de estilo representa um objeto e sempre se mantém aberto para extensão.
Are you struggling to configure and use StorybookJS? You have an alternative. It’s Pitsby. No rules. No loaders. No regex. Ship your docs with a couple of super simple declarations.