PVS-Studio - это инструмент для выявления ошибок в исходном коде программ, написанных на языках С, C++ и C#.
PVS-Studio выполняет статический анализ кода и генерирует отчёт, помогающий программисту находить и устранять ошибки. PVS-Studio выполняет широкий спектр проверок кода, но наиболее силён в поисках опечаток и последствий неудачного Copy-Paste. Показательные примеры таких ошибок: V501, V517, V522, V523, V3001.
Анализатор ориентирован на разработчиков, использующих среду Visual Studio, и может в фоновом режиме выполнять анализ измененных файлов после их компиляции. В идеале ошибки будут обнаружены и исправлены ещё до попадания в репозиторий. Однако ничто не мешает использовать анализатор для проверки всего решения целиком или для встраивания в системы непрерывной интеграции. Эти и иные способы использования анализатора описаны в документации.
PVS-Studio выполняет статический анализ кода и генерирует отчёт, помогающий программисту находить и устранять ошибки. PVS-Studio выполняет широкий спектр проверок кода, но наиболее силён в поисках опечаток и последствий неудачного Copy-Paste. Показательные примеры таких ошибок: V501, V517, V522, V523, V3001.
Анализатор ориентирован на разработчиков, использующих среду Visual Studio, и может в фоновом режиме выполнять анализ измененных файлов после их компиляции. В идеале ошибки будут обнаружены и исправлены ещё до попадания в репозиторий. Однако ничто не мешает использовать анализатор для проверки всего решения целиком или для встраивания в системы непрерывной интеграции. Эти и иные способы использования анализатора описаны в документации.
Занимательный C#
Запись от el_programmer размещена 15.06.2016 в 14:52
Обновил(-а) tezaurismosis 19.06.2016 в 16:50 (Рекламные ссылки удалены)
Обновил(-а) tezaurismosis 19.06.2016 в 16:50 (Рекламные ссылки удалены)
Метки bugs, coding, csharp, programming, tutorial
Автор: Виталий Алферов Для оценки качества диагностик анализатора C# кода PVS-Studio мы проверяем большое количество различных проектов. Т.к. проекты пишутся разными людьми в различных командах в разных компаниях, нам приходится сталкиваться с различными стилями, сокращениями, да и просто возможностями, которые предлагает язык C# программистам. В этой статье я хочу обзорно пройтись по некоторым моментам, которые предлагает нам замечательный язык C#, и по тем проблемам, на которые можно наткнуться при его использовании. [ATTACH]3880[/ATTACH] [size=5][b]Свойства и что с ними можно делать[/b][/size] Мы все знаем, что свойства - это пара функций - аксессор и мутатор - для изменения и чтения значения в каком-то поле. Ну, или по крайней мере так было до версии языка C# 3.0. Т.е. классически они должны выглядеть вот так: [CSHARP]class A { int index; public int Index { get { return index; } set { index = value; } } } [/CSHARP] Шли годы, и стандарты языка, и свойства обросли разными возможностями. Начнем понемногу. В стандарте C# 3.0 появилась всем известная возможность опустить поле, т.е. записать так: [CSHARP]class A { public int Index { get; set; } } [/CSHARP] В C# 6.0 пошли еще дальше и позволили убрать "set". [CSHARP]class A { public int Index { get; } } [/CSHARP] Так писать можно было и до C# 6.0, но записать в такую переменную что-либо было нельзя. Теперь это, по факту, является аналогом [i]readonly [/i]полей, т.е. задавать значение таких свойств можно только в конструкторе. Свойства и поля можно инициализировать различными способами. Например, так: [CSHARP]class A { public List<int> Numbers { get; } = new List<int>(); } [/CSHARP] Ну или так: [CSHARP]class A { public List<int> Numbers = new List<int>(); } [/CSHARP] А еще можно написать так: [CSHARP]class A { public List<int> Numbers => new List<int>(); } [/CSHARP] И в последнем случае вас будет ждать неприятный сюрприз. На самом деле, в последнем примере вы создали вот такое вот свойство: [CSHARP]class A { public List<int> Numbers { get { return new List<int>(); } } } [/CSHARP] Т.е. когда вы попытаетесь заполнить [i]Numbers[/i], то у вас ничего не получится в принципе, каждый раз вы будете иметь новый список. [CSHARP]A a = new A(); a.Numbers.Add(10); a.Numbers.Add(20); a.Numbers.Add(30); [/CSHARP] Будьте внимательны, когда сокращаете запись, иногда это может привести к весьма долгому поиску ошибки. Интересные свойства свойств на этом не заканчиваются. Как я уже сказал, свойство - это пара функций, а в функциях никто не мешает менять параметры, которые туда приходят. Следующий код прекрасно компилируется и даже работает. [CSHARP]class A { int index; public int Index { get { return index; } set { value = 20; index = value; } } } static void Main(string[] args) { A a = new A(); a.Index = 10; Console.WriteLine(a.Index); } [/CSHARP] Результатом работы будет вывод числа "20", а никак не "10". Казалось бы, зачем кому-то сдалось записывать значение 20 в [i]value[/i]? Оказывается, даже в этом может быть смыл. Но для пояснения этого смысла, мы немного отвлекаемся от свойств и расскажем о ключевом символе [i]@[/i]. Данный ключевой символ поваляет создавать переменные схожие по написаю с ключевыми словами, но никто не запрещает данный символ пихать куда душа пожелает, например: [CSHARP]class A { public int index; public void CopyIndex(A @this) { this.@index = @this.index; } } static void Main(string[] args) { A a = new A(); @a.@index = 10; a.@CopyIndex(new A() { @index = 20 }); Console.WriteLine(a.index); } [/CSHARP] Результатом работы, как всегда в этой статье, будет вывод числа "20", а никак не "10". На самом деле, символ [i]@[/i] необходим только в одном месте, когда пишем имя параметра [i]@this[/i] в функции [i]CopyIndex[/i]. В других местах это просто лишний код, который к тому же затрудняет понимание написанного. С этими знаниями вернемся к свойствам и предположим, что у нас есть следующий класс: [CSHARP]class A { int value; public int Value { get { return @value; } set { @value = value; } } public A() { value = 5; } } [/CSHARP] Можно подумать, что в свойстве [i]Value [/i]изменит поле [i]value[/i] класса [i]A[/i], на самом деле так не произойдет, и результатом работы следующий программы будет 5, а не 10. [CSHARP]static void Main(string[] args) { A a = new A(); a.Value = 10; Console.WriteLine(a.Value); } [/CSHARP] [size=5][b]Инициализация Dictionary[/b][/size] Для начала вспомним, как можно инициализировать массивы: [CSHARP]string[] test1 = new string[] { "1", "2", "3" }; string[] test2 = new[] { "1", "2", "3" }; string[] test3 = { "1", "2", "3" }; string[,] test4 = { { "11", "12" }, { "21", "22" }, { "31", "32" } }; [/CSHARP] Со списками дело обстоит проще: [CSHARP]List<string> test2 = new List<string>(){ "1", "2", "3" }; [/CSHARP] А теперь перейдем в [i]Dictionary[/i]. На самом деле есть два варианта сокращенной инициализации: [CSHARP]Dictionary<string, int> test = new Dictionary<string, int>() { { "a-a", 1 }, { "b-b", 2 }, { "c-c", 3 } }; [/CSHARP] Ну или так [CSHARP]Dictionary<string, int> test = new Dictionary<string, int>() { ["a-a"] = 1, ["b-b"] = 2, ["c-c"] = 3 }; [/CSHARP] [size=5][b]Немного о LINQ запросах[/b][/size] LINQ запросы в принципе сама по себе вещь удобная. Собираем цепочку с необходимыми выборками и на выходе получаем необходимую информацию. Для начала опишем пару приятных моментов, которые могут не прийти в голову, пока сам не увидишь их. Для начала рассмотрим базовый пример: [CSHARP]void Foo(List<int> numbers1, List<int> numbers2) { var selection1 = numbers1.Where(index => index > 10); var selection2 = numbers2.Where(index => index > 10); } [/CSHARP] Нетрудно заметить, что в выше описанном примере есть несколько одинаковых проверок. То есть по-хорошему, их можно вынести в отдельную "функцию": [CSHARP]void Foo(List<int> numbers1, List<int> numbers2) { Func<int, bool> whereFunc = index => index > 10; var selection1 = numbers1.Where(index => whereFunc(index)); var selection2 = numbers2.Where(index => whereFunc(index)); } [/CSHARP] Уже стало лучше, если функции большие, то вообще прекрасно. Немного смущает вызов [i]whereFunc[/i]: какой-то он неказистый. На самом деле, это тоже не проблема: [CSHARP]void Foo(List<int> numbers1, List<int> numbers2) { Func<int, bool> whereFunc = index => index > 10; var selection1 = numbers1.Where(whereFunc); var selection2 = numbers2.Where(whereFunc); } [/CSHARP] Вот теперь и лаконично и опрятно. Теперь немного о нюансах работы LINQ выражений. Например, строчка кода не приведет к моментальной выборке данных из коллекции [i]numbers1[/i]. [CSHARP]IEnumerable<int> selection = numbers1.Where(whereFunc); [/CSHARP] Выборка данных начнется, только когда будет выполнена конвертация последовательности в коллекцию List<int>: [CSHARP]List<int> listNumbers = selection.ToList(); [/CSHARP] Этот нюанс работы может легко привести к использованию захваченной переменной уже после того, как её значение изменилось. Возьмем простой пример. Допустим, нам нужна функция [i]Foo,[/i]которая вернет из массива "{ 1, 2, 3, 4, 5 }" только те элементы, численные значения которых меньше индекса элемента, т.е: [CSHARP]0 : 1 : 2 : 1 3 : 1, 2 4 : 1, 2, 3 [/CSHARP] Её сигнатура пусть будет такой: [CSHARP]static Dictionary<int, IEnumerable<int>> Foo(int[] numbers) { .... } [/CSHARP] А вызов вот такой: [CSHARP]foreach (KeyValuePair<int, IEnumerable<int>> subArray in Foo(new[] { 1, 2, 3, 4, 5 })) Console.WriteLine(string.Format("{0} : {1}", subArray.Key, string.Join(", ", subArray.Value))); [/CSHARP] Всё вроде бы просто. Теперь напишем саму реализацию на основе LINQ. Она будет выглядеть вот так: [CSHARP]static Dictionary<int, IEnumerable<int>> Foo(int[] numbers) { var result = new Dictionary<int, IEnumerable<int>>(); for (int i = 0; i < numbers.Length; i++) result[i] = numbers.Where(index => index < i); return result; } [/CSHARP] Как можно видеть, всё предельно просто. Мы берем и поочерёдно "создаем" выборки из массива [i]numbers[/i]. Результатом работы такой программы будет вот такой текст в консоли: [CSHARP]0 : 1, 2, 3, 4 1 : 1, 2, 3, 4 2 : 1, 2, 3, 4 3 : 1, 2, 3, 4 4 : 1, 2, 3, 4 [/CSHARP] Проблема тут как раз в замыкании, которое произошло в лямбде [i]index => index < i[/i]. Переменная [i]i [/i]была захвачена, но, так как вызов лямбда выражения [i]index => index < i [/i]не происходил до момента, когда мы попросили результат в функции [i]string.Join(", ", subArray.Value)[/i], значение в ней было не такое, как в момент формирования LINQ запроса. Во время получения данных из выборки значения [i]i[/i] было равным 5, что привело к неверному результату вывода. [size=5][b]Недокументированные костыли на C#[/b][/size] Язык С++ известен своими хаками, обходными путями и прочими костылями, чего стоит серия функций [i]XXX_cast[/i]. Считается, что в C# такого нет. На самом деле и это не совсем правда... Начнем, пожалуй, с нескольких слов: [list][*][i]__makeref[/i][*][i]__reftype[/i][*][i]__refvalue[/i][/list] Этих слов нет ни в IntelliSense, да и в MSDN нет официального описания к ним. Так что это за чудо-слова такие? [i]__makeref [/i]принимает объект и возвращает некую "ссылку" на объект в виде объекта типа [i]TypedReference[/i]. А, собственно, слова [i]__reftype[/i] и [i]__refvalue[/i] позволяют из этой "ссылки" узнать соответственно тип объекта и значение объекта по данной "ссылке". Рассмотрим пример: [CSHARP]struct A { public int Index { get; set; } } static void Main(string[] args) { A a = new A(); a.Index = 10; TypedReference reference = __makeref(a); Type typeRef = __reftype(reference); Console.WriteLine(typeRef); //=> ConsoleApplication23.Program+A A valueRef = __refvalue(reference, A); Console.WriteLine(valueRef.Index); //=> 10 } [/CSHARP] Но такой "финт ушами" можно сделать немного более известными средствами: [CSHARP]static void Main(string[] args) { A a = new A(); a.Index = 10; dynamic dynam = a; Console.WriteLine(dynam.GetType()); A valuDynam = (A)dynam; Console.WriteLine(valuDynam.Index); } [/CSHARP] С [i]dynamic[/i] и строк меньше, да и вопросов меньше должно вызывать у людей - "Что это?" и "Как это работает?". Но вот вам немного иной сценарий, где работа с [i]dynamic[/i] смотрится не так хорошо, как с [i]TypedReference[/i]. [CSHARP]static void Main(string[] args) { TypedReference reference = __makeref(a); SetVal(reference); Console.WriteLine(__refvalue(reference, A).Index); } static void SetVal(TypedReference reference) { __refvalue(reference, A) = new A() { Index = 20 }; } [/CSHARP] Результатом работы будет вывод на консоль числа "20". Да, можно и [i]dynamic [/i]через [i]ref [/i]в функцию передать и работать будет также. [CSHARP]static void Main(string[] args) { dynamic dynam = a; SetVal(ref dynam); Console.WriteLine(((A)dynam).Index); } static void SetVal(ref dynamic dynam) { dynam = new A() { Index = 20 }; } [/CSHARP] На мой взгляд, вариант с [i]TypedReference[/i] выглядит лучше, особенно если прокидывать информацию всё ниже, и ниже, и ниже по функциям. Кроме выше описанных, есть еще одно чудо-слово [i]__arglist,[/i] которое позволяет сделать функцию с переменным числом параметров, да еще и любого типа. [CSHARP]static void Main(string[] args) { Foo(__arglist(1, 2.0, "3", new A[0])); } public static void Foo(__arglist) { ArgIterator iterator = new ArgIterator(__arglist); while (iterator.GetRemainingCount() > 0) { TypedReference typedReference = iterator.GetNextArg(); Console.WriteLine("{0} / {1}", TypedReference.ToObject(typedReference), TypedReference.GetTargetType(typedReference)); } } [/CSHARP] Странным является то, что нельзя из коробки организовать проход по элементам с помощью [i]foreach[/i], да и напрямую к элементу из списка не обратиться. Так что до С++ или JavaScript c его [i]arguments [/i]не дотягивает.:) [CSHARP]function sum() { .... for(var i=0; i < arguments.length; i++) s += arguments[i] } [/CSHARP] В дополнение приведу ссылку на [url=http://aakinshin.net/ru/blog/dotnet/undocumented-keywords-in-cs/]статью[/url], из которой я, собственно, и узнал, что это за слова такие, когда первый раз с ними столкнулся. [size=5][b]Заключение [/b][/size] В заключение хочется сказать, что и С++ и C# - весьма свободные по грамматике языки, и тем самым с одной стороны удобны в использовании, но с другой не защищают от опечаток. Есть укоренившееся мнение, что в С# нельзя ошибаться так, как в С++, – на самом деле это вовсе не так. В данной статье приведены весьма интересные, на мой взгляд, возможности языка, но львиная доля ошибок в C# состоит не в них, а при написании обычных индукций [i]if[/i], как, например, в проекте Infragistics. [CSHARP]public bool IsValid { get { var valid = double.IsNaN(Latitude) || double.IsNaN(Latitude) || this.Weather.DateTime == Weather.DateTimeInitial; return valid; } } [/CSHARP] V3001 There are identical sub-expressions 'double.IsNaN(Latitude)' to the left and to the right of the '||' operator. WeatherStation.cs 25 Внимание рассеивается чаще всего именно в таких моментах, а потом долгие поиски "непонятно чего непонятно где". Так что не упускайте возможность уберечь себя от ошибок с помощью анализатора кода PVS-Studio. |
Всего комментариев 0
Комментарии