Soluções

Empresa

Modelos

Desenvolvedor

Recursos

Preços

Por

Keith Williams

14/10/2025

Engenharia em 2026 e além

Engenharia em 2026 e além

Engenharia em 2026 e além

Estamos a construir uma infraestrutura que deve quase nunca falhar. Para alcançar isso, movemo-nos rapidamente enquanto lançamos software de qualidade incrível, sem atalhos ou compromissos. Este documento delineia os padrões de engenharia que nos guiarão até 2026 e além.

Estrutura da Equipa

A nossa organização de engenharia é composta por cinco equipas principais, cada uma com responsabilidades distintas:

  • Equipa de Fundação: Foca-se no estabelecimento e manutenção dos padrões de codificação e padrões arquitetónicos. Esta equipa trabalha colaborativamente com outras equipas para estabelecer as melhores práticas em toda a organização.

  • Equipas de Consumidores, Empresas e Plataformas: Equipas focadas em produto que lançam funcionalidades rapidamente enquanto mantêm os padrões de qualidade definidos neste documento. Estas equipas provam que a rapidez e qualidade não são mutuamente exclusivas.

  • Equipa Comunitária: Responsável por rever rapidamente PRs da comunidade de código aberto, fornecer feedback e acompanhar esse trabalho até à fusão. Esta equipa garante que nossos contribuidores de código aberto têm uma ótima experiência e que suas contribuições atendem aos nossos padrões de qualidade.

Os Nossos Resultados Até Agora

Os dados falam por si. Ao longo do último ano e meio, transformámos fundamentalmente a forma como construímos software:

Screenshot 2025-10-01 at 9.40.36 PM.png

Aumentámos aproximadamente o nosso rendimento em engenharia para o dobro enquanto melhorámos simultaneamente a qualidade. Ainda mais impressionante é a mudança no que estamos a construir:

Screenshot 2025-10-01 at 9.41.51 PM.png

Realocámos com sucesso aproximadamente 20% do esforço de engenharia de correções para funcionalidades, melhorias de desempenho, refatorações e tarefas. Esta mudança demonstra que investir em qualidade e arquitetura não te retarda. Pelo contrário, acelera-te.

A Base da Cal.com Permite Excelência para coss.com

  • Cal.com é um negócio estável e lucrativo que continuaremos a desenvolver.

    • Este sucesso proporciona-nos uma vantagem única à medida que construímos o coss.com. Ao contrário dos primeiros dias da Cal.com, onde precisávamos de avançar rapidamente para estabelecer um ajuste de produto-mercado e construir um negócio sustentável, o coss.com começa a partir de uma posição de força.

  • Não precisamos ter pressa com o coss.com.

    • A estabilidade da Cal.com significa que podemos permitir-nos construir o coss.com da forma correta desde o primeiro dia. Temos o luxo de implementar estes padrões de engenharia sem a pressão das exigências imediatas do mercado ou restrições de financiamento. Esta é uma posição de partida fundamentalmente diferente.

  • A "lentidão" é um investimento, não um custo.

    • Sim, seguir estes padrões pode parecer mais lento inicialmente e pode até ser frustrante para alguns engenheiros. Escrever DTOs leva mais tempo do que passar tipos de base de dados diretamente para o frontend. Criar abstrações adequadas e injeção de dependências requerem mais design inicial. Manter mais de 80% de cobertura de teste para o novo código exige disciplina. Mas esta aparente lentidão é temporária, e o retorno é exponencial.

Considere os retornos compostos...

  • Código que está arquitetado corretamente desde o início não precisa de grandes refatorações posteriormente

  • Cobertura de testes elevada previne bugs que de outra forma consumiriam semanas de depuração e correções rápidas (ver 2023 a meados de 2024)

  • Abstrações adequadas tornam a adição de novas funcionalidades dramaticamente mais rápida com o tempo

  • Limites claros e DTOs previnem a erosão arquitetónica que eventualmente exige reescritas completas

