Front End

22 out, 2014

Mout e Modularidade

Publicidade

As discussões sobre modularidade são recorrentes. Algumas pessoas dizem que cada função deve ser um módulo/arquivo/pacote separado; outros dizem que os métodos devem ser contidos por um pacote e agrupados por semelhança/preocupações; e existe ainda um terceiro grupo que acredita que um único namespace seja o caminho a percorrer. Vou tentar explicar as decisões de design que influenciaram a criação e a estrutura atual da moutjs e que pacotes de função única nem sempre são a melhor solução.

Como tudo começou

Tudo começou em 2009, quando eu decidi extrair e portar alguns métodos comuns que utilizei em diferentes projetos, em pacotes reutilizáveis. Comecei criando namespaces stringUtils e arrayUtils e adicionando novos métodos conforme era necessário. Os helpers cresceram a cada projeto e depois de alguns meses eu tinha mais de 20 funções dentro do namespace stringUtils (algumas delas bem grandes, uma vez que manipularam a conversão de entidades HTML), o que significa que eu tinha que carregar as mais de 20 funções, mesmo que precisasse de um único método – tenha em mente que, na maior parte, eu faço desenvolvimento front-end e, na época, eu estava programando sites para celular, e muitas vezes o tamanho do arquivo era uma grande preocupação.

// used a long (global) namespace to avoid name collisions
// loaded all methods even if not used by the app
MM.arrayUtils.forEach(arr, doSomething);
var slug = MM.stringUtils.hyphenate(myStr);

Modular

No final de 2010, eu estava trabalhando em projetos maiores de JavaScript (+ 10K LOC) e comecei a usar RequireJS para gerenciar minhas dependências, e por isso eu estava motivado a dividir o meu código em arquivos menores. Assim sendo, eu poderia manter o código organizado e carregar somente o que realmente estava sendo usado pelo aplicativo. Isso aumentou muito a minha experiência em desenvolvimento de JavaScript, fui capaz de escrever aplicativos com mais de 50 mil linhas de código com facilidade, a reutilização de código aumentou exponencialmente a cada projeto, não tinha mais conflitos ao trabalhar com outros desenvolvedores e, especialmente, não tinha mais confiança nas globals – eu faço um monte de projetos que precisam ser executados dentro de maiores sites/aplicativos. Contar com as variáveis globais é um caminho que normalmente nos leva à dor.

// require a single method (recommended)
var forEach = require('mout/array/forEach');
forEach(arr, doSomething);
// or the whole package if you want to (I rarely do this)
var stringUtils = require('mout/string');
var slug = stringUtils.hyphenate(myStr);

Por que um pacote único?

O estado de gerenciadores de pacotes front-end

Uma das principais razões para manter mout como pacote único é que a gestão de pacotes para o front-end ainda está em andamento. Algumas pessoas foram utilizando npm enquanto outros usam bBower/volo/jamjs, por isso é difícil achar um consenso. Adotar um gerenciador de pacotes em detrimento de outro pode fazer com que alguns usuários em potencial não usem a lib, uma vez que pode não ser a sua abordagem preferida ou apenas não ser a forma como eles estão lidando com dependências. – pessoas que gostam do Node.js, saibam que nem todo mundo está escrevendo apps Node.js, assim npm e módulos cjs não são as balas de prata que vocês gostam de fazer de conta que são.

A distribuição de um único pacote é mais fácil para o consumo no navegador, pois você pode só baixar um zip/tarball para a pasta certa e chamá-lo um dia. Na verdade, eu tenho usado alguns scripts shell básicos para baixar/atualizar as minhas dependências ao longo dos últimos dois anos e tem funcionado muito bem para pacotes que possuem uma árvore de dependência superficial.

Submódulos Git são difíceis de manter

O gerenciador de pacotes não é a única razão. Tem também o fato de que a manutenção de múltiplos repositórios Git pode ser bem difícil. Todo mundo sabe que submódulos Git não funcionam tão bem assim e existem algumas coisas que são bem simples quando você lida com um único repositório (mout contém ~ 200 métodos). Coisas como scaffolding, automação e execução de testes são mais fáceis, pois você tem menos partes móveis – script de construção única, executor único de testes, servidor único CI etc.

O script de construção do mout é responsável pela manutenção de todos os pacotes atualizados antes de executar os testes. Se um módulo não contém testes, ele cria uma falha de especificação automaticamente para avisá-lo. Ele também contém alguns comandos scaffolding para criar novos módulos baseados em templates e um comando para ajudar a implementar o projeto. Esse é o tipo de automação que é mais fácil de implementar, se o projeto seguir uma estrutura sã.

PS: Eu sei que npm não requer que todos os pacotes sejam múltiplos repositórios git separados (você não precisa sequer de um repositório público), mas se eles não são repositórios separados, então provavelmente não deveriam ser um pacote separado também.

Consistência

A principal razão para a reutilização do código ser uma coisa tão difícil de alcançar é porque a maioria das bibliotecas/frameworks não é projetada para trabalhar em conjunto e faz um monte de suposições sobre a forma como eles vão/devem ser utilizados. Uma vez que tudo é codificado em conjunto, existe uma chance reduzida de conflitos de nomes e você também aumenta a coerência/coesão entre todas as funções (incluindo as convenções de nomenclatura). Ela motiva a reutilização de código entre os módulos internos também.

