Pular para conteúdo

Coding Standards

Padrões de código para Python (Backend) e JavaScript/React (Frontend).

Geral

Princípios

  • KISS: Keep It Simple, Stupid
  • DRY: Don't Repeat Yourself
  • YAGNI: You Aren't Gonna Need It
  • Legibilidade > Cleverness
  • Consistência > Preferência pessoal

Idioma

  • Código: Inglês (variáveis, funções, classes, comentários)
  • Documentação: Português para docs internas, Inglês para docs de API
  • Commits: Inglês

Python (Backend)

Style Guide

Seguimos PEP 8 com algumas customizações.

Formatter: Black
Linter: Ruff

# Formatar código
black .

# Verificar estilo
ruff check .

# Auto-fix quando possível
ruff check --fix .

Naming Conventions

# Variáveis e funções: snake_case
user_name = "John"
def calculate_total(): ...

# Classes: PascalCase
class UserService: ...

# Constantes: UPPER_SNAKE_CASE
MAX_RETRIES = 3
API_TIMEOUT = 30

# Private: prefixo com _
def _internal_helper(): ...
_cache = {}

# Arquivos: snake_case
# user_service.py, api_client.py

Docstrings

Usamos Google Style:

def calculate_discount(price: float, discount_rate: float = 0.1) -> float:
    """Calcula preço com desconto aplicado.

    Args:
        price: Preço original do produto
        discount_rate: Taxa de desconto (0.0 a 1.0). Default 0.1 (10%)

    Returns:
        Preço final com desconto aplicado

    Raises:
        ValueError: Se price for negativo ou discount_rate fora do range

    Examples:
        >>> calculate_discount(100.0, 0.2)
        80.0
        >>> calculate_discount(50.0)
        45.0
    """
    if price < 0:
        raise ValueError("Price cannot be negative")
    if not 0 <= discount_rate <= 1:
        raise ValueError("Discount rate must be between 0 and 1")

    return price * (1 - discount_rate)

Type Hints

Sempre use type hints:

from typing import Optional, List, Dict, Union

# Funções
def get_user(user_id: int) -> Optional[User]:
    ...

# Variáveis (quando não óbvio)
users: List[User] = []
config: Dict[str, Any] = {}

# Generics
from typing import Generic, TypeVar

T = TypeVar('T')

class Repository(Generic[T]):
    def get(self, id: int) -> Optional[T]:
        ...

Estrutura de Arquivos

"""Module docstring explaining purpose.

This module handles user authentication and authorization.
"""

# 1. Standard library imports
import os
import sys
from datetime import datetime

# 2. Third-party imports
from fastapi import APIRouter, Depends
from sqlalchemy import select

# 3. Local application imports
from app.models import User
from app.services import AuthService

# 4. Constants
MAX_LOGIN_ATTEMPTS = 5
SESSION_TIMEOUT = 3600

# 5. Functions/Classes
class UserController:
    ...

FastAPI Específico

from fastapi import APIRouter, HTTPException, status
from pydantic import BaseModel, Field

# Pydantic models com exemplos
class CreateUserRequest(BaseModel):
    """Request model for creating user."""

    email: str = Field(..., example="user@example.com")
    name: str = Field(..., min_length=2, max_length=100)
    age: Optional[int] = Field(None, ge=0, le=150)

    class Config:
        json_schema_extra = {
            "example": {
                "email": "john@example.com",
                "name": "John Doe",
                "age": 30
            }
        }

# Endpoints documentados
@router.post("/users", response_model=UserResponse, status_code=status.HTTP_201_CREATED)
async def create_user(
    request: CreateUserRequest,
    service: UserService = Depends(get_user_service)
) -> UserResponse:
    """Create a new user.

    Args:
        request: User creation data
        service: Injected user service

    Returns:
        Created user data

    Raises:
        HTTPException 400: If email already exists
        HTTPException 422: If validation fails
    """
    try:
        user = await service.create_user(request)
        return UserResponse.from_orm(user)
    except EmailExistsError:
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail="Email already registered"
        )

Async/Await

# Prefer async quando possível
async def fetch_user(user_id: int) -> User:
    async with get_session() as session:
        result = await session.execute(
            select(User).where(User.id == user_id)
        )
        return result.scalar_one_or_none()

# Use asyncio.gather para paralelizar
users, products = await asyncio.gather(
    fetch_users(),
    fetch_products()
)

Error Handling

# Use exceções específicas
class UserNotFoundError(Exception):
    """Raised when user is not found."""
    pass

# Catch específico
try:
    user = await service.get_user(user_id)
except UserNotFoundError:
    raise HTTPException(status_code=404, detail="User not found")
except DatabaseError as e:
    logger.error(f"Database error: {e}")
    raise HTTPException(status_code=500, detail="Internal server error")

Logging

import logging

logger = logging.getLogger(__name__)

# Levels: DEBUG, INFO, WARNING, ERROR, CRITICAL

logger.info("User logged in", extra={"user_id": user.id})
logger.warning("Invalid login attempt", extra={"email": email})
logger.error("Database connection failed", exc_info=True)

# Structured logging (JSON)
logger.info(
    "Payment processed",
    extra={
        "user_id": user.id,
        "amount": amount,
        "transaction_id": tx_id
    }
)

JavaScript/TypeScript (Frontend)

Style Guide

Seguimos Airbnb Style Guide com TypeScript.

Formatter: Prettier
Linter: ESLint

# Formatar código
npm run format

# Verificar estilo
npm run lint

# Auto-fix
npm run lint:fix

Naming Conventions

// Variáveis e funções: camelCase
const userName = "John";
function calculateTotal() {}

