Skip to content

hovecapital/java-code-style

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

4 Commits
 
 

Repository files navigation

HC Java Coding Style Guide v2

File Structure & Organization Rules

  1. UTF-8 encoding: All source files must be encoded in UTF-8.
  2. File naming: File name must match the top-level class name exactly, plus .java extension.
  3. Single top-level class: Each source file contains exactly one top-level class.
  4. File organization: Files must follow this exact order:
    • License or copyright information (if present)
    • Package statement
    • Import statements
    • Exactly one top-level class declaration
  5. Blank line separation: Exactly one blank line separates each section that is present.

Package & Import Rules

  1. Package naming: Use only lowercase letters and digits, no underscores. Concatenate words directly: com.example.deepspace, not com.example.deep_space.
  2. No wildcard imports: Never use wildcard imports (import java.util.*).
  3. Import organization:
    • All static imports in a single block
    • All non-static imports in a single block
    • Single blank line separates the two blocks if both present
    • Within each block, imports appear in ASCII sort order
  4. No static import for classes: Don't use static import for static nested classes.
  5. No line-wrapping: Import statements are never line-wrapped.

Type Safety & Declaration Rules

  1. Explicit types: Always declare explicit types, avoid raw types.
  2. Generic type safety: Always use parameterized types, avoid raw types.
  3. Array declarations: Use String[] args, not String args[] (no C-style).
  4. One variable per declaration: Declare only one variable per statement: int a; int b; not int a, b;.
  5. Variable scope: Declare local variables close to their first use, not at the start of blocks.
  6. Generic type naming: Use single capital letters (T, E, K, V) or class-style names followed by T (RequestT, ResponseT).

Code Quality Rules

  1. No code repetition: Extract repeated logic into methods or utility classes.
  2. Descriptive naming: Use clear, descriptive names that explain purpose.
  3. Single responsibility: Classes and methods should have single, focused purposes.
  4. Fail early: Validate inputs at method entry points with clear exceptions.
  5. @Override annotation: Always use @Override when overriding methods or implementing interface methods.
  6. No ignored exceptions: Never have empty catch blocks without explanation.

Fail Early Principles

  1. Guard Clauses: Use guard clauses at method entry to validate preconditions

    public void processOrder(Order order) {
        // Fail fast with clear error messages
        if (order == null) {
            throw new IllegalArgumentException("Order cannot be null");
        }
        if (order.getItems().isEmpty()) {
            throw new IllegalStateException("Order must contain at least one item");
        }
        if (order.getTotalAmount() <= 0) {
            throw new IllegalArgumentException("Order amount must be positive");
        }
        
        // Main logic here, no nested ifs needed
    }
  2. Early Returns: Return early from methods to reduce nesting

    public Optional<Customer> findCustomer(String id) {
        if (id == null || id.isBlank()) {
            return Optional.empty();
        }
        
        Customer customer = repository.findById(id);
        if (customer == null) {
            return Optional.empty();
        }
        
        return Optional.of(customer);
    }
  3. Null Object Pattern: Consider null object pattern over null checks

  4. Validation Order: Validate in order of: null checks → type checks → business rules

  5. Constructor Validation: Validate all invariants in constructors

  6. Use Objects.requireNonNull(): For non-null validation with custom messages

Method & Class Design Rules

  1. Method naming: Use lowerCamelCase, typically verbs or verb phrases (sendMessage, calculateTotal).
  2. Class naming: Use UpperCamelCase, typically nouns or noun phrases (Customer, PaymentProcessor).
  3. Method overloading: Keep overloaded methods together with no other members between them.
  4. Constructor placement: Place constructors before other methods.
  5. Class member ordering: Use logical ordering that maintainer can explain (not chronological).
  6. Access modifier ordering: Follow Java Language Specification order:
    public protected private abstract default static final sealed non-sealed
    transient volatile synchronized native strictfp
    
  7. Method length: Prefer methods under 20 lines
  8. Class cohesion: Fields should be used by multiple methods
  9. Single entry, single exit: Avoid multiple returns when it hurts readability

Object & Collection Design Rules

  1. Immutable objects: Prefer immutable objects when possible. Use final fields and no setters.
  2. Builder pattern: Use for complex object construction with many optional parameters.
  3. Collection interfaces: Program to interfaces (List<String>) not implementations (ArrayList<String>).
  4. Optional usage: Use Optional<T> for return types that may be absent, avoid for parameters.
  5. Defensive copying: Make defensive copies of mutable objects when necessary.
  6. Null-safe collections: Return empty collections, never null
  7. Collection copying: Always return unmodifiable views or copies

