Hay muchos if / else en el código, ¿qué plan de optimización tienes?

Punto de vista uno (espada espiritual):

        La iteración periódica es demasiado perezosa para optimizar, llegar a una demanda, agregar un si, con el tiempo, se convierte en una pirámide.

 

 

Cuando el código se ha vuelto demasiado complejo para mantenerlo, solo se puede refactorizar y optimizar. Entonces, ¿hay alguna solución para optimizar elegantemente estos if / else redundantes? 

 

1. Regreso por adelantado

Esta es la forma de revertir la condición de juicio. El código será más claro en la expresión lógica. Mira el siguiente código:

if (condition) {
 // do something
} else {
  return xxx;
}

 

if (!condition) {
  return xxx;

} 
// do something

 

2. Modo de estrategia

    Hay un escenario donde se utilizan diferentes lógicas según diferentes parámetros, de hecho, este escenario es muy común. La implementación más general:

if (strategy.equals("fast")) {
  // 快速执行
} else if (strategy.equals("normal")) {
  // 正常执行
} else if (strategy.equals("smooth")) {
  // 平滑执行
} else if (strategy.equals("slow")) {
  // 慢慢执行
}

Mirando el código anterior, hay 4 estrategias y dos esquemas de optimización.

      2.1 Polimorfismo

                  

interface Strategy {
  void run() throws Exception;
}

class FastStrategy implements Strategy {
    @Override
    void run() throws Exception {
        // 快速执行逻辑
    }
}

class NormalStrategy implements Strategy {
    @Override
    void run() throws Exception {
        // 正常执行逻辑
    }
}

class SmoothStrategy implements Strategy {
    @Override
    void run() throws Exception {
        // 平滑执行逻辑
    }
}

class SlowStrategy implements Strategy {
    @Override
    void run() throws Exception {
        // 慢速执行逻辑
    }
}

 

El objeto de estrategia específico se almacena en un mapa, la implementación después de la optimización

Strategy strategy = map.get(param);
strategy.run();

El esquema de optimización anterior tiene un inconveniente. Para obtener rápidamente la implementación de la estrategia correspondiente, se necesita un objeto de mapa para guardar la estrategia. Cuando se agrega una nueva estrategia, también debe agregarse manualmente al mapa, lo que es fácil de ser ignorado.

 

2.2 Enumeración

Encontré que muchos estudiantes no saben que los métodos se pueden definir en enumeraciones, aquí definimos una enumeración que representa el estado y también podemos implementar un método de ejecución.

public enum Status {
    NEW(0) {
      @Override
      void run() {
        //do something  
      }
    },
    RUNNABLE(1) {
      @Override
       void run() {
         //do something  
      }
    };

    public int statusCode;

    abstract void run();

    Status(int statusCode){
        this.statusCode = statusCode;
    }
}

Redefinir la enumeración de estrategias

public enum Strategy {
    FAST {
      @Override
      void run() {
        //do something  
      }
    },
    NORMAL {
      @Override
       void run() {
         //do something  
      }
    },

    SMOOTH {
      @Override
       void run() {
         //do something  
      }
    },

    SLOW {
      @Override
       void run() {
         //do something  
      }
    };
    abstract void run();
}

El código después de la optimización por enumeración es el siguiente

Strategy strategy = Strategy.valueOf(param);
strategy.run();

3. Aprenda a usar Opcional

Opcional se usa principalmente para juicios no vacíos Debido a que es una característica nueva de jdk8, no se usa mucho, pero es realmente genial de usar. Antes de usar:

if (user == null) {
    //do action 1
} else {
    //do action2
}

Si el usuario que ha iniciado sesión está vacío, ejecute action1; de lo contrario, ejecute la acción 2. Después de usar la optimización opcional, haga que la verificación no nula sea más elegante y reduzca indirectamente si las operaciones

Optional<User> userOptional = Optional.ofNullable(user);
userOptional.map(action1).orElse(action2);

4. Consejos para arreglos

Según la explicación de Google, este es un modo de programación llamado método basado en tablas. La esencia es consultar información de la tabla en lugar de declaraciones lógicas. Por ejemplo, hay un escenario en el que el número de días del mes se obtiene por mes. solo se utiliza como demostración de un caso y los datos no son rigurosos.

Realización general:

int getDays(int month){
    if (month == 1)  return 31;
    if (month == 2)  return 29;
    if (month == 3)  return 31;
    if (month == 4)  return 30;
    if (month == 5)  return 31;
    if (month == 6)  return 30;
    if (month == 7)  return 31;
    if (month == 8)  return 31;
    if (month == 9)  return 30;
    if (month == 10)  return 31;
    if (month == 11)  return 30;
    if (month == 12)  return 31;
}

Código optimizado

