Источник: DZone
Качество кода — важная вещь, верно? Все это знают и все согласны с этим. Но почему же тогда большинство софтверных компаний:
не имеют определения качества кода,
непоследовательно используют стандарты качества кода,
не контролируют качество своего кода?
В этой статье я собираюсь сосредоточиться на третьей проблеме, а именно на отсутствии контроля над качеством кода.Если команда не контролирует качество исходного кода, то как она определяет, что разработчики действительно создают эффективное программное обеспечение? Низкое качество кода ухудшает производительность и эффективность рабочего процесса.
Более того, осведомлены ли они о том, что их технический долг может увеличиваться? И, как следствие, вырастет стоимость добавления новых фич.
Технический долг?
О техническом долге написаны сотни текстов, но этот, написанный Мартином Фаулером, хорошо раскрывает тему.
Итак, что будет если не выплатить технический долг (или, другими словами, к чему приводит отсутствие работы над улучшением кода)?
Если вы плохо заботитесь о своем коде, то это приводит к таким последствиям:
Код не отвечает современным стандартам написания кода.
Страдают показатели качества вашего кода.
Возникают проблемы с тестированием вашего кода.
Приходится тратить лишнее время на улучшение качество кода (рефакторинг).
Вы больше времени и усилий тратите на сопровождение кода, а не на разработку.
Может, поначалу эти проблемы вы и не заметите, но со временем все станет ясно.
«Как и в случае с денежным долгом, если технический долг не погашен, он может накапливать «проценты», что затрудняет реализацию изменений».
Это был своего рода финансовый взгляд на последствия плохого качества кода. Но давайте посмотрим на эту проблему с точки зрения разработчиков программного обеспечения. Что для нас значит плохое качество кода и большой технический долг?
Повышенный уровень стресса, поскольку можно не успеть к дедлайну. Чтобы добавить что-то в код, надо больше и больше времени.
Опять же, увеличение уровня стресса, потому что на выходных вам приходится заниматься решением проблем с кодом.
Утомительная и скучная работа вместо творческого и приятного кодинга.
«Почему это занимает столько времени?» — вопрос, который ваше руководство повторяет все чаще и чаще.
Разочарование от работы с плохим кодом. Вам это знакомо, правда? ;)
Хорошо, теперь мы знаем, насколько плохо может быть, если мы недостаточно заботимся о нашем коде. Но что мы можем с этим поделать?
Процесс контроля качества кода
Я почти уверен, вы знаете, что не существует волшебной палочки, которая могла бы превратить плохой код в надежный. Особенно за один день.
Но это не значит, что мы не можем ничего сделать. Мы можем выстроить процесс. Это может быть серия повторяемых шагов, которые:
помогают поддерживать качество кода на высоком уровне для проектов, в которых качество уже удовлетворительное;
показывают, что, где и как нужно изменить в существующем коде, чтобы улучшить его качество.
Однако делать все вручную — не лучший вариант. Во-первых, это неэффективно. Во-вторых, могут возникнуть ошибки.
Вы когда-нибудь пропускали очевидную ошибку в коде во время проверки кода? Я, например, да… :(
Более того, иногда ошибки даже не обнаруживаются во время тестирования (конечно, ручного), но появляются в первый же день после релиза...
Автоматизация — ключ к успеху
Ручная проверка кода хуже, чем автоматическая. Автоматизация может снять с вас бремя постоянной проверки кода. Это позволит вам сосредоточиться на решении реальных проблем с помощью программного обеспечения, а не на решении проблем самого программного обеспечения.
В мире Java-разработки существует много инструментов, способных помочь вам в автоматизации проверки и улучшения качества кода.
Я не знаю ни одного инструмента, который мог бы преобразовать ваш устаревший, плохо спроектированный и плохо закодированный проект в хорошо организованный и легко поддерживаемый. По крайней мере, не сейчас. И не все можно полностью автоматизировать (например, ревью кода).
Но можем ли мы избавиться от многих трудно обнаруживаемых ошибок кода и сделать наш код более чистым и надежным в самом начале цикла разработки (SDLC)?
Что еще более важно, автоматизируя процесс, мы можем добиться этого, затратив лишь небольшое количество усилий и времени по сравнению с работой вручную. И все это до того, как команда тестировщиков возьмется за дело. ;)
Что проверять?
Теперь мы знаем, что нам нужен процесс. Более того, мы знаем, что он должен быть максимально автоматическим. Но что мы должны проверить?
Мы можем разделить весь процесс проверки исходного кода на 7 областей:
проверка стиля оформления кода,
проверка корректности кода и выявление типичных ошибок,
выявление недостатков конструкции,
расчет сложности кода,
проверка безопасности кода и зависимостей,
расчет покрытия кода,
обзор кода.
Вывод
Качество кода — это очень важная тема и по ней есть много различных мнений. Но что бы вы ни думали о качестве кода, это все равно влияет на нашу (разработчиков) жизнь.
Это может улучшить качество нашей жизни или ухудшить ее. Вы можете гордиться своим кодом или вам может быть за него стыдно. Вы можете решать реальные проблемы или выполнять утомительные и повторяемые задачи каждый день.
Недостаточно обучать разработчиков, организовывать тренинги или делать презентации о качестве кода. Наличие процесса обеспечения качества кода является обязательным, если компания серьезно относится к созданию программного обеспечения. Даже в «звездных» командах разработчиков.
Что такое монада? Базовая теория для Java-разработчика
Источник: DZone
Как можно догадаться из названия, основной темой этой статьи будут монады. С помощью класса Optional в Java я постараюсь описать их подробно, погрузившись в структуру и внутреннюю работу монад. В конце публикации я реализую монаду для логов, а затем опишу роль каждого основного фрагмента кода и приведу простой пример его использования.
Зачем изучать, как работают монады?
Всегда хорошо иметь базовое представление о том, как работают вещи, которые мы используем. Если вы Java-разработчик, то вы, вероятно, используете монады, даже не подозревая об этом. Две из наиболее известных фич Java 8 — это монадные реализации, а именно Stream и Optional.
Начнем с описания, что такое монада. На мой взгляд, здесь все довольно просто.
«Монада — это просто моноид в категории эндофункторов».
Это концепция, а не класс или интерфейс. Конечно, в дальнейшем ее можно реализовать как класс или интерфейс. Это можно сделать практически на любом языке со статической типизацией с поддержкой универсальных типов. Более того, мы можем рассматривать ее как оболочку, которая помещает наше значение в некоторый контекст и позволяет нам выполнять операции со значением. В этом контексте выходные данные операции на любом этапе являются входными данными для операции на следующем этапе.
Примеры монад в современных языках программирования:
Stream (Java).
Optional/Option (Java/Scala).
Either (Scala).
Try (Scala).
IO Monad (Haskell).
Правила монад
Последнее, что нужно вспомнить, говоря о монадах, — это их правила. Если мы хотим рассматривать нашу реализацию как настоящую монаду, мы должны им следовать. Есть три правила: левая идентичность, правая идентичность и ассоциативность. С помощью класса Optional я постараюсь более подробно объяснить эти правила.
Но сначала несколько определений:
F является функцией с подписью: (T -> Optional<U>) = Optional<U>.
G — функция с подписью (A -> Optional<B>) = Optional<B>.
FG = F.apply(value).flatMap(G) с подписью: (T -> Optional<B>) = Optional<B>.
Левая идентичность
Если мы создадим новую монаду и привяжем ее к функции, результат должен быть таким же, как при применении функции к значению:
Первое, что нам нужно, это параметризованный тип M<T>, оболочка для нашего значения типа T. Наш тип должен реализовывать две функции:
Unit — используется для надстройки (wrapper) нашего значения и имеет подпись (T) = M<T>.
Bind — отвечает за выполнение операций. Здесь мы передаем функцию, которая работает со значением в нашем контексте и возвращает его с другим типом, уже заключенным в контекст. Этот метод должен иметь следующую подпись (T -> M<U>) = M<U>.
Чтобы сделать все более понятным, я еще раз воспользуюсь Optional и покажу, как в данном случае выглядит приведенная выше структура.
Здесь первое условие выполняется сразу, потому что Optional — это параметризованный тип. Роль функции unit выполняют методы ofNullable и of. FlatMap играет роль функции bind. Конечно, в случае Optional, границы типов позволяют нам использовать более сложные типы, чем в определении выше.
Закончив с теорией, приступим к реализации
//imports
public final class LogMonad<T> {
private final T value;
private static final Logger LOGGER =
Logger.getLogger(LogMonad.class.getName());
private LogMonad() {
this.value = null;
}
private LogMonad(T value) {
this.value = value;
}
static <T> LogMonad<T> of(T value) {
Objects.requireNonNull(value, "value is null");
return new LogMonad<>(value);
}
<U> LogMonad<U> flatMap(Function<T, LogMonad<U>> function) {
Objects.requireNonNull(function, "function is null");
try {
return function.apply(value);
} catch (Throwable t) {
return new LogMonad<>();
}
}
LogMonad<T> log() {
LOGGER.log(Level.INFO, "{0}", value);
return this;
}
LogMonad<T> log(Level loggingLevel) {
Objects.requireNonNull(loggingLevel, "logging level is null");
LOGGER.log(loggingLevel, "{0}", value);
return this;
}
//equals, hashCode & toString
}
И вуаля, монада реализована. Опишем подробно, что именно я здесь сделал.
Что именно здесь произошло
Основой нашей реализации является параметризованный класс с неизменяемым полем с именем value, который отвечает за хранение нашего значения. Второе поле — Logger, будет отвечать за ключевой эффект нашей монады. Затем у нас есть частный конструктор, который делает невозможным создание объекта каким-либо другим способом, кроме как с помощью нашего метода надстройки.
Далее у нас есть две основные функции монад, а именно of (эквивалент unit) и flatMap (эквивалент bind), которые гарантируют, что наша реализация выполняет требуемые условия в форме законов монад. Последние два метода отвечают за наш эффект монады, то есть они отвечают за запись текущего значения в стандартный вывод. Один из них позволяет пройти уровень логирования, другой использует уровень "INFO".
Теперь настало время для примера использования. Итак, вот он.
//imports
public final class LogMonad<T> {
private final T value;
private static final Logger LOGGER =
Logger.getLogger(LogMonad.class.getName());
private LogMonad() {
this.value = null;
}
private LogMonad(T value) {
this.value = value;
}
static <T> LogMonad<T> of(T value) {
Objects.requireNonNull(value, "value is null");
return new LogMonad<>(value);
}
<U> LogMonad<U> flatMap(Function<T, LogMonad<U>> function) {
Objects.requireNonNull(function, "function is null");
try {
return function.apply(value);
} catch (Throwable t) {
return new LogMonad<>();
}
}
LogMonad<T> log() {
LOGGER.log(Level.INFO, "{0}", value);
return this;
}
LogMonad<T> log(Level loggingLevel) {
Objects.requireNonNull(loggingLevel, "logging level is null");
LOGGER.log(loggingLevel, "{0}", value);
return this;
}
//equals, hashCode & toString
}
В приведенном выше коде помимо того, что мы видим, как работают монады, мы также можем оценить несколько плюсов их использования.
Прежде всего, мы инкапсулируем наш побочный эффект логгирования в монадический контекст. Это обеспечил слой абстракции над нашей логикой. Благодаря этой абстракции мы смогли уменьшить количество шаблонов. Наш код стал более декларативным, более простым для чтения и понимания. Наконец, мы объединили все операции в единый конвейер.
С другой стороны, в немонадической части примера мы можем увидеть все детали реализации. Код менее дескриптивный и его можно использовать повторно. Более того, мы выявили побочный эффект, который может вызвать некоторые проблемы в будущем и сделали наш код менее читабельным. Если мы когда-нибудь решим обработать ошибку из-за вызова функции, нам понадобится много шаблонов.
На мой взгляд, монадический код легче читать и понимать, чем его немонадический аналог.
Подводим итоги
Монада — очень полезное понятие. Вероятно, многие из нас используют его в повседневной жизни. В этой статье я попытался дать простое объяснение его теоретической основы и логики, лежащей в основе понятия. Я внедрил LogMonad, чтобы показать, что это супер сложно, и на самом деле его можно довольно легко реализовать. На примере я показал, как может выглядеть использование монады, каковы потенциальные преимущества такого действия и чем оно может отличаться от обычного вызова методов.
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