Fatskills
Practice. Master. Repeat.
Study Guide: Agile-and-Scrum: Technical Debt, Refactoring, and Continuous Integration - Zero-Fluff Agile Survival Guide
Source: https://www.fatskills.com/wireless/chapter/agile-and-scrum-technical-debt-refactoring-and-continuous-integration-zero-fluff-agile-survival-guide

Agile-and-Scrum: Technical Debt, Refactoring, and Continuous Integration - Zero-Fluff Agile Survival Guide

By Fatskills Exam Guides Team — the exam nerds behind 28,500+ quizzes and 2.1M practice questions across 500+ global exams.

⏱️ ~10 min read

Technical Debt, Refactoring, and Continuous Integration: Zero-Fluff Agile Survival Guide

(Scrum Guide 2020 Edition)


1. What This Is & Why It Matters

You’re a Scrum Team Developer. Your Product Owner just dropped a "small" feature request—"Just tweak the login flow to support SSO, but keep the old one working for legacy users." You glance at the codebase and see:

  • A 500-line AuthService class with methods like login_v1(), login_v2(), and login_v3()—each copy-pasted with minor tweaks.
  • No tests for the last two versions.
  • A Jenkins pipeline that runs tests only if someone remembers to trigger it manually.
  • A TODO comment from 2021: "Refactor this mess someday."

This is technical debt—the invisible tax on your future work. Ignore it, and: - Your velocity slows to a crawl (every "small" change takes 3x longer). - Bugs multiply (because untested, duplicated code hides landmines). - Morale plummets (no one wants to touch the "legacy" parts).

Refactoring is how you pay down that debt. Continuous Integration (CI) is your early-warning system—it tells you before debt becomes a crisis.

Why this matters in production: - Without CI: You merge broken code, and QA (or worse, customers) find it. Downtime = lost revenue. - Without refactoring: Your codebase becomes a Jenga tower—one wrong pull request collapses it. - Without tracking debt: Your Sprint Planning turns into a guessing game. "Why did this take 3 days instead of 1?"

Real-world scenario: You’re on a team maintaining a 5-year-old e-commerce platform. The checkout flow is a 2,000-line PHP file with 15 global variables. The CTO wants to add Apple Pay. Do you: A) Hack in Apple Pay and pray? B) Refactor the checkout flow first, then add Apple Pay?

This guide gives you the tools to choose B—and justify it to your Product Owner.


2. Core Concepts & Components

? Technical Debt

  • Definition: The cost of future rework caused by choosing a quick-and-dirty solution now instead of a better approach.
  • Production insight: Debt compounds like credit card interest. A 1-hour shortcut today might cost 10 hours next quarter.
  • Types:
  • Code debt: Duplicated logic, magic numbers, poor naming.
  • Test debt: Missing unit/integration tests.
  • Design debt: Tight coupling, circular dependencies.
  • Documentation debt: Outdated READMEs, undocumented APIs.
  • Infrastructure debt: Manual deployments, hardcoded IPs.

? Refactoring

  • Definition: Improving the internal structure of code without changing its external behavior.
  • Production insight: Refactoring is not a "nice-to-have"—it’s how you keep velocity high. Think of it like changing the oil in a car: skip it, and the engine seizes.
  • Key principles:
  • Red-Green-Refactor: Write a failing test (Red), make it pass (Green), then clean up (Refactor).
  • Boy Scout Rule: Leave the codebase cleaner than you found it (e.g., rename one variable per PR).
  • Small steps: Refactor in tiny, safe increments (e.g., extract one method at a time).

? Continuous Integration (CI)

  • Definition: The practice of merging code changes into a shared branch frequently (at least daily), with automated builds and tests.
  • Production insight: CI is your immune system. Without it, broken code spreads like a virus.
  • CI Pipeline Components:
  • Build: Compile code, install dependencies.
  • Test: Run unit, integration, and static analysis tests.
  • Artifact: Package deployable assets (e.g., Docker images, JARs).
  • Notify: Alert the team on failure (Slack, email).
  • CI vs. CD:
  • CI: "Does the code work?" (Automated tests)
  • CD: "Can we deploy this safely?" (Automated deployments to staging/prod)

