In this lesson, you will learn how to systematically transform brittle, "spaghetti-code" legacy scripts into robust, maintainable units of automation.
The Problem: The "Script Graveyard"
Legacy scripts often suffer from "hard-coded rot": absolute paths, lack of error checking, and a "monolithic" structure where everything happens in one long, linear flow. When these scripts break, they are hard to debug because they lack modularity. Refactoring is the process of restructuring this existing code without changing its external behavior.
Step 1: Encapsulation via Functions
The first step is breaking the linear flow into discrete, named functions. Instead of a 200-line file that runs from top to bottom, move logic into blocks.
Before (Brittle):
# A dangerous, monolithic script
rm -rf /tmp/my_data/*
cp /var/log/app.log /tmp/my_data/
# What if the cp fails? The script keeps running anyway!
After (Refactored):
# A maintainable unit
clean_directory() {
local target_dir="/tmp/my_data"
[[ -d "$target_dir" ]] || mkdir -p "$target_dir"
rm -rf "${target_dir:?}"/* || return 1
}
archive_logs() {
cp /var/log/app.log /tmp/my_data/ || { echo "Copy failed"; return 1; }
}
# Main execution logic
clean_directory && archive_logs
Step 2: Adding Guardrails
Legacy scripts often assume the environment is perfect. Modern refactoring adds "defensive" checks. Use set -euo pipefail at the top of your scripts to ensure they stop immediately if a command fails, a variable is unset, or a pipe encounters an error. This prevents the "cascading failure" common in old scripts where one bad command causes a chain reaction of damage.
Step 3: Decoupling Configuration
Stop hard-coding variables inside your logic. Move paths, usernames, and thresholds to a configuration file or environment variables. This allows the script to be reused across different servers without modifying the source code itself. By treating your script as a unit—input goes in, work happens, status code comes out—you make it testable and predictable.
Key Takeaway: Refactoring is about moving from "linear chaos" to "modular reliability" by wrapping brittle commands in error-checked functions and abstracting configuration.