предисловие
Какой код является хорошим кодом? Хороший код должен быть хорошо назван, читаем, расширяем, надежен... Каковы типичные характеристики плохого кода? Вы должны обратить внимание на эти 25 плохих запахов кода
- публика:маленький мальчик собирает улиток
- гитхаб-адрес
1. Дублированный код
Дублирующийся кодРазные локации, одинаковая структура программы. Как правило, это вызвано копированием и вставкой, потому что итерация запроса происходит относительно быстро, и партнеры по разработке беспокоятся о том, чтобы повлиять на существующие функции. повторяющийся кодсложно поддерживатьДа, если вы хотите изменить логику одного из кусков кода, вам нужно будет изменить его много раз, и очень вероятно, что будут пропуски.
Как оптимизировать повторяющийся код? МинутатриОбсуждение ситуации:
- Две функции одного класса содержат одно и то же выражение
class A {
public void method1() {
doSomething1
doSomething2
doSomething3
}
public void method2() {
doSomething1
doSomething2
doSomething4
}
}
Средства оптимизации: можно использоватьМетод извлечения (извлечение общедоступной функции)Извлеките логику повторяющегося кода и сформируйте общий метод.
class A {
public void method1() {
commonMethod();
doSomething3
}
public void method2() {
commonMethod();
doSomething4
}
public void commonMethod(){
doSomething1
doSomething2
}
}
- Два подкласса, которые являются братьями и сестрами, содержат одно и то же выражение
class A extend C {
public void method1() {
doSomething1
doSomething2
doSomething3
}
}
class B extend C {
public void method1() {
doSomething1
doSomething2
doSomething4
}
}
Метод оптимизации: использовать оба классаМетод извлечения (извлечение общедоступной функции), затем поставьтеИзвлеченная функция помещается в родительский класссередина.
class C {
public void commonMethod(){
doSomething1
doSomething2
}
}
class A extend C {
public void method1() {
commonMethod();
doSomething3
}
}
class B extend C {
public void method1() {
commonMethod();
doSomething4
}
}
- Дублирующийся код в двух несвязанных классах
Если в двух несвязанных классах есть повторяющийся код, вы можете использоватьExtract ClassПеренесите повторяющийся код в класс. Этот новый класс может быть общим классом или классом инструментов, в зависимости от того, как разделен конкретный бизнес.
2. Длинный метод (длинная функция)
Длинные функции относятся к сотням или даже тысячам строк функционального метода, что сильно снижает читабельность и непросто для понимания.Обратное выглядит следующим образом:
public class Test {
private String name;
private Vector<Order> orders = new Vector<Order>();
public void printOwing() {
//print banner
System.out.println("****************");
System.out.println("*****customer Owes *****");
System.out.println("****************");
//calculate totalAmount
Enumeration env = orders.elements();
double totalAmount = 0.0;
while (env.hasMoreElements()) {
Order order = (Order) env.nextElement();
totalAmount += order.getAmout();
}
//print details
System.out.println("name:" + name);
System.out.println("amount:" + totalAmount);
......
}
}
можно использоватьExtract Method
, извлекать сегменты кода с помощью одной функции и формировать небольшие функции с понятными именами для решения проблем с длинными функциями,Так же, как следующее:
public class Test {
private String name;
private Vector<Order> orders = new Vector<Order>();
public void printOwing() {
//print banner
printBanner();
//calculate totalAmount
double totalAmount = getTotalAmount();
//print details
printDetail(totalAmount);
}
void printBanner(){
System.out.println("****************");
System.out.println("*****customer Owes *****");
System.out.println("****************");
}
double getTotalAmount(){
Enumeration env = orders.elements();
double totalAmount = 0.0;
while (env.hasMoreElements()) {
Order order = (Order) env.nextElement();
totalAmount += order.getAmout();
}
return totalAmount;
}
void printDetail(double totalAmount){
System.out.println("name:" + name);
System.out.println("amount:" + totalAmount);
}
}
3. Большой класс
Класс, который делает слишком много, поддерживает слишком много функций, становится менее читабельным и менее производительным. Например, вы можете поместить функции, связанные с заказом, в класс A, функции, связанные с товарным запасом, также поместить в класс A, а функции, связанные с баллами, также поместить в класс A..Противоположный примерследующее:
Class A{
public void printOrder(){
System.out.println("订单");
}
public void printGoods(){
System.out.println("商品");
}
public void printPoints(){
System.out.println("积分");
}
}
Только представьте, все беспорядочные блоки кода запихнуты в класс, а о читабельности и говорить нечего. Следует использовать как единую ответственностьExtract Class
Разделите код, как показано ниже:
Class Order{
public void printOrder(){
System.out.println("订单");
}
}
Class Goods{
public void printGoods(){
System.out.println("商品");
}
}
Class Points{
public void printPoints(){
System.out.println("积分");
}
}
}
4. Длинный список параметров
Метод со слишком большим количеством параметров не очень удобочитаем. Если есть несколько перегруженных методов со многими параметрами, иногда вы не знаете, какой из них вызывать. К тому же, если параметров много, хлопотнее разобраться с совместимостью старого и нового интерфейсов.
public void getUserInfo(String name,String age,String sex,String mobile){
// do something ...
}
Как решить проблему слишком длинного столбца параметров? Инкапсулируйте параметры в структуры или классы.Например, мы инкапсулируем параметры в класс DTO следующим образом:
public void getUserInfo(UserInfoParamDTO userInfoParamDTO){
// do something ...
}
class UserInfoParamDTO{
private String name;
private String age;
private String sex;
private String mobile;
}
5. Дивергентное изменение
При сопровождении программыЕсли вы добавляете и изменяете компоненты, вам необходимо одновременно изменять несколько методов в классе., то это Дивергентное Изменение. Возьмем, к примеру, автомобиль. Производитель автомобилей выпускает автомобили трех марок: BMW, Benz и LaoSiLaiSi, каждая марка может выбрать топливо, чисто электрический и гибридный.Вопреки следующему:
/**
* 公众号:捡田螺的小男孩
*/
public class Car {
private String name;
void start(Engine engine) {
if ("HybridEngine".equals(engine.getName())) {
System.out.println("Start Hybrid Engine...");
} else if ("GasolineEngine".equals(engine.getName())) {
System.out.println("Start Gasoline Engine...");
} else if ("ElectricEngine".equals(engine.getName())) {
System.out.println("Start Electric Engine");
}
}
void drive(Engine engine,Car car) {
this.start(engine);
System.out.println("Drive " + getBrand(car) + " car...");
}
String getBrand(Car car) {
if ("Baoma".equals(car.getName())) {
return "BMW";
} else if ("BenChi".equals(car.getName())) {
return "Benz";
} else if ("LaoSiLaiSi".equals(car.getName())) {
return "LaoSiLaiSi";
}
return null;
}
}
Если добавляется совершенно новый энергетический трамвай, а его пусковым двигателем является атомная энергия, то класс Car необходимо изменить.start
иgetBrand
метод, это плохой запах кода:Расходящиеся изменения.
Как его оптимизировать? Краткое содержание одной фразы:Разделяйте категории и объединяйте вещи, которые всегда меняются вместе.
- Поведение разделения классов с помощью Extract Class.
- Извлеките суперкласс и извлеките подкласс, если разные классы ведут себя одинаково.
Например:
Поскольку Engine изменяется независимо, интерфейс Engine извлекается.Если добавляется новый механизм запуска, достаточно еще одного класса реализации. следующее:
//IEngine
public interface IEngine {
void start();
}
public class HybridEngineImpl implements IEngine {
@Override
public void start() {
System.out.println("Start Hybrid Engine...");
}
}
так какdrive
метод зависит отCar,IEngine,getBand
метод;getBand
Метод меняется и также связан с Car, поэтому можно создать абстрактный класс Car, и каждая марка автомобиля может наследоваться от него следующим образом
public abstract class AbstractCar {
protected IEngine engine;
public AbstractCar(IEngine engine) {
this.engine = engine;
}
public abstract void drive();
}
//奔驰汽车
public class BenzCar extends AbstractCar {
public BenzCar(IEngine engine) {
super(engine);
}
@Override
public void drive() {
this.engine.start();
System.out.println("Drive " + getBrand() + " car...");
}
private String getBrand() {
return "Benz";
}
}
//宝马汽车
public class BaoMaCar extends AbstractCar {
public BaoMaCar(IEngine engine) {
super(engine);
}
@Override
public void drive() {
this.engine.start();
System.out.println("Drive " + getBrand() + " car...");
}
private String getBrand() {
return "BMW";
}
}
Осторожные друзья, вы можете найти разные подклассы BaoMaCar и BenzCardrive
метод, по-прежнему имеют тот же код, поэтому мы можем расширить еще один абстрактный подкласс, поместивdrive
Метод развивается следующим образом:
public abstract class AbstractRefinedCar extends AbstractCar {
public AbstractRefinedCar(IEngine engine) {
super(engine);
}
@Override
public void drive() {
this.engine.start();
System.out.println("Drive " + getBrand() + " car...");
}
abstract String getBrand();
}
//宝马
public class BaoMaRefinedCar extends AbstractRefinedCar {
public BaoMaRefinedCar(IEngine engine) {
super(engine);
}
@Override
String getBrand() {
return "BMW";
}
}
Если вы добавляете новую марку, создайте подкласс и наследуйтеAbstractRefinedCar
То есть, если добавляется новый механизм запуска, это тоже реализация класса.IEngine
интерфейс
6. Хирургия дробовика
Когда вы реализуете небольшую функцию, вам нужно внести небольшие изменения во множество разных классов. ЭтоДробовик Хирургия. следуетРасходящиеся измененияРазница в том, что это относится к внесению одной модификации в несколько классов одновременно, а расходящиеся изменения относятся к изменению нескольких мест в классе.Вопреки следующему:
public class DbAUtils {
@Value("${db.mysql.url}")
private String mysqlDbUrl;
...
}
public class DbBUtils {
@Value("${db.mysql.url}")
private String mysqlDbUrl;
...
}
Использование нескольких классовdb.mysql.url
Эта переменная, если вам нужно переключиться в будущемmysql
в другую базу данных, напримерOracle
, то вам нужно изменить эту переменную нескольких классов!
Как его оптимизировать? Соберите все точки модификации вместе и абстрагируйте их в новый класс.
Вы можете использовать Move Method (функция перемещения) и Move Field (движущееся поле), чтобы поместить весь код, который необходимо изменить, в один и тот же класс, если нет подходящего класса, перейдите к новому.
Например:
public class DbUtils {
@Value("${db.mysql.url}")
private String mysqlDbUrl;
...
}
7. Функция зависти
Некоторые функции вызывают почти полдюжины функций-получателей из другого объекта, чтобы вычислить какое-то значение. С точки зрения непрофессионала, функция использует большое количество членов других классов, некоторые люди называют этоФункция красного абрикоса вне стены. Обратное выглядит следующим образом:
public class User{
private Phone phone;
public User(Phone phone){
this.phone = phone;
}
public void getFullPhoneNumber(Phone phone){
System.out.println("areaCode:" + phone.getAreaCode());
System.out.println("prefix:" + phone.getPrefix());
System.out.println("number:" + phone.getNumber());
}
}
Как это решить? В этом случае вы можете подумать о переносе метода в класс, который он использует. Например, чтобыgetFullPhoneNumber()
отUser
класс переехал вPhone
класс, потому что он вызываетPhone
многие методы класса.
8. Сгустки данных
Элементы данных похожи на маленьких детей, которым нравится оставаться вместе в группах. Если некоторые элементы данных всегда отображаются вместе, и более целесообразно отображать их вместе, вы можете рассмотреть возможность их инкапсуляции в объекты данных в соответствии с бизнес-значением данных. Обратное выглядит следующим образом:
public class User {
private String firstName;
private String lastName;
private String province;
private String city;
private String area;
private String street;
}
Положительный пример:
public class User {
private UserName username;
private Adress adress;
}
class UserName{
private String firstName;
private String lastName;
}
class Address{
private String province;
private String city;
private String area;
private String street;
}
9. Первобытная одержимость
Большинство сред программирования имеют два типа данных:Структурные типы и основные типы. Базовые типы здесь, если говорить о языке Java, включают в себя не только восемь основных типов, но также String и так далее. Если это базовый тип, который часто появляется вместе, рассмотрите возможность их инкапсуляции как объектов. Я лично думаю, что это немного похоже наСгустки данныхВот пример счетчика:
// 订单
public class Order {
private String customName;
private String address;
private Integer orderId;
private Integer price;
}
Положительный пример:
// 订单类
public class Order {
private Custom custom;
private Integer orderId;
private Integer price;
}
// 把custom相关字段封装起来,在Order中引用Custom对象
public class Custom {
private String name;
private String address;
}
Конечно, не все базовые типы здесь рекомендуется инкапсулировать в объекты, а рекомендуются те, которые связаны или появляются вместе.
10. Операторы Switch
Оператор Switch здесь включает не толькоSwitch
связанные операторы, также включающие несколько слоевif...else
предложение ха. Во многих случаях проблема с операторами switch заключается в дублировании, если вы добавите к нему новый оператор case, вам придется найти все операторы switch и изменить их.
Пример кода выглядит следующим образом:
String medalType = "guest";
if ("guest".equals(medalType)) {
System.out.println("嘉宾勋章");
} else if ("vip".equals(medalType)) {
System.out.println("会员勋章");
} else if ("guard".equals(medalType)) {
System.out.println("守护勋章");
}
...
Этот сценарий может рассмотреть использование полиморфной оптимизации:
//勋章接口
public interface IMedalService {
void showMedal();
}
//守护勋章策略实现类
public class GuardMedalServiceImpl implements IMedalService {
@Override
public void showMedal() {
System.out.println("展示守护勋章");
}
}
//嘉宾勋章策略实现类
public class GuestMedalServiceImpl implements IMedalService {
@Override
public void showMedal() {
System.out.println("嘉宾勋章");
}
}
//勋章服务工厂类
public class MedalServicesFactory {
private static final Map<String, IMedalService> map = new HashMap<>();
static {
map.put("guard", new GuardMedalServiceImpl());
map.put("vip", new VipMedalServiceImpl());
map.put("guest", new GuestMedalServiceImpl());
}
public static IMedalService getMedalService(String medalType) {
return map.get(medalType);
}
}
Конечно, полиморфизм — это только одно решение, одно направление оптимизации. Если это всего лишь одна функция и несколько простых примеров выбора, не рекомендуется постоянно использовать динамическую функцию, потому что это выглядит как хак.
11. Parallel Inheritance Hierarchies (параллельная система наследования)
Система параллельного наследования фактическиShotgun Surgery
особые обстоятельства. Когда вы являетесь подклассом Ax класса A, вы также должны соответственно добавить подкласс Bx к другому классу B.
Решение: В этом случае необходимо устранить связь между двумя системами наследования.Есть класс, который может удалить отношения наследования.
12. Ленивый класс
Слейте логику в этих уже не важных классах в связанные классы и удалите старые. Более распространенный сценарий — предположить, что в системе уже есть класс инструмента даты.DateUtils
, Некоторым небольшим партнерам необходимо использовать преобразование даты и т. д. при разработке, независимо от модели 3721, и самостоятельно реализовать новый класс инструментов для работы с датами.
13. Спекулятивная общность
Старайтесь избегать чрезмерно сложного кода. Например:
- Есть только один, если иначе, то нет необходимости использовать полиморфизм;
- Если абстрактный класс мало что делает, используйте
Collapse Hierarchy
(свернутая система наследования) ``` - Если некоторые параметры функции не используются, удалите их.
14. Временное поле
Переменная экземпляра задается только для конкретной ситуации, такой код непросто понять, мы называем егоTemporary Field(令人迷惑的临时字段)
. Обратное выглядит следующим образом:
public class PhoneAccount {
private double excessMinutesCharge;
private static final double RATE = 8.0;
public double computeBill(int minutesUsed, int includedMinutes) {
excessMinutesCharge = 0.0;
int excessMinutes = minutesUsed - includedMinutes;
if (excessMinutes >= 1) {
excessMinutesCharge = excessMinutes * RATE;
}
return excessMinutesCharge;
}
public double chargeForExcessMinutes(int minutesUsed, int includedMinutes) {
computeBill(minutesUsed, includedMinutes);
return excessMinutesCharge;
}
}
Подумайте об этом, временные поляexcessMinutesCharge
Это лишнее?
15. Цепочки сообщений
Когда вы видите, как пользователь запрашивает один объект для другого объекта, который, в свою очередь, запрашивает другой объект, который, в свою очередь, запрашивает другой объект... это цепочка сообщений. В реальном коде вы можете увидеть длинный списокgetThis()
Или длинный список временных переменных. Обратное выглядит следующим образом:
A.getB().getC().getD().getTianLuoBoy().getData();
Когда А хочет получить требуемые данные, он должен знать В, и он должен знать С, и он должен знать Г. На самом деле А нужно знать слишком много, и об этом подумаем позжеинкапсуляция, Ух. Фактически, это можно решить, разделив функцию или переместив функцию.Например, с B в качестве прокси функция может напрямую возвращать данные, требуемые A.
16. Средний человек
Одной из фундаментальных характеристик объекта является инкапсуляция, то есть сокрытие его внутренних деталей от внешнего мира. Инкапсуляция часто сопровождается делегированием, а злоупотребление делегированием нехорошо: половина функций интерфейса класса делегируется другим классам. можно использоватьRemove Middle Man
оптимизация. Обратное выглядит следующим образом:
A.B.getC(){
return C.getC();
}
На самом деле, A может напрямую получить C через C, без необходимости получать B через B.
17. Неуместная близость
Если два класса слишком близки и слишком интимны, у вас есть я, у меня есть вы, и два класса используют личные вещи друг друга, это плохой запах кода. мы называем егоInappropriate Intimacy(狎昵关系)
Рекомендуется как можно больше извлекать связанные методы или свойства и помещать их в общедоступный класс, чтобы уменьшить ассоциацию.
18. Альтернативные классы с разными интерфейсами
Интерфейс a класса A и интерфейс b класса B делают одно и то же или что-то подобное. Мы называем A и B одним и тем же классом.
в состоянии пройтиПереименовать, переместить функцию или абстрактный подклассоптимизация другими способами
19. Неполный библиотечный класс
С большинством объектов все в порядке, пока их достаточно, и если библиотека классов построена неправильно, мы не можем модифицировать классы в ней, чтобы они делали то, что нам нужно. Можно соус фиолетовый:Оберните функцию или оберните ее в новый класс.
20. Класс данных (чистый класс данных)
Что такое классы данных?Они имеют некоторые поля и функции для доступа (чтения и записи) к этим полям. Эти классы очень просты, содержат только общедоступные переменные-члены или функции для простых операций.
Как его оптимизировать? будетСвязанные операции инкапсулированы, что уменьшает количество открытых переменных-членов.. Например:
- Если у вас есть публичные поля ->
Encapsulate Field
- Если эти классы содержат поля из классов-контейнеров, вы должны убедиться, что они правильно инкапсулированы ->
Encapsulate Collection
инкапсулировать - Для полей, которые не должны изменяться другими классами ->
Remove Setting Method
-> Узнать, где функции get/set используются другими классами ->Move Method
Переместите эти звонки вData Class
Приходить. Если вы не можете переместить всю функцию, используйтеExtract Method
Генерирует функцию, которую можно перемещать ->Hide Method
Скройте эти функции getter/set.
21. Отказ от завещания
Подклассы должны наследовать данные и функции родительского класса. Подклассы наследуют все функции и данные, но используют только некоторые, т.Ошибка проектирования системы наследования, нужно оптимизировать.
- Вам нужно создать новый родственный класс для этого подкласса ->
Push Down Method
иPush Down Field
Переместите все неиспользуемые функции в одноуровневые классы, чтобы суперкласс содержал только то, что является общим для всех подклассов. Все суперклассы должны быть абстрактными. - Если подкласс повторно использует реализацию суперкласса и не желает поддерживать интерфейс суперкласса, его можно отклонить. Но вы не можете изменять систему наследования без разбора ->
Replace Inheritance with Delegation
(замените наследование делегированием).
22. Комментарии (слишком много комментариев)
Этот пункт не означает, что к коду не рекомендуется писать комментарии, но рекомендуется, чтобы выИзбегайте объяснения кода с комментариями, избегайте чрезмерных комментариев. Это плохие запахи общих аннотаций:
- избыточное объяснение
- журнал комментариев
- Объясните переменные и т. д. с комментариями
- ...
Как его оптимизировать?
- функция метода, переменнаяИменование должно быть стандартизированным и простым для понимания., Избегайте объяснения кода с помощью комментариев.
- Критический, сложный бизнес,использоватьясно и лаконичноПримечания
23. Магическое именование
Функции методов, переменные, имена классов, модули и т. д. — все должно быть простым, ясным и понятным. Избегайте слепо называть имена, основанные на вашем собственном субъективном сознании.
Пример счетчика:
boolean test = chenkParamResult(req);
Положительный пример:
boolean isParamPass = chenkParamResult(req);
24. Магическое число
В повседневной разработке часто встречается такой код:
if(userType==1){
//doSth1
}else If( userType ==2){
//doSth2
}
...
это в коде1和2
Что они имеют в виду? Другой примерsetStatus(1)
середина1
Что это означает? Если вы видите такой плохой код, вы можете оптимизировать его двумя способами:
- Создайте новый постоянный класс, вставьте некоторые константы, управляйте ими единообразно и пишите комментарии;
- Создайте класс перечисленияи управлять связанными магическими числами вместе.
25. Запутанные вызовы иерархии кода
Наш код обычно делится наdao层
,service层
иcontroller层
.
- Уровень dao в основном выполняет работу уровня сохраняемости данных и имеет дело с базой данных.
- Сервисный уровень в основном отвечает за обработку бизнес-логики.
- Уровень контроллера отвечает за управление конкретными процессами бизнес-модуля.
Так вообщеcontroller
перечислитьservice
,service
мелодияdao
. Если вы видите в кодеcontroller
позвонить напрямуюdao
, то можно подумать, стоит ли его оптимизировать.Вопреки следующему:
@RestController("user")
public class UserController {
Autowired
private UserDao userDao;
@RequestMapping("/queryUserInfo")
public String queryUserInfo(String userName) {
return userDao.selectByUserName(userName);
}
}
Ссылка и спасибо
- Эксперименты по программному проектированию: общий запах плохого кода и примеры рефакторинга
- 22 неприятных запаха кода в одном предложении
- [Рефакторинг] Краткий обзор плохого запаха кода
- Code Smell
- «Рефакторинг для улучшения дизайна существующего кода»