Ivan-Igorevich / lazy-java-convetions

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Практики написания кода для проектов центра 68

"Always code as if the person who ends up maintaining your code is a violent psychopath who knows where you live." (С) Martin Golding

Общие

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

  • давать наглядные имена (например, если метод выполняет проверку на истинность, его название должно начинаться с is) - использование наглядных, длинных и однозначных имён критически важно для понимания кода в будущем, особенно в случаях, когда код дорабатывается не автором;
  • комментировать и документировать код - прежде чем начинать писать тело метода необходимо описать в комментарии что он делает, какие значения принимает в качестве аргументов, что возвращает и в каком контексте используется;
  • по мере возможности сохранять код портируемым - стараться избегать использования функций и библиотек, работающих для какой-то конкретной ОС или архитектуры;
  • разделять код на короткие понятные обособленные части;
  • избегать использования неименованных констант, в том числе числовых;

Также существуют негласные общие правила написания программ, с применением парадигмы объектно-ориентированного программирования в целом, и языка программирования Java в частности:

  • желательно использование так называемых геттеров и сеттеров, вместо обращения непосредственно к полю класса (соблюдение принципа инкапсуляции). Например, для считывания и записи значения поля private int number = 10; приянто описывать два метода с нужным модификатором доступа:
void setNumber(int newValue) {  
    this.number = newValue;
}

int getNumber() {
    return this.number;
}
  • не копипастить код, а выделять его в циклы, методы и классы;
  • похожим образом описывать методы с похожим функционалом;
  • несмотря на то, что язык Java позволяет использовать кириллические идентификаторы для переменных и методов, названия переменных и методов следует давать на латинице, на английском языке, отказавшись также от транслитерации;
  • поскольку разработка ведётся в полностью русскоязычной организации, комментарии к коду, в том числе описание класса, следует делать на русском языке кириллицей.

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

Форматирование и исходный код

- Исходные файлы должны быть в кодировке UTF-8; - ASCII горизонтального пробела `0x20` является единственным символом пробела, который должен быть использован в исходных файлах; - Использовать специальные управляющие последовательности (`\b`, `\t`, `\n`, `\f`, `\r`, `\"`, `\'`и `\\`) вместо их восьмеричных `\012` или Unicode `\u000a` аналогов; - Для не-ASCII символов используйте символы Unicode (например `∞`) или его эквивалент `\u221e`. Выбор зависит только от того, какой вариант делает код более легким для чтения и понимания; - Порядок, который Вы выбираете для свойств и методов вашего класса, может существенно повлиять на его понимание у сторонних разработчиков. Тем не менее, нет единого правильного рецепта, как это сделать. Разные классы могут быть упорядочены по-разному. Важно то, что каждый класс имеет некоторый логический порядок, который программист, его разрабатывающий, может объяснить, если его спросят. Например, новые методы не принято добавлять в конец класса, так как это приводит к упорядочиванию в хронологическом порядке по дате добавления, что не является логическим порядком; - Перегрузки: никогда не разделяйте. Если в классе имеется несколько конструкторов или несколько методов с тем же именем, они должны быть расположены последовательно, без какого-либо другого кода между ними; - Непустые блоки: K & R стиль. Назван в честь Кернигана и Ритчи из-за того, что все примеры из книги "Язык программирования Си" отформатированы подобным образом. - Отсутствие переходов на новую строку перед открывающейся скобкой - Переход на новую строку после открывающейся скобки - Переход на новую строку перед закрывающейся скобкой - Переход на новую строку после закрывающейся скобки, только если она завершает оператор или тело метода, конструктора или именованного класса. Например, после скобки не следует новая строка, если за ней следует `else` или точка с запятой `;`; - Код Java должен быть ограничен шириной строки в 120 символов. Используйте перенос строк, если строка выходит за этот предел; - При объявлении массива квадратные скобки являются частью типа, а не переменной. Несмотря на то, что язык джава позволяет инициализировать массивы как `String arr[];` следует делать это так: `String[] arr;`; - в операторе switch В блоке `case *:` каждая группа или завершается операторами `break`, `continue`, `return` или `throw`, или добавляется комментарий, чтобы показать, что выполнение будет или может быть продолжено в следующем `case *:`. Комментарий может быть любым, но обязательно должен доносить смысл, что здесь происходит падение в следующий кейс; - Каждый оператор `switch` должен иметь случай по умолчанию `default`, даже если он не содержит кода; - Аннотации, относящиеся к классу, методу или конструктору должны быть указаны сразу после блока документации и каждая аннотация должна располагаться в своей собственной строке. Исключение: если аннотация единственная, то можно использовать в начале конструкции без переноса строки (это касается как аннотаций к методам, так и аннотаций `@Inject`); - Для `long` литератов используйте суффикс `L` в верхнем регистре. Никогда не используйте этот суффикс в нижнем регистре, так как его легко спутать с цифрой `1`; - Имена пакетов должны быть записаны с использование нижнего регистра. Без подчеркиваний или форматирований; - В публичных методах следует избегать имён параметров состоящих из одного символа; - Методы должны быть отмечены аннотацией `@Override`, каждый раз когда это возможно; - Перехваченные исключения никогда нельзя игнорировать. Бывают случаи когда уместно не принимать действий в блоке `catch`, причина, почему это оправдано должна быть обязательно объяснена в комментарии.

