Código de línea única o validación Nethermind usando PVS-Studio C # para Linux

Foto 1

Este artículo está dedicado al lanzamiento de la prueba beta PVS-Studio C # para Linux, así como al complemento para Rider. Por una razón tan maravillosa, usando estas herramientas, verificamos el código fuente del producto Nethermind y en este artículo veremos errores interesantes y a veces divertidos.

Nethermind es un cliente rápido para .NET Core Ethereum para Linux, Windows, MacOs. Se puede usar en proyectos al configurar redes privadas Ethereum o dApps. Nethermind es de código abierto ubicado en GitHub. El proyecto fue fundado en 2017 y está en constante evolución.

Introducción


¿Te gusta el trabajo manual? Por ejemplo, como encontrar errores en el código del programa. De acuerdo, es bastante tedioso leer y analizar el sitio que escribió o todo el proyecto en busca de algún error complicado. Bueno, si el proyecto es pequeño, digamos 5,000 líneas, pero si su tamaño ya ha excedido cien mil o un millón de líneas. Además, fue escrito por diferentes programadores y, a veces, no en una forma muy digerible. ¿Qué hacer en este caso? ¿Es realmente necesario dormir en algún lugar, subestimar en algún lugar y el 100% de su tiempo para darse cuenta de todas estas líneas interminables para comprender dónde está este desagradable error? Dudo que te gustaría hacer esto. Entonces, ¿qué hacer, tal vez hay medios modernos para automatizar de alguna manera esto?

figura 3

Y luego entra en juego una herramienta como un analizador de código estático. El analizador estático es una herramienta para detectar defectos en el código fuente de los programas. La ventaja de esta herramienta sobre la verificación manual es la siguiente:

  • casi no pasa el tiempo buscando la ubicación del error (al menos es definitivamente más rápido que buscar el error al copiar y pegar con los ojos);
  • no se cansa, a diferencia de una persona que después de un tiempo de búsqueda necesitará descansar;
  • conoce muchos patrones de error de los que una persona ni siquiera es consciente;
  • utiliza tecnologías como: análisis de flujo de datos, ejecución simbólica, coincidencia de patrones y otras;
  • le permite analizar regularmente y en cualquier momento;
  • etc.

Por supuesto, el uso de un analizador de código estático no reemplaza ni cancela las revisiones de código. Pero las revisiones de código se están volviendo más productivas y útiles. Puede centrarse en encontrar errores de alto nivel, en transferir conocimientos, en lugar de cansarse de leer el código en busca de errores tipográficos.

Si se interesó en leer más al respecto, le sugiero el siguiente artículo , así como un artículo sobre las tecnologías utilizadas en PVS-Studio.

PVS-Studio C # para Linux / macOS


Por el momento, estamos transfiriendo nuestro analizador de C # a .NET Core, y también estamos desarrollando activamente un complemento para IDE Rider.

Si está interesado, puede inscribirse para una prueba beta completando el formulario en esta página . Las instrucciones de instalación se enviarán a su correo (no se preocupe, es muy simple), así como una licencia para usar el analizador.

Así es como se ve Rider con el complemento PVS-Studio:

Cuadro 4

Un poco de indignación


Quiero decir que en algunos lugares fue muy difícil leer el código fuente de Nethermind, porque en él las líneas de 300-500 caracteres de longitud son bastante normales. Sí, es todo el código sin formatear en 1 línea. Y estas líneas, por ejemplo, contienen varios operadores ternarios y operadores lógicos, y no hay nada allí. En general, disfrute a partir de la última temporada del juego de tronos.

Explicaré un poco para entender el alcance. Tengo un monitor UltraWide con una longitud de unos 82 centímetros. Si abre el IDE en él en pantalla completa, se ajustará a unos 340 caracteres. Es decir, las líneas de las que estoy hablando ni siquiera encajan. Si quieres ver cómo se ve, dejé enlaces a archivos en GitHub:

Ejemplo 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}");
    }
}

Ejemplo de enlace de archivo

2

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

Enlace al archivo

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);
    ...     
}

Enlace al archivo

Ahora imagine que se produce un error en dicha sección, ¿será agradable buscarlo? Estoy seguro de que no lo es, y todos entienden que es imposible escribir así. Y, por cierto, hay un lugar similar con un error en este proyecto.

Resultados de validación


Condiciones que no les gustan 0


Condición 1

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

