Developer Guidelines

Developer guidelines usually start life as team checklists, but those checklists only help when people can understand the reasoning behind them. This post turns a broad set of engineering standards into a practical field guide: how to pick ready work, write code that stays maintainable, operate services safely, and ship changes with fewer avoidable mistakes.

The goal is not to create a process for its own sake. The goal is to reduce the classes of problems that repeatedly slow teams down: unclear story scope, oversized classes, poor logging, unsafe schema changes, brittle APIs, weak CI gates, and preventable security issues.

How to Use This Guide

  • Use it when starting a new story to check readiness and definition of done.
  • Use it during implementation to keep code, database, and API decisions aligned with team standards.
  • Use it in code review to spot issues before they become production problems.
  • Use it as onboarding material for engineers who need the team’s default expectations in one place.

General

These are the habits that shape delivery quality before code is even merged.

GuidelineNotes
Pick a story only if it has Acceptance CriteriaNo AC = not ready for dev
Estimate including unit and integration testingDon’t estimate just implementation time
Strive for TDDSee TDD guide
Install the right dev tools and pluginsSee Developer Tools below
Document ADR for architectural decisionsOne ADR per significant decision
Keep README.md up to date
Define “Dev Done” as a teamExample: story is dev-done only when deployed to dev env and showcased to QA/BA

Why this matters: Teams lose time when work starts before the story is clear or when “done” means different things to different people. A small amount of discipline here prevents churn later.

Object-Oriented Design

This section focuses on maintainability. Most long-term codebase pain comes from weak boundaries, unclear names, and methods that do too much.

OOP Checklist

PrincipleNotesReference
Tell-Don’t-AskObjects should do things, not expose state for callers to act onMartin Fowler
DRYDon’t Repeat Yourself – duplication is a maintenance burden
Single ResponsibilityOne reason to change per class/method
Intention-revealing namesNames explain what, not how
Consistent formattingUse auto-formatters – remove subjective debates
Null Object patternAvoids null checks scattered everywhere
Avoid deeply nested codeExtract methods to flatten nesting > 3 levelsCode smell reference
Avoid dead codeDelete it; don’t comment it out
Methods ≤ 30 linesLong methods are a refactoring signal
Classes ≤ 200 linesreference
Avoid long parameter listsPass value objects or builders instead
Replace conditionals with polymorphismUse Strategy or Visitor pattern
Don’t swallow exceptionsAlways log or rethrow with contextreference
Prefer composition over inheritanceFavour has-a over is-a
Anti-corruption layer for external systemsIsolate external API shapes from your domainreference
Every class with behaviour has a unit testAim for 100% branch coverage

Code Example: Tell-Don’t-Ask

// ❌ BAD — asks for state, then acts on it outside the object
if (order.getStatus().equals("PENDING") && order.getTotal() > 0) {
order.setStatus("PROCESSING");
paymentService.charge(order.getTotal());
}
// ✅ GOOD — tells the order to process itself
order.process(paymentService);
// Order encapsulates the status check and state transition

Code Example: Extract Small Methods

// ❌ BAD — 40-line method doing 4 things
public void handleOrder(Order order) {
// 10 lines of validation
// 10 lines of pricing calculation
// 10 lines of inventory check
// 10 lines of payment processing
}
// ✅ GOOD — each concern is named and testable
public void handleOrder(Order order) {
validateOrder(order);
Price price = calculatePrice(order);
reserveInventory(order);
processPayment(order, price);
}

Key takeaway: Keep behaviour close to the object that owns it, keep units of code small, and optimise for code that is easy to read and safe to change.

Logging

Logs are not just debugging output. They are operational evidence when something goes wrong in production.

GuidelineNotes
Use INFO, ERROR, DEBUG appropriatelyEntry/exit → DEBUG; all exceptions → ERROR/WARN
Enable distributed loggingConfigure Istio to propagate trace headers; log the tracing header in MDC context
Never log PIIMask names, emails, account numbers, card numbers before logging
Include request/trace ID in every log lineEssential for tracing a request across services

Log Level Guide

// DEBUG — developer detail, disabled in production
log.debug("Entering calculateDiscount with orderId={}", orderId);
// INFO — normal business events
log.info("Order processed successfully. orderId={}, amount={}", orderId, amount);
// WARN — unexpected state, service still working
log.warn("Payment retry attempt {} for orderId={}", retryCount, orderId);
// ERROR — exception caught, operation failed
log.error("Failed to process payment for orderId={}. Cause: {}", orderId, e.getMessage(), e);

Danger: ANTI-PATTERN

// ❌ Never log PII
log.info("User {} with email {} placed order", user.getName(), user.getEmail());
// ✅ Log IDs only
log.info("User {} placed order {}", user.getId(), orderId);

Key takeaway: good logs help you reconstruct what happened across services without exposing sensitive data.

Database

Database changes deserve extra caution because application versions often overlap during deployment. A schema that works only for the newest code is not safe enough.

GuidelineNotes
Use schema evolution toolsFlyway or Liquibase – never manual schema changes
Naming conventionslower_case_underscore_separated for tables and columns; be consistent with plural vs singular
Configure connection pool
Be intentional about indexesCreate only based on actual access patterns – use EXPLAIN ANALYSE
Plan for data archivalGrowing tables need an archival strategy before they become a problem

Index Creation Rule

Create an index when a column appears in WHERE, JOIN ON, or ORDER BY on a table with > 10k rows. Verify with EXPLAIN ANALYSE:

EXPLAIN ANALYSE SELECT * FROM orders WHERE user_id = 123;
-- Look for "Seq Scan" on large tables — that's a missing index
-- "Index Scan" means the index is being used

