# QA Advisor — Architecture and Maintainability Reference

*Part of the QA Advisor skill: https://wavect.io/.well-known/agent-skills/qa-advisor/SKILL.md*

Hexagonal architecture (why a codebase is hard to test) plus coupling metrics, god objects, and cyclomatic complexity.

## Test Architecture — Hexagonal / Ports and Adapters

The most common reason a codebase is "hard to test" is architectural, not
technical. When business logic is entangled with infrastructure concerns
(database queries inside domain objects, HTTP calls inside business rules),
tests require real infrastructure or heavy mocking.

**Hexagonal Architecture (Alistair Cockburn) solves this:**

```
         ┌─────────────────────────────────┐
         │         Driving Adapters        │  ← Tests, HTTP, CLI, Events
         │  (call the application core)    │
         └──────────────┬──────────────────┘
                        │ drives via Ports (interfaces)
         ┌──────────────▼──────────────────┐
         │       Application Core          │  ← Pure business logic
         │  (no framework, no I/O, no ORM) │
         └──────────────┬──────────────────┘
                        │ uses via Ports (interfaces)
         ┌──────────────▼──────────────────┐
         │         Driven Adapters         │  ← Database, APIs, Email, Queue
         │  (implement the interfaces)     │
         └─────────────────────────────────┘
```

**The testability benefit:** the Application Core has no imports of framework
code, ORM, or HTTP clients. Its dependencies are all interfaces. Tests inject
fakes for the Driven Adapters and call the core directly. Tests are fast,
deterministic, and do not require a database.

**How to identify missing hexagonal structure:**
```bash
# In TypeScript: business logic files importing express/fastify/prisma/knex
grep -r "from 'express'" src/domain/
grep -r "from '@prisma/client'" src/domain/

# In Python: business logic importing SQLAlchemy/Django ORM directly
grep -r "from sqlalchemy" domain/
grep -r "from django.db" domain/
```

Every such import in a domain module is a testability debt item. Flag it and
quantify it (how many files, how many dependencies must be instantiated to run
a domain test).

---

## Maintainability — Code That Tests Can't Fix

Maintainability is not solely a testing concern, but test quality is impossible
to achieve in an unmaintainable codebase. These are the maintainability patterns
that most directly impair test quality.

### Coupling Metrics

**Afferent coupling (Ca):** how many modules depend on this module?
A high Ca module cannot be changed without risk. It needs the most test coverage.

**Efferent coupling (Ce):** how many modules does this module depend on?
A high Ce module is hard to test without mocking many dependencies. It usually
indicates a violation of the Single Responsibility Principle.

**Instability (I) = Ce / (Ca + Ce):** 0 = maximally stable (nothing can change it),
1 = maximally unstable (nothing depends on it, free to change).

The architecture principle: **stable modules should be abstract, unstable modules
should be concrete.** A concrete module with low instability (Ca >> Ce) is a
structural problem — changes to it will cascade.

### The God Object

A class or module that knows too much and does too much. Symptoms:
- More than 300 lines
- More than 10 public methods
- Appears in imports across 15+ files
- Has more than 5 constructor parameters

A God Object cannot be tested in isolation without constructing most of the
system. Tests for it are typically integration tests masquerading as unit tests.

### Cyclomatic Complexity

Cyclomatic complexity = number of linearly independent paths through a function.
Every `if`, `else if`, `for`, `while`, `case`, `&&`, `||` adds 1.

| Complexity | Risk | Action |
|---|---|---|
| 1–10 | Low | Fine |
| 11–20 | Moderate | Add tests for all branches |
| 21–50 | High | Refactor urgently |
| > 50 | Critical | Rewrite |

```bash
# JavaScript / TypeScript: complexity via ESLint
eslint --rule '{"complexity": ["error", 10]}' src/

# Python
radon cc -a -nb src/  # -nb: only show complex functions

# Java
checkstyle with CyclomaticComplexity module

# Go
gocyclo -over 10 ./...
```

Functions above complexity 20 have a combinatorial explosion of test cases.
They are usually under-tested by definition — no developer writes 30 test cases
for a single function.
