HookThe rocket that a data-type error destroyed 37 seconds after launch
On 4 June 1996, 37 seconds after lift-off, the European Space Agency's Ariane 5 rocket veered off course and blew itself up on its very first flight, taking about 370 million dollars of hardware with it. The cause was not a hardware fault. Software carried over from the older Ariane 4 tried to squeeze a large number, held as a 64-bit value, into a 16-bit space. The value did not fit, the conversion overflowed, nothing in the code was written to handle that possibility, and the guidance system failed. The new rocket had never been tested with its own real flight data.
You will not be launching rockets at GCSE, but Ariane 5 is the whole of this section in one disaster. Two ideas would have saved it, and they are exactly what 2.3 assesses. Defensive design means writing code that anticipates what could go wrong — unexpected values, misuse, malformed input — and copes gracefully instead of falling over. Testing means proving, before release, that the program does the right thing not only for sensible input but for the awkward, boundary and downright wrong input real users will throw at it. A robust program is simply one that keeps working when the data, or the person, misbehaves.
ModelDefensive design — anticipating misuse
Defensive design starts from a pessimistic assumption: users will do the unexpected, whether by accident or on purpose. The main weapon is input validation — checking that data is reasonable before the program acts on it. OCR expects several types: a range check (a month must be 1 to 12), a type check (an age must be a number), a length check (a password of at least eight characters), a presence check (a required field is not blank), and a format check (an email address contains an @). Validation usually sits inside a condition-controlled loop that keeps re-prompting until the input is acceptable.
Validation is not the only defence. Sanitisation cleans input by removing or neutralising characters that could cause harm — for example stripping out symbols that might be used in a database attack, or trimming stray spaces. Authentication confirms that a user is who they claim to be, most commonly through a username and password, so that only the right people reach sensitive features. And where validation cannot prevent every failure, defensive design also means handling errors that do slip through rather than letting the whole program crash — precisely the step Ariane 5 was missing.
Validating an age that must be between 1 and 120, re-asking until it is acceptable:
age = int(input('Enter your age')) WHILE age < 1 OR age > 120 PRINT 'Invalid age, try again' age = int(input('Enter your age')) ENDWHILE
Trace three inputs. Entering 200: 200 > 120 is true, so the loop body runs and re-prompts. Entering -5: -5 < 1 is true, so it re-prompts again. Entering 17: 17 is neither below 1 nor above 120, so the condition is false, the loop ends, and the program continues with a value it can trust. This is a range check; a fuller version would also wrap the input in a type check so that typing 'twelve' does not crash the cast to int.
MechanismMaintainability — writing code others can fix
Most software outlives the moment it was written and is later changed by someone else — or by you, months on, having forgotten how it works. Maintainability is designing code so that future editing is quick and safe, and OCR lists concrete techniques for it. Comments explain why a section exists or how a tricky part works, without repeating what the code obviously does. Indentation makes the structure of selection and iteration visible at a glance, so a reader can see instantly which statements sit inside a loop. Sensible naming conventions — totalScore rather than x, a consistent style throughout — mean the code half-documents itself.
The biggest maintainability gain is the one that overlaps with decomposition: breaking a program into subprograms. A named function or procedure can be understood, tested and fixed on its own, and reused rather than copied, so a bug needs fixing in only one place. Cryptic, unindented, comment-free code with copy-pasted blocks may run perfectly today and still be a liability, because the next person cannot change it without breaking it.
Compare two versions that do the same job. Hard to maintain:
d = p * 0.9
Easy to maintain:
// apply 10% loyalty discount CONST loyaltyRate = 0.9 discountedPrice = price * loyaltyRate
Both compute an identical result. The second tells the next programmer, in a comment, why the number 0.9 appears; replaces the mystery literal with a clearly named constant that can be updated in one place; and uses full, descriptive variable names. Nothing about how the computer runs the line has changed — but the cost of safely editing it later has fallen dramatically, which is the entire point of maintainability.
ModelTesting — purpose, types and kinds of error
The purpose of testing is to find errors before users do, and to give evidence that the program meets its requirements. OCR distinguishes two moments for it. Iterative testing happens throughout development: you build a small piece, test it, fix it and only then move on, so bugs are caught while they are cheap and easy to locate. Terminal (final) testing happens once the program is complete, checking that all the parts work together and that the whole thing meets the original specification.
Testing hunts two kinds of error. A syntax error breaks the rules of the language — a missing bracket, a misspelled keyword — and it stops the program from running or translating at all, so it is usually easy to spot because the computer refuses to proceed. A logic error is far more dangerous: the program runs perfectly happily but produces the wrong result, because the instructions, though valid, do not do what was intended. Logic errors do not announce themselves; the only way to catch them is to test with input whose correct output you already know, and compare.
This code should output the average of three marks, but it contains a logic error:
marks = [4, 8, 6] total = 0 FOR i = 0 TO 2 total = total + marks[i] NEXT i average = total / 2 PRINT average
The program runs without complaint — there is no syntax error — so a careless glance would pass it. Trace it with a known answer: the loop correctly builds total = 4 + 8 + 6 = 18, but the final line divides by 2 instead of 3, giving 18 / 2 = 9 when the true average of three marks is 6. Because you knew the expected result in advance, the wrong output exposes the logic error; changing the divisor to 3 (or better, to the number of items in the array) fixes it.
DataChoosing test data — normal, boundary and erroneous
Good testing is not about running lots of random inputs; it is about choosing a small set of deliberate ones that between them exercise every path. OCR names three categories. Normal (valid) data is typical, sensible input the program should accept and process — a value comfortably inside the allowed range. Boundary (extreme) data sits right on the edge of what is allowed, on both sides, because edges are where off-by-one mistakes hide — this is the most productive category for finding bugs. Invalid and erroneous data is input the program should reject: values outside the range, or the wrong type entirely, such as letters where a number is expected.
For each test you write down the input, what you expect to happen, and what actually happens; a mismatch is a bug to fix, after which you refine the algorithm and re-test. Choosing the categories thoughtfully means a handful of tests can give real confidence, whereas a hundred 'normal' inputs may never touch the edge case that brings the program down — as with Ariane 5, whose fatal value was one its tests never fed it.
A field must accept a whole number from 1 to 120 (the age validation from earlier). A strong test plan chooses just a few targeted values:
Normal: input 45 — expected: accepted. Boundary (lower valid): input 1 — expected: accepted. Boundary (upper valid): input 120 — expected: accepted. Boundary (just invalid): input 0 — expected: rejected. Input 121 — expected: rejected. Erroneous: input 'abc' — expected: rejected without crashing. Input left blank — expected: rejected.
The pairs 0/1 and 120/121 are doing the heavy lifting: they check that the condition uses the correct comparison and does not slip by one. If 121 were wrongly accepted, this plan would catch a >= that should have been >, whereas testing only 45 never would.
VocabularyKey terms the mark scheme pays for
TrapsMisconceptions that cost marks
ExamWhat examiners want
The 2.3 material is a favourite for the higher-tariff, banded questions that reward AO3 — making reasoned judgements about design and testing. For these, do not just list techniques; explain what each one prevents. 'Use validation' is a weak point; 'a range check rejecting values above 120 stops the later calculation receiving an impossible age' is a mark, because it links the technique to the misuse it defends against.
When asked to identify an error, name the type precisely — syntax or logic — and justify it: a syntax error stops the program running, a logic error lets it run but gives the wrong output. If you can, quote the fix. On testing questions, the phrase examiners look for is a completed test plan: for each test give the input, the expected result and the reason for choosing it, and make sure your chosen values span normal, boundary and erroneous categories rather than three near-identical 'normal' inputs. Boundary values, quoted in pairs either side of the limit (0 and 1, 120 and 121), are the ones that earn credit.
For maintainability, tie each technique to the future programmer: comments explain intent, indentation reveals structure, meaningful names remove guesswork, and subprograms mean a bug is fixed in one place. And where a question offers a real-world framing, an honest answer admits that no design is perfectly robust — you reduce risk by anticipating and testing, exactly the discipline whose absence destroyed Ariane 5.