Codegen SDK provides powerful tools for analyzing and improving code modularity. This guide will help you identify and fix common modularity issues like circular dependencies, tight coupling, and poorly organized imports.

Common use cases include:

  • Breaking up circular dependencies
  • Organizing imports and exports
  • Identifying highly coupled modules
  • Extracting shared code into common modules
  • Analyzing module boundaries

Analyzing Import Relationships

First, let’s see how to analyze import relationships in your codebase:

import networkx as nx
from collections import defaultdict

# Create a graph of file dependencies
def create_dependency_graph():
    G = nx.DiGraph()
    
    for file in codebase.files:
        # Add node for this file
        G.add_node(file.filepath)
        
        # Add edges for each import
        for imp in file.imports:
            if imp.from_file:  # Skip external imports
                G.add_edge(file.filepath, imp.from_file.filepath)
    
    return G

# Create and analyze the graph
graph = create_dependency_graph()

# Find circular dependencies
cycles = list(nx.simple_cycles(graph))
if cycles:
    print("🔄 Found circular dependencies:")
    for cycle in cycles:
        print(f"  • {' -> '.join(cycle)}")

# Calculate modularity metrics
print("\n📊 Modularity Metrics:")
print(f"  • Number of files: {len(graph.nodes)}")
print(f"  • Number of imports: {len(graph.edges)}")
print(f"  • Average imports per file: {len(graph.edges)/len(graph.nodes):.1f}")

Breaking Circular Dependencies

When you find circular dependencies, here’s how to break them:

def break_circular_dependency(cycle):
    # Get the first two files in the cycle
    file1 = codebase.get_file(cycle[0])
    file2 = codebase.get_file(cycle[1])
    
    # Create a shared module for common code
    shared_dir = "shared"
    if not codebase.has_directory(shared_dir):
        codebase.create_directory(shared_dir)
    
    # Find symbols used by both files
    shared_symbols = []
    for symbol in file1.symbols:
        if any(usage.file == file2 for usage in symbol.usages):
            shared_symbols.append(symbol)
    
    # Move shared symbols to a new file
    if shared_symbols:
        shared_file = codebase.create_file(f"{shared_dir}/shared_types.py")
        for symbol in shared_symbols:
            symbol.move_to_file(shared_file, strategy="update_all_imports")

# Break each cycle found
for cycle in cycles:
    break_circular_dependency(cycle)

Organizing Imports

Clean up and organize imports across your codebase:

def organize_file_imports(file):
    # Group imports by type
    std_lib_imports = []
    third_party_imports = []
    local_imports = []
    
    for imp in file.imports:
        if imp.is_standard_library:
            std_lib_imports.append(imp)
        elif imp.is_third_party:
            third_party_imports.append(imp)
        else:
            local_imports.append(imp)
    
    # Sort each group
    for group in [std_lib_imports, third_party_imports, local_imports]:
        group.sort(key=lambda x: x.module_name)
    
    # Remove all existing imports
    for imp in file.imports:
        imp.remove()
    
    # Add imports back in organized groups
    if std_lib_imports:
        for imp in std_lib_imports:
            file.add_import_from_import_string(imp.source)
        file.insert_after_imports("")  # Add newline
        
    if third_party_imports:
        for imp in third_party_imports:
            file.add_import_from_import_string(imp.source)
        file.insert_after_imports("")  # Add newline
        
    if local_imports:
        for imp in local_imports:
            file.add_import_from_import_string(imp.source)

# Organize imports in all files
for file in codebase.files:
    organize_file_imports(file)

Identifying Highly Coupled Modules

Find modules that might need to be split up:

from collections import defaultdict

def analyze_module_coupling():
    coupling_scores = defaultdict(int)
    
    for file in codebase.files:
        # Count unique files imported from
        imported_files = {imp.from_file for imp in file.imports if imp.from_file}
        coupling_scores[file.filepath] = len(imported_files)
        
        # Count files that import this file
        importing_files = {usage.file for symbol in file.symbols 
                         for usage in symbol.usages if usage.file != file}
        coupling_scores[file.filepath] += len(importing_files)
    
    # Sort by coupling score
    sorted_files = sorted(coupling_scores.items(), 
                         key=lambda x: x[1], 
                         reverse=True)
    
    print("\n🔍 Module Coupling Analysis:")
    print("\nMost coupled files:")
    for filepath, score in sorted_files[:5]:
        print(f"  • {filepath}: {score} connections")

analyze_module_coupling()

Extracting Shared Code

When you find highly coupled modules, extract shared code:

def extract_shared_code(file, min_usages=3):
    # Find symbols used by multiple files
    for symbol in file.symbols:
        # Get unique files using this symbol
        using_files = {usage.file for usage in symbol.usages 
                      if usage.file != file}
        
        if len(using_files) >= min_usages:
            # Create appropriate shared module
            module_name = determine_shared_module(symbol)
            if not codebase.has_file(f"shared/{module_name}.py"):
                shared_file = codebase.create_file(f"shared/{module_name}.py")
            else:
                shared_file = codebase.get_file(f"shared/{module_name}.py")
            
            # Move symbol to shared module
            symbol.move_to_file(shared_file, strategy="update_all_imports")

def determine_shared_module(symbol):
    # Logic to determine appropriate shared module name
    if symbol.is_type:
        return "types"
    elif symbol.is_constant:
        return "constants"
    elif symbol.is_utility:
        return "utils"
    else:
        return "common"

Was this page helpful?