contentintech
Learn/cs fundamentals/Software Engineering
Intermediate~25 min read

Software Engineering

The practice of building software at scale — SDLC models, Agile/Scrum, requirements, design patterns, testing, version control, CI/CD, estimation, and code review.

SDLCAgileTestingCI/CD

What Software Engineering Is

Software engineering is the disciplined application of engineering practices to building software that is correct, maintainable, and delivered on time. Programming is writing code; software engineering is everything around it — gathering requirements, designing systems, coordinating teams, testing, shipping, and evolving software over years. The core tension is managing complexity and change as systems and teams grow.

The Software Development Life Cycle

The SDLC describes the stages software moves through from idea to retirement. Every methodology touches the same activities — the difference is how they sequence and iterate over them.

StageGoalOutput
RequirementsUnderstand what to buildSpecs, user stories
DesignDecide how to build itArchitecture, interfaces
ImplementationWrite the codeWorking software
TestingVerify correctnessTest reports, bug fixes
DeploymentShip to usersReleased version
MaintenanceFix & evolvePatches, new features

Process Models

  • Waterfall: stages run once, in strict sequence. Simple and predictable, but change is expensive and problems surface late. Suits fixed-scope, regulated projects.
  • Iterative / Incremental: build in repeated cycles, each adding capability. Feedback comes earlier.
  • Agile: short iterations, continuous feedback, and a preference for working software over documentation. Now the dominant approach for most product teams.

Agile & Scrum

The Agile Manifesto values individuals and interactions, working software, customer collaboration, and responding to change over rigid process and documentation. Scrum is the most common Agile framework: work happens in fixed-length sprints (typically 1–2 weeks), each producing a potentially shippable increment.

RoleResponsibility
Product OwnerOwns and prioritises the backlog; represents the customer
Scrum MasterFacilitates the process, removes blockers, coaches the team
Development TeamCross-functional group that builds the increment

The Scrum ceremonies give the sprint its rhythm: sprint planning (select and estimate backlog items), the daily standup (short sync on progress and blockers), the sprint review (demo the increment to stakeholders), and the retrospective (inspect and improve how the team works). Kanban is a lighter alternative — continuous flow with work-in-progress limits instead of fixed sprints.

Definition of Done

A shared checklist for when work is truly finished — code reviewed, tests passing, documentation updated, deployed to staging. It prevents the "90% done" trap and keeps quality consistent across the team.

Requirements Engineering

Getting requirements wrong is the most expensive kind of mistake because it propagates through every later stage. Requirements split into functional (what the system does — "users can reset their password") and non-functional (how well it does it — performance, security, availability, usability).

// User story format As a [role], I want [capability], so that [benefit]. As a registered user, I want to reset my password by email, so that I can regain access if I forget it. // Acceptance criteria (Given / When / Then) Given a registered user on the login page When they request a password reset with a valid email Then a reset link is sent and expires after 30 minutes // INVEST: good stories are Independent, Negotiable, Valuable, // Estimable, Small, Testable

Design Principles & Patterns

Good design keeps software changeable. The SOLID principles are the standard guide for object-oriented design:

PrincipleMeaning
Single ResponsibilityA class should have one reason to change
Open/ClosedOpen for extension, closed for modification
Liskov SubstitutionSubtypes must be usable wherever the base type is
Interface SegregationPrefer small, specific interfaces over fat ones
Dependency InversionDepend on abstractions, not concrete classes

