skip to content

How is the abstractness metric A = Na / Nc computed for a software component, and what do the values A = 0 and A = 1 mean in practice?

level: middleimportance: must knowfreq 45%

answer

  1. A = Na / Nc, abstract types over all types
  2. 0 = all implementation, 1 = all contracts
  3. Proxy for 'extend without modify'
  4. Pairs with I = Ce/(Ca+Ce)
  5. Gameable: empty interfaces, ignores quality

basics

~20 s

Count the component's abstract types (interfaces and abstract classes) as Na and all its types as Nc; A = Na/Nc, from 0 to 1. A = 0 means entirely concrete implementation; A = 1 means nothing but interfaces, no implementation at all.

solid answer

~50 s

For one component (jar/module/package): **Nc** = total number of classes/types in it; **Na** = number of those that are abstract — interfaces, abstract classes, protocols, traits with no instantiable implementation. **A = Na / Nc**, normalized to [0, 1]. A = 0 is a fully concrete component: every type is instantiable, so behaviour can only change by editing it. A = 1 is a pure abstraction component: only contracts, no implementation — callers depend on it, implementers realize it elsewhere. A is deliberately crude: it's a structural proxy for "can dependents get new behaviour without me changing?" It says nothing about whether the abstractions are *good* (a single fat interface and a well-segregated set score the same), it can be gamed by adding empty interfaces, and language differences (Go's implicit interfaces, duck-typed languages, sealed hierarchies) blur the count. Pair it with instability I = Ce/(Ca+Ce): SAP wants A high where I is low.

go deeper

for a junior

Give the formula, name what counts as abstract, and state the two endpoints (0 = all concrete, 1 = all contracts).

for a middle

Add why abstractness matters — extension without modification — and connect it to the instability metric I; work a small numeric example.

for a senior

Discuss limitations: gameability, blindness to interface quality, value objects depressing A, tool-definition drift, language differences; recommend pairing with churn data.

for a principal

Position A/I as diagnostics, not gates: use them to find volatile stable-concrete components, set architectural intent per component (api vs impl), and encode direction with dependency rules (ArchUnit, dependency-cruiser) rather than chasing metric targets.

### Definitions - **Component**: the release/deployment unit — jar, DLL/assembly, module, package, npm package. Metrics are computed per component, not per class. - **Nc**: the number of types (classes/interfaces/structs/protocols) contained in the component. Some tools count only *public* types on the grounds that only exported types participate in inter-component coupling; be explicit about which convention your tool uses. - **Na**: the number of those types that are **abstract** — cannot be instantiated on their own and exist to be implemented or extended: interfaces, abstract classes, traits, protocols, pure-virtual classes. - **A = Na / Nc**, in `[0, 1]`. ### Reading the value | A | Meaning | Typical inhabitant | |---|---|---| | 0 | Fully concrete — every type is implementation | a UI adapter, a persistence implementation, a main/composition-root module | | ~0.5 | Mixed — contracts plus implementations | a domain module with entities plus repository ports | | 1 | Pure abstraction — contracts only | an "api" / "ports" / "contracts" jar with only interfaces | ### Why abstractness is the right lever Abstractness is a proxy for **extensibility without modification**. If a component exposes an interface, dependents get new behaviour when someone writes a new implementation *in another component*; the abstract component's source is never touched, so nothing downstream recompiles or breaks. If a component is concrete, new behaviour requires editing it — and editing a widely depended-on component is exactly the expensive event SAP is trying to avoid. Hence the SAP pairing: **A should rise as instability `I = Ce/(Ca+Ce)` falls**. ### Worked example A `payments-api` component contains `PaymentGateway` (interface), `RefundPolicy` (interface), `PaymentResult` (concrete value type), `Money` (concrete value type). Na = 2, Nc = 4, **A = 0.5**. If 30 classes across the system depend on it and it depends on nothing outside, Ca = 30, Ce = 0, so **I = 0**. SAP says a component at I = 0 should sit near A = 1 — this one is at 0.5, so it carries some non-extensible content. That may be perfectly fine (immutable value types are non-volatile), which is why the metric prompts a conversation rather than dictating a refactor. ### Edge cases and honest limitations 1. **Gaming.** Adding empty marker interfaces raises A without improving anything. The metric measures shape, not design quality. 2. **Interface quality is invisible.** One 40-method god interface and ten cohesive 4-method interfaces (ISP-compliant) can produce identical A. Combine with Interface Segregation reasoning. 3. **Value objects and enums.** Concrete, but non-volatile and often unavoidable in an API component; they depress A without creating real rigidity. 4. **Language variance.** In Go, interfaces are satisfied implicitly and often declared in the *consumer*, so counting them in the producer misreports abstraction. In dynamic languages (Python, Ruby, JavaScript) duck typing means an abstraction may have no declared type at all. In Kotlin/Scala, sealed hierarchies are abstract but deliberately closed to outside extension — abstract by the count, non-extensible in intent. 5. **Definition drift.** Some analyzers count only public types; some count nested/generated types; some count abstract classes with implementations as fully abstract. Compare like with like over time; never compare raw numbers across tools. 6. **Zero-type components** make A undefined; tools typically report 0 or skip them. ### How to use it Track A and I per component and look at the **outliers**, especially components with `I ≈ 0` and `A ≈ 0` (stable and concrete — the Zone of Pain) that are also **churning** in version control. The combination of low A, low I, and high commit frequency is a far stronger signal than A alone.

  • Two components both score A = 0.5. Can you conclude they are equally well designed?
    No. A counts type kinds, not design quality. One might expose a single 40-method god interface plus one class; the other, cohesive small interfaces plus a value type. A also can't see whether the abstractions are actually implemented elsewhere, whether they leak implementation detail, or whether they're stable contracts versus churning ones.
  • How would you measure abstractness in a duck-typed language like Python or JavaScript where interfaces may not be declared?
    You approximate. Count abstract base classes / Protocol declarations / TypeScript interfaces and type aliases where they exist, but lean more on dependency-direction and churn analysis: which modules are imported by many others, and which of those change often. The structural intent of SAP — stable things should be extension points, not implementation — survives without a literal Na count.

saying these in an interview costs you the question

  • Counting only abstract classes and forgetting interfaces (or vice versa) when computing Na
  • Treating a high A as automatically good, ignoring whether anything depends on the component
  • Believing A measures design quality rather than the ratio of abstract to total types
  • Comparing A values produced by different tools with different counting rules
  • Boosting A with empty marker interfaces and calling it a SAP improvement

context