Organização de Código Fonte

Um código fonte pode ser organizado de muitas formas. Linguagens, frameworks e até ferramentas de construção de projeto indicam diversas abordagens, e com tantas formas diferentes muitas vezes a organização se torna confusa. O tema apresenta aspectos que podem ser considerados na organização do código fonte de qualquer tipo de projeto para que desenvolvedores possam criar, entender e trabalhar de forma simplificada.

Organizar algo de forma adequada pode ser uma questão relativa, já que o termo “adequado” pode estar associado a critérios como preferências pessoais e compreensão e visão sobre determinados assuntos.

Em código fonte, a organização também é relativa, já que cada linguagem, framework ou ferramentas de construção de projeto propõe suas formas de organizar os artefatos existentes em um projeto.

Project Organization

É difícil determinar um modelo de organização perfeito, que se aplique a todos os casos. Mas é possível ter um olhar generalista sobre coisas comuns entre projetos com código fonte e estabelecer um conjunto de boas práticas que podem ser aplicadas a qualquer tipo de projeto.

Toda organização de um projeto passa pela forma como arquivos e diretórios são compostos. Ao utilizar frameworks e ferramentas de construção de projeto, normalmente há uma convenção com relação a estrutura a ser utilizada, a qual irá determinar os diretórios e arquivos principais. É uma boa prática seguir as convenções estabelecidas, mas é importante também compreender tais convenções e questionar se todas fazem sentido ao projeto e se deixam claro o propósito.

O tema detalha na sequência estratégias e pontos de atenção para composição de arquivos e diretórios, e embora essas questões também façam parte do universo de Clean Code, neste tema elas são abordadas de forma mais abstrata sem entrar em detalhes de linguagens ou estruturas de seu conteúdo.

Arquivos

O código fonte de um software normalmente é salvo em arquivos de formato texto plano, salvo exceções de algumas linguagens proprietárias. Nestes arquivos, de acordo com o paradigma de programação utilizado, estarão todas as instruções a serem executadas pelo computador, podendo estar organizadas por pacotes, namespaces, classes, métodos, funções, dentre outras estruturas e nomenclaturas utilizadas.

Além dos arquivos com o código da linguagem utilizada, normalmente também há arquivos complementares que fazem parte do projeto como um todo. Para todos estes arquivos podem ser seguidas boas práticas que independentemente da linguagem, facilitam o dia a dia no desenvolvimento do software.

Nomenclatura

O nome de um arquivo é certamente o principal indicador com relação ao conteúdo existente nele, desta forma o nome escolhido precisa indicar de forma direta e clara o seu propósito. Parece uma questão simples, mas que muitas vezes é ignorada ou despercebida por desenvolvedores(as). Essa questão é válida para qualquer tipo de arquivo, seja arquivos de código da linguagem, de configurações, scripts, imagens, dentre outros.

Abaixo segue pontos que podem ser verificados ao nomear arquivos:

  • Propósito único: É importante estar atento ao propósito do arquivo. Se o mesmo possui um propósito mais amplo, deve-se buscar uma nomenclatura mais genérica, mas se o propósito se restringe a algo mais específico, utilizar uma nomenclatura que não deixe dúvidas sobre o escopo e propósito do arquivo. Para estes casos o uso de nomes compostos é uma boa alternativa.
  • Uso de conjunções: O uso de conjunções como “e”, “ou” são indicativos de que o arquivo não possui um propósito único. Sempre que parecer necessário fazer uso delas na nomenclatura, deve-se buscar separar em outros arquivos o conteúdo.
  • Verbos como nome: Um verbo indica uma ação, e um arquivo por si só não é uma ação. Ele pode conter dentro dele rotinas que serão ações a serem executadas e estas sim podem conter verbos.
  • Uso de espaço: Embora os Sistemas Operacionais permitam o uso do caractere “espaço” nos nomes, muitas ferramentas que trabalham com tais arquivos não são bem estruturadas para tal. Um exemplo são ações executadas por linha de comando, onde um espaço pode representar um novo parâmetro para a execução. Normalmente pode-se contornar essa questão com uso de delimitadores, como aspas simples ou duplas, porém é um tratamento adicional que pode ser evitado.

