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.
| Stage | Goal | Output |
|---|---|---|
| Requirements | Understand what to build | Specs, user stories |
| Design | Decide how to build it | Architecture, interfaces |
| Implementation | Write the code | Working software |
| Testing | Verify correctness | Test reports, bug fixes |
| Deployment | Ship to users | Released version |
| Maintenance | Fix & evolve | Patches, 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.
| Role | Responsibility |
|---|---|
| Product Owner | Owns and prioritises the backlog; represents the customer |
| Scrum Master | Facilitates the process, removes blockers, coaches the team |
| Development Team | Cross-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:
| Principle | Meaning |
|---|---|
| Single Responsibility | A class should have one reason to change |
| Open/Closed | Open for extension, closed for modification |
| Liskov Substitution | Subtypes must be usable wherever the base type is |
| Interface Segregation | Prefer small, specific interfaces over fat ones |
| Dependency Inversion | Depend 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:
| Category | Patterns | Solves |
|---|---|---|
| Creational | Factory, Builder, Singleton | How objects are created |
| Structural | Adapter, Decorator, Facade | How objects are composed |
| Behavioral | Observer, Strategy, Command | How objects interact |
// 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.
| Level | Scope | Speed / Count |
|---|---|---|
| Unit | One function/class in isolation | Fast, many |
| Integration | Modules working together (e.g. DB) | Medium, some |
| End-to-end | Whole system as a user sees it | Slow, few |
// 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.
# 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.
# 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
- Take a feature idea and write three user stories in the "As a… I want… so that…" format with acceptance criteria.
- Identify which SOLID principle is violated in a class that reads a file, parses it, and emails a report, then refactor it.
- Refactor an if/else chain that selects a payment method into the Strategy pattern.
- Write a failing unit test, then the minimal code to pass it, following the TDD Red-Green-Refactor cycle.
- Sketch a CI/CD pipeline for a web app, listing each stage and what would cause it to fail.
- Estimate a small backlog of five items in story points using planning poker with a teammate, then reconcile the differences.