A trajetória da Cal.com mostra o que acontece quando se otimiza para velocidade imediata. Alta velocidade inicial que gradualmente degrada à medida que a dívida técnica se acumula, atalhos arquitetónicos criam gargalos, e mais tempo é gasto para corrigir problemas do que construindo funcionalidades (ver gráfico anterior onde estávamos gastando 55-60% do esforço de engenharia em correções).

A trajetória do coss.com abraçará o poder de construir corretamente desde o primeiro dia. Velocidade inicial ligeiramente mais lenta enquanto estabelece padrões adequados, seguida de aceleração exponencial à medida que esses padrões trazem dividendos e permitem um desenvolvimento mais rápido com maior confiança.

Princípios Núcleo

1. Sem adiamento de qualidade

  • Minimizaremos o "Farei numa PR de seguimento" para pequenas refatorações.

    • PRs de seguimento para melhorias menores raramente se materializam. Em vez disso, acumulam-se como dívida técnica que nos sobrecarrega meses ou anos depois. Se uma pequena refatoração pode ser feita agora, faça-a agora. Seguintes devem ser reservados para mudanças substanciais que realmente justifiquem PRs separadas ou para casos excepcionais e urgentes.

2. Elevados padrões na revisão de código

  • Não deixes PRs passarem com muitos comentários apenas para evitar ser "a pessoa má."

    • É precisamente assim que as bases de código se tornam descuidadas com o tempo. A revisão de código não é sobre ser simpático. Trata-se de manter os padrões de qualidade que a nossa infraestrutura exige. Cada detalhe importa. Cada violação de padrão importa. Resolva-as antes de mesclar, não depois.

3. Empurre uns aos outros para fazer a coisa certa

  • Responsabilizamo-nos mutuamente pela qualidade

    • Tomar atalhos pode parecer mais rápido no momento, mas cria problemas que atrasam a todos mais tarde. Quando vês um colega prestes a mesclar uma PR com problemas óbvios, fala. Quando alguém sugere um truque rápido em vez de a solução adequada, resista. Quando estás tentado a pular testes ou ignorar padrões arquitetónicos, espera que os teus colegas te questionem.

  • Isto não é sobre ser difícil ou atrasar as pessoas

    • É sobre a propriedade coletiva de nossa base de código e nossa reputação. Cada atalho que uma pessoa toma torna-se problema de todos. Cada esquina cortada hoje significa mais sessões de depuração, mais correções rápidas, e mais clientes frustrados amanhã.

  • Faça com que desafiar decisões ruins, respeitosamente, seja um hábito

    • Se alguém diz "vamos apenas codificar isso por agora", a resposta esperada deve ser "o que seria necessário para fazê-lo da forma correta desde o início?" Se alguém quer cometer código não testado, a equipa deve reagir. Se alguém sugere copiar e colar em vez de criar uma abstração adequada, chame-o respeitosamente.

  • Estamos a construir algo que precisa de quase nunca falhar

    • Esse nível de confiabilidade não acontece por acaso. Acontece quando cada engenheiro se sente responsável pela qualidade, não apenas do seu próprio código, mas do sistema inteiro. Temos sucesso como equipa ou falhamos como equipa.

4. Visar a simplicidade

  • Priorizar clareza sobre engenhosidade

    • O objetivo é código que seja fácil de ler e entender rapidamente, não complexidade elegante. Sistemas simples reduzem a carga cognitiva para cada engenheiro.

  • Faça a si próprio as perguntas certas

    • Estou realmente a resolver o problema em questão?

    • Estou a pensar demais em possíveis casos de uso futuros?

    • Considerei pelo menos 1 outra alternativa para resolver isto? Como ela se compara?

  • Simplicidade não significa falta de funcionalidades

    • Só porque nosso objetivo é criar sistemas simples, isso não significa que eles devam parecer anémicos e faltando funcionalidade óbvia.

