WRT Java Style Guide
how we write code on team 2974. read this before writing a single line for a real robot :)
this is not optional!! PRs that don't follow these conventions will get review comments and sent back. it's not personal, it's just how we keep 8+ programmers from losing their minds during build season :D
naming conventions
we use specific prefixes so you can tell what kind of thing a variable is just by looking at its name. no guessing required.
| prefix | what it's for | example |
|---|---|---|
| k | constants (final static values, usually in a Constants.java nested class) | kShooterMotorID, kMaxTranslationSpeed_mps |
| m_ | member/instance variables (fields on a class) | m_drivetrain, m_shooter, m_isRunning |
| trg_ | Trigger objects (WPILib button bindings) | trg_shoot, trg_intakeIn |
| log_ | logging/telemetry objects (AdvantageKit, DataLog) | log_robotPose, log_shooterRPM |
| sig_ | Phoenix 6 StatusSignal objects | sig_velocity, sig_appliedVolts |
public class ShooterSubsystem extends SubsystemBase { // m_ prefix for instance fields private final TalonFX m_shooterMotor; private double m_targetRPS = 0.0; private boolean m_isSpunUp = false; public ShooterSubsystem() { // k prefix for constants from Constants.java m_shooterMotor = new TalonFX(ShooterK.kMotorID); } }
constants file structure
all constants live in Constants.java using nested static classes with the K suffix (short for "constants" -- yes it's a convention, just roll with it lol).
public final class Constants { // one nested class per subsystem public static final class ShooterK { public static final int kMotorID = 11; public static final double kIdleRPS = 0.0; public static final double kShootRPS = 80.0; public static final String kLogTab = "Shooter"; } public static final class DriveK { // include units in the name to avoid conversion bugs!! public static final double kMaxTranslation_mps = 4.5; public static final double kMaxRotation_radps = 9.42; public static final double kWheelRadius_m = 0.0508; } public static final class ControllerK { public static final int kDriverPort = 0; public static final int kOperatorPort = 1; } }
unit suffixes!! always include the unit in the constant name for any physical value. kShootSpeed is ambiguous. kShootRPS (rotations per second) or kMaxTranslation_mps (meters per second) is not. this has literally prevented robot crashes. not joking.
casing rules
motorSpeed, isRunning, getTargetAngle(). first word lowercase, each new word capitalized.ShooterSubsystem, DriveCommand, RobotContainer. every word capitalized.kMotorID, kMaxSpeed_mps. always k lowercase, then PascalCase. not SCREAMING_SNAKE on this team.m_shooter, m_isSpunUp. the underscore is part of the prefix, not the name.we don't use SCREAMING_SNAKE_CASE for constants on this team. i know java tutorials use it. we don't. use k prefix + PascalCase. yes this is different from standard java conventions. no we don't care lol
comments
comments explain why, not what. the code already says what it does. you explain why it's doing that specific thing, especially if it looks weird.
// BAD: just restates the code, adds nothing // set motor speed to 0.5 m_motor.set(0.5); // GOOD: explains why 0.5, what this achieves // ramp slowly to avoid current spike on enable m_motor.set(0.5); // ALSO GOOD: explains a non-obvious workaround // inverted because motor is mounted backwards on Oasis (but not Watergate) m_motor.setInverted(true);
javadoc for public methods
any public method that's non-trivial needs a javadoc. especially anything other subsystems or commands will call.
/** * Sets the shooter target speed and begins spinning up. * * @param targetRPS desired flywheel speed in rotations per second */ public void setTargetRPS(double targetRPS) { m_targetRPS = targetRPS; m_controller.setSetpoint(targetRPS); }
magic numbers = banned :)
a magic number is a raw number in your code with no explanation. they're basically bugs waiting to happen because nobody knows what they mean 6 months later.
// BAD -- what is 11? what is 80? who knows!! m_motor = new TalonFX(11); m_controller.setSetpoint(80.0); // GOOD -- self-documenting, easy to change from one place m_motor = new TalonFX(ShooterK.kMotorID); m_controller.setSetpoint(ShooterK.kShootRPS);
subsystem structure
all subsystems extend SubsystemBase and follow the same section layout. makes it easier to jump between files without getting lost.
package frc.robot.subsystems; import edu.wpi.first.wpilibj2.command.SubsystemBase; import com.ctre.phoenix6.hardware.TalonFXS; import com.ctre.phoenix6.configs.TalonFXSConfiguration; import com.ctre.phoenix6.configs.CurrentLimitsConfigs; import com.ctre.phoenix6.configs.MotorOutputConfigs; import com.ctre.phoenix6.signals.NeutralModeValue; import com.ctre.phoenix6.signals.InvertedValue; public class ExampleSubsystem extends SubsystemBase { // ---- HARDWARE ----------------------------------------------- private final TalonFXS m_motor; // ---- STATE -------------------------------------------------- private double m_targetRPS = 0.0; // ---- CONSTRUCTOR -------------------------------------------- public ExampleSubsystem() { m_motor = new TalonFXS(ExampleK.kMotorID); configureMotor(); } private void configureMotor() { var currentCfg = new CurrentLimitsConfigs() .withStatorCurrentLimit(ExampleK.kCurrentLimit_A) .withSupplyCurrentLimit(15) .withStatorCurrentLimitEnable(true) .withSupplyCurrentLimitEnable(true); var outputCfg = new MotorOutputConfigs() .withInverted(InvertedValue.CounterClockwise_Positive) // flip if needed .withNeutralMode(NeutralModeValue.Coast); m_motor.getConfigurator().apply( new TalonFXSConfiguration() .withCurrentLimits(currentCfg) .withMotorOutput(outputCfg) ); } // ---- PUBLIC API --------------------------------------------- public void setSpeed(double rps) { m_targetRPS = rps; } public boolean isAtTarget() { return Math.abs(m_motor.getVelocity().getValueAsDouble() - m_targetRPS) < ExampleK.kTolerance_rps; } // ---- PERIODIC ----------------------------------------------- @Override public void periodic() { // runs every 20ms. telemetry + closed-loop control goes here m_motor.set(m_targetRPS / ExampleK.kMaxRPS); } }
no while loops in robot code
seriously, no while loops. the robot runs a 20ms control loop managed by WPILib. a blocking while loop freezes that loop and the watchdog kills the robot. use commands, state machines, or periodic methods instead.
// NEVER DO THIS in robot code while (!m_shooter.isAtTarget()) { // blocks the entire robot loop. watchdog WILL fire. robot dies. }
git conventions
check O1 for the full git workflow. these are the non-negotiables:
| rule | why |
|---|---|
never commit directly to main | main = always working. one bad push during competition is a crisis |
branch name: type/short-desc | feature/shooter-pid, fix/intake-stall, refactor/constants-cleanup |
| keep PRs small and focused | one feature per PR. giant PRs are painful to review and silently break things |
| needs at least one approval | one of our mentors (Steve or Banks) reviews before merging to main |
WPILib 2026 notes
WPILib updates every year and things get deprecated. here's what matters for 2026:
not sure what version we're on? check build.gradle in the robot repo. look for the wpilib version string. if you're confused just ask before changing anything :)
heads up — System Core is coming (2027)
for the 2027 season we're switching to System Core, FIRST's new robot controller that replaces the roboRIO. it runs on a faster processor, has more I/O, and uses a new HAL underneath — but WPILib abstracts most of the differences so the code you write now will largely carry over. a few things to know now so you're not caught off guard:
don't stress about this now. other teams are still on roboRIO for 2026, while we will be on systemcore because ~~we're just that goated~~ we're a beta testing team. learn the current stack well first — the concepts transfer directly. System Core is just new hardware underneath the same WPILib you already know.
pre-PR checklist
run through this before opening a pull request. if you can't check all of these, fix it first!!
TalonFXConfiguration applied via getConfigurator().apply() in the constructor. neutral mode, current limits, inversions -- all in there.