AI Code Isn't Production-Ready: My 3-Hour Refactoring Session with Claude Code
On a Sunday morning, I reviewed a PR that had been created by Claude Code via GitHub Actions for implementing a scalable scheduled notification job system for my Vapor-based backend app. The core idea was to implement foundational logic on the server side to identify users who had signed up for push notifications of various event types (e.g., start of week, mid-week reminders for completing tasks) at specific days and times based on local timezones. I also wanted this new system to be scalable so that future push notification use cases could be easily added.
When I reviewed the code written by Claude, I identified many areas that needed revisions. I want to share what those were here. Documenting these areas for improvement might help us pay more attention to similar issues in the future and avoid accumulating tech debt.
TL;DR
After ~10 iterations, I transformed the initial code into a more readable, maintainable, well-tested system that’s ready for production use.
The 3-Hour Refactoring Journey
Code Quality, Performance & Architecture Improvements
1. Eliminate Unused Parameters
Claude had implemented a builder pattern with 4 methods. I noticed that all methods had an optional argument, but only 3 of them actually used it. I asked it to change to using an enum with associated values, which worked better.
Before:
// Claude's initial approach - notice incompleteTasks parameter in all methods
func buildNewWeekPayload(incompleteTasks: Int?) -> NotificationPayload {
// incompleteTasks was never used here!
return NotificationPayload(
type: "newWeekStart",
title: "New Week Starts Today!",
message: "It's Sunday - time to get started with your goals!"
)
}
func buildMidWeekPayload(incompleteTasks: Int?) -> NotificationPayload {
let tasks = incompleteTasks ?? 0
return NotificationPayload(
type: "midWeekReminder",
title: "Mid-Week Check-In 📋",
message: "You have \(tasks) tasks left to complete this week."
)
}
After:
// Refactored with enum and associated values
enum PayloadContext {
case newWeekStart
case midWeekReminder(incompleteTasks: Int) // Only carries data when needed
case weeklySummary
case monthlySummary
case everyday5pmTestNotification
}
static func buildPayload(for context: PayloadContext) -> NotificationPayload {
switch context {
case .newWeekStart:
return NotificationPayload(
type: "newWeekStart",
title: "New Week Starts Today!",
message: "It's Sunday - time to get started with your goals!"
)
case .midWeekReminder(let incompleteTasks):
let taskWord = incompleteTasks == 1 ? "task" : "tasks"
return NotificationPayload(
type: "midWeekReminder",
title: "Mid-Week Check-In 📋",
message: "You have \(incompleteTasks) \(taskWord) left to complete this week.",
badge: incompleteTasks
)
// ... other cases
}
}
2. Remove Force Unwraps
This wasn’t a major issue, but Claude had used a few force-unwraps for known TimeZone initialization attempts (e.g., TimeZone(identifier: "UTC")!
). I asked it to avoid doing that to eliminate the possibility of crashes in production.
Before:
static func createUTCCalendar() -> Calendar {
var calendar = Calendar.current
calendar.timeZone = TimeZone(identifier: "UTC")! // Force unwrap - risky!
return calendar
}
static func createCalendar(for timezone: String) -> Calendar {
var calendar = Calendar.current
calendar.timeZone = TimeZone(identifier: timezone) ?? TimeZone(identifier: "UTC")!
return calendar
}
After:
static func createUTCCalendar() -> Calendar {
var calendar = Calendar.current
// Safe approach - use a constant that we know exists
calendar.timeZone = TimeZone(identifier: Constants.utcTimezoneIdentifier) ?? TimeZone.current
return calendar
}
static func createCalendar(for timezone: String, logger: Logger? = nil) -> Calendar {
if let tz = timezone.asTimeZone {
var calendar = Calendar.current
calendar.timeZone = tz
return calendar
} else {
logger?.warning("⚠️ Invalid timezone \(timezone), using UTC")
return createUTCCalendar() // Reuse the safe UTC calendar creation
}
}
3. Code Reusability
Claude had used duplicate logic for getting default/backup calendar setup. I requested to reuse one of the related methods in another, and it refactored to remove the code duplication.
Before:
static func createUTCCalendar() -> Calendar {
var calendar = Calendar.current
calendar.timeZone = TimeZone(identifier: "UTC") ?? TimeZone.current
return calendar
}
static func createCalendar(for timezone: String) -> Calendar {
if let tz = TimeZone(identifier: timezone) {
var calendar = Calendar.current
calendar.timeZone = tz
return calendar
} else {
// Duplicate logic here!
var calendar = Calendar.current
calendar.timeZone = TimeZone(identifier: "UTC") ?? TimeZone.current
return calendar
}
}
After:
static func createCalendar(for timezone: String, logger: Logger? = nil) -> Calendar {
if let tz = timezone.asTimeZone {
var calendar = Calendar.current
calendar.timeZone = tz
return calendar
} else {
logger?.warning("⚠️ Invalid timezone \(timezone), using UTC")
return createUTCCalendar() // Reuse existing method
}
}
4. Concurrent Processing
Claude had implemented a series of API calls sequentially rather than concurrently. I requested to use withTaskGroup
for parallel user processing, which resulted in better performance when processing multiple users simultaneously.
Before:
// Sequential processing - slow!
for userID in userIDToNotificationSetting.keys {
await processUserNotifications(
userID: userID,
eventType: eventType,
targetTimeZones: targetTimeZones,
context: context
)
}
After:
// Concurrent processing - much faster!
await withTaskGroup(of: Void.self) { group in
for userID in userIDToNotificationSetting.keys {
group.addTask {
await processUserNotifications(
userID: userID,
eventType: eventType,
targetTimeZones: targetTimeZones,
context: context
)
}
}
}
Testing & Quality Assurance
After asking Claude to write a comprehensive set of unit tests, I had to run the tests myself to make sure they all passed. Some of them didn’t, so I needed to fix them myself or ask Claude to fix them, which usually took only 1 additional iteration.
Example of a test that failed initially:
func testTimezoneUtilities_invalidInputRecovery() {
// Claude assumed EST was invalid, but it's actually valid!
let validated = TimezoneUtilities.validateTimezone("EST")
XCTAssertEqual(validated, "UTC") // This failed because EST is valid
}
Documentation
1. Dev Comments
Claude’s initial code wasn’t always easy to understand. For instance, some of the methods it wrote accepted a few more nice-to-have arguments (e.g., logger). I asked Claude to write more detailed dev comments on internal or public methods. After improving, all methods became much easier to understand in terms of their intent.
Before:
static func sendPushNotifications(
eventType: NotificationSetting.EventType,
targetTimeZones: [TimezoneIdentifier],
context: QueueContext
) async {
// minimal comments...
}
After:
/// Sends push notifications to all eligible users in the specified timezones
///
/// This is the main entry point for sending timezone-aware notifications. It:
/// 1. Queries users who have opted in for the specific event type
/// 2. Filters devices by target timezones
/// 3. Processes each user concurrently for performance
/// 4. Skips users based on event-specific criteria (e.g., no incomplete tasks)
///
/// - Parameters:
/// - eventType: The type of notification event (newWeekStart, midWeekReminder, etc.)
/// - targetTimeZones: Array of timezone identifiers where it's currently the target time
/// Example: ["America/New_York", "America/Chicago"] if it's 9 AM there
/// - context: The queue context providing access to database and logger
static func sendPushNotifications(
eventType: NotificationSetting.EventType,
targetTimeZones: [TimezoneIdentifier],
context: QueueContext
) async {
// implementation...
}
2. README and Claude.md
After finalizing all changes, I asked Claude to update the README and Claude.md to document how the timezone-sensitive notification system works with diagrams. It did a pretty good job, but I found that one section used SQL-style model definitions vs. Swift Fluent models (which is what my Vapor server code used). I asked it to update the section, and afterwards, it looked accurate.
Before (in CLAUDE.md):
-- Claude initially documented with SQL
CREATE TABLE devices (
id UUID PRIMARY KEY,
user_id UUID REFERENCES users(id),
push_token TEXT,
timezone TEXT,
system TEXT,
os_version TEXT
);
After:
// Corrected to show actual Swift Fluent models
final class Device: Model, Content {
static let schema = "devices"
@ID(key: .id) var id: UUID?
@Parent(key: "user_id") var user: User
@OptionalField(key: "push_token") var pushToken: String?
@Field(key: "timezone") var timeZone: String // e.g., "America/New_York"
@OptionalField(key: "system") var system: String?
@OptionalField(key: "os_version") var osVersion: String?
}
Summary of Impacts from the Session
This 3-hour refactoring session highlighted several key takeaways about working with AI-generated code:
- AI code needs human review: While Claude produced functional code, it required careful review to catch subtle issues like unnecessary force unwraps, sequential processing where concurrent would be better, and unused parameters.
- Iterative refinement works: Through ~10 iterations, we transformed rough code into production-ready systems. Each iteration built upon the previous improvements, showing that AI + human collaboration can be highly effective when approached systematically.
- Documentation is crucial: Asking Claude to add comprehensive documentation, including dev comments and system diagrams, made the codebase much more maintainable. However, domain-specific details (like using Swift Fluent instead of SQL syntax) still needed human correction.
- Test-driven validation: Running the generated tests myself was essential. Some tests failed initially, but fixing them was straightforward with Claude’s help, usually requiring just one additional iteration.
The session reinforced that while AI can accelerate development significantly, the best results come from treating it as a collaborative partner rather than a black box. By actively reviewing, testing, and iterating on the generated code, we can leverage AI’s speed while maintaining high code quality standards.