We make references to “writing code the right way”, but that is secondary to getting the correct answer. After all, how can you get a good grade if it doesn’t work?
In software engineering, everything needs to work, but doing it the right way is equally important. Why?
Because you are on a team, and someone else may have to understand and edit your code. Including your future self. We call this understandability.
Poorly-implemented solutions are more difficult to change without introducing bugs. We call this maintainability.
Poorly-implemented solutions may work with small data, but become intolerable with millions of records. We call this efficiency.
Overly-specific solutions that make assumptions about the data will break when encountering “the real world”. Avoiding this is called robustness.
The Rules
These characteristics are the result of your code design. The labs in these sections will go through code-level design principles that you, the developer, are responsible for when writing code.
The rules are:
Avoid magic literals.
Functions should have a single responsibility.
DRY (Don’t Repeat Yourself) and the Rule of Three.
Separate input/output logic from business logic.
Handle errors at the lowest sensible level, and re-raise/re-throw them otherwise.
Raise specific errors and define your own if needed.
Write these down! We will explore them in-depth in turn. We will start by creating a simple game, then applying design rules to it.
Click below to get started.
1 - pygame setup
Getting started with a game.
Example event-driven program using pygame
We’ll have some fun by creating a very simple game using the pygame library. Our example program comes from a very excellent YouTube tutorial called “The Ultimate introduction to Pygame” by Clear Code. I highly recommend his channel as his tutorials are clear and to the point.
We will implement the code as in his tutorial, but we will re-design the code by applying the rules above. His code works just fine, but our re-design will help improve the understandability, maintainability, efficiency, and robustness of the software.
Setup
Open a Terminal and use cd to get into your seng-201 directory.
Run the command git clone https://github.com/UNCW-SENG/pygame-design. This will create a subdirectory named pygame-design.
Open PyCharm. Go through the menus: File -> Open. Find and open the pygame-design/ folder, then hit the Open button. You should see the following structure:It is essential that the root folder is pygame-design/
Click in the bottom right of your PyCharm window where it either says Add interpreter... or Python 3.x (something).
Then select Add Interpreter -> Add Local Interpreter. You should see something similar to the following:
Make sure Generate New is selected. The pre-populated location should be fine. Then hit OK.
Open the Integrated Terminal in PyCharm. Type the command pip install pygame to download the pygame library.
Open runner.py and run it. A black screen should pop-up and you should see Hello from the pygame community in the integrated Terminal.
You should now be good to go.
Class recording
Code at the end
You must have cloned the project from the setup section. Here is the code at the end of class:
A magic literal is a raw value (number, string, None, etc.) that appears in code without a name explaining its meaning or origin. They harm readability, hide intent, and make changes risky—because the same value might be duplicated in many places.
Rule of thumb: If a value has domain meaning (tax rate, role name, error code, feature flag, file path, regex, etc.), name it once and reuse that name everywhere.
Benefits
Clear intent (self-documenting)
Single source of truth (change in one place)
Fewer bugs during refactors
Easier testing & configuration
Example 1 - numeric literal
Problematic code
Where does the value 0.085 come from? Why is it there? Not knowing this harms maintainability.
deffinal_price(subtotal):# Why 0.085? City tax? Promo? Future me has no idea.returnsubtotal*(1+0.085)
Fixed with a constant that conveys intent
Constants are variables that don’t vary. They are set once and not changed. In Python, the convention is to name Python constants as ALL_UPPERCASE_AND_UNDERSCORES.
CITY_SALES_TAX_RATE=0.085# 8.5% city sales taxdeffinal_price(subtotal:float)->float:returnsubtotal*(1+CITY_SALES_TAX_RATE)
Why this is better: The constant gives the number meaning, centralizes the value, and invites documentation and tests around that concept. Keep constants close to where they’re used (module-level), or in a dedicated constants.py if shared broadly.
When a literal is not magic
Sentinel/obvious values: 0, 1, -1, True, False, "" used in generic math or indexing (e.g., arr[-1]) are usually fine.
Short-lived throwaway code/tests: Inline values in extremely small, clear scopes can be acceptable.
Data structure examples: Literals inside illustrative examples or test fixtures are usually okay unless they are likely to change.