JavaRush /Java блог /Random UA /Кава-брейк #72. Контроль якості коду та навіщо він вам по...

Кава-брейк #72. Контроль якості коду та навіщо він вам потрібен. Що таке монада? Базова теорія для Java-розробника

Стаття з групи Random UA

Контроль якості коду та навіщо він вам потрібен

Джерело: DZone Якість коду - важлива річ, вірно? Все це знають і всі погоджуються з цим. Але чому тоді більшість софтверних компаній:
  • не мають визначення якості коду,
  • непослідовно використовують стандарти якості коду,
  • Чи не контролюють якість свого коду?
У цій статті я збираюся зосередитись на третій проблемі, а саме на відсутності контролю за якістю коду . Кава-брейк #72.  Контроль якості коду та навіщо він вам потрібен.  Що таке монада?  Базова теорія для Java-розробника - 1Якщо команда не контролює якість вихідного коду, то як вона визначає, що розробники справді створюють ефективне програмне забезпечення? Низька якість коду погіршує продуктивність та ефективність робочого процесу. Більше того, чи поінформовані вони про те, що їхній технічний борг може збільшуватися? І, як наслідок, зросте вартість додавання нових фіч.

Технічний обов'язок?

Про технічний обов'язок написано сотні текстів, але цей , написаний Мартіном Фаулером, добре розкриває тему. Отже, що буде, якщо не виплатити технічний борг (або, іншими словами, до чого призводить відсутність роботи над покращенням коду)? Якщо ви погано дбаєте про свій код , то це призводить до таких наслідків:
  1. Код не відповідає сучасним стандартам написання коду.
  2. Страждають показники якості вашого коду.
  3. Виникають проблеми із тестуванням вашого коду.
  4. Доводиться витрачати зайвий час на покращення якості коду (рефакторинг).
  5. Ви більше часу та зусиль витрачаєте на супровід коду , а не на розробку.
Може спочатку ці проблеми ви і не помітите, але з часом все стане ясно. «Як і у випадку з грошовим боргом, якщо технічний борг не погашено, він може накопичувати «відсотки», що ускладнює реалізацію змін». Це був своєрідний фінансовий погляд на наслідки поганої якості коду. Але погляньмо на цю проблему з точки зору розробників програмного забезпечення. Що для нас означає погану якість коду та великий технічний обов'язок?
  1. Підвищений рівень стресу , оскільки можна не встигнути до дедлайну. Щоб додати щось у код, треба більше часу.
  2. Знову ж таки, збільшення рівня стресу , тому що на вихідних вам доводиться займатися вирішенням проблем із кодом.
  3. Втомлива та нудна робота замість творчого та приємного кодингу.
  4. "Чому це займає стільки часу?" — питання, яке ваше керівництво повторює дедалі частіше.
  5. Розчарування від роботи з поганим кодом . Вам це знайоме, правда? ;)
Добре, тепер ми знаємо, наскільки погано може бути, якщо недостатньо піклуємося про наш код. Але що ми можемо з цим поробити?

Процес контролю якості коду

Я майже впевнений, ви знаєте, що не існує чарівної палички, яка могла б перетворити поганий код на надійний. Особливо за день. Але це не означає, що ми не можемо нічого вдіяти. Ми можемо побудувати процес . Це може бути серія повторюваних кроків, які:
  • допомагають підтримувати якість коду високому рівні для проектів, у яких якість вже задовільна;
  • показують, що, де і як потрібно змінити існуючий код, щоб поліпшити його якість.
Однак робити все вручну – не найкращий варіант. По-перше, це неефективно. По-друге, можуть виникнути помилки. Ви колись пропускали очевидну помилку в коді під час перевірки коду? Я, наприклад, так… :( Більше того, іноді помилки навіть не виявляються під час тестування (звичайно, ручного), але з'являються першого ж дня після релізу...

Автоматизація – ключ до успіху

Ручна перевірка коду гірша, ніж автоматична. Автоматизація може зняти тягар постійної перевірки коду. Це дозволить вам зосередитись на вирішенні реальних проблем за допомогою програмного забезпечення, а не на вирішенні проблем самого програмного забезпечення.. У світі Java-розробки існує багато інструментів, здатних допомогти вам в автоматизації перевірки та покращення якості коду. Я не знаю жодного інструменту, який міг би перетворити ваш застарілий, погано спроектований та погано закодований проект у добре організований та легко підтримуваний. Принаймні не зараз. І не все можна повністю автоматизувати (наприклад, реву коду). Але чи можемо ми позбутися багатьох помилок коду, що важко виявлятися, і зробити наш код чистішим і надійнішим на самому початку циклу розробки ( SDLC )? Що ще важливіше, автоматизуючи процес, ми можемо досягти цього, витративши лише невелику кількість зусиль та часу порівняно з роботою вручну. І все це перед тим, як команда тестувальників візьметься за справу. ;)

