Software Design & Development · Applied Cycle

SDD20 — Mini-project 2: Arrays of Records and File I/O

📅 Tue 29 Sep 2026 · P3 (single)
~60 minutes
Learning intentions
Success criteria
Warm up — recap from SDD19
Answer all three questions, then check your answers.
Question 1
What are the five stages of the full development cycle, in order: analysis, design, implementation, testing, and ______?
Question 2
SDD19's mini-project deliberately excluded two things, saved for this lesson: file handling and ______?
Question 3
Which mode opens a file for writing, erasing any existing content?

Key vocabulary

Persistent storage
Data saved to a file so it still exists after the program finishes running, rather than being lost when the program ends.
Array of records
A collection of structured items, each with multiple named fields, stored together in one array.
Sequential file
A file read or written one line at a time, from beginning to end, rather than jumping to a specific position.
Round trip
Reading data from a file, changing it in memory, and writing the updated version back — so the file reflects the change.
File-related erroneous data
Invalid input specific to file handling, such as an empty file or a line with missing fields, that a robust program should not crash on.

Combining records and file handling — the second full cycle

Why this lesson builds on SDD19, not replaces it

SDD19 deliberately kept its scenario narrow — one array, no records, no files — so the five-stage cycle itself could be learned without extra complexity. This lesson adds back exactly what SDD19 left out: arrays of records (SDD6, SDD15) and file handling (SDD11, SDD12), combined together in one program. This combination — reading records from a file, searching or processing them with a standard algorithm, and writing the changed records back — is the closest rehearsal this course has for the actual assignment (Task 1, 25 marks, 6 supervised hours, Feb–Mar 2027), which expects exactly this kind of structured, persistent, file-backed program.

1
Analysis
Requirements for stored, structured data
2
Design
Structure including file I/O
3
Implementation
Read, search, update, write
4
Testing
Including file-specific edge cases
5
Evaluation
Judge the whole program

The problem for this lesson

A school library wants a small program to manage its book records: each book has a title, an author, and a number of copies available. The records are stored in a CSV file so they persist between runs. The program should read the records, find a specific book by title using a standard algorithm, issue a loan by reducing that book's copies by one (only if a copy is actually available), and then write the updated records back to the file so the change is saved.

Stage 1 — Analysis

The functional requirements for this problem: read an array of book records (title, author, copies) from a CSV file; find a specific book by title; reduce a found book's copy count by one when a loan is issued, but only if copies are available; and write the updated records back to the file so the change persists. Each of these becomes a specific, testable requirement — notably, "only if copies are available" is itself a functional requirement, not just a nice-to-have, since a program that lets copies go negative has failed this requirement even if it never crashes.

Stage 2 — Design

