Defensive Design and Validation
Real users will type the wrong thing. Defensive design is the discipline of writing programs that anticipate misuse, validate every input, refuse to continue with bad data and produce code that other people can maintain. Required by OCR (2.3.1), AQA (3.2), Edexcel (1CP2 2.5) and CIE (8.4).
In 2017 a single user pasted 65,000 characters into a Twitter status field and crashed every Twitter app on iOS. The bug had been there for years. The input was never length-checked. One bad input took down a whole product. Defensive design is the difference between code that handles real users and code that breaks the moment it leaves the developer's machine.
0 <= age <= 130.1. The five kinds of validation check
All four boards expect you to know these by name and apply them.
| Check | What it tests | Example |
|---|---|---|
| Range | Number between a min and max | Age between 0 and 130 |
| Presence | Field is not empty | Username must be entered |
| Length | String length within limits | Password 8-20 characters |
| Format | Matches an expected pattern | Email contains @ and a dot |
| Type | Right data type | Quantity must be an integer |
# Range check age = int(input("Age: ")) if age < 0 or age > 130: print("Age must be between 0 and 130.") # Presence check name = input("Name: ") if name == "": print("Name cannot be empty.") # Length check password = input("Password: ") if len(password) < 8 or len(password) > 20: print("Password must be 8-20 characters.") # Format check email = input("Email: ") if "@" not in email or "." not in email: print("That does not look like a valid email.") # Type check (using try/except) try: qty = int(input("Quantity: ")) except ValueError: print("Quantity must be a whole number.")
// Range check int age = int.Parse(Console.ReadLine()); if (age < 0 || age > 130) Console.WriteLine("Age must be between 0 and 130."); // Presence check string name = Console.ReadLine(); if (string.IsNullOrEmpty(name)) Console.WriteLine("Name cannot be empty."); // Length check string password = Console.ReadLine(); if (password.Length < 8 || password.Length > 20) Console.WriteLine("Password must be 8-20 characters."); // Format check string email = Console.ReadLine(); if (!email.Contains("@") || !email.Contains(".")) Console.WriteLine("That does not look like a valid email."); // Type check if (!int.TryParse(Console.ReadLine(), out int qty)) Console.WriteLine("Quantity must be a whole number.");
2. Validate-until-valid using a while loop
Printing an error and continuing is rarely enough. The standard pattern is to loop until the user gives valid input. This combines validation with exception handling.
age = -1 while age < 0 or age > 130: try: age = int(input("Enter age (0-130): ")) if age < 0 or age > 130: print("Out of range, try again.") except ValueError: print("Whole numbers only.") age = -1 print(f"Age accepted: {age}")
int age = -1; while (age < 0 || age > 130) { Console.Write("Enter age (0-130): "); if (!int.TryParse(Console.ReadLine(), out age)) { Console.WriteLine("Whole numbers only."); age = -1; } else if (age < 0 || age > 130) { Console.WriteLine("Out of range, try again."); } } Console.WriteLine($"Age accepted: {age}");
Validation handles bad values (an age of -5). Exception handling handles bad types (the user typed letters). A robust program needs both: try catches the type error, the while loop catches the value error.
3. Authentication: a basic login
Authentication asks "are you who you say you are?". The simplest version compares a typed password to a stored one. Real systems hash passwords (Lesson 6 of the CS series), but the GCSE concept is the comparison plus the design choices around it.
users = {
"alice": "Sunset!42",
"ben": "Pa55word!"
}
attempts = 0
while attempts < 3:
username = input("Username: ")
password = input("Password: ")
if username in users and users[username] == password:
print("Login successful.")
break
attempts = attempts + 1
print("Username or password incorrect.")
else:
print("Account locked. Try again later.")Dictionary<string, string> users = new Dictionary<string, string> { {"alice", "Sunset!42"}, {"ben", "Pa55word!"} }; int attempts = 0; while (attempts < 3) { string u = Console.ReadLine(); string p = Console.ReadLine(); if (users.ContainsKey(u) && users[u] == p) { Console.WriteLine("Login successful."); break; } attempts++; Console.WriteLine("Username or password incorrect."); }
Real systems never tell you whether the username or the password was wrong. Saying "yes, that username exists, but the password was wrong" gives an attacker half the secret for free. The exam expects you to identify this.
4. Anticipating misuse
Defensive design is more than validation. The exam wants you to think about what users will do wrong, not just what they should do right. Examples:
- What if the user closes the program halfway through entering data? (Save progress; do not corrupt the file.)
- What if the user enters their date of birth as 30 February 2010? (Validate dates.)
- What if two users press "Save" at the same time? (Lock the file or warn one of them.)
- What if the user types their password with caps lock on? (Warn them; do not silently fail.)
- What if a teacher accidentally deletes a class? (Confirm dialog; soft-delete with undo.)
Each scenario is a defensive-design decision the developer makes before the user encounters it.
5. Maintainability matters too
Defensive design is not only about input. The exam also wants you to talk about maintainability: code that other people can read and change without breaking it.
| Maintainability feature | Why it helps |
|---|---|
Meaningful identifier names (student_score not s) | The reader does not have to trace the code to learn what each variable means. |
| Comments that explain why, not what | The code already says what it does; comments explain the reason behind the choice. |
| Consistent indentation | Reveals the logical structure visually so nesting is obvious at a glance. |
| Subroutines (functions/procedures) | Each task is named and isolated, so changes are local. |
| Avoiding magic numbers | A constant like MAX_AGE = 130 is changed in one place. |
"Discuss maintainability" means at least two named features above, each with a reason. Saying "good comments help" once is one mark; naming three distinct features with reasons is three marks.
Validation makes a system robust against accidents. Security against deliberate attack also needs hashing of passwords, encrypted transmission, rate limiting, and account-lockout, plus protection against injection attacks. Validation is part of defensive design but it is not the whole of security. The exam will not award security marks for a validation answer.
6. A six-mark question, fully marked
Question: A school is designing an online form to register new pupils. Discuss the defensive-design features the developer should include. [6 marks]
- Range check on date of birth so that impossible dates (e.g. year 2030) are rejected.
- Presence check on required fields (name, year group) so the form cannot be submitted half-empty.
- Length check on password so it meets the minimum 8 characters.
- Format check on parent email so an obvious typo (no @) is caught at the form rather than later.
- Validate-until-valid loop so the user is prompted to fix mistakes rather than the form silently failing.
- Confirmation step before final submission so accidental presses do not register a pupil with wrong data.
- Maintainable code (clear names, subroutines, constants) so the form can be updated next year without breaking.
Beyond the basics
The better design:
- Choose a much higher generous limit for human names, e.g. 200 characters. Real names rarely exceed this and the database column can be sized to match.
- Combine the limit with a request-size cap at the server level so a 65,000-character paste is rejected before it reaches the validation logic.
- Warn the user gently if they type something unusual rather than blocking submission outright.
The principle: validation should reject impossible or malicious input, not unusual-but-legitimate input. When validation excludes real users, the developer has weighted the wrong risk.