GuidesBest Practices

Best Practices

Guidelines for writing clean, maintainable JOEL code.

Code Organization

File Structure

# Good: Clear module organization
module calculator

# Public API
fn add(a: i32, b: i32) -> i32 { ... }
fn subtract(a: i32, b: i32) -> i32 { ... }

# Private helpers
private fn validate(x: i32) -> bool { ... }

Naming Conventions

# Variables: snake_case
let user_name = "Alice"
let item_count = 10

# Functions: snake_case
fn calculate_total() { ... }
fn get_user_data() { ... }

# Constants: UPPER_SNAKE_CASE
const MAX_SIZE = 100
const API_URL = "https://api.example.com"

# Types: PascalCase (coming soon)
type User = { name: str, age: i32 }

Error Handling

Use Result Types

# Good: Explicit error handling
fn read_file(path: str) -> Result<str, Error> {
  # Handle errors explicitly
}

# Bad: Silent failures
fn read_file(path: str) -> str {
  # May fail silently
}

Validate Inputs

fn divide(a: f64, b: f64) -> Result<f64, str> {
  if b == 0.0 {
    return Err("Division by zero")
  }
  return Ok(a / b)
}

Performance

Use Compiled Mode for Production

# Development
[Interpreted]

# Production
[Compiled]

Avoid Unnecessary Allocations

# Good: Reuse variables
let result = 0
for i in range(0, 1000) {
  result += i
}

# Bad: Create new objects in loop
for i in range(0, 1000) {
  let result = 0  # Creates new variable each iteration
  result += i
}

Documentation

Document Public APIs

# Calculates the factorial of a number
# 
# Args:
#   n: Non-negative integer
# 
# Returns:
#   Factorial of n
fn factorial(n: i32) -> i32 {
  # Implementation
}

Security

Validate All Inputs

fn process_user_input(input: str) -> Result<str, Error> {
  if input.length() > 1000 {
    return Err("Input too long")
  }
  # Process input
}

Use Type Annotations

# Good: Explicit types
fn add(a: i32, b: i32) -> i32 {
  return a + b
}

# Less clear: Inferred types
fn add(a, b) {
  return a + b
}

Next Steps