mirror of
https://github.com/JuliaFEM/JuliaFEM.jl.git
synced 2026-08-29 07:32:54 +00:00
f8851ef7d6
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.
500 lines
11 KiB
Markdown
500 lines
11 KiB
Markdown
---
|
||
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.
|