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
protected
podem começar com_
(underline - sublinhado) para diferenciá-las nas implementações. - Variáveis
private
que tenham o mesmo nome de umget/set
podem 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
public
que 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 {}