JavaScript – Chapter 2: Development Environment
Learn what JavaScript is, where it runs, why it is important, and how beginners can start using it with simple examples and clear output.
Main reading content
Chapter 36: Code Quality and Professional Python Style
A complete beginner-friendly guide to clean Python code, PEP standards, formatting tools, linters, documentation, refactoring, code smells, complexity, automation, and professional project standards.
Chapter 36 Topics
```- 36.1 Clean Code Principles
- 36.2 PEP 8
- 36.3 PEP 257
- 36.4 Naming Conventions
- 36.5 Code Formatting
- 36.6 Black
- 36.7 Ruff
- 36.8 Flake8
- 36.9 Pylint
- 36.10 Isort
- 36.11 Docstrings
- 36.12 Comments
- 36.13 Documentation
- 36.14 Refactoring
- 36.15 Reducing Code Duplication
- 36.16 DRY Principle
- 36.17 KISS Principle
- 36.18 YAGNI Principle
- 36.19 Code Smells
- 36.20 Cyclomatic Complexity
- 36.21 Pre-Commit Hooks
- 36.22 Professional Project Standards
- 36.23 Chapter Practice Exercises
- 36.24 Chapter Refactoring Project
36.1 Clean Code Principles
```Clean code is code that another programmer can read, understand, test, change, and extend without unnecessary difficulty. A clean program uses meaningful names, small focused functions, clear control flow, consistent formatting, and limited duplication.
Clean code is not only about making code look attractive. It reduces misunderstandings, prevents mistakes, improves teamwork, and makes future changes safer. Code is usually read many more times than it is written, so readability is extremely important.
Poor Example
def x(a, b, c):
t = a * b
if c:
t = t - t * .1
return t
The function may work, but the names do not explain what the values represent. A future programmer must inspect every line to understand its purpose.
Improved Example
def calculate_order_total(
unit_price: float,
quantity: int,
has_discount: bool
```
) -> float:
subtotal = unit_price * quantity
```
if has_discount:
discount = subtotal * 0.10
subtotal -= discount
return subtotal
```
print(
calculate_order_total(
unit_price=20,
quantity=3,
has_discount=True
)
)
```
Output
54.0
Clean Code Guidelines
- Use names that explain purpose.
- Keep functions focused on one responsibility.
- Avoid deeply nested conditions.
- Remove unused code and variables.
- Prefer clear code over clever code.
- Write tests before major refactoring.
- Keep formatting consistent.
- Document important decisions and public interfaces.
36.2 PEP 8
```PEP 8 is the main style guide for Python code. It recommends conventions for indentation, spacing, line breaks, imports, naming, blank lines, and code organization.
PEP 8 does not change how Python executes a program. Its purpose is to make Python projects more consistent and readable. Teams may adjust some rules, but consistency within a project should remain the main goal.
Poor Formatting
def calculate(a,b):
```
return a+b
result=calculate(10,20)
print(result)
```
PEP 8 Style
def calculate(
first_number: int,
second_number: int
```
) -> int:
return first_number + second_number
result = calculate(10, 20)
print(result)
```
Output
30
Common PEP 8 Recommendations
- Use four spaces for each indentation level.
- Use lowercase words with underscores for functions and variables.
- Use capitalized words for class names.
- Place imports near the top of the file.
- Use blank lines to separate major sections.
- Avoid unnecessary whitespace.
- Keep lines reasonably short.
- Place spaces around most operators.
Spacing Example
# Poor style
```
total=price*quantity+tax
# Improved style
total = price * quantity + tax
36.3 PEP 257
```PEP 257 provides conventions for Python docstrings. A docstring is a string placed at the beginning of a module, class, function, or method to explain its purpose.
A short docstring may fit on one line. Longer docstrings usually begin with a summary, followed by a blank line and additional details. The summary should describe the action using clear language.
One-Line Docstring
def add(first: int, second: int) -> int:
"""Return the sum of two integers."""
return first + second
Multi-Line Docstring
def calculate_average(
scores: list[float]
```
) -> float:
"""Calculate the average score.
```
Return zero when the supplied list is empty.
Otherwise, divide the total by the number of scores.
"""
if not scores:
return 0.0
return sum(scores) / len(scores)
```
print(calculate_average([80, 90, 100]))
```
Output
90.0
Module Docstring
"""Utilities for calculating student grades.
```
This module contains functions for averages,
letter grades, and pass-or-fail decisions.
"""
36.4 Naming Conventions
```Good names communicate what data represents and what a function does. Names should be specific enough to remove confusion but not unnecessarily long.
Python commonly uses different naming styles for variables, functions, classes, constants, modules, and internal attributes.
Common Naming Styles
# Variables
```
student_name = "Sara"
total_price = 49.99
# Functions
def calculate_total():
pass
# Classes
class ShoppingCart:
pass
# Constants
TAX_RATE = 0.13
MAXIMUM_ATTEMPTS = 3
# Internal-use name
_cached_result = None
```
Poor Names
a = 20
```
b = 3
c = a * b
```
Improved Names
unit_price = 20
```
quantity = 3
subtotal = unit_price * quantity
print(subtotal)
```
Output
60
Naming Advice
- Use nouns for variables and classes.
- Use verbs for functions and methods.
- Avoid unclear abbreviations.
- Do not reuse names for unrelated meanings.
- Avoid names that differ only by capitalization.
- Use Boolean names such as
is_activeorhas_access.
36.5 Code Formatting
```Code formatting controls indentation, line breaks, spacing, quotation style, and layout. Consistent formatting makes code easier to scan and reduces arguments about appearance during teamwork.
Manual formatting is possible, but automated formatters can apply a consistent style across an entire project.
Poorly Formatted Code
def create_user(name,email,active=True):
return {"name":name,"email":email,"active":active}
Improved Formatting
def create_user(
name: str,
email: str,
active: bool = True
```
) -> dict[str, str | bool]:
return {
"name": name,
"email": email,
"active": active
}
```
Long Expression Formatting
total = (
subtotal
+ tax
+ delivery_charge
- discount
```
)
```
Formatting Principles
- Use consistent indentation.
- Break long expressions logically.
- Separate unrelated sections with blank lines.
- Avoid placing many statements on one line.
- Use automated tools consistently across the team.
36.6 Black
```Black is an automatic Python code formatter. It reformats source files into a consistent style with very little configuration.
Black does not usually change program behavior. It changes layout, spacing, line wrapping, and quotation formatting so developers do not need to format every detail manually.
Install Black
python -m pip install black
Format One File
python -m black app.py
Format a Folder
python -m black .
Check Without Changing Files
python -m black --check .
Before Black
def calculate(a,b,c=0):
```
return a+b+c
```
After Black
def calculate(a, b, c=0):
return a + b + c
Typical Output
reformatted app.py
```
All done!
1 file reformatted.
36.7 Ruff
```Ruff is a fast Python linter and code-quality tool. It can detect unused imports, undefined names, style problems, suspicious expressions, unnecessary code, and many other issues.
Ruff can also automatically correct many findings. It is often configured through a pyproject.toml file.
Install Ruff
python -m pip install ruff
Check a Project
python -m ruff check .
Automatically Fix Safe Problems
python -m ruff check . --fix
Format with Ruff
python -m ruff format .
Problem Example
import os
```
import math
def add(first, second):
unused_value = 100
return first + second
```
Possible Ruff Findings
F401 `os` imported but unused
```
F401 `math` imported but unused
F841 Local variable `unused_value` is assigned but never used
```
Corrected Version
def add(first, second):
return first + second
Basic Configuration
[tool.ruff]
```
line-length = 88
[tool.ruff.lint]
select = ["E", "F", "I", "B"]
36.8 Flake8
```Flake8 is a widely used Python linting tool. It combines style checking, error detection, and complexity-related plugins.
Flake8 reports problems but normally does not reformat code automatically. Developers review its messages and correct the source.
Install Flake8
python -m pip install flake8
Check One File
python -m flake8 app.py
Check the Current Project
python -m flake8 .
Problem Example
import os
```
def greet(name):
message="Hello "+name
return message
```
Possible Output
app.py:1:1: F401 'os' imported but unused
```
app.py:5:12: E225 missing whitespace around operator
```
Corrected Code
def greet(name):
message = "Hello " + name
return message
Configuration Example
[flake8]
```
max-line-length = 88
exclude =
.git,
.venv,
**pycache**
36.9 Pylint
```Pylint performs detailed static analysis of Python code. It checks style, possible errors, naming, design problems, duplicated code, unused values, and other quality concerns.
Pylint may report more messages than simpler tools. Not every warning requires a change, but each message should be understood before it is disabled.
Install Pylint
python -m pip install pylint
Check a File
python -m pylint app.py
Problem Example
def F(x):
y = 10
return x + 1
Possible Findings
Missing function or method docstring
```
Function name "F" doesn't conform to snake_case naming style
Unused variable "y"
```
Improved Code
def increment(number: int) -> int:
"""Return the supplied number increased by one."""
return number + 1
Generate a Configuration File
python -m pylint --generate-rcfile > .pylintrc
```
36.10 Isort
```Isort automatically organizes Python imports. It groups standard-library imports, third-party imports, and local project imports into consistent sections.
Organized imports make dependencies easier to understand and prevent developers from manually rearranging long import lists.
Install Isort
python -m pip install isort
Sort Imports
python -m isort .
Check Without Changing
python -m isort . --check-only
Before Isort
from shop.cart import ShoppingCart
```
import os
from pathlib import Path
import requests
import json
```
After Isort
import json
```
import os
from pathlib import Path
import requests
from shop.cart import ShoppingCart
```
Black-Compatible Configuration
[tool.isort]
```
profile = "black"
36.11 Docstrings
```
Docstrings document modules, classes, functions, and methods. They can be viewed with help(), development tools, and documentation generators.
A useful function docstring explains what the function does, important parameters, the return value, and expected exceptions when these are not already obvious.
Function Docstring
def divide(
dividend: float,
divisor: float
```
) -> float:
"""Divide one number by another.
```
Args:
dividend: Number that will be divided.
divisor: Number used as the divisor.
Returns:
The division result.
Raises:
ValueError: If the divisor is zero.
"""
if divisor == 0:
raise ValueError(
"Divisor cannot be zero."
)
return dividend / divisor
Class Docstring
class ShoppingCart:
"""Store products and calculate the cart subtotal."""
def __init__(self) -> None:
"""Create an empty shopping cart."""
self.items: list[float] = []
View Documentation
help(divide)
Output
Help on function divide:
```
divide(dividend: float, divisor: float) -> float
Divide one number by another.
36.12 Comments
```Comments explain information that is not clear from the code itself. Good comments describe reasons, important decisions, unusual limitations, or business rules.
Comments should not repeat obvious code. They must also be updated when the code changes, or they can become misleading.
Poor Comment
# Add one to count
```
count += 1
```
Useful Comment
# The external service uses one-based page numbers.
```
page_number = internal_index + 1
```
Business Rule Comment
# Ontario tax rate used for this sample calculation.
```
TAX_RATE = 0.13
```
TODO Comment
# TODO: Replace the in-memory repository with database storage.
Comment Guidelines
- Explain why, not only what.
- Keep comments accurate.
- Do not use comments to excuse confusing code.
- Prefer meaningful names when they can remove the need for a comment.
- Do not leave large blocks of old commented-out code.
36.13 Documentation
```Documentation explains how to install, configure, use, test, and maintain a project. It may include a README file, API documentation, examples, setup instructions, architecture notes, and contribution rules.
Good documentation should answer the questions a new user or developer is likely to ask. It should be updated whenever important behavior changes.
README Structure
# Student Manager
```
## Description
A Python application for storing students and scores.
## Requirements
* Python 3.11 or newer
## Installation
python -m pip install -r requirements.txt
## Run
python main.py
## Test
python -m pytest
## Format
python -m black .
## Lint
python -m ruff check .
```
Useful Project Documentation
- Project purpose
- Installation requirements
- Environment configuration
- Usage examples
- Testing commands
- Project structure
- API or function references
- Known limitations
- Contribution instructions
- Licence information
36.14 Refactoring
```Refactoring means improving the internal structure of code without intentionally changing its external behavior. Common refactorings include renaming variables, extracting functions, simplifying conditions, and moving responsibilities into suitable classes.
Tests should be run before and after refactoring. They provide evidence that behavior has remained correct.
Before Refactoring
def print_receipt(items):
subtotal = 0
for item in items:
subtotal += (
item["price"]
* item["quantity"]
)
tax = subtotal * 0.13
total = subtotal + tax
print("Subtotal:", subtotal)
print("Tax:", tax)
print("Total:", total)
After Refactoring
TAX_RATE = 0.13
```
def calculate_subtotal(
items: list[dict[str, float | int]]
) -> float:
return sum(
item["price"] * item["quantity"]
for item in items
)
def calculate_tax(
subtotal: float
) -> float:
return subtotal * TAX_RATE
def print_receipt(
items: list[dict[str, float | int]]
) -> None:
subtotal = calculate_subtotal(items)
tax = calculate_tax(subtotal)
total = subtotal + tax
```
print("Subtotal:", subtotal)
print("Tax:", tax)
print("Total:", total)
Benefits
- Calculations can be tested separately.
- Functions have clearer responsibilities.
- The tax rate has a meaningful constant name.
- Future changes are easier to make.
36.15 Reducing Code Duplication
```Duplication occurs when the same or nearly identical logic appears in several places. Repeated code increases maintenance work because every copy may need to be updated.
Duplication can often be reduced by extracting a shared function, using a loop, creating a reusable class, or storing changing values in data structures.
Duplicated Code
pizza_subtotal = 14.99 * 2
```
pizza_tax = pizza_subtotal * 0.13
pizza_total = pizza_subtotal + pizza_tax
burger_subtotal = 9.99 * 3
burger_tax = burger_subtotal * 0.13
burger_total = burger_subtotal + burger_tax
```
Improved Code
TAX_RATE = 0.13
```
def calculate_item_total(
price: float,
quantity: int
) -> float:
subtotal = price * quantity
tax = subtotal * TAX_RATE
```
return subtotal + tax
```
pizza_total = calculate_item_total(
14.99,
2
)
burger_total = calculate_item_total(
9.99,
3
)
print(pizza_total)
print(burger_total)
```
Output
33.8774
```
33.8661
36.16 DRY Principle
```DRY means “Do Not Repeat Yourself.” The principle encourages developers to keep one reliable source for each important rule or piece of knowledge.
DRY does not mean that every similar-looking line must be combined. Creating a complicated abstraction too early can make code harder to understand. Remove meaningful duplication when a shared rule is clear.
Repeated Business Rule
checkout_tax = subtotal * 0.13
```
refund_tax = refund_amount * 0.13
report_tax = monthly_sales * 0.13
```
Single Source of Truth
TAX_RATE = 0.13
```
def calculate_tax(
amount: float
) -> float:
return amount * TAX_RATE
checkout_tax = calculate_tax(subtotal)
refund_tax = calculate_tax(refund_amount)
report_tax = calculate_tax(monthly_sales)
```
Benefits
- The rule is defined once.
- A future tax-rate change occurs in one place.
- Tests can focus on one shared function.
- Different parts of the application remain consistent.
36.17 KISS Principle
```KISS means “Keep It Simple.” A straightforward solution is usually easier to read, test, debug, and maintain than an unnecessarily complicated solution.
Simplicity does not mean ignoring requirements. It means satisfying the real requirements using the clearest reasonable design.
Unnecessarily Complicated
def is_adult(age):
result = {
True: "yes",
False: "no"
}[age >= 18]
return result == "yes"
Simple Version
def is_adult(age: int) -> bool:
return age >= 18
```
print(is_adult(20))
print(is_adult(15))
```
Output
True
```
False
```
KISS Guidelines
- Use direct expressions when they are clear.
- Avoid unnecessary classes and layers.
- Do not optimize before performance is a real problem.
- Use familiar language features when possible.
- Prefer readable conditions over clever tricks.
36.18 YAGNI Principle
```YAGNI means “You Are Not Going to Need It.” It advises developers not to build features, abstractions, or configuration options before a real requirement exists.
Future needs are difficult to predict. Unused features increase code size, testing requirements, documentation work, and maintenance cost.
Overbuilt Design
class ReportEngine:
def generate(
self,
format_name,
language,
theme,
encryption,
cloud_provider,
compression
):
pass
If the current requirement is only to print a basic text report, most of these options are unnecessary.
Current Requirement Only
def create_text_report(
title: str,
body: str
```
) -> str:
return f"{title}\n{'=' * len(title)}\n{body}"
print(
create_text_report(
"Sales Report",
"Total sales: $500"
)
)
```
Output
Sales Report
```
============
Total sales: $500
```
New formats can be added when there is a confirmed need and enough information to design them properly.
```36.19 Code Smells
```A code smell is a sign that code may be difficult to maintain or may contain a deeper design problem. A smell is not always a bug, but it deserves investigation.
Common Code Smells
- Very long functions
- Large classes with many responsibilities
- Deeply nested conditions
- Duplicated logic
- Too many function parameters
- Unclear names
- Global mutable variables
- Repeated Boolean flags
- Large blocks of commented-out code
- Functions that change unrelated data
Long Function Smell
def process_order(order):
# Validate customer
# Validate products
# Calculate subtotal
# Apply discount
# Calculate tax
# Calculate delivery
# Save order
# Send email
# Print receipt
pass
Improved Organization
def process_order(order):
validate_order(order)
totals = calculate_totals(order)
saved_order = save_order(
order,
totals
)
send_receipt(saved_order)
return saved_order
Each helper function can now focus on one responsibility and be tested separately.
```36.20 Cyclomatic Complexity
```Cyclomatic complexity estimates the number of independent paths through a function. Conditions, loops, and exception branches increase complexity.
A highly complex function is harder to understand and requires more tests. Complexity can often be reduced by extracting functions, returning early, and replacing long conditional chains with data-driven logic.
Complex Example
def shipping_cost(
country,
subtotal,
is_member,
is_express
```
):
if country == "Canada":
if subtotal >= 50:
if is_express:
return 10
return 0
```
if is_member:
return 5
return 8
if country == "USA":
if is_express:
return 20
return 12
return 30
Simplified Version
def canadian_shipping(
subtotal: float,
is_member: bool,
is_express: bool
```
) -> float:
if subtotal >= 50:
return 10 if is_express else 0
```
if is_member:
return 5
return 8
```
def shipping_cost(
country: str,
subtotal: float,
is_member: bool,
is_express: bool
) -> float:
if country == "Canada":
return canadian_shipping(
subtotal,
is_member,
is_express
)
```
if country == "USA":
return 20 if is_express else 12
return 30
Measuring Complexity
python -m pip install radon
```
python -m radon cc app.py -a
```
Example Output
app.py
F 1:0 shipping_cost - B
```
Average complexity: B
36.21 Pre-Commit Hooks
```Pre-commit hooks automatically run checks before a Git commit is created. They can format code, sort imports, detect lint problems, and prevent common mistakes from entering the repository.
Hooks create consistent checks for every contributor. They do not replace continuous integration, but they catch many problems earlier.
Install Pre-Commit
python -m pip install pre-commit
Configuration File
# .pre-commit-config.yaml
```
repos:
* repo: https://github.com/psf/black
rev: 24.10.0
hooks:
* id: black
* repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.8.0
hooks:
* id: ruff
args: [--fix]
* id: ruff-format
Install the Git Hook
python -m pre_commit install
Run All Hooks Manually
python -m pre_commit run --all-files
Example Output
black................................................Passed
ruff.................................................Passed
ruff-format..........................................Passed
```
Tool versions in real projects should be reviewed and updated regularly rather than copied permanently without maintenance.
```36.22 Professional Project Standards
```Professional Python projects use an organized structure, automated tests, dependency management, documentation, code-quality checks, version control, and clear configuration.
Standards vary by team, but developers should be able to install, run, test, format, and understand the project using documented commands.
Example Project Structure
professional_project/
```
│
├── src/
│ └── store/
│ ├── **init**.py
│ ├── models.py
│ ├── pricing.py
│ └── service.py
│
├── tests/
│ ├── test_pricing.py
│ └── test_service.py
│
├── README.md
├── pyproject.toml
├── .gitignore
├── .pre-commit-config.yaml
└── requirements-dev.txt
```
Professional Standards Checklist
- Clear folder structure
- Meaningful names
- Consistent formatting
- Automated linting
- Static type checking
- Unit and integration tests
- Documented setup commands
- Dependency version management
- No passwords or secrets in source code
- Useful error handling
- Version control with meaningful commits
- Automated continuous integration
- Code review before merging important changes
Example Development Commands
python -m black .
```
python -m ruff check .
python -m mypy src
python -m pytest
python -m pytest --cov=src
36.23 Chapter Practice Exercises
```Complete these exercises to practise naming, formatting, linting, documentation, refactoring, duplication removal, complexity reduction, and professional project organization.
- Rename five unclear variables in a small program.
- Rewrite a function using PEP 8 spacing.
- Correct inconsistent indentation.
- Add a module docstring.
- Add a one-line function docstring.
- Add a detailed class docstring.
- Replace an unnecessary comment with a meaningful name.
- Remove commented-out code.
- Install and run Black.
- Use Black check mode.
- Install and run Ruff.
- Use Ruff automatic fixes.
- Install and run Flake8.
- Install and run Pylint.
- Sort imports with Isort.
- Create a
pyproject.tomlfile. - Extract a long calculation into a function.
- Split a function with multiple responsibilities.
- Remove duplicated tax calculations.
- Create a shared validation function.
- Apply the DRY principle to repeated business rules.
- Simplify an overcomplicated Boolean function.
- Remove an unused future feature using YAGNI.
- Identify five code smells.
- Replace deep nesting with early returns.
- Measure complexity with Radon.
- Reduce the complexity of a grade function.
- Create a pre-commit configuration.
- Add Black and Ruff hooks.
- Create a professional README.
- Create a
.gitignorefile. - Create separate source and test folders.
- Add type hints to public functions.
- Add tests before refactoring.
- Run formatter, linter, type checker, and tests together.
- Create a professional project-quality checklist.
Practice Refactoring Example
# Before refactoring
```
def p(a, b, c):
x = a * b
```
if c == True:
x = x - x * .1
print("total:", x)
return x
Improved Version
DISCOUNT_RATE = 0.10
```
def calculate_total(
unit_price: float,
quantity: int,
has_discount: bool
) -> float:
"""Calculate an order total with an optional discount."""
subtotal = unit_price * quantity
```
if has_discount:
subtotal -= subtotal * DISCOUNT_RATE
return round(subtotal, 2)
```
def display_total(total: float) -> None:
"""Display a formatted order total."""
print(f"Total: ${total:.2f}")
order_total = calculate_total(
unit_price=20,
quantity=3,
has_discount=True
)
display_total(order_total)
```
Output
Total: $54.00
```
36.24 Chapter Refactoring Project
```This project begins with a poorly designed order-processing script. The program works for basic data, but it contains unclear names, duplicated rules, deeply mixed responsibilities, weak validation, global state, and poor formatting.
Unrefactored Program
orders=[]
```
def p(n,e,items,member=False):
t=0
for i in items:
if i["q"]>0:
t=t+i["p"]*i["q"]
d=0
if member==True:
d=t*.1
t=t-d
tax=t*.13
if t>40:
shipping=0
else:
shipping=5
total=t+tax+shipping
o={"name":n,"email":e,"items":items,"subtotal":t,"tax":tax,"shipping":shipping,"total":total}
orders.append(o)
print("customer",n)
print("subtotal",t)
print("tax",tax)
print("shipping",shipping)
print("total",total)
return o
p("Sara","[sara@example.com](mailto:sara@example.com)",[{"n":"Pizza","p":14.99,"q":2},{"n":"Drink","p":2.5,"q":2}],True)
```
Problems in the Original Code
- Names such as
p,t,d, andoare unclear. - Formatting is inconsistent.
- Business rules are hidden as unexplained numbers.
- Validation, calculation, storage, and display are mixed together.
- A mutable global order list stores application data.
- Dictionary keys are abbreviated.
- Money values are not consistently rounded.
- The function has too many responsibilities.
- There are no type hints or docstrings.
- The design is difficult to test.
Refactored Project Structure
order_project/
```
│
├── src/
│ └── orders/
│ ├── **init**.py
│ ├── models.py
│ ├── pricing.py
│ ├── repository.py
│ ├── service.py
│ └── presentation.py
│
├── tests/
│ ├── test_models.py
│ ├── test_pricing.py
│ └── test_service.py
│
├── main.py
├── pyproject.toml
├── requirements-dev.txt
├── README.md
└── .pre-commit-config.yaml
```
File 1: src/orders/models.py
"""Domain models for the order application."""
```
from dataclasses import dataclass, field
@dataclass(frozen=True)
class Product:
"""Represent a product available for purchase."""
```
name: str
unit_price: float
def __post_init__(self) -> None:
if not self.name.strip():
raise ValueError(
"Product name cannot be empty."
)
if self.unit_price < 0:
raise ValueError(
"Product price cannot be negative."
)
```
@dataclass(frozen=True)
class OrderItem:
"""Represent a product and its ordered quantity."""
```
product: Product
quantity: int
def __post_init__(self) -> None:
if self.quantity <= 0:
raise ValueError(
"Quantity must be greater than zero."
)
def subtotal(self) -> float:
"""Return the subtotal for this order item."""
return round(
self.product.unit_price
* self.quantity,
2
)
```
@dataclass(frozen=True)
class Customer:
"""Represent a customer placing an order."""
```
name: str
email: str
is_member: bool = False
def __post_init__(self) -> None:
if not self.name.strip():
raise ValueError(
"Customer name cannot be empty."
)
if "@" not in self.email:
raise ValueError(
"Customer email is invalid."
)
```
@dataclass
class Order:
"""Represent a completed customer order."""
```
order_id: int
customer: Customer
items: list[OrderItem]
subtotal: float
discount: float
tax: float
shipping: float
total: float
File 2: src/orders/pricing.py
"""Pricing rules for orders."""
```
from orders.models import OrderItem
TAX_RATE = 0.13
MEMBER_DISCOUNT_RATE = 0.10
FREE_SHIPPING_MINIMUM = 40.00
STANDARD_SHIPPING_CHARGE = 5.00
def calculate_items_subtotal(
items: list[OrderItem]
) -> float:
"""Return the total before discounts, tax, and shipping."""
return round(
sum(
item.subtotal()
for item in items
),
2
)
def calculate_discount(
subtotal: float,
is_member: bool
) -> float:
"""Return the membership discount."""
if not is_member:
return 0.0
```
return round(
subtotal
* MEMBER_DISCOUNT_RATE,
2
)
```
def calculate_tax(
discounted_subtotal: float
) -> float:
"""Return the tax for the discounted subtotal."""
if discounted_subtotal < 0:
raise ValueError(
"Subtotal cannot be negative."
)
```
return round(
discounted_subtotal * TAX_RATE,
2
)
```
def calculate_shipping(
discounted_subtotal: float
) -> float:
"""Return the shipping charge."""
if discounted_subtotal >= FREE_SHIPPING_MINIMUM:
return 0.0
```
return STANDARD_SHIPPING_CHARGE
File 3: src/orders/repository.py
"""Order storage implementations."""
```
from orders.models import Order
class OrderRepository:
"""Store orders in memory for this sample project."""
```
def __init__(self) -> None:
self._orders: dict[int, Order] = {}
def save(self, order: Order) -> None:
"""Save or replace an order."""
self._orders[order.order_id] = order
def find(self, order_id: int) -> Order | None:
"""Return an order by identifier."""
return self._orders.get(order_id)
def all_orders(self) -> list[Order]:
"""Return a copy of all stored orders."""
return list(self._orders.values())
def count(self) -> int:
"""Return the number of stored orders."""
return len(self._orders)
File 4: src/orders/service.py
"""Application services for creating orders."""
```
from orders.models import (
Customer,
Order,
OrderItem
)
from orders.pricing import (
calculate_discount,
calculate_items_subtotal,
calculate_shipping,
calculate_tax
)
from orders.repository import (
OrderRepository
)
class OrderService:
"""Create and store customer orders."""
```
def __init__(
self,
repository: OrderRepository
) -> None:
self._repository = repository
def create_order(
self,
order_id: int,
customer: Customer,
items: list[OrderItem]
) -> Order:
"""Create, calculate, and save a new order."""
if not items:
raise ValueError(
"An order must contain at least one item."
)
if self._repository.find(order_id) is not None:
raise ValueError(
"Order identifier already exists."
)
items_subtotal = calculate_items_subtotal(
items
)
discount = calculate_discount(
items_subtotal,
customer.is_member
)
discounted_subtotal = round(
items_subtotal - discount,
2
)
tax = calculate_tax(
discounted_subtotal
)
shipping = calculate_shipping(
discounted_subtotal
)
total = round(
discounted_subtotal
+ tax
+ shipping,
2
)
order = Order(
order_id=order_id,
customer=customer,
items=items,
subtotal=discounted_subtotal,
discount=discount,
tax=tax,
shipping=shipping,
total=total
)
self._repository.save(order)
return order
File 5: src/orders/presentation.py
"""Functions for displaying orders."""
```
from orders.models import Order
def format_money(amount: float) -> str:
"""Return a currency-formatted value."""
return f"${amount:.2f}"
def create_receipt(order: Order) -> str:
"""Create a plain-text receipt for an order."""
lines = [
"ORDER RECEIPT",
"=" * 50,
f"Order ID: {order.order_id}",
f"Customer: {order.customer.name}",
f"Email: {order.customer.email}",
"-" * 50
]
```
for item in order.items:
lines.append(
f"{item.product.name:<20} "
f"{item.quantity:>3} x "
f"{format_money(item.product.unit_price):>8} "
f"= {format_money(item.subtotal()):>8}"
)
lines.extend(
[
"-" * 50,
f"{'Discount':<35}{format_money(order.discount):>15}",
f"{'Subtotal after discount':<35}{format_money(order.subtotal):>15}",
f"{'Tax':<35}{format_money(order.tax):>15}",
f"{'Shipping':<35}{format_money(order.shipping):>15}",
f"{'Total':<35}{format_money(order.total):>15}"
]
)
return "\n".join(lines)
File 6: main.py
"""Run the sample order application."""
```
from orders.models import (
Customer,
OrderItem,
Product
)
from orders.presentation import create_receipt
from orders.repository import OrderRepository
from orders.service import OrderService
def main() -> None:
"""Create and display a sample order."""
pizza = Product(
name="Pizza",
unit_price=14.99
)
```
drink = Product(
name="Drink",
unit_price=2.50
)
items = [
OrderItem(
product=pizza,
quantity=2
),
OrderItem(
product=drink,
quantity=2
)
]
customer = Customer(
name="Sara",
email="sara@example.com",
is_member=True
)
repository = OrderRepository()
service = OrderService(repository)
order = service.create_order(
order_id=1001,
customer=customer,
items=items
)
print(create_receipt(order))
```
if **name** == "**main**":
main()
```
Program Output
ORDER RECEIPT
```
==================================================
Order ID: 1001
Customer: Sara
Email: [sara@example.com](mailto:sara@example.com)
--------------------------------------------------
Pizza 2 x $14.99 = $29.98
Drink 2 x $2.50 = $5.00
----------------------------------------------
Discount $3.50
Subtotal after discount $31.48
Tax $4.09
Shipping $5.00
Total $40.57
```
File 7: tests/test_pricing.py
"""Tests for order pricing functions."""
```
import pytest
from orders.models import (
OrderItem,
Product
)
from orders.pricing import (
calculate_discount,
calculate_items_subtotal,
calculate_shipping,
calculate_tax
)
def test_items_subtotal() -> None:
items = [
OrderItem(
Product("Pizza", 14.99),
2
),
OrderItem(
Product("Drink", 2.50),
2
)
]
```
assert calculate_items_subtotal(items) == 34.98
```
@pytest.mark.parametrize(
"subtotal, is_member, expected",
[
(100, True, 10),
(100, False, 0),
(0, True, 0)
]
)
def test_discount(
subtotal: float,
is_member: bool,
expected: float
) -> None:
assert (
calculate_discount(
subtotal,
is_member
)
== expected
)
def test_tax() -> None:
assert calculate_tax(100) == 13
@pytest.mark.parametrize(
"subtotal, expected",
[
(39.99, 5),
(40, 0),
(100, 0)
]
)
def test_shipping(
subtotal: float,
expected: float
) -> None:
assert (
calculate_shipping(subtotal)
== expected
)
```
File 8: tests/test_service.py
"""Tests for the order service."""
```
import pytest
from orders.models import (
Customer,
OrderItem,
Product
)
from orders.repository import OrderRepository
from orders.service import OrderService
def create_service() -> OrderService:
"""Create a service with an empty repository."""
return OrderService(
OrderRepository()
)
def test_create_member_order() -> None:
service = create_service()
```
customer = Customer(
"Sara",
"sara@example.com",
True
)
items = [
OrderItem(
Product("Pizza", 20),
2
)
]
order = service.create_order(
1001,
customer,
items
)
assert order.discount == 4
assert order.subtotal == 36
assert order.tax == 4.68
assert order.shipping == 5
assert order.total == 45.68
```
def test_empty_order_is_rejected() -> None:
service = create_service()
```
customer = Customer(
"Sara",
"sara@example.com"
)
with pytest.raises(
ValueError,
match="at least one item"
):
service.create_order(
1001,
customer,
[]
)
File 9: pyproject.toml
[project]
```
name = "professional-order-project"
version = "1.0.0"
requires-python = ">=3.11"
[tool.black]
line-length = 88
[tool.ruff]
line-length = 88
[tool.ruff.lint]
select = ["E", "F", "I", "B"]
[tool.isort]
profile = "black"
[tool.pytest.ini_options]
pythonpath = ["src"]
testpaths = ["tests"]
```
File 10: requirements-dev.txt
black
```
isort
mypy
pre-commit
pytest
pytest-cov
ruff
```
File 11: .pre-commit-config.yaml
repos:
```
* repo: https://github.com/psf/black
rev: 24.10.0
hooks:
* id: black
* repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.8.0
hooks:
* id: ruff
args: [--fix]
* id: ruff-format
File 12: README.md
# Professional Order Project
## Description
A small Python order application demonstrating clean code,
professional project structure, testing, type hints, formatting,
linting, documentation, and refactoring.
## Install
python -m pip install -r requirements-dev.txt
## Run
python main.py
## Test
python -m pytest -v
## Coverage
python -m pytest --cov=orders --cov-report=term-missing
## Format
python -m black .
python -m isort .
## Lint
python -m ruff check .
## Type Check
python -m mypy src
```
How to Run the Project
- Create the complete folder structure.
- Create an empty
__init__.pyfile insidesrc/orders. - Copy each code section into its matching file.
- Open a terminal in the project root.
- Create a virtual environment with
python -m venv .venv. - Activate the virtual environment.
- Install tools with
python -m pip install -r requirements-dev.txt. - Run the application with
python main.py. - Run tests with
python -m pytest -v. - Format with
python -m black .. - Sort imports with
python -m isort .. - Check code with
python -m ruff check .. - Run type checking with
python -m mypy src. - Measure coverage with
python -m pytest --cov=orders. - Install hooks with
python -m pre_commit install.
Refactoring Improvements
- Unclear names were replaced with meaningful names.
- Formatting was standardized.
- Business rules became named constants.
- Validation moved into domain models.
- Pricing rules moved into focused functions.
- Storage moved into a repository.
- Application workflow moved into a service.
- Receipt formatting moved into presentation code.
- Global mutable state was removed.
- Tests were added for important behavior.
- Development tools were configured.
- Project commands were documented.
Project Challenges
- Add percentage discount codes.
- Add fixed-value discount codes.
- Add product categories.
- Add customer loyalty points.
- Add a JSON repository implementation.
- Add CSV receipt export.
- Add structured logging.
- Add a protocol for repository implementations.
- Add a payment-service abstraction.
- Mock payment processing in tests.
- Add complete type checking in strict mode.
- Increase test coverage.
- Reduce any new complex functions.
- Add continuous integration configuration.
- Create professional contribution guidelines.
A modern course built to help learners study step by step with clarity, comfort, and confidence.