Advertencia de PVS-Studio: V3106 Posiblemente el índice está fuera del límite. El índice '0' apunta más allá del límite de 'bytes'. Nethermind.Network ReceiptsMessageSerializer.cs 50

a notar un error, consideremos el caso cuando el número de elementos de la matriz es 0. A continuación, el bytes.Length == 0 condición será verdadera y un IndexOutOfRangeException de tipo 0 será lanzado al acceder al elemento de matriz .

Querían salir de este método inmediatamente si la matriz está vacía o si el elemento 0 es igual a un cierto valor, pero parece que accidentalmente confundieron "||" con "&&". Propongo solucionar este problema de la siguiente manera:

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

Condición 2

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

Advertencia de PVS-Studio: V3106 Posiblemente el índice está fuera del límite. El índice '0' apunta más allá del límite 'typesInGroup'. Nethermind.Runner EthereumStepsManager.cs 70

Se produce una situación similar a la descrita anteriormente. Si el número de elementos en typesInGroup es igual a 0, al acceder al elemento 0, se generará una excepción de tipo IndexOutOfRangeException .

Solo en este caso no entiendo lo que el desarrollador quería. Lo más probable es que, en lugar de typesInGroup [0] , necesite escribir nulo .

¿Error u optimización 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;
         }
      }
   }
   ...
}

Advertencia de PVS-Studio: V3102 Acceso sospechoso al elemento del objeto 'currentLevel.BlockInfos' mediante un índice constante dentro de un bucle. Nethermind.Blockchain BlockTree.cs 895

A primera vista, el error es obvio: el bucle tiene como objetivo iterar sobre los elementos currentLevel.BlockInfos , pero al acceder a currentLevel.BlockInfos [i], escribieron currentLevel.BlockInfos [0] . Arreglamos 0 en i y la misión se completa. Pero no te apresures, vamos a resolverlo.

Ahora, nosotros Longitud acceso al BlockHash del elemento nulo. Si es igual a currentHash , entonces tomamos de currentLevel.BlockInfos todos los elementos que no son igualescurrentHash , escríbele y sal del bucle. Resulta que el ciclo es superfluo.

Creo que antes había un algoritmo que decidieron cambiar / optimizar usando linq , pero algo salió mal. Ahora, en el caso de que la condición sea falsa, obtenemos iteraciones sin sentido.

Por cierto, si el desarrollador que escribió esto usaría el modo de análisis incremental , inmediatamente se daría cuenta de que algo andaba mal y solucionaría todo rápidamente. Por el momento, volvería a escribir el código de esta manera:

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

Desreferencia de referencia nula


Desreferenciar 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);
}

Advertencia de PVS-Studio: V3095 El objeto '_logger' se usó antes de que se verificara como nulo. Verifique las líneas: 118, 118. Nethermind.Wallet DevKeyStoreWallet.cs 118

Error en la secuencia incorrecta. En primer lugar hay una apelación a _logger.IsDebug y sólo después de eso es _logger controlada por nula . Por consiguiente, en el caso de que _logger sea nulo , obtenemos una excepción de tipo NullReferenceException .

Desreferenciar 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();
}

Advertencia de PVS-Studio: V3095 El objeto '_enode' se usó antes de que se verificara como nulo. Verifique las líneas: 55, 56. Nethermind.JsonRpc AdminModule.cs 55 El

error es completamente el mismo que el descrito anteriormente, solo que esta vez el culpable es _enode .

Quiero agregar que si olvidó verificar nulo, recuerde esto solo cuando su programa se bloquee. El analizador le recordará esto y todo estará bien.

Nuestro favorito copiar y pegar


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;
}

Advertencia de PVS-Studio: V3001 Hay subexpresiones idénticas 'a.s2 == b.s2' a la izquierda y a la derecha del operador '&&'. Nethermind.Dirichlet.Numerics UInt256.cs 1154

Aquí se verifica la misma condición 2 veces:

a.s2 == b.s2

Dado que los parámetros de una y b tienen un campo s3 , supongo que al copiar simplemente se olvidó de cambio S2 a S3 .

Resulta que los parámetros serán iguales más a menudo de lo esperado por el autor del código. Al mismo tiempo, algunos desarrolladores piensan que no hacen esto y comienzan a buscar un error en un lugar completamente diferente, gastando mucha energía y nervios.

Por cierto, los errores en las funciones de comparación son generalmente un clásico. Aparentemente, los programadores, al considerar tales funciones simples, se relacionan con su escritura de manera muy casual y desatendida. Prueba . Sabiendo esto, ahora ten 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);
    }
    ...
}

