Refactoring and Structure
Refactoring and Structure
Your application now works.
In the past few lessons, you have actually been writing well-structured code already — but you may not have noticed why.
In this lesson, you will:
- review the structural patterns your code uses
- understand why each one matters
- spot opportunities to clean up further
- prepare your codebase for testing
Goal of This Lesson
By the end of this lesson, you will:
- understand the patterns that make your code maintainable
- recognize separation of concerns in your project
- identify and clean up any remaining issues
- leave your codebase ready for tests
Pattern 1: Separation of Responsibilities
Your structure follows this rule:
UI → calls → services → talks to → database
Rules
- UI never contains SQL
- UI never contains business logic
- Services never contain UI code
Open any service file (e.g. task_service.py). You will not find a single tk or Listbox reference.
Open app_ui.py. You will not find a single cursor.execute(...).
This is intentional. Each layer only knows about the layer below it.
Pattern 2: Centralized Theme
In app_ui.py, all colors live in one dictionary:
THEME = {
"bg": "#f3f4f6",
"card": "#ffffff",
"primary": "#2563eb",
# ...
}
Why this matters
Imagine you decide to change your primary color from blue to purple.
- Without
THEME: you search the file for every"#2563eb"and hope you find them all - With
THEME: you change one line
Same logic for fonts. Constants like FONT_TITLE, FONT_HEAD, FONT_LABEL exist for the same reason.
Pattern 3: Reusable Helpers
You did not write three separate “stat card” code blocks for tasks, completed, and expenses.
You wrote one helper:
def _build_stat_card(parent, label, value, accent_bg, accent_fg):
...
return {"card": card, "value": value_lbl}
And called it three times.
Other helpers you have
_styled_button(...)— every button in the app_build_panel(...)— every panel in the dashboard_clear(...)— every screen transition
The rule
If you find yourself writing similar code more than twice, extract a helper.
Pattern 4: Section Builders
build_task_section and build_expense_section are not just helpers — they are section builders.
Each one:
- takes a
parentwidget to build into - takes a
user_idto know whose data to load - takes an optional
on_changecallback for the dashboard to react to changes
This means the dashboard does not need to know how tasks work internally.
It just says: “build a task section here, and call refresh_summary when something changes.”
Pattern 5: The on_change Callback
Look at how the dashboard wires the sections:
build_task_section(task_panel["body"], user_id, on_change=refresh_summary)
build_expense_section(expense_panel["body"], user_id, on_change=refresh_summary)
The section does not import refresh_summary.
The dashboard does not reach inside the section.
Instead: the dashboard hands the section a function to call.
This is called dependency injection, and it is one of the most important patterns in software.
It keeps modules decoupled — easy to test, easy to move, easy to change.
Pattern 6: Returning Dicts from Builders
Notice how _build_stat_card and _build_panel return dictionaries:
return {"card": card, "value": value_lbl}
return {"card": card, "body": body}
This lets the caller access multiple parts of what was built.
For stat cards: the dashboard places the card and updates the value label later.
For panels: the dashboard places the card and fills the body with section content.
This is cleaner than returning tuples — result["card"] is more readable than result[0].
Step 1: Cleanup Pass
Open app_ui.py and check each of these:
☐ Are there any hard-coded colors outside THEME?
Search for "#" in your file. Every hex color should come from THEME.
☐ Are there any direct tk.Button calls?
You should be using _styled_button(...). Search for tk.Button — there should be zero results.
☐ Are there any database calls in app_ui.py?
Search for cursor, execute, commit. There should be zero results.
☐ Are functions getting too long?
If show_dashboard is over ~50 lines, look for chunks to extract into helpers.
Step 2: Naming Check
Names should describe what something is, not what type it is.
Bad
data = get_dashboard_data(user_id)
frame = tk.Frame(window)
Good
dashboard_data = get_dashboard_data(user_id)
content_frame = tk.Frame(window)
Look through your file for variables named data, frame, result, temp, etc. and rename them.
Step 3: Service Cleanliness
Open each service file and check:
- ☐ Only contains database logic
- ☐ Returns clean data (lists, dicts, simple values)
- ☐ Does not print
- ☐ Does not import anything from
app/ui/
If any service imports from the UI layer, that is a serious problem — fix it before moving on.
Step 4: Folder Structure Check
Your project should look like:
productivity_app/
app/
services/
database.py
user_service.py
task_service.py
expense_service.py
dashboard_service.py
ui/
app_ui.py
tests/
main.py
If anything is in the wrong folder, move it now.
What You Achieved
Your code already demonstrates:
- separation of concerns
- centralized theming
- reusable helpers
- section builders
- dependency injection via callbacks
- clean naming
These are not “advanced” patterns reserved for senior developers.
They are normal, expected practices in any real codebase.
Important Mindset
Refactoring is not optional in real projects.
It is part of development.
The reason your code is clean now is because the lessons were structured to teach the patterns gradually — but in your own future projects, you will need to refactor as you go.
The signal is always: “Am I about to write this same code a third time?”
If yes, stop and extract a helper.
Why This Step Matters
This is what distinguishes:
beginner code → professional code
Not the language. Not the framework. The structure.
Summary
You reviewed the structural patterns your code already uses, cleaned up any remaining issues, and confirmed your project is ready for testing. You now understand why the code is organized the way it is — not just how.