Соглашения Java (конвенции)

Файлы Java делятся на две основные категории - исходники и байт-код. Исходники хранятся в файлах `*.java`, а байт-код в файлах `*.class`. Поскольку файлы байт-кода формируются компилятором, правила написания кода распространяются только на исходники (файлы классов и интерфейсов (далее объединено под общим названием **Класс**)).

Классы и пакеты

**Файл Класса** должен называться также, как и основной (публичный) Класс, который в нём содержится. Пакеты создают иерархию классов и группируют их по логике. - название класса пишется с большой буквы, слова в названии не разделяются, но пишутся каждое с большой буквы; - все Классы обязательно должны принадлежать пакету; - названия пакетов пишутся строчными буквами, слова в названии разделяются символом нижнего подчёркивания; - иерархия пакетов отображается через точку;

Файл Класса имеет следующий порядок написания:

  • начальный комментарий Класса (имя Класса, версия, дата создания, дата изменения, краткое описание того, какой объект или интерфейс представлен Классом. оформляется в многострочный комментарий в стиле Javadoc /**...*/);
  • принадлежность к пакету (оператор package);
  • импорт библиотек (оператор import). При этом, желательно отказаться от использования библиотек полностью, а импортировать только необходимые компоненты (не использовать множественный импорт оператором *);
  • декларация Класса (модификатор доступа public, название, наследование (если есть));
  • статические классовые переменные;
  • статические методы;
  • переменные экземпляра;
  • конструкторы;
  • методы;

Отступы и переносы

В качестве единицы отступа используется четыре пробела. Точное построение отступов не определено, но символ табуляции должен быть установлен на восемь пробелов, а не на четыре; - перенос выражения осушествляется по следующему принципу: - перенос после запятой; - перенос перед оператором; - предпочтительнее перенос на более высоком уровне выражения, чем на низком; - предпочтительно выравнивание новой строки выражения так, чтобы её начало было с тем же отступом, как и на предыдущей строке; - если предыдущие правила приведут к снижению читаемости кода, предлагается использовать отступ в восемь пробелов, например:

function(longExpression1, longExpression2, longExpression3,
         longExpression4, longExpression5);
var = function1(longExpression1,
                function2(longExpression2,
                          longExpression3));

И ещё одно сравнение. Первый вариант предпочтительнее, поскольку перенос производится на более высоком уровне выражения:

longName1 = longName2 * (longName3 + longName4 - longName5)
            + 4 * longname6; // ПРЕДПОЧТИТЕЛЬНЕЕ
longName1 = longName2 * (longName3 + longName4
                         - longName5) + 4 * longname6; // ИЗБЕГАЙТЕ

Основной идеей оформления отступами является увеличение читаемости кода, поэтому данные правила не являются строгими;

  • при записи тернарного оператора допускается три типа переноса:
alpha = (aLongBooleanExpression) ? beta : gamma;
alpha = (aLongBooleanExpression) ? beta
                                   : gamma;
alpha = (aLongBooleanExpression)
        ? beta
        : gamma;

Комментарии

Комментарии в программах делятся на три основных типа: - строковые `//...`; - многострочные `/*...*/`; - документационные `/**...*/`. Комментарии нужны чтобы описать код или пояснить моменты, которые сложно понять непосредственно из кода. Следует избегать комментариев, которые могут стать неактуальными по мере развития кода. Большое количество комментариев зачастую отражает низкое качество кода (если есть необходимость добавить комментарий, возможно лучше переписать код, чтобы он стал более понятным)

Объявление переменных и методов

Рекомендуется использовать одно объявление на строку, так как это облегчает комментирование. - Переменная и функция не должны быть объявлены на одной строке, также, как и разные типы данных; - Располагайте объявления только в начале блоков кода, если иное не обусловлено алгоритмом явно; - Старайтесь инициализировать локальные переменные там, где они объявляются. Единственная причина не инициализировать переменную в месте её объявления — если её начальное значение зависит от некоторых предварительных вычислений; - Открывающая тело метода фигурная скобка должна быть в конце той же строки, где объявляется метод.

Объявление классов и интерфейсов

При программировании Классов следует придерживаться следующих правил: - не использовать пробел между именем метода и открывающей скобкой списка аргументов; - открывающая тело класса фигурная скобка должна быть в конце той же строки, где объявляется Класс; - закрывающая фигурная скобка ставится на отдельной строке, с тем же отступом, что и соответствующий ей оператор. Кроме случаев, когда описывается пустой оператор - в этом случае, закрывающая скобка ставится сразу после открывающей.

Операторы

Каждая строка должна содержать не более одного выражения. Для группировки операторов используются фигурные скобки. Для улучшения читаемости кода рекомендуется выделять скобками даже единичные операторы в условиях и циклах.

 `return`

В случае, если у оператора `return` есть значение, оно не должно быть в скобках, кроме случаев, когда это делает код более читаемым, например, при использовании тернарного оператора.

 `if`, `if-else`, `if-else if-else`

Условный оператор должен иметь следующий вид: ```java if (условие) { операторы; } ``` ```java if (условие) { операторы; } else { операторы; } ``` ```java if (условие) { операторы; } else if (условие) { операторы; } ... любое количество else if (условие) { операторы; } else { операторы; } ```

 Циклы

Цикл `for` должен иметь следующий вид:

