Код с запашком
Код с запашком — это распространённые дефекты дизайна программного обеспечения, обозначающие плохие практики проектирования, которые зачастую приводят к ошибкам и проблемам сопровождения[1][2]. Такие дефекты часто возникают вследствие неудачных решений при реализации или проектировании и ведут к усложнению исходного кода, его поддержки и доработки. В отличие от антипаттернов, «код с запашком» не обязательно является ошибкой — такие конструкции могут продолжать существовать в программе длительное время без непосредственного влияния на её функциональность. Для устранения «запаха кода» используют рефакторинг — изменение кода программы без изменения её внешнего поведения. Показателями наличия кода с запашком служат такие качества, как читаемость кода, простота его модификации, способность к развитию с изменением требований, повторное использование в разных контекстах, тестируемость и надёжность[3].
Примеры кода с запашком
Подобно шаблонам проектирования, в научной литературе описано множество типовых примеров кода с запашком. Один из самых известных списков был составлен Мартином Фаулером с рекомендациями по подходящему рефакторингу[4].
Антипаттерн «Дублирование кода»[4] — классический пример кода с запашком, выражающийся в наличии одинаковых фрагментов кода в разных местах приложения. Дублирование бывает и внутри одного класса (разных методов), и между разными классами.
Антипаттерн «Зависть к данным»[4] — ситуация, когда метод активно обращается к методам и данным других классов, чаще всего — к геттерам, чтобы получить нужные данные. Это может указывать на то, что метод находится не в том классе.
Также известен под названиями «божественный класс» или «Winnebago»; антипаттерн «Blob» — это класс, чрезмерно концентрирующий ответственность, обладающий множеством полей и зависящий от большого числа других классов с большими наборами данных.
Антипаттерн «Длинный список параметров»[4] — пережиток ранней эры процедурного программирования, когда функциям передавалось множество параметров вместо использования глобальных переменных. В современных ООП-языках такой подход ухудшает читаемость и затрудняет рефакторинг.
Реализация чрезмерно длинных методов — типичная ошибка начинающих разработчиков. Такие методы излишне сложны, хотя их можно и нужно декомпозировать[4] — разбиение упрощает чтение и последующую поддержку.
Антипаттерн «Large Class»[4] подразумевает класс с большим количеством переменных. Это часто приводит к обособленному использованию разных переменных и порождает дублирование кода.
Выявление и устранение кода с запашком
Для каждого типа кода с запашком существуют более или менее формализованные способы обнаружения, а также практики для устранения или предотвращения ошибки. Ниже представлены методы выявления и рекомендации по исправлению для шести наиболее распространённых случаев.
Обнаружение кода типа «Duplicated Code» заключается в оценке процента дублированных строк в программе. Простое полное совпадение выявляется легко, но дублирование часто завуалировано переименованиями переменных или небольшими изменениями.
Например, в языке Java:
Integer var = 5;
String str = String.valueOf(var);
является дублированием кода по отношению к:
Integer nombre = 5;
String chaine = String.valueOf(nombre);
Чтобы не упустить такие случаи, алгоритм должен учитывать возможные переименования и незначительные отличия. Также встречается случай, когда два разных алгоритма решают одну и ту же задачу; такую ошибку часто допускают новички при копировании кода. Это не только увеличивает объём программы, но и усложняет сопровождение. Исправление — вынесение общего кода в отдельный метод и использование его вместо дубликатов. Для предотвращения подобных ошибок важно:
- Факторизовать код на максимально ранних этапах.
- Работать по принципам разработки через тестирование.
Запах кода «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-объектов рекомендуется:
- Использовать принцип «разделяй и властвуй»;
- Активно применять абстракцию и наследование.
Исправление предполагает реорганизацию кода и разделение обязанностей:
- Переместить методы, не относящиеся по смыслу к данному классу, в корректные классы;
- Убрать нелогичные поля и размещать их в соответствующих классах;
- Устранить ненужные связи между классами.
Обнаружение производится с помощью метрик[8]:
DECOR определяет BLOB через величину LCOM5 (слабая связность методов, Lack of Cohesion Of Methods) — если она превышает 20, а число методов и полей в классе больше 20, а также по подозрительным названиям классов (Process, Control, Manage, System и др.)[9][10].
Для дальнейшего анализа применяется кластеризация (разделение на кластеры по метрике Жаккара), например как реализовано в JDeodorant и других инструментах[11].
Антипаттерн возникает в случаях, когда метод/класс перегружен параметрами, обычно вследствие объединения нескольких алгоритмов или чрезмерного усложнения интерфейсов. Корректировка заключается в:
- Замене группы параметров на передачу объекта;
- Сокращении числа параметров (
Mesures mesuresвместо трёх отдельных аргументов).
Инструменты анализа, такие как PMD и Checkstyle, обычно устанавливают порог на максимально допустимое количество параметров (10 и 7 соответственно).
Запах «Long Method» (слишком длинный метод) встречается при отсутствии декомпозиции. Его профилактика — TDD, разбиение на небольшие функции, повышение читаемости. Для обнаружения используют не только число строк, но и такие показатели, как цикломатическая сложность и метрики Хэлстеда[12]. Многие инструменты считают длинной функцию в 100–150 строк и более.
Запах «Large Class» — класс, сочетающий в себе слишком много обязанностей. NASA Software Assurance Technology Center публиковал рекомендуемые пороги:
- меньше 20 методов в классе;
- суммарная «масса» методов (длина) — не более 100;
- суммарное число методов и вызываемых ими функций — не более 100;
- число классов, на которые ссылается текущий, — не более 5.
PMD и Checkstyle считают класс крупным при превышении 1000–2000 строк. Исправляется проблема выделением отдельных классов для локальных обязанностей.
Существует ряд инструментов, реализующих автоматическое выявление кода с запашком.
DECOR[9] (Detection&Correction) — комплекс, выполняющий выявление и автоматическую корректировку дефектов. Процесс построен как система правил на специальном языке высокого уровня, задаваемых пользователем для поиска и (опционально) исправления. DECOR фиксирует 19 различных паттернов, включая Blob, Long Method и Large Class[13].
Коммерческое решение, базирующееся на платформе iPlasma; работает с Java, C, C++. Определяет более 20 дефектов, среди которых Duplicated Code, Blob, Long Method и Large Class.
Плагин для Eclipse, созданный в Университете Конкордии (Канада) и Университете Македонии (Греция)[14]. Позволяет находить и устранять основные дефекты типа Feature Envy, Type-Checking, Long Method, Blob, Duplicated Code[15].
Stench Blossom[16] — инструмент, предлагающий визуальное представление анализа, где каждый вид дефекта отмечен в виде «лепестков». Чем больше проблема, тем больше лепесток.
Плагин для Eclipse, распознающий 8 разновидностей дефектов в Java-коде.
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.