5. Automatize tudo

  • Aproveitar a IA

    • Gerar 80% do boilerplate e código não crítico usando IA, permitindo-nos focar exclusivamente na lógica de negócios complexa e arquiteturas críticas.

    • Construir alertas sem ruído e manuseio inteligente de erros.

    • Testes manuais são cada vez mais coisa do passado. A IA pode rapidamente e inteligentemente construir suítes de teste mega para nós.

  • O nosso CI é o chefe final

    • Tudo neste documento de padrões é verificado antes que o código seja fundido em PRs

    • Surpresas não chegam ao main

    • As verificações são rápidas e úteis

Padrões Arquitetónicos

Estamos a transitar para um modelo arquitetónico rigoroso baseado em Arquitetura de Slice Vertical e Design Orientado ao Domínio (DDD). Os seguintes padrões e princípios serão rigorosamente aplicados em revisões de PR e através de linting.

Arquitetura de Slice Vertical: pacotes/funcionalidades

A nossa base de código é organizada por domínio, não por camada técnica. O diretório pacotes/funcionalidades é o coração desta abordagem arquitetónica. Cada pasta dentro representa um slice vertical completo da aplicação, dirigido pelo domínio que toca.

Estrutura:


Cada pasta de funcionalidade é um slice vertical auto-contido que inclui tudo o que é necessário para esse domínio:

  • Lógica de domínio: As regras e entidades de negócios principais específicas dessa funcionalidade

  • Serviços de aplicação: Orquestração de casos de uso para esse domínio

  • Repositórios: Acesso a dados específico das necessidades dessa funcionalidade

  • DTOs: Objetos de transferência de dados para cruzar limites

  • Componentes de UI: Componentes do frontend relacionados a esta funcionalidade (quando aplicável)

  • Testes: Testes unitários, de integração, e e2e para esta funcionalidade

Por que as Slices Verticais Importam

A arquitetura tradicional em camadas organiza-se por preocupações técnicas:


Isso cria vários problemas:

  • Mudanças em uma funcionalidade requerem alterar arquivos espalhados por múltiplos diretórios

  • É difícil entender o que uma funcionalidade faz porque seu código está fragmentado

  • As equipas pisam nos pés umas das outras ao trabalhar em funcionalidades diferentes

  • Não é fácil extrair ou depreciar uma funcionalidade

A arquitetura de slice vertical organiza-se por domínio:


Isso resolve esses problemas:

  • Tudo relacionado à disponibilidade vive em pacotes/funcionalidades/disponibilidade

  • É possível entender toda a funcionalidade de disponibilidade ao explorar um diretório

  • As equipas podem trabalhar em funcionalidades diferentes sem conflitos (se a equipa de engenharia da Cal.com crescer, mas certamente no coss.com teremos equipas a assumir pacotes principais)

  • Funcionalidades são fracamente acopladas e podem evoluir independentemente

Diretrizes para a Organização de Funcionalidades

Em teoria, cada funcionalidade é implantável independentemente. Embora possamos não implantá-las separadamente, organizar dessa forma força-nos a manter dependências claras e acoplamento mínimo. Este é o ponto de partida e sucesso de microserviços, embora ainda não estejamos a implementar microserviços.

As funcionalidades comunicam-se através de interfaces bem definidas. Se reservas precisar de dados de disponibilidade, importa de @calcom/funcionalidades/disponibilidade através de interfaces exportadas, não penetrando em detalhes internos de implementação.

Código compartilhado vive em locais apropriados:

  • Utilitários independentes de domínio e preocupações transversais (autenticação, logging): pacotes/lib

  • UI compartilhada: pacotes/ui (e em breve coss.com ui)

Limites de domínio são aplicados automaticamente. Construiremos linting que evita entrar nos internos de funcionalidades onde não deves ter permissão. Se pacotes/funcionalidades/reservas tentar importar de pacotes/funcionalidades/disponibilidade/serviços/internal, o linter irá bloqueá-lo. Todas as dependências entre funcionalidades devem passar pela API pública da funcionalidade.

