Когда я вижу кучу, если еще, мое сердце разбито

Java

написать впереди


Я не знаю, сталкивались ли вы с вложением if else в виде «горизонтальной пирамиды»:

if (true) {
   if (true) {
       if (true) {
           if (true) {
               if (true) {
                   if (true) {

                   }
               }
           }
       }
   }
}

Я не преувеличиваю, я действительно сталкивался с этим! Вложенные 6 или 7 уровней, функция сотни строк, Джейн! прямой! Смотреть! умри! люди!

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

текст

Прежде чем говорить о моем методе, я мог бы также использовать пример, чтобы проиллюстрировать недостатки слишком большого количества вложений if else.

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

Сначала определите общий тип, общий компонент и общий класс обратного вызова:

private static final int TYPE_LINK = 0;
private static final int TYPE_IMAGE = 1;
private static final int TYPE_TEXT = 2;
private static final int TYPE_IMAGE_TEXT = 3;

public class ShareItem {
   int type;
   String title;
   String content;
   String imagePath;
   String link;
}

public interface ShareListener {

   int STATE_SUCC = 0;
   int STATE_FAIL = 1;

   void onCallback(int state, String msg);
}

Хорошо, тогда определите интерфейс обмена и поделитесь каждым типом отдельно, это нормально:

public void share (ShareItem item, ShareListener listener) {
   if (item != null) {
       if (item.type == TYPE_LINK) {
           // 分享链接
           if (!TextUtils.isEmpty(item.link) && !TextUtils.isEmpty(item.title)) {
               doShareLink(item.link, item.title, item.content, listener);
           } else {
               if (listener != null) {
                   listener.onCallback(ShareListener.STATE_FAIL, "分享信息不完整");
               }
           }
       } else if (item.type == TYPE_IMAGE) {
           // 分享图片
           if (!TextUtils.isEmpty(item.imagePath)) {
               doShareImage(item.imagePath, listener);
           } else {
               if (listener != null) {
                   listener.onCallback(ShareListener.STATE_FAIL, "分享信息不完整");
               }
           }
       } else if (item.type == TYPE_TEXT) {
           // 分享文本
           if (!TextUtils.isEmpty(item.content)) {
               doShareText(item.content, listener);
           } else {
               if (listener != null) {
                   listener.onCallback(ShareListener.STATE_FAIL, "分享信息不完整");
               }
           }
       } else if (item.type == TYPE_IMAGE_TEXT) {
           // 分享图文
           if (!TextUtils.isEmpty(item.imagePath) && !TextUtils.isEmpty(item.content)) {
               doShareImageAndText(item.imagePath, item.content, listener);
           } else {
               if (listener != null) {
                   listener.onCallback(ShareListener.STATE_FAIL, "分享信息不完整");
               }
           }
       } else {
           if (listener != null) {
               listener.onCallback(ShareListener.STATE_FAIL, "不支持的分享类型");
           }
       }
   } else {
       if (listener != null) {
           listener.onCallback(ShareListener.STATE_FAIL, "ShareItem 不能为 null");
       }
   }
}

На этом этапе создается простая модель совместного использования. Есть проблема? Честно говоря, если нет погони, то и проблемы действительно нет, по крайней мере, мысли ясны. Но что будет через неделю? А через месяц? Или через год? Метод share имеет 15 ветвей, а это значит, что каждый раз, когда вы оглядываетесь на код, вам приходится превращать свой мозг в крохотный процессор и рассматривать 15 кейсов. Если есть ошибка, вы должны рассмотреть 15 случаев и протестировать все 15 случаев. А если нужно добавить еще раздачу небольших видео сейчас, то надо добавить еще 3 ветки, и надо менять код, а он вообще не "открывается-закрывается". Более того, если последующие проекты будут переданы другим для доработки, а другие превратят свой мозг в процессоры, чтобы обдумать роль каждой ветки, я уверен, что 80% людей будут жаловаться на код.

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

Нулевое оценочное суждение

деловое суждение

государственный приговор

Почти все предприятия неотделимы от этих суждений, что приводит к слишком большому количеству вложений. Нет ли способа решить это? Ответ определенно нет.

Я написал приведенный выше код на java, а java-программистов суждение о нулевом значении просто раздражает и утомляет. Приведенный выше код должен определять, пуст ли слушатель каждый раз, когда он выполняет обратный вызов, и определять, пуст ли переданный пользователем объект ShareItem, а также определять, пусты ли поля в объекте ShareItem...

В этой ситуации мой подход прост: многослойность интерфейса.

Уменьшить, если еще метод 1: многослойность интерфейса

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

Давай, посмотри на код более интуитивно:

public void share(ShareItem item, ShareListener listener) {
   if (item == null) {
       if (listener != null) {
           listener.onCallback(ShareListener.STATE_FAIL, "ShareItem 不能为 null");
       }
       return;
   }

   if (listener == null) {
       listener = new ShareListener() {
           @Override
           public void onCallback(int state, String msg) {
               Log.i("DEBUG", "ShareListener is null");
           }
       };
   }

   shareImpl(item, listener);
}

