Delphi-spec-kit Delphi Memory and Exceptions
Good memory management practices, memory leak prevention and exception handling in Delphi
git clone https://github.com/delphicleancode/delphi-spec-kit
T=$(mktemp -d) && git clone --depth=1 https://github.com/delphicleancode/delphi-spec-kit "$T" && mkdir -p ~/.claude/skills && cp -r "$T/.claude/skills/delphi-memory-exceptions" ~/.claude/skills/delphicleancode-delphi-spec-kit-delphi-memory-and-exceptions && rm -rf "$T"
.claude/skills/delphi-memory-exceptions/SKILL.md🧠 Memory and Exceptions Management in Delphi
Contexto
Delphi has manual memory management for class instances (not derived from interfaces) and uses ARC (Automatic Reference Counting) only for interfaces (
IInterface), Strings, Dynamic Arrays and anonymous types. Poor exception handling and forgetting to release memory result in chronic Memory Leaks, catastrophic production failures and systemic instability.
Like AI, you must proactively ensure that every object you create is freed regardless of error streams.
Objectives of this Skill
- Teach how to generate safe blocks of
.try..finally - Prevent Memory Leaks by advising on
andFree
.FreeAndNil - Promote the use of Interfaces for memory automation.
- Institute defensive and typed handling of Exceptions (
).try..except - Introduce custom Domain Exceptions.
🛑 Memory Management: Critical Rules
1. The Gold Standard: try..finally
try..finallyWhenever an object instance is created and does not have an Owner who manages it, instantiate it in a
try..finally block.
try must occur IMMEDIATELY on the line following creation.
var LList: TStringList; begin LList := TStringList.Create; try LList.Add('Item 1'); // ... finally LList.Free; end; end;
Anti-Pattern (DO NOT USE): Code between
Create and try may generate an exception, leaking the newly created object.
//WRONG - potential leak! LList := TStringList.Create; LList.Add('Item 1'); try // ...
2. Multiple Objects in the Same Block
When allocating multiple temporary resources in the same method, do not nest dozens of
try..finally if it is not strictly necessary. But be careful to initialize them all with nil beforehand if there is a chance of leakage, or nest them prudently. The ideal pattern is guaranteed sequential release, but strict nesting is safest for chained allocations:
var LStream: TMemoryStream; LReader: TStreamReader; begin LStream := TMemoryStream.Create; try LReader := TStreamReader.Create(LStream); try //logics with both finally LReader.Free; end; finally LStream.Free; end; end;
3. Avoid Creating Objects for Single Passage
If an API takes a class parameter, declare an interface or instantiate it before the method with
try..finally. Never pass an inline .Create to a parameter in a method if you do not have an absolute guarantee that the consuming function will free the memory.
4. Garbage Collection via Intefaces
For Dependency Injection, Repository/Service Patterns or Temporary Functional Classes, use inheritance from
TInterfacedObject linked to a Interface.
Delphi will kill the instance when the reference counter reaches zero.
var LService: ICustomerService; begin //No try..finally and no .Free calls. //Memory is scanned when leaving the scope of this procedure. LService := TCustomerService.Create; LService.ProcessDailyBatch; end;
🚨 Exception Handling: The Transparent Standard
1. Specific, Not Generic Catches
Use
try..except primarily to trap recoverable errors, log failures without breaking loops, or transform infrastructure exceptions into more semantic domain exceptions.
Never "Swallow" an exception without logical justification.
try PerformDatabaseCommit; except //SPECIFIC database capture on E: EFDDBEngineException do begin Logger.Error('Falha no banco de dados [Cód: %d]: %s', [E.ErrorCode, E.Message]); raise EDatabaseConnectionException.Create('Serviço temporariamente indisponível.'); end; //Validation SPECIFIC Capture on E: EValidationException do begin ShowWarning(E.Message); end; end;
Anti-Pattern (DO NOT USE): This blinds the application trace (hides
AccessViolations and Out of Memory).
try ProcessData; except //Wrong! Hides any developer errors during debugging! end;
2. Creating Exceptions Based on Business Logic (DDD)
Do not use
raise Exception.Create(str). Declare cohesive exceptions to enable elegant interception by upper layers (REST Controllers, UI Interface).
type //Domain/Essence of the Rules EBusinessRuleException = class(Exception); ECustomerLimitReachedException = class(EBusinessRuleException); //Infrastructure EInfrastructureException = class(Exception); EDatabaseConnectionException = class(EInfrastructureException);
3. Encapsulating Errors and Raise
without Modifying Context
RaiseIf you only need to perform a one-off log but want the exception to flow naturally to the global UI, just use pure
raise;.
try SomeDangerousCall; except on E: Exception do begin Logger.LogError('Critical failure', E); raise; //REPROJECT the original exception with the same stack-trace end; end;
💡 AI Action Flow
When asked to write/refactor code:
- Review whether each
will result in a deallocation (TObject.Create
or Third-Party Ownership)..Free - Inject
if you notice legacy code without it.try..finally - If you generate Services, recommend Dependency Injection via Interfaces (
) to simplify garbage scanning (GC).IService - In logic that may fail, create Typological Exceptions to validate flow and prevent error code spaghetti conditionals (
).if return = -1 then