for (инициализация; условие; обновление) {
    операторы;
}

или в случае цикла с отсутствующим и пустым телом, соответственно

for (инициализация; условие; обновление);
for (инициализация; условие; обновление) { }

Цикл for не должен иметь более одного выражения в инициализации и более одного выражения в обновлении, несмотря на то, что язык Java позволяет подобную запись, она резко снижает читаемость кода, и как следствие - сопровождаемость.

Циклы while должны записываться как

while (условие) {
    операторы;
}
while (условие);
while (условие) { }
do {
    операторы;
} while (условие);

 `switch`

Операторы `case` должны быть описаны на одном уровне с оператором `switch`. - Все операторы внутри каждого кейса с отступом. - Оператор `break` пишется также с отступом. После него оставляется одна пустая строка, которая может быть использована для комментариев. - В случае отсутствия оператора `break` (то есть при необходимости описания "падения в следующий кейс") перед следующим оператором `case` также необходимо оставлять пустую строку.

 `try-catch-finally`

```java try { операторы; } catch (КлассИсключения экземпляр) { операторы; } finally { операторы; } ```

Пробелы

Пустые линии и пробелы улучшают читаемость кода за счёт отделения логических объединений кода друг от друга. Две пустые линии необходимо оставлять между секциями исходного файла, и между объявлениями Классов. Одну пустую линию между методами, между переменными метода и первыми операторами, перед комментариями, между логическими блоками внутри метода, для улучшения читаемости. Пробелы должны быть использованы в следующих обстоятельствах: - ключевое слово и скобки должны быть разделены одним пробелом, при этом, названия методов и аргументы методов не должны быть разделены; - открывающие скобки тела методов и операторов должны быть отделены пробелом; - в списках аргументов, после запятой необходимо ставить один пробел; - все математические операторы, кроме точки, инкремента, декремента и унарного минуса должны быть отделены пробелами с обеих сторон; - выражения в операторе `for` должны быть разделены пробелом; - приведения типов должны отделяться от операнда пробелом;

Названия

Классы - это имена существительные в именительном падеже. Названия классов принято писать с прописной буквы используя UpperCamelCase. - В написании имени класса желательно отказываться от аббревиатур; - Методы - это глаголы, названия методов пишутся со строчной буквы с использованием lowerCamelCase; - Переменные принято писать используя lowerCamelCase. - Названия переменных желательно делать короткими, но осмысленными, отражающими суть того, что хранится в переменной. Исключением являются однобуквенные "выбрасываемые" переменные, существующие только в рамках минимальных блоков кода. Обычно используются `i`, `j`, `k`, `m`, `n` для целочисленных, и `c`, `d`, `e` для символьных; - Классовые константы пишутся полностью прописными буквами, с разделением слов символом нижнего подчёркивания; - Если в названии переменной необходимо использовать аббревиатуры, следует их писать с первой заглавной буквы, остальными строчными. При объявлении такой переменной следует оставить комментарий с расшифровкой аббревиатуры;

Практики программирования

 Доступ

Избегайте создания классов, полей и методов с публичным доступом. Уместно, например, было бы использовать максимально открытый модификатор доступа, когда класс лишь описывает структуру данных без поведения. Также следует избегать доступа к статическим полям и методам класса через объекты, а не через имя класса. Принцип инкапсуляции предполагает, что область видимости объектов должна сводиться к минимуму;

 Переменные и константы

Избегайте присваивания значений нескольким переменным или константам на одной строке, особенно одним оператором; Избегайте использования оператора присваивания там, где его можно легко спутать с оператором сравнения; Избегайте использования встроенных присваиваний в попытке ускорить исполнение кода, например, строку `d = (a = b + c) + r;` необходимо разделить на две операции, сначала присвоив `а`, и затем присвоив `d`;

 Разное

  Скобки

Используйте скобки для улучшения понимания кода даже если правильную работу обеспечивает приоритет операторов.

  Возвращаемые значения

Избегайте дополнительного кода, если базовый функционал соответствует намерениям, например, вместо ```java if (booleanExpression) { return true; } else { return false; } ``` следует использовать ```java return booleanExpression; ```

Аналогично, вместо

if (condition) {
    return x;
}
return y;

лучше использовать

return (condition ? x : y);

  Специальные комментарии

Используйте в комментарии флаги - `ХХХ` если какой-то участок работает, но выглядит ошибочным; - `FIXME` если что-то выглядит ошибочным и не работает; - `TODO` если участок кода требует доработки.

Практические рекомендации

Помимо описанных в соглашении правил существуют рекомендации по написанию кода, выработанные практикующими программистами, зачастую связанные с особенностями языка и среды исполнения, например: - помните, что в языке Java любое изменение строки - это всегда новый объект класса `String`. Если в программе подразумевается активная работа со строками, во избежание проблем с ожиданием работы сборщика мусора рекомендуется использовать классы-обёртки, например `StringBuilder`; - Используйте быструю инициализацию строк:

