New 500-line standards document covering: - Core principles (readability, type stability, zero allocations, explicit code) - Variable naming: NO Greek letters in code (critical rule - use u,v,w not ξ,η,ζ) - Type naming: PascalCase for types, snake_case for functions, Basis suffix pattern - Performance guidelines: type stability, zero allocations, tuple returns - Documentation style: docstrings with examples, theory, performance notes - Testing standards: test organization, floating point comparisons - Anti-patterns: Dict without types, abstract types in structs, globals, type piracy - Git commit style: Conventional Commits format with examples - Editor configuration: .editorconfig and JuliaFormatter.toml settings - Summary checklist for pre-submission verification Rationale for no Greek letters: keyboard accessibility, editor compatibility, copy-paste issues, search/replace problems, terminal rendering, git diffs, internationalization, and accessibility concerns.
11 KiB
title, description, date, author, categories, keywords, audience, level, type, status
| title | description | date | author | categories | keywords | audience | level | type | status | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| JuliaFEM Coding Standards | Required coding conventions and style guide for all contributors | 2025-11-09 | Jukka Aho |
|
|
developers | required | standards | active |
This document defines the coding standards and conventions for JuliaFEM development.
Last Updated: November 9, 2025
Status: Active - all new code must follow these standards
Core Principles
- Readability over cleverness - Code should be understandable by FEM practitioners
- Type stability first - Performance depends on it (100x difference)
- Zero allocations in hot paths - Profiling required
- Explicit over implicit - No magic, show what happens
- Composition over inheritance - Structs and free functions, not OOP
Variable Naming Conventions
No Greek Letters in Code (Critical!)
Rule: Never use Greek letters (ξ, η, ζ, α, β, γ, etc.) in code.
Rationale:
- Keyboard accessibility - Not all keyboards support Greek input
- Editor compatibility - Some editors struggle with Unicode math symbols
- Copy-paste issues - Greek letters cause encoding problems
- Search/replace problems - Text tools may not handle Unicode correctly
- Terminal rendering - SSH sessions may not display correctly
- Internationalization - Non-Western keyboards make editing difficult
- Git diffs - Unicode can cause merge conflicts or display issues
- Accessibility - Screen readers struggle with Greek letters
Correct:
# Reference element coordinates
function eval_basis(u::Float64, v::Float64, w::Float64)
# Natural coordinates u, v, w ∈ [-1, 1]
N1 = (1 - u) * (1 - v) * (1 - w) / 8
return N1
end
# Physical coordinates
function map_to_physical(x::Vec, y::Vec, z::Vec, u::Float64, v::Float64, w::Float64)
# Map from natural (u,v,w) to physical (x,y,z)
end
Incorrect:
# ❌ DON'T DO THIS
function eval_basis(ξ::Float64, η::Float64, ζ::Float64)
N1 = (1 - ξ) * (1 - η) * (1 - ζ) / 8
return N1
end
Exception: Greek letters are acceptable in:
- Comments - Mathematical notation for clarity:
# Shape function: Nᵢ(ξ) - Documentation - Latex math blocks:
$\\xi \\in [-1, 1]$ - String literals - Plot labels:
xlabel="ξ coordinate" - Error messages - User-facing text:
"Invalid ξ coordinate"
Standard variable names:
- Reference coordinates:
u,v,w(not ξ, η, ζ) - Physical coordinates:
x,y,z - Derivatives:
du,dv,dwordudx,dudy, etc. - Jacobian:
Jorjac(not ∂) - Determinant:
detJ(not |J|) - Inverse:
invJorJinv(not J⁻¹)
Type Naming
Structs and Types
- PascalCase - All type names:
Element,Problem,Material - No abbreviations -
QuadrilateralnotQuad(unless established convention) - Descriptive suffixes - Purpose clear from name
Basis types - Append "Basis" suffix to distinguish from topology:
# ✅ Correct - No name collision
struct Tri3Basis <: AbstractBasis{2} end # Interpolation scheme
struct Tri3 <: AbstractTopology end # Element geometry
# ❌ Wrong - Name collision!
struct Tri3 <: AbstractBasis{2} end
struct Tri3 <: AbstractTopology end # ERROR: type Tri3 already defined
Functions
- snake_case - All function names:
assemble_element,solve_static - Verb-noun pattern - Action clear:
compute_stiffness,evaluate_basis - Boolean predicates -
is_*orhas_*:is_converged,has_contact
Constants
- UPPER_CASE - Module-level constants:
MAX_ITERATIONS,TOLERANCE - Type-stable - Always specify type:
const MAX_ITER::Int = 100
Internal/Private
- Underscore prefix - Not exported:
_compute_internal_forces - Not API - Can change between versions
Code Organization
File Structure
# Standard file header
# This file is a part of JuliaFEM.
# License is MIT: see https://github.com/JuliaFEM/JuliaFEM.jl/blob/master/LICENSE
"""
Brief one-line description of file purpose.
Extended description if needed.
"""
# Imports (grouped)
using LinearAlgebra
using SparseArrays
# Local imports
using ..JuliaFEM: AbstractElement, AbstractBasis
# Type definitions
struct MyType
# ...
end
# Function implementations
function my_function(args)
# ...
end
Import Style
# ✅ Correct - Explicit imports
using LinearAlgebra: norm, dot, cross
using SparseArrays: sparse, spzeros
# ❌ Avoid - Blanket imports (pollutes namespace)
using LinearAlgebra
using SparseArrays
Performance Guidelines
Type Stability
# ✅ Type-stable - Return type inferrable
function compute_mass(element::Quad4, density::Float64)
m::Float64 = 0.0
# ...
return m
end
# ❌ Type-unstable - Return type changes!
function compute_mass(element, density)
if density > 0
return 1.0 # Float64
else
return nothing # Nothing - type-unstable!
end
end
Zero Allocations
# ✅ Pre-allocated cache
struct AssemblyCache
K_local::Matrix{Float64}
f_local::Vector{Float64}
end
function assemble!(cache::AssemblyCache, element)
fill!(cache.K_local, 0.0)
# Reuse cache.K_local - no allocations
end
# ❌ Allocates every call
function assemble(element)
K_local = zeros(8, 8) # Allocates!
return K_local
end
Tuple Returns (Zero Allocation)
# ✅ Tuple return - no allocation
function eval_basis(element::Tri3, u::Float64, v::Float64)
N1 = 1 - u - v
N2 = u
N3 = v
return (N1, N2, N3) # NTuple{3,Float64} - stack allocated
end
# ❌ Vector return - allocates!
function eval_basis(element::Tri3, u::Float64, v::Float64)
return [1 - u - v, u, v] # Vector{Float64} - heap allocation
end
Documentation Style
Docstrings
Use Julia's docstring format with standard sections:
"""
assemble_element(element::Quad4, u::Vector{Float64}) -> Matrix{Float64}
Assemble element stiffness matrix for 4-node quadrilateral element.
# Arguments
- `element::Quad4`: Quadrilateral element with nodal connectivity
- `u::Vector{Float64}`: Nodal displacement vector (8 DOFs: u1,v1,u2,v2,...)
# Returns
- `K::Matrix{Float64}`: 8×8 element stiffness matrix in global DOFs
# Theory
Uses 2×2 Gauss quadrature with bilinear shape functions:
```math
K_{ij} = \\int_{\\Omega_e} B_i^T D B_j \\, d\\Omega
```
# Example
```julia
nodes = [Node(0,0), Node(1,0), Node(1,1), Node(0,1)]
element = Quad4(nodes)
u = zeros(8)
K = assemble_element(element, u)
```
# Performance
This function allocates. For zero-allocation assembly, use `assemble_element!`
with pre-allocated cache.
"""
function assemble_element(element::Quad4, u::Vector{Float64})
# Implementation
end
Comments
# ✅ Good comments - Why, not what
# Use RCM ordering to minimize bandwidth (10x faster solve)
perm = rcm_permutation(mesh)
# Check convergence: ||Δu|| < ε||u||
# Relative norm prevents scale-dependent tolerance
if norm(Δu) < tol * norm(u)
break
end
# ❌ Bad comments - Obvious from code
# Loop over elements
for element in elements
# Add to global matrix
K_global += K_local
end
Testing Standards
Test Organization
@testset "Quad4 element" begin
@testset "Stiffness matrix" begin
# Unit square, E=1, ν=0.3
element = Quad4([Node(0,0), Node(1,0), Node(1,1), Node(0,1)])
K = stiffness_matrix(element, E=1.0, ν=0.3)
# Symmetry
@test issymmetric(K)
# Positive definite (after BC)
@test all(eigvals(K[3:end, 3:end]) .> 0)
end
@testset "Patch test" begin
# Linear displacement field must be exact
# ... validation test ...
end
end
Floating Point Comparisons
# ✅ Use tolerances
@test result ≈ expected atol=1e-10
@test isapprox(result, expected, rtol=1e-6)
# ❌ Never exact equality for floats
@test result == expected # Fragile!
Anti-Patterns (Don't Do This!)
1. Dict Without Type Parameters
# ❌ Type-unstable Dict (100x slower!)
fields = Dict("displacement" => u, "velocity" => v)
# ✅ Type-stable alternative
fields = (displacement=u, velocity=v) # NamedTuple
# or
struct Fields
displacement::Vector{Float64}
velocity::Vector{Float64}
end
2. Abstract Types in Structs
# ❌ Type-unstable struct
struct Element
nodes::AbstractVector # Type-unstable!
end
# ✅ Parametric struct
struct Element{N}
nodes::NTuple{N, Node} # Type-stable, zero-allocation
end
3. Global Variables
# ❌ Global mutable state
global_stiffness = zeros(1000, 1000)
function assemble!(element)
global_stiffness .+= K_local # Spooky action at a distance!
end
# ✅ Explicit parameters
function assemble!(K_global::Matrix, element)
K_global .+= K_local # Clear data flow
end
4. Type Piracy
# ❌ Extending methods on types you don't own
Base.+(a::Vector, b::Matrix) = ... # DON'T!
# ✅ Wrapper type or different function
struct MyVector
data::Vector
end
Base.+(a::MyVector, b::Matrix) = ... # OK - our type
Git Commit Style
Commit Messages
feat(topology): Add Pyr5 pyramid element topology
- Implement 5-node pyramid reference element
- Add connectivity information (faces, edges)
- Zero-allocation tuple interface
- Tests: reference coordinates, edge/face queries
Closes #123
Format: <type>(<scope>): <subject>
Types:
feat: New featurefix: Bug fixdocs: Documentation onlyrefactor: Code restructuring (no behavior change)perf: Performance improvementtest: Adding testschore: Tooling, dependencies
Scope: Module or component (topology, assembly, solver, etc.)
Subject:
- Imperative mood: "Add feature" not "Added feature"
- No period at end
- Max 72 characters
Editor Configuration
Recommended Settings
# .editorconfig
[*.jl]
charset = utf-8
end_of_line = lf
indent_style = space
indent_size = 4
trim_trailing_whitespace = true
insert_final_newline = true
JuliaFormatter.jl
# .JuliaFormatter.toml
indent = 4
margin = 92
always_for_in = true
whitespace_typedefs = true
whitespace_ops_in_indices = true
remove_extra_newlines = true
Summary Checklist
Before submitting code, verify:
- No Greek letters in variable names (use u, v, w)
- All types are PascalCase
- All functions are snake_case
- Type-stable (check with
@code_warntype) - Zero allocations in hot paths (check with
@allocationsor@btime) - Docstrings for exported functions
- Tests pass locally
- Comments explain "why" not "what"
- No type piracy
- No global mutable state
References
- Performance tips: https://docs.julialang.org/en/v1/manual/performance-tips/
- Style guide: https://docs.julialang.org/en/v1/manual/style-guide/
- JuliaFEM vision:
llm/VISION_2.0.md - Architecture:
llm/ARCHITECTURE.md - Technical lessons:
llm/TECHNICAL_VISION.md
Enforcement: These standards are enforced through code review. All PRs must follow these conventions.
Evolution: This document evolves with the project. Propose changes via pull request.