Skip to content

← Grade 12 Computing

Software Engineering

Unit 3 · Creating

Hide the stories, facts, photos, activities and interactives, for revising before an exam
In this unit 14 sections
  1. Software that lasts
  2. Version control
  3. Tests that run themselves
  4. Finding a bug with a debugger
  5. Code review
  6. Documentation
  7. Other ways to program
  8. Patterns in code that lasts
  9. Code an AI wrote
  10. Open source: using it, crediting it, joining in
  11. Ways to organize the work
  12. Project: software built to last, in a team
  13. In short
  14. Exercise

A Grade 12 student writes a program over a weekend to work out her school’s results. It works, the teachers love it, and the school uses it. Then she leaves for university. The next year the exam rules change, a teacher finds that one student’s grade is wrong, and nobody knows how the program works, which version is the right one, or whether fixing one thing will break another. Most programs that are any use live for years, are changed many times, and are looked after by people who didn’t write them.

This unit is about building software that lasts, in a team. You will follow four students, Aarati, Dawa, Kiran and Sabina, as they build a results program for their school in Bhaktapur, and do what they do: keep every version with git, write tests that run themselves, find a bug with a debugger, review each other’s code and write it up for the next person. On the way you will meet other ways to program, patterns that keep code easy to change, and how to check code an AI wrote. Last comes how teams organize the work, from the first plan to years of upkeep: the plan you will use for your capstone in Unit 9.

Software that lasts

In Grade 10 you built a product in a team, with requirements, a design document, dated versions and tests. In Grade 11 you built an app with real users and made a second version. Those were small projects, finished in a term. Real software is different in three ways:

  • It lives for years. A bank’s program, a school’s fee system or a phone’s operating system is used and changed long after it was first written. Most of the work on a program comes after its first version.
  • It is read more than it is written. Every change starts with someone reading the code to understand it. Code that is quick to write but hard to read costs more every year.
  • Many people work on it at once, and the people change. Whoever wrote a part may have left.

Software engineering is the discipline of building software that keeps working while it is changed, by teams, for years: with plans, versions kept, tests, reviews, documentation and agreed ways of working. It isn’t a different kind of programming. It is programming done so that other people, and you a year from now, can trust and change it.

A woman stands beside a stack of thick printed program listings as tall as she is
Figure 3.1 Margaret Hamilton in 1969, beside printouts of the Apollo guidance software her team wrote. Photo: Adam Cuerden, Public domain

Why is most of the work on a useful program done after its first version?

Pick an answer to see why.

Version control

In Grade 10 the milk cooperative’s team kept a copy of the whole folder for each version, and wrote every change in a change log Grade 10: Dated versions and a change log. They also lost an evening’s work when two people copied the same file back, one over the other. A version control system does all of that bookkeeping for you: it keeps every version of every file, who changed what and why, and it joins different people’s changes, warning when they clash.

This unit uses git, free and open source, for Windows, macOS and Linux. Its commands are typed on the command line (Grade 10, Unit 6), and editors have buttons for them too. The ideas are the same in any version control system.

Commits: saved snapshots

The folder git looks after is a repository (a “repo”). Aarati makes one for the results program, writes grades.py, and saves its first version. These are the commands she typed and exactly what git answered:

$ git initInitialized empty Git repository in /home/aarati/results/.git/$ git statusOn branch main No commits yet Untracked files:  (use "git add <file>..." to include in what will be committed)    grades.py nothing added to commit but untracked files present (use "git add" to track)$ git add grades.py$ git commit -m "Add grade_for(): marks to a letter grade"[main (root-commit) 9876d82] Add grade_for(): marks to a letter grade 1 file changed, 18 insertions(+) create mode 100644 grades.py
  • git init makes the repository: a hidden folder, .git, where every version will be kept.
  • git status says what has changed since the last saved version. Here, a file git isn’t looking after yet.
  • git add chooses which changes go into the next saved version. This is called staging: you might have changed five files and want to save only two of them now.
  • git commit saves them. A commit is a snapshot of the whole project at one moment, with who made it, when, and a commit message saying what changed. Git gives each commit an id, worked out from its contents with a hash (Grade 9’s SHA idea): 9876d82 is the start of it. The same contents, author, time and message always give the same id, and any change gives a different one, so history can’t be changed quietly.

The next day Dawa tests the program and finds that 35 marks gives NG, though 35 is the lowest D. He fixes it. Before committing, git diff shows exactly what he changed:

$ git diffdiff --git a/grades.py b/grades.pyindex 9f3c362..2b422af 100644--- a/grades.py+++ b/grades.py@@ -12,7 +12,7 @@ def grade_for(marks):         return "C+"     elif marks >= 40:         return "C"-    elif marks > 35:+    elif marks >= 35:         return "D"     else:         return "NG"

A diff shows the lines taken out (with -) and put in (with +), with a few unchanged lines around them so you can see where. @@ -12,7 +12,7 @@ means “7 lines starting at line 12, before and after”. Then he commits, and git log lists the history, newest first:

$ git commit -am "Fix grade_for(): 35 marks is a D, not NG"[main 2691407] Fix grade_for(): 35 marks is a D, not NG 1 file changed, 1 insertion(+), 1 deletion(-)$ git log --oneline2691407 Fix grade_for(): 35 marks is a D, not NG9876d82 Add grade_for(): marks to a letter grade

(-a adds every changed file git already looks after, so git add isn’t needed.) Now any version can be looked at again, compared with any other (git diff 9876d82 2691407), or brought back. git revert 2691407 would make a new commit that undoes that change, keeping the history honest. And Grade 10’s binary search through versions to find the one that broke a test is built in: git bisect does it for you.

A good commit message

Six months later, someone reads the history to find when, and why, something changed. A message like “fixed stuff” or “update” tells them nothing. A good commit message:

  • has a short first line, about 50 characters, saying what the commit does, as an instruction: “Fix grade_for(): 35 marks is a D, not NG”, not “I fixed some things”;
  • says why in a few more lines after a blank line, when the reason isn’t obvious from the change: “The school’s grading table says 35 to 39 is D; we had 36 to 39.”;
  • covers one change. A commit that fixes a bug, renames ten variables and adds a feature can’t be understood, or undone, one part at a time.

Which is the best commit message?

Pick an answer to see why.

Branches and merging

Kiran is going to write gpa(), which will take him a few days. While it is half finished, it may not even run, and the rest of the team still needs a working program. So he makes a branch: a line of commits of his own, that leaves main alone until his work is ready.

A branch is only a name pointing at a commit. When Kiran commits on his branch, his branch’s name moves on to the new commit; main stays where it was. Meanwhile Sabina adds check_marks() on main. When gpa() is finished and tested, Kiran’s branch is merged into main: git makes a merge commit that joins the two lines of work, with both commits as its parents.

Try it

Two branches, one file

Each circle is a commit, joined to the one before it, oldest at the top. Make Kiran’s branch, let Kiran and Sabina each commit, then merge. Try a different order too: merge before Sabina commits, or let Sabina commit before the branch is made.