Abaixo segue exemplos de nomes bons e ruins de arquivos, usados por um ferramenta hipotética de build:

  • build.yml: um arquivo único para um projeto grande, com 25 ações referentes à compilação, empacotamento, testes e criação do banco de dados.
  • build.yml: um arquivo único para um projeto pequeno, com 4 ações para compilação, empacotamento, testes, deploy.
  • build.yml: importando ações de outros arquivos e centralizando a execução de construção do projeto.
  • build-and-deploy.yml: com ações para compilação, empacotamento e deploy do projeto.
  • compilar.yml: com ações para compilação do projeto.
  • compile.yml: com ações para compilação do projeto.
  • package.yml: com ações para empacotamento do projeto.
  • deploy.yml: com ações para deploy do projeto.
  • test.yml: com ações para testes automatizados do projeto.
  • acceptance test.yml: com ações para testes automatizados de aceitação do projeto.
  • acceptance-test.yml: com ações para testes automatizados de aceitação do projeto.
  • configurations.conf: com configurações dos estágios de desenvolvimento, pré-produção e produção.
  • development.conf: com configurações do ambiente de desenvolvimento.
  • staging.conf: com configurações do ambiente de pré-produção.
  • production.conf: com configurações do ambiente produção.

Independente do formato (XML, JSON, YML) e de ferramentas (Maven, Gradle, ANT, Make, Rake, Gulp, Grunt, Yarn, PyBuilder, Ninja, Mix, Composer, MSBuild), é importante focar na forma de nomear arquivos.

Mesmo que o formato ou ferramenta possibilite arquivos com muitas informações, às vezes até bem estruturadas, é importante verificar o propósito do arquivo, e a estratégias que podem ser adotadas para organizá-los e nomeá-los. Isso vale para arquivos de configuração, código da linguagem, documentação, scripts, cenário de dados, dentre outros.

Tamanho

Há muito tempo desenvolvedores(as) já compreenderam que arquivos grandes não são uma boa prática, porém até os dias atuais ainda são vistos nos projetos arquivos que ultrapassam a casa de 500, 1.000 ou até 10.000 linhas.

Times que aplicam os conceitos de Clean Code, normalmente possuem um cuidado com relação ao tamanho dos arquivos, porém este cuidado algumas vezes fica restrito a arquivos com código da linguagem, e outros arquivos do projeto não tem o devido tratamento para não se tornarem extensos.

Normalmente arquivos grandes indicam que o mesmo possui conteúdo relacionado a um propósito amplo demais, e a resolução nestes casos é identificar subgrupos de propósitos dentro do propósito principal e usá-los para quebrar o arquivos em vários outros.

Para arquivos que possuem conteúdo referente a vários propósitos, a resolução é mais simples, bastando a criação de um arquivo para cada propósito.

Formato e indentação

Embora grande parte dos arquivos usados no desenvolvimento de software sejam em texto plano, há muitos formatos aos quais eles podem ser escritos. Para cada formato existe uma indentação indicada.

De arquivos da linguagem de programação às configurações do projeto, todos devem seguir a indentação indicada para o formato. Mesmo que em alguns casos seja tentador utilizar convenções próprias, a maioria dos profissionais da área trabalham com a indentação indicada para o formato.

Criar indentações próprias normalmente gera dificuldades de uso e adaptação das pessoas e ferramentas.

Para simplificar a indentação de arquivos é recomendado o uso de editores que facilitem isso, seja durante a codificação ou indentando a cada alteração salva.

Há também ferramentas que aplicam a indentação junto a ferramenta de versionamento, ou seja, ao fazer um commit tais ferramentas ajustam a indentação do código, porém em alguns casos elas podem impactar negativamente durante o merge de códigos, gerando problemas de conflito.

Desenvolvedores(as) precisam ser responsáveis por todo código gerado, desta forma além do uso de editores/ides adequados, sugere-se o uso de ferramentas de validação da indentação no processo de construção do projeto, onde caso a validação aponte erros, a mesma gere alertas o desenvolvedor. Tal validação também pode estar presente no pipeline de integração contínua, bloqueando o build em caso de violações na validação.

Diretórios

O código fonte possui normalmente em sua raiz um conjunto de diretórios e subdiretórios direcionados ao propósito dos artefatos que neles estão contidos, alguns exemplos comuns são: source, src, app, conf, config, build, target, dist, static, bin, lib, test, scripts, docs. Abaixo algumas variações comuns:

Directories

A nomenclatura pode variar, alguns utilizam abreviações, outros nomes completos, e independente da nomenclatura específica utilizada, um código fonte comum possui diretórios para as seguintes necessidades:

  • Código da aplicação;
  • Código de testes da aplicação;
  • Artefatos oriundos da construção do projeto;
  • Pacotes que serão publicados;
  • Arquivos de configuração para construção e execução da aplicação.
  • Scripts e/ou Binários executáveis;
  • Documentação.

