Общие советы по рефакторингу кода

Java
Общие советы по рефакторингу кода

О рефакторинге

Зачем проводить рефакторинг

1_代码重构漫画.jpeg

Поскольку проект продолжает развиваться, код продолжает накапливаться. Если никто не отвечает за качество кода, код всегда будет развиваться во все более запутанном направлении. Когда хаос достигает определенного уровня, количественные изменения вызывают качественные изменения, а стоимость сопровождения проекта уже выше, чем стоимость разработки нового набора кода.Хочешь провести рефакторинг, никто этого не сделает.

Причины этого чаще всего следующие:

  1. Отсутствие эффективного дизайна перед кодированием
  2. Соображения стоимости в исходном программировании стекирования функций
  3. Отсутствие эффективного механизма контроля качества кода

Для таких проблем в отрасли уже есть хорошее решение: убрать «неприятный запах» в коде за счет непрерывного рефакторинга.

что такое рефакторинг

Мартин Фаулер, автор книги «Рефакторинг», дает такое определение рефакторинга:

Рефакторинг (существительное): корректировка внутренней структуры программного обеспечения с целью улучшения его понятности и снижения стоимости модификации без изменения наблюдаемого поведения программного обеспечения.
Рефакторинг (глагол): использование ряда методов рефакторинга для корректировки структуры программного обеспечения без изменения его наблюдаемого поведения.

По масштабу рефакторинга его можно условно разделить на большой рефакторинг и малый рефакторинг:

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

небольшой рефакторинг: Рефакторинг деталей кода, в основном для рефакторинга классов, функций, переменных и т. д. на уровне кода, например, стандартизация именования и комментариев, удаление очень больших классов или функций, извлечение повторяющегося кода и т. д. Небольшие рефакторинги больше связаны с использованием единых соглашений о кодировании. Этот тип рефакторинга должен быть изменен относительно концентрированным и простым способом, с высокой работоспособностью, меньшими затратами времени и относительно меньшим риском внесения ошибок. Когда проводить рефакторинг Когда разрабатываются новые функции, исправляются ошибки или появляются «плохие запахи кода» в обзорах кода, мы должны вовремя проводить рефакторинг. Продолжение небольших рефакторингов в повседневной разработке может снизить стоимость рефакторинга и тестирования.

плохой запах кода

2_代码常见问题.png

  • дублирование кода
    • Логика реализации та же, и процесс выполнения тот же
  • метод слишком длинный
    • Операторы в методе не находятся на одном уровне абстракции
    • Логика сложна для понимания и требует много комментариев
    • Процедурное программирование не объектно-ориентированное
  • негабаритный класс
    • Классы делают слишком много
    • Содержит слишком много переменных и методов экземпляра
    • Названия класса недостаточно, чтобы описать, что делается
  • логическая дисперсия
    • Дивергентное изменение: класс часто меняется в разных направлениях по разным причинам.
    • Модификации дробовика: Когда происходит определенное изменение, модификации должны быть сделаны в нескольких классах.
  • тяжелая сложная привязанность
    • Слишком много методов одного класса используют члены других классов
  • Data Mud/Basic Type Paranoia
    • Два класса, сигнатуры методов содержат одно и то же поле или параметр
    • Должны использоваться классы, но примитивные типы, такие как Money для чисел и валют и Range для начального и конечного значений.
  • неразумная система наследования
    • Наследование нарушает инкапсуляцию, и подклассы полагаются на детали реализации определенных функций в своих родительских классах.
    • Подклассы должны развиваться вместе с обновлениями своего суперкласса, если только суперкласс не разработан специально для расширения и хорошо документирован.
  • Слишком много условных суждений
  • Слишком длинный столбец параметров
  • Слишком много временных переменных
  • запутанное временное поле
    • Переменная экземпляра устанавливается только для конкретной ситуации
    • Извлечь переменные экземпляра и соответствующие методы в новый класс
  • чистый класс данных
    • Содержит только поля и методы для доступа (чтения и записи) к этим полям.
    • Этот класс называется контейнером данных и должен поддерживать минимальную изменчивость.
  • неуместное название
    • Название не точно описывает, что делать
    • Именование не соответствует правилам разговорных имен.
  • Слишком много комментариев или устаревшие комментарии

Проблема с плохим кодом

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

что такое хороший код

3_代码质量如何衡量.jpg

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

Чтобы писать высококачественный код, нам необходимо овладеть некоторыми более подробными и практичными методологиями программирования, включая идеи объектно-ориентированного проектирования, принципы проектирования, шаблоны проектирования, стандарты кодирования и методы рефакторинга.

Как провести рефакторинг

ТВЕРДЫЕ принципы

4_SOLID原则.png

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

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

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

принцип открыто-закрыто

Добавление новой функции должно осуществляться путем расширения кода (добавления модулей, классов, методов, свойств и т. д.) на основе существующего кода, а не модификации существующего кода (модификации модулей, классов, методов, свойств и т. д.). ) Готово.

Принцип открытости-закрытости означает не полное исключение модификаций, а завершение разработки новых функций с минимальными затратами на модификацию кода.

Многие принципы проектирования, идеи проектирования и шаблоны проектирования направлены на улучшение масштабируемости кода. В частности, большинство из 23 классических шаблонов проектирования сведены к решению проблемы масштабируемости кода и руководствуются принципом открытости-закрытости. Методы, наиболее часто используемые для улучшения расширяемости кода: полиморфизм, внедрение зависимостей, программирование на основе интерфейсов, а не реализаций, и большинство шаблонов проектирования (например, декорации, стратегии, шаблоны, цепочки ответственности, состояние).

Принцип замены Лисков