private void shareImpl (ShareItem item, ShareListener listener) {
   if (item.type == TYPE_LINK) {
       // 分享链接
       if (!TextUtils.isEmpty(item.link) && !TextUtils.isEmpty(item.title)) {
           doShareLink(item.link, item.title, item.content, listener);
       } else {
           listener.onCallback(ShareListener.STATE_FAIL, "分享信息不完整");
       }
   } else if (item.type == TYPE_IMAGE) {
       // 分享图片
       if (!TextUtils.isEmpty(item.imagePath)) {
           doShareImage(item.imagePath, listener);
       } else {
           listener.onCallback(ShareListener.STATE_FAIL, "分享信息不完整");
       }
   } else if (item.type == TYPE_TEXT) {
       // 分享文本
       if (!TextUtils.isEmpty(item.content)) {
           doShareText(item.content, listener);
       } else {
           listener.onCallback(ShareListener.STATE_FAIL, "分享信息不完整");
       }
   } else if (item.type == TYPE_IMAGE_TEXT) {
       // 分享图文
       if (!TextUtils.isEmpty(item.imagePath) && !TextUtils.isEmpty(item.content)) {
           doShareImageAndText(item.imagePath, item.content, listener);
       } else {
           listener.onCallback(ShareListener.STATE_FAIL, "分享信息不完整");
       }
   } else {
       listener.onCallback(ShareListener.STATE_FAIL, "不支持的分享类型");
   }
}

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

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

Уменьшить, если еще метод 2: полиморфизм

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

public abstract class ShareItem {
   int type;

   public ShareItem(int type) {
       this.type = type;
   }

   public abstract void doShare(ShareListener listener);
}

public class Link extends ShareItem {
   String title;
   String content;
   String link;

   public Link(String link, String title, String content) {
       super(TYPE_LINK);
       this.link = !TextUtils.isEmpty(link) ? link : "default";
       this.title = !TextUtils.isEmpty(title) ? title : "default";
       this.content = !TextUtils.isEmpty(content) ? content : "default";
   }

   @Override
   public void doShare(ShareListener listener) {
       // do share
   }
}

public class Image extends ShareItem {
   String imagePath;

   public Image(String imagePath) {
       super(TYPE_IMAGE);
       this.imagePath = !TextUtils.isEmpty(imagePath) ? imagePath : "default";
   }

   @Override
   public void doShare(ShareListener listener) {
       // do share
   }
}

public class Text extends ShareItem {
   String content;

   public Text(String content) {
       super(TYPE_TEXT);
       this.content = !TextUtils.isEmpty(content) ? content : "default";
   }

   @Override
   public void doShare(ShareListener listener) {
       // do share
   }
}

public class ImageText extends ShareItem {
   String content;
   String imagePath;

   public ImageText(String imagePath, String content) {
       super(TYPE_IMAGE_TEXT);
       this.imagePath = !TextUtils.isEmpty(imagePath) ? imagePath : "default";
       this.content = !TextUtils.isEmpty(content) ? content : "default";
   }

   @Override
   public void doShare(ShareListener listener) {
       // do share
   }
}

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

После реализации полиморфизма общий интерфейс стал намного проще:

public void share(ShareItem item, ShareListener listener) {
   if (item == null) {
       if (listener != null) {
           listener.onCallback(ShareListener.STATE_FAIL, "ShareItem 不能为 null");
       }
       return;
   }

   if (listener == null) {
       listener = new ShareListener() {
           @Override
           public void onCallback(int state, String msg) {
               Log.i("DEBUG", "ShareListener is null");
           }
       };
   }

   shareImpl(item, listener);
}

private void shareImpl (ShareItem item, ShareListener listener) {
   item.doShare(listener);
}

Хе-хе, что, внутренний интерфейс исчез без if else, разве это не круто? Если эта функция обмена является функцией вашего собственного приложения, а не стороннего SDK, здесь нет проблем. Однако, если третья сторона разделяет функции SDK, количество классов, предоставляемых пользователю, значительно увеличится (подклассы каждого ShareItem эквивалентны выдаче пользователю if else), а стоимость доступа пользователя увеличится, что нарушает особый принцип «Дими».

Обработка этой ситуации также очень проста, просто снова инкапсулируйте слой. Уменьшите права доступа подкласса ShareItem, определите несколько методов в основном классе, доступном пользователю, и помогите пользователю создать определенный тип общего ресурса внутри, чтобы пользователю не нужно было знать конкретный класс:

public ShareItem createLinkShareItem(String link, String title, String content) {
   return new Link(link, title, content);
}

public ShareItem createImageShareItem(String ImagePath) {
   return new Image(ImagePath);
}

public ShareItem createTextShareItem(String content) {
   return new Text(content);
}

public ShareItem createImageTextShareItem(String ImagePath, String content) {
   return new ImageText(ImagePath, content);
}

Или некоторые люди скажут, что пользователям нужно знать еще несколько методов. Я лично считаю, что лучше дать пользователям знать еще несколько методов, чем знать еще несколько классов, но имя метода может с первого взгляда указать на намерение, а стоимость все еще довольно мала, что приемлемо.

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

Уменьшите, если еще Метод 3: используйте карту вместо оператора ветвления

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

private Map<Integer, Class<? extends ShareItem>> map = new HashMap<>();

private void init() {
   map.put(TYPE_LINK, Link.class);
   map.put(TYPE_IMAGE, Image.class);
   map.put(TYPE_TEXT, Text.class);
   map.put(TYPE_IMAGE_TEXT, ImageText.class);
}

public ShareItem createShareItem(int type) {
   try {
       Class<? extends ShareItem> shareItemClass = map.get(type);
       return shareItemClass.newInstance();
   } catch (Exception e) {
       return new DefaultShareItem(); // 返回默认实现,不要返回null
   } 
}

Этот метод имеет свои преимущества и недостатки по сравнению с вышеуказанными методами, выбор за вами~

напиши в конце

Вы что-нибудь из этого получили? Кратко опишите метод сокращения if else

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

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

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

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