Alguns bugs e algumas melhorias afetam vários módulos ao mesmo tempo. Ter um issue tracker descentralizado pode levar a muitas duplicações e também torna mais difícil para os usuários/mantenedores acompanhar o progresso. Como exemplo, decidimos pegar emprestada uma característica de Lo-Dash em mout v0.4, a capacidade de usar uma notação abreviada na maioria dos métodos de array/ objeto/coleção, então você pode fazer find(users, {name:’john’}) em vez de find(users, function(u){return u.name === ‘john’}). Essa mudança afetou 18 módulos e requeria 2 novos métodos (1 privado). Esse é o tipo de mudança que deve ser feita através da placa, de uma só vez. Liberar 20 pacotes! == Diversão. Se cada pacote foi mantido por diferentes usuários, certamente não iria funcionar, uma vez que nunca poderíamos entrar em um acordo se esse recurso deveria realmente ser implementado ou até mesmo coordenar uma data de lançamento…

Se cada método aceita um conjunto diferente de argumentos (ou ordem diferente) e/ou é nomeado de acordo com convenções diferentes, é provável que você cometa mais erros. Convenções comuns significam menos tempo gasto lendo a documentação ou corrigindo bugs bobos.

Menos burocracia

Como tudo faz parte do mesmo pacote, é mais fácil lidar com a dependência em relação ao “contexto do projeto” e ainda exigir métodos individuais, conforme necessário. Se cada função fosse um pacote separado, você precisaria listar duas vezes: uma no package.json e outra toda vez que você fizer require no módulo.

“Descoberta”

Já que tudo faz parte do mesmo pacote, você aumenta a chance de o usuário descobrir essa característica. Existem várias funções que não são populares e que o usuário nunca iria descobrir que existiam, ou como elas são úteis. Às vezes, você não sabe o que precisa até que você vê. Como você quer encontrar alguma coisa se não sabe mesmo o que está procurando?

Isso significa menos tempo gasto à procura de pacotes. Você pode confiar que todas as funções têm o mesmo nível de qualidade e são destinadas a trabalhar juntas.

pkg-damn_high

Os objetivos do projeto

Eu criei o mout com a intenção de usá-lo como um substituto para a biblioteca JavaScript padrão, onde você não precisa se preocupar com peculiaridades do navegador e pode usar todos os métodos em todos os ambientes (IE 7 +) com uma boa dose de confiança.

Como ele é destinado a ser um “padrão lib”, você não esperaria que ele fosse espalhado por vários lugares.

Dependências cruzadas

Existe um monte de dependências cruzadas entre os módulos. Lidar com isso entre vários projetos separados seria uma coisa complicada, especialmente para os navegadores.

mout-graph_s

O gráfico de dependência acima foi gerado com node-Madge (excluídos os pacotes para torná-lo mais fácil de identificar dependências reais).

“Identidade”

Vários pacotes não possuem qualquer “identidade” e, provavelmente, não criarão um forte senso de “comunidade”.

Por que AMD como o formato de autoria?

O formato do módulo de autoria é apenas um detalhe. Ele pode ser facilmente traduzido para qualquer outro formato de módulo, caso seja necessário.

Já que eu faço principalmente desenvolvimento front-end, faz mais sentido escrever os módulos em um formato que funciona no navegador sem precisar de uma construção. Ele também torna todo o processo de implementação mais fácil. Uma vez que a maioria dos módulos Node.js são instalados por meio npm, podemos convertê-lo em um formato compatível com Node.js durante npm publish e garantir que os usuários receberão os arquivos que precisam, sem qualquer sobrecarga extra (sem desempenho ou legibilidade inconvenientes). Como os gerenciadores de pacotes do navegador costumam usar o repositório Git como fonte (não há algo similar para npm publish), é melhor deixar os arquivos no GitHub sempre prontos para o consumo do navegador.

Não existem balas de prata

O mout tem objetivos e necessidades muito específicos, por isso é codificado dessa forma específica. O raciocínio descrito acima certamente não se aplica a todos os projetos. Na verdade, creio que se aplica a uma minoria.

Eu realmente acredito que os módulos de função única vão se tornar mais populares nos anos seguintes, mas certamente não é a forma como eu escrevo a maioria dos meus códigos de aplicativo. Nem todo código precisa ser reutilizado em diferentes contextos, e a modularidade nem sempre é uma vantagem (às vezes, pode ser uma camada extra de burocracia). Considere as opções e escolha a que se adequa melhor às suas necessidades.

Minha regra de ouro atualmente é que se algo não me ajuda provavelmente vai me atrasar. Eu não vou seguir convenções/padrões/dogmas específicos só porque outras pessoas dizem que é a coisa certa, a menos que eu ache que se apliquem ao meu próprio cenário. Trabalhar em um ambiente rápido, com muitas mudanças durante o processo de desenvolvimento, me tornou mais pragmático sobre como eu abordo problemas, e essa mesma regra pode não se aplicar a você.

“Ele simplesmente funciona” é fantástico, quando ele realmente simplesmente funciona. Mas é brutal quando ele realmente não funciona. Então eu prefiro transparência. – @JohnDCook

Dê uma chance

O mout contém muitos recursos úteis, API consistente, boa documentação e está sendo mantido ativamente. Temos também um extenso conjunto de testes unitários para garantir a exatidão. Espere mais novidades nos próximos meses, e um ciclo de lançamento rápido. Temos trabalhado duro para torná-lo o mais incrível/confiável possível. Confira em http://moutjs.com e star/fork the project on github. Estamos também no canal #moutjs em irc.freenode.net.

***

Artigo traduzido pela Redação iMasters com autorização do autor. Publicado originalmente em http://blog.millermedeiros.com/mout-and-modularity/