Объекты подкласса (объект подтипа/производного класса) могут заменить любое место, где объект родительского класса (объект базового/родительского класса) появляется в программе (программе), и обеспечивать логическое поведение (поведение) и корректность исходной программы остаются неизменными.

Подклассы могут расширять функции суперкласса, но не могут изменять исходные функции суперкласса.

Любые хорошо реализованные методы в родительском классе (в отличие от абстрактных методов) на самом деле устанавливают ряд спецификаций и контрактов, хотя это не требует, чтобы все подклассы соответствовали этим контрактам, но если подкласс Любая модификация этих не- абстрактные методы нанесут ущерб всей системе наследования.

Принцип разделения интерфейса

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

Принцип инверсии зависимости

Модули высокого уровня не должны зависеть от модулей низкого уровня, оба должны зависеть от своих абстракций; абстракции не должны зависеть от деталей, а детали должны зависеть от абстракций.

Закон Деметры

Объект должен иметь минимальные знания о других объектах

Принцип синтетического повторного использования

Попробуйте использовать композицию/агрегацию вместо наследования.

Принцип единой ответственности говорит нам реализовать класс с одной ответственностью; принцип подстановки Лисков говорит нам не разрушать систему наследования; принцип инверсии зависимостей говорит нам программировать для интерфейсов; принцип разделения интерфейса говорит нам упрощать и объединять, когда проектирование интерфейсов; закон говорит нам уменьшить связанность. Принцип открытого-закрытого — это общая схема, говорящая нам быть открытыми для расширения и закрытыми для модификации.

Шаблоны проектирования

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

  • Творчество: в основном решить проблему создания объекта, инкапсулировать сложный процесс создания, разделить код создания объекта и код использования.
  • структурный: Разделение связи различных функций в основном за счет различных комбинаций классов или объектов.
  • поведенческий: Основное решение — объединение поведения взаимодействия между классами или объектами.
тип модель инструкция Применимая сцена
Творчество синглтон Класс позволяет создать только один экземпляр или объект и предоставляет ему глобальную точку доступа. Доступ к ресурсам без сохранения состояния/уникальный в глобальном масштабе/контролируемый доступ к ресурсам
фабрика Создайте один или несколько связанных объектов, не заботясь о конкретном классе реализации. Создание и использование отдельных объектов
строитель Используется для создания сложных объектов одного типа, «настраиваемых» путем установки различных необязательных параметров. Объекты имеют много параметров построения, и большинство из них являются необязательными.
прототип Создайте новый объект, скопировав существующий объект Объекты дорого создавать и разные объекты одного и того же класса раньше не сильно отличались
структурный играет роль Добавьте функциональность к исходному классу, введя прокси-класс без изменения исходного класса и без использования наследования. Увеличьте доступ к прокси, такой как мониторинг, кэширование, ограничение тока, транзакции, RPC
декоратор Динамически расширяйте функциональность исходного класса за счет композиции без изменения исходного класса и без использования наследования. Динамически расширять функциональность класса
адаптер Не изменяя исходный класс, адаптируйте его к новому интерфейсу, объединив Повторно использовать существующий класс, но он не соответствует ожидаемому интерфейсу.
наведение мостов Когда класс имеет несколько независимо изменяющихся измерений, его можно независимо расширить, комбинируя Когда существует несколько измерений наследования
фасад Определите интерфейс более высокого уровня для набора интерфейсов в подсистеме, упрощая использование подсистемы. Устранение противоречия между возможностью повторного использования интерфейса (мелкозернистый) и простотой использования интерфейса (грубый)
комбинация Объединяйте объекты в древовидные структуры для представления иерархий часть-целое, унифицируя логику обработки для отдельных пар и комбинированных объектов. Удовлетворить древовидную структуру части и целого
Наилегчайший вес Эффективно поддерживать большое количество мелких объектов, используя методы совместного использования. При большом количестве объектов в системе многие поля этих объектов имеют фиксированный диапазон значений
поведенческий наблюдатель Несколько наблюдателей слушают один и тот же предметный объект и уведомляют всех наблюдателей, когда состояние предметного объекта изменяется, позволяя им автоматически обновлять себя. Разделение создателей и получателей событий
шаблон Определить скелет алгоритма в операции, отложив реализацию определенных шагов до подклассов. Решение проблем повторного использования и расширения
Стратегия Определите набор классов алгоритмов, инкапсулируйте каждый алгоритм отдельно, чтобы их можно было заменить друг другом Устранение различных суждений ветвления if-else
Определение, создание и использование стратегий разделения
государство Позволяет объекту изменять свое поведение при изменении его внутреннего состояния Разделение состояния и поведения объекта
цепь ответственности Объединяет набор объектов в цепочку, и запросы передаются по цепочке до тех пор, пока объект не сможет их обработать. Разделение отправителя и получателя запроса
итератор Предоставляет способ последовательного доступа к элементам объекта коллекции без раскрытия внутреннего представления объекта. Отделите внутреннее представление объектов коллекции от доступа к обходу
посетитель Инкапсулирует некоторые операции, воздействующие на элементы в определенной структуре данных, и определяет новые операции, воздействующие на эти элементы без изменения структуры данных. Разделение структуры данных и поведения объектов
меморандум При условии, что не нарушается принцип инкапсуляции, захватить внутреннее состояние объекта и сохранить это состояние вне объекта, чтобы объект можно было восстановить в прежнее состояние позже. Для резервного копирования и восстановления объектов
Заказ Инкапсулируйте различные запросы в соответствующие объекты команд, контролируйте выполнение команд и будьте прозрачными для пользователя Используется для управления выполнением команд, таких как асинхронность, задержка, очередь, отмена, сохранение и отмена.
устный переводчик определить представление грамматики для языка и определить интерпретатор для обработки этой грамматики Используется в определенных сценариях, таких как компиляторы, механизмы правил, регулярные выражения и т. д.
посредник Определите отдельный промежуточный объект, чтобы инкапсулировать взаимодействие между набором объектов и избежать прямого взаимодействия между объектами. Делает отдельные объекты слабо связанными, не требуя явных ссылок друг на друга.