? Code Smells (Signs of Technical Debt)

Smell Example Fix
Duplicated code Copy-pasted calculateTax() in 3 files Extract to a shared TaxCalculator class
Long method 200-line processOrder() Split into validateOrder(), calculateTotals(), etc.
Large class UserService with 50 methods Split into UserAuthService, UserProfileService
Magic numbers if (status == 3) { ... } Replace with const STATUS_ACTIVE = 3
Shotgun surgery Changing discountRate requires edits in 5 files Centralize logic in one place

? Refactoring Techniques

Technique When to Use Example
Extract Method A method does too much Split processOrder() into validateOrder() + chargeCustomer()
Rename Variable Poorly named variable int x-int userAge
Replace Conditional with Polymorphism Complex if-else chains Replace if (userType == "ADMIN") with AdminUser class
Introduce Parameter Object Method has too many params Replace calculateTotal(price, tax, discount, shipping) with calculateTotal(Order order)
Move Method Method belongs in another class Move sendEmail() from User to EmailService

? CI Tools & Terms

Tool/Term Purpose Example
Jenkins Self-hosted CI server pipeline { agent any; stages { stage('Test') { sh 'npm test' } } }
GitHub Actions Cloud-based CI/CD .github/workflows/test.yml
GitLab CI Built-in CI/CD for GitLab .gitlab-ci.yml
SonarQube Static code analysis Detects bugs, vulnerabilities, and code smells
Trunk-Based Development Everyone commits to main (or trunk) daily No long-lived feature branches
Feature Flags Toggle features without deploying if (featureFlags.isEnabled("newCheckout")) { ... }

3. Step-by-Step Hands-On: Paying Down Debt with CI

Prerequisites

  • A GitHub repository with a simple app (e.g., a Node.js/Express API or Python Flask app).
  • A basic test suite (e.g., Jest for Node, pytest for Python).
  • GitHub Actions enabled (free for public repos).

Goal:

Set up a CI pipeline that:
1. Runs tests on every push.
2. Blocks merges if tests fail.
3. Reports code coverage.

Step 1: Add a GitHub Actions Workflow

Create .github/workflows/test.yml:

name: CI Pipeline
on: [push, pull_request]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Set up Node.js
        uses: actions/setup-node@v4
        with:
          node-version: 20
      - name: Install dependencies
        run: npm install
      - name: Run tests
        run: npm test
      - name: Run linter
        run: npm run lint
      - name: Check code coverage
        run: npm run coverage
        env:
          CI: true

Step 2: Add a Test Script to package.json

{
  "scripts": {
    "test": "jest --coverage",
    "lint": "eslint .",
    "coverage": "jest --coverage --watchAll=false"
  }
}

Step 3: Add a Failing Test (to Simulate Debt)

Create tests/auth.test.js:

test("login should return 401 for invalid credentials", () => {
  const response = login("wrong", "password");
  expect(response.status).toBe(401); // This will fail if `login()` doesn't handle errors
});

Step 4: Push and Watch CI Fail

git add .
git commit -m "Add failing test to simulate debt"
git push
  • Go to GitHub-Actions to see the pipeline fail.
  • Why this matters: CI caught the debt before it merged.

Step 5: Refactor to Fix the Debt

Update src/auth.js:

function login(username, password) {
  if (!username || !password) {
    return { status: 400, error: "Missing credentials" }; // Fix: Handle missing inputs
  }
  if (username !== "admin" || password !== "secret") {
    return { status: 401, error: "Invalid credentials" }; // Fix: Return 401 for invalid creds
  }
  return { status: 200, token: "abc123" };
}

Step 6: Verify CI Passes

