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_profilesNode.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_profiles3.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_NameNode.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 adequada3.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:
- Rastreamento detalhado de erros com Sentry, incluindo contexto adicional.
- Monitoramento de desempenho com New Relic, incluindo métricas personalizadas.
- Logging estruturado com informações contextuais para facilitar a depuração.
- Observabilidade aprimorada com tracing de consultas de banco de dados.
- 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.