наслоение кода

image.png

Структура модуля Описание

  • server_main: уровень конфигурации, отвечающий за управление модулями всего проекта, управление конфигурацией maven, управление ресурсами и т. д.;

  • server_application: уровень доступа к приложению, который берет на себя ввод внешнего трафика, такого как: реализация интерфейса RPC, обработка сообщений, задачи синхронизации и т. д. Не включайте сюда бизнес-логику;

  • server_biz: основной бизнес-уровень, сервисы вариантов использования, объекты домена, события домена и т. д.

  • server_irepository: уровень интерфейса ресурсов, отвечающий за раскрытие интерфейсов ресурсов.

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

  • server_common: общий уровень, vo, инструменты и т. д.

При разработке кода следует следовать спецификациям каждого уровня и обращать внимание на зависимости между уровнями.

Соглашения об именах

Хорошее имя должно удовлетворять следующим двум ограничениям:

  • точно опишите что было сделано
  • Формат соответствует общепринятым соглашениям

Если вы чувствуете, что классу или методу трудно дать имя, возможно, он несет слишком много функций и нуждается в дальнейшем разделении.

соглашение

Сцены Сильные ограничения Пример
Имя элемента Все строчные буквы, несколько слов, разделенных тире '-' spring-cloud
Имена пакетов все в нижнем регистре com.alibaba.fastjson
имя класса/имя интерфейса Сделать заглавной первую букву слова ParserConfig,DefaultFieldDeserializer
имя переменной Первая буква строчная. При формировании нескольких слов, кроме первого слова, первая буква других слов должна быть заглавной. password, userName
постоянное имя Все слова в верхнем регистре, несколько слов, разделенных символом '_' CACHE_EXPIRED_TIME
метод Та же переменная read(), readObject(), getById()

именование классов

В именах классов используется верблюжий регистр, а в именах классов обычно используются существительные или именные словосочетания. В дополнение к существительным и словосочетаниям в именах интерфейсов также могут использоваться прилагательные или прилагательные, такие как Cloneable, Callable и т. д., чтобы указать, что класс, реализующий интерфейс, имеет определенную функцию или возможность.

Сцены ограничение Пример
абстрактный класс Начните с абстрактного или базового BaseUserService
перечисляемый класс Перечисление как суффикс GenderEnum
Инструменты Утилита как суффикс StringUtils
класс исключений Исключение заканчивается RuntimeException
класс реализации интерфейса имя интерфейса + реализация UserServiceImpl
Классы, связанные с шаблоном проектирования Строитель, Фабрика и т.д. При использовании шаблона проектирования необходимо использовать соответствующий шаблон проектирования в качестве суффикса, например ThreadFactory.
Классы, которые обрабатывают определенные функции Обработчик, Предикат, Валидатор Представляет процессоры, валидаторы, утверждения, эти фабрики классов также имеют соответствующие имена методов, такие как дескриптор, предикат, проверка
класс на определенном уровне Контроллер, Сервис, ServiceImpl, суффикс Dao UserController, UserServiceImpl, UserDao
объект ценности на определенном уровне АО, Парам, Во, Конфиг, Сообщение Param вызывает входные параметры, Ao возвращает результат для экономии, Vo общее значение объекта;
Класс конфигурации Config; сообщение — это сообщение MQ
тестовый класс Конец теста UserServiceTest, который используется для тестирования класса UserService.

именование методов

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

Сцены ограничение Пример
вернуть истинное значение is/can/has/needs/should isValid/canRemove
на проверку ensure/validate ensureCapacity/validateInputs
Выполнять по требованию IfNeeded/try/OrDefault/OrElse drawIfNeeded/tryCreate/getOrDefault
связанные с данными get/search/save/update/batchSave/
batchUpdate/saveOrUpdateselect
/insert/update/delete
getUserById/searchUsersByCreateTime
жизненный цикл initialize/pause/stop/destroy initialize/pause/onPause/stop/onStop
общие пары глаголов разделить/объединить, вводить/извлекать, связывать/разделять,
увеличение/уменьшение, запуск/запуск, наблюдение/прослушивание, сборка/публикация,
кодирование/декодирование, отправка/фиксация, push/pull, ввод/выход,
развернуть/свернуть, кодировать/декодировать

Советы по рефакторингу

Метод уточнения

Код нескольких методов дублируется, код в методе слишком длинный или операторы в методе не находятся на одном уровне абстракции.
Метод — это наименьшая степень детализации повторного использования кода. Слишком длинный метод не способствует повторному использованию, а его читабельность низкая. Уточнение метода часто является первым шагом в работе по рефакторингу.

программирование с намерением: Отделите процесс обработки чего-либо от способа его реализации.

  • Разложите проблему на ряд функциональных шагов, предполагая, что эти функциональные шаги были реализованы.
  • Мы можем решить эту проблему, просто сгруппировав функции вместе
  • После организации всей функции мы реализуем каждую функцию метода отдельно
/** 
  * 1、交易信息开始于一串标准ASCII字符串。 
  * 2、这个信息字符串必须转换成一个字符串的数组,数组存放的此次交易的领域语言中所包含的词汇元素(token)。 
  * 3、每一个词汇必须标准化。 
  * 4、包含超过150个词汇元素的交易,应该采用不同于小型交易的方式(不同的算法)来提交,以提高效率。 
  * 5、如果提交成功,API返回”true”;失败,则返回”false”。 
  */
