AI-Assisted Development

AI Limitations: A Test Case

Lecture 2

Understanding when AI coding assistants struggle and how to simplify problems for better results

When AI-Assisted Coding Falls Short

A concrete test case demonstrating the need for simplification

The Complex Version: AI Struggles

Custom image editor with complex state management

This complexity makes it difficult for AI to generate efficient, correct code without extensive guidance.

Complex Implementation (Part 1)

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
        # ...
    

Complex Implementation (Part 2)

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()
    

Complex Implementation (Part 3)

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
    

Complex Implementation (Part 4)

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...
    

Complex Implementation (Part 5)

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
    

Why AI Struggles with This

The Simplified Version: AI-Friendly

Breaking down the problem for effective AI assistance

This approach makes it much easier for AI to provide useful assistance.

Simplified Implementation (Part 1)


# 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...
    

Simplified Implementation (Part 2)


    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
    

Simplified Implementation (Part 3)


# 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)
    

Simplified Implementation (Part 4)


# 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"))
    

Simplified Implementation (Part 5)


    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()
    

Why This Works Better with AI

Key Takeaways

Practical Application

How to apply these insights in your development workflow

Visual Comparison

Complex Approach

  • Tightly coupled components
  • Complex state management
  • Custom UI widgets
  • Intricate event handling
  • Difficult for AI to understand

Simplified Approach

  • Clear separation of concerns
  • Standard architectural pattern
  • Well-documented components
  • Standard UI widgets
  • AI can assist effectively

The simplified approach is more accessible for both students and AI tools

Slide Overview