Assim como em qualquer projeto, ser consistente é extremamente importante. Por isso, criamos uma série de padrões para guiar o desenvolvimento da SDK e ajudar projetos que usam a SDK a manter o mesmo padrão.
Comentários são muito úteis, mas pense bem ao nomear suas variáveis. O objetivo é que um comentário não seja necessário para entender o que sua variável faz. Essa abordagem facilita muito a vida de quem faz manutenção no código.
Exemplos válidos:
export class Demo {
isLoggedIn: boolean;
private canViewPage: boolean;
}Exemplos inválidos:
export class Demo {
/**
* Indica se o usuário está logado.
**/
logged: boolean;
private canView: boolean; // Indica se o usuário pode ver a página.
}Todas as variáveis de classe devem ter nomes seguindo o padrão camelCase . As únicas exceções para essa regra são:
- Variáveis
protectedpodem começar com_(underline - sublinhado) para diferenciá-las nas implementações. - Variáveis
privateque tenham o mesmo nome de umget/setpodem começar com_(underline - sublinhado). Essa exceção, no entanto, deve ser usada com muita cautela. O ideal seria procurar um nome diferente que representasse a mesma ideia ou adicionar o sufixo local no nome da variável, ex:canViewLocal. - Variáveis
publicque sejam representações locais (acesso ao template - HTML) devem ter o mesmo padrão do import.
Exemplos válidos:
import { DemoTypeEnum } from '../models/enum/demo.type';
export class Auth {
DemoTypeEnum = DemoTypeEnum;
isLoggedIn: boolean;
protected _updatedUserData: UserDataModel;
private canViewPage: boolean;
}Exemplos inválidos:
import { DemoTypeEnum } from '../models/enum/demo.type';
export class Auth {
demoTypeEnum = DemoTypeEnum;
_isLoggedIn: boolean;
protected UpdatedUserData: UserDataModel;
private canview: boolean;
}Todas as variáveis de método devem ter nomes seguindo o padrão camelCase .
Todas as variáveis de arrow functions devem ter nomes seguindo o padrão camelCase . Tente usar nomes significativos, mas não longos. Evite usar sempre x ou alguma outra letra padrão.
O exemplo abaixo não seria considerado aceitável:
this.relatedItemsResource.search(request).subscribe(x => {
// ...
});Já os próximos, seriam:
this.relatedItemsResource.search(request).subscribe(ri => {
// ...
});
this.relatedItemsResource.search(request).subscribe(related => {
// ...
});
this.relatedItemsResource.search(request).subscribe(items => {
// ...
});
this.relatedItemsResource.search(request).subscribe(relatedItems => {
// ...
});Ao nomear variáveis de arrow functions tente ser adaptável à quantidade de linhas dela. Se uma arrow function é curta, um nome como ri pode ser o suficiente, pois o desenvolvedor consegue ver facilmente o que é ri . Quando ela for maior, tente ser mais declarativo como relatedItems , pois no corpo do código o desenvolvedor poderia facilmente se perder e não saber o que representa ri . Todos os exemplos acima são válidos, vai do bom senso do desenvolvedor escolher em qual momento usar cada uma das técnicas apresentadas.
As variáveis devem ser organizadas da seguinte maneira:
imports para uso no template
recomendação: quebra de linha
com decorators
recomendação: quebra de linha
públicas
públicas readonly
recomendação: quebra de linha
protected
protected readonly
recomendação: quebra de linha
private
private readonly
recomendação: quebra de linha
Exemplo:
import { DemoTypeEnum } from '../models/enum/demo.type';
@Component()
export class AuthComponent {
DemoTypeEnum = DemoTypeEnum;
@Input() id: string;
@Input() type: DemoTypeEnum;
isLoggedIn: boolean;
readonly isLoggedInSubject = new Subject<boolean>();
protected _updatedUserData: UserDataModel;
private canViewPage: boolean;
}Siga as orientações abaixo para cada tipo de comentário, mas em todos os casos lembre-se de que devem sempre terminar com ponto final.
Comentários de variáveis public devem sempre ser feitos com /** , nunca com // . Eles devem também ter no mínimo 3 linhas:
@Component()
export class DemoComponent {
/**
* ID do registro.
*/
@Input() id: string;
}Nunca escreva comentários públicos como comentários de uma linha. Os dois exemplos abaixo estão incorretos:
@Component()
export class DemoComponent {
/** ID do registro. */
@Input() id: string;
// Nome da configuração.
@Input() configName: string;
}Seguem o mesmo padrão de Comentários de variáveis public
Comentários de variáveis private devem sempre ser feitos com // , nunca com /** . Eles devem, preferencialmente, ter apenas uma linha e ficar na frente da definição. Caso seja necessário um comentário maior, eles podem ficar em cima da definição e ter mais de uma linha:
export class Demo {
private id: string; // ID do registro.
// Indica se o usuário logado tem direito a fazer a carga da página atual.
private canViewPage: boolean;
// Índice atual do arquivo de demonstração.
// Caso o índice esteja entre -1 e 1, o pai deste item deve ser validado manualmente.
// Caso contrário, deve ser validado automaticamente.
private demoIndex: number;
}Nunca escreva comentários private como comentários multi-linha e nunca deixe um comentário que cabe na frente da definição em cima dela. Os dois exemplos abaixo estão incorretos:
export class Demo {
// ID do registro.
private id: string;
/**
* Índice atual do arquivo de demonstração.
* Caso o índice esteja entre -1 e 1, o pai deste item deve ser validado manualmente.
* Caso contrário, deve ser validado automaticamente.
**/
private demoIndex: number;
}Todas os métodos devem ter nomes seguindo o padrão camelCase . A única exceção para essa regra é se o método for protected . Nesse caso, ele pode começar com _ (underline - sublinhado) para diferenciá-lo nas implementações.
Os métodos devem ser organizados da seguinte maneira:
construtor
quebra de linha
lifecycle do angular
quebra de linha
com decorators
quebra de linha
públicas
quebra de linha
protected
quebra de linha
private
quebra de linha
Exemplo:
@Component()
export class DemoComponent implements OnInit {
constructor(
private myService: MyService
) { }
ngOnInit(): void {
// ...
}
@HostListener('keydown')
keyDown(): void {
// ...
}
search(): void {
// ...
}
protected _locationSearch(): LocationModel {
// ...
}
private findUser(): UserModel {
// ...
}
}Comentários de métodos public devem sempre ser feitos com /** , nunca com // . Eles devem também ter no mínimo 3 linhas:
export class Demo {
/**
* Gera um slug para o registro atual.
*/
generateSlug(): string {
return '';
}
}Nunca escreva comentários públicos como comentários de uma linha. Os dois exemplos abaixo estão incorretos:
@Component()
export class DemoComponent {
/** Gera um slug para o registro atual. */
generateSlug(): string {
return '';
}
// Obtém o índice atual.
getCurrentIndex(): number {
return 0;
}
}Seguem o mesmo padrão de "Comentários de métodos public "
Seguem o mesmo padrão de "Comentários de métodos public "
Comentários de lógica devem sempre ser feitos com // , nunca com /** . Exemplo:
export class Demo {
private openAsDialog(): number {
// Normalmente, esse processo seria tratado pelo `open` que garante que temos apenas um overlay
// aberto por vez, mas já que resetamos as variáveis em funções assíncronas, alguns overlays
// podem passar sem tratamento se o usuário abre e fecha muitas vezes em seguida.
if (this.dialogRef) {
this.dialogRef.close();
}
}
}Nunca escreva comentários de lógica com /** . O exemplo abaixo está incorreto:
export class Demo {
private openAsDialog(): number {
/**
* Normalmente, esse processo seria tratado pelo `open` que garante que temos apenas um overlay
* aberto por vez, mas já que resetamos as variáveis em funções assíncronas, alguns overlays
* podem passar sem tratamento se o usuário abre e fecha muitas vezes em seguida.
**/
if (this.dialogRef) {
this.dialogRef.close();
}
}
}Todas as classes devem ter nomes seguindo o padrão PascalCase , também conhecido como TitleCase .
Todos os comentários de classes devem seguir o mesmo padrão de "2.c.i - Comentários de métodos public ".
Todas as interfaces devem ter nomes seguindo o padrão PascalCase , também conhecido como TitleCase e nunca devem começar com I sendo usado como identificador de interface.
Exemplo válido:
export interface OverlayReference { }
export interface IndexConfiguration { }Exemplos inválidos:
export interface IOverlayReference { }
export interface overlayReference { }Todos os comentários de interfaces devem seguir o mesmo padrão de "2.c.i - Comentários de métodos public ".
Todas os componentes devem ter nomes seguindo o padrão PascalCase , também conhecido como TitleCase e terminar com o sufixo Component .
Exemplos válidos:
export class CalendarComponent { }
export class DatepickerComponent { }Exemplos inválidos:
export class Calendar { }
export class datepickerComponent { }
export class MyPage { }
export class AddUserView { }Todos os comentários de componentes devem seguir o mesmo padrão de "2.c.i - Comentários de métodos public ".
Todas as diretivas devem ter nomes seguindo o padrão PascalCase , também conhecido como TitleCase e terminar com o sufixo Directive .
Exemplos válidos:
export class ClaimsDirective { }
export class ScrollableDirective { }Exemplos inválidos:
export class Claims { }
export class scrollableDirective { }Todos os comentários de diretivas devem seguir o mesmo padrão de "2.c.i - Comentários de métodos public ".
Todas os membros de um enum devem ter nomes seguindo o padrão PascalCase , também conhecido como TitleCase .
Todas os enums devem ter nomes seguindo o padrão PascalCase , também conhecido como TitleCase .
Todos os comentários de enums e membros de enums devem seguir o mesmo padrão de "2.c.i - Comentários de métodos public ".
Todo código deve ter indentação usando espaço em vez de tab e ter tamanho 4 .
Sempre adicione as interfaces quando utilizar métodos do lifecycle.
Exemplo válido:
export class DemoComponent implements OnInit, OnDestroy {
ngOnInit(): void {
// ...
}
ngOnDestroy(): void {
// ...
}
}Exemplos inválidos:
export class DemoComponent {
ngOnInit(): void {
// ...
}
ngOnDestroy(): void {
// ...
}
}
export class DemoComponent implements OnInit {
ngOnInit(): void {
// ...
}
ngOnDestroy(): void {
// ...
}
}Métodos de lifecycle do angular ( ngOnInit , ngOnDestroy , etc) só devem ser utilizados no seu contexto apropriado. Por exemplo o método ngOnInit nunca deveria ser usado numa classe com @Injectable .
Evite usar métodos de lifecycle que são conflitantes entre si. Exemplo: Uma diretiva, normalmente, não deveria usar DoCheck e OnChanges para tratar alterações no mesmo input, já que o ngOnChanges será sempre invocado cada vez que o change detector detectar mudanças.
Chamadas explícitas aos métodos de lifecycle do angular não devem ser feitas. Essa responsabilidade é do angular. Fazer esse tipo de chamada no código poderia ser confuso. Se você precisa executar uma série de comandos no ngOnInit e em outro momento da sua aplicação, por exemplo, coloque esse código em um método local e invoque esse método no ngOnInit e onde mais precisar.
Exemplo válido:
export class DemoComponent implements OnInit {
ngOnInit(): void {
// ...
this.setupConfig();
}
reloadConfig(): void {
this.setupConfig();
// ...
}
private setupConfig(): void {
}
}Exemplo inválido:
export class DemoComponent implements OnInit {
ngOnInit(): void {
// ...
}
reloadConfig(): void {
this.ngOnInit();
// ...
}
}Sempre use @HostListener e @HostBinding em vez de usar a tag host nos decorators @Component e @Directive .
Exemplo válido:
@Directive({
selector: '[appValidator]'
})
export class ValidatorDirective {
@HostBinding('attr.role') role = 'button';
@HostListener('mouseenter')
onMouseEnter(): void {
// ...
}
}Exemplo inválido:
@Directive({
selector: '[appValidator]',
host: {
'[attr.role]': 'role',
'(mouseenter)': 'onMouseEnter()'
}
})
export class ValidatorDirective {
role = 'button';
onMouseEnter(): void {
// ...
}
}Não utilize tipos onde eles não são necessários, como em atribuições de tipos diretos.
Exemplo válido:
export class Demo {
isOnline = false;
myMethod(): void {
const a = 10n;
const b = false;
const c = new RegExp('c');
const d = 'string';
}
mySecondMethod(a = 3, b = false): void {
}
myThirdMethod(a: number, b: boolean): void {
}
}Exemplo inválido:
export class Demo {
isOnline: boolean = false;
myMethod(): void {
const a: bigint = 10n;
const b: boolean = false;
const c: RegExp = new RegExp('c');
const d: string = 'string';
}
mySecondMethod(a: number = 5, b: boolean = false): void {
}
}Quaisquer itens não utilizados devem ser removidos. Esses itens geram lixo que, com o tempo, vão acumulando e tornando o código cada vez mais difícil de dar manutenção. Por isso, sempre verifique se existem imports, métodos ou variáveis que não estão sendo usados e os remova!
Escolhemos para o projeto o padrão "the one true brace style" (1TBS ou OTBS).
Exemplo válido:
export class Demo {
myMethod(): void {
if (isTrue) {
// ...
} else {
}
}
}Exemplo inválido:
export class Demo
{
myMethod(): void
{
if (isTrue) {
// ...
} else
{
}
if (isTrue) {
// ...
}
else
{
}
if (isTrue) {
// ...
}
else {
}
}
}Apesar de serem consideradas válidas no ECMAScript 5 e até no 3, não recomendamos seu uso. O uso dessas vírgulas desnecessárias pode confundir o desenvolvedor, por isso não usamos.
Exemplo válido:
export class Demo {
myArray = [
'Item 1',
'Item 2'
]
}Exemplo inválido:
export class Demo {
myArray = [
'Item 1',
'Item 2', // <- note a vírgula desnecessária
]
}O JavaScript permite omitir as chaves em um bloco de código que contenha só um comando. Entretanto, muitos consideram essa prática errada, pois podem gerar erros e reduzir a legibilidade do código. Por isso nunca omitimos as chaves.
Exemplo válido:
export class Demo {
myMethod(): void {
if (true) {
return;
}
}
}Exemplo inválido:
export class Demo {
myMethod(): void {
if (true) return;
}
}É considerada uma boa prática utilizar os operadores de tipo === e !== em vez dos regulares == e != . O motivo é que no JavaScript os operadores == e != fazem uma conversão de tipo que é considerada bem obscura (Abstract Equality Comparison Algorithm). Com isso vários casos "estranhos" são considerados como verdadeiros.
Todos esses exemplos a seguir são considerados true :
[] == false
[] == ![]
3 == '03'Se isso acontecer numa comparação, aparentemente inocente como a == b , o erro será muito difícil de encontrar. Por esse motivo, sempre use os operadores igualitários de tipo.
Utilizar o for in pode retornar propriedades incluídas no prototype. Para evitar que isso aconteça, sempre "proteja" o for in .
Por exemplo, em vez de:
export class Demo {
myMethod(): void {
for (key in myObj) {
const x = myObj[key];
}
}
}Faça:
export class Demo {
myMethod(): void {
for (key in myObj) {
if (myObj.hasOwnProperty(key)) {
const x = myObj[key];
}
}
}
}A ordenação dos imports é muito importante para facilitar a leitura e organização. Nós organizamos os imports em dois grupos e sempre devemos separar esses grupos com uma linha em branco.
Primeiro grupo:
builtin (node)
bibliotecas externas
Segundo grupo:
internos
Exemplo:
import fs from 'fs';
import { DefaultEntityResource } from '@unio/components';
import { DemoModel } from '../models/demo.model';Usar caminhos absolutos nos imports é uma prática ruim, pois o import estará atrelado ao caminho usado na máquina do desenvolvedor.
Exemplos válidos:
import { DemoModel } from '../models/demo.model';
import f from 'library';Exemplos inválidos:
import { DemoModel } from '/src/models/demo.model';
import f from '/my-drive/path';Exemplos:
import './../pages/about'; // deveria ser './pages/about'
import '../pages/about'; // deveria ser './pages/about'
import './pages//about'; // deveria ser './pages/about'
import './pages/'; // deveria ser './pages'
import './pages/index'; // deveria ser './pages'Após finalizar os imports deixe sempre uma linha em branco antes de começar a escrever o código.
Exemplo válido:
import { DemoModel } from '../models/demo.model';
export class Test {}Exemplo inválido:
import { DemoModel } from '../models/demo.model';
export class Test {}