Delphi (12.3 Athens)
This assessment applies the coupling account — coupling as access to a shared resource, and the coupling floor [1], [2] — and the knowledge-embodiment theorem [3]; the language is assessed against its official reference [4].
1 Assessment
This page assesses Delphi in five parts: core coupling — what the language’s own constructs force; ecosystem coupling — what the surrounding platform, tooling, and community cost; engineering readiness — whether the artifact can be reproduced, verified, observed, worked in quickly, connected to other languages, and governed openly; the profile — the unscored facts a score cannot express; and the scoring detail — the mechanism marks and the arithmetic behind every score. A ✅ bullet marks a construct that helps. A ⚠️ or ❌ bullet marks a cost; the scoring model does not price every cost named here.
Core coupling 43/100 · Ecosystem coupling 84/100 · Engineering readiness 38/100
1.1 Core coupling
1.1.1 Type
- ✅ Unit
interfacesections separate the declaration from the implementation: A consumer compiles against theinterfacepart of a unit and never sees itsimplementationpart, so either part can change without the consumer being rewritten - ✅ Class, record, and interface types are declared and checked at compile time: A method call, a field access, and an argument are resolved against the declared type, so no name lookup happens at run time
- ✅ Dotted unit names give a namespace:
System.SysUtils,Vcl.Forms, and the project’s unit scope names qualify every entity, so two units may declare the same identifier without a collision - ⚠️ A variant record is a sum type without a checked tag: A
casepart in a record lays alternatives over the same storage, but reading the field of the wrong alternative is not diagnosed, so the tag lives in the programmer’s discipline (T4) - ⚠️ Generics exist but the constraint vocabulary is small: Generic classes and records (
TList<T>,TDictionary<TKey, TValue>) have been in the language since Delphi 2009, but a type parameter can be constrained only by a class, a record, a constructor, or an interface, so older library code still passesTObjectandTValue - ⚠️
nilis a value of every class, interface, and pointer type: There is no non-nullable reference type, so a call throughnilis a run-time fault rather than a compile-time error
1.1.2 Construction
- ✅ A property separates the name from the accessor:
property Count: Integer read GetCount write SetCountlets a consumer use one name while the class moves between a field and a method without touching the consumer - ✅ An event separates the publisher from the subscriber:
property OnClick: TNotifyEventand anonymous methods let the framework raise a notification without knowing which handler is attached, which is the observer pattern in the language - ✅ A
class helperextends an existing type from outside it: A helper adds a method to a class declared in another unit, so an extension does not require editing the type or inheriting it - ✅ A constructor is an ordinary method named
Create: Construction is a call rather than a special form, andCreatemay bevirtual, so a factory returns the right subclass - ✅
initializationandfinalizationgive a unit a declared start and end: The start-up and shutdown code of a unit has a named place, so a unit-level resource is not acquired in a random procedure - ❌ Mutability is the default: A field, a global variable, and an ordinary parameter are writable unless the declaration says
constorconstref, so the default reading of a variable promises nothing about it being unchanged (C2)
1.1.3 Memory
- ❌ Nothing ties a release to the end of a block: An object from
Createis released by an explicit.Free, usually insidetry/finally, and no destructor runs at scope exit, so cleanup is a written obligation (M1, M3) - ❌ There is no ownership model: The compiler does not track which reference owns an instance, so a double
.Free, a use after.Free, and a leak are neither diagnosed nor reported (M4) - ⚠️ Managed types are reference-counted and the rule differs by compiler:
string, dynamic arrays, and interface references release their storage when the last reference goes away, but an object is reference-counted only on the mobile compilers, so the lifetime rule depends on the target - ⚠️ An interface reference cycle is never released: The reference count of a cycle never reaches zero and there is no tracing collector, so the cycle leaks unless a weak reference breaks it
- ✅
TComponent.Ownergives a declared lifetime to a component graph: A component frees the components it owns when it is freed, so the object graph of a form is released deterministically - ✅ A managed record extends scope-tied release to a value type:
Initialize,Finalize,AddRef, andRemoveRefoperators let arecordrun code at the end of the block that holds it (Delphi 10.4 and later)
1.1.4 Error
- ❌ Exceptions are unchecked:
raiseneeds no declaration in the signature and no caller is obliged to handle the exception type, so the failure set of a routine is not part of its interface (Er2) - ✅
exceptwithon E: T dobinds the handler to a type: The handler names the exception class it catches and an unmatched exception propagates, so the recovery region is stated - ✅
try/finallyseparates cleanup from recovery: Afinallyblock runs on both the normal exit and the exceptional one, which is where a release belongs - ✅ An exception object carries its own data: A class derived from
Exceptioncan carry context andraisepropagates it unchanged, so a caller does not parse a message string - ⚠️ A bare
exceptswallows the cause: Anexceptblock withoutoncatches everything, including an access violation, and the recovery code may not know which failure it is covering - ⚠️ A memory fault arrives as an exception: A read through a dangling pointer raises
EAccessViolationat the faulting instruction, so an unsafe pointer becomes a run-time error that the program may or may not handle
1.1.5 Concurrency
- ❌ Synchronisation is manual and unchecked:
System.SyncObjssuppliesTCriticalSection,TMutex,TSemaphore,TEvent, andTInterlocked, but the compiler does not check the locking discipline or diagnose a race (K3) - ✅
System.Threadinggives a task abstraction:TTask.Run,TParallel.For, and futures carry work across a thread pool, so a parallel loop is stated rather than hand-threaded - ✅
TThreadwraps a thread as an object with a lifetime: The execution and the termination of a thread belong to one object, andSynchronizeandQueuemarshal a call back to another thread - ✅ A queued call separates a worker from the UI thread:
TThread.Queue,TThread.ForceQueue, andPostMessagedeliver work without a shared lock - ⚠️ Shared mutable state is the default: An ordinary field or global variable is reachable from every thread, and the language does not mark which state is confined to one thread (K2)
- ⚠️ The VCL is single-threaded by contract: The GUI may be touched only from the main thread, and that rule is documented rather than enforced
1.1.6 Platform
- ✅ One source targets Windows, macOS, iOS, Android, and Linux: The Object Pascal front end and the RTL are shared, and only the GUI framework differs between the targets
- ✅ A second compiler implements the same language: Free Pascal and Lazarus compile Object Pascal in a Delphi-compatible mode (
{$MODE DELPHI}), so a program is not bound to one vendor’s binary alone - ✅ The RTL is usable without the GUI framework: A console or service program links the runtime alone, so a non-GUI program does not depend on a windowing framework
- ⚠️ FireMonkey is the cross-platform GUI path and it is second to the VCL: The component sets and the visual behaviour of the two frameworks differ, so a GUI program depends on the one it was written for
- ⚠️ A program that calls the Windows API is bound to Windows: The
Windowsunit, COM, and the registry are used beside the RTL, and that part of the program is not portable - ⚠️ The compiler and the IDE are one vendor’s licensed product: The direction of the language follows that vendor, even though a compatible open compiler exists
1.1.7 Trade-offs (not coupling)
- The VCL/FireMonkey choice is a GUI decision: A RAD component couples the program to the framework it was dropped onto, and the alternative is to build the user interface against the raw platform
- Single inheritance is a deliberate simplification: The language gives interface types for the shared-behaviour case and keeps the class tree one level deep
- The RAD model trades control for speed of construction: A designer-saved form and a live object graph are why the framework owns the lifetime of the components
nilis a convenience as much as a hazard: An unset reference has a natural empty value, and the discipline of anassignedcheck is the programmer’s- Pointer arithmetic is opt-in in practice:
Pointer,GetMem, andSystem.Moveexist for the low-level case, and ordinary code need not use them - The language is an Object Pascal dialect, not Pascal: The dialect follows from the RAD requirement, and the compatibility with Turbo Pascal is historical
1.2 Ecosystem coupling
1.2.1 Performance
- ✅ The compiler emits native machine code:
dcc32anddcc64produce an executable directly, so no interpreter or JIT sits between the source and the CPU - ✅ A desktop release does not scan the heap: With reference-counted strings, dynamic arrays, and interfaces, the runtime has no tracing collector, so there is no collection pause to plan around
- ✅
inline,constparameters, and record passing control cost at the declaration: The call convention is stated beside the routine rather than inferred from its use - ⚠️ The classic optimiser is modest beside a modern back end: The vendor compiler does not apply the cross-module inlining and vectorisation that Clang or LLVM at
-O2applies, so a numerical kernel is usually delegated to a library - ⚠️ An interface call is an indirect call: Dispatch through an interface adds an indirection the compiler cannot resolve across units
1.2.2 Ecosystem
- ❌ The library pool is small and centered on the vendor’s own packages: A project draws on
GetIt, the bundled RTL/VCL/FireMonkey, and a small third-party set, so a missing library is often written in-house (Ec3) - ✅ The IDE is a complete tool: Editor, visual designer, debugger, profiler, and refactoring sit in one product, with no plugin assembly required
- ✅
GetItand open source cover the common needs: The IDE’s package channel, and community projects such asDUnitX,Spring4D, and ORM and JSON libraries, supply the usual layer - ✅ The vendor documentation is a reference work: The DocWiki holds an entry per type and per routine of the language, the RTL, and the VCL
- ⚠️ The platform is one vendor’s commercial product: The compiler, the IDE, and the frameworks are maintained by Embarcadero, so support and direction are commercial decisions
- ⚠️ Adoption is declining and the community is concentrated: The hiring pool and the library pool shrink with the Windows desktop market, and the active community is organized around a few sites
1.2.3 Evolution
- ✅ Annual releases add features with a published changelog: Each
12.xrelease records its language and RTL additions in the release notes - ✅ Backward compatibility is long-lived: Turbo Pascal-era source mostly still compiles with warnings, so a codebase is rarely rewritten for the toolchain alone
- ✅ A deprecation is reported with a replacement: The compiler warns on a deprecated routine and names its successor, so a migration is guided
- ⚠️ The roadmap is one vendor’s: A needed language feature waits for the vendor’s release decision, and the community cannot change the language
- ⚠️ A program that uses the VCL cannot leave Windows: The framework choice fixes the platform, and the cross-platform path is a separate framework
- ⚠️ A project is often pinned to an older IDE: The compiler version becomes part of the codebase, and an upgrade is a project of its own
1.2.4 Domain
- ✅ Windows desktop line-of-business software is the native target: A database front end, a form, and a grid are the idiom the framework is built for
- ✅ Rapid UI construction is a documented strength: The visual designer, the data-aware components (
TDataSet,TDataSource), and live bindings make a screen quick to build - ✅ A cross-platform client is supported through FireMonkey: One source produces a Windows, macOS, iOS, and Android client, although that path is secondary to the VCL
- ✅ Native Windows integration is direct: The
Windowsunit, COM, and the registry are reachable from the same code as the language - ⚠️ The market the language fits best is now a legacy market: New Windows desktop development is a smaller share than web and service development
- ⚠️ The fit outside a GUI application is thin: A service, a web back end, or an embedded program is written against the RTL alone, with little framework support
1.2.5 Learning
- ✅ The curve is gentle for a Pascal or C-family developer:
begin/end, a single-pass declaration order, and readable identifiers make the language quick to read - ✅ The IDE teaches the framework interactively: Dropping a component and reading the generated code is the standard on-ramp
- ✅ A reader sees each name’s type at hand:
var,const, andtypesections and typed parameters put the declaration beside its use - ⚠️ The framework is a second subject beyond the language: The VCL/FireMonkey object model, the streaming of a form (
.dfm), and the RTTI layer are knowledge the language does not imply - ⚠️ The Windows mental model is required for real work: The message loop, handles, threads, and COM are needed as soon as the program touches the system
1.2.6 Syntax
- ✅ Keywords delimit blocks and the statements read as English:
if ... then ... else,for ... do,while ... do, andbegin/end - ✅ Identifiers are case-insensitive and the layout is free: The compiler ignores case, and the IDE’s formatter keeps the source uniform across a team
- ⚠️ The language is verbose: Each declaration opens a
var,const, ortypesection, and a unit needs aninterface, animplementation, and a closingend.even for a few statements (S1) - ⚠️
tryandbeginpairs nest into anendladder: An ordinary routine with twotry/finallyblocks ends in a run ofend;lines - ⚠️ Attributes and compiler directives share the bracket notation:
[Weak],{$IFDEF}, and{$WARN}mix the language and the compiler controls in the same text
1.2.7 Testing
- ✅ A test framework ships with the toolchain:
DUnitX, and the olderDUnit, provide fixtures, attributes, and assertions, and the IDE runs the suite - ✅ The debugger steps through a test and shows the fault: The IDE debugger shows the call stack, the locals, and the thread state at the failure
- ✅
Assertis in the RTL:Assert(cond)raisesEAssertionFailedwhen assertions are enabled, so a precondition is stated in the code - ⚠️ The checks that would catch a fault are off by default: Range and overflow checking (
{$R+},{$Q+}) must be enabled per project, so an out-of-bounds index in a release build is undefined - ⚠️ Assertions are compiled out of a release build:
{$ASSERTIONS OFF}removes them, so their guarantee is a debug-time one - ⚠️ Debugging is the vendor’s IDE’s: A command-line debug session is not the standard workflow, so a headless build server runs tests without the interactive state
1.2.8 Security
- ⚠️ Memory safety is not guaranteed:
Pointer,GetMem/FreeMem,System.Move, andPChararithmetic are available, nothing marks the part of a program that uses them, and in that part the type system is not in the path, so its safety is the author’s claim - ✅ Managed types carry their length and are checked when range checking is on:
{$R+}makes an out-of-range index raiseERangeError, and a string or a dynamic array knows its own length - ✅ There is no dynamic evaluation of source text: The language has no
evalthat compiles a string, and reflection reaches only published members through the RTTI, so an injection path needs the program to build the call itself - ⚠️ The language offers no sandbox and no capability model: A program runs with the authority of the process, and the mitigations (DEP, ASLR, control-flow guard) are linker and loader options
1.3 Engineering readiness
1.3.1 Reproducibility
- ❌ Builds are not byte-reproducible by default.
- ❌ Dependencies resolve without a lock by default.
1.3.2 Supply chain
- ⚠️ Published packages carry no signed provenance by default.
- ⚠️ The package registry is a flat name space.
1.3.3 Observability
- ⚠️ No stable tracing and metrics standard.
- ⚠️ The standard library has no structured logging.
1.3.4 Feedback
- ⚠️ No fast in-editor analysis.
1.3.5 Interop
- ⚠️ No maintained portable target.
1.3.6 Governance
- ⚠️ One vendor decides the language’s direction.
- ⚠️ The specification is not public.
1.4 Profile
Unscored, and kept separate from every score on purpose. The profile records what a mechanism price cannot express: what a running program costs, how well a coding model and an agent work in the language, and whether a safety-critical path exists.
1.4.1 Energy and cost
What this axis asks: what does a running program cost in energy and time?
1.4.2 AI and agent readiness
What this axis asks: how well does a coding model and an agent work in this language?
- ❌ Sparse training data: models write it unreliably [8].
1.4.3 Safety certification
What this axis asks: is there a certified or standards-qualified path to a safety-critical system?
- ⚠️ No certified or standards-qualified path for safety-critical systems.
1.5 Scoring detail
1.5.1 Core coupling Dimensions
| Dimension | Mechanism | Description | Present | Cost |
|---|---|---|---|---|
| Type | T1 |
dynamic no static check | — | 0 |
T2 |
weak static | — | 0 | |
T3 |
dynamic/duck dispatch | — | 0 | |
T4 |
no sum types | ✅ | −4 | |
T5 |
no generics | — | 0 | |
T6 |
no namespaces | — | 0 | |
T7 |
type erasure/reflection | — | 0 | |
T8 |
operator overloading | — | 0 | |
T9 |
inheritance required for polymorphism | — | 0 | |
T10 |
no null-safety | — | 0 | |
T11 |
no module boundary | — | 0 | |
T12 |
no effect tracking | ✅ | −8 | |
T13 |
async/sync colouring | — | 0 | |
T14 |
unchecked foreign boundary | ✅ | −4 | |
| Type subtotal | −15 (−16) | |||
| Construction | C1 |
global mutable state by default | — | 0 |
C2 |
no immutability by default | ✅ | −4 | |
C3 |
constructors coupled to allocation | — | 0 | |
C4 |
no constructor/validation | — | 0 | |
| Construction subtotal | −4 | |||
| Memory | M1 |
manual deallocation | ✅ | −15 |
M2 |
GC non-deterministic pauses | — | 0 | |
M3 |
no RAII | ✅ | −4 | |
M4 |
no ownership model | ✅ | −8 | |
| Memory subtotal | −15 (−27) | |||
| Error | Er1 |
no structured path | — | 0 |
Er2 |
unchecked exceptions only | ✅ | −8 | |
Er3 |
errors ignored/non-exhaustive | — | 0 | |
Er4 |
no composable result type | — | 0 | |
| Error subtotal | −8 | |||
| Concurrency | K1 |
GIL/no parallel | — | 0 |
K2 |
shared mutable state default | ✅ | −8 | |
K3 |
manual sync no race detection | ✅ | −8 | |
K4 |
no structured concurrency | — | 0 | |
K5 |
no primitives | — | 0 | |
| Concurrency subtotal | −15 (−16) | |||
| Platform | P1 |
cannot function outside one vendor/domain | — | 0 |
P2 |
single-vendor but other uses | — | 0 | |
P3 |
legacy/discontinued platform | — | 0 | |
| Platform subtotal | 0 |
Core Dimensions score: 43/100 — total cost = −57 (per-dimension subtotals are capped at −15; an uncapped cost is shown in parentheses where the cap bites).
1.5.2 Ecosystem coupling Dimensions
| Dimension | Mechanism | Description | Present | Cost |
|---|---|---|---|---|
| Performance | R1 |
interpreted no AOT/JIT | — | 0 |
R2 |
GC pauses | — | 0 | |
R3 |
JIT warmup | — | 0 | |
| Performance subtotal | 0 | |||
| Ecosystem | Ec1 |
no package manager | — | 0 |
Ec2 |
fragmented tooling | — | 0 | |
Ec3 |
small/niche/discontinued | ✅ | −8 | |
Ec4 |
proprietary | — | 0 | |
Ec5 |
platform-locked tooling | — | 0 | |
Ec6 |
monoculture | — | 0 | |
Ec7 |
legacy burden | — | 0 | |
| Ecosystem subtotal | −8 | |||
| Evolution | V1 |
breaking changes | — | 0 |
V2 |
frozen | — | 0 | |
V3 |
discontinued | — | 0 | |
V4 |
glacial standards | — | 0 | |
| Evolution subtotal | 0 | |||
| Domain | D1 |
domain/vendor lock-in | — | 0 |
D2 |
poor domain fit | — | 0 | |
D3 |
framework forces architecture | — | 0 | |
| Domain subtotal | 0 | |||
| Learning | L1 |
steep | — | 0 |
L2 |
large surface/multi-paradigm | — | 0 | |
L3 |
sparse docs | — | 0 | |
| Learning subtotal | 0 | |||
| Syntax | S1 |
verbose | ✅ | −8 |
S2 |
inconsistent | — | 0 | |
S3 |
punctuation-heavy | — | 0 | |
| Syntax subtotal | −8 | |||
| Testing | G1 |
weak tooling | — | 0 |
G2 |
poor debugging | — | 0 | |
G3 |
runtime-only errors | — | 0 | |
| Testing subtotal | 0 | |||
| Security | Q1 |
memory-unsafe | — | 0 |
Q2 |
dynamic/weak typing risk | — | 0 | |
Q3 |
known vulnerability/injection | — | 0 | |
| Security subtotal | 0 |
Meta Dimensions score: 84/100 — total cost = −16 (per-dimension subtotals are capped at −15; an uncapped cost is shown in parentheses where the cap bites).
1.5.3 Engineering readiness Dimensions
| Dimension | Mechanism | Description | Present | Cost |
|---|---|---|---|---|
| Reproducibility | Y1 |
build not reproducible | ✅ | −8 |
Y2 |
dependencies not pinned | ✅ | −8 | |
| Reproducibility subtotal | −15 (−16) | |||
| Supply chain | Y3 |
no signed provenance | ✅ | −8 |
Y4 |
registry without namespacing | ✅ | −4 | |
| Supply chain subtotal | −12 | |||
| Observability | Y5 |
no stable tracing standard | ✅ | −8 |
Y6 |
no structured logging | ✅ | −4 | |
| Observability subtotal | −12 | |||
| Feedback | Y7 |
slow build | — | 0 |
Y8 |
no fast in-editor analysis | ✅ | −4 | |
| Feedback subtotal | −4 | |||
| Interop | Y9 |
no foreign-function interface | — | 0 |
Y10 |
no portable target | ✅ | −4 | |
| Interop subtotal | −4 | |||
| Governance | Y11 |
single-vendor governance | ✅ | −8 |
Y12 |
closed specification | ✅ | −8 | |
| Governance subtotal | −15 (−16) |
Readiness Dimensions score: 38/100 — total cost = −62 (per-dimension subtotals are capped at −15; an uncapped cost is shown in parentheses where the cap bites).
Verdict: Delphi’s design yields a middling core score, because its separating constructs sit beside its coupling ones. The interface section of a unit, a property, an event, and an interface type each decouple a consumer from an implementation, while the single-inheritance tree is the one hierarchy the language imposes. Main strengths: native compilation, a gentle Pascal-derived syntax, and a mature IDE and framework for Windows desktop work. Main weaknesses: exceptions are unchecked, a release is written rather than tied to scope, references are nilable with no ownership check, and the platform is one vendor’s commercial product in a declining market.
The formal development. The definitions, theorems, and proofs behind this page are published in:
- On the Nature of Coupling: All Coupling as Shared Resource Access — Zenodo, 2026 — doi:10.5281/zenodo.21925122
- Coupling as a Consequence of Cohesion: Decomposition, the Coupling Floor, and Domain-Imposed Coupling — Zenodo, 2026 — doi:10.5281/zenodo.21925201
- On the Nature of Cohesion: Cohesion as Correctness Relative to a Partitioning Rule — Zenodo, 2026 — doi:10.5281/zenodo.21703193