Alongside SOLID, keep code DRY (Don't Repeat Yourself), KISS (Keep It Simple), and resist YAGNI (You Aren't Gonna Need It — don't build for imagined futures). Design patterns are named, reusable solutions to recurring design problems:

CategoryPatternsSolves
CreationalFactory, Builder, SingletonHow objects are created
StructuralAdapter, Decorator, FacadeHow objects are composed
BehavioralObserver, Strategy, CommandHow objects interact
javascript
// Strategy pattern: swap an algorithm at runtime via a common interface
class Sorter {
  constructor(strategy) { this.strategy = strategy; }
  sort(data) { return this.strategy(data); }        // depends on abstraction
}
const byPrice = data => [...data].sort((a,b) => a.price - b.price);
const byName  = data => [...data].sort((a,b) => a.name.localeCompare(b.name));

new Sorter(byPrice).sort(products);   // switch behavior without changing Sorter

Testing Strategy

Tests are what let you change code without fear. The test pyramid guides the mix: many fast, cheap unit tests at the base; fewer integration tests in the middle; a thin layer of slow, brittle end-to-end tests at the top.

LevelScopeSpeed / Count
UnitOne function/class in isolationFast, many
IntegrationModules working together (e.g. DB)Medium, some
End-to-endWhole system as a user sees itSlow, few
javascript
// TDD cycle: Red -> Green -> Refactor
// 1. Write a failing test first
test("applies a 10% discount", () => {
  expect(applyDiscount(100, 0.1)).toBe(90);   // RED: function doesn't exist yet
});

// 2. Write the minimum code to pass
function applyDiscount(price, rate) { return price * (1 - rate); }  // GREEN

// 3. Refactor with the test as a safety net

// AAA structure for any test: Arrange, Act, Assert

Coverage is a floor, not a goal

High code coverage means lines were executed, not that behavior is correct. Test edge cases, error paths, and boundaries — not just the happy path. 100% coverage of trivial getters is worth less than one good test of your pricing logic.

Version Control

Git is the standard distributed version control system. It tracks history as a graph of commits, letting teams work in parallel on branches and merge changes safely. A branching strategy keeps that collaboration orderly.

bash
# Feature-branch workflow (most common today)
git checkout -b feature/password-reset   # branch off main
# ... commit work ...
git push -u origin feature/password-reset
# open a pull request -> review -> CI passes -> merge to main

# Keep history clean
git commit -m "Add password reset email flow"   # small, focused commits
git rebase main                                  # replay work on latest main

# Trunk-based: short-lived branches, merge to main many times a day,
# hide unfinished work behind feature flags.

CI/CD

Continuous Integration means every push is automatically built and tested, so integration problems surface within minutes. Continuous Delivery keeps the software always releasable; Continuous Deployment goes further and ships every passing change to production automatically.

bash
# A typical pipeline (e.g. GitHub Actions / GitLab CI)
on: push
jobs:
  build:  checkout -> install deps -> compile
  test:   run unit + integration tests, fail fast
  lint:   static analysis, formatting, security scan
  deploy: if branch == main && tests pass -> deploy to staging
          # gated promotion to production (manual or automatic)

# Deployment strategies to reduce risk:
#   Blue-green   : swap traffic between two identical environments
#   Canary       : release to a small % of users, watch metrics, roll out
#   Rolling      : replace instances gradually

Estimation

Estimation is hard because software work is uncertain. Agile teams often estimate in story points — relative size (complexity + effort + uncertainty) rather than hours — using a Fibonacci-like scale (1, 2, 3, 5, 8, 13). Planning poker has the team estimate independently then discuss divergences. Over a few sprints, the team's average points-per-sprint (velocity) becomes a reliable forecasting tool.

  • Estimate relatively, not absolutely — humans compare sizes better than they predict durations.
  • Break large items down; anything above ~8 points should be split.
  • Track velocity to forecast; never use it to compare or pressure teams.
  • Beware the planning fallacy — pad for the unknowns you always hit.

Code Review

Code review catches bugs, spreads knowledge, and keeps a codebase consistent. Reviews happen on pull requests before merge. The most effective reviews are small (a few hundred lines), prompt, and focused on substance over style (leave style to automated formatters).

  • As an author: keep PRs small, write a clear description of what and why, and self-review first.
  • As a reviewer: check correctness, edge cases, readability, tests, and security. Ask questions rather than issue commands; distinguish blocking issues from nits.
  • Be kind — review the code, not the person. A review is a conversation, not a gate to defend.

Technical debt

Shortcuts taken to ship faster accrue "interest" — future work becomes slower and riskier. Some debt is deliberate and fine; the danger is silent, accumulating debt. Track it, and pay it down before it compounds into a rewrite.

Practice Exercises

  1. Take a feature idea and write three user stories in the "As a… I want… so that…" format with acceptance criteria.
  2. Identify which SOLID principle is violated in a class that reads a file, parses it, and emails a report, then refactor it.
  3. Refactor an if/else chain that selects a payment method into the Strategy pattern.
  4. Write a failing unit test, then the minimal code to pass it, following the TDD Red-Green-Refactor cycle.
  5. Sketch a CI/CD pipeline for a web app, listing each stage and what would cause it to fail.
  6. Estimate a small backlog of five items in story points using planning poker with a teammate, then reconcile the differences.

Section navigation