Validação de código de linha única ou Nethermind usando o PVS-Studio C # para Linux

Imagem 1

Este artigo é dedicado ao lançamento do teste beta do PVS-Studio C # para Linux, bem como do plug-in para Rider. Por um motivo tão maravilhoso, usando essas ferramentas, verificamos o código-fonte do produto Nethermind e, neste artigo, veremos erros interessantes e às vezes engraçados.

Nethermind é um cliente rápido para o .NET Core Ethereum para Linux, Windows, MacOs. Pode ser usado em projetos ao configurar redes privadas Ethereum ou dApps. Nethermind é um software livre localizado no GitHub. O projeto foi fundado em 2017 e está em constante evolução.

Introdução


Você gosta de trabalho manual? Por exemplo, como encontrar erros no código do programa. Concordo, é bastante entediante ler e analisar o site que você escreveu ou o projeto inteiro em busca de algum bug complicado. Bem, se o projeto for pequeno, digamos 5.000 linhas, mas se seu tamanho já excedeu cem mil ou um milhão de linhas? Além disso, foi escrito por diferentes programadores e, às vezes, não de forma muito digerível. O que fazer neste caso? Você realmente precisa dormir em algum lugar, em algum lugar subestimado e 100% do seu tempo para perceber todas essas intermináveis ​​linhas para entender onde está esse erro desagradável? Duvido que você gostaria de fazer isso. Então, o que fazer, talvez haja meios modernos para automatizar isso de alguma forma?

Figura 3

E então uma ferramenta como um analisador de código estático entra em jogo. O analisador estático é uma ferramenta para detectar defeitos no código fonte dos programas. A vantagem desta ferramenta sobre a verificação manual é a seguinte:

  • quase não gasta seu tempo procurando a localização do erro (pelo menos é definitivamente mais rápido do que procurar a falha de copiar e colar com seus olhos);
  • não se cansa, ao contrário de uma pessoa que, após algum tempo de busca, precisa descansar;
  • conhece muitos padrões de erro dos quais uma pessoa pode nem estar ciente;
  • utiliza tecnologias como: análise de fluxo de dados, execução simbólica, correspondência de padrões e outras;
  • permite que você analise regularmente e a qualquer momento;
  • etc.

Obviamente, o uso de um analisador de código estático não substitui ou cancela as revisões de código. Mas as revisões de código estão se tornando mais produtivas e úteis. Você pode se concentrar em encontrar erros de alto nível, na transferência de conhecimento, em vez de se cansar de ler o código em busca de erros de digitação.

Se você ficou interessado em ler mais sobre isso, sugiro o seguinte artigo , bem como um artigo sobre as tecnologias usadas no PVS-Studio.

PVS-Studio C # para Linux / macOS


No momento, estamos portando nosso analisador de C # para o .NET Core e também estamos desenvolvendo ativamente um plug-in para o IDE Rider.

Se você estiver interessado, pode se inscrever para um teste beta preenchendo o formulário nesta página . As instruções de instalação serão enviadas para o seu e-mail (não se preocupe, é muito simples), além de uma licença para usar o analisador.

É assim que o Rider se parece com o plug-in PVS-Studio:

Quadro 4

Um pouco de indignação


Quero dizer que em alguns lugares foi muito difícil ler o código fonte do Nethermind, porque nele linhas de 300 a 500 caracteres são bastante normais. Sim, é todo o código sem formatar em 1 linha. E essas linhas, por exemplo, contêm vários operadores ternários e operadores lógicos, e não há nada lá. Em geral, prazer a partir da última temporada do jogo dos tronos.

Vou explicar um pouco para entender o escopo. Eu tenho um monitor UltraWide com um comprimento de cerca de 82 centímetros. Se você abrir o IDE em tela cheia, ele terá cerca de 340 caracteres. Ou seja, as falas das quais estou falando nem se encaixam. Se você quiser ver como fica, deixei links para arquivos no GitHub:

Exemplo 1

