50+ Proven Tips to Slash Bugs in Your Backend Development
This article compiles over fifty practical tips covering database design, code practices, and cache usage to help developers dramatically reduce bugs, improve reliability, and boost overall software quality in everyday backend projects.
Introduction
Today we discuss how to reduce bugs in daily development, summarizing more than 50 practical points across three major areas: database, code, and cache usage.
1. Database Section
Key areas prone to bugs: slow queries, field considerations, transaction failures, deadlocks, master‑slave delay, data compatibility, classic SQL pitfalls.
1.1 Slow Queries
1.1.1 Index Hit
Queries without indexes or that fail to hit indexes cause slow performance. Indexes may become ineffective when:
Query conditions contain
ORString fields are not quoted
Using
LIKEwith wildcards
Composite index columns are not in the leading position
Applying MySQL functions on indexed columns
Performing arithmetic on indexed columns
Using
!=,
<>, or
NOT INon indexed fields
Testing for
NULLor
NOT NULLMismatched character sets in joins
MySQL optimizer prefers full table scan
1.1.2 Large Data Sets – Sharding
When a single table exceeds ~20 million rows (B‑tree height grows), performance degrades; consider sharding with middleware such as
mycator
sharding-jdbc.
1.1.3 Unreasonable SQL
Avoid excessive joins (e.g., six tables) and too many indexes (more than five per table) as they hurt insert/update performance.
1.2 Field Considerations
1.2.1 Length Overflow
<code>`name` varchar(255) DEFAULT NOT NULL</code>Validate input length to prevent overflow errors.
1.2.2 Nullability
Prefer
NOT NULLwith sensible defaults (0 or -1 for integers, empty string for text) to avoid null‑pointer issues.
1.2.3 Missing Fields
Ensure schema changes in testing are propagated to production scripts.
1.2.4 Emoji Support
Use
utf8mb4for columns that need to store emojis.
1.2.5 Text/Blob Caution
Store file paths instead of full files; use prefix indexes for large
TEXTcolumns.
1.3 Transaction Pitfalls
1.3.1 @Transactional on non‑public methods
Spring AOP proxies ignore non‑public methods, so transactions won’t apply.
1.3.2 Self‑invocation
<code>public class TransactionTest{<br/> public void A(){ B(); }<br/> @Transactional<br/> public void B(){ /* insert */ }<br/>}</code>Direct calls bypass the proxy, causing transaction loss.
1.3.3 Swallowed Exceptions
<code>@Transactional<br/>public void method(){<br/> try{ /* insert */ }catch(Exception e){ logger.error("exception caught", e); }<br/>}</code>Catching exceptions without rethrowing prevents rollback.
1.3.4 rollbackFor Misuse
Spring rolls back only unchecked exceptions by default; specify
rollbackForfor checked ones.
1.3.5 Engine Support
MyISAM does not support transactions; use InnoDB.
1.3.6 Thread Context
Transactional code must run in the same thread as the Spring proxy.
1.4 Deadlocks
Deadlocks occur when multiple transactions hold locks the other needs, reducing resource utilization.
1.4.1 Lock Scenarios
Primary key + RC
Unique secondary index + RC
Non‑unique secondary index + RC
No index + RC
Primary key + RR
Unique secondary index + RR
Non‑unique secondary index + RR
No index + RR
Serializable
1.4.2 Analysis Steps
Simulate deadlock
Run
SHOW ENGINE INNODB STATUSIdentify offending SQL
Analyze lock types
Review lock compatibility matrix
1.5 Master‑Slave Delay
Read‑after‑write may miss recent data if the slave lags; use the master for strong consistency or accept eventual consistency for less critical reads.
1.6 Data Compatibility
When adding new columns, define default values for existing rows; handle nulls carefully to avoid NPEs.
1.7 Classic SQL Tips
1.7.1 Large Pagination
Three solutions: use incremental IDs, limit page numbers, or employ sub‑queries to fetch IDs first.
<code>select id,name from employee where id>1000000 limit 10;</code> <code>SELECT a.* FROM employee a, (select id from employee where ... limit 1000000,10) b where a.id=b.id;</code>1.7.2 Batch Operations
Prefer batch inserts/updates (e.g., 500 rows per batch) over single‑row loops.
<code>remoteBatchQuery(param);</code> <code>for(int i=0;i<100000;i++){ remoteSingleQuery(param); }</code>2. Code Layer
2.1 Coding Details
2.1.1 Six Typical Null‑Pointer Issues
Wrapper type nulls
Chained calls
Equals left‑hand null
ConcurrentHashMap does not allow null keys/values
Direct array/collection access
Direct field access
<code>if(object!=null){ String name = object.getName(); }</code>2.1.2 Thread‑Pool Usage
Avoid
Executors.newFixedThreadPool(unbounded queue may OOM)
Use custom pools with clear naming
Isolate pools per business domain
Handle pool exceptions properly
2.1.3 Linear‑Safety Collections
Use thread‑safe structures like
ConcurrentHashMapinstead of
HashMapin high‑concurrency scenarios.
Non‑thread‑safe: HashMap, ArrayList, LinkedList, TreeMap
Thread‑safe: Vector, Hashtable, ConcurrentHashMap
2.1.4 Date/Amount Precision
<code>Calendar calendar = Calendar.getInstance();<br/>calendar.set(2019, Calendar.DECEMBER, 31);<br/>Date testDate = calendar.getTime();<br/>SimpleDateFormat dtf = new SimpleDateFormat("YYYY-MM-dd");<br/>System.out.println(dtf.format(testDate));</code>Using
YYYYyields 2020‑12‑31; prefer
yyyy.
<code>public class DoubleTest {<br/> public static void main(String[] args){<br/> System.out.println(0.1+0.2);<br/> System.out.println(1.0-0.8);<br/> System.out.println(4.015*100);<br/> System.out.println(123.3/100);<br/> double amount1 = 3.15;<br/> double amount2 = 2.10;<br/> if(amount1 - amount2 == 1.05){ System.out.println("OK"); }<br/> }<br/>}</code>2.1.5 Large File Handling
Avoid
Files.readAllBytesfor big files; use
BufferedReaderline‑by‑line or NIO channels.
2.1.6 Resource Closing
<code>try (FileInputStream inputStream = new FileInputStream(new File("jay.txt"))) {<br/> // use resources<br/>} catch (FileNotFoundException e) { log.error(e); } catch (IOException e) { log.error(e); }</code>2.1.7 Exception Handling Pitfalls
Avoid
e.printStackTrace()Log exceptions appropriately
Do not catch generic
Exceptionfor everything
Never use exception handling as business logic
2.1.8 Concurrency Consistency (Check‑then‑Update)
Replace separate check‑then‑update with atomic DB update/delete to avoid race conditions.
<code>if(deleteAvailableTicketById(ticketId) == 1){ /* proceed */ } else { return "No ticket"; }</code>2.2 External Interface Design
2.2.1 Parameter Validation
Validate length, format, and range before persisting to prevent DB errors.
2.2.2 Backward Compatibility
<code>// Old interface<br/>void oldService(A,B){ newService(A,B,null); }<br/><br/>// New interface<br/>void newService(A,B,C);</code>2.2.3 Rate Limiting
Use Guava
RateLimiteror Alibaba Sentinel to protect services from traffic spikes.
2.2.4 Security (Signing & Auth)
Critical APIs (e.g., transfers) must enforce authentication, signing, and verification.
2.2.5 Idempotency
Design APIs to be idempotent—use unique keys, tokens, database constraints, or distributed locks (Redis, Zookeeper) to prevent duplicate processing.
2.3 Third‑Party Calls
2.3.1 Timeout Handling
On remote call timeout, avoid updating local state; instead, query the remote system later to confirm outcome.
2.3.2 Retry Mechanism
Implement retries for transient failures, respecting idempotency.
2.3.3 Degradation Strategy
If a non‑essential downstream service fails (e.g., email), degrade gracefully—complete the primary flow and handle the optional step asynchronously.
2.3.4 Asynchronous Processing
Offload notification calls (SMS, email) to async threads to improve response time.
2.3.5 Exception Handling for Remote Calls
Define clear fallback logic, decide between retry or failure, and ensure eventual consistency.
3. Cache Section
3.1 Database‑Cache Consistency
3.1.1 Cache Patterns
Cache‑Aside (read‑through/write‑through)
Read‑Through/Write‑Through
Write‑Behind (asynchronous)
Most systems use Cache‑Aside: read cache first, fall back to DB on miss, then populate cache.
3.1.2 Delete vs Update Cache
Deleting stale cache entries avoids dirty reads compared to directly updating cache after DB writes.
3.1.3 Order of Operations
In Cache‑Aside, always write to the DB first, then delete or update the cache to prevent stale reads.
3.1.4 Ensuring Final Consistency
Cache‑aside double delete with delay
Retry deletion
Asynchronous log‑driven cache eviction
3.2 Cache Penetration
Cache penetration occurs when requests for non‑existent keys repeatedly miss the cache and hit the DB, causing unnecessary load.
Mitigation:
Validate request parameters at API entry
Cache empty results with short TTL
Use Bloom filters to pre‑check existence
3.3 Cache Avalanche
Cache avalanche happens when many keys expire simultaneously, flooding the DB.
Mitigation: stagger TTLs (e.g., base + random offset) and ensure Redis high‑availability.
3.4 Cache Breakdown (Hot‑Key Miss)
When a hot key expires, a burst of concurrent requests may bypass the cache and overload the DB.
Solutions:
Mutex lock (e.g., Redis
SETNX) to allow only one thread to rebuild the cache
Never‑expire hot data and refresh asynchronously
3.5 Hot Key Handling
Scale Redis clusters, hash‑shard hot keys, or add a local JVM cache to distribute load.
3.6 Memory & Eviction
3.6.1 Capacity Planning
Cache only frequently accessed data; monitor memory usage to avoid OOM.
3.6.2 Redis Eviction Policies
volatile‑lru / allkeys‑lru volatile‑lfu / allkeys‑lfu volatile‑random / allkeys‑random volatile‑ttl noeviction
3.6.3 Data Structure Choice
Leaderboard →
zsetUser info →
hashMessage queue / article list →
listTags / social sets →
setCounters / locks →
stringConclusion
This article summarizes more than fifty coding and architectural best practices to help developers reduce bugs and improve system reliability.
macrozheng
Dedicated to Java tech sharing and dissecting top open-source projects. Topics include Spring Boot, Spring Cloud, Docker, Kubernetes and more. Author’s GitHub project “mall” has 50K+ stars.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.