Best Practices for Production Julia
Prerequisites: Package development and deployment
A checklist of habits that make Julia code fast, correct, and easy to maintain.
Design
- Put code in functions and packages, not in global scripts.
- Design around multiple dispatch: small generic functions, abstract types for families of types, methods for special cases.
- Don't over-type arguments. Use
::Realor::AbstractVectoronly to choose methods or document intent. Over-specific types break composability (units, dual numbers, GPU arrays). - Avoid type piracy: only extend functions for types you own.
- Return
nothingfor "no result" and throw exceptions for real errors.
Performance
- Keep functions type-stable; test critical ones with
@inferred. - Give struct fields concrete or parametric types.
- No untyped globals in hot code — pass arguments or use
const. - Preallocate and use in-place
!functions, views, and fused broadcasts in loops. - Benchmark with BenchmarkTools and profile before optimizing.
Reliability
- One project environment per project; commit
Manifest.tomlfor applications. - Tests in
test/runtests.jl, run in CI on the Julia versions you support (the current release and the LTS). - Seed random number generators in tests and simulations with explicit
Xoshiro(seed)objects. - Use the
Loggingstandard library (@info,@warn,@error,@debug) instead ofprintlnfor diagnostics.
Security
- Never
evalorMeta.parseuntrusted input — it runs arbitrary code. - Build shell commands with backtick syntax (
`ls $dir`) — Julia passes each interpolated value as a separate argument, with no shell injection. - Keep dependencies up to date (
] update) and review what you add.
Putting it together
using Logging, Statistics
"""
summarize(xs::AbstractVector{<:Real}) -> NamedTuple
Return the count, mean, and standard deviation of `xs`,
skipping `NaN` values.
"""
function summarize(xs::AbstractVector{<:Real})
clean = filter(!isnan, xs)
if length(clean) < length(xs)
@warn "skipped NaN values" skipped = length(xs) - length(clean)
end
isempty(clean) && throw(ArgumentError("no valid values"))
return (n = length(clean), mean = mean(clean), std = length(clean) > 1 ? std(clean) : 0.0)
end
logger = ConsoleLogger(stdout) # log to stdout instead of stderr
with_logger(logger) do
println(summarize([1.0, 2.0, 3.0, 4.0]))
println(summarize([2, 4, NaN]))
end
try
summarize(Float64[])
catch err
println(sprint(showerror, err))
end
(n = 4, mean = 2.5, std = 1.2909944487358056)
┌ Warning: skipped NaN values
│ skipped = 1
└ @ Main C:\demo\main.jl:12
(n = 2, mean = 3.0, std = 1.4142135623730951)
ArgumentError: no valid values
A docstring, a generic signature, structured logging, clear errors, and a type-stable named-tuple result — the habits that make Julia code reliable.
What's next
Try the Advanced Projects, then read What You Should Know Now — and What's Next for where to go from here.