Files
JuliaFEM.jl/docs/contributor/coding_standards.md
T
Jukka Aho f8851ef7d6 docs: Add comprehensive coding standards document
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.
2025-11-09 08:23:52 +02:00

500 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
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.