Resuelva el problema de demasiados si ... más en el código

Prólogo

if ... else es una característica imprescindible para todos los lenguajes de programación de alto nivel. Pero el código en realidad a menudo tiene demasiados si ... de lo contrario. Aunque si ... más es necesario, el mal uso de si ... más causará un gran daño a la legibilidad y facilidad de mantenimiento del código, y luego pondrá en peligro todo el sistema de software. Ahora han aparecido muchas nuevas tecnologías y nuevos conceptos en el campo del desarrollo de software, pero la forma básica del programa de if ... else no ha cambiado mucho. Usar bien if ... else es muy significativo no solo para el presente, sino también para el futuro. Hoy veremos cómo "matar" si ... más en el código, y devolveremos el código para que sea refrescante.

Pregunta 1: demasiados si ... más

Problema de rendimiento

 

Si ... de lo contrario, se puede abstraer demasiado código como el siguiente código. Solo se enumeran 5 ramas lógicas, pero en el trabajo real, podemos ver que un método contiene 10, 20 o más ramas lógicas. Además, demasiados si ... más generalmente irán acompañados de otros dos problemas: expresiones lógicas complejas y si ... más están demasiado anidados. Para las dos últimas preguntas, este artículo se presentará en las dos secciones siguientes. Esta sección primero discute la situación de demasiados si ... si no.

 

if (condition1) {

} else if (condition2) {

} else if (condition3) {

} else if (condition4) {

} else {

}

 

Por lo general, hay demasiados métodos if ... else, y la legibilidad y la escalabilidad generalmente no son buenas. Desde la perspectiva del diseño de software, hay demasiados si ... si no, en el código a menudo significa que este código viola el principio de responsabilidad única y el principio de apertura y cierre. Porque en los proyectos reales, la demanda cambia constantemente y también están surgiendo nuevas demandas. Por lo tanto, la escalabilidad del sistema de software es muy importante. El mayor significado de resolver si ... de lo contrario, demasiados problemas es a menudo mejorar la escalabilidad del código.

 

Como arreglar

 

A continuación, veremos cómo resolver el problema de demasiados si ... si no. A continuación he enumerado algunas soluciones.

 

  1. Mesa conducida

  2. Cadena de responsabilidad

  3. Impulsado por anotaciones

  4. Impulsado por eventos

  5. Máquina de estados finitos

  6. Opcional

  7. Afirmar

  8. Polimorfismo

 

Método uno: unidad de tabla

 

Introduccion

 

Para if ... else código con un modo de expresión lógica fija, puede expresar la expresión lógica en una tabla a través de una determinada relación de mapeo; luego use el método de búsqueda de tabla para encontrar la función de procesamiento correspondiente a una entrada, use este procesamiento Función para la operación.

 

Escena aplicable

Modo de expresión lógica corregido si ... más

 

Implementación y ejemplos

 

if (param.equals(value1)) {
    doAction1(someParams);
} else if (param.equals(value2)) {
    doAction2(someParams);
} else if (param.equals(value3)) {
    doAction3(someParams);
}
// ...

 

Reconfigurable como

 

Map<?, Function<?> action> actionMappings = new HashMap<>(); // 这里泛型 ? 是为方便演示,实际可替换为你需要的类型

// When init
actionMappings.put(value1, (someParams) -> { doAction1(someParams)});
actionMappings.put(value2, (someParams) -> { doAction2(someParams)});
actionMappings.put(value3, (someParams) -> { doAction3(someParams)});

// 省略 null 判断
actionMappings.get(param).apply(someParams);

 

El ejemplo anterior utiliza Java 8 Lambda e Functional Interface, que no se explicarán aquí.

La relación de mapeo entre tablas puede ser centralizada o descentralizada, es decir, cada clase de procesamiento se registra a sí misma. También se puede expresar en forma de archivos de configuración. En resumen, hay muchas formas.

 

Todavía hay algunos problemas. La expresión condicional no es tan simple como en el ejemplo anterior, pero con una pequeña transformación, la unidad de tabla también se puede aplicar. Lo siguiente toma prestado un ejemplo de cálculo de impuestos de "Programming Pearl":

 

if income <= 2200
  tax = 0
else if income <= 2700
  tax = 0.14 * (income - 2200)
else if income <= 3200
  tax = 70 + 0.15 * (income - 2700)
else if income <= 3700
  tax = 145 + 0.16 * (income - 3200)