maingpagrade_for()Aaratifix 35Dawamain
grades.py on main, last lines
        return "NG"
grade_for()
on main
check_marks()
–
gpa()
–

main has two commits: Aarati’s grade_for() and Dawa’s fix. Make a branch for Kiran’s work, or let Sabina commit first: any order the buttons allow.

What you should see

When two changes meet: conflicts

Git joins changes to different files, or to different parts of one file, by itself. But Kiran and Sabina both added code at the end of grades.py, at the same place, and git can’t know which should come first, or whether one should replace the other. So it stops:

$ git merge gpaAuto-merging grades.pyCONFLICT (content): Merge conflict in grades.pyAutomatic merge failed; fix conflicts and then commit the result.

and marks both versions in the file, between lines git writes into it:

grades.py (the end)

        return "NG" <<<<<<< HEADdef check_marks(marks):    """Refuse marks that can't be right."""    if not 0 <= marks <= 100:        raise ValueError(f"marks must be 0 to 100, not {marks}")=======POINTS = {"A+": 4.0, "A": 3.6, "B+": 3.2, "B": 2.8,          "C+": 2.4, "C": 2.0, "D": 1.6, "NG": 0.0} def gpa(all_marks):    """The average grade point of a student's subjects."""    points = [POINTS[grade_for(m)] for m in all_marks]    return round(sum(points) / len(points), 2)>>>>>>> gpa

Between <<<<<<< HEAD and ======= is what the branch you are on (main) has; between ======= and >>>>>>> gpa is what the branch being merged has. A merge conflict is git asking a person to decide. Here neither change is wrong; they only landed in the same place. Aarati keeps both, deletes the three marker lines, runs every test, and finishes the merge:

$ git add grades.py$ git commit -m "Merge branch 'gpa': keep both check_marks() and gpa()"[main ebf21dd] Merge branch 'gpa': keep both check_marks() and gpa()$ git log --oneline --graph*   ebf21dd Merge branch 'gpa': keep both check_marks() and gpa()|\  | * 740f391 Add gpa(): average grade point* | 6bca0f4 Add check_marks(): refuse marks outside 0-100|/  * 2691407 Fix grade_for(): 35 marks is a D, not NG* 9876d82 Add grade_for(): marks to a letter grade

--graph draws the history as the widget did, on its side: the two lines of work part after Dawa’s fix and join again at the merge.

Conflicts are fewer when a team commits small changes often, merges branches back within a few days, and splits the work so that people seldom change the same lines (modules, Grade 10).

Two people changed different functions in the same file on two branches. What happens when the branches are merged?

Pick an answer to see why.

Sharing with a team

So far the repository is on Aarati’s computer. To share it, the team keeps a copy on a server, called a remote. git clone copies a repository from a remote, with its whole history; git push sends your new commits to the remote; git pull brings in everyone else’s and merges them into yours. Every member has the whole history, so the project survives a lost laptop, or a server that goes down.

Teams usually don’t push straight to main. Each change is made on a branch, pushed, and offered for merging as a pull request: a page showing the diff, where teammates read it, comment on lines, and approve it before it is merged. That is where code review (later in this unit) happens, and where automatic tests can run on every change.

The team’s agreed workflow, written into their design document:

  1. Take a task from the board (Grade 10’s task board), and make a branch for it.
  2. Commit small steps with clear messages; run the tests before each push.
  3. Open a pull request; at least one other member reviews it.
  4. Merge into main only when the tests pass and the review is approved. main always works.

Tests that run themselves

In Grade 10 the milk team wrote tests before the code and ran them after every change; in Grade 9 you tested a web app with ordinary, edge and wrong inputs. As a program grows, nobody can test every part by hand after every change. So tests are written as code too, and a computer runs all of them in seconds.

A test is a program

An automated test calls a function with chosen inputs and checks the result against the right answer, worked out by hand from the requirements. In Python the simplest check is assert: assert grade_for(72) == "B+" does nothing if it is true, and raises an AssertionError if it isn’t. Here is Aarati’s first grade_for(), before Dawa’s fix, with four tests and a tiny test runner that finds every function whose name starts with test_ and runs it. Run it.

def grade_for(marks):    """The letter grade for marks out of 100."""    if marks >= 90:        return "A+"    elif marks >= 80:        return "A"    elif marks >= 70:        return "B+"    elif marks >= 60:        return "B"    elif marks >= 50:        return "C+"    elif marks >= 40:        return "C"    elif marks > 35:        return "D"    else:        return "NG" # The tests: each checks one thing, with an answer worked out by handdef test_normal():    assert grade_for(72) == "B+"    assert grade_for(45) == "C" def test_a_plus_boundary():    assert grade_for(90) == "A+"    assert grade_for(89) == "A" def test_d_boundary():    assert grade_for(35) == "D", f"35 gave {grade_for(35)}"    assert grade_for(34) == "NG" def test_lowest_and_highest():    assert grade_for(0) == "NG"    assert grade_for(100) == "A+" # A tiny test runner: run every function whose name starts with test_passed = failed = 0for name, test in list(globals().items()):    if name.startswith("test_"):        try:            test()            print("PASS", name)            passed += 1        except AssertionError as problem:            print("FAIL", name, problem)            failed += 1print(passed, "passed,", failed, "failed")

Output

PASS test_normalPASS test_a_plus_boundaryFAIL test_d_boundary 35 gave NGPASS test_lowest_and_highest3 passed, 1 failed

Each test is small and named for what it checks, so a failure says at once where to look: the D boundary. (The text after the comma in an assert is the message shown when it fails.) Change line 15 to marks >= 35 and run it again: all four pass. Real projects use a ready-made test runner that works the same way: it finds every test_ function in every test file, runs them, and reports.

Choosing test data

You can’t test every possible input: even marks from 0 to 100 are 101 cases for one function, and most functions take more than one value. So you choose inputs that are most likely to catch mistakes. Exams sort them into three kinds:

  • Normal data: ordinary, valid values a user would usually give, well inside the allowed range. 72, 45. The program must get the everyday cases right.
  • Boundary data: values at the edges, where the program’s behavior changes: the lowest and highest values allowed (0 and 100), and the values on both sides of every line inside the range (89 and 90; 34 and 35). Mistakes cluster at boundaries, because > and >= look so alike: Aarati’s bug was exactly there.
  • Erroneous data (also called invalid or abnormal data): values that should be refused: −5, 101, the word “ninety”, an empty box. The program must reject them clearly, not crash and not quietly give an answer.

Try it

Normal, boundary or erroneous?

Sort each test value for the results program.

72

Marks must be whole numbers from 0 to 100, and 90 is the lowest A+, 35 the lowest D. What kind of test data is it?

Tap a group above.

Card
1 of 10
Right the first time
0

Run them after every change

Tests are worth most when they run all the time. A test that passed and now fails shows a regression: something that used to work, broken by a change (Grade 9). With a fast set of tests, every member runs all of them before every commit, and a continuous integration server runs them again on every push, so a regression is caught within minutes of the change that caused it, while the change is still fresh in its author’s mind.

