|
| 1 | +# Java DOP Coding Standards #################### |
| 2 | + |
| 3 | +This file is a Gen AI summary of CODING_STYLE.md to use less tokens of context window. Read the original file for full details. |
| 4 | + |
| 5 | +IMPORTANT: We do TDD so all code must include targeted unit tests. |
| 6 | +IMPORTANT: Never disable tests written for logic that we are yet to write we do Red-Green-Refactor coding. |
| 7 | + |
| 8 | +## Core Principles |
| 9 | + |
| 10 | +* Use Records for all data structures. Use sealed interfaces for protocols. |
| 11 | +* Prefer static methods with Records as parameters |
| 12 | +* Default to package-private scope |
| 13 | +* Package-by-feature, not package-by-layer |
| 14 | +* Create fewer, cohesive, wide packages (functionality modules or records as protocols) |
| 15 | +* Use public only when cross-package access is required |
| 16 | +* Use JEP 467 Markdown documentation examples: `/// good markdown` not legacy `/** bad html */` |
| 17 | +* Apply Data-Oriented Programming principles and avoid OOP |
| 18 | +* Use Stream operations instead of traditional loops. Never use `for(;;)` with mutable loop variables use |
| 19 | + `Arrays.setAll` |
| 20 | +* Prefer exhaustive destructuring switch expressions over if-else statements |
| 21 | +* Use destructuring switch expressions that operate on Records and sealed interfaces |
| 22 | +* Use anonymous variables in record destructuring and switch expressions |
| 23 | +* Use `final var` for local variables, parameters, and destructured fields |
| 24 | +* Apply JEP 371 "Local Classes and Interfaces" for cohesive files with narrow APIs |
| 25 | + |
| 26 | +## Data-Oriented Programming |
| 27 | + |
| 28 | +* Separate data (immutable Records) from behavior (never utility classes always static methods) |
| 29 | +* Use immutable generic data structures (maps, lists, sets) and take defense copies in constructors |
| 30 | +* Write pure functions that don't modify state |
| 31 | +* Leverage Java 21+ features: |
| 32 | + * Records for immutable data |
| 33 | + * Pattern matching for structural decomposition |
| 34 | + * Sealed classes for exhaustive switches |
| 35 | + * Virtual threads for concurrent processing |
| 36 | + |
| 37 | +## Package Structure |
| 38 | + |
| 39 | +* Use default (package-private) access as the standard. Do not use 'private' or 'public' by default. |
| 40 | +* Limit public to genuine cross-package APIs |
| 41 | +* Prefer package-private static methods. Do not use 'private' or 'public' by default. |
| 42 | +* Limit private to security-related code |
| 43 | +* Avoid anti-patterns: boilerplate OOP, excessive layering, dependency injection overuse |
| 44 | + |
| 45 | +## Constants and Magic Numbers |
| 46 | + |
| 47 | +* **NEVER use magic numbers** - always use enum constants |
| 48 | +* **NEVER write large if-else-if statements over known types** - will not be exhaustive and creates bugs when new types are added. Use exhaustive switch statements over bounded sets such as enum values or sealed interface permits |
| 49 | + |
| 50 | +## Functional Style |
| 51 | + |
| 52 | +* Combine Records + static methods for functional programming |
| 53 | +* Emphasize immutability and explicit state transformations |
| 54 | +* Reduce package count to improve testability |
| 55 | +* Implement Algebraic Data Types pattern with Function Modules |
| 56 | +* Modern Stream Programming |
| 57 | +* Use Stream API instead of traditional loops |
| 58 | +* Write declarative rather than imperative code |
| 59 | +* Chain operations without intermediate variables |
| 60 | +* Support immutability throughout processing |
| 61 | +* Example: `IntStream.range(0, 100).filter(i -> i % 2 == 0).sum()` instead of counting loops |
| 62 | +* Always use final variables in functional style. |
| 63 | +* Prefer `final var` with self documenting names over `int i` or `String s` but its not possible to do that on a `final` variable that is not yet initialized so its a weak preference not a strong one. |
| 64 | +* Avoid just adding new functionality to the top of a method to make an early return. It is fine to have a simple guard statement. Yet general you should pattern match over the input to do different things with the same method. Adding special case logic is a code smell that should be avoided. |
| 65 | + |
| 66 | +## Documentation using JEP 467 Markdown documentation |
| 67 | + |
| 68 | +IMPORTANT: You must not write JavaDoc comments that start with `/**` and end with `*/` |
| 69 | +IMPORTANT: You must "JEP 467: Markdown Documentation Comments" that start all lines with `///` |
| 70 | + |
| 71 | +Here is an example of the correct format for documentation comments: |
| 72 | + |
| 73 | +```java |
| 74 | +/// Returns a hash code value for the object. This method is |
| 75 | +/// supported for the benefit of hash tables such as those provided by |
| 76 | +/// [java.util.HashMap]. |
| 77 | +/// |
| 78 | +/// The general contract of `hashCode` is: |
| 79 | +/// |
| 80 | +/// - Whenever it is invoked on the same object more than once during |
| 81 | +/// an execution of a Java application, the `hashCode` method |
| 82 | +/// - If two objects are equal according to the |
| 83 | +/// [equals][#equals(Object)] method, then calling the |
| 84 | +/// - It is _not_ required that if two objects are unequal |
| 85 | +/// according to the [equals][#equals(Object)] method, then |
| 86 | +/// |
| 87 | +/// @return a hash code value for this object. |
| 88 | +/// @see java.lang.Object#equals(java.lang.Object) |
| 89 | +``` |
| 90 | + |
| 91 | +## Logging |
| 92 | + |
| 93 | +- Use Java's built-in logging: `java.util.logging.Logger` |
| 94 | +- Log levels: Use appropriate levels (FINE, FINER, INFO, WARNING, SEVERE) |
| 95 | + - **FINE**: Production-level debugging, default for most debug output |
| 96 | + - **FINER**: Verbose debugging, detailed internal flow, class resolution details |
| 97 | + - **INFO**: Important runtime information |
| 98 | +- LOGGER is a static field: `static final Logger LOGGER = Logger.getLogger(ClassName.class.getName());` where use the primary interface or the package as the logger name with the logger package-private and shared across the classes when the package is small enough. |
| 99 | +- Use lambda logging for performance: `LOGGER.fine(() -> "message " + variable);` |
| 100 | + |
| 101 | +# Compile, Test, Debug Loop |
| 102 | + |
| 103 | +- **Check Compiles**: Focusing on the correct mvn module run without verbose logging and do not grep the output to see compile errors: |
| 104 | + ```bash |
| 105 | + ./mvn-test-no-boilerplate.sh -pl json-java21-api-tracker -Djava.util.logging.ConsoleHandler.level=SEVERE |
| 106 | + ``` |
| 107 | +- **Debug with Verbose Logs**: Use `-Dtest=` to focus on just one or two test methods, or one class, using more logging to debug the code: |
| 108 | + ```bash |
| 109 | + ./mvn-test-no-boilerplate.sh -pl json-java21-api-tracker -Dtest=XXX -Djava.util.logging.ConsoleHandler.level=FINER |
| 110 | + ``` |
| 111 | +- **No Grep Filtering**: Use logging levels to filter output, do not grep the output for compile errors, just run less test methods with the correct logging to reduce the output to a manageable size. Filtering hides problems and needs more test excution to find the same problems which wastes time. |
| 112 | + |
| 113 | +## Modern Java Singleton Pattern: Sealed Interfaces |
| 114 | + |
| 115 | +**Singleton Object Anti-Pattern**: Traditional singleton classes with private constructors and static instances are legacy should be avoided. With a functional style we can create a "package-private companion module" of small package-private methods with `sealed interfacee GoodSingletonModule permits Nothing { enum Nothing extends GoodSingletonModule{}; /* static functional methods here */ }`. |
| 116 | + |
| 117 | +### Assertions and Input Validation |
| 118 | + |
| 119 | +1. On the public API entry points use `Objects.assertNonNull()` to ensure that the inputs are legal. |
| 120 | +2. After that on internal method that should be passed only valid data use `assert` to ensure that the data is valid. |
| 121 | + - e.g. use `assert x==y: "unexpected x="+x+" y="+y;` as `mvn` base should be run with `-ea` to enable assertions. |
| 122 | +3. Often there is an `orElseThrow()` which can be used so the only reason to use `assert` is to add more logging to the error message. |
| 123 | +4. Consider using the validations of `Object` and `Arrays` and the like to ensure that the data is valid. |
| 124 | + - e.g. `Objects.requireNonNull(type, "type must not be null")` or `Arrays.checkIndex(index, array.length)`. |
| 125 | + |
| 126 | +## JEP References |
| 127 | + |
| 128 | +[JEP 467](https://openjdk.org/jeps/467): Markdown Documentation in JavaDoc |
| 129 | +[JEP 371](https://openjdk.org/jeps/371): Local Classes and Interfaces |
| 130 | +[JEP 395](https://openjdk.org/jeps/395): Records |
| 131 | +[JEP 409](https://openjdk.org/jeps/409): Sealed Classes |
| 132 | +[JEP 440](https://openjdk.org/jeps/440): Record Patterns |
| 133 | +[JEP 427](https://openjdk.org/jeps/427): Pattern Matching for Switch |
0 commit comments