......
else
  tax = 53090 + 0.7 * (income - 102200)

 

Para el código anterior, de hecho, simplemente extraiga la fórmula de cálculo de impuestos, extraiga el estándar de cada archivo en una tabla y agregue un ciclo. El código después de la refactorización específica no se da, todos piensan por sí mismos.

 

Método 2: modelo de cadena de responsabilidad

 

Introduccion

 

Cuando la expresión condicional en if ... else es flexible y no puede resumirse en una tabla y juzgarse de manera unificada, el juicio de la condición debe transferirse a cada componente funcional. En forma de cadena, estos componentes están conectados en serie para formar una función completa.

 

Escena aplicable

 

Las expresiones condicionales son flexibles y no hay una forma unificada.

 

Implementación y ejemplos

 

El modo de cadena de responsabilidad se puede ver en la realización de las funciones de Filtro e Interceptor del marco de código abierto. Echemos un vistazo al modo de uso general:

 

Antes de refactorizar:

 

public void handle(request) {
    if (handlerA.canHandle(request)) {
        handlerA.handleRequest(request);
    } else if (handlerB.canHandle(request)) {
        handlerB.handleRequest(request);
    } else if (handlerC.canHandle(request)) {
        handlerC.handleRequest(request);
    }
}
 

Después de refactorizar:

 

public void handle(request) {
  handlerA.handleRequest(request);
}

public abstract class Handler {
  protected Handler next;
  public abstract void handleRequest(Request request);
  public void setNext(Handler next) { this.next = next; }
}

public class HandlerA extends Handler {
  public void handleRequest(Request request) {
    if (canHandle(request)) doHandle(request);
    else if (next != null) next.handleRequest(request);
  }
}
 

Por supuesto, el código antes de refactorizar en el ejemplo hizo alguna extracción y refactorización de clases y métodos para expresarlo claramente. En realidad, es más una implementación de código en mosaico.

 

Nota: El modo de control de la cadena de responsabilidad.

 

El modelo de cadena de responsabilidad tendrá algunas formas diferentes en el proceso de implementación específico. Desde la perspectiva del control de llamadas en cadena, se puede dividir en control externo y control interno.

 

El control externo no es flexible, pero reduce la dificultad de implementación. La implementación específica en un anillo de la cadena de responsabilidad no necesita considerar la llamada al siguiente anillo, ya que el control unificado externo. Pero el control externo general no puede realizar llamadas anidadas. Si hay llamadas anidadas y desea controlar la llamada de la cadena de responsabilidad desde el exterior, será un poco más complicado de implementar. Para obtener más información, consulte el método de implementación del mecanismo Spring Web Interceptor.

 

El control interno es más flexible, y la implementación específica puede decidir si llamar al próximo anillo de la cadena. Pero si el modo de control de llamadas es fijo, dicha implementación es inconveniente para el usuario.

 

Hay muchas variaciones de patrones de diseño en uso específico, todos deben ser flexibles

 

Método 3: impulsado por anotaciones

 

Introduccion

 

Las anotaciones de Java (o mecanismos similares en otros lenguajes) definen las condiciones para ejecutar un método. Cuando se ejecuta el programa, se decide si llamar a este método comparando si las condiciones definidas en los comentarios participantes coinciden. En una implementación específica, se puede implementar en forma de tabla o cadena de responsabilidad.

 

Escena aplicable

 

Es adecuado para escenarios con muchas ramas condicionales y altos requisitos de escalabilidad del programa y facilidad de uso. Por lo general, es una función central que a menudo encuentra nuevos requisitos en un sistema.

 

Implementación y ejemplos

 

El uso de este patrón se puede ver en muchos marcos, como el Spring MVC común. Debido a que estos marcos se usan con mucha frecuencia y las demostraciones se pueden ver en todas partes, aquí no hay un código de demostración más específico.

 

El enfoque de este modelo es la implementación. Los marcos existentes se utilizan para implementar funciones en un campo específico, como MVC. Por lo tanto, si el sistema empresarial adopta este modo, debe implementar funciones básicas relacionadas por sí mismo. Implicará principalmente tecnologías como la reflexión y la cadena de responsabilidad. La implementación específica no se demostrará aquí.

 

Método 4: impulsado por eventos

 

Introduccion

 

Al asociar diferentes tipos de eventos y los mecanismos de procesamiento correspondientes, se logra una lógica compleja, mientras se logra el objetivo de desacoplamiento.

 

