Avisos opt-in de código sem uso
Verificações de limpeza que vêm desligadas: campo sem uso, atribuída e não lida, código inalcançável, case incompleto e outras.
O que é e por que existe#
Código morto acumula: um campo private que ninguém lê, um parâmetro sem uso, um uses que sobrou. Esses diagnósticos acusam as sobras, mas todos vêm desligados, porque custam análise e alguns exigem julgamento. Quem não liga nenhum não paga nada: o padrão do compilador não muda.
Cada um tem a sua diretiva {$config Nome nível}, com ignore, hint, warning, error ou fatal (e os apelidos off, warn, on).
{$config …} | O que acusa |
|---|---|
UnusedPrivateField | campo private que ninguém usa |
UnusedPrivateMethod | método private que ninguém chama |
AssignedNotRead | variável local que recebe valor e nunca é lida |
UnreachableCode | código depois de Exit, raise, Break, Continue ou goto |
CaseNotExhaustive | case sobre enum, sem else, que não cobre todos os membros |
UnusedParam | parâmetro nunca usado (se a assinatura é imposta, chame-o _B) |
UnusedUses | módulo no uses de que nenhum nome é citado (o --diag=json leva a edição que o remove) |
UnusedGlobal | variável global nunca usada |
ForVarModified | variável do for alterada dentro do laço |
FloatEquals | igualdade exata entre reais (compare com tolerância) |
TupleNames | nomes de item de tupla que não casam |
program OptIn;
// Demo: avisos "opt-in" de código sem uso. Todos vêm DESLIGADOS; ligue com {$config Nome nível}.
// Úteis para limpar o código: cada um acusa um tipo de sobra ou descuido.
{$config UnusedPrivateField hint} // campo private que ninguém usa
{$config UnusedPrivateMethod hint} // método private que ninguém chama
{$config AssignedNotRead hint} // variável que recebe valor mas nunca é lida
{$config UnreachableCode hint} // código depois de Exit/raise/Break/Continue
{$config CaseNotExhaustive hint} // case de enum sem else que não cobre todos os membros
type
TColor = (cRed, cGreen, cBlue);
TBox = class
private
FUsed: Int32;
FDead: Int32; // Dica: campo nunca usado
procedure Helper; // Dica: método nunca chamado
public
procedure Run;
end class;
procedure TBox.Helper;
begin
end procedure;
procedure TBox.Run;
begin
FUsed := 1;
WriteLn('FUsed = ', FUsed);
end procedure;
function ColorName(AColor: TColor): UString;
begin
Result := '?';
case AColor of // Dica: faltam cBlue
cRed: Result := 'vermelho';
cGreen: Result := 'verde';
end case;
end function;
procedure Stuff;
var
LTmp: Int32;
begin
LTmp := 5; // Dica: recebe valor mas nunca é lida
Exit;
WriteLn('nunca roda'); // Dica: código inalcançável
end procedure;
begin
TBox.Create.Run;
WriteLn(ColorName(cRed), ', ', ColorName(cBlue));
Stuff;
end.FUsed = 1
vermelho, ?Demo 51. As cinco primeiras ligadas como Dica; o programa roda normalmente e as dicas saem na compilação.
Dicas da compilação do demo:
OptIn.fxp(36,3): Dica: o case sobre "TColor" nao tem else e nao cobre todos os membros: faltam cBlue
OptIn.fxp(48,10): Dica: codigo inalcancavel: o comando anterior (Exit, raise, Break, Continue ou goto) sempre desvia o fluxo
OptIn.fxp(17,5): Dica: o campo private "TBox.FDead" nunca e usado
OptIn.fxp(18,5): Dica: o metodo private "TBox.Helper" nunca e chamado
OptIn.fxp(44,3): Dica: a variavel "LTmp" recebe valor mas nunca e lidaNo projeto inteiro: Lint#
Em vez de uma diretiva por arquivo, o fc.config (ou o <projeto>.config) liga tudo de uma vez, em todos os módulos do projeto (a RTL nunca é cobrada):
Config All Lint on # o conjunto sensato, como dica
Config All Lint strict # todos, como aviso
Config All UnusedParam hint # uma chave individualLint on liga campo/método private, atribuída-não-lida, inalcançável, UnusedUses, ForVarModified e UnusedGlobal; warning é o mesmo como aviso; strict liga todos (inclui os de julgamento: CaseNotExhaustive, UnusedParam, FloatEquals). Precedência, da mais fraca para a mais forte: Lint do .config, chave individual do .config, diretiva no fonte.
O que fica de fora, de propósito#
Para evitar falso positivo, não são cobrados: membros de classe genérica, da RTL, que implementam interface ou têm asm/llvm, virtual/override/abstract, published, construtores, destrutores, operadores e external. Campo só escrito conta como usado. Local de tipo classe, interface ou record só avisa se toda atribuição for trivial (nil, literal, cópia): X := TFoo.Create fica fora, porque guardar a referência é o efeito (ARC).
Armadilhas#
- A diretiva vale no arquivo onde está, do ponto em que aparece para a frente. Para cobrir um projeto, use a chave do
.config. - Com
erroroufatalligados em código de terceiros a compilação cai, por design. x.f := 1ex[i] := 1contam como leitura dex. Um métodoprivatesó chamado por outro igualmente morto conta como usado.CaseNotExhaustivesozinho acha dezenas de casos no próprio compilador: por isso o padrão éignore.
Veja também#
- Marcas de uso, Configuração do compilador.
- Demo
51-avisos-optin.