Código Limpo

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 { 
/* ... */
catch (Exception e) {

}
try { 
/* ... */
catch (Exception e) {
LOGGER.info("Erro inesperado");
}
try { 
/* ... */
catch (Exception e) {
e.printStackTrace();
}

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) {
throw new throw new MyCustomException("Mensagem específica" , 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) {
LOGGER.error("Exception occurred", exp);
throw 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:

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:

  1. Instrução package
  2. Instruções import
  3. 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:

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:

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:

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):

Exemplo:

return new MinhaClasse() {
@Override
public void metodo() {
if (condicao()) {
fazerAlgo();
} else {
try {
alternative();
} catch (ProblemException ex) {
recuperar();
}
}
}
};

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() &&
chamadaDeMetodo4()) {
// ...
}
// Quebrando a linha antes de atingir 120 caracteres para melhorar a legibilidade.
return peticao.envolvidos().stream()
.map(envolvidoDtoAssembler::toDto)
.collect(Collectors.toList());

Quando quebrar

A seguir estão algumas diretrizes de quando quebrar a 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())
.filter(OAuth2Authentication.class::isInstance)
.map(OAuth2Authentication.class::cast)
.map(auth -> auth.getUserAuthentication().getDetails())
.map(Map.class::cast)
.map(principal -> principal.get("componentes"))
.map(List.class::cast)
.map(componentes -> componentes.contains(componente.getId()))
.orElse(false);

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
private static final Object NULL_HOLDER = new NullHolder();
public static final int DEFAULT_PORT = -1;

// Não são constantes
private static final ThreadLocal executorHolder = new ThreadLocal();
private static final Set internalAnnotationAttributes = new HashSet();

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:

/*
* Exemplo de // Exemplo de
* comentário. // comentário.
*/

Se o comentário tiver apenas uma linha, deve-se utilizar preferencialmente o estilo // …. Por exemplo:

// Recupera o próximo status.
Status status = statusAdapter.nextStatus(protocolo.identity(), command.getTipoProcesso());

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:

/**
* Este é um exemplo de um Javadoc. O texto deve ser escrito e quebrará
* normalmente.
*/
public Integer metodo(String parametro) {
...
}

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:

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:

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)
@ApiOperation(value = "Realiza a assinatura do ofício de devolução de uma remessa")
public void assinarOficio(@RequestBody @Valid AssinarOficioParaDevolucaoCommand command,
BindingResult binding) {
if (binding.hasErrors()) {
throw new IllegalArgumentException(message(binding));
}

recebimentoApplicationService.handle(command);
}

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(
description = "Command que realiza a ação de assinatura do ofício de devolução de uma remessa")
public class AssinarOficioParaDevolucaoCommand {

@NotNull
@ApiModelProperty("Protocolo da remessa")
private Long protocoloId;

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:

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:

Exemplos:

// Arquivo autuacao-recursal.service.ts
...
import autuacaoRecursal from "../shared/recursal.module";

class ValidadorAutuacao implements cmd.CommandValidator {
... // Essa classe pode ficar no mesmo arquivo que a classe AutuacaoRecursalService, pois é utilizada apenas aqui.
}

export class AutuacaoRecursalService {
... // Classe principal do arquivo, que vai determinar o nome dele.
}

autuacaoRecursal.service("app.autuacao.recursal.AutuacaoRecursalService", AutuacaoRecursalService);

...

// Arquivo autuacao-recursal.model.ts. "autuacao-recursal" é o nome da funcionalidade que utiliza esses tipos.
...
export class AutuarProcessoRecursalCommand implements Command {
...
}

export interface ProcessoRecursal extends Processo {
...
}

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:

  1. Bloco de namespace
  2. Instruções import
  3. Definições de tipos (classes, interfaces e enums, etc) e funções
  4. 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;
...
autuacaoRecursal.controller("app.autuacao.recursal.AutuacaoRecursalController", AutuacaoRecursalController);

export default autuacaoRecursal;

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:

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
import IHttpPromiseCallbackArg = angular.IHttpPromiseCallbackArg;
import IPromise = angular.IPromise;

import cmd = app.support.command;
import Properties = app.support.constants.Properties;

import {AutuarProcessoRecursalCommand, Tese} from "../shared/recursal.model"; // Ordem alfabética dentro dos parênteses
// Apesar de o nome do módulo abaixo começar com "a", o módulo acima, cujo nome começa com "s",
// está mais distante relativamente do que o abaixo
import {AutuacaoRecursalService} from "./autuacao-recursal.service";
import autuacaoRecursal from "../shared/recursal.module";

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:

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:

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):

