Refactoring and Optimization Workflows: Turning Messy Code into Clean, Fast Systems An engineer demonstrated how Claude Code can refactor messy code into clean, modular systems while preserving exact behavior, using examples including an order total calculator and a logger. The refactoring process broke a monolithic function into smaller, reusable units and identified design patterns like Singleton for thread-safe log consistency. You have working code that you dread touching. Variable names make no sense. The same logic appears in three places. It's slow on large inputs, and adding a new feature feels fragile. Refactoring fixes that — and Claude Code makes it faster. This guide covers how to clean up, restructure, and optimize code without breaking what already works: spotting design patterns in spaghetti logic, improving performance, balancing simplicity against efficiency, and applying all of it to a real-world web scraper. Refactoring improves existing code without changing its external behavior. Not rewriting from scratch — making the code cleaner, more readable, and easier to maintain while keeping exactly what it does. Three rules to hold throughout any refactor: Original works, but messy : python def calculate order total items, discount rate, tax rate : total = 0 for item in items: price = item 'price' quantity = item 'quantity' total += price quantity discount = total discount rate total = total - discount tax = total tax rate total = total + tax return round total, 2 Problems: repeated variable reassignment, all logic in one function, no docstrings, hard to unit-test. Prompt: "Refactor this order total function for clarity and maintainability. Break it into smaller, reusable functions. Preserve the exact same behavior." Claude's refactored version: python def calculate order total items, discount rate=0.0, tax rate=0.0 : """Calculate the total order price with discount and tax applied.""" subtotal = calculate subtotal items discounted = apply discount subtotal, discount rate total = apply tax discounted, tax rate return round total, 2 def calculate subtotal items : """Compute subtotal based on item prices and quantities.""" return sum item 'price' item 'quantity' for item in items def apply discount subtotal, discount rate : """Apply discount to subtotal.""" return subtotal 1 - discount rate def apply tax amount, tax rate : """Apply tax to the discounted amount.""" return amount 1 + tax rate Validation test: items = {"price": 10.0, "quantity": 2}, {"price": 5.0, "quantity": 3} Original inline calculation original = 0 for item in items: original += item "price" item "quantity" original -= original 0.1 original += original 0.07 original = round original, 2 Refactored new total = calculate order total items, discount rate=0.1, tax rate=0.07 print original == new total Expected: True True confirms behavior is preserved, structure improved. | Aspect | Before | After | |---|---|---| | Length | Single long function | Modular functions | | Responsibility | Mixed | Each function handles one task | | Extensibility | Hard to modify | Easy to adjust discount or tax rules | | Testability | Hard to isolate | Unit-test friendly | Patterns often emerge in code before they're named. Claude can recognize them — Singleton, Factory, Observer — even when they developed unintentionally, and refactor them into explicit, robust implementations. Original logger: python class Logger: def init self : self.logs = def log self, message : self.logs.append message print f" LOG : {message}" logger = Logger logger.log "Application started." This works in a single module. Import it into multiple modules and each gets its own instance, breaking log consistency. Prompt: "This logger creates new instances when imported in different modules. Identify the design pattern it resembles and refactor it properly." Claude's analysis: "This should be a singleton — one shared instance across the application. The basic new override is the common introduction, but it's not thread-safe. In multi-threaded environments, two threads can check instance is None simultaneously and both create an instance." Thread-safe Singleton: python import threading class Logger: instance = None lock = threading.Lock def new cls : if cls. instance is None: with cls. lock: Double-checked locking: re-check after acquiring the lock if cls. instance is None: cls. instance = super Logger, cls . new cls cls. instance.logs = return cls. instance def log self, message : self.logs.append message print f" LOG : {message}" Verification: logger1 = Logger logger2 = Logger logger1.log "Application started." logger2.log "Processing data..." print f"Same instance? {logger1 is logger2}" True One important caveat Claude should flag: for async frameworks FastAPI, aiohttp, asyncio-based servers , threading.Lock doesn't work correctly. Use asyncio.Lock with a factory method instead — new can't be awaited. For most logging scenarios, a module-level singleton a plain module-level instance is simpler and avoids the problem entirely. Optional: Factory pattern class LoggerFactory: logger = None lock = threading.Lock @staticmethod def get logger : if LoggerFactory. logger is None: with LoggerFactory. lock: if LoggerFactory. logger is None: LoggerFactory. logger = Logger return LoggerFactory. logger Use this when you want to separate creation from usage — useful when the logger initialization takes arguments or depends on configuration. | Pattern | Symptom in Code | Benefit | |---|---|---| | Singleton | Shared-state class instantiated repeatedly | Centralized control, consistent resource access | | Factory | Objects created manually in multiple places | Clean separation of creation and usage | | Observer | Event-driven callbacks without structure | Decoupled, maintainable event handling | Optimize where you have a measured bottleneck. Claude helps identify inefficiencies early, explains root causes, and proposes targeted improvements. Original O n² : python def count unique words file path : unique words = with open file path, "r", encoding="utf-8" as f: for line in f: words = line.strip .split for word in words: if word not in unique words: O n scan per word unique words.append word return len unique words Each membership check scans the entire list. On a large file this compounds quickly. Prompt: "This function becomes very slow with large text files. Analyze and optimize it without changing its behavior." Claude's reasoning: "List membership checks are O n . Replace the list with a set for O 1 average lookups. Also normalize to lowercase for consistent deduplication." Optimized O n : python def count unique words file path : """Count unique words in a text file using a set for O 1 lookups.""" unique words = set with open file path, "r", encoding="utf-8" as f: for line in f: for word in line.strip .split : unique words.add word.lower return len unique words On a 10MB file the difference is minutes vs. milliseconds. | Focus | Problem | Recommendation | Impact | |---|---|---|---| | Data structure | List for membership checks | Replace with set | O n → O 1 average | | Algorithm | Nested iteration | Simplify loop logic | Better scalability | | Memory | Unbounded list growth | Hash-based collection | Reduced storage | | I/O | Synchronous DB calls in async framework | Async or connection pooling | Faster throughput | Not every optimization is worth its complexity. Sometimes the simpler code is the better choice. Simple, readable version: python def filter even numbers numbers : """Return all even numbers from the list.""" return n for n in numbers if n % 2 == 0 Prompt: "Can this be optimized for performance if it needs to handle millions of integers, or should I keep it simple?" Claude's reasoning: "List comprehensions run in C and are already fast. For most cases this is optimal. If your dataset exceeds available memory, a generator reduces memory overhead by yielding one element at a time." Memory-efficient generator: python def filter even numbers stream numbers : """Yield even numbers lazily for large datasets.""" for n in numbers: if n % 2 == 0: yield n Trade-off: python import time nums = list range 10 000 000 start = time.time filter even numbers nums print "List comprehension:", round time.time - start, 3 , "s" start = time.time list filter even numbers stream nums print "Generator:", round time.time - start, 3 , "s" | Approach | Performance | Memory | Best Use | |---|---|---|---| | List comprehension | Fast for small-medium | Stores all results | General purpose | | Generator | Slightly slower iteration | Streams one at a time | Large datasets, pipelines | | NumPy vectorized | Very fast for numerics | Moderate | Numerical heavy lifting | Simplify where clarity yields lasting value. Optimize only where you have a measured bottleneck. A complete example: take a slow synchronous scraper and turn it into a concurrent, fault-tolerant pipeline. python import time import requests from bs4 import BeautifulSoup HEADERS = {"User-Agent": "MasteringClaudeCode/1.0"} def fetch url, timeout=10 : resp = requests.get url, headers=HEADERS, timeout=timeout resp.raise for status return resp.text def parse html : soup = BeautifulSoup html, "html.parser" title = soup.title.string.strip if soup.title else "" h1 = soup.find "h1" .get text strip=True if soup.find "h1" else "" return {"title": title, "h1": h1} def scrape urls : results = for url in urls: html = fetch url data = parse html data "url" = url results.append data time.sleep 0.2 polite delay return results Problems: synchronous waits for each request , no retries, no concurrency, fragile error handling. python import asyncio import aiohttp from bs4 import BeautifulSoup import random HEADERS = {"User-Agent": "MasteringClaudeCode/1.0"} MAX CONCURRENCY = 10 REQUEST TIMEOUT = aiohttp.ClientTimeout total=12, connect=5 async def fetch session, url, attempt=1, max attempts=3 : try: async with session.get url as resp: resp.raise for status raises ClientResponseError for 4xx/5xx return await resp.text except aiohttp.ClientError, asyncio.TimeoutError : if attempt = max attempts: raise backoff = 2 attempt - 1 + random.uniform 0, 0.25 await asyncio.sleep backoff return await fetch session, url, attempt + 1, max attempts def parse fast html : soup = BeautifulSoup html, "lxml" faster C-based parser title = soup.title.string.strip if soup.title else "" h1 = soup.find "h1" .get text strip=True if soup.find "h1" else "" return {"title": title, "h1": h1} async def scrape one semaphore, session, url : async with semaphore: try: html = await fetch session, url data = parse fast html data "url" = url return url, data, None except Exception as e: return url, None, str e async def scrape urls : semaphore = asyncio.Semaphore MAX CONCURRENCY connector = aiohttp.TCPConnector limit=MAX CONCURRENCY async with aiohttp.ClientSession headers=HEADERS, timeout=REQUEST TIMEOUT, connector=connector as session: tasks = scrape one semaphore, session, url for url in urls results = for coro in asyncio.as completed tasks : url, data, err = await coro results.append {"url": url, "error": err} if err else data return results Key changes Claude explains: N latency to roughly latency . TCPConnector reuses connections, reducing handshake overhead. lxml is a C-based HTML parser — significantly faster than html.parser for large pages. Note onThe optimized version calls raise for status : resp.raise for status to handle 4xx/5xx responses. This is the correct pattern — aiohttp.ClientResponseError requires internal request info and history arguments and should not be constructed manually. Run it: URLS = "https://example.com", "https://httpbin.org/html" items = asyncio.run scrape URLS | Optimization | What Changed | Why It Matters | |---|---|---| | Concurrency | Single-threaded → asyncio + semaphore | Overlaps I/O, wall time ≈ one request latency | | Connection reuse | New TCP per request → pooled | Reduces handshake overhead | | Timeouts + backoff | None → explicit + exponential | Faster failure, automatic recovery | | Parser | html.parser → lxml | C-based, measurably faster on large HTML | | Error handling | Exceptions crash pipeline → captured per-URL | Pipeline survives partial failures | Token costs are real for teams running Claude at scale. A few practices keep them under control. Current API pricing May 2026, per million tokens : | Model | Input | Output | Best For | |---|---|---|---| | Haiku 4.5 | $1.00 | $5.00 | Simple refactors, repetitive tasks | | Sonnet 4.6 | $3.00 | $15.00 | General coding — best cost/quality ratio | | Opus 4.7 | $5.00 | $25.00 | Complex architecture, deep analysis | Prompt caching cuts input costs by up to 90%. The Batch API cuts everything 50%. Combined, costs can drop by up to 95% for high-volume workloads. A rough cost estimator: php def estimate cost prompt: str, response tokens: int, model: str = "sonnet" - float: """Estimate API cost for a single request USD .""" pricing = { "haiku": {"in": 1.00, "out": 5.00}, "sonnet": {"in": 3.00, "out": 15.00}, "opus": {"in": 5.00, "out": 25.00}, } ~4 chars per token in tokens = max 1, len prompt // 4 p = pricing model return round in tokens / 1 000 000 p "in" + response tokens / 1 000 000 p "out" , 6 prompt = "Refactor this 200-line Python class for readability." print f"Estimated cost: ${estimate cost prompt, response tokens=500, model='sonnet' }" Cost-control habits: | Practice | Why It Helps | |---|---| | Match model to task complexity | Haiku for simple refactors; Opus only for deep architecture work | | Keep a 2-4 sentence project anchor | Reduces repeated context tokens across turns | | Ask for only what you need | "Return only the refactored function" cuts unnecessary output | | Batch similar requests | Amortizes system tokens; 50% off with the Batch API | | Trim context to relevant files | Large irrelevant context dilutes focus and burns tokens | Refactoring and optimization are small, intentional improvements that compound over time. With Claude Code you can decompose tangled functions into clean modules, name the patterns that already exist in your code, replace O n² lookups with O 1 data structures, and scale a synchronous scraper into a concurrent pipeline — without breaking what already works. Try this: Pick a messy function from your own codebase. Prompt Claude: "Refactor this for clarity and maintainability. Preserve all behavior. Break it into smaller, reusable pieces." Run the validation test. Then profile before and after on real data if performance matters. Acknowledgment:Based on official Anthropic documentation and community research. Technical details reflect the state of Claude Code as of May 2026.