Lecture 2
Understanding when AI coding assistants struggle and how to simplify problems for better results
This work is licensed under CC BY-NC-SA 4.0
© Way-Up 2025
This complexity makes it difficult for AI to generate efficient, correct code without extensive guidance.
Anti-patterns: Too many instance variables, lack of separation between UI and data, excessive state management
import tkinter as tk
from PIL import Image, ImageTk, ImageFilter, ImageEnhance
import numpy as np
class ComplexImageEditor:
def __init__(self, root):
self.root = root
self.root.title("Complex Image Editor")
# Complex state management with multiple interdependent variables
self.current_tool = None
self.tool_settings = {}
self.history = []
self.future = []
self.selection = None
self.layers = []
self.active_layer = None
self.brush_settings = {"size": 10, "hardness": 0.5, "opacity": 1.0, "flow": 0.8}
self.color_primary = (0, 0, 0)
self.color_secondary = (255, 255, 255)
self.custom_brushes = {}
self.filter_cache = {}
# Create complex UI with many interdependent widgets
self._setup_ui()
self._bind_complex_events()
def _setup_ui(self):
# Create dozens of interdependent UI elements
self.menu_bar = self._create_menu_bar()
self.tool_panel = self._create_tool_panel()
self.layer_panel = self._create_layer_panel()
self.canvas_area = self._create_canvas_area()
self.property_panel = self._create_property_panel()
# Layout with complex grid management
# ...
Anti-patterns: Excessive event binding, complex conditional logic, tightly coupled event handlers
def _bind_complex_events(self):
# Complex event binding with intricate dependencies
self.canvas.bind("", self._on_canvas_click)
self.canvas.bind("", self._on_canvas_drag)
self.canvas.bind("", self._on_canvas_release)
self.canvas.bind("", self._on_right_click)
self.canvas.bind("", self._on_mouse_wheel)
self.canvas.bind("", self._on_zoom)
self.canvas.bind("", self._on_rotate_canvas)
# Many more complex bindings...
def _on_canvas_click(self, event):
# Complex handling based on current tool, modifiers, and state
if self.current_tool == "brush":
self._start_brush_stroke(event)
elif self.current_tool == "selection":
self._start_selection(event)
elif self.current_tool == "eyedropper":
self._pick_color(event)
# Many more tool conditions...
# Update UI state based on complex conditions
self._update_ui_state_after_action()
Anti-patterns: Complex caching logic, special case handling, deeply nested conditionals
def _apply_filter_with_custom_settings(self, layer_index, filter_type, settings):
# Complex filter application with custom parameters and caching
cache_key = f"{layer_index}_{filter_type}_{hash(frozenset(settings.items()))}"
if cache_key in self.filter_cache:
return self.filter_cache[cache_key]
layer = self.layers[layer_index]
original_image = layer["image"]
# Complex filter logic with many special cases
if filter_type == "custom_blur":
kernel_size = settings.get("kernel_size", 5)
sigma = settings.get("sigma", 1.5)
preserve_edges = settings.get("preserve_edges", False)
if preserve_edges:
# Complex edge-preserving blur algorithm
# ...
result = self._apply_edge_preserving_blur(original_image, kernel_size, sigma)
else:
result = original_image.filter(ImageFilter.GaussianBlur(radius=sigma))
# Many more filter types with complex implementations...
# Cache the result
self.filter_cache[cache_key] = result
return result
Anti-patterns: Monolithic methods, mixed responsibilities, complex state updates across multiple UI components
def _handle_layer_operations(self, operation, **kwargs):
# Complex layer management with many operations and state changes
if operation == "add":
new_layer = {
"name": kwargs.get("name", f"Layer {len(self.layers) + 1}"),
"image": kwargs.get("image", Image.new("RGBA", self.canvas_size, (0, 0, 0, 0))),
"visible": True,
"opacity": 1.0,
"blend_mode": "normal",
"locked": False,
"mask": None,
"effects": []
}
position = kwargs.get("position", len(self.layers))
self.layers.insert(position, new_layer)
self.active_layer = position
# Update UI to reflect new layer
self._update_layer_panel()
self._composite_layers()
self._save_history_state("Add Layer")
elif operation == "merge_down":
if self.active_layer > 0:
# Complex merging logic with blend modes
upper = self.layers[self.active_layer]
lower = self.layers[self.active_layer - 1]
# Apply blend mode, opacity, and effects
merged = self._blend_layers(lower["image"], upper["image"],
upper["blend_mode"], upper["opacity"])
# Replace lower layer with merged result
lower["image"] = merged
# Remove upper layer
self.layers.pop(self.active_layer)
self.active_layer -= 1
# Update UI and history
self._update_layer_panel()
self._composite_layers()
self._save_history_state("Merge Down")
# Many more complex layer operations...
Anti-patterns: Complex state capture and restoration, deep copying of nested structures, tightly coupled UI updates
def _undo(self):
# Complex undo system with state management
if not self.history:
return # Nothing to undo
# Get current state and move to future for potential redo
current_state = self._capture_current_state()
self.future.append(current_state)
# Restore previous state
previous_state = self.history.pop()
self._restore_state(previous_state)
# Update UI to reflect restored state
self._update_all_ui_elements()
def _capture_current_state(self):
# Deep copy of all relevant state
state = {
"layers": self._deep_copy_layers(),
"selection": self.selection.copy() if self.selection else None,
"active_layer": self.active_layer,
"current_tool": self.current_tool,
"tool_settings": self.tool_settings.copy(),
"canvas_transform": {
"zoom": self.zoom_level,
"rotation": self.rotation,
"offset_x": self.offset_x,
"offset_y": self.offset_y
}
}
return state
This approach makes it much easier for AI to provide useful assistance.
# Simplified version using MVC pattern
import tkinter as tk
from PIL import Image, ImageTk, ImageFilter, ImageEnhance
from typing import Dict, List, Optional, Tuple, Callable
# Model: Handles data and business logic
class ImageEditorModel:
def __init__(self, image_path=None):
self.current_image = Image.open(image_path) if image_path else Image.new("RGBA", (800, 600), (255, 255, 255, 255))
self.history = []
self.future = []
def apply_filter(self, filter_name: str, **params) -> None:
"""Apply a filter to the current image."""
# Save state for undo
self.save_state()
# Apply the requested filter
if filter_name == "blur":
radius = params.get("radius", 2)
self.current_image = self.current_image.filter(ImageFilter.GaussianBlur(radius=radius))
elif filter_name == "sharpen":
self.current_image = self.current_image.filter(ImageFilter.SHARPEN)
elif filter_name == "brightness":
factor = params.get("factor", 1.5)
enhancer = ImageEnhance.Brightness(self.current_image)
self.current_image = enhancer.enhance(factor)
# More filters can be added here...
def save_state(self) -> None:
"""Save current state to history for undo functionality."""
self.history.append(self.current_image.copy())
self.future = [] # Clear redo stack when a new action is performed
def undo(self) -> bool:
"""Undo the last action. Returns True if successful."""
if not self.history:
return False
# Save current state to future for redo
self.future.append(self.current_image.copy())
# Restore previous state
self.current_image = self.history.pop()
return True
def redo(self) -> bool:
"""Redo the last undone action. Returns True if successful."""
if not self.future:
return False
# Save current state to history for undo
self.history.append(self.current_image.copy())
# Restore future state
self.current_image = self.future.pop()
return True
# View: Handles UI display
class ImageEditorView:
def __init__(self, root):
self.root = root
self.root.title("Simple Image Editor")
# Main frame
self.main_frame = tk.Frame(root)
self.main_frame.pack(fill=tk.BOTH, expand=True)
# Create UI components
self._create_menu()
self._create_toolbar()
self._create_canvas()
self._create_status_bar()
def _create_menu(self):
"""Create the application menu."""
self.menu = tk.Menu(self.root)
self.root.config(menu=self.menu)
# File menu
file_menu = tk.Menu(self.menu, tearoff=0)
self.menu.add_cascade(label="File", menu=file_menu)
file_menu.add_command(label="Open", command=lambda: None) # Will be connected to controller
file_menu.add_command(label="Save", command=lambda: None)
file_menu.add_separator()
file_menu.add_command(label="Exit", command=self.root.quit)
# Edit menu
edit_menu = tk.Menu(self.menu, tearoff=0)
self.menu.add_cascade(label="Edit", menu=edit_menu)
edit_menu.add_command(label="Undo", command=lambda: None)
edit_menu.add_command(label="Redo", command=lambda: None)
# Controller: Handles user interaction and connects model with view
class ImageEditorController:
def __init__(self, root):
self.model = ImageEditorModel()
self.view = ImageEditorView(root)
# Connect view events to controller methods
self._bind_events()
# Initial display
self.update_display()
def _bind_events(self):
"""Connect UI events to controller methods."""
# File menu
file_menu = self.view.menu.nametowidget(".file")
file_menu.entryconfig("Open", command=self.open_image)
file_menu.entryconfig("Save", command=self.save_image)
# Edit menu
edit_menu = self.view.menu.nametowidget(".edit")
edit_menu.entryconfig("Undo", command=self.undo)
edit_menu.entryconfig("Redo", command=self.redo)
# Filter buttons
self.view.blur_button.config(command=lambda: self.apply_filter("blur"))
self.view.sharpen_button.config(command=lambda: self.apply_filter("sharpen"))
self.view.brightness_button.config(command=lambda: self.apply_filter("brightness"))
def apply_filter(self, filter_name):
"""Apply the selected filter to the image."""
# Apply filter through model
self.model.apply_filter(filter_name)
# Update display to show changes
self.update_display()
def update_display(self):
"""Update the canvas to display the current image."""
# Convert PIL image to Tkinter PhotoImage
photo = ImageTk.PhotoImage(self.model.current_image)
# Update canvas
self.view.canvas.config(width=photo.width(), height=photo.height())
self.view.canvas.itemconfig(self.view.image_on_canvas, image=photo)
self.view.canvas.image = photo # Keep reference to prevent garbage collection
# Update status bar
width, height = self.model.current_image.size
self.view.status_bar.config(text=f"Image: {width}x{height} pixels")
# Main application
def main():
root = tk.Tk()
app = ImageEditorController(root)
root.mainloop()
if __name__ == "__main__":
main()
The simplified approach is more accessible for both students and AI tools