Рубрики
Без рубрики

Предотвращение Теории Разбитых окон с использованием шаблонов проектирования

Теория разбитого окна, представленная Эндрю Хантом и Дэвидом Томасом в книге “Прагматичный программист: От… Помеченный как java, соответствие коду, рефакторинг, ооп.

Теория разбитого окна, представленная Эндрю Хантом и Дэвидом Томасом в Прагматичный программист: от подмастерья до Мастера – одна из важнейших концепций в разработке программного обеспечения. Авторы предположили, что вещи, такие как система, страдают от чего-то, называемого энтропией. Энтропия, как физический термин, переводится как уровень беспорядка в системе. К сожалению, этот уровень всегда стремится к максимуму, а это значит, что если есть энтропия, то энтропии будет все больше и больше.

В 1982 году Джеймс К. Уилсон и Джордж Л. Келлинг в своей статье “Разбитые окна” привели следующий пример: Представьте себе здание с несколькими (несколькими) разбитыми окнами. Если окна не будут отремонтированы как можно скорее, то, как правило, кто-то другой (например, вандалы) разобьет больше окон и, в конце концов, ворвется в здание, разожжет внутри него костер и т. Д.

Но в 1969 году Филип Зимбардо, профессор психологии из Стэнфорда, проверил теорию на практике. Он оставил машину без номерных знаков в районе Бронкса, а вторую машину с такими же условиями – в Пало-Альто. Машина, находившаяся в Бронксе, подверглась нападению через несколько минут после того, как ее бросили. Менее чем через 24 часа вандалы забрали из машины все ценное. Некоторое время спустя окна машины были разбиты, обивка была порвана, и дети использовали машину в качестве игровой площадки. Тем временем машина в Пало-Альто осталась нетронутой. Через неделю, когда никто ничего не сделал с машиной, Зимбардо сам разбил ее кувалдой. Очень скоро после этого то же самое начало происходить и с другой машиной. Люди тоже начали уничтожать транспортное средство. Зимбардо заметил, что в обоих случаях люди, напавшие на машину, были в основном хорошо одетыми, опрятными и, казалось бы, респектабельными людьми.

Теперь вы, возможно, думаете: “Хорошо, как могут быть сломаны “окна” программного обеспечения, которое я разрабатываю?”. Что ж, аналогично опыту Зимбардо, если есть фрагмент кода плохого качества, то тенденция такова, что кода плохого качества будет больше. Помните, что если есть энтропия, то энтропии будет больше.

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

public class User {

    private String userType;

    public void calculateTaxes() {
        if (userType.equals("A")) {
            //do A
        } else if (userType.equals("B")) {
            //do B
        } else if (userType.equals("C")) {
            //do C
        } else {
            //do D
        }
    }
}

Будет ли этот код работать? Да, безусловно. Это будет идеально подходит для вашей необходимости доставить его в короткие сроки. Но там есть огромное разбитое окно. Это накладывает ряд правил из чистого кода: почему класс пользователей рассчитывает свои собственные налоги? Пользователь не должен этого делать. Существует огромная проблема ответственности. Вторая большая проблема заключается в следующем: если в другой момент кто-то другой захочет рассчитать налог для типа пользователя C, что будет делать разработчик? Возможно, добавьте еще одно условие “если” или, в лучшем случае, измените код на случай переключения. А код будет увеличиваться и увеличиваться. И почему? Потому что ты запустил плохой код. Вы разбили первое окно и оставили его там, не починив. Если есть энтропия, то энтропии будет больше.

Итак, учитывая, что вы разбили окно, но хотите починить его как можно скорее, чтобы другие окна не были разбиты. Что можно сделать?

Первая проблема, которую можно заметить, заключается в том, что класс пользователя делает то, чего не следует. Учитывая, что пользователь является моделью, то есть тем, что потенциально хранится в базе данных приложения, оно не должно содержать никакой “продвинутой” логики. Давайте решим эту проблему, создав класс, который предоставляет услуги для пользователя.

public class UserServiceImpl implements UserService {

    @Override
    public void calculateTaxesForUser(User user) {
        if (user.getUserType().equals("A")) {
            //do A
        } else if (user.getUserType().equals("B")) {
            //do B
        } else if (user.getUserType().equals("C")) {
            //do C
        } else {
            //do D
        }
    }

}