public class Transaction {    
  public Boolean commit(String command) {        
    Boolean result = true;        
    String[] tokens = tokenize(command);        
    normalizeTokens(tokens);        
    if (isALargeTransaction(tokens)) {            
      result = processLargeTransaction(tokens);        
    } else {            
      result = processSmallTransaction(tokens);        
    }        
    return result;    
  }
}

заменить функцию функциональным объектом

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

объект параметра импорта

Если параметров метода много, инкапсулируйте параметры как объекты параметров.

удалить присвоение параметру

public int discount(int inputVal, int quantity, int yearToDate) {
  if (inputVal > 50) inputVal -= 2;
  if (quantity > 100) inputVal -= 1;
  if (yearToDate > 10000) inputVal -= 4;
  return inputVal;
}

public int discount(int inputVal, int quantity, int yearToDate) { 
  int result = inputVal;
  if (inputVal > 50) result -= 2; 
  if (quantity > 100) result -= 1; 
  if (yearToDate > 10000) result -= 4; 
  return result; 
}

Отделить запрос от модификации

Любой способ, который возвращает значение, не должен иметь побочные эффекты

  • Не вызывайте операции записи в convert, чтобы избежать побочных эффектов
  • Распространенное исключение: кэширование результатов запроса локально

Удалить ненужные временные переменные

Временные переменные используются только один раз или когда стоимость логики значения низкая.

Ввести объясняющие переменные

Поместите результат сложного выражения (или его часть) во временную переменную, используя имя переменной для объяснения цели выражения.

if ((platform.toUpperCase().indexOf("MAC") > -1) 
    && (browser.toUpperCase().indexOf("IE") > -1) && wasInitialized() && resize > 0) {   
  // do something 
} 
  
final boolean isMacOs = platform.toUpperCase().indexOf("MAC") > -1; 
final boolean isIEBrowser = browser.toUpperCase().indexOf("IE") > -1; 
final boolean wasResized = resize > 0; 
if (isMacOs && isIEBrowser && wasInitialized() && wasResized) {   
  // do something 
}

Используйте защитные операторы вместо вложенных условных выражений

Комплексные условные выражения разделены в множество условных выражений, уменьшают гнездо. Вложенный несколько уровней, если бы - затем заявления, преобразованные в несколько утверждений

//未使用卫语句
public void getHello(int type) {
    if (type == 1) {
        return;
    } else {
        if (type == 2) {
            return;
        } else {
            if (type == 3) {
                return;
            } else {
                setHello();
            }
        }
    }
} 

//使用卫语句
public void getHello(int type) {
    if (type == 1) {
        return;
    }
    if (type == 2) {
        return;
    }
    if (type == 3) {
        return;
    }
    setHello();
}

Используйте полиморфные альтернативные условные суждения

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

public int calculate(int a, int b, String operator) {
    int result = Integer.MIN_VALUE;
 
    if ("add".equals(operator)) {
        result = a + b;
    } else if ("multiply".equals(operator)) {
        result = a * b;
    } else if ("divide".equals(operator)) {
        result = a / b;
    } else if ("subtract".equals(operator)) {
        result = a - b;
    }
    return result;
}

При большом количестве проверок типов и суждений объем оператора if else (или switch) будет раздуваться, что, несомненно, снижает читабельность кода. Кроме того, если else (или переключатель) сам по себе является «точкой изменения», то при необходимости расширения нового типа мы должны добавить блок операторов if else (или переключатель) и соответствующую логику, что, несомненно, снижает масштабируемость Программа также нарушает объектно-ориентированный принцип открытого-закрытого.

Основываясь на этом сценарии, мы можем рассмотреть возможность использования «полиморфизма» для замены длинного условного суждения и инкапсулировать «точку изменения» в if else (или switch) в подклассы. Таким образом, нет необходимости использовать операторы if else (или switch), а вместо этого используются полиморфные экземпляры подклассов, что делает код более читабельным и расширяемым. Таким образом используются многие шаблоны проектирования, такие как шаблон стратегии и шаблон состояния.

public interface Operation { 
  int apply(int a, int b); 
}

public class Addition implements Operation { 
  @Override 
  public int apply(int a, int b) { 
    return a + b; 
  } 
}

public class OperatorFactory {
    private final static Map<String, Operation> operationMap = new HashMap<>();
    static {
        operationMap.put("add", new Addition());
        operationMap.put("divide", new Division());
        // more operators
    }
 
    public static Operation getOperation(String operator) {
        return operationMap.get(operator);
    }
}

public int calculate(int a, int b, String operator) {
    if (OperatorFactory .getOperation == null) {
      	throw new IllegalArgumentException("Invalid Operator");
    }
    return OperatorFactory .getOperation(operator).apply(a, b);
}

Используйте исключения вместо возврата кодов ошибок

Для обработки ненормального бизнес-статуса используйте метод создания исключения вместо возврата кода ошибки.

  • Не используйте обработку исключений для обычного управления бизнес-процессами
    • Обработка исключений требует очень высокой производительности.
  • По возможности используйте стандартные исключения
  • Избегайте создания исключений в блоках finally
    • Если два исключения выбрасываются одновременно, стек вызовов первого исключения теряется.
    • Блок finally должен делать только такие вещи, как закрытие ресурсов.
//使用错误码
public boolean withdraw(int amount) {
    if (balance < amount) {
        return false;
    } else {
        balance -= amount;
        return true;
    }
}

//使用异常
public void withdraw(int amount) {
    if (amount > balance) {
        throw new IllegalArgumentException("amount too large");    
    }
    balance -= amount;
}

Введите утверждение

