Đạ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)

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:

  1. Single Responsibility (1 function = 1 job)
  2. DRY (Don’t Repeat Yourself… finally)
  3. Modular (plug and play components)
  4. Clear naming (no more process_stuff())
  5. 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:

  1. Map Dependencies First – What calls what? – What breaks if you move this?
  2. Extract Configuration – Hardcoded values → config file – One source of truth
  3. Create Clean Interfaces – Clear function names – Predictable inputs/outputs
  4. Test Each Module – In isolation first – Then integration
  5. 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!

Similar Posts