Skip to content
Educora
University25 min58 / 59

Software engineering

Learn the software development life cycle, agile methods and Scrum, version control with Git, testing levels, the SOLID principles and code review.

Check yourself
In this lesson you will learn
  • Compare the waterfall and agile models and explain Scrum's roles and events
  • Work with branches in Git and undo a change safely
  • Choose testing levels and compute cyclomatic complexity
  • Spot violations of the SOLID principles in code

Writing a 200-line script on your own is programming; a team of 50 developing millions of lines of code for years, testing them and shipping updates every week without breaking anything is software engineering. The main difficulty here is not algorithms but complexity, changing requirements and communication between people. In this lesson we learn how professional teams manage these problems.

The life cycle: from waterfall to agile

  1. 1
    Requirements

    What must the system do (functional), and how well (speed, security — non-functional)?

  2. 2
    Design

    Architecture, modules, database, interfaces.

  3. 3
    Implementation

    Writing code, code review, version control.

  4. 4
    Testing

    Unit, integration, system and acceptance tests.

  5. 5
    Deployment and maintenance

    Release, monitoring, bug fixes and new requirements — the longest part of a system's life.

CriterionWaterfallAgile
phasessequential, oncerepeated in short iterations
changing requirementsexpensiveexpected and welcomed
customer sees working softwareat the endevery 1–4 weeks
good fitstable requirements, strict regulation (medicine, aviation)uncertain markets, web and mobile products

The Agile Manifesto of 2001 puts working software, collaboration with the customer and readiness for change first. The most popular agile framework is Scrum. It has three roles: the Product Owner (manages the Product Backlog and priorities), the Scrum Master (supports the process and removes obstacles) and the Developers (the team doing the work). Work proceeds in sprints — fixed periods of up to one month: Sprint Planning, a 15-minute Daily Scrum every day, and at the end the Sprint Review (showing the result) and the Retrospective (improving the process).

Example 1: forecasting with velocity

The Product Backlog has 150 story points left. In the last three sprints the team completed 16, 20 and 18 points. A sprint lasts 2 weeks. About how long until the release?

Show solution
Average velocity: (16 + 20 + 18) / 3 = 54 / 3 = 18 points per sprint.
Number of sprints: ⌈150 / 18⌉ = ⌈8.33⌉ = 9 sprints.
Time: 9 · 2 = 18 weeks (about 4 months).
It is a forecast, not a promise: update it after every sprint. Velocity must not be compared between teams — points are each team's own scale.
C = n(n − 1) / 2C = n(n − 1) / 2
where:
  • Cnumber of pairwise communication channels in a team
  • nnumber of team members

5 people → 10 channels, 10 people → 45 channels. This explains Brooks's law: adding people to a late project often makes it later. That is why Scrum teams are kept small (typically up to 10 people).

Version control with Git

Git is a distributed version control system: every developer's computer holds a complete repository with the whole history. A commit is a “snapshot” of the project (changes, author, time, message); commits form a chain and each is named by a hash. A branch is a movable pointer to a commit: you build a new feature on its own branch and, when it is ready, send it for review with a pull request and merge it into the main branch.

Terminal
git switch -c feature/login          # create and switch to a new branch
git add login.py
git commit -m 'Validate the login form'
git push -u origin feature/login     # then open a pull request

git switch main
git pull                             # get the reviewed and merged work
git revert 3f2a9c1                   # safely undo a bad pushed commit
A typical workflow. git revert creates a new commit that undoes a bad one — history is not rewritten, so it is safe on shared branches.

Testing

LevelWhat it checksSpeed and number
Unitone function or class, in isolationmilliseconds, thousands
Integrationmodules working together (code + database, APIs)seconds, hundreds
System (end-to-end)the whole system, as a user wouldminutes, dozens
Acceptancefit with the customer's requirementsbefore release
The “test pyramid”: many fast unit tests at the bottom, a few slow system tests at the top.
Python
def is_leap(year):
    return year % 4 == 0 and (year % 100 != 0 or year % 400 == 0)

