Главная страница
Навигация по странице:

  • replaceImage(ImageUtilities.getScaledImage( image, scalingFactor, scalingFactor));

  • image = newImage; }

  • calculateBaseVacationHours(); alterForLegalMinimums(); applyToPayroll();

  • Зачем нужна многопоточность

  • Мифы и неверные представления

  • Защита от ошибок многопоточности

  • Принцип единой ответственности

  • Рекомендация

  • Создание, анализ ирефакторинг


    Скачать 3.16 Mb.
    НазваниеСоздание, анализ ирефакторинг
    Дата29.09.2022
    Размер3.16 Mb.
    Формат файлаpdf
    Имя файлаChistyj_kod_-_Sozdanie_analiz_i_refaktoring_(2013).pdf
    ТипКнига
    #706087
    страница24 из 49
    1   ...   20   21   22   23   24   25   26   27   ...   49
    203
    image.dispose();
    System.gc();
    image = newImage;
    }
    Чтобы обеспечить чистоту системы, следует устранить незначительное дублиро- вание между методами scaleToOneDimension и rotate
    :
    public void scaleToOneDimension(
    float desiredDimension, float imageDimension) {
    if (Math.abs(desiredDimension - imageDimension) < errorThreshold)
    return;
    float scalingFactor = desiredDimension / imageDimension;
    scalingFactor = (float)(Math.floor(scalingFactor * 100) * 0.01f);
    replaceImage(ImageUtilities.getScaledImage(
    image, scalingFactor, scalingFactor));
    }
    public synchronized void rotate(int degrees) {
    replaceImage(ImageUtilities.getRotatedImage(image, degrees));
    }
    private void replaceImage(RenderedOp newImage) {
    image.dispose();
    System.gc();
    image = newImage;
    }
    В ходе выделения общности конструкций на этом микроскопическом уровне начинают проявляться нарушения принципа SRP . Таким образом, только что сформированный метод можно переместить в другой класс . Это расширяет видимость метода . Другой участник группы может найти возможность дальней- шего абстрагирования нового метода и его использования в другом контексте .
    Таким образом, принцип «повторного использования даже в мелочах» может привести к значительному сокращению сложности системы . Понимание того, как обеспечить повторное использование в мелочах, абсолютно необходимо для его обеспечения в большом масштабе .
    Паттерн ШАБЛОННЫЙ МЕТОД [GOF] относится к числу стандартных при- емов устранения высокоуровневого дублирования . Пример:
    public class VacationPolicy {
    public void accrueUSDivisionVacation() {
    // Код вычисления продолжительности отпуска
    // по количеству отработанных часов
    // ...
    // Код проверки минимальной продолжительности отпуска
    // по стандартам США
    // ...
    // Код внесения отпуска в платежную ведомость
    // ...
    }
    203

    204
    Глава 12 . Формирование архитектуры public void accrueEUDivisionVacation() {
    // Код вычисления продолжительности отпуска
    // по количеству отработанных часов
    // ...
    // Код проверки минимальной продолжительности отпуска
    // по европейским стандартам
    // ...
    // Код внесения отпуска в платежную ведомость
    // ...
    }
    }
    Код accrueUSDivisionVacation и accrueEuropeanDivisionVacation в основном совпада- ет, если не считать проверки минимальной продолжительности . Этот фрагмент алгоритма изменяется в зависимости от типа работника .
    Для устранения этого очевидного дублирования можно воспользоваться паттер- ном ШАБЛОННЫЙ МЕТОД:
    abstract public class VacationPolicy {
    public void accrueVacation() {
    calculateBaseVacationHours();
    alterForLegalMinimums();
    applyToPayroll();
    }
    private void calculateBaseVacationHours() { /* ... */ };
    abstract protected void alterForLegalMinimums();
    private void applyToPayroll() { /* ... */ };
    }
    public class USVacationPolicy extends VacationPolicy {
    @Override protected void alterForLegalMinimums() {
    // Логика для США
    }
    }
    public class EUVacationPolicy extends VacationPolicy {
    @Override protected void alterForLegalMinimums() {
    // Логика для Европы
    }
    }
    Субклассы «заполняют пробел» в обобщенном алгоритме accrueVacation
    ; они предоставляют только ту информацию, которая различается в специализиро- ванных версиях алгоритма .
    Выразительность
    Большинству читателей доводилось работать с запутанным кодом . Многие из них создавали запутанный код сами . Легко написать код, понятный для нас самих,
    204

    Выразительность
    205
    потому что в момент его написания мы глубоко понимаем решаемую проблему .
    У других программистов, которые будут заниматься сопровождением этого кода, такого понимания не будет .
    Основные затраты программного проекта связаны с его долгосрочным сопрово- ждением . Чтобы свести к минимуму риск появления дефектов в ходе внесения изменений, очень важно понимать, как работает система . С ростом сложности системы разработчику приходится разбираться все дольше и дольше, а вероят- ность того, что он поймет что-то неправильно, только возрастает . Следовательно, код должен четко выражать намерения своего автора . Чем понятнее будет код, тем меньше времени понадобится другим программистам, чтобы разобраться в нем . Это способствует уменьшению количества дефектов и снижению затрат на сопровождение .
    Хороший выбор имен помогает выразить ваши намерения . Имя класса или функ- ции должно восприниматься «на слух», а когда читатель разбирается в том, что делает класс, это не должно вызывать у него удивления .
    Относительно небольшой размер функций и классов также помогает выразить ваши намерения . Компактным классам и функциям проще присваивать имена; они легко пишутся и в них легко разобраться .
    Стандартная номенклатура также способствует выражению намерений автора .
    В частности, передача информация и выразительность являются важнейшими целями для применения паттернов проектирования . Включение стандартных названий паттернов (например, КОМАНДА или ПОСЕТИТЕЛЬ) в имена клас- сов, реализующих эти паттерны, помогает кратко описать вашу архитектуру для других разработчиков .
    Хорошо написанные модульные тесты тоже выразительны . Они могут рассматри- ваться как разновидность документации, построенная на конкретных примерах .
    Читая код тестов, разработчик должен составить хотя бы общее представление о том, что делает класс .
    И все же самое важное, что можно сделать для создания выразительного кода — это постараться сделать его выразительным . Как только наш код заработает, мы обычно переходим к следующей задаче, не прикладывая особых усилий к тому, чтобы код легко читался другими людьми . Но помните: следующим человеком, которому придется разбираться в вашем коде, с большой вероятностью окажетесь вы сами .
    Так что уделите немного внимания качеству исполнения своего продукта . Не- много поразмыслите над каждой функцией и классом . Попробуйте улучшить имена, разбейте большие функции на меньшие и вообще проявите заботу о том, что вы создали . Неравнодушие — воистину драгоценный ресурс .
    205

    206
    Глава 12 . Формирование архитектуры
    Минимум классов и методов
    Даже такие фундаментальные концепции, как устранение дубликатов, вырази- тельность кода и принцип единой ответственности, могут зайти слишком дале- ко . Стремясь уменьшить объем кода наших классов и методов, мы можем напло- дить слишком много крошечных классов и методов . Это правило рекомендует ограничиться небольшим количеством функций и классов .
    Многочисленность классов и методов иногда является результатом бессмыслен- ного догматизма . В качестве примера можно привести стандарт кодирования, который требует создания интерфейса для каждого без исключения класса . Или разработчиков, настаивающих, что поля данных и поведение всегда должны быть разделены на классы данных и классы поведения . Избегайте подобных догм, а в своей работе руководствуйтесь более прагматичным подходом .
    Наша цель — сделать так, чтобы система была компактной, но при этом одно- временно сохранить компактность функций и классов . Однако следует помнить, что из четырех правил простой архитектуры это правило обладает наименьшим приоритетом . Свести к минимуму количество функций и классов важно, одна- ко прохождение тестов, устранение дубликатов и выразительность кода все же важнее .
    Заключение
    Может ли набор простых правил заменить практический опыт? Нет, конечно .
    С другой стороны, правила, описанные в этой главе и в книге, представляют со- бой кристаллизованную форму многих десятилетий практического опыта ав- торов . Принципы простой архитектуры помогают разработчикам следовать по тому пути, который им пришлось бы самостоятельно прокладывать в течение многих лет .
    литература
    [XPE]: Extreme Programming Explained: Embrace Change, Kent Beck, Addison-
    Wesley, 1999 .
    [GOF]: Design Patterns: Elements of Reusable Object Oriented Software, Gamma et al ., Addison-Wesley, 1996 .
    206

    Многопоточность
    Бретт Л. Шухерт
    Объекты — абстракции для обработки данных .
    Программные потоки — абстракции для плани- рования .
    Джеймс О. Коплиен

    Написать чистую многопоточную программу трудно — очень трудно . Гораздо проще писать код, выполняемый в одном программном потоке . Многопоточный
    13
    207

    208
    Глава 13 . Многопоточность код часто выглядит нормально на первый взгляд, но содержит дефекты на более глубоком уровне . Такой код работает нормально до тех пор, пока система не за- работает с повышенной нагрузкой .
    В этой главе мы поговорим о том, почему необходимо многопоточное программи- рование и какие трудности оно создает . Далее будут представлены рекомендации относительно того, как справиться с этими трудностями и как написать чистый многопоточный код . В завершение главы рассматриваются проблемы тестиро- вания многопоточного кода .
    Чистый многопоточный код — сложная тема, по которой вполне можно было бы написать отдельную книгу . В этой главе приводится обзор, а более подробный учебный материал содержится в приложении «Многопоточность II» на с . 357 .
    Если вы хотите получить общее представление о многопоточности, этой главы будет достаточно . Чтобы разобраться в теме на более глубоком уровне, читайте вторую главу .
    Зачем нужна многопоточность?
    Многопоточное программирование может рассматриваться как стратегия устра- нения привязок . Оно помогает отделить выполняемую операцию от момента ее выполнения . В однопоточных приложениях «что» и «когда» связаны так сильно, что просмотр содержимого стека часто позволяет определить состояние всего приложения . Программист, отлаживающий такую систему, устанавливает точку прерывания (или серию точек прерывания) и узнает состояние системы на мо- мент остановки .
    Отделение «что» от «когда» способно кардинально улучшить как производитель- ность, так и структуру приложения . Со структурной точки зрения многопоточное приложение выглядит как взаимодействие нескольких компьютеров, а не как один большой управляющий цикл . Такая архитектура упрощает понимание си- стемы и предоставляет мощные средства для разделения ответственности .
    Для примера возьмем «сервлет», одну из стандартных моделей веб-приложений .
    Такие системы работают под управлением веб-контейнера или контейнера EJB, который частично управляет многопоточностью за разработчика . Сервлеты вы- полняются асинхронно при поступлении веб-запросов . Разработчику сервера не нужно управлять входящими запросами . В принципе каждый выполняемый экземпляр сервлета существует в своем замкнутом мире, отделенном от всех остальных экземпляров сервлетов .
    Конечно, если бы все было так просто, эта глава стала бы ненужной . Изоляция, обеспечиваемая веб-контейнерами, далеко не идеальна . Чтобы многопоточный код работал корректно, разработчики сервлетов должны действовать очень вни- мательно и осторожно . И все же структурные преимущества модели сервлетов весьма значительны .
    208

    Зачем нужна многопоточность?
    209
    Но структура — не единственный аргумент для многопоточного программиро- вания . В некоторых системах действуют ограничения по времени отклика и про- пускной способности, требующие ручного кодирования многопоточных решений .
    Для примера возьмем однопоточный агрегатор, который получает информацию с многих сайтов и объединяет ее в ежедневную сводку . Так как система работает в однопоточном режиме, она последовательно обращается к каждому сайту, всегда завершая получение информации до перехода к следующему сайту . Ежедневный сбор информации должен занимать менее 24 часов . Но по мере добавления новых сайтов время непрерывно растет, пока в какой-то момент на сбор всех данных не потребуется более 24 часов . Однопоточной реализации приходится подолгу ожидать завершения операций ввода/вывода в сокетах . Для повышения произ- водительности такого приложения можно было бы воспользоваться многопо- точным алгоритмом, параллельно работающим с несколькими сайтами .
    Или другой пример: допустим, система в любой момент времени работает только с одним пользователем, обслуживание которого у нее занимает всего одну секун- ду . При малом количестве пользователей система оперативно реагирует на все запросы, но с увеличением количества пользователей растет и время отклика . Ни- кто не захочет стоять в очереди после 150 других пользователей! Время отклика такой системы можно было бы улучшить за счет параллельного обслуживания многих пользователей .
    Или возьмем систему, которая анализирует большие объемы данных, но выдает окончательный результат только после их полной обработки . Наборы данных могут обрабатываться параллельно на разных компьютерах .
    Мифы и неверные представления
    Итак, существуют весьма веские причины для использования многопоточности .
    Но как говорилось ранее, написать многопоточную программу трудно . Необхо- димо действовать очень осторожно, иначе в программе могут возникнуть крайне неприятные ситуации . С многопоточностью связан целый ряд распространенных мифов и неверных представлений .
    
    Многопоточность всегда повышает быстродействие .
    Действительно, многопоточность иногда повышает быстродействие, но только при относительно большом времени ожидания, которое могло бы эффективно использоваться другими потоками или процессорами .
    
    Написание многопоточного кода не изменяет архитектуру программы.
    На самом деле архитектура многопоточного алгоритма может заметно отличать- ся от архитектуры однопоточной системы . Отделение «что» от «когда» обычно оказывает огромное влияние на структуру системы .
    
    При работе с контейнером (например, веб-контейнером или EJB-контейнером)
    разбираться в проблемах многопоточного программирования не обязательно.
    209

    210
    Глава 13 . Многопоточность
    В действительности желательно знать, как работает контейнер и как защититься от проблем одновременного обновления и взаимных блокировок, описанных позднее в этой главе .
    Несколько более объективных утверждений, относящихся к написанию много- поточного кода:
    
    Многопоточность сопряжена с определенными дополнительными затрата- ми — в отношении как производительности, так и написания дополнительного кода .
    
    Правильная реализация многопоточности сложна даже для простых задач .
    
    Ошибки в многопоточном коде обычно не воспроизводятся, поэтому они часто игнорируются как случайные отклонения
    1
    (а не как систематические дефекты, которыми они на самом деле являются) .
    
    Многопоточность часто требует фундаментальных изменений в стратегии проектирования .
    трудности
    Что же делает многопоточное программирование таким сложным? Рассмотрим тривиальный класс:
    public class X {
    private int lastIdUsed;
    public int getNextId() {
    return ++lastIdUsed;
    }
    }
    Допустим, мы создаем экземпляр
    X
    , присваиваем полю lastIdUsed значение 42, а затем используем созданный экземпляр в двух программных потоках . В обоих потоках вызывается метод getNextId()
    ; возможны три исхода:
    
    Первый поток получает значение 43, второй получает значение 44, в поле lastIdUsed сохраняется 44 .
    
    Первый поток получает значение 44, второй получает значение 43, в поле lastIdUsed сохраняется 44 .
    
    Первый поток получает значение 43, второй получает значение 43, поле las- tIdUsed содержит 43 .
    Удивительный третий результат
    2
    встречается тогда, когда два потока «перебива- ют» друг друга . Это происходит из-за того, что выполнение одной строки кода
    Java в двух потоках может пойти по разным путям, и некоторые из этих путей порождают неверные результаты . Сколько существует разных путей? Чтобы от- ветить на этот вопрос, необходимо понимать, как JIT-компилятор обрабатывает
    1
    Фазы Луны, космические лучи и т . д .
    2
    См . раздел «Копаем глубже» на с . 364 .
    210

    Защита от ошибок многопоточности
    211
    сгенерированный байт-код, и разбираться в том, какие операции рассматривают- ся моделью памяти Java как атомарные .
    В двух словах скажу, что в сгенерированном байт-коде приведенного фрагмента существует 12 870 разных путей выполнения
    1
    метода getNextId в двух программ- ных потоках . Если изменить тип lastIdUsed c int на long
    , то количество возмож- ных путей возрастет до 2 704 156 . Конечно, на большинстве путей выполнения вычисляются правильные результаты . Проблема в том, что на некоторых путях результаты будут неправильными .
    Защита от ошибок многопоточности
    Далее перечислены некоторые принципы и приемы, которые помогают защитить вашу систему от проблем многопоточности .
    Принцип единой ответственности
    Принцип единой ответственности (SRP) [PPP] гласит, что метод/класс/компо- нент должен иметь только одну причину для изменения . Многопоточные архи- тектуры достаточно сложны, чтобы их можно было рассматривать как причину изменения сами по себе, а следовательно, они должны отделяться от основного кода . К сожалению, подробности многопоточной реализации нередко встраива- ются в другой код . Однако разработчик должен учитывать ряд факторов:
    
    Код реализации многопоточности имеет собственный цикл разработки, мо- дификации и настройки .
    
    При написании кода реализации многопоточности возникают специфические сложности, принципиально отличающиеся от сложностей однопоточного кода
    (и часто превосходящие их) .
    
    Количество потенциальных сбоев в неверно написанном многопоточном коде достаточно велико и без дополнительного бремени в виде окружающего кода приложения .
    Рекомендация: отделяйте код, относящийся к реализации многопоточности, от
    остального кода
    2
    Следствие: ограничивайте область
    видимости данных
    Как было показано ранее, два программных потока, изменяющих одно поле обще- го объекта, могут мешать друг другу, что приводит к непредвиденному поведению .
    Одно из возможных решений — защита критической секции кода, в которой про-
    1
    См . раздел «Пути выполнения» на с . 262 .
    2
    См . раздел «Пример архитектуры «клиент/сервер»» на с . 357 .
    211

    1   ...   20   21   22   23   24   25   26   27   ...   49


    написать администратору сайта