Diretrizes de Desenvolvimento
3. Padrões de Programação

3. Padrões de Programação

3.1. Convenções de nomenclatura

Adotamos convenções de nomenclatura consistentes para garantir legibilidade e manutenibilidade do código. Seguimos o padrão camelCase para a base de código e snake_case para o banco de dados.

PHP (Laravel)

// Classes
class UserService
{
    // ...
}
 
// Métodos e variáveis
public function getUserById($userId)
{
    $userProfile = $this->userRepository->findById($userId);
    return $userProfile;
}
 
// Constantes
const MAX_LOGIN_ATTEMPTS = 5;
 
// Nomes de arquivos
// app/Services/UserService.php
// app/Http/Controllers/UserController.php
 
// Tabelas do banco de dados
// users
// user_profiles

Node.js (TypeScript)

// Interfaces
interface UserProfile {
    id: number;
    firstName: string;
    lastName: string;
    email: string;
}
 
// Classes
class UserService {
    // ...
}
 
// Métodos e variáveis
async function getUserById(userId: number): Promise<UserProfile> {
    const userProfile = await userRepository.findById(userId);
    return userProfile;
}
 
// Constantes
const MAX_LOGIN_ATTEMPTS = 5;
 
// Nomes de arquivos
// src/services/UserService.ts
// src/controllers/UserController.ts
 
// Tabelas do banco de dados
// users
// user_profiles

3.2. Estrutura de arquivos e diretórios

Uma estrutura de arquivos e diretórios bem organizada é crucial para a manutenibilidade do projeto.

PHP (Laravel)

app/
├── Console/
├── Exceptions/
├── Http/
│   ├── Controllers/
│   ├── Middleware/
│   └── Requests/
├── Models/
├── Providers/
├── Repositories/
├── Services/
└── Utils/
config/
database/
resources/
routes/
tests/

Node.js (TypeScript)

src/
├── config/
├── controllers/
├── middlewares/
├── models/
├── repositories/
├── routes/
├── services/
├── types/
└── utils/
test/

3.3. Estilo de codificação

Seguimos guias de estilo específicos para cada linguagem para manter consistência no código.

PHP (Laravel)

Seguimos o PSR-12 e as convenções do Laravel.

<?php
 
namespace App\Services;
 
use App\Repositories\UserRepository;
use App\Exceptions\UserNotFoundException;
 
class UserService
{
    private UserRepository $userRepository;
 
    public function __construct(UserRepository $userRepository)
    {
        $this->userRepository = $userRepository;
    }
 
    public function getUserById(int $userId): array
    {
        $user = $this->userRepository->findById($userId);
 
        if (!$user) {
            throw new UserNotFoundException("User with ID {$userId} not found");
        }
 
        return $user->toArray();
    }
}

Node.js (TypeScript)

Seguimos o guia de estilo do Airbnb para TypeScript.

import { UserRepository } from '../repositories/UserRepository';
import { UserNotFoundException } from '../exceptions/UserNotFoundException';
import { User } from '../types/User';
 
export class UserService {
  private userRepository: UserRepository;
 
  constructor(userRepository: UserRepository) {
    this.userRepository = userRepository;
  }
 
  async getUserById(userId: number): Promise<User> {
    const user = await this.userRepository.findById(userId);
 
    if (!user) {
      throw new UserNotFoundException(`User with ID ${userId} not found`);
    }
 
    return user;
  }
}

3.4. Documentação de código

A documentação adequada do código é essencial para a manutenibilidade a longo prazo.

PHP (Laravel)

Usamos PHPDoc para documentar classes, métodos e propriedades.

<?php
 
namespace App\Services;
 
use App\Repositories\UserRepository;
use App\Exceptions\UserNotFoundException;
 
/**
 * Class UserService
 * 
 * Handles business logic related to users.
 */
class UserService
{
    private UserRepository $userRepository;
 
    /**
     * UserService constructor.
     *
     * @param UserRepository $userRepository
     */
    public function __construct(UserRepository $userRepository)
    {
        $this->userRepository = $userRepository;
    }
 
    /**
     * Get user by ID.
     *
     * @param int $userId The ID of the user to retrieve
     * @return array The user data as an array
     * @throws UserNotFoundException If the user is not found
     */
    public function getUserById(int $userId): array
    {
        $user = $this->userRepository->findById($userId);
 
        if (!$user) {
            throw new UserNotFoundException("User with ID {$userId} not found");
        }
 
        return $user->toArray();
    }
}

Node.js (TypeScript)

Usamos JSDoc com TypeScript para documentação.

import { UserRepository } from '../repositories/UserRepository';
import { UserNotFoundException } from '../exceptions/UserNotFoundException';
import { User } from '../types/User';
 
/**
 * UserService class
 * 
 * Handles business logic related to users.
 */
export class UserService {
  private userRepository: UserRepository;
 