String s = "HelloWorld"; // выполнится быстрее, чем
String s = new String("HelloWorld");
  • при работе со строками важно помнить о разнице одинарных и двойных кавычек. Символ в одинарных кавычках инициализирует примитивную переменную типа char, а в двойных ссылочную на экземпляр класса String;
  • избегайте лишних объектов. Самая дорогостоящая с точки зрения памяти и быстродействия операция в Java - это инициализация нового экземпляра объекта;
  • избегайте утечек памяти. Несмотря на наличие механизма сборки мусора, объекты могут не высвобождаться из под управления программы и не попадать под работу сборщика. Всегда освобождайте объекты баз данных, по окончании транзакций, используйте finally так часто, как это возможно (в Java8 механизм try-с-ресурсами);
  • при многопоточной работе избегайте состояния Deadlock, когда один поток ожидает высвобождения ресурсов другим, а другой в этот момент ожидает высвобождения ресурсов первым;
  • не используйте тип double без крайней необходимости;
  • закрывайте потоки, открытые для работы с файлами и сетью, даже если поток открыт классом-обёрткой;
  • никогда не оставляйте в коде блоки закомментированного кода, не работающий код всегда лучше удалять, чем комментировать. Неиспользуемый код также лучше удалять, в случае необходимости его вернуть следует обратиться к системе контроля версий;
  • используйте правило 10-100-500: никакие пакеты не должны содержать больше десяти классов, никакие методы не должны быть больше пятидесяти строк, никакие классы не должны быть больше пятисот строк;
  • пользуйтесь SOLID принципом:
    • классы должны иметь одно предназначение;
    • сущности должны быть открыты для расширения, но закрыты для модификации;
    • объекты в программе должны быть заменяемыми на экземпляры их подтипов без изменения правильности выполнения;
    • лучше использовать множество узкоспециальных интерфейсов, чем один универсальный;
    • зависимость абстракций. Зависимостей на что-то конкретное быть не должно.
  • не обобщайте обработку исключений. Если есть возможность, то лучше разделить действия, которые могут выбросить исключения по разным блокам try. Если такой возможности нет, следует использовать несколько конструкций catch для обработки разнородных исключений;
  • не игнорируйте исключения. Даже если Вы оставляете блок catch пустым, опищите в комментарии причину такого действия, e.printStackTrace() в этом контексте считается проигнорированным исключением;
  • предпочтительнее использование разработанных для фреймворков API, вместо функций и операторов языка. Например, вместо проверки if (collection.size() == 0) {...} лучше использовать if (collection.isEmpty()) {...}.

Применение САПР JetBrains Intellij IDEA

Для ускорения разработки и улучшения качества кода рекомендуется использование САПР `JetBrains Intellij IDEA` (далее ИДЕЯ). Помимо комфортного редактора кода и компилятора языка в САПР встроены инструменты для анализа, исправления ошибок, системы контроля версий. Помимо этого есть возможность расширения функционала с помощью плагинов, например для построения `UML`-диаграмм и автоматизации тестирования. Поскольку плагины разрабатываются сторонними компаниями, однозначно сказать о предпочтительности использования того или иного плагина нельзя. Для ускорения работы с ИДЕЕЙ рекомендуется использование клавиатурных сокращений для вызова часто используемых команд и функций, например: - `S-` для переименования переменной во всей области видимости; - `C-M-l` для принудительного вызова статического анализатора кода; - `C-` для базового автозаполнения; - `C-S-` для "умного" автозаполнения; - `` для шага внутрь функции при отладке; - `` для шага через функцию при отладке; - `C-k` для коммита проекта в систему контроля версий; - `C-S-k` для отправки текущих локальных коммитов в удалённый репозиторий.

Статический анализ кода

В стандартный пакет поставки ИДЕИ встроен статический анализатор и корректор кода на соответствие конвенции, по умолчанию настроенный на максимальную проверку кода. Изменить настройки статического анализатора можно кликнув на пиктограмме с изображением полицейского в правом нижнем углу окна САПР. Запустить автоматическое исправление ошибок можно сочетанием клавиш `C-M-l` или выбрав пункт меню `Code -> Reformat Code`. Анализатор кода обнаруживает не только ошибки компиляции, но также и неэффективные участки кода, такие как недоступные участки кода, неиспользуемый код, ненайденные вызовы, утечки памяти. Следит за соблидением правил написания кода, дубликатами. Анализатор кода в ИДЕЕ можно гибко настроить на разные уровни предупреждений. Запускается анализатор кода в меню `Analyze -> Inspect Code...` Помимо анализа кода механизм работы Java компилятора подразумевает обработку исключений при использовании классов и методов, которые могут генерировать исключительные ситуации. Возможно также использование стороннего программного обеспечения для более глубокого анализа кода. Например, применительно к программированию в ИДЕЕ удобнее всего пользоваться такими анализаторами кода, которые могут быть встроены как плагин. Например, PMD, основное назначение которого - поиск неоптимального кода, проблем с производительностью, нарушений стиля кодирования, дублей в коде и т.д.

Версионирование и распределённая разработка

Общие сведения

Системы контроля версий (СКВ) - это программное обеспечение облегчающее работу с изменяющейся информацией. СКВ регистрируют изменения в одном или нескольких файлах чтобы в дальнейшем иметь возможность вернуться к определённым старым версиям этих файлов. Традиционно СКВ используются в сфере разработки программного обеспечения и работы с текстами, но под версионный контроль можно поместить файлы практически любого типа. Таким образом, СКВ позволяют отслеживать изменения внесённые в код программы и при необходимости отменять их до любого нужного момента. Вести работу над новым функционалом без влияния на работоспособность существующего. СКВ позволяет возвращать к прежнему виду как отдельные файлы, так и проект целиком, предохраняет от порчи, потери и удаления отдельных частей проекта, позволяет отслеживать кем и когда вносились изменения, и в чём эти изменения состояли. При разработке одной программы несколькими программистами применение системы контроля версий делает работу в команде эффективнее. Архитектору проекта или тимлиду становится проще распределять работу разработчиков в рамках проекта.