Escena aplicable

 

Desde un punto de vista teórico, el evento basado en eventos se puede considerar como un tipo de mesa, pero desde un punto de vista práctico, los eventos y la mesa mencionados anteriormente son diferentes en muchos aspectos. Específicamente:

 

  1. La función de tabla suele ser una relación de uno a uno, la de eventos suele ser de uno a muchos;

  2. En la tabla, la activación y la ejecución suelen ser muy dependientes; en la función de eventos, la activación y la ejecución son débilmente dependientes

 

Es la diferencia entre los dos anteriores lo que conduce a la diferencia en los escenarios aplicables de los dos. Específicamente, el evento se puede utilizar para activar el inventario, la logística, los puntos y otras funciones, como la finalización del pago del pedido.

 

Implementación y ejemplos

 

En términos de implementación, la unidad de práctica independiente se puede implementar utilizando marcos como Guava y Spring. Distribuido generalmente se logra a través de varias colas de mensajes. Pero debido a que la discusión principal aquí es eliminar si ... de lo contrario, está orientado principalmente al dominio del problema de una sola máquina. Debido a la tecnología específica involucrada, este código de modo no se demuestra.

 

Método 5: máquina de estados finitos

 

Introduccion

 

Las máquinas de estado finito a menudo se llaman máquinas de estado (el concepto de máquinas de estado infinito puede ignorarse). Primero cite la definición en Wikipedia:

La máquina de estados finitos (inglés: máquina de estados finitos, abreviatura: FSM), denominada máquina de estados, es un modelo matemático que representa un número finito de estados y el comportamiento de las transiciones y acciones entre estos estados.

De hecho, la máquina de estados también puede considerarse como un tipo de tabla, que en realidad es una correspondencia entre la combinación del estado actual y el evento y la función de procesamiento. Por supuesto, habrá un proceso de transición de estado después de que el proceso sea exitoso.

 

Escena aplicable

 

Aunque los servicios de back-end de Internet ahora enfatizan la apatridia, esto no significa que el diseño de la máquina de estado no pueda usarse. De hecho, en muchos escenarios, como la pila de protocolos, el procesamiento de pedidos y otras funciones, la máquina de estado tiene sus ventajas naturales. Porque estas situaciones existen naturalmente en el estado y la circulación estatal.

 

Implementación y ejemplos

 

Para implementar el diseño de la máquina de estado, debe tener un marco correspondiente, que debe implementar al menos una función de definición de máquina de estado y la función de enrutamiento de llamadas correspondiente. La definición de máquina de estados puede usar DSL o anotaciones. El principio no es complicado, y los estudiantes que dominan funciones como la anotación y la reflexión deberían ser fáciles de implementar.

 

Tecnología de referencia:

  • Apache Mina State Machine
    Apache Mina framework, aunque no es tan bueno como Netty en el campo de IO framework, pero proporciona una función de máquina de estado. https://mina.apache.org/mina-project/userguide/ch14-state-machine/ch14-state-machine.html. Los estudiantes que tienen sus propias funciones de máquina de estado pueden consultar su código fuente.


  • Hay muchos subproyectos de Spring State Machine Spring, entre los cuales hay un marco de máquina de estado que no muestra montañas ni agua: Spring State Machine https://projects.spring.io/spring-statemachine/. Se puede definir de dos maneras: DSL y anotación.

 

El marco anterior solo sirve como referencia: si hay proyectos específicos involucrados, las funciones centrales de la máquina de estado deben implementarse en función de las características del negocio.

 

Método 6: opcional

Introduccion

 

Una parte de if ... else en el código Java es causada por una comprobación no nula. Por lo tanto, reducir el if ... else generado por esta parte también puede reducir el número total de if ... else.

 

Java introdujo la clase Opcional de 8 para representar objetos que pueden estar vacíos. Esta clase proporciona muchos métodos para operaciones relacionadas, que se pueden usar para eliminar si ... de lo contrario. Los frameworks de código abierto Guava y Scala también proporcionan una funcionalidad similar.

 

Usar escena

 

Hay más si ... más para un juicio no vacío.

 

Implementación y ejemplos

 

Escritura tradicional:

 

String str = "Hello World!";
if (str != null) {
    System.out.println(str);
} else {
    System.out.println("Null");
}

 

Después de usar Opcional:

 

1 Optional<String> strOptional = Optional.of("Hello World!");
2 strOptional.ifPresentOrElse(System.out::println, () -> System.out.println("Null"));

 

