Код с запашком

Код с запашком — это распространённые дефекты дизайна программного обеспечения, обозначающие плохие практики проектирования, которые зачастую приводят к ошибкам и проблемам сопровождения[1][2]. Такие дефекты часто возникают вследствие неудачных решений при реализации или проектировании и ведут к усложнению исходного кода, его поддержки и доработки. В отличие от антипаттернов, «код с запашком» не обязательно является ошибкой — такие конструкции могут продолжать существовать в программе длительное время без непосредственного влияния на её функциональность. Для устранения «запаха кода» используют рефакторинг — изменение кода программы без изменения её внешнего поведения. Показателями наличия кода с запашком служат такие качества, как читаемость кода, простота его модификации, способность к развитию с изменением требований, повторное использование в разных контекстах, тестируемость и надёжность[3].

Примеры кода с запашком

Подобно шаблонам проектирования, в научной литературе описано множество типовых примеров кода с запашком. Один из самых известных списков был составлен Мартином Фаулером с рекомендациями по подходящему рефакторингу[4].

Дублирование кода (Duplicated Code)

Антипаттерн «Дублирование кода»[4] — классический пример кода с запашком, выражающийся в наличии одинаковых фрагментов кода в разных местах приложения. Дублирование бывает и внутри одного класса (разных методов), и между разными классами.

Зависть к данным (Feature Envy)

Антипаттерн «Зависть к данным»[4] — ситуация, когда метод активно обращается к методам и данным других классов, чаще всего — к геттерам, чтобы получить нужные данные. Это может указывать на то, что метод находится не в том классе.

Глобальный объект (BLOB)

Также известен под названиями «божественный класс» или «Winnebago»; антипаттерн «Blob» — это класс, чрезмерно концентрирующий ответственность, обладающий множеством полей и зависящий от большого числа других классов с большими наборами данных.

Длинный список параметров (Long Parameter List)

Антипаттерн «Длинный список параметров»[4] — пережиток ранней эры процедурного программирования, когда функциям передавалось множество параметров вместо использования глобальных переменных. В современных ООП-языках такой подход ухудшает читаемость и затрудняет рефакторинг.

Длинный метод (Long Method)

Реализация чрезмерно длинных методов — типичная ошибка начинающих разработчиков. Такие методы излишне сложны, хотя их можно и нужно декомпозировать[4] — разбиение упрощает чтение и последующую поддержку.

Крупный класс (Large Class)

Антипаттерн «Large Class»[4] подразумевает класс с большим количеством переменных. Это часто приводит к обособленному использованию разных переменных и порождает дублирование кода.

Выявление и устранение кода с запашком

Методы обнаружения

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

Дублирование кода (Duplicated Code)

Обнаружение кода типа «Duplicated Code» заключается в оценке процента дублированных строк в программе. Простое полное совпадение выявляется легко, но дублирование часто завуалировано переименованиями переменных или небольшими изменениями.

Например, в языке Java:

Integer var = 5;
String str = String.valueOf(var);

является дублированием кода по отношению к:

Integer nombre = 5;
String chaine = String.valueOf(nombre);

Чтобы не упустить такие случаи, алгоритм должен учитывать возможные переименования и незначительные отличия. Также встречается случай, когда два разных алгоритма решают одну и ту же задачу; такую ошибку часто допускают новички при копировании кода. Это не только увеличивает объём программы, но и усложняет сопровождение. Исправление — вынесение общего кода в отдельный метод и использование его вместо дубликатов. Для предотвращения подобных ошибок важно:

Зависть к данным (Feature Envy)

Запах кода «Feature Envy» (буквально «зависть к функционалу») проявляется, когда метод обращается к элементам других классов чаще, чем к своим собственным. Исправить это можно несколькими способами:

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

Например, в классе Rectangle метод perimetre() может быть логично перемещён в другой класс:

public class Rectangle {
   ...
   public Double perimetre() {
        return 2 * AutreClasse.largeur + 2 * AutreClasse.longueur;
   }
}
  • Вынести часть метода, если она активнее использует данные другого класса. Например, если метод в классе лодка preparer_bateau() преимущественно манипулирует заглушкой SPI:
public class Bateau {
   private SPI spi;
   private Reserve reserve;
   ...
   public void preparer_bateau() {
        this.laver();
        this.verifier_coque();
        reserve.verifier_provisions();
        spi.regler_hauteur();
        spi.regler_largeur();
        spi.verifier_voile();
   }
}