Common Mistakes to Avoid

  • Adding schema changes manually in production instead of through versioned migrations
  • Creating indexes without confirming an actual query pattern or execution plan needs
  • Ignoring table growth until cleanup, partitioning, or archival becomes urgent

Key takeaway: Treat schema design as an operational concern, not just a modelling exercise.

REST / Events

Interfaces outlive implementations. API and event conventions need to be predictable because other systems build against them.

GuidelineNotes
Follow RESTful URL conventionsResource-based, lowercase, hyphenated: /order-items, not /getOrderItems
Standard response/error codes200 OK, 201 Created, 400 Bad Request, 404 Not Found, 409 Conflict, 500 Internal Server Error
Integration test all controllersCover 2XX, 4XX, and 5XX paths
Use RFC 3339 date-time format2024-01-15T10:30:00Z – not 15/01/2024 or epoch milliseconds

REST Naming Conventions

# ✅ Good
GET /orders → list orders
GET /orders/{id} → get one order
POST /orders → create order
PUT /orders/{id} → replace order
PATCH /orders/{id} → partial update
DELETE /orders/{id} → delete order
# ❌ Bad
GET /getOrders
POST /createOrder
GET /orders/getById/{id}

Key takeaway: Stable, conventional interfaces reduce client confusion and make integrations easier to test and maintain.

HTTP Client

Outbound calls are one of the fastest ways for a healthy service to become unhealthy. Defaults are often unsafe.

GuidelineNotes
Configure all three timeoutsConnection timeout (establish TCP), read timeout (wait for response), write timeout (send request body)
Configure circuit breakersOpen circuit after N failures; half-open to probe recovery
Configure HTTP connection poolPrevent connection exhaustion under load

Timeout Configuration Example (Spring RestTemplate / WebClient)

// ✅ Configure all timeouts — never use defaults
HttpClient httpClient = HttpClient.create()
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 5000) // 5s connect
.responseTimeout(Duration.ofSeconds(10)) // 10s read
.doOnConnected(conn ->
conn.addHandlerLast(new ReadTimeoutHandler(10))
.addHandlerLast(new WriteTimeoutHandler(10)));

Danger: ANTI-PATTERN

Using default HTTP client timeouts (often infinite).

An external service that hangs will hold your thread indefinitely. Under load, this exhausts your thread pool and cascades into a full outage. Always set explicit timeouts.

Key takeaway: Every external dependency needs timeout, pooling, and failure-isolation settings before it is production-ready.

CI Pipeline

The CI pipeline is the automated version of team discipline. If quality checks are optional, they eventually become inconsistent.

StageRequirementWhy
LintCheckstyle, PMD, SpotBugs – fail on violationsConsistent style without review arguments
SonarFail on quality gate violationsCatches bugs, duplication, and coverage regression
TestFail if coverage < agreed thresholdRegression protection
BuildProduce the build artifact
AuditDependencyCheck / Clair / VeracodeCatch known CVEs before they reach production
PackageOutput Docker imageImmutable, versioned artifact
PushPush to registry
Security AnalysisTrivy image scanCatch OS-level vulnerabilities in base image

Key takeaway: CI should block low-quality or risky changes early, before they become review fatigue or production incidents.

Code Repository Structure

Repository structure affects how quickly engineers can understand, navigate, and safely change a service.

RequirementNotes
Detailed README.mdService purpose, architecture links, local setup, Swagger link, tech stack versions
Segregate packages by domaincom.company.orders.pricing, not com.company.controllers
Linting tools configuredPMD, Checkstyle, SpotBugs, CPD (copy-paste detector), SonarQube
Descriptive service nameName the repo after what it does

Key takeaway: Organise repositories around business capability and discoverability, not convenience for the first developer who created them.

Security

Security guidelines are engineering defaults, not a separate phase at the end of delivery.

RequirementLink
Install Talisman as a pre-commit hookPrevents committing secrets, API keys, passwords
Run dependency scanner in CIDependencyChecker, Clair, or Veracode
Follow OWASP Top 10OWASP Top 10
Sanitise all inputsPrevent SQL injection, XSS, path traversal at system boundaries
Never hardcode secretsUse environment variables or a secrets manager

Key takeaway: Most avoidable security issues come from weak defaults, not advanced attacks. Build the safe path into normal development.

Developer Tools and Plugins

Tooling should remove friction, not add ceremony. The right defaults improve code quality before review starts.

IntelliJ IDEA recommended plugins:

PluginPurpose
SonarLintReal-time code quality feedback
CheckstyleStyle enforcement
PMD-IDEAStatic analysis
SpotBugsBug detection
Rainbow BracketsNesting clarity
Key Promoter XLearn keyboard shortcuts
diffplug/spotlessAuto-formatting

Terminal: iTerm2 + Zsh + Oh My Zsh

Mocking Services

When dependencies are unavailable, expensive, or hard to control, service virtualisation keeps development and testing moving.

Use CaseTool
Static responsesWireMock
Dynamic / scenario-based responsesMountebank

Clean Code References

Final Takeaways

  • Start only with stories that are clear, testable, and aligned with what “done” means.
  • Keep code small, intention-revealing, and easy to change.
  • Treat logging, database changes, and external calls as operational design concerns.
  • Make interfaces predictable and CI enforcement non-optional.
  • Build security and maintainability into the default workflow instead of relying on heroics later.
  • Refactoring Catalogue – Fowler
  • Clean Code Summary Cheatsheet
Balaji G
Written by
Balaji G

Leave a Reply

Discover more from 2G

Subscribe now to keep reading and get access to the full archive.

Continue reading