Defensive design is a key principle in software development. It involves anticipating potential problems and user errors to create robust and reliable programs. It's about building software that can handle unexpected situations gracefully and prevent crashes or unexpected behaviour.
Defensive design involves anticipating how users might misuse a program or system, either unintentionally or maliciously. By considering potential problems in advance, developers can create more robust and reliable solutions. A program asking for a user's age should anticipate that the user might enter text, symbols, or a number outside the expected range. Appropriate validation checks should be implemented to prevent errors.
Example: Imagine a program that asks the user for their age. A defensively designed program would not just assume the user will enter a valid number. It would include checks to ensure the input is:
A number (not text or symbols)
Within a reasonable range (e.g., between 0 and 120)
A whole number (not a decimal)
🗝️ Key points to remember
Input Validation: Checking that user input is of the correct data type, format, and within acceptable limits.
Error Handling: Implementing routines to catch and handle errors gracefully, preventing program crashes.
Maintainability: Writing clear, well-commented code that is easy to understand and modify, making it easier to fix bugs or add features later.
Testing: Thoroughly testing the program with a variety of inputs, including valid, invalid, and boundary cases, to identify and fix potential issues.
❌✅ Misconceptions
❌ "My code works, so it's fine".
✅ Even if your program seems to work for you, it might fail when faced with unexpected input or conditions. Defensive design is about anticipating those situations.
❌ "Defensive design is only about preventing crashes".
✅ While preventing crashes is important, defensive design also encompasses maintainability, readability, and overall program robustness.
❌ "Defensive design is too time-consuming".
✅ While it requires some extra effort upfront, defensive design can save time and headaches in the long run by reducing debugging and maintenance needs.
❌ "It's the user's fault".
✅ While users can make mistakes, it's the developer's responsibility to create a system that is resilient to those mistakes.
❌ "I'll fix it if it breaks".
✅ Reactive problem-solving can be costly and time-consuming. Proactive anticipation of misuse is more efficient.
❌ "My program is simple, so it won't be misused".
✅ Even simple programs can encounter unexpected situations or be used in unintended ways.
Authentication is the process of verifying the identity of a user who wishes to access a system or resource. It ensures that only authorised individuals can gain entry. This is typically achieved by requiring users to provide some form of credential, such as a username and password, or a bio metric scan.
Example: Logging into a website or email account is a common example of authentication. The user provides their username (identification) and password (verification) to prove their identity.
🗝️ Key points to remember
Identification: The user claims an identity, usually with a username or ID.
Verification: The user provides proof of their identity, such as a password, PIN, or bio metric data.
Authorisation: Once authenticated, authorisation determines what the user is allowed to access or do within the system.
❌✅ Misconceptions
❌ Strong passwords are the only security measure needed.
✅ While strong passwords are important, other methods like multi-factor authentication (MFA) and bio metric scans add extra layers of security.
❌ Once authenticated, a user has full access.
✅ User access levels (see operating systems 1.5) restrict what a user can do within the system, even after successful authentication.
# Get user input for age
age = input("Enter your age: ")
# Check if the input is a number
if age.isdigit():
age = int(age) # Convert input to an integer
# Check if the age is within a reasonable range
if 0 <= age <= 120:
print("Valid age entered:", age)
else:
print("Invalid age. Please enter a number between 0 and 120.")
else:
print("Invalid input. Please enter a number.")
Input validation is a crucial aspect of defensive design. It involves checking any data input by the user (or from other sources) to ensure it conforms to the expected format, type, and range. This helps prevent errors, crashes, and security vulnerabilities.
Example: If a program expects a user to enter a date in the format DD/MM/YYYY, input validation would check that:
The input contains only numbers and slashes.
The input has the correct number of digits in each section.
The day, month, and year values are within valid ranges.
🗝️ Key points to remember
Validate all input: Don't assume any data is safe; check everything.
Validate as early as possible: Check input as soon as it's received to prevent it from causing problems further down the line.
Provide helpful error messages: If input is invalid, tell the user what's wrong and how to fix it.
Consider different validation techniques: These include range checks, type checks, format checks, and even checking against a list of allowed values.
❌✅ Misconceptions
❌ Input validation is only for security.
✅ While it's crucial for security, it also prevents general errors and improves program reliability.
❌ Client-side validation is enough.
✅ Client-side validation (in the web browser) can be bypassed, so server-side validation is essential for security.
❌ Input validation is too time-consuming.
✅ While it requires some effort, it saves time in the long run by preventing debugging and fixing issues caused by bad data.
Simple Authentication Program / Python
stored_username = "user123"
stored_password = "password456"
# Get user input
username = input("Enter username: ")
password = input("Enter password: ")
# Validate credentials
if username == stored_username and password == stored_password:
print("Access granted!")
else:
print("Invalid username or password.")
Maintainability refers to how easily a program can be modified, updated, or fixed. It's a crucial aspect of software development, ensuring code remains understandable and manageable over time. Key factors that contribute to maintainability include:
Use of Subprograms:
Breaking down a complex program into smaller, self-contained modules (functions or procedures) improves readability and organisation.
Subprograms promote code reuse, reducing redundancy and making modifications easier.
Naming Conventions:
Using meaningful and consistent names for variables, functions, and other elements enhances code clarity.
Following established naming conventions (e.g., camelCase, snake_case) improves consistency.
Indentation:
Proper indentation visually structures the code, making it easier to follow the logic and identify blocks of code.
Consistent indentation (e.g., using spaces or tabs) is crucial for readability.
# Commenting:
Adding clear and concise comments explains the purpose and functionality of code sections.
Comments help others (and your future self) understand the code's logic and intent.
Example: Python example code implementing maintenance rules.
# Function to calculate the area of a rectangle
def calculate_area(width, height):
"""
Calculates the area of a rectangle given its width and height.
"""
area = width * height
return area
# Get user input
width = float(input("Enter width: "))
height = float(input("Enter height: "))
# Calculate and print the area
rectangle_area = calculate_area(width, height)
print("The area of the rectangle is:", rectangle_area)
Use code with caution.
🗝️ Key points to remember
Well-maintained code is easier to understand, debug, and modify.
Maintainability reduces development time and costs in the long run.
Prioritising maintainability from the start leads to better quality software.
❌✅ Misconceptions
❌ "My code works, so it's maintainable".
✅ Functionality is not the sole indicator of maintainability. Readability and organisation are equally important.
❌ "Comments are unnecessary".
✅ Comments are crucial for explaining complex logic or non-obvious code sections.
❌ "Maintainability is only for large projects".
✅ Even small programs benefit from good maintainability practices.
There are several ways in which a program can be tested and these can be broadly split into two types: iterative and final.
Iterative Testing
This is testing that occurs during the development of the program.
It is usually done by the programmer.
It is done in small stages with one or more modules being tested at a time.
This type of testing allows for any bugs to be found early and then fixed.
Each time some functionality is added to the program, testing will occur.
Final/Terminal Testing
This is testing that occurs after the program has been completed.
It is done just before the product is released to the client.
The whole system is tested to ensure that it meets the original requirements specification.
Example: Imagine a program that will allow a user to input their name and address, and then save this to a database. The program will be developed using an iterative approach with the following modules:
Sub program 1 - Input the name
Sub program 2 - Input the address
Sub program 3 - Save the data
Each module will be tested as it is created. The whole program will then be tested once it has been completed, just before it is released to the client.
When writing code, it's common to encounter errors. These errors can be broadly classified into two types: syntax errors and logic errors.
Syntax Errors: These are errors in the structure and grammar of the code, violating the rules of the programming language. They prevent the code from running.
Logic Errors: These are errors in the algorithm or logic of the code. The code may run without producing any error messages, but it won't produce the intended result.
Example: Imagine you're writing a sentence in English.
Syntax Error: "The cat sat the mat on." This sentence has a syntax error because the word order is incorrect.
Logic Error: "The cat sat on the ceiling." This sentence is grammatically correct but logically incorrect since cats don't typically sit on ceilings.
🗝️ Key points to remember
Syntax errors are detected by the compiler or interpreter and prevent the code from running.
Logic errors are not detected by the compiler or interpreter and can be harder to find.
Testing and debugging are essential to identify and fix both types of errors.
❌✅ Misconceptions
❌ All errors are syntax errors.
✅ Many errors are logic errors, where the code runs but produces incorrect results.
❌ Syntax errors are harder to fix than logic errors.
✅ Syntax errors are usually easier to fix because the compiler or interpreter provides specific error messages.
❌ Logic errors are not important.
✅ Logic errors can be just as serious as syntax errors, leading to incorrect or unexpected program behaviour.
Testing is a crucial stage in the software development process. It involves systematically checking a program to ensure it functions correctly, meets requirements, and is free of errors. Selecting suitable test data is crucial for ensuring that a program works correctly and reliably. There are three main types of test data:
Reasons to Test:
Finding defects: Testing helps identify errors, bugs, or unexpected behaviour in the program.
Meeting requirements: It verifies that the program meets the specified requirements and performs its intended functions.
Improving quality: Testing contributes to the overall quality and reliability of the software.
Reducing risks: Thorough testing helps mitigate potential risks and problems before the software is released.
Types of Data to Test:
Normal Data: This is data that falls within the expected range of values and should be accepted by the program without any issues.
Boundary Data: This is data that lies at the edges of the acceptable range of values. It tests the program's ability to handle extreme values.
Invalid/Erroneous Data: This is data that falls outside the acceptable range of values or is in an incorrect format. It tests the program's ability to handle unexpected or incorrect input.
Example: Imagine a program that asks the user for their age, which must be between 1 and 120.
Normal Data: 25, 60, 100
Boundary Data: 1, 120
Invalid/Erroneous Data: 0, 150, "twenty", -5
🗝️ Key points to remember
Using a variety of test data helps ensure that the program is robust and reliable.
Testing with boundary data is important to identify any issues at the edges of the acceptable range.
Testing with invalid/erroneous data helps ensure the program can handle unexpected input and prevent errors.
❌✅ Misconceptions
❌ Testing with only normal data is sufficient.
✅ While normal data is important, it doesn't test the program's ability to handle unexpected or extreme values.
❌ Boundary data is the same as invalid data.
✅ Boundary data is valid but lies at the edge of the acceptable range, while invalid data is unacceptable.
❌ Testing is only necessary for complex programs.
✅ Testing is important for all programs, regardless of their complexity, to ensure they function correctly.
Refining an algorithm involves improving its efficiency, accuracy, and readability. This can involve identifying and correcting errors, simplifying complex logic, and optimizing for performance.
Example: Imagine you have an algorithm for sorting a list of numbers.
Refining this algorithm could involve:
Identifying and correcting errors: Ensuring the algorithm correctly sorts the numbers in all cases, including edge cases like duplicate numbers or an empty list.
Simplifying complex logic: Replacing convoluted code with clearer and more concise alternatives.
Optimising for performance: Reducing the number of steps required to sort the list, making it faster and more efficient.
🗝️ Key points to remember
Refining algorithms is an iterative process.
Testing and debugging are essential for identifying areas for improvement.
Code readability is important for maintainability and collaboration.
Efficiency and performance should be considered, especially for large datasets.
❌✅ Misconceptions
❌ Refining algorithms is only about making them faster.
✅ While performance is important, readability and correctness are also crucial considerations.
❌ Refining algorithms is only necessary for complex algorithms.
✅ Even simple algorithms can benefit from refinement to improve clarity and efficiency.
Algorithm to Refined
student1 = input("Enter the name of student 1: ")
student2 = input("Enter the name of student 2: ")
student3 = input("Enter the name of student 3: ")
student4 = input("Enter the name of student 4: ")
student5 = input("Enter the name of student 5: ")
print(student1)
print(student2)
print(student3)
print(student4)
print(student5)
Refined Algorithm
The algorithm has been edited to use iteration instead of just manually repeated commands.
student_names = []
for loop in range(0,5,1):
student = int(input("Enter the number of students: "))
for loop in range(0,5,1);
print(student_name[loop])