Advertencia de PVS-Studio: V3004 La declaración 'then' es equivalente a la declaración 'else'. Nethermind.BeaconNode BeaconNodeFacade.cs 177

Para cualquier valor de la acceptedLocally variable, el método devuelve el mismo. Es difícil decir si es un error o no. Supongamos que un programador copió una línea y olvidó cambiar StatusCode.Success a otra cosa, entonces esto es un verdadero error. Además, StatusCode tiene InternalError e InvalidRequest . Sin embargo, la refactorización de código es, probablemente, a la culpa y que no se preocupan por la acceptedLocally valor de todos modos, en este caso, la condición se convierte en el lugar que te hace sentarte y pensar si es un error o no. En cualquier caso, este caso es extremadamente desagradable.

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);
         }
     }
}

Advertencia de PVS-Studio: V3004 La declaración 'then' es equivalente a la declaración 'else'. Nethermind.Overseer.Test TestBuilder.cs 46

Y de nuevo, no nos importa la verificación, porque al final obtenemos lo mismo. Y nuevamente nos sentamos y sufrimos, pensando, pero qué quería el desarrollador escribir aquí. Una pérdida de tiempo que podría haberse evitado mediante el uso de análisis estático e inmediatamente arreglando un código tan ambiguo.

Caso 4

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

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

Advertencia de PVS-Studio: V3021 Hay dos declaraciones 'if' con expresiones condicionales idénticas. La primera instrucción 'if' contiene el método return. Esto significa que la segunda declaración 'si' no tiene sentido Nethermind.Network.Benchmark InFlowBenchmarks.cs 55

Simplemente presione Ctrl + V nuevamente . Eliminamos el exceso de cheques y todo está bien. Estoy seguro de que si alguna otra condición eran importantes aquí, entonces todo el mundo sería escribir en un caso a través del operador lógico I.

Caso 5

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

PVS-Studio Advertencia: V3030 Verificación periódica . La condición '_logger.IsInfo' ya se verificó en la línea 242. Nethermind.Synchronization SyncServer.cs 244

De la misma manera que en el cuarto caso, se realiza una verificación adicional. Sin embargo, la diferencia es que _logger no solo tiene una propiedad, sino también, por ejemplo, ' bool IsError {get; } '. Por lo tanto, el código probablemente debería verse así:

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

Bueno, o toda la culpa de la refactorización del código y solo una verificación ya no es necesaria.

Caso 6

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

Advertencia de PVS-Studio: V3022 La expresión 'missingParamsCount! = 0' siempre es verdadera. Nethermind.JsonRpc JsonRpcService.cs 127

Verifique la condición (missingParamsCount! = 0) y si es verdadera, la calculamos nuevamente y asignaremos el resultado a una variable. De acuerdo en que esta es una forma bastante original de escribir verdadero.

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++;
    }

    ...
  }
  ...
}

Advertencia de PVS-Studio: V3022 La expresión 'i == 0' siempre es falsa. Nethermind.Synchronization BlockDownloader.cs 192

Comencemos en orden. Al inicializar, a la variable i se le asigna el valor 1. A continuación, la variable solo se incrementa, por lo tanto, el valor siempre se pasará a la función falso .

Ahora veamos HandleAddResult :

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

Aquí estamos interesados ​​en isFirstInBatch . A juzgar por el nombre de este parámetro, es responsable de si algo es el primero en la fiesta. Hm, primero. Miremos nuevamente arriba y veamos que hay 2 llamadas usando i :

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

No hay que olvidar que en este caso la cuenta es a 1. Resulta que tenemos dos opciones: o bien una "primera" significa un elemento en el índice 1, o con el símbolo 0. En cualquier caso, al mismo tiempo, i será igual a 1.

Resulta que la llamada a la función debería verse así:

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

O así:

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

Y de nuevo, si el desarrollador usara constantemente un analizador estático, entonces escribiría este código y vería una advertencia, lo arreglaría rápidamente y disfrutaría de la vida.

Una prioridad ??


Situación 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);
  }
}

Advertencias de PVS-Studio:

  • V3123 Quizás el '??' El operador funciona de una manera diferente a la esperada. Su prioridad es inferior a la prioridad de otros operadores en su parte izquierda. Nethermind.Trie TrieNode.cs 43
  • V3123 Quizás el '??' El operador funciona de una manera diferente a la esperada. Su prioridad es inferior a la prioridad de otros operadores en su parte izquierda. Nethermind.Trie TrieNode.cs 44

