Style Guide

Style guide and design principles

Style guide

This section describes the coding style rules that apply to JuMP code and that we recommend for JuMP models and surrounding Julia code. The motivations for a style guide include:

In some cases, the JuMP style guide diverges from the Julia style guide. All such cases will be explicitly noted and justified.

The JuMP style guide adopts many recommendations from the Google style guides.

Info

The style guide is always a work in progress, and not all JuMP code follows the rules. When modifying JuMP, please fix the style violations of the surrounding code (i.e., leave the code tidier than when you started). If large changes are needed, consider separating them into another PR.

Formatting

Julia unfortunately does not have an autoformatting tool like gofmt. Until a reliable autoformatting tool is available, we adopt the following conventions.

Whitespace

For conciseness, never use more than one blank line within a function, and never begin a function with a blank line.

Bad:

function foo(x)
    y = 2 * x


    return y
end

function foo(x)

    y = 2 * x
    return y
end

Julia is mostly insensitive to whitespace characters within lines. For consistency:

Good:

f(x, y) = [3 * dot(x, y); x']

Bad:

f(x,y) = [ 3*dot(x,y) ; x' ]

Good:

module Foo

function f(x)
    return x + 1
end

end # module Foo
Exceptions

For aesthetic reasons, we make an exception for whitespace surrounding the exponential operator ^.

Good:

f(x) = x^2

Bad:

f(x) = x ^ 2

We also make an exception for the : operator when it is used to form a range.

Good:

x = 1:5

Bad:

x = 1 : 5

One reason is that it can be confused with Julia's conditional statement: cond ? x : y which requires whitespace around the :.

We also make an exception for juxtaposed multiplication (i.e. dropping the * between a numeric literal and an expression) when the right-hand side is a symbol.

Good:

2x  # Acceptable if there are space constraints.
2 * x  # This preferred if space is not an issue.
2 * (x + 1)

Bad:

2(x + 1)

Return statements

To avoid situations in which it is unclear whether the author intended to return a certain value or not, always use an explicit return statement to exit from a function. If the return from a function is nothing, use return instead of return nothing.

We make an exception for assignment-form one-line functions (f(x) = 2x).

Good:

foo(x) = 2x  # Acceptable if one line
function foo(x)
    return 2x
end
function foo(x)
    x[1] += 1
    return
end

Bad:

function foo(x)
    2x
end
function foo(x)
    x[1] += 1
    return nothing
end

TODO: Line breaks

Syntax

Julia sometimes provides equivalent syntax to express the same basic operation. We discuss these cases below.

for loops

Julia allows both for x = 1:N and for x in 1:N. Always prefer to use in over =, because in generalizes better to other index sets like for x in eachindex(A).

Empty vectors

For a type T, T[] and Vector{T}() are equivalent ways to create an empty vector with element type T. Prefer T[] because it is more concise.

Trailing periods in floating-point constants

Both 1.0 and 1. create a Float64 with value 1.0. Prefer 1.0 over 1. because it is more easily distinguished from the integer constant 1.

Moreover, as recommended by the Julia style guide, never use 1.0 when 1 is okay.

Comments

For non-native speakers and for general clarity, comments in code must be proper English sentences with appropriate punctuation.

Good:

# This is a comment demonstrating a good comment.

Bad:

# a bad comment

JuMP macro syntax

For consistency, always use parentheses.

Good:

@variable(model, x >= 0)

Bad:

@variable model x >= 0

For consistency, always use constant * variable as opposed to variable * constant. This makes it easier to read models in ambiguous cases like a * x.

Good:

a = 4
@constraint(model, 3 * x <= 1)
@constraint(model, a * x <= 1)

Bad:

a = 4
@constraint(model, x * 3 <= 1)
@constraint(model, x * a <= 1)

In order to reduce boilerplate code, prefer the plural form of macros over lots of repeated calls to singular forms.

Good:

@variables(model, begin
    x >= 0
    y >= 1
    z <= 2
end)

Bad:

@variable(model, x >= 0)
@variable(model, y >= 1)
@variable(model, z <= 2)

An exception is made for calls with many keyword arguments, since these need to be enclosed in parentheses in order to parse properly.

Acceptable:

@variable(model, x >= 0, start = 0.0, base_name = "my_x")
@variable(model, y >= 1, start = 2.0)
@variable(model, z <= 2, start = -1.0)

Also acceptable:

@variables(model, begin
    x >= 0, (start = 0.0, base_name = "my_x")
    y >= 1, (start = 2.0)
    z <= 2, (start = -1.0)
end)

Naming

module SomeModule end
function some_function end
const SOME_CONSTANT = ...
struct SomeStruct end
@enum SomeEnum ENUM_VALUE_A ENUM_VALUE_B
some_local_variable = ...
some_file.jl # Except for ModuleName.jl.

Exported and non-exported names

Begin private module level functions and constants with an underscore. All other objects in the scope of a module should be exported. (See JuMP.jl for an example of how to do this.)

Names beginning with an underscore should only be used for distinguishing between exported (public) and non-exported (private) objects. Therefore, never begin the name of a local variable with an underscore.

module MyModule

export public_function, PUBLIC_CONSTANT

function _private_function()
    local_variable = 1
    return
end

function public_function end

const _PRIVATE_CONSTANT = 3.14159
const PUBLIC_CONSTANT = 1.41421

end

Use of underscores within names

The Julia style guide recommends avoiding underscores "when readable", for example, haskey, isequal, remotecall, and remotecall_fetch. This convention creates the potential for unnecessary bikeshedding and also forces the user to recall the presence/absence of an underscore, e.g., "was that argument named basename or base_name?". For consistency, always use underscores in variable names and function names to separate words.

Use of !

Julia has a convention of appending ! to a function name if the function modifies its arguments. We recommend to:

Note that ! is not a self-documenting feature because it is still ambiguous which arguments are modified when multiple arguments are present. Be sure to document which arguments are modified in the method's docstring.

See also the Julia style guide recommendations for ordering of function arguments.

Abbreviations

Abbreviate names to make the code more readable, not to save typing. Don't arbitrarily delete letters from a word to abbreviate it (e.g., indx). Use abbreviations consistently within a body of code (e.g., do not mix con and constr, idx and indx).

Common abbreviations:

TODO: add more

Miscellaneous

(TODO: Rethink categories.)

User-facing MethodError

Specifying argument types for methods is mostly optional in Julia, which means that it's possible to find out that you are working with unexpected types deep in the call chain. Avoid this situation or handle it with a helpful error message. A user should see a MethodError only for methods that they called directly.

Bad:

internal_function(x::Integer) = x + 1
# The user sees a MethodError for internal_function when calling
# public_function("a string"). This is not very helpful.
public_function(x) = internal_function(x)

Good:

internal_function(x::Integer) = x + 1
# The user sees a MethodError for public_function when calling
# public_function("a string"). This is easy to understand.
public_function(x::Integer) = internal_function(x)

If it is hard to provide an error message at the top of the call chain, then the following pattern is also ok:

internal_function(x::Integer) = x + 1
function internal_function(x)
    error("Internal error. This probably means that you called " *
          "public_function() with the wrong type.")
end
public_function(x) = internal_function(x)

@enum vs. Symbol

The @enum macro lets you define types with a finite number of values that are explicitly enumerated (like enum in C/C++). Symbols are lightweight strings that are used to represent identifiers in Julia (for example, :x).

@enum provides type safety and can have docstrings attached to explain the possible values. Use @enums when applicable, e.g., for reporting statuses. Use strings to provide long-form additional information like error messages.

Use of Symbol should typically be reserved for identifiers, e.g., for lookup in the JuMP model (model[:my_variable]).

using vs. import

using ModuleName brings all symbols exported by the module ModuleName into scope, while import ModuleName brings only the module itself into scope. (See the Julia manual) for examples and more details.

For the same reason that from <module> import * is not recommended in python (PEP 8), avoid using ModuleName except in throw-away scripts or at the REPL. The using statement makes it harder to track where symbols come from and exposes the code to ambiguities when two modules export the same symbol.

Prefer using ModuleName: x, p to import ModuleName.x, ModuleName.p and import MyModule: x, p because the import versions allow method extension without qualifying with the module name.

Documentation

This section describes the writing style that should be used when writing documentation for JuMP (and supporting packages).

We can recommend the documentation style guides by Divio, Google, and Write the Docs as general reading for those writing documentation. This guide delegates a thorough handling of the topic to those guides and instead elaborates on the more points more specific to Julia and documentation that uses Documenter.

Design principles

TODO: How to structure and test large JuMP models, libraries that use JuMP.

For how to write a solver, see MOI.