Фрагмент кода должен делать предположение о состоянии программы, и утверждение явно выражает это предположение.

  • Не злоупотребляйте утверждениями, не используйте его для проверки условий «должно быть истинным», используйте его только для проверки условий «должно быть истинным».
  • Будет ли код по-прежнему функционировать должным образом, если ограничения, указанные в утверждении, не могут быть удовлетворены? Удалите утверждения, если это возможно

Внедрение нулевых объектов или специальных объектов

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

Проблема нулевых ссылок не может быть устранена в Java, но ее можно решить с помощью методов программирования кода (введение нулевых объектов).

//空对象的例子
public class OperatorFactory { 
  static Map<String, Operation> operationMap = new HashMap<>(); 
  static { 
    operationMap.put("add", new Addition()); 
    operationMap.put("divide", new Division()); 
    // more operators 
  } 
  public static Optional<Operation> getOperation(String operator) { 
    return Optional.ofNullable(operationMap.get(operator)); 
  } 
} 
public int calculate(int a, int b, String operator) { 
  Operation targetOperation = OperatorFactory.getOperation(operator) 
    	.orElseThrow(() -> new IllegalArgumentException("Invalid Operator")); 
  return targetOperation.apply(a, b); 
}

//特殊对象的例子
public class InvalidOp implements Operation { 
  @Override 
  public int apply(int a, int b)  { 
    throw new IllegalArgumentException("Invalid Operator");
  } 
}

Уточнение

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

На этом этапе вам нужно подумать, какие части выделить в отдельный класс, что может быть основано на принципе высокой связанности и низкой связанности. Если некоторые данные и методы всегда появляются вместе или если некоторые данные часто изменяются одновременно, это указывает на то, что их следует поместить в класс. Другим сигналом является то, как класс делится на подклассы: если вы обнаружите, что создание подклассов влияет только на некоторые свойства класса или что свойства класса должны быть разделены на подклассы по-другому, это означает, что вам нужно разложить исходный класс.

//原始类
public class Person {
    private String name;
    private String officeAreaCode;
    private String officeNumber;

    public String getName() {
        return name;
    }

    public String getTelephoneNumber() {
        return ("(" + officeAreaCode + ")" + officeNumber);
    }

    public String getOfficeAreaCode() {
        return officeAreaCode;
    }

    public void setOfficeAreaCode(String arg) {
        officeAreaCode = arg;
    }

    public String getOfficeNumber() {
        return officeNumber;
    }

    public void setOfficeNumber(String arg) {
        officeNumber = arg;
    }
}

//新提炼的类(以对象替换数据值)
public class TelephoneNumber {
    private String areaCode;
    private String number;

    public String getTelephnoeNumber() {
        return ("(" + getAreaCode() + ")" + number);
    }

    String getAreaCode() {
        return areaCode;
    }

    void setAreaCode(String arg) {
        areaCode = arg;
    }

    String getNumber() {
        return number;
    }

    void setNumber(String arg) {
        number = arg;
    }
}

Композиция имеет приоритет над наследованием

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

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

// Inappropriate use of inheritance!
public class InstrumentedHashSet<E> extends HashSet<E> {
    // The number of attempted element insertions
    private int addCount = 0;

    public InstrumentedHashSet() { }

    public InstrumentedHashSet(int initCap, float loadFactor) {
        super(initCap, loadFactor);
    }

    @Override
    public boolean add(E e) {
        addCount++;
        return super.add(e);
    }

    @Override
    public boolean addAll(Collection<? extends E> c) {
        addCount += c.size();
        return super.addAll(c);
    }

    public int getAddCount() {
        return addCount;
    }
}

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

// Reusable forwarding class
public class ForwardingSet<E> implements Set<E> {
    private final Set<E> s;
    public ForwardingSet(Set<E> s) { this.s = s; }
  
    @Override
    public int size() { return s.size(); }
    @Override
    public boolean isEmpty() { return s.isEmpty(); }
    @Override
    public boolean contains(Object o) { return s.contains(o); }
    @Override
    public Iterator<E> iterator() { return s.iterator(); }
    @Override
    public Object[] toArray() { return s.toArray(); }
    @Override
    public <T> T[] toArray(T[] a) { return s.toArray(a); }
    @Override
    public boolean add(E e) { return s.add(e); }
    @Override
    public boolean remove(Object o) { return s.remove(o); }
    @Override
    public boolean containsAll(Collection<?> c) { return s.containsAll(c); }
    @Override
    public boolean addAll(Collection<? extends E> c) { return s.addAll(c); }
    @Override
    public boolean retainAll(Collection<?> c) { return s.retainAll(c); }
    @Override
    public boolean removeAll(Collection<?> c) { return s.removeAll(c); }
    @Override
    public void clear() { s.clear(); }
}

// Wrappter class - uses composition in place of inheritance
public class InstrumentedHashSet<E> extends ForwardingSet<E> {
    private int addCount = 0;

    public InstrumentedHashSet1(Set<E> s) {
        super(s);
    }

    @Override
    public boolean add(E e) {
        addCount++;
        return super.add(e);
    }

    @Override
    public boolean addAll(Collection<? extends E> c) {
        addCount += c.size();
        return super.addAll(c);
    }

    public int getAddCount() {
        return addCount;
    }
}

Как выбрать между наследованием и композицией

  • Наследование допустимо только в том случае, если подкласс действительно является подтипом родительского класса. Для двух классов A и B класс B должен наследовать A только в том случае, если между ними существует отношение «есть-a»;
  • Очень безопасно использовать наследование внутри пакета, реализация как подкласса, так и суперкласса находится под контролем одного и того же программиста;
  • Также очень безопасно использовать наследование для классов, специально предназначенных для наследования и хорошо задокументированных;
  • В других случаях следует отдавать предпочтение комбинированному методу для достижения

