Cross-Cutting Detection
Some code appears everywhere. Logging calls exist in every file. Error handling patterns repeat across every service. Analytics events fire from every user action. These are cross-cutting concerns — they cut across the grain of the architecture rather than residing in a single layer. The critical distinction is between concerns that are truly cross-cutting (woven into every group and impossible to isolate) and concerns that look cross-cutting but are actually shared infrastructure with clear ownership. Getting this wrong in either direction distorts the scope group map.
Signals and Indicators
Logging — the canonical cross-cutting concern:
Logger.log(),os.log(),NSLog(),Log.d(),console.log(),_logger.LogInformation()— logging calls scattered through every file are not a scope group; they are woven in- A centralized
LogManagerorLoggerconfiguration file that sets up log destinations IS shared infrastructure — classify as a singleLogging Infrastructureunit - The test: would removing all log calls from a scope group break its logic? No. Logging is decoration, not behavior.
Error handling:
do/catch,try/except,try/catch,.catch {}— error handling is structural to the language, not a cross-cutting concern unto itself- A centralized
ErrorHandlerthat receives errors from all layers and routes them (to UI, analytics, retry logic) IS shared infrastructure — it has its own logic and state - Custom error types /
enum AppError— shared error taxonomy is shared infrastructure if it is used across 3+ scope groups - The test: does every scope group need to know about the
ErrorHandlerexplicitly? If yes, it may be an anti-pattern (forced coupling). If error types are passed as values, the error taxonomy is shared data, not cross-cutting behavior.
Analytics instrumentation:
Analytics.track("event_name", properties: [...])calls scattered across UI and business logic — cross-cutting concern; the call sites are not a scope group- The
Analyticsservice / SDK wrapper itself — shared infrastructure with a clear boundary - Event definition files (
AnalyticsEvents.swift,events.ts) — shared data definitions; belong with the analytics infrastructure, not split across callers - The test: if you removed all analytics calls, would each scope group still function correctly? Yes — analytics is observational, not behavioral.
Authentication checks:
guard isAuthenticated else { return }scattered in individual handlers — these are policy enforcement points, not a scope group- A centralized
AuthMiddleware,AuthGuard,SessionManager, orTokenRefreshInterceptorwith its own lifecycle and state — this IS a scope group (security infrastructure) - The test: does removing the auth check break the underlying feature logic? No — the feature works; it just runs without access control. Auth checks are policy, not logic.
Caching layers:
- Cache read/write calls inside service implementations (
if let cached = cache[key] { return cached }) — this is an optimization woven into the service; not a cross-cutting concern in the same sense as logging, but also not a separate scope group - A dedicated caching service —
CacheService,ImageCache,ResponseCache— with its own eviction policy and invalidation logic IS shared infrastructure - The test: could you remove the caching layer and have everything still work correctly (just slower)? Yes — caching is an optimization. If removing it breaks correctness (not just performance), it is not a cache but a source of truth.
Retry logic:
- Inline retry loops within individual network calls — part of the networking layer, not cross-cutting
- A centralized
RetryPolicyorRetryInterceptorapplied to all outgoing requests — shared infrastructure belonging to the networking scope group - The test: would removing retry logic break features? No — features would fail on transient errors instead of succeeding, but the logic is unchanged.
Dependency injection containers:
- A DI container that wires together all scope groups is a composition root — it is not itself a scope group in the functional sense, but it is a file (or files) that must be cataloged separately as the bootstrap/wiring layer
Distinguishing cross-cutting from shared infrastructure:
| Concern | Cross-cutting (do NOT isolate) | Shared infrastructure (IS its own scope group) |
|---|---|---|
| Logging | Log call sites throughout code | Logger configuration, log routing, log formatters |
| Analytics | Event tracking call sites | Analytics SDK wrapper, event schema definitions |
| Error handling | Try/catch at call sites | Centralized error router, custom error taxonomy |
| Auth | Auth checks at handler entry points | SessionManager, TokenRefreshService, AuthGuard |
| Caching | Cache lookups inline in services | CacheService with eviction and invalidation logic |
| Retry | Inline retry loops | RetryPolicy, RetryInterceptor applied globally |
The definitive test for cross-cutting vs shared infrastructure:
Ask: "Does this code have its own state, lifecycle, or logic — or does it exist only as calls woven into other code?" Code with its own state, lifecycle, or meaningful logic is shared infrastructure. Code that is only call sites in other modules is cross-cutting.
Boundary Detection
- Cross-cutting concerns are noted, not isolated. When a concern appears across all scope groups, note it in findings as cross-cutting and move on. Do not create a scope group whose members are "all the places that call log()".
- Shared infrastructure gets exactly one scope group. If a concern has its own files (a Logger class, a SessionManager), those files form one scope group. The call sites in other groups are dependencies on this group, not members of it.
- Logging infrastructure vs logging call sites. The
OSLog/Loggerconfiguration file is infrastructure. Theos.log("did the thing")call in a view controller is not. - Pervasive coupling is an architectural smell, not a decomposition strategy. If every scope group directly calls a
GlobalSingleton, note the anti-pattern — the solution is to inject the dependency, not to merge all groups into one because they share the dependency. - Security/auth infrastructure is always its own scope group. Never treat auth enforcement as merely cross-cutting — the SessionManager and token lifecycle have real behavior and state and warrant their own scope group (separate from where auth checks are enforced).
Findings Format
CROSS-CUTTING DETECTION FINDINGS
==================================
Cross-Cutting Concerns (not scope groups — call sites are woven in):
- <Concern> — call sites in <n> files across <m> candidate groups
Infrastructure file(s): <list> — these ARE shared infrastructure (see below)
Shared Infrastructure Scope Groups (distinct from call sites):
- <Name> — files: <list>
Reason it is infrastructure, not cross-cutting: <one sentence>
Consumed by: <n> other scope groups
Coupling Anomalies (pervasive anti-patterns):
- <description — e.g., "Analytics.shared accessed in 23 files with no abstraction layer — recommend wrapping in an injected AnalyticsService">
DI / Composition Root:
- <file(s)> — wires together: <list of scope groups>
Recommended Scope Group Candidates (infrastructure only):
- <Name> — <one-line rationale>
Concerns to Exclude from Scope Group Map (truly cross-cutting):
- <list of concerns that are only call sites and should not form scope groups>