The design now needs to show file input and output alongside the function/procedure calls already familiar from SDD19: readBooks(filename) (a function, reads the file and returns an array of records); findBookByTitle(books, title) (a function, the standard linear search algorithm reused from SDD13/15, now searching a record field); issueLoan(books, index) (a procedure, updates a record's field in place); and writeBooks(books, filename) (a procedure, writes the array back to the file). Sketching this data flow — file → array of records → search → update → file — before coding prevents a common design gap: forgetting that writeBooks needs to run after issueLoan, not before, or the change is never actually saved.

Stage 3 — Implementation

Implementation combines three previously separate skills into one program: file reading (SDD11) builds the array of records; the linear search standard algorithm (SDD13, extended to records exactly as in SDD15) finds the right one; and file writing (SDD12) saves the result. The record itself is implemented as an @dataclass, consistent with every records lesson since SDD6. Note that issueLoan modifies a record's field directly (books[index].copies = books[index].copies - 1) — this only works correctly because findBookByTitle returned an index, not a copy of the record, exactly the same design point SDD15 made about minimum/maximum-finding on records.

Stage 4 — Testing

Testing this program needs the same three categories as every previous testing lesson (SDD16), but now with file-specific cases included: a normal case (a valid title with copies available); an extreme case (a valid title with exactly zero copies available, testing the "only if available" requirement at its boundary); and an erroneous case specific to file handling, such as an empty file, which SDD11's reading pattern is not designed to survive. A test plan that only tests "does it find the book" without testing the zero-copies boundary or the empty-file case would miss exactly the kind of requirement this problem depends on.

Stage 5 — Evaluation

Evaluation applies SDD18's five criteria to the combined program: fitness for purpose (does it correctly read, find, update, and save records); efficient use of coding constructs (is the standard linear search algorithm used appropriately, rather than something needlessly complex); usability (are loan confirmations and "no copies available" messages clear); maintainability (are the four sub-programs clearly named and each doing one job); and robustness (does the program survive an empty or malformed file, and does it correctly refuse a loan when no copies are available). This is a genuinely larger evaluation than SDD19's, since there are now four sub-programs and a file dependency to judge, not three sub-programs operating purely in memory.

Worked examples

Example 1 — Analysis and design
1
Functional requirements:
  • FR1 — Read an array of book records (title, author, copies) from a CSV file
  • FR2 — Find a specific book by title
  • FR3 — Reduce a found book's copies by one when a loan is issued, only if copies are available
  • FR4 — Write the updated records back to the file
1
readBooks(filename)
Open the file, read and split each line, build an array of Book records
2
findBookByTitle(books, title)
Linear search on the .title field, returns index or -1
3
issueLoan(books, index)
If copies > 0, decrease by 1; otherwise report unavailable
4
writeBooks(books, filename)
Rebuild each record as a CSV line and write back, overwriting the file
Example 2 — The starting records
1
The library's CSV file, one book per line (title,author,copies):
Mystery of the Blue Loch,A. Ferguson,3
Highland Adventures,R. Bell,5
The Coding Puzzle,S. Mackay,0
Edinburgh Nights,L. Grant,2
The Silent Algorithm,K. Reid,4
index 0
titleMystery of the Blue Loch
copies3
index 1
titleHighland Adventures
copies5
index 2
titleThe Coding Puzzle
copies0
index 3
titleEdinburgh Nights
copies2
index 4
titleThe Silent Algorithm
copies4
Index 3 (Edinburgh Nights) is highlighted — this is the record Example 3 will search for and update.
Example 3 — Implementation: read, search, update, write
1
from dataclasses import dataclass

@dataclass
class Book:
    title: str
    author: str
    copies: int

def readBooks(fileContents):
    books = []
    lines = fileContents.split("\n")
    for line in lines:
        fields = line.split(",")
        title = fields[0]
        author = fields[1]
        copies = int(fields[2])
        books.append(Book(title, author, copies))
    return books

def findBookByTitle(books, target):
    index = 0
    found = False
    while index < len(books) and not found:
        if books[index].title == target:
            found = True
        else:
            index = index + 1
    if found:
        return index
    else:
        return -1

def issueLoan(books, index):
    if books[index].copies > 0:
        books[index].copies = books[index].copies - 1
        print("Loan issued for", books[index].title,
              "- copies remaining:", books[index].copies)
    else:
        print("Cannot issue loan -", books[index].title,
              "has no copies available")

def writeBooks(books):
    lines = []
    for book in books:
        line = book.title + "," + book.author + "," + str(book.copies)
        lines.append(line)
    return "\n".join(lines)
Confirmed by an actual Python run: findBookByTitle(libraryBooks, "Edinburgh Nights")3; issueLoan(libraryBooks, 3) reduces its copies from 2 to 1 and prints a confirmation; issueLoan(libraryBooks, 2) ("The Coding Puzzle", 0 copies) correctly refuses the loan and prints "Cannot issue loan"; writeBooks(libraryBooks) rebuilds all five lines, with Edinburgh Nights now showing 1 instead of 2 — this is the "round trip" from file, to record, to updated record, back to file.
Example 4 — Testing: a test plan including file-specific cases
1
A test plan tracing back to FR1–FR4, including a file-specific erroneous case:
Test dataTypeExpected resultActual resultPass/fail
Find "Edinburgh Nights", issue loanNormalCopies reduced 2 → 1, loan confirmedCopies reduced 2 → 1, loan confirmedPass
Find "The Coding Puzzle" (0 copies), issue loanExtreme — boundary (zero copies)Loan refused, copies stay at 0Loan refused, copies stay at 0Pass
Find "Not A Real Book"Erroneous — title not in fileReturns -1, no crashReturns -1, no crashPass
Read an empty file ("")Erroneous — file-specificA clear message, no crashIndexError: list index out of rangeFail
Confirmed against an actual Python run: the first three cases behave exactly as required. The empty-file case fails — readBooks("") still produces one "line" (an empty string), and splitting it by comma gives only one field, so fields[1] raises IndexError rather than being handled gracefully. This is a genuinely new failure mode, distinct from SDD19's empty-array case, since it comes specifically from combining file reading with record construction.
Example 5 — Evaluation: judging the combined program
1
Applying all five SDD18 criteria to the whole read-search-update-write program:
CriterionJudgement
Fitness for purposeMet — FR1–FR4 are all satisfied for every valid (non-empty-file) test case.
Efficient use of coding constructsMet — reuses the standard linear search algorithm rather than a more complex or roundabout search method.
UsabilityMetissueLoan's confirmation and refusal messages are both clear and specific to the book involved.
MaintainabilityMet — four clearly-named sub-programs, each with one responsibility, make the round trip easy to follow.
RobustnessPartial — Example 4 showed the program crashes on an empty file; a robust version would need to check for this before attempting to build any records.
Now you try
Suggest a specific change to readBooks that would fix the empty-file robustness failure found in Example 4, and explain what it should do instead of crashing.
⚠️ Common mistakes — examiner feedback
📝 Exam tip

When a question combines records with file handling, be explicit in your design and testing answers about which data structure a value lives in at each stage — a value read from a file is initially just text, becomes a record field after parsing, and is only "saved" again once it has been written back out. Blurring these stages together in an answer (for example, describing a change as "saved" when it has only been updated in memory) is a common way marks are lost on this kind of question.

Task Set A — Core questions

Task Set A — Core questions
Work through all questions, then check your answers.
Question 1
In Example 3, which standard algorithm does findBookByTitle reuse?
Question 2
What value does findBookByTitle return if the title is not found in the array?
Question 3
Which sub-program in Example 1's design must run last, so that changes are actually saved?
Question 4
Given the file in Example 2, what does findBookByTitle(libraryBooks, "Edinburgh Nights") return?
Question 5
What happens when issueLoan is called on "The Coding Puzzle" (0 copies)?
Question 6
Which test data category does reading an empty file ("") belong to?
Question 7
Explain why readBooks("") raises an IndexError rather than simply returning an empty array. (3 marks)
Question 8
Explain what would go wrong if writeBooks were called before issueLoan instead of after it. (3 marks)
Write a function countAvailableBooks(books) that returns a count of how many books in the array have copies > 0, reusing the count-occurrences standard algorithm (generalised to a condition, as in SDD15) rather than strict equality.
Question 9
Compare this program's evaluation to SDD19's mini-project. Which criterion is judged differently, and why? (4 marks)
Question 10
Explain how Stage 1's functional requirements connect to the specific test cases used in Stage 4's test plan. (3 marks)

Task Set B — Extension

Task Set B — Extension · Beyond the specification
Longer written answers — no auto-check. Discuss your answers with your teacher.
Extension 1
Suggest how closely this mini-project's structure mirrors the actual assignment (Task 1), and what would be worth preparing in advance given the assignment is open book.
Extension 2
Suggest what would happen if one line in the file were malformed (e.g. missing the copies field), and explain how this differs from the empty-file case already tested.
Extension 3
Suggest what kind of question SDD21's past paper practice should specifically include, given that SDD19 and SDD20 are the only two lessons rehearsing the full cycle rather than one isolated skill.
📁 File this in OneNote under:
Higher Computing Science → Software Design & Development → SDD20
📌 Teacher notes — not for pupils

Double period — second and final mini-project before revision. This is the closest rehearsal for the actual assignment the course provides; consider flagging this explicitly to pupils.

Suggested timing: 5 min warm-up + vocab · 10 min stage-flow overview + notes · 40 min working through Examples 1–5, ideally letting pupils attempt Stage 1's functional requirements and Stage 2's design themselves before revealing Examples 1's model version · 5 min "now you try" · remainder Task Set A; Task Set B as homework/extension.

Book titles/authors are entirely invented for this lesson (not real published works or real people) — consistent with the site's privacy rule and avoiding any real-author attribution question.

The empty-file IndexError in Example 4/5 is a genuinely new, Python-verified failure mode distinct from SDD19's empty-array case — worth explicitly drawing pupils' attention to why it's different (text-splitting behaviour on an empty string, not just an empty container being passed in), since this distinction is tested directly in Task Set A Q7 and Extension 2.

SQA command words covered: identify, design, implement, test, evaluate, compare.