Интерфейсы лучше, чем абстрактные классы

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

Преимущества интерфейсов перед абстрактными классами:

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

Хотя интерфейс предоставляет методы по умолчанию, интерфейс по-прежнему имеет следующие ограничения:

  • Модификатор переменной интерфейса может быть только public static final
  • Модификаторы методов интерфейса могут быть только общедоступными.
  • Конструктора для интерфейса нет и не существует
  • В существующий интерфейс можно добавить методы по умолчанию, но нет гарантии, что эти методы будут хорошо работать в уже существующей реализации.
    • Поскольку эти методы по умолчанию внедрены в существующие реализации, их разработчики не знают и не имеют разрешения.

Цель проектирования и преимущества методов интерфейса по умолчанию:

  • За эволюцию интерфейса
    • До Java 8 мы знали, что все методы интерфейса должны быть реализованы его подклассами (конечно, этот подкласс не является абстрактным классом), но после Java 8 методы интерфейсов по умолчанию могут не реализовываться. операции могут быть скомпилированы во время компиляции. Это позволяет избежать ошибок компиляции проекта при обновлении с Java 7 до Java 8. В Java 8 к основному интерфейсу коллекции добавлен ряд новых методов по умолчанию, главным образом для облегчения использования лямбда-выражений.
  • Может уменьшить создание сторонних инструментов
    • Например, в интерфейсах коллекций, таких как List, есть некоторые методы по умолчанию. Методы по умолчанию, такие как replaceAll(UnaryOperator), sort(Comparator) и spliterator(), предоставляются по умолчанию в интерфейсе List. Эти методы создаются внутри интерфейса, избегая специализации для этих методов.Создать соответствующий класс инструментов.
  • Можно избежать создания базовых классов
    • До Java 8 нам, возможно, потребуется создать базовый класс для повторного использования кода, а появление методов по умолчанию может сделать ненужным создание базового класса.

Из-за ограничений интерфейсов и различных целей проектирования интерфейсы не полностью заменяют абстрактные классы. Но преимущества интерфейсов и абстрактных классов можно объединить, предоставив абстрактный скелетный класс реализации интерфейса.Интерфейс отвечает за определение типа и, возможно, за предоставление некоторых методов по умолчанию, в то время как класс реализации скелета отвечает за реализацию оставшихся методов интерфейса небазового типа в дополнение к методам интерфейса базового типа. На расширение реализации скелета приходится большая часть работы вне реализации интерфейса. Это шаблон проектирования Template Method.

Image [5].png

Интерфейсный протокол: определяет два основных метода уровня протокола RPC, службу экспорта и справочную службу.

Абстрактный класс AbstractProtocol: инкапсулирует экспортер после предоставляемой службы и экземпляр Invoker после эталонной службы и реализует логику уничтожения службы.

Конкретный класс реализации XxxProtocol: реализует конкретную логику службы экспорта и справочной службы.

Предпочтительны дженерики

Класс или интерфейс, в объявлении которого есть один или несколько параметров типа, является универсальным классом или интерфейсом. Универсальные классы и интерфейсы вместе называются универсальными типами. Обобщения были введены в Java 5 и обеспечивают механизм обнаружения безопасности типа во время компиляции. Суть дженериков заключается в параметризованном типе, который через параметр представляет тип данных операции и может ограничивать диапазон типов этого параметра. Преимущество дженериков заключается в обнаружении типов во время компиляции, что позволяет избежать преобразования типов.

// 比较三个值并返回最大值
public static <T extends Comparable<T>> T maximum(T x, T y, T z) {   
  T max = x; 
  // 假设x是初始最大值   
  if ( y.compareTo( max ) > 0 ) {      
    max = y; //y 更大  
  }   if ( z.compareTo( max ) > 0 ) {     
    max = z; // 现在 z 更大              
  }   return max; // 返回最大对象
}

public static void main( String args[] ) {   
  System.out.printf( "%d, %d 和 %d 中最大的数为 %d\n\n",  3, 4, 5, maximum( 3, 4, 5 ));   
  System.out.printf( "%.1f, %.1f 和 %.1f 中最大的数为 %.1f\n\n",  6.6, 8.8, 7.7,  maximum( 6.6, 8.8, 7.7 ));   
  System.out.printf( "%s, %s 和 %s 中最大的数为 %s\n","pear", "apple", "orange", maximum( "pear", "apple", "orange" ) );
}

Не используйте собственный тип

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

Максимально устранить каждое непроверенное предупреждение

Каждое предупреждение указывает, что во время выполнения может быть выдано исключение ClassCastException. Сделайте все возможное, чтобы устранить эти предупреждения. Если его нельзя устранить, но код, вызвавший предупреждение, может быть признан безопасным, используйте аннотацию @SuppressWarnings("unchecked"), чтобы подавить предупреждение в минимально возможной области, но запишите причину подавления.

Используйте ограниченные подстановочные знаки для повышения гибкости API

Параметризованные типы не поддерживают ковариацию, т. е. для любых двух разных типов Type1 и Type2 List не является ни подтипом List, ни его надклассом. Чтобы решить эту проблему и повысить гибкость, в Java предусмотрен специальный параметризованный тип, называемый ограниченным подстановочным типом, а именно List и List. Принцип использования: производитель-расширяет, потребитель-супер (PECS). Если это и производитель, и потребитель, нет необходимости использовать подстановочные знаки.

Существует также специальный неограниченный список подстановочных знаков>, который представляет определенный тип, но не является определенным. Часто используется как общая ссылка, к которой нельзя добавить ни один объект, кроме Null.