Корректная практика — внедрить новый метод в SPI, который агрегирует нужные действия, и вызывать его из preparer_bateau().

Для обнаружения таких случаев оценивается так называемая степень связанности методов с внешними классами. Lanza и Marinescu предложили специальную метрику[5]:

Где:

  • LAA (Locality of Attribute Accesses) — доля методов, использующих больше атрибутов сторонних классов, чем собственных;
  • FDP (Foreign Data Providers) — доля атрибутов, предоставляемых другими классами и редко используемых ещё где-либо;
  • ATFD (Access to Foreign Data) — доля методов, которые получают доступ к чужим данным.

Реализации этой методики есть в инструментах iPlasma, inFusion, JDeodorant и др[6]. Плагин Eclipse Metrics[7] также помогает обнаруживать такие случаи.

Глобальный объект (BLOB)

Эта ошибка связана с нежеланием или неумением проектировать сложные архитектуры. Она приводит к снижению скорости и ухудшению сопровождаемости. Для профилактики BLOB-объектов рекомендуется:

  • Использовать принцип «разделяй и властвуй»;
  • Активно применять абстракцию и наследование.

Исправление предполагает реорганизацию кода и разделение обязанностей:

  • Переместить методы, не относящиеся по смыслу к данному классу, в корректные классы;
  • Убрать нелогичные поля и размещать их в соответствующих классах;
  • Устранить ненужные связи между классами.

Обнаружение производится с помощью метрик[8]:

DECOR определяет BLOB через величину LCOM5 (слабая связность методов, Lack of Cohesion Of Methods) — если она превышает 20, а число методов и полей в классе больше 20, а также по подозрительным названиям классов (Process, Control, Manage, System и др.)[9][10].

Для дальнейшего анализа применяется кластеризация (разделение на кластеры по метрике Жаккара), например как реализовано в JDeodorant и других инструментах[11].

Длинный список параметров (Long Parameter List)

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

  • Замене группы параметров на передачу объекта;
  • Сокращении числа параметров (Mesures mesures вместо трёх отдельных аргументов).

Инструменты анализа, такие как PMD и Checkstyle, обычно устанавливают порог на максимально допустимое количество параметров (10 и 7 соответственно).

Длинный метод (Long Method)

Запах «Long Method» (слишком длинный метод) встречается при отсутствии декомпозиции. Его профилактика — TDD, разбиение на небольшие функции, повышение читаемости. Для обнаружения используют не только число строк, но и такие показатели, как цикломатическая сложность и метрики Хэлстеда[12]. Многие инструменты считают длинной функцию в 100–150 строк и более.

Крупный класс (Large Class)

Запах «Large Class» — класс, сочетающий в себе слишком много обязанностей. NASA Software Assurance Technology Center публиковал рекомендуемые пороги:

  • меньше 20 методов в классе;
  • суммарная «масса» методов (длина) — не более 100;
  • суммарное число методов и вызываемых ими функций — не более 100;
  • число классов, на которые ссылается текущий, — не более 5.

PMD и Checkstyle считают класс крупным при превышении 1000–2000 строк. Исправляется проблема выделением отдельных классов для локальных обязанностей.

Автоматические средства

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

DECOR

DECOR[9] (Detection&Correction) — комплекс, выполняющий выявление и автоматическую корректировку дефектов. Процесс построен как система правил на специальном языке высокого уровня, задаваемых пользователем для поиска и (опционально) исправления. DECOR фиксирует 19 различных паттернов, включая Blob, Long Method и Large Class[13].

inFusion

Коммерческое решение, базирующееся на платформе iPlasma; работает с Java, C, C++. Определяет более 20 дефектов, среди которых Duplicated Code, Blob, Long Method и Large Class.

JDeodorant

Плагин для Eclipse, созданный в Университете Конкордии (Канада) и Университете Македонии (Греция)[14]. Позволяет находить и устранять основные дефекты типа Feature Envy, Type-Checking, Long Method, Blob, Duplicated Code[15].

Stench Blossom

Stench Blossom[16] — инструмент, предлагающий визуальное представление анализа, где каждый вид дефекта отмечен в виде «лепестков». Чем больше проблема, тем больше лепесток.

