You are viewing a free preview of this lesson.
Subscribe to unlock all 10 lessons in this course and every other course on LearningBro.
This lesson introduces defensive design as required by OCR J277 Section 2.3. Defensive design is a programming approach that anticipates potential problems and builds safeguards into the code to prevent errors, misuse, and security vulnerabilities. This topic is distinctive to OCR and is central to producing robust programs.
This lesson mainly builds AO1 understanding of what defensive design is and why it matters, with AO2 application when you add safeguards to code, and AO3 analysis when you anticipate how a program could be misused and design defences against it.
Defensive design means writing code that anticipates things going wrong and handles them gracefully. Instead of assuming the user will always provide valid input or that the system will always work perfectly, a defensive programmer plans for errors.
The key principles of defensive design include:
| Principle | Description |
|---|---|
| Input validation | Checking that data entered by the user is reasonable and correct before processing it |
| Authentication | Verifying the identity of users before allowing access |
| Planning for contingencies | Handling unexpected situations (e.g. file not found, network errors) |
| Maintainability | Writing code that is easy to read, understand, and modify |
flowchart TD
DD[Defensive design] --> AM[Anticipate misuse]
DD --> IV[Input validation]
DD --> AU[Authentication]
DD --> MA[Maintainability]
AM --> AM1[Plan for empty / wrong type / out-of-range / malicious input]
IV --> IV1[Range / Type / Presence / Format / Length / Lookup checks]
AU --> AU1["Username + password<br/>limited attempts + lockout"]
MA --> MA1["Comments, meaningful names,<br/>indentation, subroutines, constants"]
AM1 --> R[Robust program — handles errors gracefully]
IV1 --> R
AU1 --> R
MA1 --> R
Programs interact with users, files, networks, and other systems — all of which can produce unexpected data or behaviour. Without defensive design:
OCR Exam Tip: When the exam asks about defensive design, always link it to the idea of making programs more robust — meaning they can handle unexpected situations without crashing or producing incorrect results.
# Dangerous — no validation, no error handling
age = int(input("Enter your age: "))
print("In 10 years you will be", age + 10)
What could go wrong?
OCR Pseudocode:
do
age = input("Enter your age: ")
if NOT age.isNumeric() then
print("Error: please enter a number")
elseif int(age) < 0 OR int(age) > 150 then
print("Error: age must be between 0 and 150")
endif
until age.isNumeric() AND int(age) >= 0 AND int(age) <= 150
age = int(age)
print("In 10 years you will be " + str(age + 10))
Python:
while True:
age_input = input("Enter your age: ")
if not age_input.isdigit():
print("Error: please enter a number")
elif int(age_input) < 0 or int(age_input) > 150:
print("Error: age must be between 0 and 150")
else:
break
age = int(age_input)
print("In 10 years you will be", age + 10)
This version handles:
Always assume users may enter unexpected data — either accidentally or deliberately. Design your program to handle:
When something goes wrong, the program should:
| Stage | Defensive Design Activity |
|---|---|
| Design | Identify potential inputs, edge cases, and risks |
| Implementation | Add validation, error handling, and authentication |
| Testing | Use normal, boundary, and erroneous test data |
| Maintenance | Write clear, well-commented, maintainable code |
OCR Exam Tip: Defensive design questions often ask you to identify what could go wrong with a piece of code and suggest improvements. Look for: missing validation, no error messages, potential crashes from bad input, and lack of authentication. Each improvement you suggest should be linked to a specific problem.
A science club is collecting daily temperature readings. A programmer has written a quick program to read temperature values from the user and store them. The prototype has no defensive design and several problems.
Prototype (no defensive design):
temps = []
for i = 0 to 6
t = int(input("Day " + str(i+1) + " temp: "))
temps.append(t)
next i
total = 0
for t in temps
total = total + t
next t
print("Average: " + str(total / 7))
Trace table for inputs 15, 18, "warm", 20, 22, 19, 21:
| Step | i | input | action |
|---|---|---|---|
| 1 | 0 | 15 | stored |
| 2 | 1 | 18 | stored |
| 3 | 2 | "warm" | CRASH — int() fails |
Bugs identified: (1) no type check — non-numeric input crashes the program mid-way through data collection; (2) no range check — a typo of 900 instead of 9.0 would distort the average silently (logic error hidden by bad data); (3) no authentication — anyone can run the script and overwrite yesterday's readings; (4) no feedback after a successful read; (5) magic number 7 appears twice.
Refactored pseudocode applying defensive design:
const DAYS = 7
const MIN_TEMP = -30
const MAX_TEMP = 50
// Simple authentication
storedPin = "4242"
attempts = 0
authenticated = false
while attempts < 3 AND NOT authenticated
pin = input("PIN: ")
if pin == storedPin then authenticated = true
else attempts = attempts + 1 endif
endwhile
if NOT authenticated then
print("Access denied.")
exit
endif
// Validated input collection
temps = []
for i = 0 to DAYS - 1
do
raw = input("Day " + str(i+1) + " temp: ")
valid = true
if raw == "" then valid = false
elseif NOT raw.isNumeric() then valid = false
else
t = float(raw)
if t < MIN_TEMP OR t > MAX_TEMP then valid = false endif
endif
if NOT valid then print("Enter a number between " + str(MIN_TEMP) + " and " + str(MAX_TEMP)) endif
until valid
temps.append(t)
print("Recorded: " + str(t))
next i
total = 0
for t in temps
total = total + t
next t
print("Average: " + str(total / DAYS))
Test plan:
| Test | Inputs | Type | Expected |
|---|---|---|---|
| 1 | 15,18,20,22,19,21,17 | Normal | Average printed |
| 2 | -30 and 50 included | Boundary (valid) | Accepted |
| 3 | -31, 51 | Boundary (invalid) | Rejected, re-prompt |
| 4 | "warm" | Erroneous (type) | Rejected, re-prompt |
| 5 | "" | Erroneous (presence) | Rejected, re-prompt |
| 6 | Wrong PIN x3 | Security | Access denied |
The refactor shows all four principles of defensive design working together: anticipating misuse (PIN, attempt limit), input validation (presence + type + range), authentication (PIN check with lockout) and maintainability (named constants, clear prompts).
Misconception: Defensive design is not just "add some if statements". Many students think printing an error message is enough. Defensive design means preventing the bad state — rejecting input that would cause a crash before the dangerous operation runs (e.g. check isNumeric before int()), not catching the crash afterwards. A program that displays a nice error but still corrupts its data is not robust.
Exam question (6 marks): "Explain what defensive design means and describe how it could be applied to a program that reads weekly temperature readings."
Grade 3-4 answer: "Defensive design is when you stop the program going wrong. You check the user's input is ok. If they type letters instead of numbers you show an error. You should not let anyone use the program who shouldn't."
Examiner-style commentary: General understanding is present but no specific techniques are named and no example is worked through. No mention of validation, authentication, or maintainability. Marks: 1-2 out of 6.
Grade 5-6 answer: "Defensive design means writing programs that anticipate errors. For a temperature logger, input validation would check each entry is numeric (type check) and within a sensible range such as -30 to 50 (range check), rejecting bad input with an error message and asking again. Authentication with a PIN prevents unauthorised people overwriting the data. Comments and meaningful variable names make the program easier to maintain. Testing with normal, boundary and erroneous data confirms the program is robust."
Examiner-style commentary: Correct terminology (input validation, type check, range check, authentication, maintenance, test data classes), each linked to the scenario. Missing: explicit mention of attempt limits, why rejecting before the dangerous operation matters. Marks: 4-5 out of 6.
Grade 7-9 answer: "Defensive design produces robust programs by anticipating misuse, validating input, authenticating users and structuring the code for maintenance. For a temperature logger: authentication requires a PIN with a three-attempt lockout so unauthorised users cannot overwrite data. Input validation applies a presence check (non-empty), a type check (isNumeric before int conversion to prevent a runtime error) and a range check (-30 ≤ t ≤ 50) on every reading; bad input is rejected and re-prompted rather than allowed to propagate. Maintainability uses named constants (DAYS, MIN_TEMP, MAX_TEMP), meaningful identifiers and comments so a future developer can change the valid range in one place. Testing exercises the validation with normal (20°C), boundary (-30, -31, 50, 51) and erroneous ('warm', '') data, plus authentication attacks (wrong PIN three times). Each technique targets a specific failure mode: validation prevents crashes and data corruption; authentication prevents misuse; maintainability reduces the cost of future change; testing confirms all three are effective."
Examiner-style commentary: Full-mark response — names every OCR technique, links each to the scenario, references test data classes, explains why validation must run before the dangerous operation. Marks: 6 out of 6.
Top-performing answers typically add at least one of the following extensions: (a) a trace table showing the fault before and after the defensive change; (b) a reference to syntax, logic and runtime errors and which each technique prevents (validation prevents runtime errors on bad types; defensive design against misuse prevents logic errors from unsanitised input; structured code prevents syntax errors creeping in during maintenance); (c) a statement about the cost of repair — catching errors early via defensive design is cheaper than patching them in production; (d) a reference to iterative testing during development alongside final testing of the complete program. Weaving two of these extensions into a paragraph typically pushes an answer from 4-5 marks into the full-marks band.
A strong exam technique is to briefly quote the code and identify the specific line where the defensive technique would be applied — for example, "the line t = int(input(...)) should be preceded by a validation loop using isNumeric() and a range check". This demonstrates applied understanding, which is what the mark scheme rewards.
Use these short questions to confirm you have grasped the key ideas before moving on.
Sample answers:
A common weakness in weaker answers is treating the four principles — anticipating misuse, input validation, authentication and maintainability — as an unordered shopping list. In a genuinely robust program they interlock, and understanding why is what separates a top-band answer from a middle-band one.
Anticipating misuse is the mindset; the other three are the concrete techniques that deliver it. When you sit down to anticipate misuse you ask a single question of every input and every operation: "what is the worst thing a careless or hostile user could do here?" The answers to that question tell you exactly which validation checks to write, whether authentication is needed, and which parts of the code will need to change often (and therefore must be maintainable). Input validation defends against accidental bad data — a mistyped temperature, a blank field, a decimal point in the wrong place. Authentication defends against unauthorised use — someone running the logger who has no right to overwrite the club's readings. Maintainability is the principle that keeps the other three alive over time: a validation rule buried in cryptic code with magic numbers will be broken by the next person who edits it, so good naming and constants protect the defences you have already built.
OCR Exam Tip: If a question gives you a scenario and asks you to "apply defensive design", structure your answer as misuse identified → technique chosen → benefit. For example: "a user could leave the field blank (misuse), so a presence check rejects an empty string (technique), preventing a crash later when the value is used (benefit)." This three-part structure guarantees you name the technique and justify it, which is what the mark scheme rewards.
One of the most powerful ideas underpinning defensive design is that the cost of fixing a defect rises the later it is found. A bug caught by a validation check while you are writing the code costs seconds. The same bug caught in testing costs minutes. The same bug shipped to real users — corrupting a term's worth of temperature readings, or locking a genuine user out — can cost the trust of everyone who relies on the program, and the data may be unrecoverable.
| Where the defect is caught | Approximate cost to fix | Why |
|---|---|---|
| While writing the code (defensive check prevents it) | Seconds | You are already looking at the relevant line |
| Iterative testing | Minutes | Context is fresh; only new code to check |
| Final testing | Hours | Must isolate which module caused it |
| In production, reported by a user | Very high — plus lost data and trust | Live data already corrupted; users affected |
This is exactly why defensive design is preventive rather than reactive. Writing an isNumeric() check before an int() conversion is cheap insurance; recovering a corrupted dataset after the fact may be impossible.
Specimen question modelled on the OCR J277 Paper 2 format (6 marks):
A sports club uses a program to record the number of goals scored by each player in a match. A junior programmer has written the line below with no defensive design:
goals = int(input("Goals scored: "))
Describe three defensive design techniques that could be applied to make this program more robust, and for each explain the problem it solves. [6 marks]
Mark scheme (AO2/AO3 — award 1 mark per valid technique named, up to 3, and 1 mark per correct justification, up to 3):
| Technique (1 mark each) | Justification (1 mark each) |
|---|---|
Type check — verify the input is numeric with isNumeric() before converting | Prevents a runtime error / crash when a user types text such as "two" |
| Range check — reject values below 0 or above a sensible maximum (e.g. > 20) | Prevents nonsensical data (negative goals, or a typo of 100) distorting later totals |
| Presence check — reject an empty entry | Prevents a crash when the field is left blank |
| Validation loop — keep re-prompting until the value is acceptable | Ensures the program continues rather than terminating on the first bad input |
| Meaningful feedback — display a specific error message | Helps the user correct their mistake, e.g. "Enter a whole number from 0 to 20" |
Examiner-style commentary: Full marks require three distinct techniques, each correctly named and justified — a technique with no justification, or two justifications for the same technique, caps the response. The strongest answers make the justification specific to this scenario ("negative goals are impossible in this sport") rather than generic ("it stops errors"). Note that "add a comment" or "use a better variable name" are maintainability improvements, not defences against misuse of this input, so they would not earn marks on this particular question — a reminder to read the command word carefully.
This content is aligned with OCR GCSE Computer Science (J277) specification section 2.3 Producing robust programs. For the most accurate and up-to-date information, please refer to the official OCR specification document.