Hay muchos métodos para Opcional, que no se introducen aquí uno por uno. Pero tenga en cuenta que no use los métodos get () e isPresent (), de lo contrario es lo mismo que tradicional si ... de lo contrario.

 

Extensión: Kotlin Null Safety

 

Kotlin viene con una característica llamada Seguridad nula:

bob? .department? .head? .name

 

Para una llamada encadenada, en lenguaje Kotlin, puede pasar? Para evitar excepciones de puntero nulo. Si un anillo es nulo, el valor de toda la expresión de la cadena es nulo.

 

Método 7: modo de afirmación

 

Introduccion

 

El último método es adecuado para resolver si ... de lo contrario se debe a escenarios de inspección no vacíos. Escenarios similares tienen varias verificaciones de parámetros, como que la cadena no está vacía, etc. Muchas bibliotecas de framework, como Spring y Apache Commons, proporcionan herramientas para implementar esta función común. Por lo tanto, no tiene que escribir si ... de lo contrario usted mismo.

  • Validar clase en Apache Commons Lang: https://commons.apache.org/proper/commons-lang/javadocs/api-3.1/org/apache/commons/lang3/Validate.html

  • Clase Assert de Spring: https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/util/Assert.html

 

Usar escena

 

Usualmente se usa para varios parámetros de verificación

 

Extensión: Validación de frijoles

 

Similar al método anterior, introduzca el patrón Assert e introduzca una tecnología con un efecto similar: la validación de frijoles. Bean Validation es una de las especificaciones de Java EE. Bean Validation utiliza anotaciones para definir estándares de validación en Java Beans y luego los valida de manera uniforme a través del marco. También puede desempeñar un papel en la reducción si ... de lo contrario.

 

Método 8: polimorfismo

Introduccion

 

El uso de polimorfismo orientado a objetos también puede eliminar si ... si no. En el libro de refactorización de código, esto también se presenta:

https://refactoring.com/catalog/replaceConditionalWithPolymorphism.html

 

Usar escena

 

El ejemplo dado en el enlace es relativamente simple y no puede reflejar un escenario específico adecuado para la eliminación polimórfica si ... de lo contrario. En general, cuando varios métodos en una clase tienen juicios similares si ... más en el ejemplo, y las condiciones son las mismas, entonces puede considerar usar el polimorfismo para eliminar si ... más.

 

Al mismo tiempo, el uso de polimorfismo no elimina si ... de lo contrario por completo. En cambio, la fusión if ... else se transfirió a la etapa de creación de objetos. En la etapa de creación de if .., podemos usar el método descrito anteriormente.

 

Resumen

 

La sección anterior presenta los problemas causados ​​por demasiados if ... else y las soluciones correspondientes. Además de los métodos descritos en esta sección, existen otros métodos. Por ejemplo, en el libro "Refactorización y patrones", se presentan tres métodos: "reemplazar la lógica condicional con la estrategia", "reemplazar la declaración condicional de cambio de estado con el estado" y "reemplazar el planificador condicional con el comando". El "modo de comando" es aproximadamente el mismo que el enfoque "basado en tablas" en este artículo. Los otros dos métodos, porque se han explicado en detalle en el libro "Refactorización y patrones", no se repetirán aquí.

 

Cuándo usar qué método depende del tipo de problema que enfrenta. Algunos de los escenarios aplicables descritos anteriormente son solo sugerencias, y los desarrolladores deben considerar más.

Pregunta 2: si ... más está anidado demasiado profundamente

Problema de rendimiento

 

Si ... más no suele ser el problema más grave. Algunos códigos if ... else no solo son grandes en número, sino también anidados entre if ... else es muy profundo y complejo, lo que resulta en una mala legibilidad del código y, naturalmente, es difícil de mantener.

 

if (condition1) {
    action1();
    if (condition2) {
        action2();
        if (condition3) {
            action3();
            if (condition4) {
                action4();
            }
        }
    }
}

 

Si ... else está demasiado anidado, afectará seriamente la legibilidad del código. Por supuesto, habrá dos problemas mencionados en la sección anterior.

 

Como arreglar

 

El método introducido en la sección anterior también se puede utilizar para resolver los problemas de esta sección, por lo que para el método anterior, esta sección no se repetirá. Esta sección se enfoca en algunos métodos que no reducirán el número de if ... else, pero mejorarán la legibilidad del código:

 

  1. Método de extracción

  2. Declaración Wei

 

