- 1 Introduction
- 2 Source File Structure
- 3 Project Organization
- 4 Formatting
- 4.1 Terminology Notes
- 4.2 Braces
- 4.3 Block indentation: one tab
- 4.4 One statement per line
- 4.5 Line Wrapping
- 4.6 Whitespace
- 4.7 Grouping Parentheses: recommended
- 4.8 Specific Constructs
- 5 Naming
- 6 Programming Practices
- 7 Comments and Javadoc
This document will serve as a list of conventions and recommendations for working with WPILib Java and other libraries as a member of Team 102. This guide is heavily based on WPILib's Java Style Guide and our original C++ style guide, with modifications based on our evolving conventions and practices. This is a guide, not a set of laws. If anything is unclear, use your best judgement and then contact the programming lead with the question. They will decide the correct styling and add it to this guide if necessary.
Other teams, you are free to use, distribute, and contribute to this guide under the MIT License.
This document will assume familiarity with the basic terminology of WPILib and the libraries Team 102 uses, including but not limited to, subsystems, commands, AdvantageKit inputs, etc. In this document, unless otherwise noted:
- The term class refers to an "ordinary" class, enum, interface, annotation, record, or other extension of the common Java 'class'
- The term comment always refers to implementation comments. We do not use the phrase "documentation comments", instead using the term "Javadoc."
Other terminology notes will appear throughout the document as necessary
The terms must, can, and should have special meanings in this document
- A must requirement must be followed
- A should requirement is a strong recommendation
- A can requirement is a less strict guideline
The code in this document is non-normative, meaning that styling of example code in parts that aren't about the rule it is exemplifying does not have to be followed. E.g., example code formatting is not taken as a hard rule.
A source file must consist of, in order:
- Package statement
- Import statements
- Exactly one top-level class
Exactly one blank line should separate each section that is present.
- Line wrapping: when one line of code is split into multiple for readability purposes.
Package statements must not be line-wrapped
Import statements must not be line-wrapped, and should be ordered as such (each group itself organized alphabetically):
- Static imports, in one group (note: unlike the WPILibJ guide, we allow and encourage wildcard imports in specific cases as covered in ##.##)
- Non-command local imports (ex:
frc.robot.subsystems.Intake
) - Local command imports (ex:
frc.robot.commands.intake.SetIntakeSpeed
) - WPILib package imports
- Vendor/3rd Party Libraries
- Java imports
Each top-level class must reside in its own source file, named accordingly. There must be no more than one top-level class in each file. (example, the top-level class SwerveModule
resides in SwerveModule.java
)
The ordering of the members of a class can have a great effect on learnability, but there is no single correct recipe for how to do it. Different classes may order their members differently.
What is important is that each class must order its members in some logical order, such its maintainer could explain if asked. For example, new methods are not just habitually added to the end of the class, as that would yield "chronological by date added" ordering, which is not a logical ordering.
If a class has an overloaded constructor or method, they must appear sequentially. If an overloaded constructor or method serves to provide default values, the constructor/method with more parameters (the more specific one) should appear first.
/* more specific constructor */
public SetIntakeSpeed(Intake intake, double speed, boolean isIndexing) {
this.speed = speed;
this.intake = intake;
this.isIndexing = isIndexing;
addRequirements(intake);
}
/* constructor that provides a default speed placed second */
public SetIntakeSpeed(Intake intake, boolean isIndexing) {
this(intake, isIndexing ? IntakeConstants.indexSpeed : IntakeConstants.intakeSpeed, isIndexing);
}
If the constructors/methods are not used for this purpose, order them in the most logical sequence.
All 3 required declarations/initializations for AdvantageKit input logging should be placed sequentially in the following order:
- Inputs class definition (as a static class)
- Inputs object initialization
- Definition of the updateInputs() method (as a non-static method)
@AutoLog
public static class IntakeIOInputs {
public double current_A = 0.0;
public double voltage_V = 0.0;
public double tempature_C = 0.0;
public double percentOutput = 0.0;
public boolean noteSensor = false;
}
public IntakeIOInputsAutoLogged inputs = new IntakeIOInputsAutoLogged();
public void updateInputs(IntakeIOInputs inputs) {
inputs.current_A = motor.getOutputCurrent();
inputs.voltage_V = motor.getBusVoltage();
inputs.tempature_C = motor.getMotorTemperature();
inputs.percentOutput = motor.getAppliedOutput();
inputs.noteSensor = !noteSensor.get();
}
If the inputs are defined within a separate IO file, the object initialization should be omitted and instead the inputs object should be initialized within the subsystem file.
All source code (files with a .java
extension) must be contained within src/main/java/frc/robot
. No source files can be placed any levels up from this folder. Unless otherwise noted, this folder will be treated as the source code root folder for the rest of this section. (Example: A reference to the /subsystems/
folder actually refers to src/main/java/frc/robot/subsystems/
)
Main.java
, Robot.java
, and RobotContainer.java
must be placed directly in this directory, not in subfolders.
Any and all files completely dedicated to commands (a namespace for commands, inherit from a Command class, etc.) must be placed within the /commands/
folder. Commands or methods that return commands which are placed within other files need not be moved into the /commands/
folder, assuming the placement outside of the /commands/
folder is more logical. The rest of section 3.1 will refer to commands defined within the /commands/
folder, in their own file as the top-level class unless otherwise specified.
If a command's main function is related to a single subsystem, such as only moving the arm or only spinning the shooter, it should be in a folder named after the subsystem in question.
/commands/intake/SetIntakeSpeed.java
, /commands/arm/SetArmPosition.java
, /commands/arm/ManualArmControl,java
from 2024
If a command's main function is related to a single purpose/action, and uses multiple subsystems in a way where one is not clearly the most relevant, it should be in a folder that relates to its purpose.
/commands/scoring/SetScoringPosition.java
from 2023 and 2024
Autonomous routines and commands and methods only used in autonomous should be placed either in their own file (/commands/Autos.java
or something similar), or within the folder /commands/auto/
if there is a large number of autonomous-related commands.
When using PathPlanner, named commands and autos should be registered in RobotContainer.java
All files whose top-level class is a subsystem (either extending SubsystemBase
or conceptually analagous to a WPILib Subsystem
) must be placed within the /subsystem/
folder.
Files that are core to a subsystem's functionality but do not either
- Contain the main class of the subsystem; or
- Are not the associated constants file, as covered in section 3.3
Must be placed either in a subfolder of /subsystems/
named appropriately, or in the /io/
folder. This decision should be made based on whatever enhances readability the most.
(NOTE: The /io/
folder must, as named, be exclusively used for interfaces and classes that deal with IO. IO interfaces/classes can be placed in a /subsystems/
subfolder, but non-IO interfaces/classes cannot be placed in /io/
)
subsystems/swerve/*.java
and io/*VisionIO.java
from 2024
Important constants such as motor IDs, PID/FF constants, enums, record definitions, etc. should be defined in a separate file contained within the /constants/
folder. These files should deal with a specific portion of the general code only and be named accordingly, with the format **SUBJECT**Constants.java
, where **SUBJECT** is a single, capitalized noun to describe the file. What the **SUBJECT** is will be described in the following rules.
If constants are related by a common subsystem, they should all be placed in a file named after the subsystem
constants/IntakeConstants.java
, constants/ShooterConstants.java
from 2024
If constants are related by a common topic, they should be placed in a file named after that topic
constants/ScoringConstants.java
from 2024
One file, constants/Constants.java
, should contain constants that are relevant to the entire robot, not a specific subsystem/topic.
Classes, static methods, and more that have utility functionality that are generally relevant to the robot should be placed within the /util/
folder. There is no specific naming convention for the files/classes contained within this folder.
- Braces:
{}
- Brackets:
[]
- Block-like construct: the body of a class, method or constructor.
- Logical units: Multiple lines of code that fit together logically or contextually.
- Horizontal alignment: Adding extra whitespace to lines to make certain tokens appear below other tokens
// No alignment
private int x;
private String str;
// alignment
private int x;
private String str;
Single-line if
, else
, for
, do
, and while
statements can have their braces ommitted if it enhances readability.
Braces follow the K & R style for non-empty blocks and block-like constructs:
- No line break before the opening brace
- Line break after the opening brace
- Line break before the closing brace.
- Line break after the closing brace if that brace terminates a statement or the body of a method, constructor or named class. For example, there is no line break after the brace if it is followed by else or a comma.
public class TestClass {
public static void method(boolean condition) {
if (condition) {
doSomething();
} else {
doSomethingElse();
}
}
}
An empty block or block-like construct may be closed immediately after being opened, with no spaces or line breaks in-between the braces.
public void doNothing() {}
Each time a new block or block-like construct is opened, the indent increases by one tab. When the block ends, the indent decreases by one tab, as seen in the 4.2.2 example.
Every statement must be followed by a line break.
Statements that are very long should be line-wrapped to enhance readability. Each line after the first in a line-wrapped statement should be indented with the standard one tab as described in 4.3.
A single blank line should appear between logical units of code. Spacing out code by logical units helps to improve readability and reduce clutter.
Aside where required by Java, a single space must appear in the following situations only:
- Separating any reserved word, such as
if
,for
orcatch
, from an open parenthesis ((
) that follows it on that line - Separating any reserved word, such as
else
orcatch
, from a closing curly brace (}
) that precedes it on that line - Before any open curly brace (
{}
), with two exceptions:@SomeAnnotation({a, b})
(no space is used)String[][] x = {{"foo"}};
(no space is required between{{
or}}
, by item 8 below)
- On both sides of any binary or ternary operator. This also applies to the following "operator-like" symbols
- the ampersand in a conjunctive type bound:
<T extends Foo & Bar>
- the pipe for a catch block that handles multiple exceptions:
catch (FooException | BarException e)
- the colon (
:
) in an enhancedfor
("foreach") statement
- the ampersand in a conjunctive type bound:
- After
,
,:
, or;
or the closing parenthesis of a typecast ()
) - After the double slash (
//
) that begins a single line or end-of-line comment. - Between the type and variable of a declaration
List<String> list
- Optional inside both braces of an array initializer
new int[] {5, 6}
andnew int[] { 5, 6 }
are both valid
Horizontal alignment is generally discouraged, and may only be used if it substantially increases the readability of the code.
Optional grouping parentheses are omitted only when author and reviewer agree that there is no reasonable chance the code will be misinterpreted without them, nor would they have made the code easier to read. It is not reasonable to assume that every reader has the entire Java operator precedence table memorized.
Since enum classes are classes, all other rules for formatting classes apply, but there are specific rules for enums:
For a given enum class, there can be a line break following the comma after each enum constant, or no line-breaks at all. Do not mix and match in one enum declaration, and no more than one line break per constant.
/* compliant with 4.8.1.1 */
public enum Suit {
Clubs, Hearts, Spades, Diamonds
}
/* also compliant */
public enum Suit {
Clubs,
Hearts,
Spades,
Diamonds
}
/* not compliant */
public enum Suit {
Clubs, Hearts,
Spades,
Diamonds
}
An enum with no methods and no documentation on its constants may be formatted like an array initializer
/* OK */
public enum Suit {
Clubs, Hearts, Spades, Diamonds
}
/* also OK */
public enum Suit { Clubs, Hearts, Spades, Diamonds }
Every variable declaration (field or local) declares only one variable: declarations such as int a, b;
are not used.
Local variables must not be habitually declared at the start of their containing block or block-like construct. Instead, local variables should be declared close to the point they are first used (within reason) to minimize their scope. Local variable declarations should have initializers, or be initialized immediately after declaration.
Arrays must be declared with the same formatting as enums.
/* OK */
new int[] {0, 1, 2, 3};
new int[] {
0, 1, 2, 3
};
new int[] {
0,
1,
2,
3
};
/* not OK */
new int[] {
0, 1,
2, 3
};
Square brackets must be a part of the type, not the variable name. String[] args
, not String args[]
.
The modern switch expression syntax must be used instead of the classic switch statement syntax.
/* Statements: NOT ok */
switch (alliance.get()) {
case Red:
redAllianceFunc();
break;
case Blue:
blueAllianceFunc();
break;
default:
break;
}
/* Expressions: OK */
switch (alliance.get()) {
case Red -> redAllianceFunc();
case Blue -> blueAllianceFunc();
}
The default
case expression group may be omitted if the switch statement covers all possible values of the input (e.g., is exhaustive)
Class and member modifiers, when present, appear in the order recommended by the Java Language Specification
public protected private abstract static final transient volatile synchronized native strictfp
The ternary operator should be used for any conditional statement with simple expressions (simple operations, return value switch, etc.). Ternary operators may not be nested.
camelCase
: Each word has its first letter capitalized. The first word is not capitalized. There are no underscores or other characters between words.PascalCase
: Same as camelCase, but the first word is capitalized.- descriptor: An extra word or abbreviation placed after an underscore after the name. For the purposes of this guide, the name and descriptor are considered separate items within the entire identifier, in the form
<name>_<descriptor>
. Descriptors are all lowercase and limited to a single word or abbreviation.
Identifiers use only ASCII letters and digits, and in specific cases described below, underscores.
Fields, variables, parameters, and other related constructs that have units associated with them must have those units as a descriptor. This is to reduce confusion between units that describe the same physical quantity (feet/s vs m/s, etc.). Use standard abbreviations, and to conform with 5.2, the word 'per' (as in 'feet per second') should be replaced with a p
in the abbreviation ('feet per second' becomes ftps
, not ft/s
).
/* Good examples */
double voltage_V; // Volts
double velocity_mps; // Meters per second
double velocity_rpm; // Rotations per minute
double position_ft; // Feet
/* bad examples */
double voltage; // no unit, even though may be obvious
double voltage_millivolts; // not abbreviated, should be mV
Package names are all lowercase, with separate words concatenated together. com.example.deepspace
, not com.example.deepSpace
Class and interface names must be written in PascalCase
Subsystems, records, enums, data classes, utilities, and most other classes should generally be named with nouns, such as Shooter
, ArmConstants
, ScoringPosition
, etc.
Commands that have a name (e.g., not inline) should generally be named with verbs, such as SetScoringPosition
or IntakeWithArm
, but may be named with other terms if more applicable, such as ManualArmControl
or XStance
.
AdvantageKit inputs classes must be named with the name of their enclosing class, with Inputs
appended at the end, such as ShooterInputs
and FieldVisionInputs
.
(NOTE: the 2023 and 2024 robot code currently does not comply with this rule, with the suffix instead being IOInputs
. Continue to use this non-compliant suffix or update all other inputs class names within these projects to maintain consistency.)
Interfaces that lay out IO implementations or classes that define IO must have their name suffixed with IO
, such as SwerveModuleIO
or FieldVisionIO
.
The implementations of IO Interfaces must have their name be the interface they are implementing suffixed with a descriptor of the implementation. There are no specific guidelines for this descriptor. Examples include SwerveModuleIOReal
, GyroIOPigeon2
, and SwerveModuleIOSim
.
Method names must be written in camelCase, and are generally verbs or verb phrases.
Method identifiers may share names but have descriptors to describe their implementation, such as estimateScoringPosition_math
and estimateScoringPosition_map
.
Since constants reside in their own file, it is relatively obvious when something is a constant or not, and thus they do not require a prefix or other unique characteristic. They should be named just like variables and fields.
Fields, variables, and parameters must be in camelCase, and should generally be nouns or noun phrases.
One character variable names should be avoided, except for very temporary and specific looping variable. If a multi-character name can be used without hurting readability, it should be used.
Each type variable is named in one of two styles:
- A single capital letter, optionally followed by a single numeral (such as
E
,T
,X
,T2
) - A name in the form used for classes (see 5.4.2), followed by the capital letter T (examples:
RequestT
,FooBarT
).
A general rule of thumb is that, whenever an annotation can be used in a way to reduce code clutter, it should be used. Of course, readability is prioritized, and if an annotation will make a piece of code more difficult to read, its use should be discarded.
A method is marked with the @Override
annotation whenever it is legal. This includes a class method overriding a superclass method, a class method implementing an interface method, and an interface method respecifying a superinterface method.
Private member variables with getter and setter methods must have those methods defined with the @Getter
and @Setter
annotations provided by lombok
/*Compliant with 6.1.2*/
@Getter
private double readOnlyValue = 0.0;
/*Non-compliant with 6.1.2*/
private double readOnlyValue = 0.0;
public double getReadOnlyValue() {
return readOnlyValue;
}
Getter and setter methods that transform the data in some way or perform some side-effect are exempt from rule 6.1.2, and should be placed in their own methods for readability.
When a field should be logged as-is as a RealOutput in AdvantageKit, the @AutoLogOutput
annotation should be used rather than Logger.recordOutput()
being called in periodic()
.
public class Example extends SubsystemBase {
/* compliant with 6.1.3 */
@AutoLogOutput
private double loggedOutput = 0.0;
/* not compliant */
private double loggedOutput2 = 0.0;
@Override
public void periodic() {
Logger.recordOutput("Example/loggedOutput2", loggedOutput2);
}
}
Try/catch blocks are only permitted if there is no other way to do something. In all other cases, they should be avoided.
(NOTE: if you are tempted to use a try/catch block, think deeply about whether you should be doing what you're trying to do in the first place. Only after going through this process multiple times should you ever entertain the thought of using one.)
When a reference to a static class member must be qualified, it is qualified with that class's name, not with a reference or expression of that class's type.
Foo aFoo = ...;
Foo.aStaticMethod(); // good
aFoo.aStaticMethod(); // bad
somethingThatYieldsAFoo().aStaticMethod(); // very bad
WPILib uses int
for user-facing interfaces instead of byte
to avoid casts. double
is should be used everywhere instead of float
for consistency unless absolutely necessary.
Since changes would be overwritten upon regeneration, generated files such as BuildConstants.java
and *.auto
files must not be manually edited.
As noted in 3.4, any utility methods, classes, etc. should be placed within an appropriately named file in the /utils/
directory. Creation of utilities is recommended, and any general methods that do not have direct relation with a subsystem or command should be categorized as utilities.
Comments should generally be full line or end-of-line with double slashes (//
).
Whenever the implementation of a method is anything less than incredibly obvious/self-explanatory, or additional notes need to be provided for future reviewers, descriptive comments must be added. Implementation comments should accurately walk through the thought process and program flow in a given implementation, so that a future reviewer can get into the original developer's mindset.
Extraneous comments, jokes, self-deprecation, and more are not permitted to be part of any comments. Implemetation comments and comments that provide helpful notes should be concise and professional, without extraneous language.
At the minimum, Javadoc is present for every public
class, and every public
or protected
member of a class, with a few exceptions noted below.
Other classes and methods still have Javadoc as needed. Whenever an implementation comment would be used to define the overall purpose or behavior of a class, method or field, that comment is written as Javadoc instead. (It's more uniform, and more tool-friendly.)
Javadoc is optional for simple, obvious methods like getters and setters, where there really and truly
is nothing better to say than "Returns the thing".
(NOTE:) It is not appropriate to cite this exception to justify omitting relevant information that a typical reader might need to know. For example, for a method named getCanonicalName
, don't omit its documentation (with the rationale that it would say only /** Returns the canonical name. */
) if a typical reader may have no idea what the term "canonical name" means!
Javadoc does not need to be present on a method that overrides a supertype method.