Локальные СКВ

Очень распространён подход к версионированию, когда разработчик создаёт папки с названием проекта, добавляет к ним текущую дату и копирует в них файлы проекта. Такой подход распространён в связи с его простотой. Но он чаще даёт сбои. Очень легко забыть в каком каталоге находится нужная версия нужного файла, изменить не тот файл, либо скопировать файлы не туда, куда было нужно и затереть важные файлы. Чтобы решить эту проблему были разработаны локальные СКВ, с простой базой данных, хранящей все изменения нужных файлов. Одной из наиболее популярных СКВ такого типа является rcs, которая входит в комплект поставки многих BSD-систем. Утилита основана на работе с наборами патчей между парами версий (файлами, описывающими различия между файлами), которые хранятся в специальном формате на диске. Это позволяет пересоздать любой файл на любой момент времени, последовательно накладывая патчи.

Централизованные СКВ

Современные системы контроля версий применяются как при индивидуальной так и при командной разработке. Решить проблему сотрудничества разработчиков за разными компьютерами удалось создав централизованные СКВ (CVS, Subversion). В таких системах есть централизованный сервер, на котором хранятся все файлы под версионным контролем, и ряд клиентов, которые получают копии файлов с него. Такой подход имеет множество преимуществ (особенно над локальными СКВ). К примеру, все знают, кто и чем занимается в проекте. У администраторов есть чёткий контроль над тем, кто и что может делать, и, конечно, администрировать одну централизованную СКВ гораздо проще, чем множество локальных. Однако у такого подхода есть и недостатки. Очевидно, что централизованный сервер является уязвимым местом всей системы. Выключение сервера делает невозможным сохранение изменений разработчиками. Выход из строя жёсткого диска сервера, или части жёсткого диска, на которой содержится база данных СКВ, подвергает проект риску полной потери данных (за исключением, возможно, только некоторых частей, сохранившихся на компьютерах пользователей). Той-же проблеме подвержены и локальные СКВ.

Распределённые СКВ

Для решения существующих проблем локальных и централизованных СКВ были разработаны распределённые СКВ (Git, Mercurial). В таких СКВ клиенты не просто выгружают последние версии файлов, а полностью копируют весь репозиторий. Поэтому, в случае выхода из строя сервера через который шла работа любой клиентский репозиторий может быть скопирован обратно на сервер, чтобы восстановить базу данных. Кроме того, в большей части этих систем можно работать с несколькими удалёнными репозиториями, таким образом, можно одновременно работать по-разному с разными группами людей в рамках одного проекта. Так, в одном проекте можно одновременно вести несколько типов рабочих процессов, что невозможно в централизованных системах.

Git

Что такое Git?

Это важно усвоить, поскольку если вы поймёте, что такое Git, и каковы принципы его работы, вам будет гораздо проще пользоваться им эффективно. Изучая Git, постарайтесь освободиться от всего, что вы знали о других СКВ, таких как Subversion или Perforce. В Git'е совсем не такие понятия об информации и работе с ней как в других системах, хотя пользовательский интерфейс очень похож. Знание этих различий защитит вас от путаницы при использовании Git'а.

Слепки вместо патчей

Главное отличие Git'а от любых других СКВ (например, Subversion и ей подобных) — это то, как Git смотрит на свои данные. В принципе, большинство других систем хранит информацию как список изменений (патчей) для файлов. Эти системы (CVS, Subversion, Perforce, Bazaar и другие) относятся к хранимым данным как к набору файлов и изменений, сделанных для каждого из этих файлов во времени. Git не хранит свои данные в таком виде. Вместо этого Git считает хранимые данные набором слепков небольшой файловой системы. Каждый раз, когда вы фиксируете текущую версию проекта, Git, по сути, сохраняет слепок того, как выглядят все файлы проекта на текущий момент. Ради эффективности, если файл не менялся, Git не сохраняет файл снова, а делает ссылку на ранее сохранённый файл. Это важное отличие Git'а от практически всех других систем контроля версий. Из-за него Git вынужден пересмотреть практически все аспекты контроля версий, которые другие системы переняли от своих предшественниц. Git больше похож на небольшую файловую систему с инструментами, работающими поверх неё, чем на просто СКВ.

Почти все операции в Git локальные.

Для совершения большинства операций в Git'е необходимы только локальные файлы и ресурсы, т.е. обычно информация с других компьютеров в сети не нужна. Поскольку вся история проекта хранится локально на диске, большинство операций кажутся практически мгновенными. К примеру, чтобы показать историю проекта, Git'у не нужно скачивать её с сервера, он просто читает её прямо из локального репозитория. Поэтому историю вы увидите практически мгновенно. Если нужно просмотреть изменения между текущей версией файла и версией, сделанной месяц назад, Git может взять файл месячной давности и вычислить разницу на месте, вместо того чтобы запрашивать разницу у СКВ-сервера или качать с него старую версию файла и делать локальное сравнение. Кроме того, работа локально означает, что мало чего нельзя сделать без доступа к Сети или VPN. Во многих других системах это невозможно или же крайне неудобно. Например, используя Perforce, вы мало что можете сделать без соединения с сервером. Работая с Subversion и CVS, вы можете редактировать файлы, но сохранить изменения в вашу базу данных нельзя (потому что она отключена от репозитория).