Все ли проблемы решены? Абсолютно нет. Все еще существует проблема ответственности. Если бы мы хотели рассчитать налог для другой вещи, а не только для пользователя, нам нужно было бы создать другой класс, который делает это. Так почему бы не поместить всю логику расчета налогов в центральный класс, отвечающий только за расчет налогов?

public class TaxesCalculationServiceImpl implements TaxesCalculationService {

    @Override
    public void calculateTaxesForUser(User user) {
        if (user.getUserType().equals("A")) {
            //do A
        } else if (user.getUserType().equals("B")) {
            //do B
        } else if (user.getUserType().equals("C")) {
            //do C
        } else {
            //do D
        }
    }

}

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

Но код далек от совершенства. По-прежнему существует огромная проблема: код поощряет добавление еще одного оператора if, чтобы расширить возможности пользователей. Как это можно решить? Шаблон разработки стратегии (или указатель функции) содержит ответ.

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

Шаблон разработки стратегии предполагает следующее: если существует класс, который выполняет определенную задачу различными способами, то эти способы должны быть разделены на разные классы, которые называются стратегии , имеющие реализованный общий интерфейс. Это как раз наша проблема. Метод, который выполняет вычисления, делает это по-разному для каждого типа пользователей. Таким образом, решение будет состоять в том, чтобы разделить каждый из этих способов на разные классы:

public class TaxCalculationStrategyForUserTypeA implements TaxCalculationStrategy {

    @Override
    public void calculateTaxesForUser(User user) {
        //do A
    }
}
public class TaxCalculationStrategyForUserTypeB implements TaxCalculationStrategy {

    @Override
    public void calculateTaxesForUser(User user) {
        //do B
    }
}
public class TaxCalculationStrategyForUserTypeC implements TaxCalculationStrategy {

    @Override
    public void calculateTaxesForUser(User user) {
        //do C
    }
}
public class TaxCalculationStrategyForUserTypeD implements TaxCalculationStrategy {

    @Override
    public void calculateTaxesForUser(User user) {
        //do D
    }
}

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

public class TaxesCalculationServiceImpl implements TaxesCalculationService {

    @Resource
    private TaxCalculationStrategy taxCalculationStrategy;

    @Override
    public void calculateTaxesForUser(User user) {
        taxCalculationStrategy.calculateTaxesForUser(user);
    }

    @Override
    public void setTaxCalculationStrategy(TaxCalculationStrategy taxCalculationStrategy) {
        this.taxCalculationStrategy = taxCalculationStrategy;
    }

}

Таким образом, можно изменить стратегию, используемую службой во время выполнения, когда это необходимо:

public class Application {

    @Resource
    private TaxesCalculationService taxesCalculationService;

    public void doTaxCalculationForUser (User user){
        taxesCalculationService.setTaxCalculationStrategy(new TaxCalculationStrategyForUserTypeC());
        taxesCalculationService.calculateTaxesForUser(user);
        taxesCalculationService.setTaxCalculationStrategy(new TaxCalculationStrategyForUserTypeB());
        taxesCalculationService.calculateTaxesForUser(user);
    }

}

Конечно, каждое приложение будет реализовывать его по-своему.

Возможно, вы думаете: “Полиморфизм тоже мог бы решить эту проблему”. Да, это правда. Может быть, это будет не лучший вариант, но действительно решило бы проблему. Но дело в том, что наш код сейчас стал лучше. Мы починили разбитое окно, которое было оставлено позади. Вероятно, тот, кто расширит функциональные возможности, проверит, как работает реализация, и будет следовать шаблону, сохраняя код в чистоте.

Разбитые окна должны быть постоянной проблемой при разработке программного обеспечения. Откажите себе в продолжении разработки поверх плохого кода. Сначала почините разбитые окна, найдите наилучшие подходы, изучите новые вещи и предотвратите появление новых. Это не только сделает вас лучшим разработчиком в техническом плане, но и улучшит качество вашего процесса разработки в целом.

Энтропии нет, значит, и энтропии не будет.

Оригинал: “https://dev.to/derickmr/preventing-broken-window-theory-using-design-patterns-2pp8”