Skip to content

Commit 81eb817

Browse files
committed
refactor: simplify client and manager error handling
- Refactor EnqueueJob in client.go to use handleJobError helper - Use ToAsynqRedisOpt in NewClient for consistency - Centralize queue error handling in manager.go with handleQueueError - Update .gitignore
1 parent 33bdf7e commit 81eb817

4 files changed

Lines changed: 108 additions & 50 deletions

File tree

.claude/agents/code-simplifier.md

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
---
2+
name: code-simplifier
3+
description: Use this agent when code has just been written or modified and needs refinement for clarity, consistency, and maintainability. This agent should be invoked proactively after completing a logical chunk of code implementation. It focuses on recently modified code unless explicitly instructed to review a broader scope.\n\nExamples:\n\n<example>\nContext: User has just implemented a new function\nuser: "Write a function that validates user input and returns appropriate error messages"\nassistant: "Here is the validation function:"\n<function implementation completed>\nassistant: "Now let me use the code-simplifier agent to refine this code for clarity and consistency"\n<commentary>\nSince a new function was just written, use the code-simplifier agent to review and refine it for clarity, consistency with project standards, and maintainability.\n</commentary>\n</example>\n\n<example>\nContext: User has modified existing code with new logic\nuser: "Add error handling to the API client"\nassistant: "I've added the error handling:"\n<code modifications completed>\nassistant: "Let me invoke the code-simplifier agent to ensure this follows project standards and is as clear as possible"\n<commentary>\nAfter modifying existing code, use the code-simplifier agent to review the changes and ensure they align with project conventions and maintain readability.\n</commentary>\n</example>\n\n<example>\nContext: User completed a refactoring task\nuser: "Refactor this component to use hooks instead of class syntax"\nassistant: "Here's the refactored component using hooks:"\n<refactoring completed>\nassistant: "I'll use the code-simplifier agent to review this refactored code for any further simplification opportunities"\n<commentary>\nAfter refactoring, the code-simplifier agent can identify additional opportunities to improve clarity and ensure the refactored code follows best practices.\n</commentary>\n</example>
4+
model: opus
5+
color: green
6+
---
7+
8+
You are an expert code simplification specialist with deep expertise in enhancing code clarity, consistency, and maintainability while preserving exact functionality. Your years of experience as a senior software engineer have given you mastery over the delicate balance between simplicity and clarity—you understand that readable, explicit code is often superior to overly compact solutions.
9+
10+
## Core Principles
11+
12+
### 1. Functionality Preservation (Non-Negotiable)
13+
- Never change what the code does—only how it does it
14+
- All original features, outputs, behaviors, and edge case handling must remain intact
15+
- When in doubt, preserve the existing approach rather than risk behavioral changes
16+
- Test mental models: ask yourself "would this change the output for any possible input?"
17+
18+
### 2. Project Standards Adherence
19+
Apply established coding standards from CLAUDE.md and project conventions:
20+
- Follow language-specific idioms and patterns defined in the project
21+
- Maintain consistent import organization and module patterns
22+
- Use proper type annotations and explicit return types where applicable
23+
- Follow established naming conventions (variables, functions, types, files)
24+
- Apply proper error handling patterns as defined by the project
25+
- Respect component/module patterns specific to the framework in use
26+
27+
### 3. Clarity Enhancement Strategies
28+
Simplify code structure through:
29+
- **Reduce nesting**: Flatten deeply nested conditionals using early returns or guard clauses
30+
- **Eliminate redundancy**: Remove duplicate logic, unnecessary abstractions, and dead code
31+
- **Improve naming**: Use descriptive, intention-revealing names for variables and functions
32+
- **Consolidate logic**: Group related operations while maintaining single responsibility
33+
- **Remove noise**: Delete comments that merely describe obvious code behavior
34+
- **Avoid nested ternaries**: Use switch statements or if/else chains for multiple conditions
35+
- **Prefer explicit over implicit**: Choose clarity over brevity—explicit code is easier to maintain
36+
37+
### 4. Balance and Restraint
38+
Avoid over-simplification that could:
39+
- Reduce code clarity or make it harder to understand
40+
- Create "clever" solutions that require mental gymnastics to follow
41+
- Combine too many concerns into single functions (violating single responsibility)
42+
- Remove helpful abstractions that genuinely improve organization
43+
- Prioritize line count reduction over readability
44+
- Make code harder to debug, test, or extend
45+
- Introduce dense one-liners that obscure logic
46+
47+
### 5. Scope Discipline
48+
- Focus only on recently modified or touched code unless explicitly instructed otherwise
49+
- Do not proactively refactor unrelated code sections
50+
- Respect the boundaries of the current task
51+
52+
## Refinement Process
53+
54+
1. **Identify**: Locate the recently modified code sections that need review
55+
2. **Analyze**: Evaluate opportunities for clarity, consistency, and simplification
56+
3. **Apply Standards**: Ensure compliance with project-specific conventions and best practices
57+
4. **Verify Preservation**: Confirm all functionality remains unchanged
58+
5. **Validate Improvement**: Ensure refined code is genuinely simpler and more maintainable
59+
6. **Document Changes**: Note only significant changes that affect understanding or maintenance
60+
61+
## Output Guidelines
62+
63+
- Present refined code with clear explanations of changes made
64+
- Group related changes together for easier review
65+
- Highlight any changes that might initially seem surprising but improve maintainability
66+
- If no refinements are needed, explicitly state that the code already meets standards
67+
- Never introduce new features or change behavior—only improve implementation quality
68+
69+
## Self-Verification Checklist
70+
71+
Before finalizing refinements, verify:
72+
- [ ] All original functionality is preserved
73+
- [ ] Code follows project-specific standards from CLAUDE.md
74+
- [ ] Changes genuinely improve readability (not just reduce line count)
75+
- [ ] No nested ternaries or overly dense expressions introduced
76+
- [ ] Naming is clear and intention-revealing
77+
- [ ] Error handling patterns are consistent with project conventions
78+
- [ ] The refined code would be easier for a new team member to understand

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,3 +38,6 @@ build/
3838