Git следит за целостностью данных

Перед сохранением любого файла Git вычисляет контрольную сумму, и она становится индексом этого файла. Поэтому невозможно изменить содержимое файла или каталога так, чтобы Git не узнал об этом. Эта функциональность встроена в сам фундамент Git'а и является важной составляющей его философии. Если информация потеряется при передаче или повредится на диске, Git всегда это выявит. Механизм, используемый Git'ом для вычисления контрольных сумм, называется SHA-1 хешем. Это строка из 40 шестнадцатеричных символов (`0-9` и `a-f`), вычисляемая в Git'е на основе содержимого файла или структуры каталога. SHA-1 хеш выглядит примерно так: *24b9da6552252987aa493b52f8696cd6d3b00373*. При работе с Git'ом, эти хеши встречаются повсюду, поскольку он их очень широко использует. Фактически, в своей базе данных Git сохраняет всё не по именам файлов, а по хешам их содержимого.

Чаще всего данные в Git только добавляются

Практически все действия, которые вы совершаете в Git'е, только добавляют данные в базу. Очень сложно заставить систему удалить данные или сделать что-то неотменяемое. Можно, как и в любой другой СКВ, потерять данные, которые вы ещё не сохранили, но как только они зафиксированы, их очень сложно потерять, особенно если вы регулярно отправляете изменения в другой репозиторий. Поэтому пользуясь Git'ом можно экспериментировать, не боясь что-то серьёзно поломать.

Три состояния

Это самое важное, что нужно помнить про Git, для дальнейшего изучения и работы. В Git'е файлы могут находиться в одном из трёх состояний: зафиксированном, изменённом и подготовленном. "Зафиксированный" значит, что файл уже сохранён в вашей локальной базе. К изменённым относятся файлы, которые поменялись, но ещё не были зафиксированы. Подготовленные файлы — это изменённые файлы, отмеченные для включения в следующий коммит. Таким образом, в проектах, использующих Git, есть три части: каталог Git'а (Git directory), рабочий каталог (working directory) и область подготовленных файлов (staging area). Каталог Git'а — это место, где Git хранит метаданные и базу данных объектов вашего проекта. Это наиболее важная часть Git'а, и именно она копируется, когда вы клонируете репозиторий с другого компьютера. Рабочий каталог — это извлечённая из базы копия определённой версии проекта. Эти файлы достаются из сжатой базы данных в каталоге Git'а и помещаются на диск для того, чтобы вы их просматривали и редактировали. Область подготовленных файлов — это обычный файл, обычно хранящийся в каталоге Git'а, который содержит информацию о том, что должно войти в следующий коммит. Иногда его называют индексом (index), но в последнее время становится стандартом называть его областью подготовленных файлов (staging area). Стандартный рабочий процесс с использованием Git'а выглядит примерно так:

  • Вы вносите изменения в файлы в своём рабочем каталоге;
  • Подготавливаете файлы, добавляя их слепки в область подготовленных файлов;
  • Делаете коммит, который берёт подготовленные файлы из индекса и помещает их в каталог Git'а на постоянное хранение;

Если рабочая версия файла совпадает с версией в каталоге Git'а, файл считается зафиксированным. Если файл изменён, но добавлен в область подготовленных данных, он подготовлен. Если же файл изменился после выгрузки из БД, но не был подготовлен, то он считается изменённым.

IDEA + Git

Популярные решения в области систем контроля версий, такие как Git, Mercurial, CVS, Subversion встроены в ИДЕЮ. Большая часть основного функционала встроена в САПР и не требует никаких дополнительных настроек, кроме адреса удалённого репозитория. При работе с репозиторием из ИДЕИ можно выполнять все базовые операции, такие как: - просмотр журнала репозитория; - коммит; - отправка локального репозитория на сервер и скачивание с сервера; - отмена коммита (revert); - создание, удаление и слияние веток; - сравнение разных версий репозитория. Из базового функционала, который недоступен непосредственно из среды можно упомянуть добавление файлов к списку игнорирования и разбиение репозитория на модули (репозитории внутри репозитория)

Сторонние клиенты СКВ

Помимо встроенных в ИДЕЮ инструментов возможно использование как консоли гит, так и сторонних приложений с графическим интерфейсом, реализующих функционал гит, например SmartGit. Все разработанные на данный момент приложения, реализующие функционал Git не являются стандартизированными, то есть могут поддерживать не все функции системы контроля версий. В приложении SmartGit (требует обновления каждые 3 месяца для поддержания некоммерческой лицензии) наиболее полно представлены все функции Git, в том числе GitFlow.

Git Flow

Наиболее современной является технология GitFlow, набор расширений гит - автоматизированная система распределения проекта на ветки для работы на разных этапах разработки проекта. Работа ГитФлоу основана на слиянии веток проекта. С помощью ГитФлоу реализуются такие распространённые действия как работа над новым функционалом, публикация приложений с обновлённым функционалом, получение обновлённого функционала с удалённого сервера, создание релиза, удаление релиза, начало и конец внесения исправлений (с автоматическим копированием исправлений в ветку разработки).