Some teams write each test before the code it tests: the test fails, then they write just enough code to pass it, then tidy the code with the tests still passing. This is called test-driven development. It makes sure every part has a test, and it makes you decide exactly what the code should do before you write it.

Dawa’s change to a function makes a test that passed last week fail. What is this called?

Pick an answer to see why.

White-box and black-box

There are two ways to choose tests:

  • Black-box testing chooses tests from the requirements alone, without looking at the code, as a user would see it: what goes in and what should come out. A teacher trying every screen, and tests chosen from the grading table, are black-box.
  • White-box testing chooses tests by reading the code, to make sure every part of it runs: every branch of every if, a loop that runs zero times, once and many times, every return. The share of the lines that the tests run is called the coverage.

Each catches what the other misses. Black-box tests catch a requirement the code forgot altogether (no code for “empty box”, so no branch for white-box tests to find). White-box tests catch a branch that no requirement mentions, such as a special case a programmer added. A dry run, working through code by hand with a trace table (Grade 8), is a white-box check done on paper.

Try it

White-box or black-box?

How was each test chosen?

Tests chosen from the requirements alone: marks 0 to 100, a new grade at 90, 80, 70 …

Is this test chosen white-box or black-box?

Tap a group above.

Card
1 of 6
Right the first time
0

From one function to the whole system

Tests are also named by how much of the program they test, and who does it:

  • Unit testing tests one part alone: a function or a class. A part it depends on that isn’t ready can be replaced by a stub, a simple stand-in that gives fixed answers.
  • Integration testing tests parts together, where they hand data to each other. Parts that each pass their own tests can still disagree: one gives marks out of 100, the other expects marks out of 75.
  • System testing tests the whole program, as it will be used, against all the requirements.
  • Alpha testing is the whole system tried by people close to the makers (the team, classmates) before it goes out.
  • Beta testing gives it to real users, for real work, before everyone has it, to find what the makers never thought of.
  • Acceptance testing is done by the customer (here, the principal and the exam coordinator) against the requirements they agreed, to decide whether to accept the system.

Try it

Which kind of testing?

Sort each test from the results program’s project.

Dawa’s test checks that grade_for(35) gives "D".

Which kind of testing is it?

Tap a group above.

Card
1 of 8
Right the first time
0

Finding a bug with a debugger

Since Grade 6 you have debugged by predicting, running and comparing, and since Grade 8 by stepping through a program a line at a time and printing values. In a bigger program, putting print() in twenty places, reading the output and taking them out again is slow. A debugger is a tool in your editor that runs the program and lets you stop it anywhere, look inside, and go on:

  • A breakpoint is a line where the program will pause, before that line runs. You set one by tapping or clicking beside the line’s number.
  • Run (or Resume) runs until the next breakpoint.
  • Step into runs one line, going into a function that line calls. Step over runs the line and any function it calls, all at once, and stops at the next line. Step out runs to the end of the function you are in and stops back where it was called.
  • The variables panel shows every variable’s value at the pause, so you can watch them change. The call stack shows which function is running and which functions called it and are waiting.

The editor recommended in Grade 10 has a debugger made for learners (its buttons are in the box below), and most editors have one that works the same way.

Kiran’s first gpa() gives 1.07 for marks of 95, 85 and 75, but working it out by hand gives (4.0 + 3.6 + 3.2) ÷ 3 = 3.6. Find out why with the debugger below. It is a debugger on the page, showing every step exactly as Python takes it.

Try it

Find the bug with a debugger

Tap line 15’s number to set a breakpoint, then press Run. Each time it stops, look at total in the variables, then Resume. Try Step into at line 15 too, to see the call stack grow, and Step out to come back.

BANDS = [(90, "A+"), (80, "A"), (70, "B+"), (60, "B"),
(50, "C+"), (40, "C"), (35, "D")]
POINTS = {"A+": 4.0, "A": 3.6, "B+": 3.2, "B": 2.8,
"C+": 2.4, "C": 2.0, "D": 1.6, "NG": 0.0}
def grade_for(marks):
for lowest, grade in BANDS:
if marks >= lowest:
return grade
return "NG"
def gpa(all_marks):
total = 0
for marks in all_marks:
total = POINTS[grade_for(marks)]
return round(total / len(all_marks), 2)
print(gpa([95, 85, 75]))

Tap a line number to set a breakpoint, then press Run. Or press Step into to pause on the first line.

Variables

Call stack

Output

 
Hint
Answer

A debugger isn’t always the fastest tool. When you can see the mistake by reading a short function, read it. When the problem only appears after a long run, or on a server, a few printed values, or a log file, may be better. A debugger wins when you don’t know where the problem is: stop near where things first look wrong, watch the values, and step closer.

You want the program to pause just before line 40 runs, every time it reaches it. What do you set?

Pick an answer to see why.

Paused on total = total + points(m), you want to see what happens inside points(). Which button?

Pick an answer to see why.

Code review

Before a change is merged, another member of the team reads it. This is code review. Reviewers catch bugs the author can’t see (it is hard to read your own code as a stranger would), check that the code meets the requirements and the team’s rules, and learn the code themselves, so that no part is understood by only one person. In Grade 11 you reviewed code for security flaws; a review covers those too. Most reviews are read alone, on a pull request. When the author instead takes the team through the code line by line in a meeting, it is called a walkthrough.

A checklist

A team agrees a short checklist, so every review covers the same ground:

  1. Requirements: does it do what the task asked, for every case, including the boundaries and erroneous data?
  2. Tests: are there tests for the new code, and do all the tests pass?
  3. Readable: do names say what things are? Is anything repeated that should be a function? Is there a comment where the reason isn’t obvious?
  4. Safe: is every input from a user, a file or a form checked? Any secrets in the code? (Grade 11’s flaws.)
  5. For everyone: does it work on a cheap phone and a slow connection, in Nepali as well as English, for someone who uses a screen reader? Does it collect only the data it needs?
  6. Credit: is borrowed code, a library or AI help recorded?

Try it

Review a teammate’s change

Three short changes waiting for your review. For each problem, tap a line of it, say what goes wrong, and choose the fix.

report.py. The requirement: list the names of the students whose GPA is 2.0 or more.

Program 1 of 3 · right the first time: 0 of 12

1. Find a flaw: tap a line of it in the program. 2 flaws left to find.

Read it as the person who must look after it next year: does every line do what the requirement says, for every input, and can you tell what each name means?

Giving and taking a review

How a review is written matters as much as what it finds:

  • Comment on the code, not the person: “This divides by zero when a student has no marks”, not “You forgot empty lists again.”
  • Say why, and suggest a fix where you can. Ask when you aren’t sure: “Should a GPA of exactly 2.0 be included?”
  • Separate what must change (a bug) from what is your taste (a name you would choose differently).
  • Say what is good, too.

When your own code is reviewed, thank the reviewer, answer every comment (fix it, or explain why not), and don’t take it personally: every programmer’s code gets comments, and the review exists to make the product better, not to judge you.

Which review comment is the most useful?

Pick an answer to see why.

Documentation