O primeiro ponto de atenção com relação a diretórios está associada a dependência entre eles, destacando-se:

  • Não haver dependência cíclica entre dois ou mais diretórios, ou seja, se o diretório A depende do conteúdo de B, B não pode depender do conteúdo de A.
  • Um diretório depender apenas de diretórios irmãos ou de níveis abaixo a ele, evitando-se conhecer diretórios acima dele, ou seja, não fazendo o uso de “../”.

Cada diretório possui características diferentes, bem como boas práticas que permitem uma organização mais eficiente dos mesmos. A seguir é detalhada tais práticas para cada diretório.

Raiz

O diretório raiz de um projeto é a porta de entrada do mesmo, em função disto é o que deve estar mais organizado. Nele é recomendado:

  • Conter um arquivo README com a documentação relacionada ao projeto, sendo a leitura deste arquivo o primeiro passo que qualquer desenvolvedor(a) deve fazer ao ingressar no projeto. Mais detalhes podem ser vistos no tema Documentação Técnica.
  • Conter arquivos de configurações gerais do projeto. Porém é recomendável estar atento à quantidade de arquivos existentes, pois um número excessivo de arquivos pode prejudicar a visão deste diretório. Nestes casos pode-se criar um subdiretório para arquivos de configuração.
  • Não possuir scripts que acessem dados em níveis acima dele. Para um projeto o diretório raiz deve ser o nível mais alto, nada acima dele deve existir.

Código da aplicação

Normalmente chamado de source ou src, em muitos projetos ele se encontra na raiz, porém também é comum que o mesmo esteja em subdiretórios cuja diretório pai indica o contexto ou camanda, ou ainda a linguagem utilizada no código, sendo todas estas práticas aceitáveis.

Porém tais nomenclaturas dão a entender que apenas o conteúdo ali é considerado “código fonte”, porém vários outros arquivos do projeto podem ser considerados parte do código fonte. Uma boa abordagem é utilizar como nomenclatura o termo que define o domínio ou nome da aplicação.

É recomendado para esse diretório:

  • Ter o código relacionado a aplicação, ou seja, regras de negócio e composição da solução.
  • Ter apenas o código gerado por desenvolvedores e não por ferramentas de geração de código no processo de construção do projeto.
  • Em projetos onde vários pacotes são publicados, os subdiretórios de primeiro nível podem representar esses pacotes.
  • Em projetos onde há camadas físicas e lógicas, os subdiretórios podem ser representar tais camadas.

Código de testes

Testes automatizados são essenciais para a garantia de qualidade do software implementado, e o código gerado para tais testes normalmente é organizado em um diretório a parte no projeto. Há abordagens que sugerem que o código de teste pode estar junto ao código da aplicação, porém isso abre espaço para confusão de responsabilidades de cada código e eventualmente maior trabalho com relação aos scripts de construção do projeto.

Algumas boas práticas:

  • Pode conter tipos e níveis diferentes de testes de software, como unitários, de componente, integrados, de carga e stress.
  • O código existente nele é para fins de teste do software em ambientes controlados, logo não deve ser incorporado aos pacotes de distribuição.
  • Deve possuir seus próprios subdiretórios para necessidades de configuração, construção, dentre outros.

Construção do projeto

É comum dar a esse diretório nomes como: build ou target, e para ele é recomendado:

  • Ser criado em tempo de construção do projeto, ou já existir como um diretório vazio versionado junto ao projeto.
  • Ter apenas artefatos oriundos do processo de construção do projeto, porém isso não inclui pacote de publicação do mesmo.
  • Seu conteúdo jamais deve ser versionado junto a ferramenta de controle de versão utilizada.
  • Todos artefatos gerados nele podem ser excluídos sem que isso afete a integridade do projeto.

Publicação de pacotes

Normalmente chamado de dist, public, esse diretório em alguns casos é um subdiretório do diretório de construção. Para ele é recomendado:

  • Pode ser criado em tempo de construção do projeto, ou já existir como um diretório vazio versionado junto ao projeto.
  • Conter apenas os artefatos que são considerados publicáveis, sendo eles normalmente arquivos executáveis ou os arquivos do resultado da construção do projeto. Também podem conter arquivos de configuração utilizados na execução da aplicação.
  • Os artefatos devem ser gerados apenas em caso de sucesso na construção do projeto.
  • O conteúdo jamais deve ser versionado junto a ferramenta de controle de versão.
  • Todos artefatos gerados nele podem ser excluídos sem que isso afete a integridade do projeto.