def buggy_is_leap(year):
    return year % 4 == 0

cases = {2024: True, 2023: False, 1900: False, 2000: True}

def run_tests(func):
    failed = [y for y, expected in cases.items() if func(y) != expected]
    return 'all passed' if not failed else 'FAILED for ' + str(failed)

print('is_leap:', run_tests(is_leap))
print('buggy_is_leap:', run_tests(buggy_is_leap))
▸ Expected output
is_leap: all passed
buggy_is_leap: FAILED for [1900]
Good unit tests also check edge cases: 1900 is divisible by 4 but, being divisible by 100 and not by 400, is not a leap year. The naive version looks right on ordinary cases, and only this test catches the bug. In real projects the same job is automated with pytest or unittest and runs on a CI server for every commit.
M = E − N + 2P (= number of decisions + 1)
where:
  • MMcCabe's cyclomatic complexity: the number of independent paths
  • E, Nedges and nodes of the control-flow graph
  • Pnumber of connected components (1 for one function)

M is a good estimate of the minimum number of tests needed to cover every branch. Functions with M > 10 are usually split into smaller ones.

Example 2: cyclomatic complexity

is_leap returns A and (B or C), where A = “divisible by 4”, B = “not divisible by 100”, C = “divisible by 400”. What is its cyclomatic complexity, and why do our tests use 4 cases?

Show solution
Each short-circuit boolean operator (and, or) is a decision point: 2 decisions → M = 2 + 1 = 3.
3 basis paths: A false → 2023; A true, B true → 2024; A true, B false → the result is C itself.
Because the third path returns C, we test both of its values: 1900 (C false) and 2000 (C true).
So M = 3 is the minimum for path coverage, and 4 tests give full condition coverage — exactly our cases dictionary.

SOLID and code review

PrincipleMeaning
S — Single responsibilitya class should have only one reason to change
O — Open/closedopen for extension, closed for modification: add behaviour with new code
L — Liskov substitutiona subclass object must work anywhere the base class is expected
I — Interface segregationseveral small, specific interfaces instead of one large one
D — Dependency inversiondepend on abstractions, not on concrete classes
Python
from abc import ABC, abstractmethod

class Discount(ABC):
    @abstractmethod
    def apply(self, price): ...

class NoDiscount(Discount):
    def apply(self, price):
        return price

class Percent(Discount):
    def __init__(self, p):
        self.p = p
    def apply(self, price):
        return round(price * (1 - self.p / 100), 2)

class StudentCard(Discount):
    def apply(self, price):
        return max(price - 5, 0)

def checkout(price, discount: Discount):
    return discount.apply(price)

for d in [NoDiscount(), Percent(20), StudentCard()]:
    print(type(d).__name__, checkout(50, d))
▸ Expected output
NoDiscount 50
Percent 40.0
StudentCard 45
checkout depends on the Discount abstraction, not on concrete discounts (D). Adding a new kind of discount does not require changing checkout — a new class is enough (O). Every subclass works wherever a Discount is expected (L).

Code review means another developer checks a change before it reaches the main branch. The reviewer looks at correctness, tests, readability, security and design; style should be checked by automatic tools (linters, formatters). Besides catching bugs, reviews spread knowledge through the team: every part of the system is known by at least two people. Small pull requests (under a few hundred lines) get read more carefully.

Key points

  • Life cycle: requirements → design → implementation → testing → deployment and maintenance; agile methods repeat it in short iterations.
  • Scrum: Product Owner, Scrum Master, Developers; sprints of up to a month and four events.
  • Git: a commit is a snapshot, a branch a pointer; never rewrite history on a shared branch — use revert.
  • Test pyramid: many unit tests, few system tests; cyclomatic complexity M = decisions + 1.
  • SOLID gives designs that survive change; code review catches bugs and spreads knowledge; team channels grow as n(n − 1)/2.

Check yourself

10 questions. Every correct answer earns XP.

1 / 10
How many pairwise communication channels are there in a team of 8?