Đại Cải Tổ: Từ Mì Ăn Liền Thành Kiến Trúc
2000 dòng code. 1 file. 8 AI agents. Tôi nhìn màn hình và tự hỏi: “Mày đã làm gì?”
Tôi gọi đó là “organized chaos”. Vợ tôi gọi đó là “digital hoarding”. Cả hai đều đúng.
10 PM, ngày 3. Sau 2 ngày agents chạy ổn định, tôi quyết định: “Refactor time!”
(Narrator: He had no idea what was coming)
Mục lục
- The Beautiful Mess
- The Breaking Point
- The Refactoring Plan
- The Great Migration Begins
- Midnight: Extract Core Functions
- 1 AM: The Configuration Extraction
- 2 AM: The Agent Instruction Revolution
- The Modular Architecture
- lib/tmux-manager.sh
- lib/queue-processor.sh
- The Testing Nightmare
- The Beautiful Result
- Unexpected Benefits
- 1. Debugging Became Pleasant
- 2. Feature Addition Became Trivial
- 3. Team Collaboration Possible
- Lessons From The Trenches
- 1. Refactor Early, Not When Desperate
- 2. Structure Enables Creativity
- 3. Comments Are Love Letters to Future You
- The Migration Guide
- The 6 AM Victory
The Beautiful Mess
growth-engine.sh – The Monolith:
#!/bin/bash
# Line 1-200: Initialize tmux sessions
# Line 201-500: Agent instructions (hardcoded)
# Line 501-800: Message passing logic
# Line 801-1200: Queue management
# Line 1201-1500: Error handling (sort of)
# Line 1501-1800: Random utilities
# Line 1801-2000: Functions I forgot existed
# Actual comment found at line 1337:
# TODO: Refactor this mess someday
# Update: That day is today
Symptoms của Technical Debt:
- Thêm feature mới = scroll 30 phút tìm đúng chỗ
- Fix bug = break 3 features khác
- Onboard người mới = “Đọc hết 2000 dòng đi”
- Duplicate code everywhere
- Comments contradict code
- Functions do 10 things instead of 1
The Breaking Point
10:30 PM – The Incident:
Me: "Let me just add email notifications real quick"
*changes line 1456*
Result: All agents start speaking Spanish
Me: "...what?"
Turns out line 1456 was part of a function that set language preferences. Which was somehow connected to agent initialization. Which was tied to message routing. Which affected… everything.
Spaghetti code doesn’t describe it. This was code lasagna – layers upon layers of interdependence.
The Refactoring Plan
11 PM – Whiteboard Session (aka Paper Napkin):
growth-engine-v2/
├── growth-engine.sh (main - 200 lines max)
├── lib/
│ ├── tmux-manager.sh
│ ├── agent-communicator.sh
│ ├── queue-processor.sh
│ └── error-handler.sh
├── agents/
│ ├── instructions/
│ └── configs/
├── templates/
└── utils/
Design Principles:
- Single Responsibility (1 function = 1 job)
- DRY (Don’t Repeat Yourself… finally)
- Modular (plug and play components)
- Clear naming (no more
process_stuff()
) - Comments that actually help
The Great Migration Begins
Midnight: Extract Core Functions
# Old way (buried in line 847)
function snd_msg() { # what does this do??
tmux send-keys -t "growth-team:$1" "$2" C-m
echo "sent" > /tmp/status # why??
}
# New way (lib/agent-communicator.sh)
send_to_agent() {
local agent_name=$1
local message=$2
local timestamp=$(date +%s)
# Validate inputs
[[ -z "$agent_name" ]] && error "Agent name required"
[[ -z "$message" ]] && error "Message required"
# Send via tmux
tmux send-keys -t "growth-team:$agent_name" "$message" C-m
# Log for debugging
log "[$timestamp] Sent to $agent_name: $message"
return 0
}
1 AM: The Configuration Extraction
Before: Hardcoded values scattered like landmines
# Found on line 234, 567, 1203, 1876...
QUEUE_DIR="/tmp/queue" # Different in each place!
# Also found:
QUEUE_DIR="/tmp/growth-queue"
QUEUE_DIR="./queue"
QUEUE_DIR="$HOME/queue"
After: One source of truth
# config/growth-engine.conf
GROWTH_ENGINE_VERSION="2.0"
QUEUE_DIR="${GROWTH_ENGINE_HOME}/queue"
SESSION_DIR="${GROWTH_ENGINE_HOME}/sessions"
LOG_DIR="${GROWTH_ENGINE_HOME}/logs"
MAX_AGENTS=8
DEFAULT_MODEL="gpt-4"
# Source once, use everywhere
source "${GROWTH_ENGINE_HOME}/config/growth-engine.conf"
2 AM: The Agent Instruction Revolution
Before: 300-line string concatenations
ANALYTICS_INSTRUCTION="You are an Analytics Agent. You analyze data and "\
"provide insights. You should focus on metrics and KPIs. You must "\
"communicate findings clearly. You need to... (continues for 50 lines)"
After: Clean markdown files
agents/instructions/analytics.md:
# Analytics Specialist Role
You are the Analytics Specialist for the Growth Engine team.
## Core Responsibilities
- Analyze data and metrics
- Identify trends and patterns
- Provide actionable insights
## Communication Protocol
- Start messages with "[Analytics]"
- Use data to support conclusions
- Be concise but thorough
The Modular Architecture
lib/tmux-manager.sh
#!/bin/bash
# Handles all tmux operations
create_session() {
local session_name=$1
tmux new-session -d -s "$session_name" 2>/dev/null || {
log "Session $session_name already exists"
return 1
}
}
create_agent_window() {
local session=$1
local window_id=$2
local agent_name=$3
tmux new-window -t "$session:$window_id" -n "$agent_name"
tmux send-keys -t "$session:$window_id" "clear" C-m
log "Created window for $agent_name"
}
lib/queue-processor.sh
#!/bin/bash
# Manages task queue operations
enqueue_task() {
local task_file=$1
local priority=${2:-normal}
# Validate task
validate_task_format "$task_file" || return 1
# Add to appropriate queue
case $priority in
urgent) mv "$task_file" "$QUEUE_DIR/urgent/" ;;
high) mv "$task_file" "$QUEUE_DIR/high/" ;;
*) mv "$task_file" "$QUEUE_DIR/normal/" ;;
esac
log "Task queued: $(basename $task_file) [Priority: $priority]"
}
The Testing Nightmare
3 AM – First Test Run:
$ ./growth-engine-v2.sh
./growth-engine-v2.sh: line 10: tmux-manager.sh: No such file
./growth-engine-v2.sh: line 11: queue-processor.sh: No such file
./growth-engine-v2.sh: line 12: agent-communicator.sh: No such file
Everything is broken.
Me: "Ah, paths. My old nemesis."
3:15 AM – Path Fix:
# Smart path resolution
GROWTH_ENGINE_HOME="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
export GROWTH_ENGINE_HOME
# Source with absolute paths
source "${GROWTH_ENGINE_HOME}/lib/tmux-manager.sh"
source "${GROWTH_ENGINE_HOME}/lib/queue-processor.sh"
3:30 AM – Second Test:
$ ./growth-engine-v2.sh
Starting Growth Engine v2...
✓ Libraries loaded
✓ Configuration loaded
✗ Failed to read agent instructions
Me: "WHAT NOW?!"
Turns out: Tôi quên tạo instruction files. 🤦♂️
The Beautiful Result
5 AM – Finally Working:
$ tree growth-engine-v2/
growth-engine-v2/
├── growth-engine.sh (198 lines!)
├── lib/
│ ├── tmux-manager.sh (87 lines)
│ ├── agent-communicator.sh (123 lines)
│ ├── queue-processor.sh (156 lines)
│ ├── error-handler.sh (67 lines)
│ └── logger.sh (45 lines)
├── agents/
│ ├── instructions/
│ │ ├── analytics.md
│ │ ├── content.md
│ │ ├── seo.md
│ │ └── ... (5 more)
│ └── configs/
├── config/
│ └── growth-engine.conf
└── logs/
Total: ~700 lines (vs 2000 before)
Clarity: 1000% improved
Unexpected Benefits
1. Debugging Became Pleasant
# Before: "Error somewhere in 2000 lines"
# After:
[ERROR] queue-processor.sh:45 - Invalid task format: missing priority
# Exact file, exact line, exact problem!
2. Feature Addition Became Trivial
# Want to add Slack notifications?
# Just create lib/notifier.sh
notify_slack() {
local message=$1
curl -X POST $SLACK_WEBHOOK -d "text=$message"
}
# Source it in main script - Done!
3. Team Collaboration Possible
Gửi cho junior dev: “Check out queue-processor.sh”
Instead of: “Read lines 800-1200, but also check 1456, and maybe 234”
Lessons From The Trenches
1. Refactor Early, Not When Desperate
2000 lines = 10x harder than 200 lines
“I’ll refactor later” = “I’ll suffer later”
2. Structure Enables Creativity
Clean code = More time for features
Messy code = More time debugging
3. Comments Are Love Letters to Future You
# Bad comment:
# Process stuff
# Good comment:
# Validates task JSON format and checks required fields
# Returns: 0 if valid, 1 if invalid
# Example: validate_task_format "/queue/task-123.json"
The Migration Guide
For those facing their own monoliths:
- Map Dependencies First – What calls what? – What breaks if you move this?
- Extract Configuration – Hardcoded values → config file – One source of truth
- Create Clean Interfaces – Clear function names – Predictable inputs/outputs
- Test Each Module – In isolation first – Then integration
- Document As You Go – Not after – DURING
The 6 AM Victory
$ ./growth-engine-v2.sh start
[INFO] Starting Growth Engine v2.0
[INFO] Loading configuration... ✓
[INFO] Initializing agents... ✓
[INFO] Setting up queues... ✓
[INFO] All systems operational
$ ./growth-engine-v2.sh status
📊 Growth Engine Status
─────────────────────
Agents: 8/8 online
Queue: 3 pending
Uptime: 0h 2m 13s
Health: All green ✓
From chaos to clarity. From spaghetti to structure. From “it works somehow” to “it works beautifully”.
Best part? Adding new features is now fun instead of frightening.
Questions for fellow developers:
- Worst refactoring nightmare của bạn?
- Monolith lớn nhất bạn từng face là bao nhiêu lines?
- Team “refactor often” hay “if it works don’t touch”?
P.S: Line 1337 của old code vẫn còn comment “TODO: Refactor this mess someday”. Someday came. And it was glorious.
Ngày 4: Session Management Hell – When blog posts started having identity crises và tôi phải build cả hệ thống session isolation. Preview: UUID everywhere!