good_architecture_diagram.pngbad_architecture_diagram.png

Novas funcionalidades começam como slices verticais. Ao construir algo novo, cria uma nova pasta em pacotes/funcionalidades com o slice vertical completo. Isso torna claro o que estás a construir e mantém tudo organizado desde o primeiro dia.

Benefícios

  • Descobribilidade

    • Procurando lógica de reserva? Está tudo em pacotes/funcionalidades/reservas. Não há necessidade de caçar através de controladores, serviços, repositórios e utilitários espalhados pela base de código.

  • Testes mais fáceis

    • Teste a funcionalidade inteira como uma unidade. Tens todas as peças num só lugar, tornando o teste de integração natural e direto.

  • Dependências mais claras

    • Quando vês import { getAvailability } from '@calcom/funcionalidades/disponibilidade', sabes exatamente a qual funcionalidade estás a depender. Quando as dependências se tornam muito complexas, isso é óbvio e pode ser resolvido.

Padrão de Repositório e Injeção de Dependências

As escolhas tecnológicas não devem infiltrar-se através da aplicação. O problema do Prisma ilustra isso perfeitamente. Atualmente, temos referências ao Prisma espalhadas por centenas de arquivos. Isso cria um acoplamento massivo e torna as mudanças tecnológicas extremamente caras. Estamos a sentir a dor disso agora ao atualizar o Prisma para v6.16. Algo que deveria ter sido apenas uma refatoração localizada por trás de repositórios blindados tem sido uma perseguição quase interminável de questões em vários aplicativos.

O padrão daqui em diante:

  • Todo o acesso a banco de dados deve passar por classes de Repositório. Já temos um bom avanço nisso.

  • Repositórios são o único código que conhece o Prisma (ou qualquer outro ORM). Nenhuma lógica deve estar neles.

  • Repositórios são injetados através de contêineres de Injeção de Dependência

  • Se algum dia trocarmos do Prisma para o Drizzle ou outro ORM, as únicas mudanças necessárias são:

    • Implementações de Repositório

    • Cabeamento de contêiner DI para novos repositórios

    • Nada mais na base de código deve preocupar-se ou mudar

Isso não é teórico. É assim que construímos sistemas manuteníveis.

Objetos de Transferência de Dados (DTOs)

Tipos de banco de dados não devem vazar para o frontend. Isso tornou-se um atalho popular em nossa stack tecnológica, mas é um cheiro de código que cria múltiplos problemas.

  • Acoplamento tecnológico (tipos do Prisma acabam em componentes React)

  • Riscos de segurança (vazamento acidental de campos sensíveis)

  • Contratos frágeis entre servidor e cliente (isso é particularmente problemático à medida que construímos muitas mais APIs)

  • Incapacidade de evoluir o esquema de banco de dados de forma independente

  • Todas as conversões de DTOs através de Zod, mesmo para uma resposta de API, para garantir que todos os dados estão sendo validados antes de enviar ao usuário. Melhor falhar do que retornar algo errado.

O padrão daqui em diante:
Criar DTOs explícitos em cada limite arquitetónico.

  1. Camada de dados → Camada de aplicação → API: Transformar modelos de banco de dados em DTOs da camada de aplicação, depois transformar DTOs de aplicação em DTOs específicos de API

  2. API → Camada de aplicação → Camada de dados: Transformar DTOs de API através da camada de aplicação e em DTOs específicos de dados

Sim, isso requer mais código. Sim, vale a pena. Limites explícitos previnem a erosão arquitetónica que cria pesadelos de manutenção a longo prazo.

Padrões de Design Orientado ao Domínio

