AI-Assisted Coding
Back to Daily Workflow with AI

Build 2 — Refactor a Messy Codebase

Refactor the provided starter or your own code with AI. Preserve intended behavior and document any deliberate security or validation changes.

115 minutesProject submission
🎯 Free Guest Mode: You are learning for free. Sign in to save your completion progress and quiz answers.

Assignment Instructions

🎯 Hands-on Portfolio Deliverable: Take a legacy monolithic Python application with mixed concerns and SQL injection risks, and systematically refactor it using AI assist while preserving intended behavior except documented security or validation fixes.


The brief

Download the runnable refactoring starter or use your own messy codebase. Preserve documented intended behavior while making explicitly documented security or validation changes. Measure before and after. The snippet below illustrates mixed concerns; it is not the runnable starter. The numbered prompts use this users API as an example: adapt layers, tests, and risk checks to your own project rather than inventing unrelated requirements.

The ugly code

Sample structure (or use your own):

# main.py — 500 lines of mixed concerns

def get_user(id):
    conn = sqlite3.connect('app.db')
    c = conn.cursor()
    c.execute(f"SELECT * FROM users WHERE id = {id}")  # SQL injection!
    row = c.fetchone()
    conn.close()
    if row:
        return {'id': row[0], 'name': row[1], 'email': row[2]}
    return None

def create_user(name, email, password):
    # ...similar mess...

@app.route('/api/users/<id>')
def user_endpoint(id):
    u = get_user(id)
    if u:
        return jsonify(u)
    return jsonify({'error': 'not found'}), 404

# ... 400 more lines like this

Issues:

  • SQL injection.
  • No type hints.
  • No tests.
  • Mixed concerns (DB, routing, business logic).
  • No structure.
  • Long file.

Step 1: Establish baseline (10 min)

Before touching anything:

  • Run the app; verify it works.
  • Manually test each endpoint with curl; note behavior.
  • Run any tests that exist.

Document this baseline. Preserve intended behavior; document and test exceptions made deliberately for security or validation.

Step 2: Plan the refactor (15 min)

Discuss with AI:

"I have a 500-line Flask app that does X, Y, Z. It mixes DB calls, business logic, and routing in single functions. It has SQL injection. No tests. What's the refactor plan? Don't write code yet; just sketch the steps."

AI suggests:

  1. Add type hints + linter.
  2. Add minimal tests for current behavior.
  3. Extract DB layer (use SQLAlchemy properly).
  4. Extract service layer.
  5. Use parameterized queries (fix SQL injection).
  6. Split files by domain.
  7. Add validators (Pydantic).

Approve the plan; adjust if needed.

Step 3: Add tests for current behavior (20 min)

CRITICAL FIRST STEP. Without tests, refactoring is gambling.

"Generate pytest tests for the current behavior of these endpoints. Don't change the code; just write tests that pass against current code. Cover all endpoints; include the buggy behavior for now (we'll fix that later, with a different test for the fix)."

Tests pass = baseline locked.

Step 4: Fix critical bugs (e.g., SQL injection) (15 min)

"Fix the SQL injection in get_user. Use parameterized queries. Same signature; same return. The existing tests should still pass."

Tests verify the fix doesn't break behavior.

Step 5: Extract DB layer (20 min)

"For the example user app, extract DB code into a UserRepository class (src/repositories/user_repository.py). Use SQLAlchemy. Keep the existing function names as wrappers if needed, OR update all callers. Tests should still pass."

AI refactors; you verify tests pass.

If tests fail: something behavior-changed. Investigate; don't continue.

Step 6: Extract service layer (15 min)

"For the example user app, move business logic out of routes into a UserService class. Routes call services; services call repositories. Tests should still pass."

Layered architecture emerging.

Step 7: Add validators (10 min)

"Add Pydantic schemas for user input. Use them in route signatures. Validation errors should return 422."

Tests for invalid input should be added (and pass).

Step 8: Final polish (15 min)

  • Type hints everywhere.
  • Linter passes.
  • Run all tests.
  • Update README.

Step 9: Measure

Before:
- Lines: 500
- Files: 1
- Cyclomatic complexity (highest): 12
- Type hint coverage: 0%
- Test coverage: 0%
- Linter issues: 47

After:
- Lines: 600 (more files, more boilerplate, but better)
- Files: 8
- Cyclomatic complexity (highest): 5
- Type hint coverage: 100%
- Test coverage: 85%
- Linter issues: 0
- Security issues: 0 (SQL injection fixed)

Use radon for complexity; coverage.py for coverage.

Deliverables

  • Refactored codebase.
  • Baseline characterization tests pass before refactoring. Afterward, tests for unchanged behavior and new tests for documented security or validation changes pass; update tests that asserted unsafe behavior.
  • Metrics: before/after comparison.
  • A REFACTOR.md documenting:
    • The refactor plan.
    • Which steps used AI heavily.
    • Where AI got it right; where it didn't.
    • Total time taken.

What you'll learn

  • The "tests first" refactor discipline.
  • AI's strength in mechanical refactors (extract function, rename).
  • AI's struggle with semantic refactors ('make it cleaner').
  • Layered architecture patterns.
  • Measuring code quality improvements.

Stretch

  • Add async (convert sync Flask to async FastAPI).
  • Add Docker.
  • Deploy.

📊 100-Point Evaluation Rubric

Evaluation DimensionWeightTarget Standard (Pass ≥ 80%)
1. Zero Behavioral Regressions25 ptsCharacterization test suite established before refactoring. All existing functionality and endpoints work seamlessly post-refactor while preserving intended behavior except documented security or validation fixes.
2. Security & Bug Elimination25 ptsIdentify and address risks applicable to the chosen project, such as SQL injection or invalid input; document and test intentional behavior changes. For a project with no applicable issue, explain the checks performed.
3. Layered Architecture25 ptsSeparate relevant responsibilities clearly for the chosen project; repository, service and routing layers are examples where applicable, not mandatory class names. Explain complexity changes.
4. Metrics & REFACTOR.md25 ptsCompleted REFACTOR.md with before-and-after metrics (lines, files, cyclomatic complexity, test coverage) and detailed analysis of AI strengths/limitations during refactoring.

📤 Submission Instructions & Guidelines

  1. Deliverable Formats:
    • Option A (Recommended): Upload a single .zip archive containing your refactored project directory, tests, and REFACTOR.md.
    • Option B: Upload your REFACTOR.md and paste your GitHub repository or Pull Request link in the Submission Notes box below.
  2. Pre-Submission Verification:
    • Execute your full test suite to confirm intended behavior is preserved, apart from the documented security or validation fixes.
    • Run a linter (e.g., ruff or flake8) to verify zero style/syntax issues.
    • Validate before/after metrics documented in REFACTOR.md.
  3. Submit Your Work:
    • Use the file dropzone below to upload your .zip file (up to 50MB).
    • Add any notes in the Submission Notes box.
    • Click Submit Assignment to record your submission and mark this lesson complete.
Checking your access…