Code that only its author understands dies when the author leaves. Documentation is everything written so that others can use and change the program. Three kinds matter most:

  • A README, a file at the top of the repository that a newcomer reads first: what the program is for, how to install it and run it, how to run the tests, how to help, and its license.
  • Docstrings (Grade 8) at the top of every function and class, saying what it does, what it takes and what it gives back.
  • Comments where the reason isn’t obvious from the code. Don’t repeat what the code says (total = 0 # set total to 0 is noise); say why: # The school rounds a GPA to 2 places, as on the mark sheet. A comment that has fallen out of date, describing what the code used to do, is worse than none.

Here is the start of the team’s README. It is written in Markdown, the format this book is written in: # makes a heading, and text between backquotes is shown as code.

README.md

# Results Works out each student's letter grades and GPA from their marks,and prints report cards, for Grades 9 to 12 of our school. ## Running it You need Python 3.12 or newer. In this folder, run:     python3 results.py marks.csv marks.csv has one row per student per subject: symbol number,subject, marks out of 100. See example-marks.csv. ## Running the tests     python3 -m pytest All tests must pass before a change is merged. ## How the grades are worked out From the school's grading table (docs/grading.md): 90 and over A+,80 to 89 A, ... 35 to 39 D, under 35 NG. A GPA is the average ofthe subjects' grade points (our subjects all have the samecredit hours), rounded to 2 places. ## Who made it, and help we had Aarati, Dawa, Kiran and Sabina (Grade 12, 2083). The SMS partwas first drafted with an AI chatbot, then rewritten and testedby Sabina. Uses pytest (MIT License). ## License MIT License: see LICENSE.

Which comment is worth writing?

Pick an answer to see why.

Other ways to program

Every program in this course so far has given the computer instructions to carry out in order, changing variables as it goes: that is imperative programming. Grouped into functions, it is procedural programming (Grade 8 on); with data and its functions together in objects, it is object-oriented (Grade 11). But there are other ways to think about a program, and each makes some jobs easier.

Pure functions and side effects

Compare these two functions:

total = 0 def add_fee(amount):    """Not pure: it changes total, a variable outside it."""    global total    total = total + amount    return total def late_fee(fee, days_late):    """Pure: the answer depends only on what goes in."""    return fee + 10 * days_late print(add_fee(500), add_fee(500))print(late_fee(500, 3), late_fee(500, 3))

Output

500 1000530 530

add_fee(500) gives a different answer each time, because it changes something outside itself. Anything a function does besides giving back a value (changing a global, changing a list it was passed, printing, writing a file, sending a message) is a side effect. A pure function has none, and its answer depends only on its arguments: the same inputs always give the same output, like a function in mathematics.

Pure functions are easy to test (call, compare, done: nothing to set up, nothing to undo), easy to understand alone, and safe to run at the same time on many processors, because they can’t interfere with each other. Every program needs some side effects, or it couldn’t show anything. The habit is to keep the working-out in pure functions and the side effects at the edge: read the input, call pure functions, print the result. Functional programming is the way of programming built on pure functions.

Functions as values: lambda, map, filter, reduce

In Python a function is a value: it can be put in a variable, passed to another function, or given back by one. A function that takes or gives back a function is a higher-order function. You used one in Grades 10 and 12: sorted(names, key=len) and min(villages, key=...) take a function that says what to compare. A lambda is a small function written in one expression, with no name: lambda s: s[1] >= 35 takes s and gives back whether its second item is 35 or more.

Three higher-order functions do most of the work on lists in functional programming:

  • map(f, items) applies f to every item;
  • filter(f, items) keeps the items for which f gives True;
  • reduce(f, items) (from the functools module) combines all the items into one, two at a time: reduce(f, [a, b, c]) is f(f(a, b), c). Some languages call it fold.
from functools import reduce students = [("Aarati", 91), ("Dawa", 34), ("Kiran", 67),            ("Sabina", 78), ("Nabin", 45)] passed = list(filter(lambda s: s[1] >= 35, students))names = list(map(lambda s: s[0].upper(), passed))total = reduce(lambda a, b: a + b, [m for _, m in students]) print(passed)print(names)print(total, "marks in all, average", total / len(students))

Output

[('Aarati', 91), ('Kiran', 67), ('Sabina', 78), ('Nabin', 45)]['AARATI', 'KIRAN', 'SABINA', 'NABIN']315 marks in all, average 63.0

Nothing was changed: each step made a new list from the last. In Python, a comprehension (Grade 10) says the same thing more readably, and is what most Python programmers write:

students = [("Aarati", 91), ("Dawa", 34), ("Kiran", 67),            ("Sabina", 78), ("Nabin", 45)] passed = [s for s in students if s[1] >= 35]names = [name.upper() for name, marks in passed]total = sum(marks for name, marks in students)best = max(students, key=lambda s: s[1]) print(names)print(total, best)

Output

['AARATI', 'KIRAN', 'SABINA', 'NABIN']315 ('Aarati', 91)

Which of these functions is pure?

Pick an answer to see why.

Data that never changes

Data that can’t be changed once it is made is immutable. In Python, numbers, strings and tuples are immutable; lists and dictionaries are mutable. Why does it matter? Look at this bug:

CORE = ["English", "Nepali", "Maths", "Science"] def subjects_for(grade):    subjects = CORE    if grade == 11:        subjects.append("Computer")    return subjects print(subjects_for(11))print(subjects_for(10))

Output

['English', 'Nepali', 'Maths', 'Science', 'Computer']['English', 'Nepali', 'Maths', 'Science', 'Computer']

Grade 10 has Computer too. subjects = CORE doesn’t copy the list: both names refer to the same list object (Grade 11’s names and objects), so append changed the core list for everyone. With an immutable tuple, that mistake can’t happen: a tuple has no append, so the only way is to make a new tuple.

CORE = ("English", "Nepali", "Maths", "Science") def subjects_for(grade):    if grade == 11:        return CORE + ("Computer",)    return CORE print(subjects_for(11))print(subjects_for(10))

Output

('English', 'Nepali', 'Maths', 'Science', 'Computer')('English', 'Nepali', 'Maths', 'Science')

Data that never changes is safe to share: between functions, between parts of a program written by different people, and between threads running at once, since nobody can change it under anyone else’s feet. Functional languages make most data immutable, and a new version is made instead of changing the old one, just as git makes a new commit instead of changing an old one.

Say what, not how

In Grade 10 you asked a database a question in SQL:

SELECT name, marks FROM results WHERE marks >= 35 ORDER BY marks DESC;

Look at what it doesn’t say. It doesn’t say which row to look at first, whether to use an index (Grade 10), how to sort, or when to stop. It says what result is wanted, and the database works out how. This is declarative programming. The same question in Python says how, step by step:

answer = []for name, marks in results:    if marks >= 35:        answer.append((name, marks))answer.sort(key=lambda row: row[1], reverse=True)

Declarative languages leave the how to the system, which can then choose a fast way, and change it later, without the question changing. HTML and CSS (Grade 8) are declarative too: they say what the page should look like, and the browser works out how to draw it. So is a regular expression (Grade 11), which says what a pattern is, not how to search for it.

Facts and rules: logic programming

One kind of declarative programming goes further. Logic programming gives the computer facts (what is true) and rules (what follows from what), and then asks it questions; the computer finds the answers by working out what the facts and rules make true.

Nepali is a good language to try it in. English has one word, “uncle”; Nepali has a different word depending on whose brother he is and whether he is older or younger: your father’s younger brother is your kaka, his elder brother your thulobuwa, your mother’s brother your mama. Those are rules. Here is a small facts-and-rules engine in Python, with one family’s facts and the rules for some Nepali family words. Read the facts and rules first; the engine in the middle is short, and you don’t need to follow every line of it to use it.

# Facts: what we are told about one familyfacts = {    ("parent", "Hari", "Ram"), ("parent", "Hari", "Gita"), ("parent", "Hari", "Shyam"),    ("parent", "Ram", "Aarati"), ("parent", "Ram", "Bikash"),    ("parent", "Gita", "Nabin"), ("parent", "Shyam", "Puja"),    ("male", "Hari"), ("male", "Ram"), ("male", "Shyam"), ("male", "Bikash"), ("male", "Nabin"),    ("female", "Gita"), ("female", "Aarati"), ("female", "Puja"),    ("born", "Ram", 1980), ("born", "Gita", 1982), ("born", "Shyam", 1984),    ("born", "Aarati", 2008), ("born", "Bikash", 2011),} # Rules: "the head is true if every part of the body is true"# Words starting with ? are variables.rules = [    (("sibling", "?a", "?b"), [("parent", "?p", "?a"), ("parent", "?p", "?b"), ("!=", "?a", "?b")]),    (("older", "?a", "?b"), [("born", "?a", "?x"), ("born", "?b", "?y"), ("<", "?x", "?y")]),    (("dai", "?a", "?b"), [("sibling", "?a", "?b"), ("male", "?a"), ("older", "?a", "?b")]),    (("bhai", "?a", "?b"), [("sibling", "?a", "?b"), ("male", "?a"), ("older", "?b", "?a")]),    (("didi", "?a", "?b"), [("sibling", "?a", "?b"), ("female", "?a"), ("older", "?a", "?b")]),    (("kaka", "?a", "?c"), [("parent", "?f", "?c"), ("male", "?f"), ("bhai", "?a", "?f")]),    (("thulobuwa", "?a", "?c"), [("parent", "?f", "?c"), ("male", "?f"), ("dai", "?a", "?f")]),    (("mama", "?a", "?c"), [("parent", "?m", "?c"), ("female", "?m"), ("sibling", "?a", "?m"), ("male", "?a")]),    (("ancestor", "?a", "?c"), [("parent", "?a", "?c")]),    (("ancestor", "?a", "?c"), [("parent", "?a", "?b"), ("ancestor", "?b", "?c")]),] def match(pattern, fact, found):    """Bindings that make pattern equal fact, or None."""    if len(pattern) != len(fact):        return None    found = dict(found)    for p, f in zip(pattern, fact):        if isinstance(p, str) and p.startswith("?"):            if found.setdefault(p, f) != f:                return None        elif p != f:            return None    return found def solve(body, found):    """Every set of bindings that makes every part of body true."""    if not body:        yield found        return    first, rest = body[0], body[1:]    if first[0] in ("!=", "<"):        a, b = (found.get(x, x) for x in first[1:])        if (a != b) if first[0] == "!=" else (a < b):            yield from solve(rest, found)        return    for fact in list(facts):        more = match(first, fact, found)        if more is not None:            yield from solve(rest, more) # Apply every rule until nothing new can be worked outwhile True:    new = set()    for head, body in rules:        for found in solve(body, {}):            new.add(tuple(found.get(x, x) for x in head))    if new <= facts:        break    facts |= new def ask(*pattern):    return sorted(m["?who"] for f in facts if (m := match(pattern, f, {}))) print("Aarati's kaka:", ask("kaka", "?who", "Aarati"))print("Puja's thulobuwa:", ask("thulobuwa", "?who", "Puja"))print("Nabin's mama:", ask("mama", "?who", "Nabin"))print("Bikash's didi:", ask("didi", "?who", "Bikash"))print("Puja's ancestors:", ask("ancestor", "?who", "Puja"))

Output

Aarati's kaka: ['Shyam']Puja's thulobuwa: ['Ram']Nabin's mama: ['Ram', 'Shyam']Bikash's didi: ['Aarati']Puja's ancestors: ['Hari', 'Shyam']

Nowhere does the program say how to find Aarati’s kaka. The rule says only what a kaka is: someone who is the younger brother (bhai) of your father. ?a, ?c and ?f are variables, and the engine tries the facts until a rule’s whole body is true. Rules build on rules: kaka uses bhai, which uses sibling and older. The last two rules are recursive, like Grade 11’s functions: your ancestors are your parents, and your parents’ ancestors.

  • match() (lines 27–38) checks whether a fact fits a pattern, filling in the variables: ("parent", "?f", "Aarati") fits ("parent", "Ram", "Aarati") with ?f as Ram.
  • solve() (lines 40–54) finds every way to make a rule’s body true, one part at a time, trying each fact for the first part and the rest with what it found so far; != and < are checked directly.
  • The while loop (lines 57–64) applies every rule to the facts, adds whatever new facts the rules give, and repeats until nothing new appears. Working forward from facts like this is called forward chaining.

A table of paradigms

A programming paradigm is a way of thinking about and organizing programs. Most languages, Python among them, let you mix several.

Paradigm The program is… Met in Good for
Low-level (assembly, machine code) instructions for the processor itself, one tiny step each Grade 11, Unit 4 the fastest code, small devices, understanding the machine
Imperative and procedural steps in order that change variables, grouped into functions Grades 8–10 most everyday programs; easy to follow step by step
Object-oriented objects that hold data together with their methods Grade 11, Unit 1 big programs modeling many kinds of thing: games, apps, simulations
Event-driven blocks of code that run when something happens Grade 6 (when blocks), Grade 9 (web pages) apps, web pages, devices that wait for a tap, a key or a sensor
Functional pure functions combined, data that doesn’t change this unit working on data, especially huge data on many computers; code that is easy to test
Declarative: query languages such as SQL a description of the result wanted Grade 10, Unit 3 asking databases questions
Declarative: logic programming facts and rules, then questions this unit rules, puzzles, relationships, checking that things follow

SELECT name FROM students WHERE gpa >= 3.6; is an example of which kind of programming?

Pick an answer to see why.

Patterns in code that lasts

Programmers kept meeting the same problems in different programs, and kept solving them the same few ways. A design pattern is a named, well-tried shape of solution to a problem that comes up again and again. Knowing them helps you recognize what a piece of code is doing, and gives a team short names to talk about designs. Three you will meet in real code:

Observer: tell everyone who asked

When the results are published, several things must happen: an SMS to every parent, the notice board updated, the website updated, and next year maybe more. If the results code called each of them by name, every new one would mean changing it. Instead, each part that wants to know subscribes, and the results code just tells everyone on its list. The thing being watched doesn’t need to know who is watching.

class Results:    """Tells everyone who asked, when the results are published."""    def __init__(self):        self.listeners = []     def subscribe(self, listener):        self.listeners.append(listener)     def publish(self, grade, gpas):        for listener in self.listeners:            listener(grade, gpas) def sms_parents(grade, gpas):    print(f"SMS: Grade {grade} results are out. {len(gpas)} texts sent.") def notice_board(grade, gpas):    print(f"Notice board: Grade {grade}, highest GPA {max(gpas.values())}") results = Results()results.subscribe(sms_parents)results.subscribe(notice_board)results.publish(12, {"Aarati": 3.6, "Kiran": 3.2, "Sabina": 2.8})

Output

SMS: Grade 12 results are out. 3 texts sent.Notice board: Grade 12, highest GPA 3.6

This is the observer pattern. You have used it since Grade 9: button.addEventListener("click", ...) subscribes a function to a button’s clicks, and every event in a web page works this way.

Strategy: pass in the part that varies

A school bill is worked out the same way for every student, except the discount: none, 10% off for a second child at the school, or a full scholarship. Instead of an if for every kind of discount inside the bill function, the discount is passed in as a function. The bill function stays the same however many kinds of discount are added.

def no_discount(fee):    return fee def sibling_discount(fee):    return fee * 0.9          # 10% off for a second child at the school def scholarship(fee):    return 0 def monthly_bill(fee, discount):    """discount is a strategy: any function that takes a fee and gives one back."""    return round(discount(fee)) print(monthly_bill(4500, no_discount))print(monthly_bill(4500, sibling_discount))print(monthly_bill(4500, scholarship))

Output

450040500

This is the strategy pattern: a family of ways of doing one step, each swappable for another. sorted(..., key=...) is one too: the sorting is fixed, the way of comparing is passed in. The principle behind it is often put as “find what varies, and keep it apart from what stays the same”.

Facade: one simple door

Publishing results means loading the marks, working out every GPA, making report cards and sending SMS messages, each done by its own part of the program. The office assistant’s screen shouldn’t need to know all that. A facade is one simple function, or class, in front of a complicated set of parts, giving the rest of the program one door to use.

# The parts behind the scenes: each does one jobBANDS = [(90, 4.0), (80, 3.6), (70, 3.2), (60, 2.8), (50, 2.4), (40, 2.0), (35, 1.6)] def load_marks(grade):    return {"Aarati": [95, 85, 75], "Kiran": [72, 81, 68]} def grade_point(marks):    return next((point for lowest, point in BANDS if marks >= lowest), 0.0) def gpa(all_marks):    return round(sum(map(grade_point, all_marks)) / len(all_marks), 2) def report_card(name, value):    return f"Report card: {name}, GPA {value}" def send_sms(name):    print(f"(SMS to {name}'s parents)") # The facade: one simple function in front of them alldef publish_results(grade):    for name, all_marks in load_marks(grade).items():        print(report_card(name, gpa(all_marks)))        send_sms(name) publish_results(12)

Output

Report card: Aarati, GPA 3.6(SMS to Aarati's parents)Report card: Kiran, GPA 3.2(SMS to Kiran's parents)

If the SMS company changes, only send_sms() changes; nothing that calls publish_results() notices. Python’s own turtle module is a facade: forward(100) hides all the drawing behind it.

A weather app must update its screen, its widget on the home screen and its rain alarm whenever new data arrives, and more such parts may be added later. Which pattern fits?

Pick an answer to see why.

A map app lets the user choose “fastest”, “shortest” or “no tolls”, and works out the route the same way except for how it scores a road. Which pattern fits?

Pick an answer to see why.

Code an AI wrote

Programmers now use AI to write code every day. In Grades 8 and 10 you tested AI-written code against its requirements Grade 10: Checking code against its requirements. In a team, the rule is simple: AI-written code is checked like anyone else’s, and more carefully, because it looks confident and tidy whether it is right or wrong, and because nobody on the team has thought it through line by line.

Sabina asks a chatbot for a function that gives Nepal’s fiscal year for a date in Bikram Sambat. Nepal’s fiscal year starts on Shrawan 1 (the first day of month 4) and ends on the last day of Asar, so a date in Poush 2082 is in fiscal year 2082/83. Before using the answer, she turns the requirements into tests, with normal and boundary data:

def fiscal_year(year, month, day):    """Nepal's fiscal year for a date in Bikram Sambat, such as '2082/83'."""    if month > 4:        return f"{year}/{(year + 1) % 100}"    return f"{year - 1}/{year % 100}" # The requirements, turned into teststests = [    ((2082, 9, 15), "2082/83"),   # normal: Poush, in the middle of the year    ((2083, 2, 10), "2082/83"),   # normal: Jestha, before the new year    ((2083, 3, 32), "2082/83"),   # boundary: the last day of Asar    ((2083, 4, 1), "2083/84"),    # boundary: Shrawan 1, the first day    ((2099, 6, 1), "2099/00"),    # boundary: the century turns]for (y, m, d), want in tests:    got = fiscal_year(y, m, d)    print("PASS" if got == want else "FAIL", (y, m, d), "gave", got, "| wanted", want)

Output

PASS (2082, 9, 15) gave 2082/83 | wanted 2082/83PASS (2083, 2, 10) gave 2082/83 | wanted 2082/83PASS (2083, 3, 32) gave 2082/83 | wanted 2082/83FAIL (2083, 4, 1) gave 2082/83 | wanted 2083/84FAIL (2099, 6, 1) gave 2099/0 | wanted 2099/00

The normal cases pass, which is why the code looked right. The boundaries fail: month > 4 puts the whole of Shrawan, the first month of the new year, in the old year, and % 100 gives 0 instead of 00. And nothing refuses a month 13 or a day 40.

Checking AI-written code, in order:

  1. Requirements first. Write down what it must do before you ask, so you judge the answer against them, not against how convincing it looks.
  2. Test it, with normal, boundary and erroneous data, as above. Code that runs is not code that is right.
  3. Read every line, and review it with the same checklist as a teammate’s code. If you can’t explain a line, you can’t use it.
  4. Check what it uses. AI sometimes names a library or a function that doesn’t exist, or an old version with known flaws. Check that each one is real, current and from its real publisher before installing anything: people have published harmful packages under names that AI tools tend to invent.
  5. Check it is yours to use. Long passages may match someone else’s code under a license; follow the license or rewrite it.
  6. Record where AI was used: in the commit message (“First draft by an AI chatbot; tested and corrected”) and in the README, as the team did. A school or exam may have its own rules for AI in coursework (Grade 8’s using it honestly): follow them, and say what you used.

An AI tool gives a function that runs without errors on the three examples you tried. What should you do before merging it?

Pick an answer to see why.

Open source: using it, crediting it, joining in

Almost every program today is built on open-source code (Grade 9): the language, its libraries, the test runner, the editor. In Grade 9 you met the licenses (permissive, such as MIT; copyleft, such as the GPL) and how to give credit in code (Software licenses, Giving credit in code). In a team project:

  • Choose a license for your own code, and put it in a LICENSE file. A permissive license (MIT) lets anyone use your code, even in a program they sell; a copyleft one (GPL) makes sure changed versions stay open too. With no license, nobody else may legally reuse it.
  • List what you use: every library with its version and license (in Python, a file called requirements.txt lists the libraries a program needs), and every borrowed piece of code, photo, icon and font with its source. Check that each license allows your use: a copyleft library doesn’t fit a program you plan to keep closed.
  • Record AI help the same way, as the team’s README does.

You can also give back. Open-source projects are run by their communities, and most welcome help from newcomers. Three ways to start:

  1. A good bug report. Search first, in case it has been reported. Then say exactly what you did, what you expected, what happened instead (with the exact error message), and your device and version. A report that lets a stranger see the bug for themselves is a real contribution.
  2. A translation. Many apps can be translated through a website, a sentence at a time, with no programming. Nepali versions of free software exist because volunteers translated them; an app in good Nepali is one more app that people who don’t read English can use.
  3. A fix. Find an issue marked as good for newcomers. Copy the project to your own account (a fork), make a branch, fix it with a test, and open a pull request, following the project’s guide for contributors (often a file called CONTRIBUTING). Expect review comments, and answer them.

Be patient and polite: the people who look after projects are often volunteers, in their spare time.

Your team’s app uses a library under a copyleft license (the GPL), and you share the app with others. What does the license ask?

Pick an answer to see why.

Ways to organize the work

A team must also agree in what order the work happens. These agreed ways of working are called development methods (or software development life cycles). All of them have the same kinds of work in them: analysis (finding out what is needed: the requirements), design, implementation (writing the code), testing, deployment (putting it into use), evaluation against the requirements, and maintenance. They differ in how those are arranged in time.

WaterfallRequirementsDesignImplementationTestingDeploymentMaintenanceeach stage finished before the nextAgilePlanBuildTestReviewone sprint:1 to 4 weeksv1v2v3v4a working version after every sprint,shown to users, then improvedSpiral1 Set goals2 Find andreduce risks3 Build and test4 Plan thenext roundeach round bigger, risks first WaterfallRequirementsDesignImplementationTestingDeploymentMaintenanceeach stage finished before the nextAgilePlanBuildTestReviewone sprint:1 to 4 weeksv1v2v3v4a working version after every sprint,shown to users, then improvedSpiral1 Set goals2 Find andreduce risks3 Build and test4 Plan thenext roundeach round bigger, risks first
Figure 3.2 Three ways to organize the same kinds of work. In the waterfall model each stage is finished before the next begins. In agile methods all the kinds of work are done in every short round, a sprint, which ends with a working version. In the spiral model each round grows outwards and begins by finding the biggest risks.

Waterfall

In the waterfall model, the stages happen once, in order, like water falling down steps: all the requirements are written and signed, then the whole design is made, then all the code is written, then it is all tested, then delivered. Each stage ends with documents that the next stage works from.

It suits projects whose requirements are clear and won’t change, and where mistakes are very costly: a bridge’s control system, a bank’s core accounts, software that must be approved by a regulator before it is used. Its weakness is that users see nothing working until near the end. If the requirements were misunderstood, which is common, it is found late, when changing them costs the most.

Agile, with extreme programming

Agile methods do a little of everything, again and again. The work is split into short rounds called sprints or iterations, usually one to four weeks. Each sprint takes the most important items from a list of what is wanted (the product backlog, often written as user stories, Grade 11), designs, builds and tests them, and ends with a working version that users can try. What users say goes into the plan for the next sprint. Short daily meetings (a stand-up, a few minutes each morning: what I did, what I will do, what is in my way) keep the team in step.

In February 2001, seventeen programmers met at a ski resort in Utah, in the United States, and wrote the Manifesto for Agile Software Development. Its four values:

Individuals and interactions over processes and tools
Working software over comprehensive documentation
Customer collaboration over contract negotiation
Responding to change over following a plan

It adds that while there is value in the items on the right, they value the items on the left more. So agile doesn’t mean no plan and no documents; it means a plan that expects to change.

Agile suits projects whose users can be asked often and whose needs aren’t fully known at the start, which is most apps and websites. Its risks: without discipline it can become no plan at all, and a customer who isn’t available to try each version takes away its main strength.

Extreme programming (XP), made by Kent Beck and others in the late 1990s, is an agile method that takes good practices to the extreme: tests written before the code (test-driven development), pair programming (two people at one computer, one typing and one reviewing as they go, swapping often), very small releases, merging everyone’s work many times a day with every test run (continuous integration), and the customer working beside the team. Rapid application development (RAD) is an older idea in the same spirit: build prototypes quickly, show them to users and improve them, instead of writing long specifications.

Spiral

The spiral model, described by Barry Boehm in 1986, puts risk at the center. The project goes round in rounds, each bigger than the last. Each round sets its goals, then asks “what is most likely to make this project fail?” and deals with that first, often by building a prototype to find out, then builds and tests the next part, then plans the next round. It suits big, expensive projects with serious unknowns: a new kind of system nobody has built before.

A team builds an app for a cooperative whose members aren’t sure what they want, and who can meet the team every two weeks. Which method suits it best?

Pick an answer to see why.

Changing over to the new system

When the results program is ready, the school must move from its old way of working, a spreadsheet, to the new one. This is the changeover, or implementation, and there are four ways to do it:

  • Direct changeover: the old system stops and the new one starts on the same day. Quick and cheap, but if the new system fails there is nothing to fall back on.
  • Parallel running: the old and new systems run side by side for a while, doing the same work, and their results are compared. Safe, and any difference shows a mistake, but everything is done twice.
  • Phased changeover: the new system replaces the old one a part at a time: report cards first, then fees, then attendance. Each part can settle before the next, but the old and new must work together in between.
  • Pilot changeover: the whole new system is used first by one group, Grade 11 or one branch, and then by everyone. Problems hurt only the pilot group, and they become experts who help the rest.
old systemnew systemDirectone day the old, the next day the newParallelboth run, and results are comparedoldnewPhasedone part at a time, for everyonereport cardsfeesattendancePilotone group first, then the restGrade 11other gradestime → old systemnew systemDirectone day the old, the next day the newwhole schoolParallelboth run, and results are comparedoldnewPhasedone part at a time, for everyonereport cardsfeesattendancePilotone group first, then the restGrade 11other gradestime →
Figure 3.3 The four ways to change over from an old system (gray) to a new one (blue), on the same time line, for a school replacing its fee and results spreadsheet.

For the school’s results, the team chooses parallel running for the first terminal exam: the office enters every mark in both, and compares every grade. A wrong grade on a mark sheet is too costly to risk a direct changeover, and one exam’s double work is a small price.

Try it

Which way of changing over?

Sort each changeover.

On Saturday the old fee spreadsheet is stopped, and from Sunday every class uses the new program.

Which way of changing over is it?

Tap a group above.

Card
1 of 8
Right the first time
0

Maintenance

Once a system is in use, the work on it goes on for as long as it is used. Changes made after delivery are maintenance, and they come in three kinds:

  • Corrective maintenance fixes faults found in use: a student with no marks made the report stop.
  • Adaptive maintenance changes the program because the world around it changed: new grading rules, a new tax rate, a new version of the phone’s operating system.
  • Perfective maintenance makes a working program better: faster report cards, a Nepali version users asked for, clearer code.

Over a program’s whole life, maintenance usually costs more than building the first version. Everything in this unit (version control, tests, reviews, documentation, clear patterns) is there to make that work cheaper and safer.

Try it

Which kind of maintenance?

Sort each change to the results program and other systems.

A student with no marks yet made the report stop with an error. It is fixed.

Which kind of maintenance is it?

Tap a group above.

Card
1 of 8
Right the first time
0

Planning with a Gantt chart

A project plan says what will be done, by whom, and when, and which tasks must wait for others (Grade 10’s plan on a task board). A Gantt chart draws it on a time line: one row per task, a bar from its start to its end, and diamonds for milestones, the moments a stage is finished (design agreed, beta version, handover).

123456789WeekInterview users, write requirementsallPaper design, tested with teachersSabinaDesign the databaseDawaBuild marks entryKiranBuild report cards and GPAAaratiWrite the README and help pageDawaBeta test with five teachersallFix and improveallAcceptance test and handoveralldesign agreedbeta versionhandovercritical pathtime to spare 123456789WeekInterview users, write requirements (all)Paper design, tested with teachers (Sabina)Design the database (Dawa)Build marks entry (Kiran)Build report cards and GPA (Aarati)Write the README and help page (Dawa)Beta test with five teachers (all)Fix and improve (all)Acceptance test and handover (all)design agreedbeta versionhandovercritical pathtime to spare
Figure 3.4 The results program’s plan as a Gantt chart, over 9 weeks. Each task starts as soon as the tasks it waits for are done. The red tasks are the critical path: if any of them is late, the whole project is late. The blue tasks have time to spare, shown dotted.

Reading Figure 3.4:

  • Marks entry can’t start until both the paper design and the database design are finished, so it starts in week 4.
  • The critical path is the longest chain of tasks that wait for each other, here requirements → design → marks entry → beta test → fixes → handover: 2 + 1 + 3 + 1 + 1 + 1 = 9 weeks. A week lost on any of these makes the whole project a week late.
  • Tasks off the critical path have float (slack): the README can start up to three weeks late, and report cards one week late, without moving the end date. That is where to find time when someone is ill or exams come.

A Gantt chart is a plan, not a promise. Keep it up to date: when a task slips, move its bar, and see at once what else moves.

In a project plan, a task on the critical path finishes two weeks late. What happens to the project?

Pick an answer to see why.

Project: software built to last, in a team

In a team of three or four, build a small program for a real user: a fee calculator for a tuition class, a library’s overdue list, a cooperative’s savings statement, a cricket club’s scores. Use the workflow of this unit from start to finish. It is a rehearsal for your capstone (Unit 9). This needs a computer with Python and git for at least one member; with only phones, plan and write the documents, and run your functions and tests in this page’s programs.

  1. Plan. Write the requirements with your user, numbered so they can be tested. Choose a development method and say why. Draw a Gantt chart with every task, who does it, the critical path and three milestones.
  2. Set up. One repository, shared on a remote. Agree your workflow (branches, pull requests, who reviews) and each member’s role (who keeps the plan, who looks after the tests, who talks to the user), and write both into a short design document.
  3. Build in branches. Every member commits on their own branch, with clear messages, and merges through a reviewed pull request. Settle at least one merge conflict, and say how.
  4. Test. Automated tests for every function, with normal, boundary and erroneous data, run before every merge. Say which tests are black-box and which white-box.
  5. Debug. Find at least one bug with a debugger, and write down how: the breakpoint, what you watched, what you found.
  6. Review. Review each other’s code with the checklist, in writing, and answer every comment.
  7. Document. A README a newcomer can follow to run the program and its tests; docstrings; a LICENSE; a list of libraries and anything borrowed, and where AI was used, if it was.
  8. Hand over. Plan the changeover (which of the four ways, and why) and an acceptance test with your user. Then evaluate the program against its requirements and its users: is it correct (the tests), does it save the user time, is it easy to use for everyone who must, and does it keep only the data it needs? Last, list one change of each kind of maintenance it is likely to need.

Present your project with your git history (git log --oneline --graph), your test results, your Gantt chart (planned and what really happened), and what you would do differently.

In short

Exercise

What does git commit do?

Pick an answer to see why.

A function accepts ages from 18 to 60. Which set of test data includes normal, boundary and erroneous data?

Pick an answer to see why.

Testing that the report module and the SMS module work correctly together is:

Pick an answer to see why.

A program is changed because the government changed the VAT rate. Which kind of maintenance is this?

Pick an answer to see why.

Which function is pure?

Pick an answer to see why.

a.

Explain what each of these git commands does: git add, git commit, git log, git diff, git branch/git switch -c, git merge, git push, git pull.

Answer
b.

What is a merge conflict? Why does it happen, and how is it resolved?

Answer
c.

A program converts a temperature typed in °C, which must be from −50 to 60, for a weather station in Jumla. Give two examples each of normal, boundary and erroneous test data, saying what each tests.

Answer
d.

Explain the difference between black-box and white-box testing, with an example of each for grade_for().

Answer
e.

What are a breakpoint, step into, step over and step out in a debugger? When is a debugger quicker than adding print() calls?

Answer
f.

Write four points for a code review checklist, and rewrite this review comment so that it is useful: “This code is bad. Fix it.”

Answer
g.

What is a pure function? Give two reasons why pure functions make programs easier to test and to share between parts of a team’s program.

Answer
h.

Rewrite this loop using (i) filter and map with lambdas, and (ii) a list comprehension. What does it give for prices = [120, 450, 80, 900]?

cheap = []for p in prices:    if p < 500:        cheap.append(p * 1.13)
Answer
i.

Using the family engine’s style, write a rule for hajurbuwa, your father’s father, and say what the engine would answer for Aarati.

Answer
j.

Make a table of the programming paradigms met in this course, with an example language or tool for each and one kind of job each suits.

Answer
k.

Describe the observer, strategy and facade patterns, each with an example from an app you use.

Answer
l.

Sabina used an AI chatbot to write a function. List the steps she should take before it is merged, and say how and where she should record that AI was used.

Answer
m.

Compare the waterfall and agile methods: describe each, and give one advantage and one disadvantage of each. Then say what extreme programming and the spiral model add.

Answer
n.

A district hospital is replacing its paper patient records with a computer system. Describe the four ways to change over, and recommend one, with reasons.

Answer
o.

Name and describe the three kinds of maintenance, with an example of each for a school’s fee program.

Answer
p.

A team’s tasks: A requirements (2 weeks); B design (2 weeks, after A); C build the app (4 weeks, after B); D write the user guide (2 weeks, after B); E test with users (1 week, after C and D). Draw the Gantt chart, find the critical path and the project’s length, and say which task has float, and how much.

Answer