private void LogBlockAuthorNicely(Block block, ISyncPeer syncPeer)
{
    string authorString = (block.Author == null ? null : "sealed by " +
(KnownAddresses.GoerliValidators.ContainsKey(block.Author) ?
KnownAddresses.GoerliValidators[block.Author] : block.Author?.ToString())) ??
(block.Beneficiary == null ? string.Empty : "mined by " +
(KnownAddresses.KnownMiners.ContainsKey(block.Beneficiary) ?
KnownAddresses.KnownMiners[block.Beneficiary] : block.Beneficiary?.ToString()));
    if (_logger.IsInfo)
    {
        if (_logger.IsInfo) _logger.Info($"Discovered a new block
{string.Empty.PadLeft(9 - block.Number.ToString().Length, '
')}{block.ToString(Block.Format.HashNumberAndTx)} {authorString}, sent by
{syncPeer:s}");
    }
}

Exemplo 2 do link do arquivo



private void BuildTransitions()
{
    ...
    releaseSpec.IsEip1283Enabled = (_chainSpec.Parameters.Eip1283Transition ??
long.MaxValue) <= releaseStartBlock &&
((_chainSpec.Parameters.Eip1283DisableTransition ?? long.MaxValue) 
> releaseStartBlock || (_chainSpec.Parameters.Eip1283ReenableTransition ??
long.MaxValue) <= releaseStartBlock);           
    ...
}

Link para o arquivo

public void 
Will_not_reject_block_with_bad_total_diff_but_will_reset_diff_to_null()
{
    ...
    _syncServer = new SyncServer(new StateDb(), new StateDb(), localBlockTree,
NullReceiptStorage.Instance, new BlockValidator(Always.Valid, new
HeaderValidator(localBlockTree, Always.Valid, MainnetSpecProvider.Instance,
LimboLogs.Instance), Always.Valid, MainnetSpecProvider.Instance, 
LimboLogs.Instance), Always.Valid, _peerPool, StaticSelector.Full, 
new SyncConfig(), LimboLogs.Instance);
    ...     
}

Link para o arquivo

Agora, imagine que ocorra um erro nessa seção, será agradável procurá-lo? Tenho certeza de que não, e todo mundo entende que é impossível escrever assim. E, a propósito, há um lugar semelhante com um erro neste projeto.

Resultados da validação


Condições que não gostam de 0


Condição 1

public ReceiptsMessage Deserialize(byte[] bytes)
{
    if (bytes.Length == 0 && bytes[0] == Rlp.OfEmptySequence[0])
        return new ReceiptsMessage(null);
    ...
}

PVS-Studio Warning: V3106 Possivelmente, o índice está fora do limite. O índice '0' está apontando além do limite de 'bytes'. Nethermind.Network ReceiptsMessageSerializer.cs 50

Para observar um erro, considere o caso quando o número de elementos na matriz for 0. Em seguida, a condição bytes.Length == 0 será verdadeira e uma indexOutOfRangeException do tipo 0 será lançada ao acessar o elemento da matriz .

Eles queriam sair desse método imediatamente se a matriz estiver vazia ou o elemento 0 for igual a um determinado valor, mas parece que eles acidentalmente misturaram "||" com "&&". Proponho corrigir esse problema da seguinte maneira:

public ReceiptsMessage Deserialize(byte[] bytes)
{
    if (bytes.Length == 0 || bytes[0] == Rlp.OfEmptySequence[0])
        return new ReceiptsMessage(null);
    ...
}

Condição 2

public void DiscoverAll()
{
    ...
    Type? GetStepType(Type[] typesInGroup)
    {
        Type? GetStepTypeRecursive(Type? contextType)
        {
            ...
        }
        ...
        return typesInGroup.Length == 0 ? typesInGroup[0] :
               GetStepTypeRecursive(_context.GetType());
    }
    ...
}

PVS-Studio Warning: V3106 Possivelmente, o índice está fora do limite. O índice '0' está apontando além do limite 'typesInGroup'. Nethermind.Runner EthereumStepsManager.cs 70 Ocorre

uma situação semelhante à descrita acima. Se o número de elementos em typesInGroup for igual a 0, ao acessar o elemento 0, uma exceção do tipo IndexOutOfRangeException será lançada .

Somente neste caso eu não entendo o que o desenvolvedor queria. Provavelmente, em vez de typesInGroup [0], você precisa escrever nulo .

Erro ou otimização inacabada?


private void DeleteBlocks(Keccak deletePointer)
{
   ...
   if (currentLevel.BlockInfos.Length == 1)
   {
      shouldRemoveLevel = true;
   }
   else
   {
      for (int i = 0; i < currentLevel.BlockInfos.Length; i++)
      {
         if (currentLevel.BlockInfos[0].BlockHash == currentHash) // <=
         {
            currentLevel.BlockInfos = currentLevel.BlockInfos
                                      .Where(bi => bi.BlockHash != currentHash)
                                      .ToArray();
            break;
         }
      }
   }
   ...
}

PVS-Studio Warning: V3102 Acesso suspeito ao elemento do objeto 'currentLevel.BlockInfos' por um índice constante dentro de um loop. Nethermind.Blockchain BlockTree.cs 895

À primeira vista, o erro é óbvio - o loop visa iterar sobre os elementos currentLevel.BlockInfos , mas ao acessar currentLevel.BlockInfos [i], eles escreveram currentLevel.BlockInfos [0] . Fixamos 0 em ie a missão está concluída. Mas não se apresse, vamos descobrir.

Agora, acessamos Length o BlockHash do elemento nulo. Se for igual a currentHash , removeremos de currentLevel.BlockInfos todos os elementos que não são iguaiscurrentHash , escreva para ele e saia do loop. Acontece que o ciclo é supérfluo.

Eu acho que anteriormente havia um algoritmo que eles decidiram alterar / otimizar usando linq , mas algo deu errado. Agora, no caso em que a condição é falsa, obtemos iterações sem sentido.

A propósito, se o desenvolvedor que escreveu isso usasse o modo de análise incremental , ele imediatamente percebeu que algo estaria errado e teria consertado tudo rapidamente. No momento, eu reescreveria o código assim:

private void DeleteBlocks(Keccak deletePointer)
{
    ...
    if (currentLevel.BlockInfos.Length == 1)
    {
        shouldRemoveLevel = true;
    }
    else
    {
        currentLevel.BlockInfos = currentLevel.BlockInfos
                                  .Where(bi => bi.BlockHash != currentHash)
                                  .ToArray();
    }
    ...
}

Desreferenciando referência nula


Dereferencing 1

public void Sign(Transaction tx, int chainId)
{
    if (_logger.IsDebug)
        _logger?.Debug($"Signing transaction: {tx.Value} to {tx.To}");
    IBasicWallet.Sign(this, tx, chainId);
}

Aviso do PVS-Studio: V3095 O objeto '_logger' foi usado antes de ser verificado com valor nulo. Verifique as linhas: 118, 118. Nethermind.Wallet DevKeyStoreWallet.cs 118

Erro na sequência errada. Primeiro, há um recurso para _logger.IsDebug e somente depois disso é _logger verificado como nulo . Portanto, no caso em que _logger é nulo , obtemos uma exceção do tipo NullReferenceException .

Desreferenciação 2

private void BuildNodeInfo()
{
    _nodeInfo = new NodeInfo();
    _nodeInfo.Name = ClientVersion.Description;
    _nodeInfo.Enode = _enode.Info;                           // <=
    byte[] publicKeyBytes = _enode?.PublicKey?.Bytes;        // <=
    _nodeInfo.Id = (publicKeyBytes == null ? Keccak.Zero :
                   Keccak.Compute(publicKeyBytes)).ToString(false);
    _nodeInfo.Ip = _enode?.HostIp?.ToString();
    _nodeInfo.ListenAddress = $"{_enode.HostIp}:{_enode.Port}";
    _nodeInfo.Ports.Discovery = _networkConfig.DiscoveryPort;
    _nodeInfo.Ports.Listener = _networkConfig.P2PPort;
    UpdateEthProtocolInfo();
}

PVS-Studio Warning: V3095 O objeto '_enode' foi usado antes de ser verificado em relação a nulo. Verifique as linhas: 55, 56. Nethermind.JsonRpc AdminModule.cs 55 O

erro é completamente o mesmo que o descrito acima, só que desta vez o culpado é _enode .

Quero acrescentar que, se você esqueceu de verificar se é nulo, lembre-se disso somente quando o programa travar. O analisador lembrará você disso e tudo ficará bem.

Nosso Copy-Paste favorito


Caso 1

public static bool Equals(ref UInt256 a, ref UInt256 b)
{
    return a.s0 == b.s0 && a.s1 == b.s1 && a.s2 == b.s2 && a.s2 == b.s2;
}

PVS-Studio Warning: V3001 Existem subexpressões idênticas 'a.s2 == b.s2' à esquerda e à direita do operador '&&'. Nethermind.Dirichlet.Numerics UInt256.cs 1154

Aqui a mesma condição é verificada 2 vezes:

a.s2 == b.s2

Uma vez que os parâmetros a e b têm um campo s3 , presumo que ao copiar nós simplesmente esqueceu de mudança s2 para s3 .

Acontece que os parâmetros serão iguais com mais frequência do que o esperado pelo autor do código. Ao mesmo tempo, alguns desenvolvedores pensam que não fazem isso e começam a procurar um erro em um lugar completamente diferente, gastando muita energia e nervosismo.

A propósito, erros nas funções de comparação são geralmente um clássico. Aparentemente, os programadores, considerando tais funções simples, se relacionam com a sua escrita de maneira muito casual e desatenta. Prova . Sabendo disso, agora tenha cuidado :)!

Caso 2

public async Task<ApiResponse> 
PublishBlockAsync(SignedBeaconBlock signedBlock,
                  CancellationToken cancellationToken)
{
    bool acceptedLocally = false;
    ...
    if (acceptedLocally)
    {
        return new ApiResponse(StatusCode.Success);
    }
    else
    {
        return new ApiResponse(StatusCode.Success);
    }
    ...
}

PVS-Studio Warning: V3004 A instrução 'then' é equivalente à instrução 'else'. Nethermind.BeaconNode BeaconNodeFacade.cs 177

Para qualquer valor do acceptedLocally variável, o método retorna a mesma. É difícil dizer se é um erro ou não. Suponha que um programador copiou uma linha e esqueceu de alterar StatusCode.Success para outra coisa, então isso é um erro real. Além disso, o StatusCode possui InternalError e InvalidRequest . Mas a refatoração de código é provavelmente a culpa e não se preocupam com o acceptedLocally valor de qualquer maneira, nesse caso, a condição se torna o lugar que faz você se sentar e pensar se é um erro ou não. Portanto, em qualquer caso, este caso é extremamente desagradável.

Caso 3

public void TearDown()
{
    ...
    foreach (var testResult in _results)
    {
         string message = $"{testResult.Order}. {testResult.Name} has " 
               + $"{(testResult.Passed ? "passed [+]" : "failed [-]")}";
         if (testResult.Passed)
         {
               TestContext.WriteLine(message);
         }
         else
         {
               TestContext.WriteLine(message);
         }
     }
}

PVS-Studio Warning: V3004 A instrução 'then' é equivalente à instrução 'else'. Nethermind.Overseer.Test TestBuilder.cs 46

E novamente, não nos importamos com a verificação, porque no final obtemos a mesma coisa. E, novamente, sentamos e sofremos, pensando, mas o que o desenvolvedor queria escrever aqui. Um desperdício de tempo que poderia ter sido evitado usando análise estática e corrigindo imediatamente esse código ambíguo.

Caso 4

public void Setup()
{
    if (_decoderBuffer.ReadableBytes > 0)
    {
        throw new Exception("decoder buffer");
    }

    if (_decoderBuffer.ReadableBytes > 0)
    {
        throw new Exception("decoder buffer");
    }
    ...
}

PVS-Studio Warning: V3021 Existem duas instruções 'if' com expressões condicionais idênticas. A primeira instrução 'if' contém retorno de método. Isso significa que a segunda declaração 'if' não faz sentido;

basta pressionar Ctrl + V novamente . Removemos o excesso de verificação e está tudo bem. Tenho certeza de que, se alguma outra condição fosse importante aqui, todos escreveriam em uma se através do operador lógico I.

Caso 5

private void LogBlockAuthorNicely(Block block, ISyncPeer syncPeer)
{
    if (_logger.IsInfo)
    {
        if (_logger.IsInfo)
        {
            ...
        }
    }
}

PVS-Studio Warning: V3030 Verificação recorrente. A condição '_logger.IsInfo' já foi verificada na linha 242. Da

mesma maneira que no quarto caso, uma verificação extra é executada. No entanto, a diferença é que _logger não possui apenas uma propriedade, mas também, por exemplo, ' bool IsError {get; } '. Portanto, o código provavelmente deve ficar assim:

private void LogBlockAuthorNicely(Block block, ISyncPeer syncPeer)
{
    if (_logger.IsInfo)
    {
        if (!_logger.IsError) // <=
        {
            ...
        }
    }
}

Bem, ou toda a falha na refatoração do código e apenas uma verificação não é mais necessária.

Caso 6

if (missingParamsCount != 0)
{
    bool incorrectParametersCount = missingParamsCount != 0; // <=
    if (missingParamsCount > 0)
    {
        ...
    }
    ...
}

Aviso do PVS-Studio: A expressão V3022 'missingParamsCount! = 0' sempre é verdadeira. Nethermind.JsonRpc JsonRpcService.cs 127

Verifique a condição (missingParamsCount! = 0) e, se for verdadeira, calculamos novamente e atribuímos o resultado a uma variável. Concorde que esta é uma maneira bastante original de escrever verdade.

Cheque confuso


public async Task<long> 
DownloadHeaders(PeerInfo bestPeer, 
                BlocksRequest blocksRequest, 
                CancellationToken cancellation)
{
  ...
  for (int i = 1; i < headers.Length; i++)
  {
    ...
    BlockHeader currentHeader = headers[i];
    ...
    bool isValid = i > 1 ? 
        _blockValidator.ValidateHeader(currentHeader, headers[i - 1], false):
        _blockValidator.ValidateHeader(currentHeader, false);
    ...
    if (HandleAddResult(bestPeer, 
                        currentHeader, 
                        i == 0,                              // <=
                        _blockTree.Insert(currentHeader))) 
    {
       headersSynced++;
    }

    ...
  }
  ...
}

Aviso do PVS-Studio: A expressão V3022 'i == 0' é sempre falsa. Nethermind.Synchronization BlockDownloader.cs 192

Vamos começar em ordem. Ao inicializar, a variável i recebe o valor 1. Em seguida, a variável é incrementada apenas, portanto, o valor sempre será passado para a função false .

Agora vamos dar uma olhada no HandleAddResult :

private bool HandleAddResult(PeerInfo peerInfo, 
                             BlockHeader block,
                             bool isFirstInBatch, 
                             AddBlockResult addResult)
{
    ...
    if (isFirstInBatch)
    {
        ...
    }
    else
    {
        ...
    }
    ...
}

Aqui estamos interessados ​​em isFirstInBatch . A julgar pelo nome desse parâmetro, é responsável por saber se algo é o primeiro na parte. Hum, primeiro. Vamos olhar novamente acima e ver que existem 2 chamadas usando i :

BlockHeader currentHeader = headers[i];
_blockValidator.ValidateHeader(currentHeader, headers[i - 1], false)

Não se esqueça que a contagem regressiva, neste caso, passa de 1. Acontece que temos 2 opções: ou a “primeira” significa um elemento no índice 1 ou no índice 0. Mas, em qualquer caso, isso será igual a I.

Acontece que a chamada de função deve ficar assim:

HandleAddResult(bestPeer, currentHeader, 
                i == 1, _blockTree.Insert(currentHeader))

Ou assim:

HandleAddResult(bestPeer, currentHeader, 
                i - 1 == 0, _blockTree.Insert(currentHeader))

E, novamente, se um desenvolvedor usasse constantemente um analisador estático, ele escreveria esse código e veria um aviso, o corrigiria rapidamente e gozaria a vida.

Uma prioridade ??


Situação 1

public int MemorySize
{
  get
  {
    int unaligned = (Keccak == null ? MemorySizes.RefSize : 
        MemorySizes.RefSize + Keccak.MemorySize) 
        + (MemorySizes.RefSize + FullRlp?.Length 
                                 ?? MemorySizes.ArrayOverhead)   // <=
        + (MemorySizes.RefSize + _rlpStream?.MemorySize 
                                 ?? MemorySizes.RefSize)         // <=
        + MemorySizes.RefSize + (MemorySizes.ArrayOverhead + _data?.Length 
        * MemorySizes.RefSize ?? MemorySizes.ArrayOverhead) 
        + MemorySizes.SmallObjectOverhead + (Key?.MemorySize ?? 0);
    return MemorySizes.Align(unaligned);
  }
}

Avisos do PVS-Studio:

  • V3123 Talvez o '??' O operador trabalha de uma maneira diferente da esperada. Sua prioridade é menor que a prioridade de outros operadores na parte esquerda. Nethermind.Trie TrieNode.cs 43
  • V3123 Talvez o '??' O operador trabalha de uma maneira diferente da esperada. Sua prioridade é menor que a prioridade de outros operadores na parte esquerda. Nethermind.Trie TrieNode.cs 44

O analisador aconselha que você verifique como usamos os operadores "??" e, para entender qual é o problema, proponho considerar a seguinte situação. Nós olhamos para esta linha aqui:

(MemorySizes.RefSize + FullRlp?.Length ?? MemorySizes.ArrayOverhead)

MemorySizes.RefSize e MemorySizes.ArrayOverhead são constantes:

public static class MemorySizes
{
    ...
    public const int RefSize = 8;
    public const int ArrayOverhead = 20;
    ...
}

Portanto, para maior clareza, proponho reescrever a string, substituindo seus valores:

(8 + FullRlp?.Length ?? 20)

Agora, suponha que FullRlp seja nulo. Então (8 + nulo) será nulo . Em seguida, obtemos a expressão ( null ?? 20 ), que retornará 20.

Acontece que, desde que FullRlp seja nulo , o valor de MemorySizes.ArrayOverhead sempre será retornado, independentemente do que estiver armazenado em MemorySizes.RefSize. O fragmento na linha abaixo é semelhante.

Mas a questão é: o desenvolvedor queria esse comportamento? Vejamos a seguinte linha:

MemorySizes.RefSize + (MemorySizes.ArrayOverhead 
    + _data?.Length * MemorySizes.RefSize ?? MemorySizes.ArrayOverhead) 

Aqui, como nas seções consideradas acima, MemorySizes.RefSize é adicionado à expressão, mas

observe que após o primeiro operador "+", há um colchete. Acontece que devemos adicionar alguma expressão ao MemorySizes.RefSize e, se for nulo , adicionaremos outra. Portanto, o código deve ficar assim:

public int MemorySize
{
  get
  {
    int unaligned = (Keccak == null ? MemorySizes.RefSize : 
       MemorySizes.RefSize + Keccak.MemorySize) 
       + (MemorySizes.RefSize + (FullRlp?.Length 
                                 ?? MemorySizes.ArrayOverhead))    // <=
       + (MemorySizes.RefSize + (_rlpStream?.MemorySize 
                                 ?? MemorySizes.RefSize))          // <=
       + MemorySizes.RefSize + (MemorySizes.ArrayOverhead + _data?.Length 
       * MemorySizes.RefSize ?? MemorySizes.ArrayOverhead) 
       + MemorySizes.SmallObjectOverhead + (Key?.MemorySize ?? 0);
    return MemorySizes.Align(unaligned);
  }
}

Novamente, isso é apenas uma suposição, no entanto, se o desenvolvedor quiser um comportamento diferente, você deve indicar explicitamente isso:

((MemorySizes.RefSize + FullRlp?.Length) ?? MemorySizes.ArrayOverhead)

E então, quem lê esse código não precisa se aprofundar por muito tempo no que está acontecendo aqui e no que o programador queria.

Situação 2

private async Task<JsonRpcResponse> 
ExecuteAsync(JsonRpcRequest request, 
             string methodName,
             (MethodInfo Info, bool ReadOnly) method)
{
    var expectedParameters = method.Info.GetParameters();
    var providedParameters = request.Params;
    ...
    int missingParamsCount = expectedParameters.Length 
            - (providedParameters?.Length ?? 0) 
            + providedParameters?.Count(string.IsNullOrWhiteSpace) ?? 0; // <=
    if (missingParamsCount != 0)
    {
        ...
    }
    ...
}

Aviso do PVS-Studio: V3123 Talvez o '??' O operador trabalha de uma maneira diferente da esperada. Sua prioridade é menor que a prioridade de outros operadores na parte esquerda. Nethermind.JsonRpc JsonRpcService.cs 123

E novamente, a prioridade da operação é "??", portanto, como na última vez, consideramos a situação. Nós olhamos para esta linha aqui:

expectedParameters.Length 
            - (providedParameters?.Length ?? 0) 
            + providedParameters?.Count(string.IsNullOrWhiteSpace) ?? 0;

Suponha que fornecidoParameters seja nulo , para maior clareza, substitua imediatamente tudo relacionado a fornecidoParameters por nulo e substitua um valor aleatório em vez depectedParameters.Length :

100 - (null ?? 0) + null ?? 0;

Agora é perceptível imediatamente que há duas verificações semelhantes, apenas em um caso há colchetes e no outro, em lá. Vamos executar este exemplo. Primeiro, obtemos que ( null ?? 0 ) retornará 0, subtraia 0 de 100 e obterá 100:

100 + null ?? 0;

Agora, em vez de executar primeiro " null ?? 0 " e finalmente obter ( 100 + 0 ), ficamos completamente diferentes.

Primeiro ele será executado ( 100 + nulo ) e obteremos nulo . Em seguida, verifica ( null ?? 0 ), o que leva ao fato de que o valor da variável missingParamsCount será 0.

Como a próxima condição é que o missingParamsCount seja igual a zero, podemos assumir que esse é o comportamento que o desenvolvedor procurou. E eu digo: por que não colocar parênteses e expressar meus pensamentos explicitamente? Em geral, é possível que essa verificação tenha sido causada por um mal-entendido do motivo pelo qual 0 às vezes é retornado, e isso não passa de uma muleta.

E, novamente, estamos perdendo tempo, embora não tenhamos feito isso; use o modo de análise incremental ao escrever código .

Conclusão


Concluindo, espero poder transmitir a você que o analisador estático é seu amigo, e não um superintendente do mal que está apenas esperando que você cometa um erro.

Deve-se também observar que, se você usar o analisador uma vez ou raramente, certamente encontrará erros e alguns deles serão rapidamente corrigidos, mas também haverá outros em que você precisará esmagar a cabeça. Portanto, você precisa usar um analisador estático constantemente. Em seguida, você encontrará muitos outros erros e os corrigirá no momento em que o código do programa for gravado e você entenderá exatamente o que está tentando fazer.

A verdade simples é que todos cometem erros e isso é normal. Todos aprendemos com os erros, mas apenas daqueles que foram capazes de perceber e entender. Portanto, use ferramentas modernas para procurar esses mesmos erros, por exemplo - PVS-Studio. Obrigado pela atenção.



Se você deseja compartilhar este artigo com um público que fala inglês, use o link para a tradução: Nikolay Mironov. Código de linha única ou verificação do Nethermind usando o PVS-Studio C # para Linux .

All Articles