Configurações

Comumente chamado de config ou apenas conf, nele é recomendado:

  • Arquivos de configuração utilizados para o processo de construção do projeto.
  • Templates de arquivos de configuração utilizados na execução do projeto.
  • Arquivos de configuração direcionados aos estágios da aplicação, mas não diretamente a cada ambiente de publicação, isso porque pode-se ter vários ambientes em um mesmo estágio.

Binários e scripts

É usual que projeto possuam um diretório chamado bin, onde nele podem estar arquivos binários e/ou scripts, que geralmente são executáveis relacionados a ações complementares do projeto.

Para arquivos binários, os mesmos podem ser usados durante a construção ou execução da aplicação. Não são arquivos indicados para versionamento, porém é aceitável que ocorra em detrimento da facilitação de uso do projeto pelos times de desenvolvimento. Como recomendações:

  • Ter apenas arquivos binários/executáveis que realmente não sejam fornecidos pelo SO ou através da instalação de ferramentas.
  • Não ser usado como diretório para distribuição de binários da própria aplicação, tais binários devem estar no diretório de distribuição.

Para scripts, o mesmos podem ser usados para construção, execução ou alguma ação executada repetitivamente durante a implementação de código. Tais scripts não fazem parte da aplicação, mas merecem ser versionados juntos ao código fonte do projeto. Algumas recomendações:

  • Os scripts jamais devem conter lógicas relacionadas a aplicação. Devem ser restritos a rotinas de execução de tarefas durante a fase de desenvolvimento.
  • Os scripts devem priorizar o recebimento de argumentos na sua execução e não informações fixadas no próprio script.
  • Cada script existente deve ser devidamente documentado para facilitar a compreensão de sua finalidade e uso.

Documentações

Um projeto organizado normalmente conta com uma documentação que seja útil. Embora a recomendação seja ter a documentação essencial descrita no arquivo README, é comum haver detalhamentos descritos em outros arquivos, os quais podem então ser salvos neste diretório, que normalmente é chamado de docs. Algumas recomendações:

  • Ter apenas documentos relacionados ao projeto em específico. Documentações gerais relacionadas a solução como um todo podem estar em outras fontes.
  • As documentações existentes podem ser referenciadas no arquivo README.
  • Possuir documentações geradas manualmente. Material gerado automaticamente por processos automatizados podem ser colocados junto ao diretório de construção ou distribuição.

Segregação de Código

Quando um projeto cresce, ou ganha um arquitetura de camadas ou outras distribuídas, é comum que o mesmo tenha seus artefatos segregados. Quando isso ocorre, parte do código irá compor um novo projeto com um foco mais específico. Segregar um projeto é uma boa prática de organização, mas precisa ser bem realizada para evitar complexidade. Abaixo segue algumas boas práticas:

  • Utilizar como critério de segregação os contextos de um domínio (seguindo uma abordagem de DDD), ou as camadas físicas (tiers).
  • Não criar dependências entre projetos de forma local, ou seja, que um projeto precise conhecer outro projeto dentro do mesmo local de armazenamento. Um projeto deve sempre depender do artefato publicado por outro projeto, o qual deve ser disponibilizado através de ferramentas de gerenciamento de bibliotecas.
  • Cada projeto deve ganhar seu próprio repositório e versionamento. Utilizar um diretório que agrupe os projetos, e este ser versionado, normalmente leva ao problema do item citado anteriormente sobre dependências.
  • Não replicar conteúdos entre projetos, gerando situações onde ao atualizar em um projeto seja necessário atualizar nos demais. Estes conteúdos tendem a ficar desatualizados nos demais projetos.

Muitos projetos, mesmo organizados em camadas físicas ou contextos diferentes tendem a ter dificuldades de segregação quando crescem. Uma boa prática é reforçar a visão de independência entre os diretórios desde o início do projeto, considerando que eles poderão ser segregados a qualquer momento. Diretórios independentes facilitam ações futuras de segregação.



Muitas variações podem ser utilizadas na organização de um projeto. Todas elas podem ser válidas se cumprirem com o objetivo de facilitar para desenvolvedores(as) a localização dos artefatos desejados.

Deve-se estar atento ao “tempo gasto” para encontrar algo no projeto, se algo não é localizado de forma rápida e simples, esse é o principal sinal de que melhorias podem ser realizadas, sejam elas seguindo as convenções de linguagens, frameworks e ferramentas de construção, ou seguindo convenções definidas pelos times de desenvolvimento.

Histórico