  /**
   * Creates an instance of UserService.
   * @param {UserRepository} userRepository - The user repository instance
   */
  constructor(userRepository: UserRepository) {
    this.userRepository = userRepository;
  }
 
  /**
   * Get user by ID
   * @param {number} userId - The ID of the user to retrieve
   * @returns {Promise<User>} The user data
   * @throws {UserNotFoundException} If the user is not found
   */
  async getUserById(userId: number): Promise<User> {
    const user = await this.userRepository.findById(userId);
 
    if (!user) {
      throw new UserNotFoundException(`User with ID ${userId} not found`);
    }
 
    return user;
  }
}

3.5. Tratamento de erros, exceções e observabilidade

O tratamento adequado de erros e exceções, combinado com estratégias robustas de logging e observabilidade, é crucial para a confiabilidade, manutenção e desempenho do sistema em escala empresarial.

3.5.1. Configuração de APM e Error Tracking

Utilizamos Sentry para rastreamento de erros e New Relic para APM. Ambas as ferramentas devem ser configuradas em todos os ambientes (desenvolvimento, staging e produção).

PHP (Laravel)

// config/sentry.php
return [
    'dsn' => env('SENTRY_LARAVEL_DSN'),
    'traces_sample_rate' => (float)(env('SENTRY_TRACES_SAMPLE_RATE', 0.2)),
];
 
// app/Exceptions/Handler.php
use Sentry\Laravel\Integration;
 
public function report(Throwable $exception)
{
    if ($this->shouldReport($exception) && app()->bound('sentry')) {
        app('sentry')->captureException($exception);
    }
 
    parent::report($exception);
}
 
// New Relic é configurado via .ini ou variáveis de ambiente
// Exemplo de configuração via variável de ambiente:
// NEW_RELIC_LICENSE_KEY=your_license_key
// NEW_RELIC_APP_NAME=Your_Application_Name

Node.js (TypeScript)

// src/config/sentry.ts
import * as Sentry from "@sentry/node";
import { RewriteFrames } from "@sentry/integrations";
 
Sentry.init({
  dsn: process.env.SENTRY_DSN,
  integrations: [
    new RewriteFrames({
      root: global.__dirname,
    }),
  ],
  tracesSampleRate: 0.2,
});
 
// src/config/newrelic.ts
import newrelic from 'newrelic';
 
export { newrelic };
 
// Certifique-se de que o arquivo newrelic.js está na raiz do projeto
// com a configuração adequada

3.5.2. Tratamento de Erros e Logging

Implementamos uma estratégia de logging em camadas que captura informações detalhadas sobre erros, facilitando a depuração e a análise.

PHP (Laravel)

<?php
 
namespace App\Exceptions;
 
use Exception;
use Illuminate\Support\Facades\Log;
use Sentry\State\Scope;
 
class UserNotFoundException extends Exception
{
    public function report()
    {
        Log::error('User not found', [
            'exception' => $this,
            'user_id' => $this->getUserId(),
        ]);
 
        \Sentry\configureScope(function (Scope $scope): void {
            $scope->setExtra('user_id', $this->getUserId());
        });
        \Sentry\captureException($this);
    }
 
    public function render($request)
    {
        return response()->json([
            'error' => 'User not found',
            'message' => $this->getMessage(),
            'error_id' => \Sentry\captureException($this),
        ], 404);
    }
 
    private function getUserId()
    {
        return $this->getMessage();
    }
}
 
// No controller
use App\Exceptions\UserNotFoundException;
use Illuminate\Support\Facades\Log;
 
public function show($id)
{
    try {
        $user = $this->userService->getUserById($id);
        Log::info('User retrieved successfully', ['user_id' => $id]);
        return response()->json($user);
    } catch (UserNotFoundException $e) {
        return $e->render($request);
    } catch (\Exception $e) {
        Log::error('Unexpected error occurred', [
            'exception' => $e,
            'user_id' => $id,
        ]);
        return response()->json([
            'error' => 'An unexpected error occurred',
            'message' => $e->getMessage(),
            'error_id' => \Sentry\captureException($e),
        ], 500);
    }
}

Node.js (TypeScript)

// src/exceptions/UserNotFoundException.ts
import * as Sentry from "@sentry/node";
 
export class UserNotFoundException extends Error {
  constructor(userId: string) {
    super(`User with ID ${userId} not found`);
    this.name = 'UserNotFoundException';
  }
 
  report() {
    Sentry.withScope((scope) => {
      scope.setExtra("user_id", this.getUserId());
      Sentry.captureException(this);
    });
  }
 
  private getUserId(): string {
    return this.message.split(' ')[3];
  }
}
 
// src/middlewares/errorHandler.ts
import { Request, Response, NextFunction } from 'express';
import { UserNotFoundException } from '../exceptions/UserNotFoundException';
import * as Sentry from "@sentry/node";
import { newrelic } from '../config/newrelic';
import { logger } from '../utils/logger';
 