Os seguintes padrões devem ser usados correta e consistentemente:

  • Serviços de Aplicação

    • Orquestrar casos de uso, coordenar entre serviços de domínio e repositórios

  • Serviços de Domínio

    • Conter lógica de negócios que não pertence naturalmente a uma única entidade

  • Repositórios

    • Abstrair acesso a dados, isolar escolhas tecnológicas

  • Injeção de Dependências

    • Permitir acoplamento solto, facilitar testes, isolar preocupações

  • Proxies de Cache

    • Envolver repositórios ou serviços para adicionar comportamento de cache de forma transparente

    • Não é a única maneira de fazer caching, é claro, mas um bom ponto de partida

  • Decoradores

    • Adicionar preocupações transversais (logs, métricas, etc.) sem poluir a lógica de domínio

Consistência da Base de Código

Nossas bases de código devem parecer que uma pessoa as escreveu. Esse nível de consistência requer adesão estrita aos padrões estabelecidos, regras de linting abrangentes que aplicam padrões arquitetónicos, revisões de código que rejeitam violações de padrões + a ajuda de revisores de código de IA.

Mover Condicionais para o Ponto de Entrada da Aplicação

Declarações condicionais pertencem ao ponto de entrada, não espalhadas pelos teus serviços. Este é um dos princípios arquitetónicos mais importantes para manter código limpo e focado que não se descontrola em complexidade indomável.

Aqui está como o código se degrada ao longo do tempo: Um serviço é escrito para um propósito claro e específico. A lógica é limpa e focada. Então chega uma nova exigência de produto, e alguém adiciona uma declaração if. Alguns anos e várias mais exigências depois, esse serviço está repleto de verificações condicionais para diferentes cenários. O serviço tornou-se:

  • Complicado e difícil de ler

  • Difícil de entender e raciocinar

  • Mais suscetível a bugs

  • Violando responsabilidade única (tratando muitos casos diferentes)

  • Quase impossível de testar completamente

O serviço ultrapassou seus limites em termos de responsabilidades e lógica.

Uma Solução: Padrão de Fábrica com Serviços Especializados
Usar o padrão de fábrica para tomar decisões no ponto de entrada, depois delegar para serviços especializados que tratam sua lógica específica sem condicionais.

Exemplo da nossa base de código:
O BillingPortalServiceFactory determina se a faturação é para uma organização, equipa ou usuário individual, depois retorna o serviço apropriado:

