Новогодний релиз PVS-Studio 6.00: проверяем Roslyn
Настало долгожданное событие. Мы выпустили релизную версию статического анализатора кода PVS-Studio 6.00, поддерживающего проверку C#-проектов. Теперь осуществляется проверка кода, написанного на следующих языках: C, C++, C++/CLI, C++/CX, C#. К выпуску шестой версии анализатора мы приурочили проверку открытого проекта Roslyn. Именно благодаря Roslyn в анализаторе PVS-Studio появилась поддержка C#, и мы очень благодарны компании Microsoft за реализацию и развитие этого проекта.
PVS-Studio 6.00
PVS-Studio — статический анализатор кода, ориентированный на простоту использования и поиск ошибок на этапе написания кода.
Мы постоянно добавляем диагностические правила для поиска новых разновидностей ошибок в программах на языке C/C++. Например, недавно мы добавили поиск членов класса, которые не инициализируется в конструкторе, что было весьма непростой задачей. Однако, усовершенствование диагностик не является поводом смены старшей версии. И мы ждали, когда добавим в анализатор что-то по-настоящему новое. И вот такой момент настал. Мы представляем миру шестую версию анализатора, поддерживающую язык C#.
Триальная версия PVS-Studio 6.00 доступна по ссылке:
http://www.viva64.com/ru/pvs-studio-download/
В шестой версии анализатора мы отказались от поддержки старых версий Visual Studio. Теперь не поддерживается VS2005 и VS2008. Если ваша команда по-прежнему использует эти версии Visual Studio, то мы предлагаем продолжить использовать предыдущую версию 5.31 или её апгрейды, если такие появятся.
Демонстрационная версия имеет следующие ограничения:
- Для просмотра доступны сообщения только 1 уровня.
- Можно выполнить 50 переходов по коду. После этого анализатор предложит человеку отправить информацию о себе. Если он согласится, то ему будет предоставлено ещё 50 переходов.
Ограничения демонстрационной версии могут показаться суровыми. Однако им есть обоснование, и мы пришли к ним после многих экспериментов. Подробнее о причинах ограничений рассказано в статье «PVS-Studio: новый триальный режим». Если кратко, то:
- Просмотр предупреждений только первого уровня гарантирует, что человек не пропустит настоящие ошибки. Это очень важно. Нужно сразу показать наличие ошибок в коде. Иначе высока вероятность, что он откажется от изучения предупреждения и пройдёт мимо в метре от клада.
- Малое количество «кликов» и ограничение анализа первым уровнем поможет быстрее начать общение в почте. Если кто-то захочет посмотреть на другие сообщения, то мы готовы выдать ему регистрационный ключ, скажем, на 1 неделю. Ему достаточно написать нам письмо. Как правило, в процессе переписки мы помогаем быстрее сориентироваться человеку, как он может быстро и просто получить пользу от использования анализатора.
Я понимаю, что прозвучало неубедительно. Поэтому все-таки прощу познакомиться со статей. Я уверен, что после прочтения вы поймете и поддержите наш подход к ограничениям.
Roslyn
Программисты не восприимчивы к рекламе. Их невозможно увлечь словами «миссия», «безупречно» и «фокус на инновациях». Программист не читает рекламные анонсы и знает, как отключить баннеры с помощью Adblock Plus.
Единственное, чем их можно увлечь, это показать, как тот или иной инструмент может быть им полезен. Мы избрали именно такой путь и показываем, какую пользу может приносить инструмент статического анализа, проверяя открытые проекты.
PVS-Studio умеет находить ошибки в таких известных проектах как CoreCLR, LibreOffice, Linux Kernel, Qt, Unreal Engine 4, Chromium и так далее. На данный момент наша команда проверила 230 открытых проектов и обнаружила в них 9355 ошибок. Да, да 9355 — это количество ошибок, а не количество диагностических сообщений. С наиболее интересными проверками можно познакомиться в соответствующих статьях.
Теперь очередь дошла и до проверки C# проектов. Не удивительно, что одним из первых проверенных проектов стал Roslyn. В конце концов, благодаря именно этому проекту и появилась возможность поддержать в PVS-Studio анализ C# кода.
Хочу выразить благодарность компании Microsoft за то, что она создала этот открытый проект. Он окажет большое влияние на развитие C#-инфраструктуры. Да он уже оказывает влияние! Например, на базу Rolsyn переносятся такие известные проекты как ReSharper или CodeRush.
Теперь немного о самом проекте Roslyn.
.NET Compiler Platform, более известная под кодовым названием Roslyn, — это набор компиляторов с открытым кодом и API для анализа исходного кода для языков C# и Visual Basic .NET. Платформа разрабатывается компанией Microsoft.
Данная платформа включает в себя самодостаточные компиляторы для C# и VB.NET. Они доступны не только в виде традиционных приложений командной строки, но и в качестве нативных API, к которым можно обращаться из .NET-кода. Roslyn открывает доступ к модулям синтаксического (лексического) анализа кода, семантического анализа, динамической компиляции в CIL-байткод и генерации сборки. Предоставляемые Roslyn API можно разделить на три типа: функциональные API (feature API), API рабочего пространства (workspace API) и API компиляторов (compiler API). Функциональные API облегчают процесс рефакторинга и отладки. API рабочего пространства позволяют разработчикам плагинов реализовывать определенные механизмы, специфичные для IDE наподобие Visual Studio — например, поиск соответствий между переменными и значениями или форматирование кода. API компиляторов обеспечивают возможности для еще более продвинутого анализа исходного кода, предоставляя информацию о прямых вызовах модулей анализа синтаксического дерева и анализа потока управления на этапе связывания идентификаторов со значениями.
Дополнительные ссылки:
- GitHub. Roslyn.
- Wikipedia. .NET Compiler Platform («Roslyn»)
- .NET Compiler Platform («Roslyn») Overview.
- MSDN. Forum. Microsoft «Roslyn» CTP.
- MSDN. Taking a tour of Roslyn.
- Learn Roslyn Now.
- Miguel de Icaza. Mono and Roslyn.
Найденные ошибки
Анализатор PVS-Studio нашел мало ошибок в Roslyn. Это естественно для столь известного проекта, разрабатываемого компанией Microsoft с её отлаженными системами контроля качества. Найти хоть что-то в коде Roslyn уже является большой победой, и мы гордимся, что можем это сделать.
Многие из приведенных далее ошибок относятся к тестам или обработчикам ошибок. Это естественно. Ошибки в часто используемых фрагментах кода исправляются благодаря тестированию и отзывам пользователям. Но для нас важен сам факт, что PVS-Studio может находить ошибки. Это означает, что многие ошибки, которые были исправлены с большими трудозатратами, могли бы исправляться сразу при регулярном использовании PVS-Studio.
Еще раз, другими словами. Ценность анализатора не в разовых запусках, а в его регулярном использовании. Статический анализ кода можно рассматривать как расширенный вариант предупреждений компилятора. Глупо включать предупреждения компилятора раз в год. На предупреждения нужно смотреть сразу после их возникновения. Это экономит время разработки на поиск глупых багов с помощью отладки. Со статическим анализатором всё то же самое.
Давайте посмотрим, что интересного удалось обнаружить с помощью PVS-Studio:
Ошибка N1 в тестах. Copy-Paste.
public void IndexerMemberRace()
{
....
for (int i = 0; i < 20; i++)
{
....
if (i % 2 == 0)
{
thread1.Start();
thread2.Start();
}
else
{
thread1.Start();
thread2.Start();
}
....
}
....
}
Предупреждение PVS-Studio: V3004 The 'then' statement is equivalent to the 'else' statement. GetSemanticInfoTests.cs 2269
Вот один из примеров ошибки в тестах, которая может жить в проекте годами, так как никому не мешает. Просто тест проверяет не всё, что планировалось. В начале всегда стартует поток 1, а потом поток 2. Скорее всего, планировалось написать тест так:
if (i % 2 == 0)
{
thread1.Start();
thread2.Start();
}
else
{
// Поменяли порядок запуска потоков
thread2.Start();
thread1.Start();
}
Ошибка N2 в тестах. Опечатка.
public DiagnosticAsyncToken(
AsynchronousOperationListener listener,
string name,
object tag,
string filePath,
int lineNumber)
: base(listener)
{
Name = Name;
Tag = tag;
FilePath = filePath;
LineNumber = lineNumber;
StackTrace = PortableShim.StackTrace.GetString();
}
Предупреждение PVS-Studio: V3005 The 'Name' variable is assigned to itself. AsynchronousOperationListener.DiagnosticAsyncToken.cs 32
На взгляд ошибку здесь найти весьма непросто. Подвело неудачное именование переменных. Имена свойств класса отличаются от аргументов функции только первой заглавной буквой. Как результат — легко допустить опечатку, что и произошло: Name = Name.
Ошибка N3, N4. Copy-Paste.
private Task
GetNewTokenWithRemovedOrToggledPragmaAsync(....)
{
var result = isStartToken
? GetNewTokenWithPragmaUnsuppress(
token, indexOfTriviaToRemoveOrToggle, _diagnostic, Fixer,
isStartToken, toggle)
: GetNewTokenWithPragmaUnsuppress(
token, indexOfTriviaToRemoveOrToggle, _diagnostic, Fixer,
isStartToken, toggle);
return Task.FromResult(result);
}
Предупреждение PVS-Studio: V3012 The '?:' operator, regardless of its conditional expression, always returns one and the same value. AbstractSuppressionCodeFixProvider.RemoveSuppressionCodeAction_Pragma.cs 177
Вне зависимости от значения 'isStartToken' функция GetNewTokenWithPragmaUnsuppress () вызывается с одним и тем же набором фактических аргументов. Скорее всего, вызов функции был продублирован с помощью Copy-Paste, а потом что-то забыли изменить в скопированном коде.
Вот ещё один похожий случай:
private void DisplayDiagnostics(....)
{
....
_console.Out.WriteLine(
string.Format((notShown == 1) ?
ScriptingResources.PlusAdditionalError :
ScriptingResources.PlusAdditionalError,
notShown));
....
}
Предупреждение PVS-Studio: V3012 The '?:' operator, regardless of its conditional expression, always returns one and the same value: ScriptingResources.PlusAdditionalError. CommandLineRunner.cs 428
Ошибки N5, N6. Невнимательность.
У меня ещё мало статистики по типовым ошибкам, которые допускают C# программисты. Но помимо стандартных опечаток явно начинает лидировать следующий паттерн ошибки: часто после приведения ссылки с помощью оператора 'as' проверяют не получившеюся ссылку, а исходную. После чего используют непроверенную ссылку. Синтетический код:
var A = B as T;
if (B) A.Foo();
А вот как эта ошибка выглядит на практике:
public override bool Equals(object obj)
{
var d = obj as DiagnosticDescription;
if (obj == null)
return false;
if (!_code.Equals(d._code))
return false;
....
}
Предупреждение PVS-Studio: V3019 Possibly an incorrect variable is compared to null after type conversion using 'as' keyword. Check variables 'obj', 'd'. DiagnosticDescription.cs 201
Следующий пример более многословен, но на самом деле всё тоже самое:
protected override bool AreEqual(object other)
{
var otherResourceString = other as LocalizableResourceString;
return
other != null &&
_nameOfLocalizableResource ==
otherResourceString._nameOfLocalizableResource &&
_resourceManager == otherResourceString._resourceManager &&
_resourceSource == otherResourceString._resourceSource &&
....
}
Предупреждение PVS-Studio: V3019 Possibly an incorrect variable is compared to null after type conversion using 'as' keyword. Check variables 'other', 'otherResourceString'. LocalizableResourceString.cs 121
Ошибка N7. Двойное обнаружение.
О наличии ошибки в коде иногда свидетельствуют сразу 2, а то и 3 предупреждения анализатора. Сейчас мы как раз рассмотрим такой случай.
private bool HasMatchingEndTag(
XmlElementStartTagSyntax parentStartTag)
{
if (parentStartTag == null)
{
return false;
}
var parentElement = parentStartTag.Parent as XmlElementSyntax;
if (parentStartTag == null)
{
return false;
}
var endTag = parentElement.EndTag;
....
}
Предупреждения PVS-Studio:
- V3019 Possibly an incorrect variable is compared to null after type conversion using 'as' keyword. Check variables 'parentStartTag', 'parentElement'. XmlTagCompletionCommandHandler.cs 123
- V3021 There are two 'if' statements with identical conditional expressions. The first 'if' statement contains method return. This means that the second 'if' statement is senseless XmlTagCompletionCommandHandler.cs 117
В начале функции проверяется, что аргумент 'parentStartTag' не является нулевой ссылкой. Если она нулевая, то происходит выход из функции.
Затем хотели проверить, что ссылка на самом деле указывает на класс типа 'XmlElementSyntax'. Но в этот момент в код вкралась опечатка. Вместо того, чтобы проверить 'parentElement', повторно проверяется 'parentStartTag'.
Анализатор замечает сразу две аномалии. Первая аномалия: нет смысла повторно проверять значение 'parentStartTag', так как если это нулевая ссылка, то мы уже покинули функцию. Вторая аномалия: анализатор подозревает, что проверяется не та переменная после оператора 'as'.
Корректный вариант кода:
if (parentStartTag == null)
{
return false;
}
var parentElement = parentStartTag.Parent as XmlElementSyntax;
if (parentElement == null)
{
return false;
}
Впрочем, этот код можно сократить до:
var parentElement = parentStartTag.Parent as XmlElementSyntax;
if (parentElement == null)
{
return false;
}
Ошибки N8, N9. Copy-Paste.
Прошу прощения за длинный фрагмент кода, но не хотелось заменять его на синтетический пример.
internal static bool ReportConflictWithParameter(....)
{
....
if (newSymbolKind == SymbolKind.Parameter ||
newSymbolKind == SymbolKind.Local)
{
diagnostics.Add(ErrorCode.ERR_LocalSameNameAsTypeParam,
newLocation, name);
return true;
}
if (newSymbolKind == SymbolKind.TypeParameter)
{
return false;
}
if (newSymbolKind == SymbolKind.Parameter ||
newSymbolKind == SymbolKind.Local)
{
diagnostics.Add(ErrorCode.ERR_LocalSameNameAsTypeParam,
newLocation, name);
return true;
}
....
}
Предупреждение PVS-Studio: V3021 There are two 'if' statements with identical conditional expressions. The first 'if' statement contains method return. This means that the second 'if' statement is senseless InMethodBinder.cs 264
В приведенном в статье фрагменте кода совпадает первый и третий оператор 'if'. Видимо блок был скопирован, а затем в нем что-то забыли изменить. Хотя возможно, повторный 'if' просто лишний и его следует удалить.
Есть ещё один подобный участок кода, но я не буду мучить читателя длинным примером. Просто приведу диагностическое предупреждение:
V3021 There are two 'if' statements with identical conditional expressions. The first 'if' statement contains method return. This means that the second 'if' statement is senseless WithLambdaParametersBinder.cs 131
Ошибка N10. Неверное условие.
public enum TypeCode
{
....
Object = 1,
....
DateTime = 16,
....
}
static object GetHostObjectValue(Type lmrType, object rawValue)
{
var typeCode = Metadata.Type.GetTypeCode(lmrType);
return (lmrType.IsPointer || lmrType.IsEnum ||
typeCode != TypeCode.DateTime ||
typeCode != TypeCode.Object)
? rawValue : null;
}
Предупреждение PVS-Studio: V3022 Expression 'lmrType.IsPointer || lmrType.IsEnum || typeCode!= TypeCode.DateTime || typeCode!= TypeCode.Object' is always true. DkmClrValue.cs 136
Выражение достаточно сложное, поэтому я выделю главную суть:
(typeCode != 1 || typeCode != 16)
Это выражение всегда является истинным, независимо от значения переменной 'typeCode'.
Ошибка N11. Избыточное условие.
public enum EventCommand
{
Disable = -3,
Enable = -2,
SendManifest = -1,
Update = 0
}
protected override void OnEventCommand(
EventCommandEventArgs command)
{
base.OnEventCommand(command);
if (command.Command == EventCommand.SendManifest ||
command.Command != EventCommand.Disable ||
FunctionDefinitionRequested(command))
....
}
Предупреждение PVS-Studio: V3023 Consider inspecting this expression. The expression is excessive or contains a misprint. RoslynEventSource.cs 79
Вновь выделю суть:
if (A == -1 || A != -3)
Это выражение ошибочно или избыточно. Его можно сократить до:
if (A != -3)
Ошибка N12 при логировании.
static CompilerServerLogger()
{
....
loggingFileName = Path.Combine(loggingFileName,
string.Format("server.{1}.{2}.log",
loggingFileName,
GetCurrentProcessId(),
Environment.TickCount));
....
}
Предупреждение PVS-Studio: V3025 Incorrect format. A different number of format items is expected while calling 'Format' function. Expected: 2. Present: 3. CompilerServerLogger.cs 49
Переменная 'loggingFileName' никак не используется при вызове функции Format (). Это подозрительно.
Ошибка N13 в обработчике ошибок.
private const string WriteFileExceptionMessage =
@"{1}
To reload the Roslyn compiler package, close Visual Studio and
any MSBuild processes, then restart Visual Studio.";
private void WriteMSBuildFiles(....)
{
....
catch (Exception e)
{
VsShellUtilities.ShowMessageBox(
this,
string.Format(WriteFileExceptionMessage, e.Message),
null,
OLEMSGICON.OLEMSGICON_WARNING,
OLEMSGBUTTON.OLEMSGBUTTON_OK,
OLEMSGDEFBUTTON.OLEMSGDEFBUTTON_FIRST);
}
}
Предупреждение PVS-Studio: V3025 Incorrect format. A different number of format items is expected while calling 'Format' function. Expected: 2. Present: 1. CompilerPackage.cs 105
По всей видимости при попытке показать Message Box будет сгенерировано исключение. Дело в том, что функция Format () попытается распечатать второй дополнительный аргумент. А его нет.
Мне кажется, константная строка, задающая форматирование, должна начинаться с:
@"{0}
Ошибка N14, N15 в обработчике ошибок.
Я готов поспорить, что функция DumpAttributes () пока никак не используется. В ней есть сразу 2 ошибки, каждая из которых должна приводить к генерации исключения:
private void DumpAttributes(Symbol s)
{
int i = 0;
foreach (var sa in s.GetAttributes())
{
int j = 0;
foreach (var pa in sa.CommonConstructorArguments)
{
Console.WriteLine("{0} {1} {2}", pa.ToString());
j += 1;
}
j = 0;
foreach (var na in sa.CommonNamedArguments)
{
Console.WriteLine("{0} {1} {2} = {3}",
na.Key, na.Value.ToString());
j += 1;
}
i += 1;
}
}
Предупреждения PVS-Studio:
- V3025 Incorrect format. A different number of format items is expected while calling 'WriteLine' function. Expected: 3. Present: 1. LoadingAttributes.cs 551
- V3025 Incorrect format. A different number of format items is expected while calling 'WriteLine' function. Expected: 4. Present: 2. LoadingAttributes.cs 558
В обоих случаях вызова функции WriteLine (), ей передается меньше фактических аргументов, чем требуется. Как результат — должны возникать исключения FormatException.
Ошибка N16. Опасное выражение.
Уверен, что сейчас вы посмотрите на следующий фрагмент кода и не захотите в нем разбираться. Это хорошо показывает, как полезны неутомимые анализаторы кода.
private static bool SymbolsAreCompatibleCore(....)
{
....
var type = methodSymbol.ContainingType;
var newType = newMethodSymbol.ContainingType;
if ((type != null && type.IsEnumType() &&
type.EnumUnderlyingType != null &&
type.EnumUnderlyingType.SpecialType ==
newType.SpecialType) ||
(newType != null && newType.IsEnumType() &&
newType.EnumUnderlyingType != null &&
newType.EnumUnderlyingType.SpecialType ==
type.SpecialType))
{
return true;
}
....
}
Предупреждение PVS-Studio: V3027 The variable 'newType' was utilized in the logical expression before it was verified against null in the same logical expression. AbstractSpeculationAnalyzer.cs 383
Чтобы пояснить, в чем опасность кода, я составлю на его основе простой синтетический пример:
if ((A != null && A.x == B.y) || (B != null && B.q == A.w))
Как видите, подразумевается, что A и B могут являться нулевыми ссылками. Выражение состоит из двух частей. В первой части проверяется ссылка A, но не проверена ссылка B. Во второй части проверяется ссылка B, но не проверена ссылка A.
Возможно код и работает благодаря везению, но выглядит очень подозрительным и опасным.
Ошибки N17, N18. Повторные присваивания.
public static string Stringize(this Diagnostic e)
{
var retVal = string.Empty;
if (e.Location.IsInSource)
{
retVal = e.Location.SourceSpan.ToString() + ": ";
}
else if (e.Location.IsInMetadata)
{
return "metadata: ";
}
else
{
return "no location: ";
}
retVal = e.Severity.ToString() + " " + e.Id + ": " +
e.GetMessage(CultureInfo.CurrentCulture);
return retVal;
}
Предупреждение PVS-Studio: V3008 The 'retVal' variable is assigned values twice successively. Perhaps this is a mistake. Check lines: 324, 313. DiagnosticExtensions.cs 324
Обратите внимание, что в одной из веток оператора 'if' переменной 'retVal' присваиваются значение. Однако в конце функции значение переменной 'retVal' перезаписывается. Я не уверен, но возможно последнее присваивание должно быть таким:
retVal = retVal + e.Severity.ToString() + " " + e.Id + ": " +
e.GetMessage(CultureInfo.CurrentCulture);
Рассмотрим ещё один аналогичный случай:
public int GetMethodsInDocument(
ISymUnmanagedDocument document,
int bufferLength,
out int count,
....)
{
....
if (bufferLength > 0)
{
....
count = actualCount;
}
else
{
count = extentsByMethod.Length;
}
count = 0;
return HResult.S_OK;
}
Предупреждение PVS-Studio: V3008 The 'count' variable is assigned values twice successively. Perhaps this is a mistake. Check lines: 317, 314. SymReader.cs 317
Функция возвращает значение по ссылке 'count'. В разных частях функции в 'count' записываются различные значения. Подозрительно то, что в конце функции в 'count' вдруг всегда записывается 0. Очень странно.
Ошибка N19 в тестах. Опечатка.
internal void VerifySemantics(....)
{
....
if (additionalOldSources != null)
{
oldTrees = oldTrees.Concat(
additionalOldSources.Select(s => ParseText(s)));
}
if (additionalOldSources != null)
{
newTrees = newTrees.Concat(
additionalNewSources.Select(s => ParseText(s)));
}
....
}
Предупреждение PVS-Studio: V3029 The conditional expressions of the 'if' operators situated alongside each other are identical. Check lines: 223, 228. EditAndContinueTestHelpers.cs 223
Во втором условии следовало проверять не 'additionalOldSources', а 'additionalNewSources'. Если ссылка 'additionalNewSources' вдруг окажется нулевой, то будет сгенерировано исключение при попытке вызова функции Select ().
Ошибка N20. Из спорного.
Естественно, в статье я разобрал далеко не все предупреждения, которые выдал анализатор PVS-Studio. Есть немало явно ложных срабатываний, но ещё больше ситуаций, где у меня просто нет знаний о проекте Roslyn, чтобы судить, содержит код ошибку, или нет. Рассмотрим один из таких случаев.
public static SyntaxTrivia Whitespace(string text)
{
return Syntax.InternalSyntax.SyntaxFactory.Whitespace(
text, elastic: false);
}
public static SyntaxTrivia ElasticWhitespace(string text)
{
return Syntax.InternalSyntax.SyntaxFactory.Whitespace(
text, elastic: false);
}
V3013 It is odd that the body of 'Whitespace' function is fully equivalent to the body of 'ElasticWhitespace' function (118, line 129). SyntaxFactory.cs 118
Две функции имеют идентичные тела. Это подозрительно с точки зрения анализатора. Подозрительны эти функции и мне. Но я не знаю проект, и, возможно, код написан совершенно правильно. Поэтому я выскажу только предположение. Возможно, внутри функции ElasticWhitespace () следует использовать фактический параметр 'elastic' равный 'true'.
Ошибка Nxx.
Надеюсь, читатели понимают, что я не могу подробно разбираться с каждым таким случаем, как был показан выше. Я проверяю много проектов и каждый из них мне не знаком. Поэтому я описываю в статьях только наиболее очевидные ошибки. В этой статье я остановился на 20 таких случаях. Однако, думаю, PVS-Studio смог обнаружить гораздо больше дефектов. Поэтому предлагаю разработчикам Roslyn не полагаться только на эту статью, а проверить проект самостоятельно. Для этого демонстрационной версии анализатора будет недостаточно, но мы готовы предоставить на время лицензионный ключ.
Сравнение с ReSharper
Я написал ещё совсем немного статей про проверку C# проектов и выступил только на одной конференции. Но уже понял, что каждый раз будет задаваться вопрос: «А у вас есть сравнение с ReSharper?».
Мне этот вопрос не нравится по двум причинам. Во-первых, это всё-таки инструменты разного типа. PVS-Studio это классический анализатор кода, ориентированный на поиск ошибок. ReSharper это Productivity Tool, предназначенный облегчить программирование и умеющий выдавать большое количество рекомендаций.
PVS-Studio и ReSharper имеют совершенно разные подходы по многим вопросам. Смотрите, например, у PVS-Studio есть документация с подробным описанием каждой диагностики, примерами и советами по исправлению кода. В случае ReSharper есть утверждение о »1400 code inspections in C#, VB.NET, XAML, XML, ASP.NET, ASP.NET MVC, Razor, JavaScript, TypeScript, HTML, CSS, ResX». Число 1400 выглядит солидно, но на самом деле ни о чем не говорит. Возможно где-то и есть описание всех этих code inspection, но сходу я не смог его найти. Как мне сравнивать инструмент, если я даже не могу прочитать о том, какие именно ошибки обнаруживает ReSharper в C# программах.
Во-вторых, любое сравнение, которое мы сделаем, будет полностью раскритиковано. Мы уже проходили это и зареклись впредь делать какие-либо сравнения. Например, мы тщательно подошли к сравнению PVS-Studio с Cppcheck и Visual Studio SCA. Было потрачено много времени и сил. Результаты были представлены в кратком и подробном варианте. После чего только ленивый не написал, что мы всё сделали неправильно, или что наше сравнение является нечестным благодаря специально выбранным проектам для проверки.
Не видим смысл тратить время на сравнение. Как бы тщательно и честно мы не подошли к нему, всегда можно будет сказать, что сравнение было предвзято.
Тем не менее должен быть ответ, лучше ли мы в чем-то, чем ReSharper. И такой ответ у меня есть.
Вы уже используете ReSharper? Отлично! Теперь установите PVS-Studio и найдите в своём проекте ошибки!
Заключение
Предлагаю не откладывая скачать PVS-Studio и проверить свой проект. Видите ошибки? А ведь у вас вполне работающий код. Скорее всего найденные ошибки расположены в редко используемых участках кода. В часто используемых участках кода подобные ошибки хоть медленно и мучительно, но были исправлены. Представьте теперь, как много мог бы сэкономить сил анализатор PVS-Studio при регулярном использовании. Конечно анализатор способен найти далеко не все ошибки. Но чем тратить время на поиски опечаток и ляпов, лучше провести его с пользой. А глупые ошибки отдайте на растерзание PVS-Studio.
P.S. Вы не делаете глупых ошибок? Ну-ну. Это только так кажется. Все делают. Посмотрите хотя-бы сюда.
Если хотите поделиться этой статьей с англоязычной аудиторией, то прошу использовать ссылку на перевод: Andrey Karpov. New Year PVS-Studio 6.00 Release: Scanning Roslyn.