git add .
git commit -m "Fix auth logic to handle invalid credentials"
git push
  • Check GitHub Actions: Pipeline should now pass.
  • Production insight: This is how you prove refactoring didn’t break anything.

Step 7: Add a Code Coverage Threshold

Update jest.config.js:

module.exports = {
  collectCoverage: true,
  coverageThreshold: {
    global: {
      branches: 80,
      functions: 80,
      lines: 80,
      statements: 80
    }
  }
};
  • Why this matters: Forces you to write tests for new code. If coverage drops below 80%, the pipeline fails.

4.-Production-Ready Best Practices

? Security

  • Never hardcode secrets in CI configs. Use GitHub Secrets or AWS Parameter Store. yaml env: DB_PASSWORD: ${{ secrets.DB_PASSWORD }} #-Good DB_PASSWORD: "hunter2" #-Bad
  • Scan for vulnerabilities in dependencies. Use npm audit or snyk test.
  • Restrict CI permissions. GitHub Actions should have the least privilege needed (e.g., no admin access).

? Cost Optimization

  • Cache dependencies in CI. Reduces build time and costs. ```yaml
  • name: Cache node modules uses: actions/cache@v3 with: path: node_modules key: ${{ runner.os }}-node-${{ hashFiles('package-lock.json') }} ```
  • Use spot instances for CI runners (if self-hosting).
  • Clean up old artifacts. Delete old Docker images, test databases, etc.

Reliability & Maintainability

  • Make pipelines idempotent. Running the same pipeline twice should produce the same result.
  • Tag Docker images with Git SHA. Avoid "latest" tag ambiguity. bash docker build -t myapp:${{ github.sha }} .
  • Use feature flags for refactoring. Deploy refactored code behind a flag, then gradually roll out. javascript if (featureFlags.isEnabled("newAuthFlow")) { return newAuthService.login(username, password); } else { return legacyAuthService.login(username, password); }

? Observability

  • Monitor pipeline duration. A sudden slowdown might indicate debt (e.g., tests taking longer).
  • Track flaky tests. Use tools like jest --detectOpenHandles to find hanging tests.
  • Alert on test failures. Slack/email notifications for broken builds.

5. Common Mistakes & Traps

Mistake Symptom Fix/Prevention
Refactoring without tests Breaking changes go unnoticed until QA (or prod). Always write tests before refactoring. Use the Red-Green-Refactor cycle.
Big-bang refactoring A 2-week "refactor sprint" that breaks everything. Refactor in tiny, safe steps. Use feature flags to hide unfinished work.
Ignoring CI failures Team merges code despite failing tests ("We’ll fix it later"). Enforce branch protection rules (e.g., "Require status checks to pass").
Over-engineering Spending a week building a "perfect" abstraction for a simple feature. Follow YAGNI (You Aren’t Gonna Need It). Refactor only when debt hurts.
Not tracking debt Debt accumulates invisibly until it’s too late. Add a Technical Debt column to your Scrum board. Log debt as tasks (e.g., "Refactor AuthService").

6.-Exam/Certification Focus (Scrum Guide 2020)

Typical Question Patterns

  1. "How should a Scrum Team handle technical debt?"
  2. ? Correct: "Add debt items to the Product Backlog and prioritize them alongside features."
  3. Trap: "The Developers should fix debt in their spare time."

  4. "What’s the purpose of refactoring?"

  5. ? Correct: "To improve code quality without changing behavior, making future changes easier."
  6. Trap: "To add new features faster."

  7. "When should a team refactor?"

  8. ? Correct: "Continuously, in small increments (e.g., during Sprint work)."
  9. Trap: "Only during a dedicated 'refactor sprint'."

  10. "What’s the main benefit of CI?"

  11. ? Correct: "Early detection of integration issues, reducing merge conflicts."
  12. ? Trap: "Automating deployments to production."

Key Distinctions

Concept Scrum Guide 2020 Perspective
Technical Debt The Developers are accountable for managing it. The Product Owner decides when to address it (via the Product Backlog).
Refactoring Part of the Developers’ work during the Sprint. Not a separate activity.
CI/CD Not mentioned in the Scrum Guide, but implied by the Definition of Done (e.g., "Code is integrated and tested").

