🎯 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:
- Add type hints + linter.
- Add minimal tests for current behavior.
- Extract DB layer (use SQLAlchemy properly).
- Extract service layer.
- Use parameterized queries (fix SQL injection).
- Split files by domain.
- 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.mddocumenting:- 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 Dimension | Weight | Target Standard (Pass ≥ 80%) |
|---|---|---|
| 1. Zero Behavioral Regressions | 25 pts | Characterization 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 Elimination | 25 pts | Identify 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 Architecture | 25 pts | Separate 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.md | 25 pts | Completed 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
- Deliverable Formats:
- Option A (Recommended): Upload a single
.ziparchive containing your refactored project directory, tests, andREFACTOR.md. - Option B: Upload your
REFACTOR.mdand paste your GitHub repository or Pull Request link in the Submission Notes box below.
- Option A (Recommended): Upload a single
- 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.,
rufforflake8) to verify zero style/syntax issues. - Validate before/after metrics documented in
REFACTOR.md.
- Submit Your Work:
- Use the file dropzone below to upload your
.zipfile (up to 50MB). - Add any notes in the Submission Notes box.
- Click Submit Assignment to record your submission and mark this lesson complete.
- Use the file dropzone below to upload your