Delphi Code Refactoring-b643e2 — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited Delphi Code Refactoring-b643e2 (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
Use this skill when the user requests refactoring, code review or removal of code smells in Object Pascal. Refactoring never changes observable behavior — it only improves the internal structure of the code.
"Refactoring is the art of changing the structure of code without changing its behavior." Always write (or check for) tests before refactoring.
Detect: Method with more than 20 lines or that needs comments to explain blocks.
Before:
procedure TInvoiceService.GenerateInvoice(AOrder: TOrder);
var
LTax, LSubtotal, LTotal: Currency;
LItem: TOrderItem;
LLines: TStringList;
begin
// Calcula subtotal
LSubtotal := 0;
for LItem in AOrder.Items do
LSubtotal += LItem.UnitPrice * LItem.Quantity;
// Calcula imposto
if AOrder.IsExempt then LTax := 0
else LTax := LSubtotal * 0.12;
LTotal := LSubtotal + LTax;
// Gera linhas do relatório
LLines := TStringList.Create;
try
LLines.Add('NOTA FISCAL');
LLines.Add(Format('Cliente: %s', [AOrder.Customer.Name]));
for LItem in AOrder.Items do
LLines.Add(Format(' %s x%d = R$%.2f',
[LItem.Product.Name, LItem.Quantity, LItem.UnitPrice * LItem.Quantity]));
LLines.Add(Format('Total: R$%.2f', [LTotal]));
LLines.SaveToFile(AOrder.InvoicePath);
finally
LLines.Free;
end;
end;After:
procedure TInvoiceService.GenerateInvoice(AOrder: TOrder);
var
LSubtotal, LTax: Currency;
begin
LSubtotal := CalculateSubtotal(AOrder);
LTax := CalculateTax(AOrder, LSubtotal);
SaveInvoiceFile(AOrder, LSubtotal + LTax);
end;
function TInvoiceService.CalculateSubtotal(AOrder: TOrder): Currency;
var LItem: TOrderItem;
begin
Result := 0;
for LItem in AOrder.Items do
Result := Result + (LItem.UnitPrice * LItem.Quantity);
end;
function TInvoiceService.CalculateTax(AOrder: TOrder; ASubtotal: Currency): Currency;
const
STANDARD_TAX_RATE = 0.12;
begin
if AOrder.IsExempt then Result := 0
else Result := ASubtotal * STANDARD_TAX_RATE;
end;
procedure TInvoiceService.SaveInvoiceFile(AOrder: TOrder; ATotal: Currency);
var
LLines: TStringList;
LItem: TOrderItem;
begin
LLines := TStringList.Create;
try
LLines.Add('NOTA FISCAL');
LLines.Add(Format('Cliente: %s', [AOrder.Customer.Name]));
for LItem in AOrder.Items do
LLines.Add(Format(' %s x%d = R$%.2f',
[LItem.Product.Name, LItem.Quantity, LItem.UnitPrice * LItem.Quantity]));
LLines.Add(Format('Total: R$%.2f', [ATotal]));
LLines.SaveToFile(AOrder.InvoicePath);
finally
LLines.Free;
end;
end;Detect: Class with fields of different nature, methods without cohesion.
Before:
TEmployee = class
private
// Data pessoais
FName: string;
FBirthDate: TDate;
FCpf: string;
// Data salariais
FBaseSalary: Currency;
FBonusPercentage: Double;
FDepartmentId: Integer;
// Data de RH
FHiredDate: TDate;
FVacationDaysLeft: Integer;
FPerformanceScore: Integer;
public
function CalculateGrossSalary: Currency;
function CalculateNetSalary: Currency;
function CalculateVacationPay: Currency;
function GetNextVacationDate: TDate;
function GetYearsOfService: Integer;
function IsEligibleForBonus: Boolean;
end;After:
// Value Object — imutável
TEmployeePersonalData = record
Name: string;
BirthDate: TDate;
Cpf: string;
end;
// Classe coesa: responsibility salarial
TEmployeeSalary = class
private
FBaseSalary: Currency;
FBonusPercentage: Double;
public
constructor Create(ABaseSalary: Currency; ABonusPercentage: Double);
function CalculateGross: Currency;
function CalculateNet: Currency;
function IsEligibleForBonus: Boolean;
end;
// Classe coesa: responsibility de RH
TEmployeeHrRecord = class
private
FHiredDate: TDate;
FVacationDaysLeft: Integer;
FPerformanceScore: Integer;
public
function GetYearsOfService: Integer;
function GetNextVacationDate: TDate;
function CalculateVacationPay(AGrossSalary: Currency): Currency;
end;
// Entidade principal — agora only agrega as partes
TEmployee = class
private
FPersonalData: TEmployeePersonalData;
FSalary: TEmployeeSalary;
FHrRecord: TEmployeeHrRecord;
public
constructor Create(AData: TEmployeePersonalData;
ASalary: TEmployeeSalary; AHr: TEmployeeHrRecord);
destructor Destroy; override;
property PersonalData: TEmployeePersonalData read FPersonalData;
property Salary: TEmployeeSalary read FSalary;
property HrRecord: TEmployeeHrRecord read FHrRecord;
end;Detect: More than 2 levels of if..then..begin..end nested.
Before:
function TBankService.Withdraw(AAccount: TAccount; AAmount: Currency): Boolean;
begin
Result := False;
if Assigned(AAccount) then
begin
if AAccount.IsActive then
begin
if AAmount > 0 then
begin
if AAccount.Balance >= AAmount then
begin
if not AAccount.IsBlocked then
begin
AAccount.Balance := AAccount.Balance - AAmount;
FRepository.Save(AAccount);
Result := True;
end;
end;
end;
end;
end;
end;After:
function TBankService.Withdraw(AAccount: TAccount; AAmount: Currency): Boolean;
begin
if not Assigned(AAccount) then
raise EArgumentNilException.Create('Conta não pode ser nula');
if not AAccount.IsActive then
raise EBusinessRuleException.Create('Conta inativa');
if AAmount <= 0 then
raise EValidationException.Create('Valor do saque deve ser positivo');
if AAccount.Balance < AAmount then
raise EInsufficientFundsException.CreateFmt(
'Saldo insuficiente. Disponível: R$%.2f', [AAccount.Balance]);
if AAccount.IsBlocked then
raise EBusinessRuleException.Create('Conta bloqueada');
AAccount.Balance := AAccount.Balance - AAmount;
FRepository.Save(AAccount);
Result := True;
end;Detect: Numeric or string literals without an explanatory name in the code.
Before:
if AProduct.Stock < 5 then NotifyLowStock(AProduct);
LInstallmentValue := AOrder.Total / 12;
if APassword.Length < 8 then raise ...;
if AUser.FailedLogins >= 3 then LockAccount(AUser);
LInterest := ADebt * 0.02; // juros de moraAfter:
const
LOW_STOCK_THRESHOLD = 5;
MAX_INSTALLMENTS = 12;
MIN_PASSWORD_LENGTH = 8;
MAX_FAILED_LOGIN_ATTEMPTS = 3;
MONTHLY_INTEREST_RATE = 0.02;
if AProduct.Stock < LOW_STOCK_THRESHOLD then NotifyLowStock(AProduct);
LInstallmentValue := AOrder.Total / MAX_INSTALLMENTS;
if APassword.Length < MIN_PASSWORD_LENGTH then raise ...;
if AUser.FailedLogins >= MAX_FAILED_LOGIN_ATTEMPTS then LockAccount(AUser);
LInterest := ADebt * MONTHLY_INTEREST_RATE;Detect: case or if/else if string that checks the type or regime of an object.
Before:
function TShippingService.CalculateFreight(AOrder: TOrder): Currency;
begin
case AOrder.ShippingMethod of
smPAC: Result := AOrder.Weight * 2.50 + 8.0;
smSEDEX: Result := AOrder.Weight * 4.80 + 15.0;
smTransp: Result := AOrder.Weight * 1.20;
smRetirada:Result := 0;
end;
end;After:
// Interface Strategy
IFreightStrategy = interface
['{GUID}']
function Calculate(AWeightKg: Double): Currency;
function GetCarrierName: string;
end;
// Implementations: uma por variaction
TPACFreightStrategy = class(TInterfacedObject, IFreightStrategy)
function Calculate(AWeightKg: Double): Currency; // peso * 2.50 + 8.0
function GetCarrierName: string;
end;
TSEDEXFreightStrategy = class(TInterfacedObject, IFreightStrategy)
function Calculate(AWeightKg: Double): Currency; // peso * 4.80 + 15.0
function GetCarrierName: string;
end;
TTransportadoraFreightStrategy = class(TInterfacedObject, IFreightStrategy)
function Calculate(AWeightKg: Double): Currency; // peso * 1.20
function GetCarrierName: string;
end;
TRetiradaFreightStrategy = class(TInterfacedObject, IFreightStrategy)
function Calculate(AWeightKg: Double): Currency; // sempre 0
function GetCarrierName: string;
end;
// Context — not precisa mais do case
function TShippingService.CalculateFreight(AOrder: TOrder;
AStrategy: IFreightStrategy): Currency;
begin
Result := AStrategy.Calculate(AOrder.WeightKg);
end;Detect: Method with > 4 parameters, especially if several are optional.
Before:
function TReportService.GenerateSalesReport(
AStartDate: TDate;
AEndDate: TDate;
const AProductCategory: string;
const ASalesRepId: string;
AMinAmount: Currency;
AGroupByMonth: Boolean;
AIncludeReturns: Boolean): TReportResult;After:
TSalesReportFilter = record
StartDate: TDate;
EndDate: TDate;
ProductCategory: string;
SalesRepId: string;
MinAmount: Currency;
GroupByMonth: Boolean;
IncludeReturns: Boolean;
// Construtor com defaults
class function Default: TSalesReportFilter; static;
end;
class function TSalesReportFilter.Default: TSalesReportFilter;
begin
Result.StartDate := StartOfYear(Date);
Result.EndDate := Date;
Result.ProductCategory := '';
Result.SalesRepId := '';
Result.MinAmount := 0;
Result.GroupByMonth := False;
Result.IncludeReturns := False;
end;
function TReportService.GenerateSalesReport(
AFilter: TSalesReportFilter): TReportResult;with StatementDetect: Any with Objeto do begin...end in the code.
Rule: Never use with. Prefer local variables or explicit qualification.
Before:
procedure TCustomerForm.LoadData;
begin
with qryCustomers do
begin
Close;
SQL.Text := 'SELECT * FROM customers WHERE id = :id';
ParamByName('id').AsInteger := FCustomerId;
Open;
with FieldByName('address') do
begin
edtStreet.Text := AsString;
end;
end;
end;After:
procedure TCustomerForm.LoadData;
var LAddressField: TField;
begin
qryCustomers.Close;
qryCustomers.SQL.Text := 'SELECT * FROM customers WHERE id = :id';
qryCustomers.ParamByName('id').AsInteger := FCustomerId;
qryCustomers.Open;
LAddressField := qryCustomers.FieldByName('address');
edtStreet.Text := LAddressField.AsString;
end;Detect: Service or class depending directly on concrete class (not interface).
Before:
TOrderService = class
private
FEmailSender: TSmtpEmailSender; // classe concreta
FRepository: TFireDACOrderRepo; // classe concreta
public
constructor Create; // instancia internamente — impossível testar
end;
constructor TOrderService.Create;
begin
FEmailSender := TSmtpEmailSender.Create('smtp.server.com', 587);
FRepository := TFireDACOrderRepo.Create(GetDatabaseConnection);
end;After:
// 1. Extraia interfaces
IEmailSender = interface
['{GUID}']
procedure Send(const ATo, ASubject, ABody: string);
end;
IOrderRepository = interface
['{GUID}']
function FindById(AId: Integer): TOrder;
procedure Save(AOrder: TOrder);
end;
// 2. Injete no construtor — testável e flexível
TOrderService = class
private
FEmailSender: IEmailSender;
FRepository: IOrderRepository;
public
constructor Create(ARepository: IOrderRepository; AEmailSender: IEmailSender);
end;
// 3. Em testes, injete fakes
TFakeEmailSender = class(TInterfacedObject, IEmailSender)
FSentMessages: TStringList;
procedure Send(const ATo, ASubject, ABody: string);
end;Detect: Variables like x, tmp, data, flag; methods like Proc1, DoIt, Handle.
// ❌ ANTES
var x, tmp: Integer;
s: string;
flag: Boolean;
procedure Handle(d: TData);
function Calc(v: Double): Double;
// ✅ DEPOIS
var LRetryCount, LMaxRetries: Integer;
LCustomerFullName: string;
LIsPaymentApproved: Boolean;
procedure ProcessCustomerOrder(AOrderData: TOrderData);
function CalculateShippingCost(AWeightKg: Double): Currency;Detect: Method that just calls another method, without additional logic.
// ❌ ANTES — delegaction desnecessária
function TOrder.GetTotal: Currency;
begin
Result := CalculateOrderTotal; // só delega
end;
function TOrder.CalculateOrderTotal: Currency;
begin
Result := FSubtotal + FTax - FDiscount;
end;
// ✅ DEPOIS — only um método com nome descritivo
function TOrder.GetTotal: Currency;
begin
Result := FSubtotal + FTax - FDiscount;
end;1. ENTENDER → Leia e compreenda o código atual
2. TESTAR → Escreva/execute testes que cobrem o comportamento atual
3. PEQUENO → Faça uma refatoração por vez (não tudo de uma vez)
4. TESTAR → Execute os testes novamente — devem continuar passando
5. COMMIT → Commit atômico por refatoração (mensagem: "refactor: extract CalculateTax")
6. REPETIR → Próxima refatoraçãowith was introduced?~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.