|
| 1 | +# Styling protocol |
| 2 | + |
| 3 | + Ana Ernst & Alejandro Morales |
| 4 | + Centre for Crop Systems Analysis - Wageningen University |
| 5 | + |
| 6 | +A styling protocol is essential for code readability, consistency, and maintainability. It ensures that code follows predefined formatting rules, prevents errors, and suggests best practices for overall quality of the code. This type of protocol not only plays a crucial role in code quality but also when it comes to collaboration and long-term software project success. |
| 7 | + |
| 8 | +This styling protocol was based on the [SciML Style Guide for Julia](https://docs.sciml.ai/SciMLStyle/stable/), adapted for the applications of VirtualPlantLab. |
| 9 | + |
| 10 | +## Programming |
| 11 | + |
| 12 | +This section outlines programming guidelines and best practices to ensure the quality, robustness, and maintainability of your codebase. These guidelines are essential for both the clarity of your code and the ease of collaboration with other users or developers. |
| 13 | + |
| 14 | +* Defensive programming for public API only (not internal code) |
| 15 | + Defensive programming is a software development approach that focuses on |
| 16 | + anticipating and handling possible errors and exceptions, as well as improve |
| 17 | + the reliability and robustness of the code. |
| 18 | + *For example, if one knows that a function `f(w0, w)` will have an error unless `w0 < p`, you can throw the function with a domain specific error: `throw(DomainError())`* |
| 19 | +* Throw exceptions rather than `error("string")` |
| 20 | +* Always use recipes and let the user load the Makie backend explicitly (but document this in tutorials) |
| 21 | +* API must be documented using standard Julia docstrings (see templates) |
| 22 | +* When in doubt, a submodule should become a package (see package protocol) |
| 23 | +* Green [`@code_warntype`](https://docs.julialang.org/en/v1/manual/performance-tips/#man-code-warntype) for all functions |
| 24 | + In Julia, 'green' code indicates type-stability, which ensures better performance. |
| 25 | +* Define accessor for all fields and document these rather than the fields themselves |
| 26 | + Accessors provides controlled access to data is important as it allows for future changes to the internal structure without affecting users that rely on accessors. |
| 27 | + *For example, in the [tutorial](https://virtualplantlab.com/stable/tutorials/Tree/Tree/) to build the representation of a 3D binary tree, we can set accessor functions as it follows* |
| 28 | + ````julia |
| 29 | + module TreeTypes |
| 30 | + import VirtualPlantLab |
| 31 | + # ... (previously defined types) |
| 32 | + # Accessor functions for treeparams parameters |
| 33 | + # Accessor for growth parameter |
| 34 | + function get_growth(params::treeparams) |
| 35 | + return params.growth |
| 36 | + end |
| 37 | + # ... (other previously defined functions) |
| 38 | + end |
| 39 | + ```` |
| 40 | + |
| 41 | +## General style |
| 42 | + |
| 43 | +In this section, we outline the general style guidelines to maintain consistency and readability in your code. These guidelines are exemplified using the tree tutorial as a reference. However, if there is an acronym that is domain specific (e.g., LAI, SLA), it should be kept with the format - *Readability is the most important!* |
| 44 | + |
| 45 | +* 4 spaces for indentation |
| 46 | +* 92 character line length limits |
| 47 | +* `CamelCase` for modules, structs and types |
| 48 | + For example: |
| 49 | + - `module TreeTypes ... end` |
| 50 | + - `struct Meristem <: VirtualPlantLab.Node end` |
| 51 | + - `Float64, Abstract64, ...` |
| 52 | +* `snake_case` for functions and variables |
| 53 | + For example: `prob_break(bud)` |
| 54 | +* `SNAKE_CASE` or `SNAKECASE` for constants |
| 55 | + For example: `CONSTANT, CONSTANT_A` |
| 56 | +* No Unicode in public APIs |
| 57 | + As code can interact with terminals without Unicode support, such as R or Python interfaces. |
| 58 | +* `TODO` and `XXX` in comments for to do and broken code |
| 59 | +* Inline comments if within 92 character rule |
| 60 | +* Minimize whitespace, specially around brackets or certain operators. |
| 61 | + Recommended: `check, steps = has_descendant(node, condition = n -> data(n) isa TreeTypes.Meristem)` or `x^2` |
| 62 | + Not recommended: `check , steps = has_descendant ( node, condition = n -> data ( n ) isa TreeTypes.Meristem )` or `x ^ 2` |
| 63 | +* Single empty line between functions and multiline blocks (not if they are one-liners) |
| 64 | +* Function blocks end in `return` |
| 65 | +* Ternary operator if within 92 character rule |
| 66 | + |
| 67 | +**Modules** |
| 68 | +--- |
| 69 | + |
| 70 | +- Module imports at the start of module, before any code (only use `import`) |
| 71 | +- List multiple modules in a single line |
| 72 | + |
| 73 | +**Functions** |
| 74 | +--- |
| 75 | + |
| 76 | +- Short-form only if it fits within 92 characters |
| 77 | +- Prefer to use keyword arguments instead of positional arguments (data as first argument) |
| 78 | +- Use a named tuple for keyword arguments that are passed along to another method, rather than `kwargs...` (unless there is very little chance of ambiguity) |
| 79 | +- Avoid type piracy and ambiguities (use [`Aqua.jl`](https://docs.juliahub.com/General/Aqua/0.5.1/)) |
| 80 | +- Basic rules for mutating functions (`!` and modify first argument) |
| 81 | +- Prefer instances to types as arguments (BUT check with `@code_warntype` that inference is done correctly) |
| 82 | +- Avoid one argument per line of code for functions that have a lot of arguments (an exception is the constructor for a model where each argument is a parameter with a default) |
| 83 | +- Argument precedence: function > IO stream > mutated input > type > others |
| 84 | +- Merge documentation of multiple methods if possible |
| 85 | +- You can list `kwargs...` in the function signature and list the keyword arguments in the `docstring` |
| 86 | + |
| 87 | + |
| 88 | +**VS Code Julia-specific syntax** |
| 89 | +--- |
| 90 | + |
| 91 | +```yaml |
| 92 | +{ |
| 93 | + "[julia]": { |
| 94 | + "editor.detectIndentation": false, |
| 95 | + "editor.insertSpaces": true, |
| 96 | + "editor.tabSize": 4, |
| 97 | + "files.insertFinalNewline": true, |
| 98 | + "files.trimFinalNewlines": true, |
| 99 | + "files.trimTrailingWhitespace": true, |
| 100 | + "editor.rulers": [92], |
| 101 | + "files.eol": "\n" |
| 102 | + }, |
| 103 | +} |
| 104 | +``` |
| 105 | + |
| 106 | +Use JuliaFormatter(link) with `style = "sciml"` (in the future and style for VPL will be formally defined) inside `.JuliaFormatter.toml` (at the root) then |
| 107 | + |
| 108 | +```julia |
| 109 | +using JuliaFormatter, SomePackage |
| 110 | +format(joinpath(dirname(pathof(SomePackage)), "..")) |
| 111 | +``` |
0 commit comments