Separation of Concerns Architecture Rules

  1. Layer Independence: Each layer should only know about the layer directly below it

    • Controllers → Services → Repositories → Database
    • Never skip layers or create circular dependencies
  2. Interface Segregation:

    • Define minimal interfaces for each concern
    • Clients should not depend on methods they don't use
    // Good: Segregated interfaces
    interface OrderReader {
        Order findById(String id);
    }
    interface OrderWriter {
        void save(Order order);
    }
    
    // Bad: Fat interface
    interface OrderRepository {
        Order findById(String id);
        void save(Order order);
        List<Order> findByCustomer(String customerId);
        void delete(String id);
        // ... many more methods
    }
  3. Dependency Injection:

    • Inject dependencies, don't create them
    • Use constructor injection for required dependencies
    • Use interfaces for dependencies, not concrete classes
  4. Domain Logic Isolation:

    • Keep business logic in service/domain layer
    • Controllers handle HTTP concerns only
    • Repositories handle data access only
    • DTOs for external communication, entities for business logic
  5. Cross-Cutting Concerns:

    • Use aspects or interceptors for logging, security, transactions
    • Don't scatter these concerns throughout business logic

Exception Handling Rules

  1. Specific exceptions: Catch specific exceptions rather than generic Exception.
  2. Exception documentation: Document all checked exceptions with @throws.
  3. Resource management: Use try-with-resources for autocloseable resources.
  4. Exception naming: Exception classes should end with "Exception".
  5. Exception messages: Provide clear, actionable error messages.
  6. Exception chaining: Preserve original exception information when rethrowing.

Logging & Observability Rules

  1. Logger Declaration:

    private static final Logger log = LoggerFactory.getLogger(ClassName.class);
  2. Log Levels Usage:

    • ERROR: System failures requiring immediate attention
    • WARN: Recoverable issues or deprecated usage
    • INFO: Important business events (order placed, payment processed)
    • DEBUG: Detailed flow information for troubleshooting
    • TRACE: Very detailed information (method entry/exit, full payloads)
  3. Structured Logging with Context:

    // Use MDC for correlation
    MDC.put("correlationId", correlationId);
    MDC.put("userId", userId);
    MDC.put("orderId", orderId);
    
    log.info("Order processing started", 
        kv("orderId", orderId),
        kv("customerId", customerId),
        kv("amount", order.getAmount()));
  4. What to Log:

    • Entry/exit of key business operations
    • All exceptions with context
    • External system calls (with duration)
    • Security events (authentication, authorization)
    • Configuration changes
    • Performance metrics
  5. What NOT to Log:

    • Sensitive data (passwords, credit cards, PII)
    • Entire request/response bodies at INFO level
    • Loop iterations (unless TRACE level)
  6. Exception Logging Pattern:

    try {
        processPayment(order);
    } catch (PaymentException e) {
        log.error("Payment processing failed", 
            kv("orderId", order.getId()),
            kv("amount", order.getAmount()),
            kv("errorCode", e.getErrorCode()),
            e);  // Always include exception as last parameter
        throw new OrderProcessingException("Payment failed", e);
    }
  7. Performance Logging:

    @Timed
    public Order processOrder(OrderRequest request) {
        String correlationId = UUID.randomUUID().toString();
        MDC.put("correlationId", correlationId);
        
        long startTime = System.currentTimeMillis();
        log.info("Order processing started", kv("correlationId", correlationId));
        
        try {
            Order order = orderService.process(request);
            log.info("Order processing completed",
                kv("correlationId", correlationId),
                kv("duration", System.currentTimeMillis() - startTime),
                kv("orderId", order.getId()));
            return order;
        } finally {
            MDC.clear();
        }
    }
  8. Correlation & Tracing:

    • Generate correlation IDs at entry points
    • Pass correlation IDs through all layers
    • Include in all log messages and external calls
    • Use distributed tracing (OpenTelemetry, Zipkin)
  9. Log Message Guidelines:

    • Start with action/event ("Order created", "Payment failed")
    • Use consistent terminology
    • Include relevant IDs and business context
    • Make messages searchable and parseable
    • Avoid string concatenation, use parameterized messages

Formatting Rules

  1. Braces: Use K&R style (Egyptian brackets) for all blocks:

    if (condition) {
        doSomething();
    } else {
        doSomethingElse();
    }
  2. Empty blocks: May be concise {} except in multi-block statements.

  3. Indentation: Use +2 spaces for each new block level.

  4. Line length: Maximum 100 characters per line.

  5. One statement per line: Each statement on its own line.

  6. Line wrapping: Break at higher syntactic levels, continuation lines +4 spaces.