Exemplo:

export class MinhaClasse {

private api: string;

public constructor(private $http: IHttpService) {
this.api = "minha-api";
}

public listarAssuntos(assunto: string): IPromise<Assunto[]> {
return this.$http.get(this.api + '/assuntos', {params: {'termo' : assunto}})
.then((response : ng.IHttpPromiseCallbackArg<Assunto[]>) => {
return response.data;
});
}

}

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 {
if (command.poloAtivo.length > 0 && command.poloPassivo.length > 0 && command.anexos.length > 0 &&
command.classeId && command.sigilo && command.orgaoId) {
return true;
}
return false;
}

Quando quebrar

A seguir estão algumas diretrizes de quando quebrar a 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) Exemplos:

class Foo {

public metodo(parametro: string): void {}

}

export class Bar {}

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 {
this.distribuicaoComumService.enviarProcessoParaDistribuicao(this.cmdDistribuir).then(() => {
this.$state.go("app.tarefas.minhas-tarefas");
this.messagesService.success("Processo distribuído com sucesso.");
}, () => {
this.messagesService.error("Erro ao distribuir o processo.");
});
}

public consultarAnaliseProcesso(processoId: number): IPromise {
return this.$http.get(this.api + "/" + processoId + "/analise-pressupostos-formais")
.then(response => response.data);
}

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

}

namespace app.novoProcesso { // Nome incorreto de namespace. Não se deve separar palavras com cammelCase

}

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 {

public classe: string;

public criarNovoProcesso(): Processo {
return new Processo(this.classe, this.gerarNumeroProcesso());
}

public gerarNumeroProcesso(): number {
...
}

}

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:

/*
* Exemplo de // Exemplo de
* comentário. // comentário.
*/

Se o comentário tiver apenas uma linha, deve-se utilizar preferencialmente o estilo // …. Por exemplo:

// Realiza a devolução da remessa.
this.devolucaoService.devolver(this.cmdDevolucao);

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.
let indice = 0;

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:

/**
* Este é um exemplo de um JSDoc. O texto deve ser escrito e quebrará
* normalmente.
*/
public metodo(parametro: string): string {
...
}

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:

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

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).

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())
.chain((certificate: Certificate) => this.storeCertificate(certificate))
.promise();

Strings

Utilize aspas duplas “” para strings, inclusive para strings em instruções import.

// Ruim
let nomeSistema = 'STF Digital';

// Bom
let nomeSistema = "STF Digital";

// Ruim
import {Remessa} from '../../services/model';
import {RemessasDashletService} from '../remessas-dashlet.service';

// Bom
import {Remessa} from "../../services/model";
import {RemessasDashletService} from "../remessas-dashlet.service";

Métodos

If

Os ifs devem seguir a formatação do estilo K&R, como nos exemplos a seguir:

if (/* condicao */) {
...
}

if (/* condicao */) {
...
} else {
...
}

if (/* condicao */) {
...
} else if (/* condicao */) {
...
} else {
...
}

For

As instruções for devem seguir a formatação do estilo K&R, como nos exemplos a seguir:

for (/* inicialização */; /* condicao */; /* atualizacao */) {
...
}

for (let variavel in objeto) {
...
}

for (let variavel of objeto) {
...
}

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 {
...
} while (/* condicao */);

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 */) {
case /* expressao1 */:
...
/* terminacao1 */
case /* expressao2 */:
...
/* terminacao2 */
default:
...
}

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 {
...
} catch (error: Error) {
...
}

try {
...
} catch (error: Error) {
...
} finally {
...
}

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:

Sonarqube

No SonarQube, há algumas outras métricas facilmente disponíveis em sua interface:

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:

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étricaMeta
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étricaMeta
Taxa de cobertura de código (Teste)>=80%
GPA=A

Codacy

MétricaMeta
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%