Backend Development 5 min read

Common Backend Code Smells and How to Prevent Over‑Engineering, Deep Inheritance, Excessive DTO Layers, Fake Interfaces, and Contract‑less Microservices

The article outlines common backend code smells such as over‑engineering, deep inheritance hierarchies, excessive DTO/VO layers, unnecessary interfaces, and contract‑less microservice designs, explains why they harm team efficiency and project quality, and provides concrete Java examples and best‑practice recommendations to avoid them.

Continuous Delivery 2.0
Continuous Delivery 2.0
Continuous Delivery 2.0
Common Backend Code Smells and How to Prevent Over‑Engineering, Deep Inheritance, Excessive DTO Layers, Fake Interfaces, and Contract‑less Microservices

This article presents a series of typical backend code‑smell patterns that can cripple team efficiency and project quality, and offers concrete Java examples together with practical refactoring guidelines.

1. Over‑Engineering – developers often add numerous abstractions, interfaces, and design patterns for hypothetical future requirements, resulting in unreadable and overly complex code that is mistakenly praised as “elegant architecture”.

public interface UserPersistenceStrategy {
    void saveUser(User user);
}

public class DefaultUserPersistenceStrategy implements UserPersistenceStrategy {
    public void saveUser(User user) {
        // 保存逻辑
    }
}

public class UserManager {
    private UserPersistenceStrategy strategy;
    public UserManager(UserPersistenceStrategy strategy) {
        this.strategy = strategy;
    }
    public void save(User user) {
        strategy.saveUser(user);
    }
}

Correct practice: place business logic in domain models or service layers instead of scattering it across utility classes or hard‑to‑read services.

2. Misuse of Inheritance (Deep Inheritance Trees) – a cascade of managers inheriting from base managers makes any change in a parent class break many children, and forces subclasses to inherit unnecessary details.

public class AbstractUserManager {
    protected void connect() { /* 连接DB */ }
}

public class BaseUserManager extends AbstractUserManager {
    protected void authenticate() { /* 鉴权 */ }
}

public class PremiumUserManager extends BaseUserManager {
    public void premiumFeatures() {
        connect();
        authenticate();
        // 高级功能
    }
}

Correct practice: prefer composition over inheritance; use a “has‑a” relationship rather than an “is‑a” hierarchy.

3. Excessive DTO/Entity/VO Layers – creating multiple objects (DTO, Entity, Form, VO, BO) that contain almost identical fields leads to needless copying, performance loss, and reduced development speed.

public class UserDTO {
    private String name;
    private int age;
}

public class UserEntity {
    private String name;
    private int age;
}
// 然后各种Mapper工具来回copy...

Correct practice: design objects according to clear upstream/downstream system boundaries; introduce DTO/VO only when a real cross‑system or cross‑layer contract is required.

4. Pseudo‑Interface Programming – forcing every class to implement an interface that has only a single implementation adds unnecessary abstraction and maintenance overhead.

public interface UserService {
    void createUser();
}

public class UserServiceImpl implements UserService {
    public void createUser() { /* 逻辑 */ }
}

Correct practice: create an interface only when you anticipate multiple implementations or need to vary behavior; otherwise use a concrete class directly.

5. Contract‑less Microservices – APIs are defined with loosely typed Maps, lacking versioning, clear request/response contracts, and proper documentation, making client integration error‑prone.

@PostMapping("/user")
public Map
createUser(@RequestBody Map
body) {
    // 用Map接参数,返回也是Map
}

Correct practice: define strict request and response DTOs, document the API with Swagger/OpenAPI, and enforce version control to keep contracts stable.

In large projects, the real challenge is not whether code can be written, but whether it can be quickly understood, easily maintained, and scaled; poor code quality becomes a toxic burden that drags down the entire team.

JavamicroservicesDTOcode qualityinheritanceover-engineering
Continuous Delivery 2.0
Written by

Continuous Delivery 2.0

Tech and case studies on organizational management, team management, and engineering efficiency

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.