IDEA + Git Flow

По умолчанию, работа с GitFlow не поддерживается ИДЕЕЙ. Поскольку GitFlow это расширение (и автоматизация) базового функционала - при определённом навыке работы с СКВ и знании особенностей работы GitFlow, можно работать в рамках GitFlow "вручную". На данный момент разрабатываются плагины для ИДЕИ интегрирующие GitFlow в среду разработки, например Git Flow Integration plugin (в текущей версии плагина не устранена проблема с установкой на ИДЕЮ 2017-го года).

Применение инструментов

При работе над проектами следует придерживаться следующих рекомендаций: - Хранить код в локальном (и желательно удалённом) репозитории; - Настроить файл .gitignore для того, чтобы не перегружать систему контроля версий файлами, не нуждающимися в версионировании; - Прежде, чем вносить изменения в код, убедиться, что правки вносятся в нужную ветку проекта. - Прежде, чем совершить коммит (даже локальный) необходимо проверить: - не нарушает ли вновь написанный код работу существующего кода; - содержит ли код комментарии в тех местах, где действия неочевидны; - соответствует ли написанный код стандартам форматирования; - есть ли в коде возможные оптимизации, указанные анализатором ИДЕИ; - есть ли в коде ошибки, указанные статическим анализатором; - покрыт ли вновь написанный код тестами в должном объёме; - Для каждой новой функции в программе следует создавать новую ветку репозитория, чтобы не нарушить работу существующего кода; - Для исправления выявленных в результате бета тестирования ошибок следует править код в отдельной ветке, и после исправления делать коммит как в ветку релиза, так и в ветку для дальнейших разарботок; - Каждый раз, перед совершением коммита удостовериваться, что в локальном репозитории находится актуальная версия ветки; - В случае появления за время работы над кодом изменений в ветке - сначала скачать обновлённый репозиторий, и убедиться, что вносимые изменения не дублируют существующие, и не противоречат им. На практие алгоритм будет выглядеть примерно так: 1. Делаем Clone удалённого репозитория со всеми ветками; 2. Выбираем последний, самый актуальный коммит; 3. Добавляем локальную ветку для внесения изменений; 4. Вносим изменения, проверяем работоспособность, комментируем, форматируем; 5. Делаем Pull той ветки, от которой ответвились (проверяем наличие обновлений); - В случае, если в удалённую (родительскую) ветку были добавлены коммиты необходимо зайти в свою локальную ветку с изменениями; - Сделать Merge с родительской веткой (притянуть удалённые изменения к себе, разрешить возможные конфликты, перепроверить работоспособность); 6. Переходим в удалённую ветку и делаем Merge с локальной веткой (последними изменениями); 7. При желании - удаляем локальную ветку.

Подробнее об использовании Git: http://1.0.0.137:3033/ovchinnikov_ii/git-man

Нагрузочное тестирование

Для постоянной поддержки кода в рабочем состоянии необходимо после добавления или переработки функционала провести нагрузочное тестирование. Наиболее простым в освоении и доступным для промежуточного тестирования является Apache JMeter. Для его использования не требуется какая-то особенная инфраструктура для тестирования нагрузки. Он обеспечивает поддержку нескольких инжекторов нагрузки, управляемых одним контроллером. На данный момент он может использоваться в трёх режимах: графическом, серверном и консольном. Результаты исполнения тест-кейсов могут отображаться в различном виде: таблицы, диаграмы, графики, лог файлы, дерево решений и т.п. JMeter обеспечивает параллельную и одновременную выборку различных функций отдельной группой потоков. Есть возможность писать собственные тест-кейсы, моделировать поведение нескольких пользователей с параллельными потоками и создавать большую нагрузку на тестируемые веб-приложения. Графический интерфейс пользователя предоставляет собой написанную на Java программу для создания нагрузочных тест-кейсов, которые предлагается запускать в консольном или серверном режиме.

Подробнее о нагрузочном тестировании на примере МОБД ЭКБ КП

Unit-тестирование

_(Не нужно писать тесты, если вы всегда пишете код без ошибок, обладаете идеальной памятью и даром предвидения. Ваш код настолько крут, что изменяет себя сам, вслед за требованиями клиента, а также иногда код объясняет клиенту, что его требования — ~~гов~~ не нужно реализовывать)_

Модульное тестирование, или юнит-тестирование (англ. unit testing) — процесс в программировании, позволяющий проверить на корректность отдельные модули исходного кода программы. Идея состоит в том, чтобы писать тесты для каждой нетривиальной функции или метода. Это позволяет достаточно быстро проверить, не привело ли очередное изменение кода к регрессии, то есть к появлению ошибок в уже оттестированных местах программы, а также облегчает обнаружение и устранение таких ошибок. Цель модульного тестирования — изолировать отдельные части программы и показать, что по отдельности эти части работоспособны. Для получения выгоды от модульного тестирования требуется строго следовать технологии тестирования на всём протяжении процесса разработки программного обеспечения. Нужно хранить не только записи обо всех проведённых тестах, но и обо всех изменениях исходного кода во всех модулях. С этой целью следует использовать систему контроля версий ПО. Таким образом, если более поздняя версия ПО не проходит тест, который был успешно пройден ранее, будет несложным сверить варианты исходного кода и устранить ошибку. Также необходимо убедиться в неизменном отслеживании и анализе неудачных тестов. Игнорирование этого требования приведёт к лавинообразному увеличению неудачных тестовых результатов. Для написания юнит-тестов для языка Java существуют библиотеки JUnit, TestNG, JavaTESK. Наиболее распространённым инструментом является JUnit.

