Code Guidelines
This document outlines recommended coding standards and best practices for writing clean, maintainable Strata code.
Naming Conventions
Functions
Use camelCase for function names:
fn getUser(id: Int): User { }
fn calculateTotal(items: Array<Item>): Float { }
fn isValidEmail(email: String): Bool { }Classes
Use PascalCase for class names:
class UserService { }
class DatabaseConnection { }
class PaymentProcessor { }Variables
Use camelCase for variable names:
let userName: String = "Luna";
let totalCount: Int = 42;
let isActive: Bool = true;Constants
Use UPPER_SNAKE_CASE for constants:
const MAX_SIZE: Int = 1000;
const DEFAULT_TIMEOUT: Int = 30;
const API_BASE_URL: String = "https://api.example.com";Namespaces
Use PascalCase with dots for namespaces:
namespace App.Services;
namespace App.Models;
namespace App.Controllers;Type Annotations
Explicit Types
Prefer explicit types for function parameters and return types:
fn calculateTotal(price: Float, quantity: Int): Float {
return price * quantity;
}Type Inference
Use type inference for local variables when the type is obvious:
// good: type is obvious from the value
let name = "Luna";
let count = 10;
let isActive = true;
// good: explicit type when helpful
let userId: Int = getUser().getId();Avoid Mixed
Avoid using Mixed unless absolutely necessary:
// avoid
fn process(data: Mixed): Void { }
// prefer
fn process(data: String | Int | Bool): Void { }Function Design
Small and Focused
Keep functions small and focused on a single responsibility:
// good: single responsibility
fn validateEmail(email: String): Bool {
return strpos(email, "@") !== false && strpos(email, ".") !== false;
}
// avoid: too many responsibilities
fn processUser(user: User): Void {
// validation, transformation, saving, etc.
}Descriptive Names
Use descriptive function names that clearly indicate their purpose:
// good
fn getUserById(id: Int): User { }
fn calculateShippingCost(weight: Float, distance: Int): Float { }
// avoid
fn get(id: Int): User { }
fn calc(w: Float, d: Int): Float { }Pure Functions
Prefer pure functions when possible (no side effects):
// good: pure function
fn add(first: Int, second: Int): Int {
return first + second;
}
// when side effects are needed, make them explicit
fn saveUser(user: User): Result<Void, String> {
// side effect is clear from the name
}Code Organization
One Per File
Keep one class, interface, trait or type per file when possible:
// UserService.str
namespace App.Services;
class UserService {
// implementation
}Group Related Functionality
Group related functions together in classes or modules:
class MathUtils {
public static fn add(first: Int, second: Int): Int { }
public static fn subtract(first: Int, second: Int): Int { }
public static fn multiply(first: Int, second: Int): Int { }
}Use Namespaces
Use namespaces to organize code logically:
namespace App.Models;
class User { }
namespace App.Services;
class UserService { }
namespace App.Controllers;
class UserController { }Minimal Top-Level Code
Keep the top level for declarations only. Execution logic must reside inside functions (like main()):
// correct: execution logic is inside a function
fn main(): Void {
let user = loadUser(id: 1);
processUser(user: user);
}
// Error: execution logic cannot be at the top level
let user = loadUser(id: 1); // Error: Standalone call at top level
processUser(user: user); // Error: Standalone call at top levelError Handling
Use Result Types
Use Result<T, E> for operations that can fail:
fn loadUser(id: Int): Result<User, String> {
if id <= 0 {
return Err("Invalid user ID");
}
return Ok(User(name: "Donald", email: "donaldpakkies@gmail.com"));
}Handle Errors Explicitly
Always handle errors explicitly; avoid ignoring them:
// good
fn processUser(id: Int): Void {
try {
let result = loadUser(id: id);
let user = result.value;
print(user.getName());
} catch (e) {
print("Error: " . e.getMessage());
}
}
// avoid
fn processUser(id: Int): Void {
let result = loadUser(id: id);
// error ignored
}Meaningful Error Messages
Provide meaningful error messages:
// good
return Err(AppError(message: "User with ID ${id} not found"));
// avoid
return Err(AppError(message: "Error"));Formatting and Style
Strata enforces consistent formatting standards. Configure formatting in your .strata.json file. See the Configuration Guide for details.
Default Formatting Standards
Strata uses these defaults across the ecosystem:
- Indentation Style: spaces
- Indentation Size: 4 spaces
- Line Endings: LF (Unix-style)
- Line Length: 120 characters
Consistent Indentation
Use consistent indentation (4 spaces by default):
fn example(): Void {
if true {
print("Indented consistently");
}
}Blank Lines
Add blank lines between logical sections:
class UserService {
public fn getUser(id: Int): User {
// implementation
}
public fn saveUser(user: User): Void {
// implementation
}
}Line Length
Keep lines to a reasonable length (120 characters by default):
// good
fn createUser(name: String, email: String, age: Int): User {
return User(name: name, email: email, age: age);
}
// avoid: too long
fn createUser(name: String, email: String, age: Int, address: String, phone: String, bio: String): User {
return User(name: name, email: email, age: age, address: address, phone: phone, bio: bio);
}Automatic Formatting
Use the strata fmt command to automatically format your code:
# format all files in current directory
strata fmt
# format a specific file
strata fmt src/index.str
# check formatting without modifying files
strata fmt --checkSee the Configuration Guide for formatting options.
Meaningful Names
Use meaningful variable names; avoid abbreviations:
// good
let userName: String = "Luna";
let totalPrice: Float = 99.99;
// avoid
let u: String = "Luna";
let tp: Float = 99.99;Comments
Document Public APIs
Document public APIs and complex logic:
/**
* Loads a user by their unique identifier.
*
* @param id The user's unique identifier (must be > 0)
* @return Result containing the User or an error object
*/
public fn getUserById(id: Int): Result<User, AppError> {
// implementation
}Self-Documenting Code
Prefer self-documenting code over comments:
// good: code is clear
fn calculateTotalPrice(items: Array<Item>): Float {
let total = 0.0;
for item in items {
total = total + item.getPrice();
}
return total;
}
// avoid: comment explains what code should make obvious
// loop through items and add up prices
let t = 0.0;
for i in items {
t = t + i.p;
}Keep Comments Updated
Keep comments up to date with code changes:
// this function processes payments (updated when payment logic changed)
fn processPayment(amount: Float): Result<Void, String> {
// current implementation
}Imports
Group Imports
Group imports logically (composer imports, then project imports):
// composer imports
import Monolog.Level;
import Monolog.Logger;
import Monolog.Handler.StreamHandler;
// project imports
import App.Models.User;
import App.Services.UserService;Summary
Following these guidelines will help you write clean, maintainable Strata code that is:
- Readable: Clear naming and structure
- Maintainable: Well-organized and documented
- Consistent: Follows established patterns
- Safe: Proper error handling and type usage
Next Steps:
- Language Reference - Learn more about Strata's features
- Examples - See real-world code examples