El analizador le aconseja que compruebe cómo usamos los operadores "??" y que, para comprender cuál es el problema, propongo considerar la siguiente situación. Miramos esta línea aquí:

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

MemorySizes.RefSize y MemorySizes.ArrayOverhead son constantes:

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

Por lo tanto, para mayor claridad, propongo reescribir la cadena, sustituyendo sus valores:

(8 + FullRlp?.Length ?? 20)

Ahora suponga que FullRlp es nulo. Entonces (8 + nulo) será nulo . A continuación, obtenemos la expresión ( nulo ?? 20 ), que devolverá 20.

Resulta que, siempre que FullRlp sea nulo , el valor de MemorySizes.ArrayOverhead siempre se devolverá, independientemente de lo que esté almacenado en MemorySizes.RefSize. El fragmento en la línea de abajo es similar.

Pero la pregunta es, ¿el desarrollador quería este comportamiento? Veamos la siguiente línea:

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

Aquí, como en las secciones consideradas anteriormente, MemorySizes.RefSize se agrega a la expresión, pero

tenga en cuenta que después del primer operador "+" hay un paréntesis. Resulta que deberíamos agregar alguna expresión a MemorySizes.RefSize , y si es nula , agregaremos otra. Entonces el código debería verse así:

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);
  }
}

Nuevamente, esto es solo una suposición, sin embargo, si el desarrollador desea un comportamiento diferente, entonces debe indicarlo explícitamente:

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

Y luego, el que lee este código no tendría que profundizar durante mucho tiempo en lo que está sucediendo aquí y en lo que el programador quería.

Situación 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)
    {
        ...
    }
    ...
}

Advertencia de PVS-Studio: V3123 Quizás el '??' El operador funciona de una manera diferente a la esperada. Su prioridad es inferior a la prioridad de otros operadores en su parte izquierda. Nethermind.JsonRpc JsonRpcService.cs 123

Y nuevamente, la prioridad de la operación es "??", por lo tanto, como en la última vez, consideramos la situación. Miramos esta línea aquí:

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

Supongamos que providedParameters es nula , a continuación, para mayor claridad, sustituir de inmediato todo lo relacionado con providedParameters con nula, y sustituirlo por un valor aleatorio en lugar de expectedParameters.Length :

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

Ahora se nota de inmediato que hay dos controles similares, solo en un caso hay corchetes y en el otro allí. Ejecutemos este ejemplo. Primero obtenemos que ( nulo ?? 0 ) devolverá 0, luego resta 0 de 100 y obtenemos 100:

100 + null ?? 0;

Ahora, en lugar de ejecutar primero " null ?? 0 " y finalmente obtener ( 100 + 0 ), nos volvemos completamente diferentes.

Primero se ejecutará ( 100 + nulo ) y obtenemos nulo . Luego verifica ( nulo ?? 0 ), lo que lleva al hecho de que el valor de la variable missingParamsCount será 0.

Dado que la siguiente condición es que missingParamsCount es igual a cero, podemos suponer que el desarrollador buscó este comportamiento. Y diré, ¿por qué no poner corchetes y expresar mis pensamientos explícitamente? En general, es posible que esta verificación se deba a un malentendido de por qué a veces se devuelve 0, y esto no es más que una muleta.

Y nuevamente, estamos perdiendo el tiempo, aunque podríamos no haber hecho esto; use el modo de análisis incremental al escribir código .

Conclusión


En conclusión, espero poder comunicarles que el analizador estático es su amigo y no un supervisor malvado que solo espera que cometa un error.

También debe tenerse en cuenta que si usa el analizador una vez o rara vez lo usa, seguramente encontrará errores y algunos de ellos incluso se solucionarán rápidamente, pero también habrá otros a los que deberá aplastar. Por lo tanto, debe usar un analizador estático constantemente. Luego encontrará muchos más errores y los corregirá en el momento en que se escriba el código del programa y entienda exactamente lo que está tratando de hacer.

La simple verdad es que todos cometen errores y eso es normal. Todos aprendemos de los errores, pero solo de aquellos que pudieron notar y comprender. Por lo tanto, utilice herramientas modernas para buscar estos mismos errores, por ejemplo, PVS-Studio. Gracias por la atención.



Si desea compartir este artículo con una audiencia de habla inglesa, utilice el enlace a la traducción: Nikolay Mironov. Código de línea única o verificación de Nethermind usando PVS-Studio C # para Linux .

All Articles