int monthDays[12] = {31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
int getDays(int month){
    return monthDays[--month];
}

En caso contrario, como declaración condicional indispensable en todos los lenguajes de programación, será ampliamente utilizada en programación. Por lo general, se recomienda que el anidamiento no exceda los tres niveles. Si hay demasiado anidado en un fragmento de código, la legibilidad del código disminuirá drásticamente y la dificultad del mantenimiento posterior mejorará considerablemente.

 

Vista dos (control de tecnología de TI):

 No preste demasiada atención a la cantidad de capas if / else, pero preste atención a si la semántica de la interfaz es lo suficientemente clara; simplemente reduzca la cantidad de capas if / else y luego elimine un montón de do_logic1, do_logic2 .. .Estas interfaces son inútiles.

El proceso de ejecución de cualquier interfaz se puede expresar como: entrada + estado interno -> salida, lo discutiremos en las siguientes situaciones:

          La entrada, el estado interno y la salida son todos simples, pero la lógica intermedia es complicada. Por ejemplo, un programa de cálculo numérico cuidadosamente optimizado puede necesitar adoptar diferentes estrategias en diferentes rangos de valores de acuerdo con la entrada, y hay muchas lógicas para lidiar con los valores límite que causarán problemas (como la división por 0). caso, si es inevitable una gran cantidad de / else. Separar algunos métodos internos según los pasos es útil, pero no puede resolver el problema por completo. Lo mejor que se puede hacer en este caso es escribir un documento detallado, partiendo del modelo matemático más primitivo, y luego mostrar qué tipo de estrategia de cálculo tomar bajo qué circunstancias, cómo derivar la estrategia, conocer la forma específica utilizada en el y luego Agregar notas a todo el método y adjuntar la dirección del documento, y agregar notas a cada rama para indicar qué fórmula corresponde al documento. En este caso, aunque el método es complicado, la semántica es clara. Si no modificas la implementación, puedes entender la semántica. Si quieres modificar la implementación, debes consultar la fórmula en el documento de control.

         La entrada es demasiado complicada, como la entrada con un montón de parámetros diferentes, o hay varios indicadores extraños, cada indicador tiene una función diferente. En este caso, el nivel de abstracción de la interfaz debe mejorarse primero: si la interfaz tiene múltiples funciones diferentes, debe dividirse en diferentes interfaces; si la interfaz se divide en diferentes ramas de acuerdo con diferentes parámetros, estos parámetros y los correspondientes Las ramas deben empaquetarse en el Adaptador y usarse El parámetro se reescribe en la interfaz del Adaptador, y se ingresan diferentes implementaciones de acuerdo con el tipo de Adaptador pasado; si hay una relación compleja de conversión de parámetros dentro de la interfaz, es necesario ser reescrito en una tabla de búsqueda. El principal problema en este caso es la abstracción de la propia interfaz, con una abstracción más clara, naturalmente, no hay tantas implementaciones if / else.

         La salida es demasiado complicada Para evitar problemas, se calculan demasiadas cosas en un proceso y se agregan un montón de indicadores para controlar si se calcula o no el rendimiento. En este caso, debe dividir decisivamente el método en varios métodos diferentes, y cada método solo devuelve lo que necesita. ¿Qué pasa si hay resultados internos compartidos entre diferentes cálculos? Si este cálculo de resultado interno no forma un cuello de botella, simplemente extraiga el método interno y llámelo por separado en diferentes procesos; si desea evitar el cálculo doble, puede agregar un objeto de caché adicional como parámetro. El contenido de la caché no es transparente para el usuario, y el usuario solo garantiza El mismo objeto de caché se puede usar para la misma entrada. En el cálculo, el resultado intermedio se guarda en la caché. Antes del siguiente cálculo, verifique si ya hay un resultado obtenido, lo que puede evitar doble cálculo.

        El estado interno es demasiado complicado. Primero verifique si la configuración del estado es razonable. ¿Hay algo que deba usarse como parámetro de entrada en el estado interno (por ejemplo, se usa para pasar parámetros implícitamente entre dos llamadas de métodos diferentes)? En segundo lugar, ¿qué aspectos están controlados por estos estados y se pueden agrupar e implementar en diferentes StateManagers? En tercer lugar, dibuje un diagrama de transición de estado, intente dividir el estado interno en ramas de un solo nivel y luego impleméntelas en métodos como on_xxx_state, y luego llámelos a través de un conmutador de un solo nivel o una tabla de búsqueda.

De hecho, lo que generalmente necesita optimizarse es la abstracción de la interfaz general, en lugar de la realización de una sola interfaz. La realización poco clara de una sola interfaz generalmente se debe a que la implementación de la interfaz y los requisitos están estructurados de manera diferente.

 

Supongo que te gusta

Origin blog.csdn.net/u011694328/article/details/113102890
Recomendado
Clasificación