- Iniciando
- Documentação
Arquitetura
Políticas e Padrões
Infraestrutura
- Ecossistema
Ambientes
Integração Contínua
Monitoramento
Utilitários
Documentos
- Equipe
Código Limpo
Segundo Robert C. Martin, uma das principais referências na área, “código limpo não é somente desejável - é necessário. Se o código não estiver limpo, o desenvolvimento decai com o tempo. Eventualmente, chega a um tal ponto de incerteza, dor e frustração que parece ser mais fácil começar de novo e reescrever tudo do início. Obviamente que se a reescrita não for melhor que o original, o problema persiste, e em pouco tempo a equipe acaba onde tinha começado”.
É claro que a definição do que é código limpo depende das preferências pessoais de cada um, mas tentamos descrever aqui um conjunto de recomendações para garantir a qualidade do código e assim facilitar a manutenção, quebrar os ciclos de reescrita e extrair do código o melhor para os usuários e para os desenvolvedores.
Tratamento de Exceções
Exceções foram introduzidas na linguagem Java para garantir resiliência e confiabilidade. O objetivo seria assegurar que os sistemas pudessem reagir apropriadamente a situações inesperadas e a contigências específicas. Embora seu propósito seja simples, a forma de utilização sempre gerou muita confusão. Abaixo, listamos um conjunto de práticas que devem sempre ser observadas ao lidar com exceções.
Tipos de Exceções
Há grande controvérsia envolvendo o uso de Checked Exceptions, com personalidades como Rod Johnson (criador do Spring Framework) e Gavin King (criador do Hibernate) se posicionando contra seu uso. De fato, Spring, Hibernate e vários outros frameworks estão usando apenas Unchecked Exceptions.
Checked Exceptions, implementadas em Java como uma subclasse de Exception foram inicialmente propostas para situações em que o cliente da API pode se recuperar do problema indicado pela exceção. O problema é que essas situações são muito raras. Em geral, seu código não terá a capacidade de se recuperar de uma situação problemática. Quando usada como recomendação padrão, esse tipo de exceção pode levar a problemas de encapsulamento e problemas graves de manutenção. Suponha, por exemplo, que uma exceção dessas é incluída na API de uma biblioteca de baixo nível. Caso não seja uma exceção recuperável, essa modificação poderá impactar toda a cadeia de chamadas que termina na chamada dessa API, forçando a inclusão de blocos “try-catch” ou a inclusão de instruções “throws” em toda a sequência de processamento.
Unchecked Exceptions, implementadas em Java como uma subclasse de RuntimeException, por outro lado, foram propostas para situações em que o cliente da API não pode se recuperar. Elas podem ser lançadas de qualquer ponto, em qualquer lugar, não exigem declaração e não demandam tratamento. São muito mais convenientes e, por isso, muito mais populares.
Recomendamos usar Unchecked Exceptions como escolha padrão e somente naqueles raros casos em que há inquestinável possibilidade de recuperação pelo cliente da API, usar Checked Exceptions. Portanto, se cliente do método pode tomar alguma ação para se recuperar de um erro esperado, então crie sua exceção como uma Checked Exceptions. Caso contrário, se o cliente não pode fazer nada útil no caso de um erro, então crie sua exceção como uma Unchecked Exceptions.
Hierarquia de Exceções
Defina sua própria hierarquia de exceções. Ao fazer isso, você poderá utilizar esses tipos para implementação do mecanismo de tratamento de erros que poderá, por exemplo, registrar informações específicas das exceções. Use os tipos de exceções claramente, para definir a causa raiz do problema e eventualmente a severidade do erro (fatal, warn, info, debug).
Controle de Fluxo
Exceções não devem ser usadas para controle de fluxo. Não gerencie lógica de negócio com exceções. Use instruções condicionais para isso. Lançar e tratar exceções são operações onerosas. Se o controle pode ser feito com uma instrução condicional, não use exceções porque isso impacta significativamente a performance geral do sistema, além de reduzir a legibilidade do código. Por exemplo, se um método retorna null, use Optionals ou uma instrução if-else em vez de depender do tratamento de exceções para fazer esse tipo de verificação.
Exceções têm um impacto sobre o desempenho global do aplicativo, então devem ser usadas criteriosamente. Não use exceções se você pode usar uma variável boleana para verificar se uma operação foi bem-sucedida ou não. Tente sempre retornar uma variável para que o cliente do código possa controlar o fluxo de execução com base nesse retorno.
Identifique e corrija a causa raiz dos problemas para evitar tratamentos desnecessários. Sempre faça as validações das variáveis que possam assumir valores null antes de executar as operações, verifique o tamanho do array em vez de confiar em ArrayIndexOutOfBoundException.
Lance exceçõs para possíveis erros reportados por um método. Por exemplo, se você retorna os valores -1, -2, -3… ao invés de FileNotFoundException, esse método pode não ser bem entendido.
Usar o tratamento de exceções dentro de um loop não é recomendado para muitos casos. Tente colocar o loop dentro do bloco de tratamento das execeções
Lançamento
É sempre melhor lançar exceções específicas em vez de lançar exceções mais genéricas, como Throwable, Excpetion or RuntimeException. Ao fazer isso, nós podemos fornecer mais informações sobre o que exatamente ocorreu e o código fica mais legível.
Sempre que precisar lançar uma exceção, siga o princípio Throw Early or Fail-Fast. A stack trace de uma exceção mostra a sequência exata de chamadas de métodos até o ponto em que ocorreu a exceção, incluindo o nome da classe, o nome do arquivo e o número da linha onde a exceção foi lançada. Por isso, é muito importante lançar a exceção o quanto antes, imediatamente quando a exceção é detectada. Por exemplo, se o erro ocorre em um método mas não é lançada por ele, mas sim pelo método chamado, a stack trace não indicará o posição exata do erro, o que poderá comprometer o tempo de diagnóstico da causa do problema.
Tratamento
Throwable é a superclasse de todos os erros e exceções em Java. Error é a superclasse de todos os erros capturáveis por qualquer aplicação. Tratar Throwable ou Error significa que erros como OutOfMemoryError, StackOverFlowError e etc. significa que sua aplicação poderia se recuperar de situações como essas, o que obviamente não é possível. Assim, Throwable ou Error não devem ser capturadas porque não há tratamento possível para elas.
Sempre faça o tratamento de exceções específicas, nunca de exceções genéricas como Exception. Não é possível se recuperar de erro que você não conhece, um erro genérico. Só podemos tentar um outro caminho se conhecemos exatamente qual problema ocorreu. Além de viabilizar o tratamento de um erro específico, isso evita problemas de performance e ainda garante maior legibilidade do código.
Sempre que precisar tratar uma exceção, siga o princípio Catch Late. No caso de checked exceptions, o compilador Java força que o cliente capture a exceção ou a declare usando a cláusula throws. Assim, geralmente os desenvolvedores tendem a capturar a exceção e não fazer nada além de registrar a stacktrace para evitar erros de compilação. Mas, ao fazer isso você estará escondendo o que realmente aconteceu. É melhor capturar a exceção somente quando ele poderá ser tratada apropriadamente. Use a cláusula throws para declarar a exceção e passar a responsabilidade para o método chamador. Dessa forma, caso nenhum dos métodos acima conseguir tratar a exceção apropriadamente, ela alcançará o mecanismo de tratamento global de exceções, cuja responsabilidade é tratar casos como esse.
Novamente, não esconda a exceção! Não substitua o tratamento de exceções por um bloco vazio, o registro da stacktrace ou da exceção, como nas listagens abaixo. Ignorar exceções poderá evitar erros de compilação, mas vai criar um caos a manutenção posterior.
try { |
try { |
try { |
Não perca a exceção original. Quase todas as exceções fornecem um construtor com um parâmetro para a causa. Ao criar uma exceção customizada, lembre-se de incluir esse construtor.
public Exception(String message, Throwable cause) |
catch (IllegalArgumentException exp) { |
Logging
Não log a mesma exceção mais de uma vez, para não haver confusão em relação à localização da exceção. Nós deveríamos sempe logar a mensagem da exceção e essa mensagem dever conter informações precisas para que o chamador possa saber facilmente o que ocorreu. Sempre usa informações detalhadas, adicionando os valores de varáiveis relevantes quando possível. Por exemplo, se nós estamos escrevendo um código em que uma regra só é aplicada para idosos, se a idade é menor que 60, então o método dever lançar InvalidAgeException. Nesse caso, se a exceção é lançada sem especificar qual a idade do cidadão, a mensagem pode não ser tão útil quanto no caso em que você informa exatamente que idade causou a exceção.
catch (NoSuchFieldException exp) { |
Logar e lançar uma exceção dentro de um bloco catch é considerado anti-pattern e deveria ser evitado. Logar e lançar a exceção pode resultar em múltiplas mensagens de erro no arquivo de log para um único problema, dificultando o trabalho dos responsáveis por identificar e diagnosticar problemas no código.
Por fim, lembre-se que granularidade é muito importante. Um bloco de tratamento deve tratar sempre um caso específico. Não coloque centenas de linhas de código dentro de um bloco try-catch.
Documentação
Atribuir um código para cada diferente mensagem de exceção é uma boa prática para documentação porque facilita muito a comunicação do tipo de erro. Também recomendamos usar Javadoc @throws para indicar as exceções lançadas pelos métodos. Isso facilita bastante principalmente no caso de interfaces que poderão ser usadas em outras aplicações.
Nomemclatura
O nome das exceções deve ser claro e significativo para comunicar objetivamente qual a causa da exceção. Ao criar suas próprias exceções, siga o padrão já definido pela linguagem. Inicie o nome com a causa e termine com o sufixo Excpetion, como NoSuchMethodExcpetion.
Lembre-se de manter exceções de mesmo tipo dentro da mesma hierarquia. Por exemplo, exceções relacionadas a problemas de IO são todas subclasses de IOExcpetion.
Padrão de Codificação
A utilização de um padrão de codificação é importante para reduzir o custo de manutenção do software, já que sua adoção melhora a legibilidade dele. A Sun Microsystems, em seu documento de Convenções de código para a linguagem de programação Java, cita algumas das razões por que convenções de codificação são importantes:
- 80% do custo de um software durante seu ciclo de vida é dedicado à manutenção.
- Dificilmente um software é mantido pelo mesmo desenvolvedor por todo seu ciclo de vida.
- Padrões de codificação melhoram a legibilidade do código, permitindo que os desenvolvedores entendam novos códigos de forma mais rápida e completa.
Quando convenções de código são projetadas para produzir código de alta qualidade e são formalmente adotadas elas se tornam um padrão de codificação. O escopo de um padrão de codificação abrange os seguintes elementos de código, dentre outros: nomes de arquivos, organização do conteúdo de arquivos, indentação, comentários, declarações de variáveis, espaços em branco, convenção de nomes, etc.
Padrão de Codificação de Back-end (Java)
Arquivos de código-fonte
Nome de arquivo
O nome de um arquivo de código-fonte consiste no nome case-sensitive da classe top-level que ele contém (haverá exatamente uma) acrescido da extensão “.java”.
Codificação de arquivo
A codificação dos arquivos de código-fonte será a UTF-8.
Estrutura de arquivos de código-fonte
Um arquivo de código-fonte consiste das seguintes seções, nessa ordem:
- Instrução package
- Instruções import
- Exatamente uma declaração de classe top-level
Exatamente uma linha em branco separará uma seção da outra.
Instrução package
A instrução package não conterá quebra de linha. O limite de tamanho de coluna não se aplica a essa instrução.
Instruções import
As instruções import não devem conter caracteres-curinga (*). Elas também não conterão quebras de linha, não se aplicando o limite de tamanho de coluna. Não deverá haver instruções imports de elementos não utilizados pelo código-fonte. Elas serão dispostas em grupos na seguinte ordem:
- Imports estáticos ordenados alfabeticamente
- Imports dos pacotes iniciados em java ordenados alfabeticamente
- Imports dos pacotes iniciados em javax ordenados alfabeticamente
- Imports dos pacotes iniciados em org ordenados alfabeticamente
- Imports dos pacotes iniciados em com ordenados alfabeticamente
- Imports dos pacotes iniciados com prefixos diferentes dos anteriores ordenados alfabeticamente
Cada grupo acima é separado do outro por uma linha em branco. Essa organização acima já é a padrão realizada pelo Eclipse ao executar o comando “Organize imports”.
Declaração de classe
Cada arquivo conterá apenas uma declaração de classe top-level.
Ordem dos membros da classe
A ordem dos membros da classe deve ser a seguinte:
- Campos estáticos
- Campos de instância
- Construtores
- Métodos privados chamados por todos os construtores
- Métodos-fábrica estáticos
- Propriedades JavaBean (getters e setters)
- Implementações de métodos oriundos de interfaces
- Métodos privados ou protegidos que são chamados por implementações de métodos oriundos de interfaces
- Outros métodos
- Métodos equals, hashCode e toString
Os métodos privados devem ser colocados imediatamente abaixo do método que os referencia.
Formatação
Indentação
Cada nível de indentação deve conter 4 espaços em branco. Tabs não deverão ser utilizados para esse propósito.
Uma instrução por linha
Cada instrução deverá ser seguida de uma quebra de linha.
Linhas em branco
Uma linha em branco deverá aparecer:
- Entre membros consecutivos (ou inicializadores) de uma classe: campos, construtores, métodos, classes aninhadas, inicializadores estáticos, inicializadores de instância.
- Entre instruções, conforme necessidade, para organizar o código em subseções lógicas.
- Opcionalmente antes do primeiro membro ou depois do último membro da classe.
Chaves
Construções similares a bloco (corpo de classe, método ou construtor) devem seguir o estilo de Kernighan & Ritchie (também conhecido como chaves egípcias):
- Nenhuma quebra de linha antes da chave de abertura mas um único espaço antes dela
- Quebra de linha após a chave de abertura
- Quebra de linha antes da chave de fechamento
- Quebra de linha depois da chave de fechamento caso ela termine uma instrução ou o corpo de um método, construtor, inicializador estático, inicializador de instância ou classe nomeada
- Nenhuma quebra de linha depois da chave caso ela seja seguida de else, catch, finally ou ponto e vírgula
Exemplo:
return new MinhaClasse() { |
Quebra de linha
O limite de tamanho de coluna é de 120 caracteres. Ao quebrar expressões grandes, coloque os símbolos separadores no fim da linha ao invés de na próxima linha. Para melhorar a legibilidade em algumas situações, uma linha poderá ser quebrada antes de atingir o limite de 120 caracteres. Deverão ser adicionados 2 níveis de indentação a uma linha quebrada. Por exemplo:
if (chamadaDeMetodo1(param1, param2) && chamadaDeMetodo2() && chamadaDeMetodo3() && |
// Quebrando a linha antes de atingir 120 caracteres para melhorar a legibilidade. |
Quando quebrar
A seguir estão algumas diretrizes de quando quebrar a linha:
- Métodos e construtores devem ter o parênteses de abertura na mesma linha, mesmo que ultrapassem o limite de tamanho de coluna. Apesar disso, deve-se evitar nomes de métodos grandes demais, a ponto de ultrapassar esse limite.
- Quando uma linha for quebrada em uma expressão de atribuição, a quebra deve ocorrer após o símbolo de igual.
- Em blocos multi-catch, o operador | deve aparecer antes da quebra de linha.
Blocos vazios
Opcionalmente um bloco vazio pode ser fechado imediatamente depois de ser aberto, sem nenhum caracter ou quebra de linha entre as chaves, a não ser que seja uma instrução de multi-bloco (uma que contém múltiplos blocos if/else-if/else ou try/catch/finally) Exemplo:
void metodo() {} |
Lambdas
Ao escrever expressões lambda, especialmente como parâmetros para métodos da API de streams, deve-se optar por deixar cada chamada de método que receba uma expressão lambda em uma linha separada. Por exemplo:
return Optional.ofNullable(SecurityContextHolder.getContext().getAuthentication()) |
Nomeação
Identicadores
Os identificadores devem seguir as regras do Java. Evite utilizar identificadores com apenas um caracter. Por exemplo, prefira Method metodo a Method m (uma exceção a essa recomendação é a já tradicional utilização de i, j, k, etc como identificadores de contadores, especialmente em instruções for)
Constantes
Nomes de constantes utilizam todas suas letras em maiúsculas com as palavras separadas por underscore (CONSTANT_CASE). Toda constante é um campo com os modificadores static e final, porém nem todos os campos com esses modificadores são constantes. Um campo static final cujo estado observável pode mudar certamente não pode ser considerado uma constante. Por exemplo:
// Constantes |
Pacotes
Nomes de pacotes devem estar com todas as letras em minúsculo, com palavras consecutivas apenas concatenadas, sem underscore entre elas. Por exemplo, br.jus.stf.plataforma.userauthentication, mas não br.jus.stf.plataforma.userAuthentication ou br.jus.stf.plataforma.user_authentication.
Classes
Os nomes de classes devem ser escritos em UpperCamelCase. Nomes de classe são tipicamente substantivos ou substantivos seguidos de qualificadores. Por exemplo, Anexo ou ClassePeticionavel. Nomes de interfaces também podem ser substativos ou substantivos seguidos de qualificadores (por exemplo, List), além de adjetivos (por exemplo, Peticionavel).
Classes de teste unitário devem iniciar com o nome da classe que estão testando seguido de UnitTests. Por exemplo, PecaUnitTests. Classes de teste de integração devem iniciar com o nome da funcionalidade seguido de IntegrationTests. Por exemplo, ProcessoOriginarioIntegrationTests.
Métodos
Nomes de métodos devem ser escritos em lowerCamelCase. Nomes de métodos são tipicamente verbos ou verbos seguidos de qualificadores. Por exemplo, handle ou autuarRecursal. No contexto do DDD, em alguns casos, podem também existir métodos escritos como substantivos. Isso ocorre especificamente quando o método retorna alguma propriedade do domínio (normalmente em substituição aos getters).
Campos
O nome de campos não constantes (estáticos ou não) devem ser escritos em lowerCamelCase. Esses nomes são normalmente substantivos ou substantivos seguidos de qualificadores. Por exemplo, numeroProcesso ou peticao.
Parâmetros
Os nomes de parâmetros de métodos devem ser escritos em lowerCamelCase. Nomes de parâmetros com apenas um caracter devem ser evitados em métodos públicos.
Variáveis locais
Os nomes de variáveis locais devem ser escritos em lowerCamelCase. Mesmo quando finais e imutáveis, variáveis locais não são consideradas constantes e não devem ter seus nomes em CONSTANT_CASE.
Variáveis de tipo parametrizado
Os nomes das variáveis de tipo parametrizado devem ser escritos com uma única letra maiúscula, opcionalmente seguida de um único numeral (por exemplo, E, T, X, T2).
Comentários
Comentários de bloco devem aparecer no mesmo nível de indentação do código no qual eles apareçam. Eles podem ser escritos no estilo / … / ou // …. Para comentários de várias linhas, cada linha subsquente deve começar com alinhado ao da linha anterior. Por exemplo:
/* |
Se o comentário tiver apenas uma linha, deve-se utilizar preferencialmente o estilo // …. Por exemplo:
// Recupera o próximo status. |
O limite de tamanho de coluna dos comentários também é de 120 caracteres.
Javadoc
O javadoc deve ser escrito no estilo /* … / e possui um limite de tamanho de coluna de 120 caracteres. Por exemplo:
/** |
A primeira linha do javadoc conterá sempre apenas os caracteres /**. Todos os parágrafos exceto o primeiro conterá
antes da primeira palavra (lembrando que essa tag não precisa ser fechada com
).O primeiro parágrafo do javadoc deverá conter uma breve descrição da classe ou método. Os parágrafos subsequentes poderão detalhar o funcionamento da classe ou método.
Cláusulas @
As cláusulas @author e @since são obrigatórias para javadocs de classes top-level e devem aparecer no fim do javadoc, depois de seus parágrafos de descrição. Normalmente não se deve utilizar @author em métodos específicos de uma classe. Caso mais de um desenvolvedor tenha contribuído com a escrita de uma classe, mais de uma cláusula @author deverá ser colocada no javadoc da classe com os respectivos autores. Já o @since poderá ser utilizado em algum método da API pública de uma classe.
As outras cláusulas @ que podem aparecer em uma linha isoladas devem aparecer na seguinte ordem: @author, @since, @param, @return, @throws, @see, @deprecated.
A seguir estão algumas orientações específicas de algumas cláusulas:
- @since: Poderá ter a versão da proxima release. Enquanto não tiver nenhuma release, deverá ser 1.0.0. Também poderá aparecer uma cláusula @since com a data na qual a classe foi criada no formato dd.mm.aaaa (dia, mês, ano)
- @param: Essa cláusula deverá ser seguida por espaço e o nome do parâmetro do método. Depois do nome, deverá seguir uma breve descrição do parâmetro precedida por um espaço em branco.
- @return: Essa cláusula deverá ser seguida por uma descrição do retorno do método.
- @throws: Essa cláusula deverá ser usada para documentar exceções checadas ou não-checadas que podem ser lançadas pelo método. Deverá seguir o padrão @throws MinhaExcecao se …, que trará a situação sob a qual essa exceção será lançada.
Quando utilizar
O javadoc deverá estar presente no mínimo para cada classe pública e para cada método público ou protegido dela, com algumas exceções a seguir:
- Métodos simples e óbvios como getSigla ou setSigla
- Métodos que sobreescrevem métodos de classes-pai.
Outras classes e métodos que não os citados acima também podem opcionalmente ter javadoc, quando necessário para esclarecer o código.
Boas práticas de programação
Utilização de @Override
Um método deve ser anotado com @Override sempre que esse método estiver sobreescrevendo um método de uma classe pai ou implementando um método de uma interface.
Métodos setter
Crie métodos setters apenas se eles realmente forem necessários. A ordem dos setters deve refletir sua importância e não apenas a ordem histórica de inclusão. Além disso, essa ordem deverá ser consistente com a ordem dos campos.
Operador ternário
Sempre coloque o operador ternário entre parênteses. Por exemplo:
return (foo != null ? foo : "default"); |
Referências a métodos e campos
Um campo de uma classe deve sempre ser referenciado utilizando this. Por outro lado, um método de uma classe nunca deve ser referenciado usando this.
Documentação de APIs
As APIs Rest deverão ser documentadas com as anotações do Swagger. O método da classe RestResource responsável por tratar alguma operação deve ser anotado com @ApiOperation com uma descrição apropriada para a operação. Por exemplo:
@RequestMapping(value = "/devolucao-assinatura", method = RequestMethod.POST) |
Os commands recebidos pelas APIs Rest deverão ser documentados com as anotações @ApiModel no nível da classe e @ApiModelProperty nos seus campos. Por exemplo:
@ApiModel( |
Práticas do Domain-Driven Design
Classes de domínio
A seguir estão algumas regras gerais sobre práticas recomendadas nas classes de domínio:
- Os nomes das classes devem refletir a linguagem ubíqua baseada no modelo de domínio.
Não deverão ser usados métodos getters iniciando com get. Tais métodos deverão ser nomeados com apenas o nome da propriedade de negócio que ele representa. Por exemplo:
public PessoaId pessoa() {
return pessoa;
}Não deverão existir métodos setters para simplesmente alterar um estado de um objeto de domínio. A alteração de seu estado deverá ocorrer por meio de métodos com nomes que reflitam a linguagem ubíqua adotada. Por exemplo:
public void autuar(Classe classe, Long numero, Set partes, Autuador autuador, Status status) {
identificar(classe, numero);
autuar(partes, autuador, status);
}
Formatador de código Java
A seguir poderá ser baixado o formatador de código Java para o Eclipse: Download
Padrão de Codificação de Front-end (Typescript)
Arquivos de código-fonte
Funcionalidade é um nome utilizado para descrever um caso de uso da interface gráfica e agrupa um conjunto de componentes AngularJS e tipos do Typescript.
Nome de arquivo
O nome de um arquivo de código-fonte terá a extensão “.ts” e seguirá as seguintes regras de formação:
Para arquivos com componentes do Angular como modules, controllers, services, filters, etc., o nome deverá ser formado por nome-componente.tipo.ts, em que nome-componente é a parte inicial do nome da classe convertido de CammelCase para kebab-case
// Arquivo peticionamento-originario.controller.ts
...
export class PeticionamentoOriginarioController {
...
}
peticionamentoOriginario.controller("app.autuacao.peticionamento.originario.PeticionamentoOriginarioController",
PeticionamentoOriginarioController);
...Tipos que são compartilhados para utilização em mais de um ponto de uma funcionalidade devem ser incluídos em um único arquivo com o nome funcionalidade.model.ts. Tipos que são utilizados apenas na implementação de um único componente podem permanecer no próprio arquivo do mesmo e não devem ser exportados.
- Quando um componente AngularJS for implementado por uma classe, o nome do arquivo que o contém deve ser o nome dela (desconsiderando o sufixo do tipo de componente) todo em minúsculo convertendo de CammelCase para a notação de cada palavra separada por hífen (kebab-case), seguido de “.”, do tipo do componente e da extensão “.ts”. Por exemplo, a classe AutuacaoRecursalService deve estar em um arquivo de nome autuacao-recursal.service, a classe AutuacaoRecursalController em um de nome autuacao-recursal.controller.
Exemplos:
// Arquivo autuacao-recursal.service.ts |
// Arquivo autuacao-recursal.model.ts. "autuacao-recursal" é o nome da funcionalidade que utiliza esses tipos. |
Codificação de arquivo
A codificação dos arquivos de código-fonte será a UTF-8.
Estrutura de arquivos de código-fonte
Um arquivo de código-fonte consiste das seguintes seções (quando existir), nessa ordem:
- Bloco de namespace
- Instruções import
- Definições de tipos (classes, interfaces e enums, etc) e funções
- Instruções executáveis (serão executadas quando um módulo for importado)
Exatamente uma linha em branco separará uma seção da outra.
Instruções import
Deve-se utilizar a notação de import/export do ECMAScript 2015. Exemplos:
import {Parte, Processo} from "../../shared/autuacao.model"; |
import IStateService = angular.ui.IStateService; |
... |
No import, não deverão ser utilizados espaços depois de { e antes de }.
Os imports deverão ser organizados em grupos na seguinte ordem, cada qual separado do outro por uma linha em branco:
- Imports de bibliotecas externas
- Imports de bibliotecas do STF Digital que estão fora do contexto atual
- Imports de bibliotecas do contexto atual (imports relativos)
- Imports diretos de arquivos
Nesses grupos, primeiro deverão aparecer os imports de alias e depois os imports de módulos externos. No caso de imports de módulos externos, deverão aparecer primeiro os imports de tipos e depois os imports de variáveis. Cada import de um módulo deve trazer todos os tipos que são importados dele entre chaves e separados por vírgula e espaço. A ordem dos tipos importados deve ser a ordem alfabética. No caso de várias instruções imports de diferentes módulos, essas instruções devem ser ordenadas alfabeticamente pelo nome do módulo. Se o nome do módulo for um nome relativo ao módulo atual, os módulos mais distantes do módulo atual devem aparecer primeiro.
Exemplos:
import IHttpService = angular.IHttpService; // Alias para biblioteca externa |
Declarações de tipos
Um arquivo pode conter várias declarações de tipos. No caso de classes que são componentes AngularJS, é recomendável que apenas essa classe seja exportada, sendo permitida a declaração de outros tipos auxiliares mas que não devem ser exportados. Um tipo deverá ser definido antes de ser utilizado por outro tipo. Portanto, se a ClasseA utilizar a ClasseB, a definição da ClasseA deverá aparecer antes da definição da ClasseB no arquivo de código-fonte.
Ordem dos membros de uma classe
A ordem dos membros da classe deve ser a seguinte:
- Campos estáticos
- Campos de instância
- Construtores
- Métodos privados chamados por todos os construtores
- Métodos-fábrica estáticos
- Getters e setters
- Implementações de métodos oriundos de interfaces
- Métodos privados ou protegidos que são chamados por implementações de métodos oriundos de interfaces
- Outros métodos
Os métodos privados devem ser colocados imediatamente abaixo do método que os referencia.
Formatação
Indentação
Cada nível de indentação deve conter 4 espaços em branco. Tabs não deverão ser utilizados para esse propósito.
Uma instrução por linha
Cada instrução deverá ser seguida de uma quebra de linha.
Linhas em branco
Uma linha em branco deverá aparecer:
- Entre membros consecutivos de uma classe: campos, construtores, métodos.
- Entre instruções, conforme necessidade, para organizar o código em subseções lógicas.
- Entre cada grupo de instruções import.
- Opcionalmente antes do primeiro membro ou depois do último membro da classe.
Chaves
Construções similares a bloco (corpo de classe, interface, método ou construtor) devem seguir o estilo de Kernighan & Ritchie (também conhecido como chaves egípcias):
- Nenhuma quebra de linha antes da chave de abertura mas um único espaço antes dela.
- Quebra de linha após a chave de abertura.
- Quebra de linha antes da chave de fechamento.
- Quebra de linha depois da chave de fechamento caso ela termine uma instrução ou o corpo de um método, construtor.
- Nenhuma quebra de linha depois da chave caso ela seja seguida de else, catch, finally ou ponto e vírgula.
Exemplo:
export class MinhaClasse { |
Quebras de linha
O limite de tamanho de coluna é de 120 caracteres. Ao quebrar expressões grandes, coloque os símbolos separadores no fim da linha ao invés de na próxima linha. Para melhorar a legibilidade em algumas situações, uma linha poderá ser quebrada antes de atingir o limite de 120 caracteres. Deverá ser adicionado 2 níveis de indentação a uma linha quebrada. Por exemplo:
public isValid(command: PeticionarOrgaoCommand): boolean { |
Quando quebrar
A seguir estão algumas diretrizes de quando quebrar a linha:
- Métodos e construtores devem ter o parênteses de abertura na mesma linha, mesmo que ultrapassem o limite de tamanho de coluna. Apesar disso, deve-se evitar nomes de métodos grandes demais, a ponto de ultrapassar esse limite.
- Quando uma linha for quebrada em uma expressão de atribuição, a quebra deve ocorrer após o símbolo de igual.
- Objetos escritos na notação JSON devem ser quebrados depois do { e antes do }, com cada propriedade escrita em uma linha. Entretanto, depois do {, apenas 1 nível de indentação deve ser adicionado após a quebra.
$stateProvider.state("app.tarefas.analise-repercussao-geral", {
url: "/autuacao/recursal/:informationId/analise-repercussao-geral",
views: {
"content@app.autenticado": {
templateUrl: "./analise-repercussao-geral.tpl.html",
controller: "app.autuacao.recursal.AnaliseRepercussaoGeralController",
controllerAs: "analise"
}
}
...
});
Blocos vazios
Opcionalmente um bloco vazio pode ser fechado imediatamente depois de ser aberto, sem nenhum caracter ou quebra de linha entre as chaves, a não ser que seja uma instrução de multi-bloco (uma que contém múltiplos blocos if/else-if/else) Exemplos:
class Foo { |
Funções anônimas
Funções anônimas devem ser escritas na notação de seta () => { } (arrow function)., quebrando sempre depois de { e antes de }. Entretanto, apenas 1 nível de indentação deve ser adicionado depois da quebra.
Opcionalmente, caso a função anônima tenha apenas uma instrução com return, a função anônima poderá ficar em apenas uma linha.
Exemplo:
public distribuirProcesso(): void { |
Nomeação
Identificadores
Todos os nomes de variáveis, funções, classes, etc devem ser compostos de uma seuqência dos caracteres A-Z, a-z, permitindo iniciar com $ ou _ quando for o caso.
Variáveis e funções
Nomes de variáveis e funções devem ser escritos em lowerCamelCase.
Namespaces
Nomes de namespaces deverão ser formados por componentes separados por ponto, sendo cada componente escrito com todas as letras em minúsculo e sem hífen separando diferentes palavras. Por exemplo:
namespace app.novoprocesso { // Nome correto de namespace |
Classes, Interfaces e Enums
Os nomes de classes, interfaces e enums deverão ser escritos em UpperCamelCase.
Métodos e Funções
Os nomes de métodos e funções devem ser escritos em lowerCamelCase. No caso de métodos, suas chamadas sempre deverão ter a palavra reservada this antes, por imposição do próprio Typescript. Exemplos:
export class NovoProcessoController { |
Propriedades
As propriedades devem ser escritas em lowerCamelCase. Esses nomes são normalmente substantivos ou substantivos seguidos de qualificadores. Por exemplo, peticaoInicial ou processo.
Parâmetros
Os nomes de parâmetros de métodos, funções e construtores devem ser escritos em lowerCamelCase. Nomes de parâmetros com apenas um caracter devem ser evitados em métodos públicos.
Variáveis locais
Os nomes de variáveis locais devem ser escritos em lowerCamelCase.
Variáveis de tipos parametrizados (generics)
Os nomes das variáveis de tipo parametrizado devem ser escritos com uma única letra maiúscula, opcionalmente seguida de um único numeral (por exemplo, E, T, X, T2).
export class StepsChain<S> { |
Comentários
Comentários de bloco devem aparecer no mesmo nível de indentação do código no qual eles apareçam. Eles podem ser escritos no estilo / … / ou // …. Para comentários de várias linhas, cada linha subsquente deve começar com alinhado ao da linha anterior. Por exemplo:
/* |
Se o comentário tiver apenas uma linha, deve-se utilizar preferencialmente o estilo // …. Por exemplo:
// Realiza a devolução da remessa. |
O limite de tamanho de coluna dos comentários também é de 120 caracteres.
Não é necessário escrever comentários para trechos de código que são autoexplicativos. Por exemplo, o comentário abaixo não contribui para facilitar o entendimento do código:
// Seta o indice para zero. |
JSDoc
Todos os tipos (classes, interfaces, enums) e métodos públicos devem ter um bloco de comentário /* … / (JSDoc) explicando seu propósito, funcionamento, descrição de parâmetros e retornos.
Formatação
O JSDoc deve ser escrito no estilo /* … / e possui um limite de tamanho de coluna de 120 caracteres. Por exemplo:
/** |
A primeira linha do JSDoc conterá sempre apenas os caracteres /**. Cada bloco de texto separado por uma linha em branco no JSDoc formará um parágrafo.
O primeiro parágrafo do JSDoc deverá conter uma breve descrição da classe ou método. Os parágrafos subsequentes poderão detalhar o funcionamento da classe ou método.
Cláusulas @
As cláusulas @author e @since são obrigatórias para JSDocs de classes exportadas e devem aparecer no fim do JSDoc, depois de seus parágrafos de descrição. Normalmente não se deve utilizar @author em métodos específicos de uma classe. Caso mais de um desenvolvedor tenha contribuído com a escrita de uma classe, mais de uma cláusula @author deverá ser colocada no JSDoc da classe com os respectivos autores. Já o @since poderá ser utilizado em algum método da API pública de uma classe.
As outras cláusulas @ que podem aparecer em uma linha isolada devem aparecer na seguinte ordem: @author, @since, @param, @return, @throws, @see, @deprecated.
Quando utilizar
O JSDoc deverá estar presente no mínimo para cada classe exportada e para cada método público ou protegido dela, com algumas exceções a seguir:
- Métodos simples e óbvios como getNumero ou setNumero.
- Métodos que sobreescrevem métodos de classes-pai.
Outras classes e métodos que não os citados acima também podem opcionalmente ter JSDoc, quando necessário para esclarecer o código.
Construções específicas
Instrução simples
- Cada linha deve contar no máximo uma instrução.
- Ponto e vírgula deverá ser sempre colocado no fim de uma instrução.
// Ruim. Sem ponto e vírgula.
let nomeSistema = "STF Digital"
// Bom. Com ponto e vírgula.
let nomeSistema = "STF Digital";
Instrução composta
Instruções compostas são instruções contendo uma lista de instruções entre chaves (também conhecido como bloco de instruções).
- As instruções devem iniciar cada uma em uma linha separada.
As instruções devem ser indentadas por um nível (4 espaços).
// Ruim
if (condicao === true) { ... }
// Bom
if (condicao === true) {
...
}As chaves devem seguir o estilo K&R, ou seja, { deve estar na mesma linha que inicia a instrução composta e } deve estar sozinha em uma linha indentada de forma a estar alinhada com {. Apesar de no estilo K&R original, o { de uma função ficar em uma linha separada, o mesmo não se aplica à variante adotada aqui.
// Ruim
if (condicao === true)
{
...
}
// Bom
if (condicao === true) {
...
}
// Ruim
while (condicao)
{
...
}
// Bom
while (condicao) {
...
}Chaves {} devem ser utilizadas para todos os blocos de instruções compostas, mesmo que eles tenham apenas uma instrução. Isso ajuda a evitar a introdução de bugs como alguém incluir mais uma instrução sem adicionar as chaves.
// Ruim
if (condicao === true) fazerAlgo();
// Ruim
if (condicao === true)
fazerAlgo();
// Bom
if (condicao === true) {
fazerAlgo();
}
Entretanto, uma exceção a essa regra são as funções flecha com apenas uma instrução, que podem ser escritas sem chaves {}. Escrevê-las dessa forma permite encadear vários callbacks que transformam um objeto sequencialmente de forma concisa.
sc.chain(() => this.requestUserCertificate()) |
Strings
Utilize aspas duplas “” para strings, inclusive para strings em instruções import.
// Ruim |
Métodos
Métodos devem ter seus modificadores de acesso explicitamente especificados (private, public ou protected), devendo-se evitar deixar um método sem nenhum modificador de acesso (o que o torna public no Typescript).
// Ruim
export class MinhaClasse {
// Ruim
metodoPublico1(parametro: string): void {
...
}
// Bom
public metodoPublico2(parametro: string): void {
...
}
// Bom
private metodoPrivado(parametro: string): number {
...
}
protected metodoProtegido(parametro: string): number {
...
}
}Métodos devem definir explicitamente o tipo de retorno. Isso permite ao compilador Typescript validar se seu método realmente está retornando o tipo especificado.
// Ruim
public metodo(parametro: string) {
...
return 7;
}
// Bom
public metodo(parametro: string): number {
...
return 7;
}
If
Os ifs devem seguir a formatação do estilo K&R, como nos exemplos a seguir:
if (/* condicao */) { |
For
As instruções for devem seguir a formatação do estilo K&R, como nos exemplos a seguir:
for (/* inicialização */; /* condicao */; /* atualizacao */) { |
While
As instruções while devem seguir a formatação do estilo K&R, como no exemplo a seguir:
while (/* condicao */) { |
Do While
As instruções do while devem seguir a formatação do estilo K&R, terminando com ;, como no exemplo a seguir:
do { |
Switch
As instruções switch devem seguir a formatação indicada a seguir, com cada grupo case exceto o default terminando com break, return ou throw.
switch (/* expressao */) { |
Try
Instruções try devem ser evitadas sempre que possível. Elas não são boas para controle de fluxo da aplicação. Quando utilizadas, elas devem ser formatadas como no exemplo a seguir:
try { |
Métricas de qualidade
O desenvolvimento de software é realizado por equipes heterogêneas, com desenvolvedores com diferentes níveis de experiência e com diferentes estilos de programação. Desse modo, caso não haja um controle mínimo sobre a qualidade do software, ele pode crescer de forma descontrolada e se tornar de difícil manutenção ou muito propenso ao aparecimento de bugs e vulnerabilidades.
As métricas de qualidade são um importante mecanismo para ajudar o acompanhamento do nível de qualidade de um software durante o seu desenvolvimento. Elas trazem visibilidade para os gestores e para os próprios desenvolvedores sobre vários aspectos do software, como: facilidade de manutenção, vulnerabilidades de segurança, propensão ao aparecimento de bugs em produção, quantidade de código testado, etc.
Muitas dessas métricas são complexas e exigem uma análise detalhada do código-fonte do software, o que torna inviável seu cálculo manual. Por isso, torna-se essencial a utilização de uma ferramenta específica que viabilize seu cálculo automático e permita a divulgação delas de forma simples e centralizada. Atualmente há várias ferramentas que podem ser utilizdas para esse propósito, dentre elas: SonarQube, Code Climate, Codacy, e outras. Cada uma dessas ferramentas possui formas diferentes de exibir métricas que indiquem a qualidade do código.
De modo geral, as seguintes métricas se aplicam a todas essas ferramentas:
- Taxa de cobertura de código (Teste)
- Taxa de sucesso em testes unitários
- Taxa de documentação de APIs públicas
- Complexidade do código
Sonarqube
No SonarQube, há algumas outras métricas facilmente disponíveis em sua interface:
- Nível de manutenibilidade
- Taxa de duplicação de código
- Violações categorizadas em diversos níveis: blocker, critical, major, minor e info. Tais violações agregam problemas de diversas naturezas que influenciam nas outras métricas.
Code Climate
O Code Climate consolida pendências de qualidade em um indicar único chamado GPA, que vai de 0.0 até 4.0, compreendendo as notas de A a F. Esse indicador agrega diferentes tipos de problemas no código. De modo geral, deve-se buscar manter o GPA no nível A.
Codacy
No Codacy, além das métricas já mencionadas acima, também estão disponíveis as seguintes métricas:
- Taxa de compatibilidade (Compatibility)
- Taxa de aderência ao padrão de codificação (Code Style)
- Taxa de tendência de aparecimento de erros (Error Prone)
- Taxa de conformidade com boas práticas de desempenho (Performance)
- Taxa de conformidade com boas práticas de segurança (Security)
- Taxa de código não utilizado (Unused Code)
Metas
As metas de nível de métricas de qualidade vão depender da ferramenta utilizada para analisar o código-fonte, pois cada ferramenta tem uma forma diferente de gerar essas métricas. De modo geral, os valores das metas desses indicadores deverão ser calibrados de acordo com cada caso. Normalmente a própria ferramenta mostra de forma visual que uma métrica está ruim. Deve-se procurar “zerar” todas as pendências indicadas pela ferramenta ou marcar como falso positivo, caso não se aplique ao caso.
A seguir temos tabelas de metas de métricas a serem perseguidas pelo STF Digital:
Sonarqube
Métrica | Meta |
---|---|
Taxa de cobertura de código (Teste) | >=80% |
Taxa de sucesso em testes unitários | =100% |
Taxa de documentação de APIs públicas | >=80% |
Complexidade do código por função/arquivo/classe | <=5.0/<=5.0/<=5.0 |
Nível de manutenibilidade | =A |
Taxa de duplicação de código | <=10% |
Violações blocker/critical/major/minor/info | =0/=0/<=100/<=200/<=50 |
Code Climate
Métrica | Meta |
---|---|
Taxa de cobertura de código (Teste) | >=80% |
GPA | =A |
Codacy
Métrica | Meta |
---|---|
Taxa de cobertura de código (Teste) | >=80% |
Taxa de sucesso em testes unitários | =100% |
Taxa de documentação de APIs públicas | >=80% |
Taxa de aderência à complexidade de código aceitável | >=90% |
Taxa de compatibilidade (Compatibility) | =100% |
Taxa de aderência ao padrão de codificação (Code Style) | >=80% |
Taxa de tendência de aparecimento de erros (Error Prone) | >=80% |
Taxa de conformidade com boas práticas de desempenho (Performance) | >=80% |
Taxa de conformidade com boas práticas de segurança (Security) | >=80% |
Taxa de código não utilizado (Unused Code) | >=80% |