Що перевіряти?

Тепер ми знаємо, що нам потрібний процес. Більше того, ми знаємо, що він має бути максимально автоматичним. Але що ми маємо перевірити? Ми можемо розділити весь процес перевірки вихідного коду на 7 областей:
  • перевірка стилю оформлення коду,
  • перевірка коректності коду та виявлення типових помилок,
  • виявлення недоліків конструкції,
  • розрахунок складності коду,
  • перевірка безпеки коду та залежностей,
  • розрахунок покриття коду,
  • огляд коду.

Висновок

Якість коду – це дуже важлива тема і щодо неї є багато різних думок. Але що б ви не думали про якість коду, це все одно впливає на наше (розробників) життя. Це може покращити якість нашого життя або погіршити його. Ви можете пишатися своїм кодом, або вам може бути за нього соромно. Ви можете вирішувати реальні проблеми або виконувати стомлюючі та повторювані завдання щодня. Недостатньо навчати розробників, організовувати тренінги чи робити презентації якість коду. Наявність процесу забезпечення якості коду є обов'язковою, якщо компанія серйозно ставиться до створення програмного забезпечення. Навіть у «зіркових» командах розробників.

Що таке монада? Базова теорія для Java-розробника

Джерело: DZone Як можна здогадатися із назви, основною темою цієї статті будуть монади. За допомогою класу Optional Java я постараюся описати їх докладно, поринувши в структуру і внутрішню роботу монад. Наприкінці публікації я реалізую монаду для ліг, а потім опишу роль кожного основного фрагмента коду і наведу простий приклад його використання.Кава-брейк #72.  Контроль якості коду та навіщо він вам потрібен.  Що таке монада?  Базова теорія для Java-розробника - 2

Навіщо вивчати, як працюють монади?

Завжди добре мати базове уявлення про те, як працюють речі, які ми використовуємо. Якщо ви 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> .

Ліва ідентичність

Якщо ми створимо нову монаду і прив'яжемо її до функції, результат має бути таким самим, як при застосуванні функції до значення:
Optional.of(value).flatMap(F).equals(F.apply(value))

Права ідентичність

Результат прив'язки одиничної функції до монади має бути таким самим, як і при створенні нової монади:
Optional.of(value).flatMap(Optional::of).equals(Optional.of(value))

Асоціативність

У ланцюжку функціональних програм не важливо, як функції вкладені:
Optional<B> leftSide = Optional.of(value).flatMap(F).flatMap(G)

Optional<B> rightSide = Optional.of(value).flatMap(F.apply(value).flatMap(G))

leftSide.equals(rightSide).

Створення монади

Перше, що нам потрібно, це параметризований тип M<T>, оболонка для нашого значення типу T. Наш тип має реалізовувати дві функції:
  • Unit - використовується для надбудови (wrapper) нашого значення і має підпис (T) = M<T> .
  • Bind - відповідає за виконання операцій. Тут ми передаємо функцію, яка працює зі значенням у нашому контексті та повертає його з іншим типом, уже укладеним у контекст. Цей метод повинен мати наступний підпис (T -> M<U>) = M<U> .
Щоб зробити все зрозумілішим, я ще раз скористаюся Optional і покажу, як у даному випадку виглядає наведена вище структура. Тут перша умова виконується відразу, тому що Optional це параметризований тип. Роль функції unit виконують методиnullable і 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 щоб показати, що це супер складно, і насправді його можна досить легко реалізувати. На прикладі я показав, як може виглядати використання монади, які потенційні переваги такої дії і чим вона може відрізнятись від звичайного виклику методів.
Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