export function errorHandler(
  err: Error,
  req: Request,
  res: Response,
  next: NextFunction
) {
  if (err instanceof UserNotFoundException) {
    err.report();
    logger.warn(`UserNotFoundException: ${err.message}`, { userId: err.getUserId() });
    return res.status(404).json({
      error: 'User not found',
      message: err.message,
      error_id: Sentry.captureException(err),
    });
  }
 
  logger.error('Unexpected error', { error: err });
  const eventId = Sentry.captureException(err);
  newrelic.noticeError(err);
 
  res.status(500).json({
    error: 'An unexpected error occurred',
    message: err.message,
    error_id: eventId,
  });
}
 
// src/controllers/UserController.ts
import { Request, Response, NextFunction } from 'express';
import { UserService } from '../services/UserService';
import { logger } from '../utils/logger';
import { newrelic } from '../config/newrelic';
 
export class UserController {
  private userService: UserService;
 
  constructor(userService: UserService) {
    this.userService = userService;
  }
 
  async getUser(req: Request, res: Response, next: NextFunction) {
    const userId = parseInt(req.params.id, 10);
    
    newrelic.addCustomAttribute('userId', userId);
    
    try {
      const user = await this.userService.getUserById(userId);
      logger.info('User retrieved successfully', { userId });
      res.json(user);
    } catch (error) {
      logger.error('Error retrieving user', { userId, error });
      next(error);
    }
  }
}
 
// src/utils/logger.ts
import winston from 'winston';
 
export const logger = winston.createLogger({
  level: 'info',
  format: winston.format.combine(
    winston.format.timestamp(),
    winston.format.json()
  ),
  defaultMeta: { service: 'user-service' },
  transports: [
    new winston.transports.Console(),
    new winston.transports.File({ filename: 'error.log', level: 'error' }),
    new winston.transports.File({ filename: 'combined.log' }),
  ],
});

3.5.3. Observabilidade

Para garantir uma observabilidade completa, implementamos métricas personalizadas, tracing distribuído e logging estruturado.

PHP (Laravel)

// Em um Service Provider
use Illuminate\Support\ServiceProvider;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
 
class ObservabilityServiceProvider extends ServiceProvider
{
    public function boot()
    {
        DB::listen(function($query) {
            Log::info('Database query', [
                'sql' => $query->sql,
                'bindings' => $query->bindings,
                'time' => $query->time,
            ]);
            
            \Sentry\addBreadcrumb(new \Sentry\Breadcrumb(
                \Sentry\Breadcrumb::LEVEL_INFO,
                \Sentry\Breadcrumb::TYPE_DEFAULT,
                'sql.query',
                $query->sql
            ));
 
            newrelic_record_datastore_segment(function() use ($query) {
                // Simula a execução da query para o New Relic
                usleep($query->time * 1000);
            }, [
                'product' => 'mysql',
                'collection' => $query->connectionName,
                'operation' => explode(' ', $query->sql)[0],
            ]);
        });
    }
}

Node.js (TypeScript)

// src/utils/observability.ts
import { newrelic } from '../config/newrelic';
import * as Sentry from "@sentry/node";
import { logger } from './logger';
 
export function trackDatabaseQuery(sql: string, params: any[], duration: number) {
  logger.info('Database query', { sql, params, duration });
 
  Sentry.addBreadcrumb({
    category: 'database',
    message: sql,
    level: Sentry.Severity.Info,
    data: {
      params,
      duration,
    },
  });
 
  newrelic.recordMetric('Custom/Database/query', duration);
}
 
// Uso em um repositório
import { trackDatabaseQuery } from '../utils/observability';
 
class UserRepository {
  async findById(id: number): Promise<User | null> {
    const startTime = process.hrtime();
    
    const result = await db.query('SELECT * FROM users WHERE id = $1', [id]);
    
    const [seconds, nanoseconds] = process.hrtime(startTime);
    const duration = seconds * 1000 + nanoseconds / 1e6;
    
    trackDatabaseQuery('SELECT * FROM users WHERE id = $1', [id], duration);
    
    return result.rows[0] || null;
  }
}

Esta abordagem abrangente para tratamento de erros, logging e observabilidade fornece:

  1. Rastreamento detalhado de erros com Sentry, incluindo contexto adicional.
  2. Monitoramento de desempenho com New Relic, incluindo métricas personalizadas.
  3. Logging estruturado com informações contextuais para facilitar a depuração.
  4. Observabilidade aprimorada com tracing de consultas de banco de dados.
  5. Integração de breadcrumbs do Sentry para uma visão mais completa do fluxo de execução que levou a um erro.

Esta estratégia permite uma resposta rápida a problemas, facilita a análise de causa raiz e fornece insights valiosos sobre o desempenho e a saúde do sistema. Estas práticas de codificação garantem consistência, legibilidade e manutenibilidade em toda a base de código, independentemente da linguagem ou framework utilizado. Elas também facilitam a colaboração entre desenvolvedores e a integração de novos membros à equipe.