//List<? extends E>
// Number 可以认为 是Number 的 "子类"
List<? extends Number> numberArray = new ArrayList<Number>(); 
// Integer 是 Number 的子类
List<? extends Number> numberArray = new ArrayList<Integer>(); 
// Double 是 Number 的子类
List<? extends Number> numberArray = new ArrayList<Double>();  

//List<? super E>
// Integer 可以认为是 Integer 的 "父类"
List<? super Integer> array = new ArrayList<Integer>();、
// Number 是 Integer 的 父类
List<? super Integer> array = new ArrayList<Number>();
// Object 是 Integer 的 父类
List<? super Integer> array = new ArrayList<Object>();

public static <T> void copy(List<? super T> dest, List<? extends T> src) {    
  int srcSize = src.size();    
  if (srcSize > dest.size())        
  	throw new IndexOutOfBoundsException("Source does not fit in dest");    
  if (srcSize < COPY_THRESHOLD || (src instanceof RandomAccess && dest instanceof RandomAccess)) {        
    for (int i=0; i<srcSize; i++)            
    dest.set(i, src.get(i));    
  } else {        
    ListIterator<? super T> di=dest.listIterator();        
    ListIterator<? extends T> si=src.listIterator();        
    for (int i=0; i<srcSize; i++) {            
      di.next();            
      di.set(si.next());        
    }    
  }
}

Статический класс-член лучше, чем нестатический класс-член

Вложенный класс — это класс, определенный внутри другого класса.Целью вложенного класса является только предоставление услуг своим внешним классам, и он должен быть классом верхнего уровня, если другие среды также будут его использовать.Существует четыре типа вложенных классов: статический класс-член, нестатический класс-член, анонимный класс и локальный класс. За исключением первого, остальные три называются внутренними классами.

анонимный класс

Без имени он создается во время объявления и может использоваться только один раз. Когда он присутствует в нестатической среде, он содержит ссылку на экземпляр внешнего класса. Обычно используется для создания объектов-функций и объектов-процедур, но теперь предпочтение отдается лямбда-выражениям.

местный класс

Локальный класс может быть объявлен везде, где может быть объявлена ​​локальная переменная, при соблюдении тех же правил области видимости. В отличие от анонимных классов, существуют имена, которые можно использовать повторно. Однако на практике локальные классы используются редко.

статический класс-член

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

нестатический класс-член

Хотя синтаксически единственное отличие от статического класса-члена состоит в том, что объявление класса не содержит static, между ними есть большая разница. Каждый экземпляр нестатического класса-члена неявно связан с экземпляром внешнего класса и может обращаться к свойствам и методам-членам внешнего класса. Кроме того, вы должны создать экземпляр внешнего класса, прежде чем сможете создать экземпляр нестатического класса-члена.

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

Сначала используйте шаблоны/инструменты

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

Создание и использование отдельных объектов

По сравнению с процессно-ориентированным программированием объектно-ориентированное программирование имеет дополнительный этап создания экземпляров, и при создании объектов необходимо указывать определенный тип. Наша обычная практика — «где использовать, где создавать», использование экземпляра и создание экземпляра — это один и тот же фрагмент кода. Кажется, что это делает код более читабельным, но в некоторых случаях создает ненужную связь.

public class BusinessObject {
	public void actionMethond {
    	//Other things
    	Service myServiceObj = new Service();
      	myServiceObj.doService();
      	//Other things
    }
}

public class BusinessObject {
	public void actionMethond {
    	//Other things
    	Service myServiceObj = new ServiceImpl();
      	myServiceObj.doService();
      	//Other things
    }
}

public class BusinessObject {
  	private Service myServiceObj;
  	public BusinessObject(Service aService) {
      	myServiceObj = aService;
    }
	public void actionMethond {
    	//Other things
      	myServiceObj.doService();
      	//Other things
    }
}

public class BusinessObject {
  	private Service myServiceObj;
  	public BusinessObject() {
      	myServiceObj = ServiceFactory;
    }
	public void actionMethond {
    	//Other things
      	myServiceObj.doService();
      	//Other things
    }
}

Создатель объекта связан с конкретным типом объекта, а пользователь объекта связан с интерфейсом объекта. То есть создателя волнует, что из себя представляет объект, а пользователя волнует, что он может делать. Их следует рассматривать как отдельные соображения, и они имеют тенденцию меняться по разным причинам.

Когда тип объекта связан с полиморфизмом, а создание объекта является сложным (со многими зависимостями), вы можете рассмотреть возможность разделения процесса создания объекта, чтобы пользователю не нужно было обращать внимание на детали создания объекта. Это начальная точка шаблона создания в шаблоне проектирования, а шаблон фабрики, компоновщики и методы внедрения зависимостей можно использовать в реальных проектах.

Минимальная доступность

Очень важным фактором, позволяющим отличить компонент от хорошо спроектированного проекта, является то, скрывает ли он свои внутренние данные и детали реализации от внешних компонентов. Java предоставляет механизмы управления доступом для определения доступности классов, интерфейсов и членов. Доступность сущности определяется местом объявления сущности и модификаторами доступа (private, protected, public), появляющимися в объявлении сущности.

Для классов и интерфейсов верхнего уровня (невложенных) существует только два уровня доступа: закрытый на уровне пакета (без публичного оформления) и общедоступный (общедоступное оформление).

