Keep code simple: avoid overly long files and functions to manage complexity. When a file exceeds ~400 lines or a function exceeds ~80 lines, consider splitting it. Break complex logic into small, focused units to reduce cognitive load.
YAGNI — don't add code or abstractions until it's actually needed. If a feature, interface, or parameter isn't required by the current requirement, omit it. You Aren't Gonna Need It.
Prefer existing solutions before writing new code, in this order:
- JDK standard library (java.util, java.time, etc.)
- Platform-native features (Spring IoC, AOP, validation, etc.)
- Already-installed dependencies (check pom.xml first)
- Only then: write custom code
Prefer official Spring Boot starters over hand-rolled infrastructure. If a problem can be solved by adding an official Spring starter plus a small amount of glue code, use the starter instead of building custom infrastructure.
Use chaining and fluent style where natural, but don't force one-liners. Break long expressions into multiple lines if that improves readability — especially when dealing with Optional chains, complex ternaries, or multi-step transformations.
Avoid over-encapsulation: don't abstract before duplication appears. A helper method used only once is just indirection, not cleanliness. Extract when the same pattern appears in ≥2 places — not earlier. But one-off private methods that clarify intent are not "duplication" — they're organization.
Don't write boilerplate that can be generated. Use Lombok (@Data, @Builder, @RequiredArgsConstructor), MapStruct for mappers, and IDE generation for equals/hashCode. If it's mechanical, don't type it by hand. For records with many fields, prefer @Builder over positional new — the named setter style improves readability at every call site. A 6-argument new Xxx(a, b, c, d, e, f) forces readers to count positions; .name(a).type(b)... does not.
Prefer imports over fully-qualified names for JDK and framework classes. Import the class at the top and use its short name in code — Objects::nonNull, not java.util.Objects::nonNull. The exception is when a project class shares the same simple name as a JDK or framework class you need to use. In that case, qualify the JDK/framework class with its full package path everywhere — even if the conflicting project class isn't imported in the current file. For example, use javax.sql.DataSource fully-qualified because io.github.malonetalk.entity.Datasource exists in the project and the two names are easily confused.
Write code for the next reader, not for the compiler. The compiler can parse anything. A human shouldn't have to. Choose names that reveal intent, structure code in small logical steps, and prefer clarity over cleverness. If a line makes you pause — it will make someone else pause too.
Use Optional for nullable values passed between internal Java layers (e.g., Service). When an internal method may return or accept a null value, wrap it in Optional to make the possibility explicit and avoid null-check boilerplate. Only fall back to a plain null reference at the boundary where the object leaves the service (controllers, external APIs, serialization). The goal is to keep if (x != null) boilerplate out of the service layer.
Write MySQL 5.7 compatible SQL — no window functions (ROW_NUMBER(), RANK(), etc.), no CTE (WITH ... AS), no other MySQL 8.0+ syntax.
New data entities belong in io.github.malonetalk.model. Place new PO/DTO/VO/BO under io.github.malonetalk.model.{po,dto,vo,bo}. Do not add new classes to the deprecated io.github.malonetalk.dto and io.github.malonetalk.entity packages.