Плагин для Eclipse, распознающий 8 разновидностей дефектов в Java-коде.

HIST

HIST (Historical Information for Smell deTection)[17] — инструмент, ориентированный на анализ истории изменений в системах контроля версий. Он выявляет закономерности частых изменений проблемных классов (например, BLOB) при рефакторинге. HIST обнаруживает Divergent Change, Shotgun Surgery, Parallel Inheritance, Blob, Feature Envy. По сравнению с другими средствами, обеспечивает более высокую точность, но требует большего количества исходных данных и работы с историей изменений[18].

Прочие

Существуют и другие инструменты, такие как Checkstyle, PMD.

Примечания

Литература

  • Fowler, Martin. Refactoring: Improving the Design of Existing Code. Addison-Wesley, 1999.
  • Lanza, Michele; Marinescu, Radu. Object-oriented metrics in practice: using software metrics to characterize, evaluate, and improve the design of object-oriented systems. Springer Science & Business Media, 2007.
  • Palomba, Fabio; Bavota, Gabriele; Oliveto, Rocco; De Luzia, Andrea. Anti-Pattern Detection: Methods, Challenges and Open Issues, Advances in Computers, Chapter 4, 2014, т. 95.
  • Moha, Naouel; Guéhéneuc, Yann-Gaël; Duchien, Laurence; Le Meur, Anne-Françoise. DECOR: A Method for the Specification and Detection of Code and Design Smells. IEEE Transactions on Software Engineering, 2010, т. 36, № 1, с. 20–36, doi: 10.1109/TSE.2009.50.
  • Fokaefs, Marios; Tsantalis, Nikolaos; Chatzigeorgiou, Alexander; Sander, Jörg. Decomposing object-oriented class modules using an agglomerative clustering technique. IEEE International Conference on Software Maintenance, 2009, с. 93–101, doi: 10.1109/ICSM.2009.5306332.
  • Marinescu, Cristina; Marinescu, Radu; Mihancea, Petru Florin; Ratiu, Daniel; Wettel, Richard. iplasma: An integrated platform for quality assessment of object-oriented design. International Conference on Software Maintenance, 2005, с. 77–80.
  • Tsantalis, Nikolaos; Chaikalis, Théodoros; Chatzigeorgiou, Alexander. JDeodorant: Identification and Removal of Type-Checking Bad Smells. IEEE European Conference on Software Maintenance and Reengineering, 2009, с. 329–331, doi: 10.1109/CSMR.2008.4493342.
  • Murphy-Hill, Emerson; Black, Andrew P. Decomposing object-oriented class modules using an agglomerative clustering technique. IEEE International Symposium on Software Visualization, 2010, с. 5–14, doi: 10.1145/1879211.1879216.
  • Palomba, Fabio; Oliveto, Rocco; Bavota, Gabriele; De Lucia, Andrea; Di Penta, Massimiliano; Poshyvanyk, Denys. Detecting Bad Smells in Source Code using Change History Information. IEEE International Conference on Automated Software Engineering, 2013, с. 268–278, doi: 10.1109/ASE.2013.6693086.
  • Palomba, Fabio; Oliveto, Rocco; Bavota, Gabriele; De Lucia, Andrea; Di Penta, Massimiliano; Poshyvanyk, Denys. Mining Version Histories for Detecting Code Smells. IEEE Transactions on Software Engineering, 2015, т. 41, с. 462–489, doi: 10.1109/TSE.2014.2372760.
  • Mann, Ankita; Dalal, Sandeep; Chhillar, Dhreej. An Effort to Improve Cohesion Metrics Using Inheritance. International Journal of Engineering and Advanced Technology, 2013, т. 2.
  • Fontana, Francesca Arcelli; Braione, Pietro; Zanoni, Marco. Automatic detection of bad smells in code: An experimental assessment. Journal of Object Technology, 2012, т. 11.
  • Walter, Bartosz; Martenka, Pawel. Looking for Patterns in Code Bad Smells Relations. IEEE International Conference on Software Testing, Verification and Validation Workshops, 2011, с. 465–466, doi: 10.1109/ICSTW.2011.89.
  • Mäntylä, M.V. An experiment on subjective evolvability evaluation of object-oriented software: explaining factors and interrater agreement. IEEE International Symposium on Empirical Software Engineering, 2005, doi: 10.1109/ISESE.2005.1541837.