Лабораторная работа №4. Форматирование кода. Метрики кода. 1 Цель работы Ознакомится с правилами форматирвоания программного кода и изучить метрики кода. 2 Задание Оформить программный код согласно стандарту кодирования C#. Рассчитать значения используя метрики Холстеда. Для одной из функций посчитать цикломатическое число. 3 Теоретический материал 3.1 Стандарт форматирования кода C#. В данном методическом пособии приведены фрагменты стандарта(соглашения) по оформлению и написанию кода на языке C#. Более подробно стандарт описан в статье [7]. Приведены основные правила оформления кода и приемы, используемые при написании программ. Соглашения по кодирвоанию предоставляют общие правила, позволяющие сохранить единый стиль написания кода, облегчив тем самым его понимание всеми участниками команды. Вводятся базовые правила написания программ, что позволит повысить предсказуемость выполнения программ, а также избежать ошибок при написании программ новыми участниками команды, не знакомыми с внутренними стандартами разработки. 3.1.1 Стили именования Pascal case – первая буква каждого слова в имени идентификатора начинается с верхнего регистра. Пример: TheCategory; Camel case – первая буква первого слова в идентификаторе в нижнем регистре, все первые буквы последующих слов – в верхнем. Пример: theCategory; UpperCase – стиль используется только для сокращений, все буквы в имени идентификатора в верхнем регистре. Пример: ID; Hungarian notation – перед именем идентификатора пишется его тип в сокращенной форме. Пример: strFirstName, iCurrentYear. 3.1.2 Общие правила именования идентификаторов При именовании идентификаторов не используются аббревиатуры или сокращения, если только они не являются общепринятыми. Пример: GetWindow(), а не GetWin(); Если имя идентификатора включает в себя сокращение – сокращение пишется в upper case. Исключение - когда имя идентификатора должно быть указано в camel case и сокращение стоит в начале имени идентификатора. В этом случае сокращение пишется в нижнем регистре. Пример: PPCAccount (PPC – сокращение от pay per click) для pascal case, ppcAccount для camel case. 3.1.3 Использование верхнего и нижнего регистра в именах Запрещается создавать два различных namespace’а, функции, типа или свойства с одинаковыми именами, отличающиеся только регистром. Запрещается создавать функции с именами параметров, отличающимися только регистром. Ниже приведены примеры НЕправильных названий. Примеры: KeywordManager и Keywordmanager; KeywordManager.Keyword и KeywordManager.KEYWORD; int id {get, set} и int ID {get, set}; findByID(int id) и FindByID(int id); void MyFunction(string s, string S). 3.1.4 Правила именования классов Следует избегать имен классов, совпадающих с именами классов .NET Framework. Для классов используется стиль именования pascal case. Для классов, унаследованных от CollectionBase используется суффикс Collection, перед которым указывается тип объектов, для которых используется коллекция. Пример: UserCollection, CompanyCollection; В качестве имен классов используются имена существительные. Имя класса не должно совпадать с именем namespace’а. Пример: namespace Debugging, класс Debug; Если класс представляет собой сущность, хранимую в базе данных – имя класса соответствует имени таблицы. В этом случае имя класса – это название сущности в единственном числе, имя таблицы – во множественном числе. Пример: таблица Users, класс User; При создании классов потомков их имена состоят из имени базового класса и суффикса класса потомка, если суффиксов несколько – они разделяются символом подчеркивания. Пример: базовый класс Figure, потомок FigureCircle; Имена файлов, в которых находятся классы, совпадают с именами классов. Для именования файлов используется стиль pascal case. 3.1.5 Правила именования интерфейсов Имена интерфейсов начинаются с буквы I, после которой следует название интерфейса в pascal case. Пример: IDisposable. 3.1.6 Правила именования generic’ов Generic’и обозначаются буквой T, если generic’ов несколько их имена начинаются с буквы T. Пример: GetItems<T>(int parentID). 3.1.7 Правила именования функций Для именования функций используется стиль pascal case. Функции объявляются согласно следующему шаблону: <Модификатор доступа> [Другие модификаторы] <Тип> <Название функции>(); Пример: protected abstract void HelloWorld(); Имена функций должны давать четкое представление о том, какое действие эта функция выполняет. Имя функции начинается с глагола, указывающего на то, какое действие она выполняет; Большие функции, не умещающиеся на одном экране, делятся на несколько private функций меньшего размера, имена таких вспомогательных функций состоят из имени основной (большой) функции и существительного, глагола или фразы, которые уточняют действие вспомогательной функций, разделенные подчеркиванием. Основная и вспомогательная функции объединяются в регионы. Вспомогательные функции вызываются только из основной функции. Пример: основная функция – CheckProduct, вспомогательные функции –CheckProduct_Price, CheckProduct_Url, CheckProduct_SearchTerm. 3.1.8 Правила именования параметров функций Для именования параметров используется стиль camel case. Имена параметров должны давать четкое представление о том для чего используется параметр, и какое значение следует передать при вызове функции. Пример: public void EncodeString(string sourceString, ref string encodedString), а не public void EncodeString(string string1, ref string string2). В том случае, когда это не препятствует понимаю кода, в качестве имени параметра функции используется имя соответствующего параметру класса. Для коллекций и массивов используется имя объектов, содержащихся в коллекции или массиве. Пример: UserFactory.Create(Company company); CheckUsers(UserCollection users); Имена параметров не должны совпадать с именами членов класса, если этого не удается избежать, то для разрешения конфликтов используется ключевое слово this. В именах параметров не используется венгерская нотация. Пример: public void CreateUser(string firstName, string lastName) { this.firstName = firstName; this.lastName = lastName; } 9 Правила именования свойств Для именования свойств используется стиль pascal case. Свойства объявляются согласно следующему шаблону: <Модификатор доступа> [Другие модификаторы] <Тип> <Название свойства>; Пример: public static User CurrentUser { get; } В том случае, когда это не препятствует понимаю кода, в качестве имени свойства используется имя соответствующего свойству класса. Для коллекций и массивов используется имя объектов, содержащихся в коллекции или массиве. Пример: public User User { get; set; } public UserCollection Users { get; set; } Название свойства типа bool должно представлять из себя вопрос, требующий ответа да или нет. Примеры названий: “CanDownload”, “HasKeywords”, “IsChecked”, “NeedsUpdate”. 3.1.10 Правила именования полей Для именования полей, доступных вне класса, используется стиль pascal case, для private полей - camel case. Поля объявляются согласно следующему шаблону: <Модификатор доступа> [Другие модификаторы] <Тип> <Название поля>; Пример: private static User currentUser = null; В том случае, когда это не препятствует понимаю кода, в качестве имени поля используется имя соответствующего полю класса. Для коллекций и массивов используется имя объектов, содержащихся в коллекции или массиве. Пример: public User User = new User(); public UserCollection Users = new UserCollection(); Название поля типа bool должно представлять из себя вопрос, требующий ответа да или нет. Примеры названий: “CanDownload”, “HasKeywords”, “IsChecked”, “NeedsUpdate”. 3.1.11 Правила именования переменных Для именования переменных используется стиль camel case. Переменные объявляются согласно следующему шаблону: <Тип> <Название переменной>; Пример: int userID = null; В циклах foreach имя переменной назначается как имя массива в единственном числе. Пример: foreach(Campaign newCampaign in NewCampaigns). 3.1.12 Правила именования констант Для именования констант используется стиль pascal case. 3.1.13 Правила именования enum’ов Для именования enum’ов и их значений используется стиль pascal case. Имена enum’ов указываются в единственном числе. Имена, как правило, состоят из имени сущности, к которой относится enum и названия содержимого enum’а (status, type, state). Пример: KeywordStatus, ConnectionState, TaskType. 3.1.14 Правила именования исключений Для exception’ов используется стиль pascal case. Имена классов для создаваемых custom exception’ов заканчиваются суффиком Exception. В качестве имени объекта исключения внтури catch, для исключений типа Exception, используется имя “ex”. Пример: catch(Exception ex). 3.1.15 Форматирование кода Используются стандартные настройки форматирования Visual Studio. В одном файле не объявляется больше одного namespace’а и одного класса (исключение – небольшие вспомогательные private классы). Фигурные скобки размещаются всегда на отдельной строке. В условии if-else всегда используются фигурные скобки. Размер tab’а – 4. Использование строк длиннее 100 символов не желательно. При необходимости инструкция переносится на другую строку. При переносе части кода на другую строку вторая и последующая строки сдвигаются вправо на один символ табуляции. Каждая переменная объявляется на отдельной строке; Все подключения namespace’ов (using) размещаются в начале файла, системные namespace’ы объявляются над custom namespace’ами. Если для свойства существует соответствующее поле (например, при загрузке по требованию), то поле объявляется над свойством. Пример: private User user; public User User { get; set; } Если set или get свойства состоит из одной операции – весь set или get размещается на одной строке. Пример: Public User { get { return user; } } Функции, поля и свойства группируются внутри класса по своему назначению. Такие группы объединяются в регионы. 3.1.16 Комментирование кода Все комментарии должны быть на русском языке. Для функций, классов, enum’ов, свойств и полей комментарии необходимо указывать в таком виде, чтобы по ним можно было автоматически сгенерировать документацию. Для этого используются стандартные tag’и такие как <summary>, <param> и <return>. Для функций создающих exception’ы – возможные исключения необходимо указывать в tag’ах <exception>. Для включения в документацию примеров использования необходимо применять tag’и <example>, <remarks> и <code>. Для ссылок в документации необходимо использовать tag’и <see cref=””/> и <seeAlso cref=””/>. При использовании в тексте комментариев символов, использующихся в xml как спецсимволы, необходимо использовать tag CDATA. Комментарии к заголовкам функций, свойств, полей, интерфейсов и прочего необходимо указывать всегда. Для функций, выполняющих сложные алгоритмы, не очевидные для восприятия, необходимо указывать подробные комментарии не только к заголовку функции, но и самому алгоритму с пояснением каждого шага выполнения алгоритма. В случае внесения изменений в критические участки кода, ядро системы, либо когда сложно проследить последствия, которые может повлечь такое изменение, необходимо указывать подробный комментарий о том кто внес изменение, когда и по какой причине. В том случае если необходимо временно добавить заплатку, без которой система не может работать, но заплатку в дальнейшем планируется убрать – необходимо добавлять ключевое слово “//TODO: ”, после которого указывается когда и что должно быть исправлено. Кроме этого необходимо указывать подробный комментарий о том, для чего предназначено временное исправление, кто его внес и когда. При разработке кода, изменения которого могут повлечь за собой сбой в других частях системы, при этом связь между этими двумя частями программы неочевидна и ошибка не будет показана на этапе компиляции, либо если сам код неочевидным образом зависит от других частей системы – необходимо указывать подробное описания взаимосвязей. 3.1.17 Конфигурация В конфигурационном файле ключи необходимо группировать по назначению. Перед началом каждой такой группы в комментариях необходимо указывать открывающий tag с названием группы, в конце – закрывающий tag. Пример: <!--Connection strings --> <add key="MainDatabaseConnectionString" value="server=...;Integrated Security=SSPI;"/> <add key=" SupportDatabaseConnectionString" value="server=...;Integrated Security=SSPI;"/> <!-- /Connection strings --> Для ключей, хранящих boolean значения, value может быть равно только true или false, а не 0/1 или yes/no. 3.1.18 Переменные и типы Свойства необходимо использовать только тогда, когда это имеет смысл. Если при получении и сохранении значений никакая дополнительная логика не участвует – вместо свойства необходимо использовать поле (исключение – когда класс bind’ится на asp.net страницах, т.к. стандартный механизм bind’а имеет доступ только к public свойствам). Необходимо использовать максимально простые типы данных. Так, например, необходимо использовать int, а не long, если известно, что для хранимых значений будет достаточно типа int. Константы необходимо использовать только для простых типов данных. Для сложных типов вместо констант необходимо использовать readonly поля. Boxing и unboxing value типов необходимо использовать только когда это действительно необходимо. При задании значений нецелых типов, значения должны содержать как минимум одну цифру до точки и одну после. Необходимо использовать именования типов C#, а не .NET common type system (CTS). Пример: int userID = -1; , а не Int32 userID=-1; Модификаторы доступа необходимо указывать всегда. Не смотря на то, что по умолчанию назначается модификатор доступа private, поле модификатора не остается пустым – модификатор private необходимо указывать явным образом. Пример: private User CreateUser(string firstName, string lastName); Модификаторы доступа (private, protected, internal и public) необходимо указывать в зависимости от того, где требуется доступность соответствующего поля, свойства, функции, класса или конструктора. Модификатор public необходимо указывать только тогда, когда необходим доступ к полю из других проектов, internal – когда необходим доступ из других классов внутри одного проекта, protected – для предоставления доступа классам – потомкам, во всех остальных случаях используется private, т.е. доступ ограничивается самим классом. Поля и переменные инициализируются при их объявлении, когда это возможно. Пример: private int userID = -1; private string firstName = “”; private string lastName = “”; Когда для создания класса необходимо передать параметры, используемые при его инициализации, на конструктор по умолчанию (MyClass() { }) необходимо накладывать модификатор доступа private, чтобы избежать создания клиентами неинициализированного объекта. Пример: private User() { } public User(int userID) { } Вместо использования “magic numbers” для идентификаторов статусов, состояний и т.п. необходимо указывать константы или enum’ы. Идентификаторы состояний в виде чисел использовать нельзя. Пример не правильного использования: public GetUserByStatus(int statusID); Пример правильного использования: public GetUserByStatus(UserStatus userStatus); Тип object необходимо использовать только когда это действительно необходимо, в большинстве случаев вместо него используются generic’и. Вместо Hashtable необходимо использовать Dictionary<>, вместо ArrayList используется List<>. В том случае если в get’е или set’е какого-либо свойства выполняются сложные вычисления, если операция, выполняемая в get или set является преобразованием, имеет побочный эффект или долго выполняется – свойство должно быть заменено функциями. Свойство не должно менять своего значения от вызова к вызову, если состояние объекта не изменяется. Если результат при новом вызове может быть другим при том же состоянии объекта, вместо свойства необходимо использовать функции. Внутри get’а и set’а не должно быть обращений к коду, не связанному напрямую с получением или сохранением значения свойства, т.к. такие действия могут быть не очевидны для клиентов, использующих свойство. Все настройки, влияющие на работу приложения, нельзя указывать жестко в коде, а необходимо выносить в config. Если есть возможность прописать значение по умолчанию – они должны быть прописаны, если значение по умолчанию не может быть задано и соответствующий ключ не прописан в config – необходимо создавать исключение. 3.1.19 Функции Функции, возвращающие массив, всегда должны возвращать массив. Если нет данных – функции возвращают пустой массив, но не null. Это же касается коллекций; В функциях никогда нельзя использовать больше 7-ми параметров. Если параметров больше – они объединяются в класс. 3.1.20 Управление выполнением программы При использовании foreach по коллекции объектов – саму коллекцию никогда нельзя модифицировать (новые элементы не добавляются, существующие не удаляются). Если задачу можно решить, используя рекурсию и используя циклы, предпочтение необходимо отдавать использованию циклов. Рекурсия необходимо применять только тогда, когда решение с использованием циклов сложнее, чем при использовании рекурсии. Тернарные операции необходимо использовать только для простых проверок. В тех случаях, когда проверка сложная и включает в себя несколько условий – используются if/else. В aspx файлах нельзя использовать код. Т.е. tag’и <%= “C# code” %> использовать нельзя. Это связано с тем, что при компиляции такой код не проверяется. Сложные проверки, состоящие из множества условий, необходимо разбивать на несколько простых. Для сохранения промежуточных результатов необходимо использовать boolean переменные. Классы, реализующие интерфейс IDisposable, создаются в директиве using. Пример: using(SqlConnection sqlConnection = new SqlConnection) { }. 3.1.21 Исключения и их обработка Блоки try-catch нельзя использовать для управления ходом работы программы, а только для обработки непредвиденных ошибок. При прокидывании исключений выше по StackTrace’у, необходимо использовать оператор “throw;”, а НЕ “throw ex;”. Custom exception’ы необходимо наследовать от класса Exception (а не ApplicationException или какого-то другого). Исключения необходимо создавать всякий раз, когда функция не может быть выполнена – переданы неверные параметры при вызове функции, нет доступа к базе данных, неизвестные идентификаторы и т.п. Все исключения должны быть записаны в log или показаны пользователю системы. Пустые секции catch использовать нельзя. При записи информации об ошибке, как правило, пишется StackTrace. 3.2 Метрики Холстеда В отличие от большинства отраслей материального производства, в вопросах проектов создания ПО недопустимы простые подходы, основанные на умножении трудоемкости на среднюю производительность труда. Это вызвано, прежде всего, тем, что экономические показатели проекта нелинейно зависят от объема работ, а при вычислении трудоемкости допускается большая погрешность. Поэтому для решения этой задачи используются комплексные и достаточно сложные методики, которые требуют высокой ответственности в применении и определенного времени на адаптацию (настройку коэффициентов). Современные комплексные системы оценки характеристик проектов создания ПО могут быть использованы для решения следующих задач: - предварительная, постоянная и итоговая оценка экономических параметров проекта: трудоемкость, длительность, стоимость; - оценка рисков по проекту: риск нарушения сроков и невыполнения проекта, риск увеличения трудоемкости на этапах отладки и сопровождения проекта и пр.; - принятие оперативных управленческих решений – на основе отслеживания определенных метрик проекта можно своевременно предупредить возникновение нежелательных ситуаций и устранить последствия непродуманных проектных решений. Метрики сложности программ принято разделять на три основные группы: - метрики размера программ; - метрики сложности потока управления программ; - метрики сложности потока данных программ. Метрики первой группы базируются на определении количественных характеристик, связанных с размером программы, и отличаются относительной простотой. К наиболее известным метрикам данной группы относятся число операторов программы, количество строк исходного текста, набор метрик Холстеда. Метрики этой группы ориентированы на анализ исходного текста программ. Поэтому они могут использоваться для оценки сложности промежуточных продуктов разработки. Метрики второй группы базируются на анализе управляющего графа программы. Представителем данной группы является метрика Маккейба. Управляющий граф программы, который используют метрики данной группы, может быть построен на основе алгоритмов модулей. Поэтому метрики второй группы могут применяться для оценки сложности промежуточных продуктов разработки. Метрики третьей группы базируются на оценке использования, конфигурации и размещения данных в программе. В первую очередь это касается глобальных переменных. К данной группе относятся метрики Чепина. Метрика Холстеда относится к метрикам, вычисляемым на основании анализа числа строк и синтаксических элементов исходного кода программы. Основу метрики Холстеда составляют четыре измеряемые характеристики программы: - NUOprtr (Number of Unique Operators) — число уникальных операторов программы, включая символы-разделители, имена процедур и знаки операций (словарь операторов); - NUOprnd (Number of Unique Operands) — число уникальных операндов программы (словарь операндов); - Noprtr (Number of Operators) — общее число операторов в программе; - Noprnd (Number of Operands) — общее число операндов в программе. На основании этих характеристик рассчитываются оценки: - Словарь программы (Halstead Program Vocabulary, HPVoc): HPVoc = NUOprtr + NUOprnd; - Длина программы (Halstead Program Length, HPLen): HPLen = Noprtr + Noprnd; - Объем программы (Halstead Program Volume, HPVol): HPVol = HPLen log2 HPVoc; - Сложность программы (Halstead Difficulty, HDiff): HDiff = (NUOprtr/2) × (NOprnd / NUOprnd); - На основе показателя HDiff предлагается оценивать усилия программиста при разработке, при помощи показателя HEff (Halstead Effort): HEff = HDiff × HPVol. 3.3 Расчет цикломатического числа. Показатель цикломатической сложности является одним из наиболее распространенных показателей оценки сложности программных проектов. Данный показатель был разработан ученым Мак-Кейбом в 1976 г., относится к группе показателей оценки сложности потока управления программой и вычисляется на основе графа управляющей логики программы (control flow graph). Данный граф строится в виде ориентированного графа, в котором вычислительные операторы или выражения представляются в виде узлов, а передача управления между узлами – в виде дуг. Показатель цикломатической сложности позволяет не только произвести оценку трудоемкости реализации отдельных элементов программного проекта и скорректировать общие показатели оценки длительности и стоимости проекта, но и оценить связанные риски и принять необходимые управленческие решения. Упрощенная формула вычисления цикломатической сложности представляется следующим образом: C = e – n + 2, где e – число ребер, а n – число узлов на графе управляющей логики. Как правило, при вычислении цикломатической сложности логические операторы не учитываются. В процессе автоматизированного вычисления показателя цикломатической сложности, как правило, применяется упрощенный подход, в соответствии с которым построение графа не осуществляется, а вычисление показателя производится на основании подсчета числа операторов управляющей логики (if, switch и т.д.) и возможного количества путей исполнения программы. Цикломатическое число Мак-Кейба показывает требуемое количество проходов для покрытия всех контуров сильносвязанного графа или количества тестовых прогонов программы, необходимых для исчерпывающего тестирования по принципу «работает каждая ветвь». Показатель цикломатической сложности может быть рассчитан для модуля, метода и других структурных единиц программы. Существует значительное количество модификаций показателя цикломатической сложности. - «Модифицированная» цикломатическая сложность – рассматривает не каждое ветвление оператора множественного выбора (switch), а весь оператор как единое целое. - «Строгая» цикломатическая сложность – включает логические операторы. - «Упрощенное» вычисление цикломатической сложности – предусматривает вычисление не на основе графа, а на основе подсчета управляющих операторов. Подробнее о метриках ПО можно узнать в статье [8] Список используемых источников: 1. Гост 34.602-89 техническое задание на создание автоматизированной системы [Электронный ресурс] / http://it-gost.ru. – [2015]. - Режим доступа : http://it-gost.ru/content/view/21/39/. 2. Гост 19.201-78 техническое задание. Требования к содержанию и оформлению [Электронный ресурс] / http://it-gost.ru. – [2015]. - Режим доступа : http://it-gost.ru/content/view/20/41/. 3. Что такое ГОСТ [Электронный ресурс] / http://it-gost.ru. – [2015]. - Режим доступа : http://it-gost.ru/content/view/76/51/. 4. Страница загрузок TortoiseHg [Электронный ресурс] / http://sourceforge.net – [2015]. - Режим доступа : http://sourceforge.net/project/showfiles.php?group_id=199155&package_id=236242&release_id=654501. 5. Сайт проекта NUnit [Электронный ресурс] / http://www.nunit.org/. – [2015]. - Режим доступа : http://www.nunit.org/. 6. Вступление в Nunit [Электронный ресурс] / http://itvdn.com – [2015]. - Режим доступа : http://itvdn.com/ru/blog/article/entry-into-nunit. 7. Стандарты и правила оформления кода C# [Электронный ресурс] / http://michaelsmirnov.blogspot.ru/ – [2015]. - Режим доступа : http://michaelsmirnov.blogspot.ru/2011/01/c.html. 8. Метрики кода и их практическая реализация в Subversion и ClearCase. Часть 1 – метрики [Электронный ресурс] / http://cmcons.com – [2015]. - Режим доступа :http://cmcons.com/articles/CC_CQ/dev_metrics/mertics_part_1/. План выпуска учеб.-метод. документ. 2015 г., поз. 15 Публикуется в авторской редакции Подписано в свет 28.11.2014. Гарнитура «Таймс». Уч.-изд. л. 1,5. Объем данных 856 Мбайт. Федеральное государственное бюджетное образовательное учреждение высшего профессионального образования «Волгоградский государственный архитектурно-строительный университет» 400074, Волгоград, ул. Академическая, 1 http://www.vgasu.ru, info@vgasu.ru |