Files
JuliaFEM.jl/docs/contributor/coding_standards.md
T

500 lines
11 KiB
Markdown
Raw Normal View History

---
title: "JuliaFEM Coding Standards"
description: "Required coding conventions and style guide for all contributors"
date: 2025-11-09
author: "Jukka Aho"
categories: ["development", "standards", "style guide"]
keywords: ["juliafem", "coding standards", "style", "conventions", "best practices"]
audience: "developers"
level: "required"
type: "standards"
status: "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
1. **Readability over cleverness** - Code should be understandable by FEM practitioners
2. **Type stability first** - Performance depends on it (100x difference)
3. **Zero allocations in hot paths** - Profiling required
4. **Explicit over implicit** - No magic, show what happens
5. **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:**
```julia
# 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:**
```julia
# ❌ 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`, `dw` or `dudx`, `dudy`, etc.
- Jacobian: `J` or `jac` (not ∂)
- Determinant: `detJ` (not |J|)
- Inverse: `invJ` or `Jinv` (not J⁻¹)
---
## Type Naming
### Structs and Types
- **PascalCase** - All type names: `Element`, `Problem`, `Material`
- **No abbreviations** - `Quadrilateral` not `Quad` (unless established convention)
- **Descriptive suffixes** - Purpose clear from name
**Basis types** - Append "Basis" suffix to distinguish from topology:
```julia
# ✅ 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_*` or `has_*`: `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
```julia
# 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
```julia
# ✅ 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
```julia
# ✅ 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
```julia
# ✅ 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)
```julia
# ✅ 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:
`````markdown
"""
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
```julia
# ✅ 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
```julia
@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
```julia
# ✅ 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
```julia
# ❌ 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
```julia
# ❌ 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
```julia
# ❌ 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
```julia
# ❌ 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
```text
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 feature
- `fix`: Bug fix
- `docs`: Documentation only
- `refactor`: Code restructuring (no behavior change)
- `perf`: Performance improvement
- `test`: Adding tests
- `chore`: 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
```julia
# .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
```julia
# .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 `@allocations` or `@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.