export class BillingPortalServiceFactory {  
  static async createService(teamId: number): Promise<BillingPortalService>

Cada serviço então trata sua lógica específica sem precisar verificar "sou uma organização ou uma equipa?":

// OrganizationBillingPortalService handles ONLY organization logic
class OrganizationBillingPortalService extends BillingPortalService {  
  async checkPermissions(userId: number, teamId: number): Promise<boolean> {    
    return await this.permissionService.checkPermission({      
      userId,      
      teamId,      
      permission: "organization.manageBilling",  // Organization-specific      
      fallbackRoles: [MembershipRole.ADMIN, MembershipRole.OWNER],    
    });  
  }  
  // ... more organization-specific logic
}

// TeamBillingPortalService handles ONLY team logic
class TeamBillingPortalService extends BillingPortalService {  
  async checkPermissions(userId: number, teamId: number): Promise<boolean> {    
    return await this.permissionService.checkPermission({      
      userId,      
      teamId,      
      permission: "team.manageBilling",  // Team-specific      
      fallbackRoles: [MembershipRole.ADMIN, MembershipRole.OWNER]

Por que Isso Importa

  • Os serviços mantêm-se focados

    • Cada serviço tem uma responsabilidade e não precisa saber sobre outros contextos. A OrganizationBillingPortalService não contém declarações if verificando if (isTeam) ou if (isUser). Ela apenas sabe como lidar com organizações.

  • As mudanças são isoladas

    • Quando precisas modificar a lógica de faturação de uma organização, só tocas na OrganizationBillingPortalService. Não corres o risco de quebrar a faturação de equipas ou usuários. Não precisas rastrear através de condicionais aninhados para descobrir qual caminho o teu código toma.

  • Testar é direto

    • Teste cada serviço independentemente com seus cenários específicos. Não há necessidade de testar cada combinação de condicionais em diferentes contextos.

  • Novos requisitos não poluem o código existente

    • ex. Quando precisas adicionar faturação empresarial com regras diferentes, crias EnterpriseBillingPortalService. A fábrica ganha mais um condicional, mas os serviços existentes permanecem intactos e focados.

Como alcançar

  • Empurre condicionais para controladores, fábricas, ou lógica de roteamento. Deixe esses pontos de entrada tomar decisões sobre qual serviço usar.

  • Mantenha os serviços puros e focados em uma única responsabilidade. Se um serviço precisa verificar "que tipo sou eu?", provavelmente precisas de múltiplos serviços.

  • Prefira polimorfismo sobre condicionais

    • Interfaces definem o contrato. Implementações concretas fornecem os detalhes.

  • Preste atenção na acumulação de declarações if

    • Durante a revisão de código, se vires um serviço ganhando condicionais para diferentes cenários, esse é um sinal para refatorar em serviços especializados.

Design de API: Controladores Finos e Abstração HTTP

  • Controladores são camadas finas que tratam apenas de preocupações HTTP.

    • Eles recebem pedidos, processam-nos, e mapeiam dados para DTOs que são passados para a lógica central da aplicação. A partir de agora, nenhuma lógica de aplicação ou central deve ser vista em rotas de API ou manipuladores tRPC.

  • Devemos separar a tecnologia HTTP da nossa aplicação.

    • A forma como transferimos dados entre cliente e servidor (seja REST, tRPC, etc.) não deve influenciar como a nossa aplicação central funciona. HTTP é um mecanismo de entrega, não um fator de condução arquitetónico.

Responsabilidades do controlador (e APENAS estas):

  • Receber e validar pedidos recebidos

  • Extrair dados de parâmetros de pedido, corpo, headers

  • Transformar dados do pedido em DTOs

  • Chamar serviços de aplicação apropriados com esses DTOs

  • Transformar respostas de serviços de aplicação em DTOs de resposta

  • Retornar respostas HTTP com códigos de estado adequados

Controladores não devem:

  • Conter lógica de negócios ou regras de domínio

  • Aceder diretamente a bancos de dados ou serviços externos

  • Realizar transformações de dados complexas ou cálculos

  • Tomar decisões sobre o que a aplicação deve fazer

  • Conhecer detalhes da implementação do domínio

Exemplo de padrão de controlador fino:


Versionamento de API e Alterações Inquebráveis

Sem alterações inquebráveis. Isto é crítico. Uma vez que um ponto de extremidade de API é público, ele deve permanecer estável. Alterações inquebráveis destroem a confiança dos desenvolvedores e criam pesadelos de integração para os nossos usuários.

Estratégias para evitar alterações inquebráveis:

  • Sempre adicione novos campos como opcionais

  • Use versionamento de API quando precisar mudar o comportamento existente

  • Desaclassifique endpoints antigos de forma graciosa, com caminhos claros de migração

  • Mantenha a compatibilidade retroativa por pelo menos duas versões principais

Quando precisas fazer alterações inquebráveis:

  • Crie uma nova versão de API usando o versionamento específico por data na API v2 (talvez venhamos a considerar o versionamento nomeado que a Stripe recentemente introduziu também)

  • Execute ambas as versões simultaneamente durante a transição (já fazemos isso na API v2)

  • Forneça ferramentas de migração automatizadas, quando possível

  • Dê aos usuários tempo abundante para migrar (mínimo de 6 meses para APIs públicas)

  • Documente exatamente o que mudou e por quê

Desempenho e Complexidade de Algoritmos

Construímos para grandes organizações e equipas. O que funciona bem com 10 usuários ou 50 registos pode colapsar sob o peso da escala empresarial. Performance não é algo que otimizamos depois. É algo que construímos corretamente desde o início.

Pensa em Escala Desde o Primeiro Dia

Quando a construir funcionalidades, pergunta sempre: "Como isso se comporta com 1.000 usuários? 10.000 registos? 100.000 operações?" A diferença entre algoritmos O(n) e O(n²) pode ser imperceptível no desenvolvimento, mas catastrófica em produção.

Padrões O(n²) comuns para evitar:

  • Iterações de array aninhadas (.map dentro de .map, .forEach dentro de .forEach)

  • Métodos de array como .some, .find, ou .filter dentro de loops ou callbacks

  • Verificar cada item em relação a cada outro item sem otimização

  • Filtros encadeados ou mapeamento aninhado sobre listas grandes

Exemplo do mundo real: Para 100 slots disponíveis e 50 períodos ocupados, um algoritmo O(n²) realiza 5.000 verificações. Amplia isso para 500 slots e 200 períodos ocupados, e estás a fazer 100.000 operações. Isso é um aumento de 20x na carga computacional por apenas um aumento de 5x nos dados.

Escolhe as Estruturas de Dados e Algoritmos Certos

Problemas de performance são frequentemente resolvidos ao escolher melhores estruturas de dados e algoritmos:

  • Ordenação + saída antecipada: Ordena os teus dados uma vez, depois sai dos loops quando souberes que itens restantes não irão corresponder

  • Pesquisa binária: Usa pesquisa binária para buscas em arrays ordenados em vez de verificações lineares

  • Técnicas de dois ponteiros: Para mesclar ou intercalar sequências ordenadas, caminha através de ambas com ponteiros em vez de loops aninhados

  • Mapas/sets de hash: Usa objetos ou Sets para buscas O(1) em vez de .find ou .includes em arrays

  • Árvores de intervalo: Para agendamento, disponibilidade e consultas de intervalos, usa estruturas de árvore adequadas em vez de comparação por força bruta

Transformação de exemplo:

// Bad: O(n²) - checks every slot against every busy time
availableSlots.filter(slot => {  
  return !busyTimes.some(busy => checkOverlap(slot, busy));
});

// Good: O(n log n) - sort once, break early
const sortedBusy = [...busyTimes]

Verificações de Performance Automatizadas

Implementaremos múltiplas camadas de defesa contra regressões de performance:

Regras de linting que sinalizam:

  • Funções com loops aninhados ou métodos de array aninhados

  • Chamadas múltiplas aninhadas a .some, .find, ou .filter

  • Recursão sem memoização

  • Padrões anti conhecidos para o nosso domínio (agendamento, verificações de disponibilidade, etc.)

Benchmarks de performance em CI que:

  • Executam algoritmos críticos em dados realistas e de grande escala

  • Comparam tempos de execução com um baseline em cada PR

  • Bloqueiam fusões que introduzem regressões de performance

  • Teste com dados em escala empresarial (milhares de usuários, dezenas de milhares de registos)

Monitoramento de produção que:

  • Rastreia tempo de execução para caminhos críticos

  • Alerta quando algoritmos tornam-se mais lentos à medida que os dados crescem

  • Captura regressões antes que os usuários percebam

  • Fornece dados de performance do mundo real para informar otimizações

Performance é uma Funcionalidade

Performance não é opcional. Não é algo a que "chegamos mais tarde." Para clientes empresariais a reservar entre equipas grandes, respostas lentas significam produtividade perdida e usuários frustrados (nossa experiência com alguns clientes empresariais maiores pode atestar isso).

Cada engenheiro deve:

  • Perfis de código antes de otimizar, mas pensa sobre complexidade desde o início

  • Teste com dados realistas e de grande escala (não apenas 5 registos de teste). Já temos scripts de seed criados. Provavelmente precisaremos estender.

  • Escolhe algoritmos eficientes e estruturas de dados desde o início

  • Observe por iterações aninhadas na revisão de código

  • Questione qualquer algoritmo que escala com o produto de duas variáveis

A Realidade NP-Difícil do Agendamento

Problemas de agendamento são fundamentalmente NP-difíceis. Isso significa que à medida que o número de restrições, participantes ou slots de tempo aumenta, a complexidade computacional pode explodir exponencialmente. A maioria dos algoritmos de agendamento ótimos tem pior caso de complexidade de tempo exponencial, tornando a escolha do algoritmo absolutamente crítica.

Implicações do mundo real:

  • Encontrar o tempo de reunião ideal para 10 pessoas em 3 fusos horários com restrições de disponibilidade individuais é computacionalmente caro

  • Adicionar detecção de conflitos, buffers, e uma infinidade de outras opções amplifica o problema

  • Escolhas de algoritmo ruins que funcionam bem para equipas pequenas tornam-se completamente inutilizáveis para grandes organizações

  • O que leva milissegundos para 5 usuários pode levar muitos segundos para organizações

Estratégias para gerenciar complexidade NP-difícil:

  • Use algoritmos de aproximação que encontram soluções "bom o suficiente" rapidamente em vez de soluções perfeitas lentamente

  • Implemente caching agressivo de horários e disponibilidades computados

  • Pré-compute cenários comuns durante horas de menor movimento

  • Divida problemas de agendamento grandes em pedaços menores e mais geríveis

  • Defina limites de tempo razoáveis e recue para algoritmos mais simples quando necessário

É por isso que a performance não é apenas um bom complemento em software de agendamento. É a fundação que determina se o teu sistema pode escalar até às necessidades empresariais ou colapsa sob padrões de uso do mundo real.

Requisitos de Cobertura de Código

  • Rastreamento global de cobertura

    • Rastreamos a cobertura total da base de código como uma métrica chave que melhora ao longo do tempo. Isso nos dá visibilidade sobre a maturidade de nossos testes e ajuda a identificar áreas que precisam de atenção. A porcentagem de cobertura global é exibida de forma proeminente em nossos dashboards.

  • 80%+ de cobertura para novo código

    • Cada PR deve ter 80%+ de cobertura de teste para o código que introduz ou modifica. Isso é aplicado automaticamente em nossa pipeline de CI. Se adicionas 50 linhas de novo código, essas 50 linhas devem estar cobertas por testes. Se modificas uma função existente, suas mudanças devem ser testadas. Esta é a cobertura de teste geral. A cobertura de teste unitário precisa estar próxima de 100%, especialmente com a capacidade de usar IA para ajudar a gerar estes.

Abordando o argumento "a cobertura não é toda a história": Sim, sabemos que a cobertura não garante testes perfeitos. Sabemos que podes escrever testes sem sentido que cobrem todas as linhas mas não testam nada significativo. Sabemos que a cobertura é apenas uma métrica entre muitas. Mas é certamente melhor visar uma alta porcentagem do que não ter ideia de onde estás.

Medindo Sucesso

  • "Velocidade" (roubando isso do Scrum, mesmo que não usemos Scrum)

    • Crescimento contínuo nas estatísticas mensais (funcionalidades, melhorias, refatorações)

  • Qualidade

    • Reduzir o esforço de PR gasto em correções de 35% atualmente para 20% ou menos até o final de 2026 (calculado com base em alterações de arquivos e adições/deleções)

  • Saúde arquitetónica

    • Métricas sobre adesão a padrões, acoplamento tecnológico, violações de limites

  • Eficiência de revisão

    • PRs menores, revisões mais rápidas, menos rodadas de feedback

  • Tempo de atividade da aplicação e API

    • Quão perto estamos de 99,99%?

Comece com o Cal.com gratuitamente hoje!

Experimente uma programação e produtividade sem interrupções, sem taxas ocultas. Registe-se em segundos e comece a simplificar a sua programação hoje, sem necessidade de cartão de crédito!