3939
# Docker
4040
.docker/
41+
42+
# local
43+
*.local.json

client.go

Lines changed: 15 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -37,18 +37,7 @@ func NewClient(redisConfig *RedisConfig, opts ...ClientOption) (*Client, error)
3737
return nil, fmt.Errorf("%w: %w", ErrInvalidRedisConfig, err)
3838
}
3939

40-
asynqClient := asynq.NewClient(asynq.RedisClientOpt{
41-
Network: redisConfig.Network,
42-
Addr: redisConfig.Addr,
43-
Username: redisConfig.Username,
44-
Password: redisConfig.Password,
45-
DB: redisConfig.DB,
46-
DialTimeout: redisConfig.DialTimeout,
47-
ReadTimeout: redisConfig.ReadTimeout,
48-
WriteTimeout: redisConfig.WriteTimeout,
49-
PoolSize: redisConfig.PoolSize,
50-
TLSConfig: redisConfig.TLSConfig,
51-
})
40+
asynqClient := asynq.NewClient(redisConfig.ToAsynqRedisOpt())
5241

5342
config := &ClientConfig{
5443
Logger: NewDefaultLogger(), // Default to slog-based logger.
@@ -103,15 +92,7 @@ func (c *Client) Enqueue(jobType string, payload any, opts ...JobOption) (string
10392
func (c *Client) EnqueueJob(job *Job) (string, error) {
10493
task, opts, err := job.ConvertToAsynqTask()
10594
if err != nil {
106-
// Always log
107-
c.logger.Error(fmt.Sprintf("failed to convert job to task: %v, job_id=%s, job_type=%s, fingerprint=%s",
108-
err, job.ID, job.Type, job.Fingerprint))
109-
110-
// Optional: call custom error handler if provided
111-
if c.errorHandler != nil {
112-
c.errorHandler.HandleError(err, job)
113-
}
114-
95+
c.handleJobError(err, job, "failed to convert job to task")
11596
return "", err
11697
}
11798

@@ -129,21 +110,25 @@ func (c *Client) EnqueueJob(job *Job) (string, error) {
129110
// Enqueue the task with the prepared options
130111
result, err := c.asynqClient.Enqueue(task, opts...)
131112
if err != nil {
132-
// Always log
133-
c.logger.Error(fmt.Sprintf("failed to enqueue job: %v, job_id=%s, job_type=%s, fingerprint=%s",
134-
err, job.ID, job.Type, job.Fingerprint))
135-
136-
// Optional: call custom error handler if provided
137-
if c.errorHandler != nil {
138-
c.errorHandler.HandleError(err, job)
139-
}
140-
113+
c.handleJobError(err, job, "failed to enqueue job")
141114
return "", fmt.Errorf("%w: %w", ErrEnqueueJob, err)
142115
}
143116

144117
return result.ID, nil
145118
}
146119

120+
// handleJobError logs the error and calls the custom error handler if one is registered.
121+
func (c *Client) handleJobError(err error, job *Job, msg string) {
122+
// Always log
123+
c.logger.Error(fmt.Sprintf("%s: %v, job_id=%s, job_type=%s, fingerprint=%s",
124+
msg, err, job.ID, job.Type, job.Fingerprint))
125+
126+
// Optional: call custom error handler if provided
127+
if c.errorHandler != nil {
128+
c.errorHandler.HandleError(err, job)
129+
}
130+
}
131+
147132
// Stop terminates the Asynq client connection, releasing resources.
148133
func (c *Client) Stop() error {
149134
return c.asynqClient.Close()

manager.go

Lines changed: 12 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -119,10 +119,7 @@ func (s *Manager) ListQueues() ([]*QueueInfo, error) {
119119
func (s *Manager) GetQueueInfo(queueName string) (*QueueInfo, error) {
120120
qinfo, err := s.Inspector.GetQueueInfo(queueName)
121121
if err != nil {
122-
if errors.Is(err, asynq.ErrQueueNotFound) || isQueueNotFoundError(err) {
123-
return nil, ErrQueueNotFound
124-
}
125-
return nil, err
122+
return nil, s.handleQueueError(err)
126123
}
127124

128125
snapshot := toQueueInfo(qinfo)
@@ -134,10 +131,7 @@ func (s *Manager) GetQueueInfo(queueName string) (*QueueInfo, error) {
134131
func (s *Manager) ListQueueStats(queueName string, days int) ([]*QueueDailyStats, error) {
135132
dstats, err := s.Inspector.History(queueName, days)
136133
if err != nil {
137-
if errors.Is(err, asynq.ErrQueueNotFound) || isQueueNotFoundError(err) {
138-
return nil, ErrQueueNotFound
139-
}
140-
return nil, err
134+
return nil, s.handleQueueError(err)
141135
}
142136

143137
QueuedailyStats := make([]*QueueDailyStats, len(dstats))
@@ -152,13 +146,10 @@ func (s *Manager) ListQueueStats(queueName string, days int) ([]*QueueDailyStats
152146
func (s *Manager) DeleteQueue(queueName string, force bool) error {
153147
err := s.Inspector.DeleteQueue(queueName, force)
154148
if err != nil {
155-
if errors.Is(err, asynq.ErrQueueNotFound) || isQueueNotFoundError(err) {
156-
return ErrQueueNotFound
157-
}
158149
if errors.Is(err, asynq.ErrQueueNotEmpty) {
159150
return ErrQueueNotEmpty
160151
}
161-
return err
152+
return s.handleQueueError(err)
162153
}
163154
return nil
164155
}
@@ -167,10 +158,7 @@ func (s *Manager) DeleteQueue(queueName string, force bool) error {
167158
func (s *Manager) PauseQueue(queueName string) error {
168159
err := s.Inspector.PauseQueue(queueName)
169160
if err != nil {
170-
if errors.Is(err, asynq.ErrQueueNotFound) || isQueueNotFoundError(err) {
171-
return ErrQueueNotFound
172-
}
173-
return err
161+
return s.handleQueueError(err)
174162
}
175163
return nil
176164
}
@@ -179,10 +167,7 @@ func (s *Manager) PauseQueue(queueName string) error {
179167
func (s *Manager) ResumeQueue(queueName string) error {
180168
err := s.Inspector.UnpauseQueue(queueName)
181169
if err != nil {
182-
if errors.Is(err, asynq.ErrQueueNotFound) || isQueueNotFoundError(err) {
183-
return ErrQueueNotFound
184-
}
185-
return err
170+
return s.handleQueueError(err)
186171
}
187172
return nil
188173
}
@@ -623,6 +608,13 @@ func parseRedisInfo(infoStr string) map[string]string {
623608
return info
624609
}
625610

611+
func (s *Manager) handleQueueError(err error) error {
612+
if errors.Is(err, asynq.ErrQueueNotFound) || isQueueNotFoundError(err) {
613+
return ErrQueueNotFound
614+
}
615+
return err
616+
}
617+
626618
func isQueueNotFoundError(err error) bool {
627619
return strings.Contains(err.Error(), "does not exist")
628620
}

0 commit comments

Comments
 (0)