Что тестировать, а что – нет? Одни говорят о необходимости покрытия кода на 100%, другие считают это лишней тратой ресурсов. Для экономии времени без потери качества можно использовать аналитический подход: расчертить лист бумаги по оси X и Y, где X – алгоритмическая сложность, а Y – количество зависимостей. Таким образом, код разделится на 4 группы:

  • Простой код без зависимостей. Скорее всего здесь и так все ясно. Его можно не тестировать;
  • Сложный код с большим количеством зависимостей. Хм... если у вас есть такой код, очень велика вероятность того, что это "God Object" с сильной связностью. Скорее всего, неплохо будет провести рефакторинг. Не стоит покрывать этот код юнит-тестами, потому что его следует переписать, а значит, изменятся сигнатуры методов и появятся новые классы. Так зачем писать тесты, которые придется выбросить? Хочу оговориться, что для проведения такого рода рефакторинга нам все же нужно тестирование, но лучше воспользоваться более высокоуровневыми приемочными тестами;
  • Cложный код без зависимостей. Это некие алгоритмы или бизнес-логика. Отлично, это важные части системы, их нужно тестировать в первую очередь;
  • Не очень сложный код с зависимостями. Этот код связывает между собой разные компоненты. Тесты важны, чтобы уточнить, как именно должно происходить взаимодействие.

При написании тестов, как и при написании кода программы следует придерживаться единого стиля написания тела теста. Отлично зарекомендовал себя подход AAA (arrange, act, assert). Например, есть класс, реализующий калькулятор, и у этого класса есть метод сложения, который должен возвращать сумму двух принятых в аргументе чисел. Используйте такой же способ именования для тестовых классов, как и для классов программы. Если есть класс ProblemResolver - добавьте в тестовый проект ProblemResolverTests. Каждый тестирующий класс должен тестировать только одну сущность. Иначе будут написаны тесты, которые не будут запускаться. Выберите «говорящий» способ именования методов тестирующих классов. На текущий момент, существует принятый способ именования методов: [Тестируемый метод]_[Сценарий]_[Ожидаемое поведение]. Такая запись понятна без объяснений. Это спецификация к вашему коду.

class CalculatorTests
{
    public void sum_2plus5_7returned()
    {
  	 // arrange
		 Calculator calc = new Calculator();

   	 // act
   	 long res = calc.sum(2,5);

   	 // assert
   	 Assert.areEqual(7, res);
	   }
    }

Такая форма записи гораздо легче читается, чем, например

class CalculatorTests
{
    public void sum_2plus5_7returned()
		 {
   	     Assert.areEqual(7, new Calculator().sum(2,5));
   	 }
}

А значит, такой код проще поддерживать. Каждый тест дожен проверять только одну вещь. Если процесс слишком сложен его необходимо поделить на несколько частей и тестировать отдельно. Если не придерживаться этого правила - по мере развития проекта тесты станут нечитаемыми и их окажется очень сложно поддерживать. В крупных проектах, где тестом невозможно покрыть сложный класс - тестом подменяют часть функционала, предполагая, что эта часть работает корректно. Такие заменённые части тестируют подобным образом, подменяя тестбенчем окружение. Выделяют два типа подделок: стабы (stubs) и моки (mock). Разница в том, что стаб ничего не проверяет, а лишь имитирует заданное состояние. А мок – это объект, у которого есть ожидания. Например, что данный метод класса должен быть вызван определенное число раз. Иными словами, тест никогда не сломается из-за «стаба», а вот из-за мока может. С технической точки зрения это значит, что используя стабы в Assert мы проверяем состояние тестируемого класса или результат выполненного метода. При использовании мока мы проверяем, соответствуют ли ожидания мока поведению тестируемого класса. Почему важно понимать разницу между моками и стабами? Представим, что нам нужно протестировать автоматическую систему полива. Можно подойти к этой задаче двумя способами:

Тестирование состояния Запускаем цикл (12 часов). И через 12 часов проверяем, хорошо ли политы растения, достаточно ли воды, каково состояние почвы и т.д.

Тестирование взаимодействия Установим датчики, которые будут засекать, когда полив начался и закончился, и сколько воды поступило из системы. Стабы используются при тестировании состояния, а моки – взаимодействия. Лучше использовать не более одного мока на тест. Иначе с высокой вероятностью вы нарушите принцип «тестировать только одну вещь». При этом в одном тесте может быть сколько угодно стабов или же мок и стабы.

Подробнее о Unit-тестировании: http://1.0.0.137:3033/Ilin_Boris/junit-man

Тестирование в ИДЕЕ

В ИДЕЕ можно создать тестирующий класс автоматически. Для этого можно нажать M- на классе и выбрать «Create test». Далее выбрать методы, которые нужно будет протестировать. В результате будет создан класс ClassnameTest с выбранными методами. Эти методы необходимо реализовать самостоятельно. Для создания модульных тестов с использованием библиотеки JUnit существует набор аннотаций и операторов, задающих поведение тестбенча.

About