Whitespace Rules

  1. Horizontal spacing:

    • Space after keywords: if (condition), for (int i = 0; ...)
    • Space around binary operators: x + y, a == b
    • Space after commas, semicolons, and closing parentheses in casts
    • Space between type and variable: List<String> names
    • No space inside parentheses: method(arg) not method( arg )
    • Space before opening braces: class Foo {
  2. Vertical spacing:

    • Single blank line between consecutive class members (fields, constructors, methods)
    • Blank lines to organize code into logical subsections
    • No multiple consecutive blank lines (discouraged)

Naming Conventions

  1. Classes: UpperCamelCase (CustomerService, PaymentProcessor)
  2. Methods: lowerCamelCase (calculateTotal, sendMessage)
  3. Variables: lowerCamelCase (customerName, totalAmount)
  4. Constants: UPPER_SNAKE_CASE (MAX_SIZE, DEFAULT_TIMEOUT)
  5. Packages: lowercase, no underscores (com.company.product)
  6. Test classes: End with Test (CustomerServiceTest)
  7. Test methods: lowerCamelCase, may use underscores (transferMoney_deductsFromSource)

Constant Definition Rules

Constants are static final fields whose contents are deeply immutable:

// Constants
static final int MAX_SIZE = 100;
static final List<String> VALID_CODES = ImmutableList.of("A", "B", "C");
static final Joiner COMMA_JOINER = Joiner.on(','); // Joiner is immutable

// Not constants  
static String nonFinal = "mutable";
final String nonStatic = "non-static";
static final List<String> mutableList = new ArrayList<>();
static final Logger logger = Logger.getLogger(MyClass.getName());

Switch Statement Rules

  1. Exhaustiveness: Every switch must be exhaustive, add default cases even if empty.
  2. Modern syntax: Prefer new-style switches with arrows (->) over colons (:) when possible:
    return switch (day) {
        case MONDAY, TUESDAY -> "Workday";
        case SATURDAY, SUNDAY -> "Weekend";
        default -> "Midweek";
    };
  3. Fall-through comments: In old-style switches, mark intentional fall-through with // fall through.
  4. Switch expressions: Must use new-style syntax with arrows.

Annotation Rules

  1. Type-use annotations: Place immediately before the annotated type: @Nullable String name
  2. Class annotations: Each on separate line after documentation block
  3. Method annotations: Each on separate line, except single parameterless may share line
  4. Field annotations: Multiple annotations may share line if space permits
  5. Standard annotations: Use @Override, @Deprecated, @SuppressWarnings appropriately

Documentation & Comment Rules

  1. Javadoc required: All public classes, methods, and fields must have Javadoc.
  2. Javadoc format:
    /**
     * Brief summary fragment starting with verb or noun phrase.
     *
     * @param paramName description of parameter
     * @return description of return value
     * @throws ExceptionType when this exception is thrown
     */
  3. Summary fragments: Start with noun or verb phrase, not complete sentence
  4. Block tags order: @param, @return, @throws, @deprecated
  5. Implementation comments: Use // for single-line, /* */ for multi-line
  6. TODO comments: Format as // TODO: context - explanation with specific timeline

Enum Design Rules

  1. Enum formatting: Each constant on its own line if methods present:
    public enum Status {
        ACTIVE {
            @Override
            public boolean isProcessable() { return true; }
        },
        INACTIVE,
        PENDING;
        
        public abstract boolean isProcessable();
    }
  2. Simple enums: May be formatted on single line if no methods: enum Color { RED, GREEN, BLUE }

Specific Java Patterns & Best Practices

  1. Static member access: Qualify with class name, not instance: Math.max(a, b)
  2. String comparison: Use .equals() method, not ==
  3. Numeric literals: Use uppercase L for long literals: 3000000000L
  4. No finalizers: Never override Object.finalize()
  5. Utility classes: Make constructor private and class final
  6. Builder pattern: For objects with many optional parameters
  7. Immutable collections: Use Guava's ImmutableList, ImmutableMap, etc.

Class Design Patterns

  1. Immutable classes: Final class, final fields, no setters, defensive copying
  2. Value objects: Implement equals(), hashCode(), toString()
  3. Service classes: Stateless, injectable dependencies
  4. Repository interfaces: Abstract data access concerns
  5. DTO/Data classes: Simple data containers with validation

Project Structure Rules

  1. Package organization: Group related classes in logical packages by feature/domain
  2. Layer separation:
    • com.company.product.controller - REST controllers/presentation
    • com.company.product.service - Business logic
    • com.company.product.repository - Data access
    • com.company.product.dto - Data transfer objects
    • com.company.product.entity - Domain entities
    • com.company.product.exception - Custom exceptions
    • com.company.product.config - Configuration classes
    • com.company.product.util - Utility classes (minimize these)
    • com.company.product.validation - Validation logic
    • com.company.product.mapper - Object mapping/transformation
  3. Dependency direction: Higher-level packages should not depend on lower-level implementation details
  4. Test organization: Mirror main source structure in test directories

Error Handling & Exception Design

  1. Checked exceptions: For recoverable conditions that caller must handle
  2. Runtime exceptions: For programming errors and unrecoverable conditions
  3. Exception hierarchy: Create specific exception types for different error conditions
  4. Resource cleanup: Always use try-with-resources for AutoCloseable resources
  5. Exception wrapping: Wrap lower-level exceptions with domain-specific exceptions

Testing Patterns

  1. Test class naming: ClassNameTest for classes, FeatureIntegrationTest for integration
  2. Test method naming: Use descriptive names, underscores allowed for readability
  3. Test organization: Given-When-Then structure in test methods
  4. Mock usage: Mock external dependencies, test behavior not implementation
  5. Test data: Use builders or factories for complex test object creation

About

Java code style

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published