Scenario-Based Question

"Your team is falling behind on Sprint Goals because of technical debt. What should you do?" --Correct: 1. Add debt items to the Product Backlog. 2. Work with the Product Owner to prioritize them. 3. Refactor in small increments during Sprints. --Trap: "Stop working on features and spend a Sprint just refactoring."


7.-Hands-On Challenge

Challenge:

You have a legacy Python function that calculates discounts. It’s 50 lines long, has no tests, and uses magic numbers. Refactor it to:
1. Be under 10 lines.
2. Have 100% test coverage.
3. Use constants instead of magic numbers.

Original Code (discount.py):

def calculate_discount(price, customer_type):
    if customer_type == 1:
        return price * 0.9
    elif customer_type == 2:
        return price * 0.85
    elif customer_type == 3:
        if price > 1000:
            return price * 0.8
        else:
            return price * 0.9
    else:
        return price

Solution:

# discount.py
STANDARD_DISCOUNT = 0.9
PREMIUM_DISCOUNT = 0.85
VIP_DISCOUNT_HIGH = 0.8
VIP_DISCOUNT_LOW = 0.9
VIP_THRESHOLD = 1000

def calculate_discount(price, customer_type):
    if customer_type == 1:
        return price * STANDARD_DISCOUNT
    elif customer_type == 2:
        return price * PREMIUM_DISCOUNT
    elif customer_type == 3:
        return price * (VIP_DISCOUNT_HIGH if price > VIP_THRESHOLD else VIP_DISCOUNT_LOW)
    return price

Tests (test_discount.py):

import pytest
from discount import calculate_discount, STANDARD_DISCOUNT, PREMIUM_DISCOUNT, VIP_DISCOUNT_HIGH, VIP_DISCOUNT_LOW

def test_standard_customer():
    assert calculate_discount(100, 1) == 100 * STANDARD_DISCOUNT

def test_premium_customer():
    assert calculate_discount(100, 2) == 100 * PREMIUM_DISCOUNT

def test_vip_customer_high_price():
    assert calculate_discount(1500, 3) == 1500 * VIP_DISCOUNT_HIGH

def test_vip_customer_low_price():
    assert calculate_discount(500, 3) == 500 * VIP_DISCOUNT_LOW

def test_no_discount():
    assert calculate_discount(100, 4) == 100

Why this works: - Small steps: Extracted magic numbers first, then simplified logic. - Tests first: Wrote tests before refactoring to ensure behavior didn’t change. - Readable: Constants make the code self-documenting.


8.-Rapid-Reference Crib Sheet

Technical Debt

  • Debt compounds. A 1-hour shortcut today might cost 10 hours next quarter.
  • Track debt in the Product Backlog. Treat it like any other work item.
  • Boy Scout Rule: Leave the codebase cleaner than you found it.

Refactoring

  • Red-Green-Refactor: Write a failing test-Make it pass-Clean up.
  • Extract Method: Split long functions into smaller ones.
  • Rename Variable: Improve clarity (e.g., x-userAge).
  • Never refactor without tests. You’ll break things.

Continuous Integration

  • CI Pipeline Steps: Build-Test-Artifact-Notify.
  • GitHub Actions Example: yaml on: [push, pull_request] jobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - run: npm test
  • Branch protection: Require status checks to pass before merging.
  • Feature flags: Deploy refactored code behind a toggle.

Code Smells

Smell Fix
Duplicated code Extract to a shared function/class
Long method Split into smaller methods
Magic numbers Replace with constants
Shotgun surgery Centralize logic

9.-Where to Go Next

  1. Martin Fowler’s Refactoring Catalog – The definitive guide to refactoring techniques.
  2. GitHub Actions Docs – Learn to build CI/CD pipelines.
  3. Scrum Guide 2020 – Official Scrum rules