// Classes e Componentes: PascalCase
class UserService {}
function UserProfile() {}

// Constantes: UPPER_SNAKE_CASE
const MAX_RETRIES = 3;
const API_BASE_URL = "https://api.example.com";

// Private: prefixo com #
class Example {
  #privateField = 0;
}

// Arquivos
// - Componentes: PascalCase (UserProfile.tsx)
// - Utils/Services: camelCase (apiClient.ts)
// - Hooks: camelCase com use prefix (useAuth.ts)

React Components

import React, { useState, useEffect } from 'react';

interface UserProfileProps {
  userId: number;
  onUpdate?: (user: User) => void;
}

/**
 * Displays user profile information.
 * 
 * @param userId - The ID of the user to display
 * @param onUpdate - Optional callback when user is updated
 */
export function UserProfile({ userId, onUpdate }: UserProfileProps) {
  const [user, setUser] = useState<User | null>(null);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    fetchUser(userId)
      .then(setUser)
      .finally(() => setLoading(false));
  }, [userId]);

  if (loading) return <Loader />;
  if (!user) return <NotFound />;

  return (
    <div className="user-profile">
      <h1>{user.name}</h1>
      <p>{user.email}</p>
    </div>
  );
}

Hooks

import { useState, useEffect, useCallback } from 'react';

/**
 * Custom hook for fetching and managing user data.
 * 
 * @param userId - The ID of the user to fetch
 * @returns User data, loading state, and refresh function
 */
export function useUser(userId: number) {
  const [user, setUser] = useState<User | null>(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState<Error | null>(null);

  const fetchUser = useCallback(async () => {
    try {
      setLoading(true);
      const data = await apiClient.getUser(userId);
      setUser(data);
    } catch (err) {
      setError(err as Error);
    } finally {
      setLoading(false);
    }
  }, [userId]);

  useEffect(() => {
    fetchUser();
  }, [fetchUser]);

  return { user, loading, error, refresh: fetchUser };
}

TypeScript

Sempre use tipos explícitos:

// Interfaces para objetos
interface User {
  id: number;
  name: string;
  email: string;
  createdAt: Date;
}

// Types para unions/intersections
type Status = 'pending' | 'approved' | 'rejected';
type UserWithRole = User & { role: string };

// Generics
function getValue<T>(key: string): T | null {
  const value = localStorage.getItem(key);
  return value ? JSON.parse(value) as T : null;
}

Error Handling

// Try-catch para async
async function loadData() {
  try {
    const data = await apiClient.getData();
    return data;
  } catch (error) {
    if (error instanceof ApiError) {
      toast.error(error.message);
    } else {
      logger.error('Unexpected error', error);
      toast.error('Something went wrong');
    }
    return null;
  }
}

// Error boundaries para React
class ErrorBoundary extends React.Component<Props, State> {
  state = { hasError: false };

  static getDerivedStateFromError(error: Error) {
    return { hasError: true };
  }

  componentDidCatch(error: Error, info: React.ErrorInfo) {
    logger.error('React error boundary', { error, info });
  }

  render() {
    if (this.state.hasError) {
      return <ErrorFallback />;
    }
    return this.props.children;
  }
}

Testes

Naming

# Python (pytest)
def test_user_creation_with_valid_data():
    """Test that user is created successfully with valid input."""
    ...

def test_user_creation_fails_with_invalid_email():
    """Test that user creation raises error with invalid email."""
    ...

# JavaScript (Jest)
describe('UserService', () => {
  describe('createUser', () => {
    it('creates user with valid data', () => {
      ...
    });

    it('throws error with invalid email', () => {
      ...
    });
  });
});

Estrutura

# Arrange-Act-Assert (AAA)
def test_calculate_discount():
    # Arrange
    price = 100.0
    discount_rate = 0.2

    # Act
    result = calculate_discount(price, discount_rate)

    # Assert
    assert result == 80.0

Comentários

Quando Comentar

Comentar: - Por quê, não o quê - Decisões não óbvias - Workarounds temporários (com TODO/FIXME) - Lógica complexa que não pode ser simplificada - Avisos importantes

Não Comentar: - Código autoexplicativo - Repetir o que o código já diz - Código comentado (delete!)

# ❌ Ruim
# Incrementa i
i += 1

# ✅ Bom
# Skip processing for admin users as they have different workflow
if user.is_admin:
    continue

# TODO(user): Refatorar para usar queue quando disponível (#123)
process_sync(data)  # Temporário até migration completa

Security

Nunca Commitar

  • ❌ Senhas ou API keys
  • ❌ Tokens de acesso
  • ❌ Certificados privados
  • ❌ Dados de produção
  • .env files (use .env.example)

Sempre

  • ✅ Validar input do usuário
  • ✅ Sanitizar output
  • ✅ Usar prepared statements (SQL injection)
  • ✅ Escapar HTML (XSS)
  • ✅ Verificar autenticação/autorização
  • ✅ Rate limiting em APIs públicas

Pre-commit Hooks

Instalamos hooks que rodam antes de commit:

# Instalar pre-commit
pip install pre-commit
pre-commit install

# Hooks configurados:
# - black (formatter Python)
# - ruff (linter Python)
# - prettier (formatter JS/TS)
# - eslint (linter JS/TS)
# - check-yaml
# - check-json
# - detect-secrets

Referências

Python: - PEP 8 - Google Python Style Guide - FastAPI Best Practices

JavaScript/TypeScript: - Airbnb Style Guide - React TypeScript Cheatsheet - Clean Code JavaScript

Geral: - Clean Code by Robert C. Martin