I made a Delphi skill document for Claude Code but should work also with any other AI.
# Global Programming Conventions
These conventions apply to all my Delphi projects.
Main target is Delphi 13. When possible keep compatibility with lowe versions of Delphi, but not lower than Delphi Rio.
## Boilerplate Code
Avoid delegation properties if the underlying object is accessible:
“`pascal
// Avoid this if User is not a private field:
function TLesson.getColor: TColor;
begin
Result := User.Color;
end;
procedure TLesson.setColor(const Value: TColor);
begin
User.Color := Value;
end;
“`
Don’t use properties unless we need to put real code in getters/setters. In general properties are preferred over fields but they add a lot of boilerplate code.
So avoid:
property Age: Integer read FAge write FAge;
## Formatting
No space before `:=` assignment operator, but one space after:
“`pascal
// Yes:
Version:= 1;
// No:
Version := 1;
“`
For if-then statements, `then` on a new line:
“`pascal
// Yes:
if Something
then DoSomething;
// Yes:
if Something
then DoSomething
else DoSomethingElse;
// No:
if Something then
DoSomething;
“`
## Safer Code
Never use silent nil checks when the object should never be nil:
“`pascal
// No – hides bugs:
if SomeObject = NIL then EXIT;
// Instead use an assertion or exception to “crash” the program:
Assert(SomeObject <> NIL);
// or
if SomeObject = NIL then raise Exception.Create(‘…’);
“`
**Always use FreeAndNil instead of .Free:**
“`pascal
FreeAndNil(MyObject);
“`
## Function Exit Style
Prefer `EXIT(value)` over setting Result then exiting:
“`pascal
// Yes:
if NOT FLesson.AvailableShortQuestions
then EXIT(false);
// Avoid:
if NOT FLesson.AvailableShortQuestions then
begin
Result := false;
EXIT;
end;
“`
## Code Quality Standards
– Zero tolerance for global variables
– Zero tolerance for compiler hints and warnings
– Zero tolerance for swallowed exceptions
– Zero tolerance for memory leaks
## Forbidden Constructs
**Never use these deprecated or dangerous patterns:**
– `absolute` keyword – Use proper variable references instead
– Raw pointers (`^`, `Ptr^`) – Use object references and dynamic arrays
– `TFile` – Use TFileStream, TStreamReader/Writer, or better TLightStream in LightSaber library
– `Application.ProcessMessages` – Use threads or async patterns
– `with` statement – Causes ambiguity, use explicit references
– Generics when simpler alternatives exist – The big problem is that they increase binary size and compilation time dramatically.
**Instead of TFile (old unit):**
“`pascal
var
Stream: TFileStream;
begin
Stream := TFileStream.Create(‘file.dat’, fmOpenRead);
try
// Work with stream
finally
FreeAndNil(Stream);
end;
end;
“`
Or better StringToFile StringFromFile from c:\Projects\LightSaber\LightCore.TextFile.pas
**Instead of pointers, use:**
“`pascal
// Use dynamic arrays instead of pointer arrays
var
Items: TArray<string>;
// Use object references instead of object pointers
var
MyList: TList; // Not TList^
“`
**Instead of Application.ProcessMessages:**
“`pascal
// Use TThread for background work
TThread.CreateAnonymousThread(
procedure
begin
// Background work here
end
).Start;
// Or use TTask from System.Threading
TTask.Run(
procedure
begin
// Background work
end
);
“`
**When generics are justified:**
“`pascal
// Type-safe dictionary when you need key-value pairs
var
Cache: TDictionary<Integer, TMyData>;
“`
**Avoid generics when simpler alternatives exist** – they increase binary size significantly.
**Use generics only when:**
– Type safety is critical
– You need multiple strongly-typed collections
– The pattern is truly reusable across many types
## Modern Patterns
– Reduce usage of string helpers. These new features are cool BUT they are not debugable.
– Use constants and enumerations.
– Avoid initialization and finalization sections, because these sections are executed in non-deterministic order.
**Use try-finally for resource management:**
“`pascal
var
Stream: TFileStream;
Reader: TStreamReader;
begin
Stream:= nil;
Reader:= nil;
try
Stream:= TFileStream.Create(‘file.txt’, fmOpenRead);
Reader:= TStreamReader.Create(Stream);
// Use reader
finally
FreeAndNil(Reader);
FreeAndNil(Stream);
end;
end;
“`
**Use System.IOUtils for file operations:**
But prefer functions such as ListDirectoriesOf, CopyFolder, DeleteFolder, GetFileSize in LightCore.IO.pas.
**Use anonymous methods and closures:**
“`pascal
// Event handlers
Button1.OnClick := procedure(Sender: TObject)
begin
ShowMessage(‘Clicked’);
end;
// Background processing
TTask.Run(
procedure
var
Result: Integer;
begin
Result := PerformCalculation;
TThread.Synchronize(nil,
procedure
begin
Label1.Caption := Result.ToString;
end
);
end
);
“`
## Exception Handling
**Use specific exception types:**
“`pascal
try
// Risky operation
except
on E: EFileNotFoundException do
ShowMessage(‘File not found: ‘ + E.Message);
on E: EAccessViolation do
ShowMessage(‘Access violation: ‘ + E.Message);
on E: Exception do
ShowMessage(‘Unexpected error: ‘ + E.Message);
end;
“`
**Don’t swallow exceptions silently:**
“`pascal
// Bad – hides problems
try
RiskyOperation;
except
// Silent failure
end;
// Good – log or handle appropriately
try
RiskyOperation;
except
on E: Exception do
begin
AppData.LogError(E);
raise; // Re-raise if caller should know
end;
end;
“`
## Build System
**Main IDE target**: Delphi 13
Here is an example of build command:
call “c:\Delphi\Delphi 13\bin\rsvars.bat”
“c:\Windows\Microsoft.NET\Framework64\v4.0.30319\MSBuild.exe” “c:\Projects\LightSaber\LightCore.Dproj” /t:Build /p:platform=Win32 /p:Configuration=Debug
Note: The MsBuild wants a Dproj file not a DPR or DPK.