Julia Coding Style Guide for PowerSystems.jl
Goals
- Define a straightforward set of rules that lead to consistent, readable code.
- Developers focus on producing high quality code, not how to format it.
Base
- Read the official
Julia style guide as reference.
- Read Julia contribution guidelines, notably its line length limit.
- Read Julia guidelines for docstrings.
- Read BlueStyle style guide.
- Consider using a plugin that configures your text editor to use EditorConfig settings.
- Consider using JuliaFormatter.jl.
Code Organization
- Import standard modules, then 3rd-party modules, then yours. Include a blank line between each group.
<!– ### Modules: TODO –>
Comments
- Use comments to describe non-obvious or non-trivial aspects of code. Describe why something was done but not how. The "how" should be apparent from the code itself.
- Use complete sentences and proper grammar.
- Include a space in between the "#" and the first word of the comment.
- Don't use block comments for Julia code. Prefer using the
#
prefix. If you are commenting code, consider deleting it instead.
Bad:
for i in 1:100
#=
arr[i] += a[i] * x^2
arr[i] += b[i] * x
arr[i] += c[i]
=#
nothing
end
Good:
for i in 1:100
# arr[i] += a[i] * x^2
# arr[i] += b[i] * x
# arr[i] += c[i]
nothing
end
- Use these tags in comments to describe known work:
TODO
: tasks that need to be doneFIXME
: code that needs refactoringBUG
: known bug that exists. Should include a bug ID and tracking system.PERF
: known performance limitation that needs improvement
Constructors
- Per guidance from Julia documentation, use inner constructors to enforce restrictions on parameters or to allow construction of self-referential objects. Use outer constructors to provide default values or to perform customization.
- Document the reason why the outer constructor is different.
- Note that the compiler will provide a default constructor with all struct
members if no inner constructor is defined.
- When creating a constructor use
function Foo()
instead ofFoo() = ...
.- One exception is the case where one file has all single-line functions.
- Prefer explicit
return
in multi line functions instead of the implicit return.
Exceptions
- Use exceptions for unexpected errors and not for normal error handling.
- Detection of an unsupported data format from a user should likely throw
- Do not use try/catch to handle retrieving a potentially-missing key from a
- Use @assert statements to guard against programming errors. Do not use them after detecting bad user input. Note that they may be compiled out in release builds.
Globals
- Global constants should be written in upper case and be declared
const
.const UPPER_CASE_VARIABLE = π / 2
- If global variables are needed, prefix them with
g_
. - Don't use magic numbers. Instead, define
const GLOBALS
orEnums
(Julia @enum).
One-line Conditionals
Julia code base uses this idiom frequently: <condition> && <statement>
.
See Example:
function fact(n::Int)
n >= 0 || error("n must be non-negative")
n == 0 && return 1
n * fact(n-1)
end
This is acceptable for simple code as in this example. However, in general, prefer to write out an entire if statement.
Ternary operators provide a way to write clean, concise code. Use good judgement.
Good:
y = x > 0 ? x : -x
There are many examples in our codebase that use the form <cond> ? <statement> : <statement>
. These may be expressed much more clearly in an if/else statement.
Unit Tests
All code should be tested.
Whitespace
- If many function arguments cause the line length to be exceeded, put one
argument per line. In some cases it may make sense to pair some variables on the same line.
Good:
function foo(
var1::String,
var2::String,
var3::String,
var6::T,
) where T <: Number
println("hello world")
end
Bad:
function foo(var1::String,
var2::String,
var3::String,
var6::T) where T <: Number
println("hello world")
end
- Surround equal signs with spaces when passing keyword args to a
function or defining default values in function declarations.
- Prefer elements in an array on separate lines. Follow opening square bracket with a new line and use closing square bracket on a separate new line.
Good:
nodes = [
Node(1),
Node(2),
Node(3),
Node(4),
Node(5),
];
Bad:
nodes = [Node(1), Node(2), Node(3), Node(4), Node(5)];
Prefer a similar rule for Dictionaries, Sets and other data structures. Use your judgement when data structures can neatly fit on a single line.
- Do not right-align equal signs when assigning groups of variables. It causes unnecessary changes whenever someone adds a new variable with a longer name.
Bad:
x = 1
foo = 2
Good:
x = 1
foo = 2
- Define abstract types on one line. Given the lack of IDE support for Julia, this makes it easier to find type definitions.
Bad:
abstract type
Foo
end
Good:
abstract type Foo end
Exports
export
should be used to make it easy for the user to use a symbol from the REPL, an interactive interface or a program.
You may need to use export
when extending functionality of other packages that have also exported the same symbol.
All symbols that have export
must have proper docstrings.