﻿# Об одной интересной ошибке в Lucene\.Net

Некоторые программисты, когда слышат про статический анализ, говорят о том, что он им не нужен, так как весь их код покрыт юнит\-тестами, и этого достаточно, чтобы выловить все ошибки\. Мне попалась ошибка, найти которую с помощью юнит\-тестов теоретически возможно, но если о ней не знать, то написать такой тест практически нереально\.

![0381_LuceneNet_ru/image1.png](https://import.viva64.com/docx/blog/0381_LuceneNet_ru/image1.png)

## Введение

Lucene\.Net \- это портированная с Java на C\# популярная библиотека для полнотекстового поиска\. Исходный код открыт и доступен на сайте проекта [https://lucenenet\.apache\.org/](https://lucenenet.apache.org/)\.

Так как этот проект развивается медленно, содержит не так много кода и используется во многих других проектах для полнотекстового поиска, то и наш анализатор нашёл всего пять подозрительных мест \[[1](https://pvs-studio.ru/ru/blog/posts/0158/)\]\. На большее я и не рассчитывал\. Но одно из этих срабатываний показалось мне особенно интересным, и я решил рассказать о нём читателям нашего блога\.

## О найденной ошибке

Есть у нас диагностика [V3035](https://pvs-studio.ru/ru/docs/warnings/v3035/) о том, что вместо \+\= можно по ошибке написать \=\+, где \+ будет унарным плюсом\. Когда я делал её по аналогии с такой же диагностикой [V588](https://pvs-studio.ru/ru/docs/warnings/v588/), предназначенной для языка C\+\+, я думал \- разве можно так ошибиться в C\#? В C\+\+ ещё ладно \- кто\-то использует разные текстовые редакторы вместо IDE, в которых можно опечататься и не заметить ошибку\. Но набирая текст в Visual Studio, которая автоматически выравнивает код после того, как поставил точку с запятой, как это можно пропустить? Оказывается, что можно\. Такую ошибку я обнаружил в Lucene\.Net\. А интересна она больше потому, что обнаружить её другими способами кроме статического анализа довольно сложно\. Рассмотрим код:

```cpp
protected virtual void Substitute( StringBuilder buffer )
{
    substCount = 0;
    for ( int c = 0; c < buffer.Length; c++ ) 
    {
        ....

        // Take care that at least one character
        // is left left side from the current one
        if ( c < buffer.Length - 1 ) 
        {
            // Masking several common character combinations
            // with an token
            if ( ( c < buffer.Length - 2 ) && buffer[c] == 's' &&
                buffer[c + 1] == 'c' && buffer[c + 2] == 'h' )
            {
                buffer[c] = '$';
                buffer.Remove(c + 1, 2);
                substCount =+ 2;
            }
            ....
            else if ( buffer[c] == 's' && buffer[c + 1] == 't' ) 
            {
                buffer[c] = '!';
                buffer.Remove(c + 1, 1);
                substCount++;
            }
            ....
        }
    }
}
```

Есть класс GermanStemmer, который обрезает суффиксы у немецких слов, чтобы выделить общий корень\. Работает он следующим образом: сначала метод Substitute заменяет разные хорошие сочетания букв на другие символы, чтобы не спутать их с суффиксом\. Заменяются: 'sch' на '$', 'st' на '\!' и так далее \(это видно из примера кода\)\. Причем количество символов, на которое такими заменами уменьшится длина слова, накапливается в переменной substCount\. Дальше метод Strip отрезает лишние суффиксы, и в конце метод Resubstitute выполняет обратную замену: '$' на 'sch', '\!' на 'st'\. То есть если у нас, например, было слово kapitalistischen \(капиталистический\), то [стеммер](https://ru.wikipedia.org/wiki/%D0%A1%D1%82%D0%B5%D0%BC%D0%BC%D0%B8%D0%BD%D0%B3) отработает следующим образом: kapitalistischen \=\> kapitali\!i$en \(Substitute\) \=\> kapitali\!i$ \(Strip\) \=\> kapitalistisch \(Resubstitute\)\.

Из\-за этой опечатки в коде при замене 'sch' на '$' в переменную substCount будет присвоено значение 2, вместо того, чтобы увеличить substCount на 2\. И такую ошибку довольно сложно найти другими методами, кроме как статическим анализом\. Есть разработчики, которые говорят: зачем мне статический анализатор, если у меня есть юнит\-тесты? Так вот, чтобы отловить такую ошибку тестами, нужно тестировать Lucene\.Net на немецком тексте, используя GermanStemmer, в тестах должно индексироваться слово, которое содержит в себе сочетание 'sch' и ещё одно сочетание букв, для которого будет выполнена подстановка, причём оно должно присутствовать в слове перед 'sch', чтобы substCount был отличен от нуля к тому моменту, когда выполнится выражение substCount \=\+ 2\. Довольно нетривиальная комбинация для теста, особенно когда не видишь ошибки\.

## Заключение

Юнит\-тесты и статический анализ \- это не исключающие, а дополняющие друг друга методики разработки программного обеспечения \[[2](https://pvs-studio.ru/ru/blog/posts/cpp/a0080/)\]\. Предлагаю [скачать](https://pvs-studio.ru/ru/pvs-studio/download/) статический анализатор PVS\-Studio, проверить свои проекты и найти ошибки, которые не были обнаружены с помощью юнит\-тестов\.

## Дополнительные ссылки

1. Андрей Карпов\. [Почему в маленьких программах низкая плотность ошибок](https://pvs-studio.ru/ru/blog/posts/0158/)\.
1. Андрей Карпов\. [Как статический анализ дополняет TDD](https://pvs-studio.ru/ru/blog/posts/cpp/a0080/)\.