Для членов (экземпляров/полей, методов, вложенных классов и вложенных интерфейсов) существует четыре уровня доступа, и доступность увеличивается следующим образом:

  • Private (закрытая модификация) — доступ к этому члену возможен только внутри класса верхнего уровня, в котором член объявлен;
  • Частный уровень пакета (по умолчанию) — любой класс внутри пакета, который объявляет член, может получить доступ к члену;
  • Protected (защищенная модификация) — подклассы класса, объявляющего член, могут получить доступ к этому члену, и любой класс внутри пакета, объявляющего член, также может получить доступ к этому члену;
  • Публичный (публичная модификация) — к члену можно получить доступ где угодно;

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

  • Если класс или интерфейс можно сделать приватным для пакета, его следует сделать приватным для пакета;
  • Если частный класс верхнего уровня или интерфейс уровня пакета используется только внутри класса, подумайте о том, чтобы сделать его частным вложенным классом этого класса;
  • Публичные классы не должны напрямую раскрывать поля экземпляра, но должны предоставлять соответствующие методы, чтобы сохранить гибкость для изменения внутреннего представления класса в будущем;
  • Когда общедоступный API класса определен, другие члены должны быть закрыты;
  • Если существует много обращений между классами в одном и том же пакете, следует рассмотреть возможность изменения дизайна, чтобы уменьшить эту связь;

Свести к минимуму изменчивость

Неизменяемые классы — это классы, экземпляры которых нельзя изменить. Вся информация, содержащаяся в каждом экземпляре, должна предоставляться при создании экземпляра и фиксируется в течение всего срока службы объекта. Преимущества неизменяемых классов заключаются в том, что они просты в использовании, потокобезопасны, свободно совместно используются и не подвержены ошибкам. Библиотека классов платформы Java содержит множество неизменяемых классов, таких как String, классы-оболочки примитивных типов, BigDecimal и т. д.

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

  • объявить все поля закрытыми
  • объявить все поля окончательными
    • Должно быть обеспечено правильное поведение, если ссылка на вновь созданный экземпляр передается из одного потока в другой при отсутствии механизмов синхронизации.
  • Не предоставляет никаких методов, которые изменяют состояние объекта
  • Гарантированный класс не будет расширен (предотвратить создание подклассов, класс объявлен окончательным)
    • Не позволяет небрежным или злонамеренным подклассам делать вид, что состояние объекта изменилось, тем самым нарушая неизменное поведение класса.
  • обеспечить взаимоисключающий доступ к любым изменяемым компонентам
    • Если в классе есть поля, указывающие на изменяемые объекты, вы должны убедиться, что клиенты класса не могут получить ссылки на эти объекты. Кроме того, никогда не инициализируйте такое поле ссылкой на объект, предоставленной клиентом, и никогда не возвращайте эту ссылку на объект из любого метода доступа. Используйте методы защитного копирования в конструкторах, методах доступа и методах readObject.

Несколько советов по минимизации изменчивости:

  • Если нет веских причин для того, чтобы класс был изменчивым, он должен быть неизменяемым;
  • Если класс нельзя сделать неизменяемым, он все равно должен максимально ограничивать свою изменчивость;
  • Сделать каждое поле закрытым окончательным, если нет веской причины сделать поле не окончательным;
  • Конструктор должен создавать полностью инициализированные объекты и устанавливать все ограничения;

Как гарантировать качество

Разработка через тестирование

Разработка через тестирование (TDD) требует, чтобы тестирование было в центре процесса разработки, требуя, чтобы тесты для производственного поведения были написаны до того, как будет написан какой-либо код, и код пишется с целью обеспечения прохождения тестов. TDD требует, чтобы тесты выполнялись полностью автоматически и должны выполняться до и после рефакторинга кода.

Конечная цель TDD — чистый код, который работает. Большинство разработчиков большую часть времени не получают чистый и пригодный для использования код. Решение - разделяй и властвуй. Сначала адрес «доступен» в таргете, затем «чистый код». Это отличается от разработки, ориентированной на архитектуру.

Еще одно преимущество использования TDD заключается в том, что у нас есть исчерпывающий набор автоматических тестов, сопровождающих код. В будущем, когда по какой-либо причине потребуется обслуживание кода (требования, рефакторинг, повышение производительности), наш код всегда будет надежным, работая под управлением этого набора тестов.

Цикл разработки TDD

Image [6].png

Добавить тест -> запустить все тесты и проверить результаты тестов -> написать код для прохождения тестов -> запустить все тесты и пройти все -> выполнить рефакторинг кода для устранения дублирования дизайна и оптимизации структуры проекта

два основных принципа

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

Разделение ответственности — еще один очень важный принцип, вытекающий из этих двух правил. Смысл его выражения состоит в том, чтобы достичь цели «пригодного для использования» кода на этапе кодирования, а затем преследовать цель «чистоты» на этапе рефакторинга и сосредоточиться только на одной вещи за раз!

Многоуровневые тестовые точки

тип теста Цель Тестирование и оценка результатов
Дао тест Проверьте правильность mybatis-config, mapper и обработчика. База данных в памяти
Вы можете использовать assert для проверки
Тест адаптера Убедитесь, что внешние зависимости взаимодействуют правильно
Убедитесь, что конвертер правильный
зависит от внешней среды
Правильность зависит от интерпретации человеком
Тест репозитория Проверить внутренний расчет, логику преобразования Мокабельные внешние зависимости
Вы можете использовать assert для проверки
тест слоя biz Проверка внутренней бизнес-логики Максимально изолируйте все внешние зависимости
Требуется несколько тестов, каждый из которых проверяет сценарий или ветвь
Используйте проверку утверждений, не полагаясь на человеческое суждение
Тест прикладного уровня Убедитесь, что входные параметры обрабатываются правильно.
Убедитесь, что ссылка в системе не заблокирована
Может изолировать внешние зависимости
Покрытие сцены управляется параметрами
Вы можете использовать пошаговую отладку, чтобы наблюдать за направлением выполнения кода.
Детальная логика не проверена

использованная литература

Рефакторинг — улучшение дизайна существующего кода

Шаблоны проектирования

Effective Java

Лучшие практики гибкой разработки и проектирования программного обеспечения

Режим реализации

Разработка через тестирование