Método 1: método de extracción

 

Introduccion

 

El método de extracción es un medio de reconstrucción de código. La definición es fácil de entender, es decir, extraer un fragmento de código y colocarlo en otro método definido por separado. Pedir prestado

Use la definición en https://refactoring.com/catalog/extractMethod.html:

 

Escena aplicable

if ... else es un código muy anidado, que a menudo es poco legible. Por lo tanto, antes de la refactorización a gran escala, se necesitan ajustes menores para mejorar la legibilidad del código. El método de extracción es el método de ajuste más utilizado.

 

Implementación y ejemplos

 

Antes de refactorizar:

 

public void add(Object element) {
  if (!readOnly) {
    int newSize = size + 1;
    if (newSize > elements.length) {
      Object[] newElements = new Object[elements.length + 10];
      for (int i = 0; i < size; i++) {
        newElements[i] = elements[i];
      }

      elements = newElements
    }
    elements[size++] = element;
  }
}

 

Después de refactorizar:

 

public void add(Object element) {
  if (readOnly) {
    return;
  }

  if (overCapacity()) {
    grow();
  }

  addElement(element);
}

 

Método 2: declaración de Wei

 

Introduccion

 

En la refactorización de código, hay un método llamado "usar la declaración de protección en lugar de la declaración condicional anidada" https://refactoring.com/catalog/replaceNestedConditionalWithGuardClauses.html. Mira directamente el código:

 

double getPayAmount() {
    double result;
    if (_isDead) result = deadAmount();
    else {
        if (_isSeparated) result = separatedAmount();
        else {
            if (_isRetired) result = retiredAmount();
            else result = normalPayAmount();
        };
    }
    return result;
}

 

Después de refactorizar

 

double getPayAmount() {
    if (_isDead) return deadAmount();
    if (_isSeparated) return separatedAmount();
    if (_isRetired) return retiredAmount();
    return normalPayAmount();
}

 

Usar escena

 

Cuando ve un método en el que un determinado bloque de código está completamente controlado por un if ... else, generalmente puede usar una declaración de guardia.

Pregunta 3: si ... de lo contrario, la expresión es demasiado complicada

Problema de rendimiento

 

El tercer problema causado por if ... else proviene de expresiones condicionales demasiado complicadas. Aquí hay un ejemplo simple: cuando las condiciones 1, 2, 3 y 4 son verdaderas y falsas, organice y combine los resultados de las siguientes expresiones.

 

1 if ((condition1 && condition2 ) || ((condition2 || condition3) && condition4)) {
2   
3 }
 

Creo que nadie quiere hacer lo anterior. La clave es, ¿cuál es el significado de esta gran expresión de Tuo? El punto es que cuando no se conoce el significado de una expresión, nadie quiere inferir su resultado.

 

Por lo tanto, la expresión es complicada y no necesariamente incorrecta. Pero las expresiones no son fáciles de entender.

 

Como arreglar

 

Para la expresión complicada de la expresión if ... else, se resuelve principalmente mediante extracción y movimiento en la reconstrucción del código. Debido a que estos métodos se introducen en el libro "Refactorización de código", no se repetirán aquí.

Resumen

Este artículo presenta 10 métodos (incluidas 12 expansiones) para eliminar y simplificar si ... de lo contrario. También hay algunos métodos, como eliminar si se realiza mediante el modo de estrategia, el modo de estado, etc. De lo contrario, también se presenta en el libro "Refactorización y modo".

 

Como se mencionó en el prefacio, if ... else es una parte importante del código, pero el uso excesivo e innecesario de if ... else afectará negativamente la legibilidad y escalabilidad del código, lo que afectará todo Sistema de software.

 

La capacidad de "matar" si ... de lo contrario refleja la capacidad integral del programador para utilizar la refactorización de software, patrones de diseño, diseño orientado a objetos, patrones arquitectónicos, estructuras de datos y otras tecnologías, y refleja el trabajo interno del programador. Si desea usar if ... else razonablemente, no puede hacerlo sin diseño o diseño excesivo. El uso integral y razonable de estas tecnologías requiere que los programadores exploren y resuman constantemente en su trabajo.

117 artículos originales publicados · 69 alabanzas · 10,000+ vistas

Supongo que te gusta

Origin blog.csdn.net/zsd0819qwq/article/details/105321890
Recomendado
Clasificación