2016-05-18 3 views
1

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

public Result nonPureMethod(String param1, String param2){ 
    this.current_status = "running"; 
    String s1 = step1(param1, param2); 
    this.logger.log("About to do step 2, this could take while"); 
    String s2 = step2(s1); 
    this.logger.log("Completed step 2"); 
    String s3 = step3(s2); 
    this.notifyOtherObject(s3); 
    if (this.UserPressedEmergencyStop){ this.current_status = "stopped"; return; } 
    String s4 = step4(s3); 
    this.current_status = "completed"; 
    this.saveFile(s4); 
    return new Result(s4); 
} 

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

public static Result pureMethod(String param1, String param2){ 
    String s1 = step1(param1, param2); 
    String s2 = step2(s1); 
    String s3 = step3(s2); 
    String s4 = step4(s3); 
    return new Result(s4); 
} 

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

В настоящее время я использую Java 8, но я думаю, что эта проблема довольно общая. До сих пор я думал о двух подходах к решению проблемы. Во-первых, я мог бы передать логическое значение методу: «runSideEffects». Если false, просто пропустите код, который запускает побочные эффекты. Альтернативным и более гибким решением было бы изменить функцию, требуя функции лямбда, переданные в качестве параметров, и вызывать их вместо вызова побочных эффектов. Например, в качестве параметра можно передать такой метод, как «void log (String msg)». Производственный вызов метода может передать функцию, которая будет записывать сообщение в регистратор. Другие вызовы могут передавать метод, который фактически ничего не делает при вызове журнала (msg). Ни одно из этих решений не чувствует себя прекрасно, поэтому я прошу сообщество предложить предложения.

+3

Я не вижу, где ничего нового в Java 8 действительно поможет вам с этим методом. То, что вы предлагаете, - это [шаблон страха] (https://en.wikipedia.org/wiki/Strategy_pattern), где побочные эффекты против побочных эффектов = стратегии. Это то, что вы могли бы сделать все время (и я не могу себе представить, что это сделает ваш код лучше tbh) – zapl

+1

Имея две реализации, определенно, способ пойти (как это предложил @zapl). Но вам нужно, чтобы сохранить статус и сохранить файл правильно? – TriCore

+0

Кроме того, прочитайте Эффективный элемент Java «Стремитесь к отказу от атомарности». У этого есть несколько советов по организации кода для минимизации сбоев, которые могут оставить вещи в противоречивом состоянии. –

ответ

0

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

теперь у меня есть это на разных языках в качестве хранилища Github: https://github.com/daveroberts/sideeffects

package foo; 

import java.util.function.BiConsumer; 
import java.util.function.BooleanSupplier; 
import java.util.function.Consumer; 

public class SideEffects{ 
    public static void main(String args[]){ 
    System.out.println("Calling logic as a pure function"); 
    String result = logic("param1", "param2", null, null, null, null, null); 
    System.out.println("Result is "+result); 
    System.out.println(); 

    System.out.println("Calling logic as a regular function"); 
    result = logic("param1", "param2", 
     (level,msg)->{System.out.println("LOG ["+level+"]["+msg+"]");}, 
     (status)->{System.out.println("Current status set to: "+status); }, 
     (obj)->{System.out.println("Called notify message on object: "+obj.toString());}, 
     ()->{boolean dbLookupResult = false; return dbLookupResult;}, 
     (info)->{System.out.println("Info written to file [["+info+"]]");} 
     ); 
    System.out.println("Result is "+result); 
    } 

    public static String logic(String param1, String param2, 
     BiConsumer<String, String> log, 
     Consumer<String> setStatus, 
     Consumer<Object> notify, 
     BooleanSupplier eStop, 
     Consumer<String> saveFile){ 
    if (setStatus != null){ setStatus.accept("running"); } 
    String s1 = param1+"::"+param2; 
    if (log != null){ log.accept("INFO", "About to do Step 2, this could take awhile"); } 
    String s2 = s1+"::step2"; 
    if (log != null){ log.accept("INFO", "Completed step 2"); } 
    String s3 = s2+"::step3"; 
    if (notify != null) { notify.accept("randomobjectnotify"); } 
    if (eStop != null && eStop.getAsBoolean()){ 
    if (setStatus != null){ setStatus.accept("stopped"); } 
    return "stoppedresult"; 
    } 
    String s4 = s3+"::step4"; 
    if (setStatus != null){ setStatus.accept("completed"); } 
    if (saveFile!= null){ saveFile.accept("Logic completed for params "+param1+"::"+param2); } 
    return s4; 
    } 
} 
-1

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

public int Steps(int param1,int param2){ 
//whatever you want your first step to do make result into a variable 
int param3 = param1-param2; 

//Same with step 2 ,3 and so on 
int param4 = param3*param1; 

}` 
+0

или любой объект, который вы хотите не просто ints. – Azamp19

+0

Я не совсем понимаю этот ответ. Я не уверен, что вы имеете в виду, вычитая параметр 1 из param 2, и что бы умножить param 3 на param 1. Шаги здесь остаются неопределенными, но они состоят из множества строк кода. Я хотел показать, что между этими строками существуют другие строки, которые изменяют состояние, но я не хочу, чтобы они иногда делали это при вызове метода. – Dave

+1

Не уверен, как это имеет отношение к вопросу. – TriCore

-1

Это возможно, но немного werid каким-то образом.

public class MethodPipeline<T, I, R> { 
    private final MethodPipeline<T, ?, I> prev; 
    private final int kind; 
    private final Function<? extends I, ? extends R> f; 
    private final Runnable r; 
    private final Consumer<? extends R> c; 
    private MethodPipeline(Function<? extends I, ? extends R> l, MethodPipeline<? extends T, ?, ? extends I> prev) { 
     kind = 0; 
     f = l; 
     r = null; 
     c = null; 
     this.prev = prev; 
    } 
    private MethodPipeline(Runnable l, MethodPipeline<? extends T, ?, ? extends I> prev) { 
     kind = 1; 
     f = null; 
     r = l; 
     c = null; 
     this.prev = prev; 
    } 
    private MethodPipeline(Consumer<? extends R> l, MethodPipeline<? extends T, ?, ? extends I> prev) { 
     kind = 2; 
     f = null; 
     r = null; 
     c = l; 
     this.prev = prev; 
    } 
    //...various public consructor 
    public <R1> MethodPipeline<T, R, R1> then(Function<? extends R, ? extends R1> convertor) { 
     return new MethodPipeline<>(convertor, this); 
    } 
    public MethodPipeline<T, I, R> sideEffect(Runnable sideEffect) { 
     return new MethodPipeline<>(sideEffect, this); 
    } 
    public MethodPipeline<T, I, R> sideEffect(Consumer<? extnds R> sideEffect) { 
     return new MethodPipeline<>(sideEffect, this); 
    } 
    public R run(T param, boolean sideEffect) { 
     I v = prev.run(param); 
     switch (kind) { 
     case 0: 
      return f.apply(v); 
     case 1: 
      if (sideEffect) 
       r.run(); 
      return v; 
     case 2: 
      if (sideEffect) 
       c.accept(v); 
      return v; 
     } 
    } 
} 

Я проектировал его как трубопровод, как и j.u.stream. run является рекурсивным для безопасности типов. Используйте его с осторожностью: не вкладывайте слишком много работы в трубопровод. Это может привести к исключению StackOverFlowException.

PS: Веб написан. Не испытано. Даже не компилируется за один раз. Используйте на свой риск. Ограниченным переменным типа может потребоваться некоторый рефакторинг, изменить его самостоятельно.

1

Одним из вариантов является извлечение метода в класс с пустым template method для каждого шага, и переопределить его для не-чистой версии:

class Method { 
    void beforeStart() {}; 
    void afterStep1(String result) {}; 
    void afterStep2(String result) {}; 
    void afterStep3(String result) {}; 
    void afterStep4(String result) {}; 

    final Result execute(String param1, String param2) { 
     beforeStart(); 
     String s1 = step1(param1, param2); 
     afterStep1(s1); 
     String s2 = step2(s1); 
     afterStep2(s2); 
     String s3 = step3(s2); 
     afterStep3(s3); 
     String s4 = step4(s3); 
     afterStep4(s4); 
     return new Result(s4); 
    } 
} 

Тогда можно определить один или несколько подклассов, отменяющие предоставили способы вставки побочных эффектов.

2

Я не реклама это как отличное решение, но больше как способ, чтобы обсудить проблемы вашей ситуации:

@SafeVarargs 
public static Result pureMethod(
    String param1, String param2, Consumer<String>... optionalSteps) { 
    if(optionalSteps.length>0) optionalSteps[0].accept(param1); 
    String s1 = step1(param1, param2); 
    if(optionalSteps.length>1) optionalSteps[1].accept(s1); 
    String s2 = step2(s1); 
    if(optionalSteps.length>2) optionalSteps[2].accept(s2); 
    String s3 = step3(s2); 
    if(optionalSteps.length>3) optionalSteps[3].accept(s3); 
    String s4 = step4(s3); 
    if(optionalSteps.length>4) optionalSteps[4].accept(s4); 
    return new Result(s4); 
} 
public Result nonPureMethod(String param1, String param2) { 
    return pureMethod(param1, param2, 
     arg -> this.current_status = "running", 
     arg -> this.logger.log("About to do step 2, this could take while"), 
     arg -> this.logger.log("Completed step 2"), 
     s3 -> { this.notifyOtherObject(s3); 
      if (this.UserPressedEmergencyStop) { 
       this.current_status = "stopped"; 
       throw new RuntimeException("stopped"); 
      } 
     }, 
     s4 -> { this.current_status = "completed"; this.saveFile(s4); }); 
} 

Что здесь бросается в глаза, как мало эти необязательные действия имеют в общем. В приведенном выше фрагменте кода предполагается, что некоторые действия не будут использовать предоставленный аргумент и для первого действия один из двух параметров был выбран произвольно. Альтернативой было бы использовать BiConsumer, требуя, чтобы все другие действия несли неиспользуемый параметр. Но худшим нарушителем здесь является прекращение через исключение в четвертом действии. Чистым решением было бы использовать тип функции, возвращающий boolean, чтобы определить, продолжать ли это, но это заставило бы все действия вернуть boolean, например.превратите простое лямбда-выражение, например arg -> this.current_status = "running", в arg -> { this.current_status = "running"; return true; } и т. д.

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

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

public static Result pureMethod(String param1, String param2, 
     Logger logger, ObjIntConsumer<String> statusUpdater, IntPredicate cont) { 
    statusUpdater.accept(null, 0); 
    String s1 = step1(param1, param2); 
    statusUpdater.accept(s1, 1); 
    if(!cont.test(1)) return null; 
    logger.log("About to do step 2, this could take while"); 
    String s2 = step2(s1); 
    statusUpdater.accept(s2, 2); 
    if(!cont.test(2)) return null; 
    logger.log("Completed step 2"); 
    String s3 = step3(s2); 
    statusUpdater.accept(s3, 3); 
    if(!cont.test(3)) return null; 
    String s4 = step4(s3); 
    statusUpdater.accept(s4, 4); 
    return new Result(s4); 
} 
public static Result pureMethod(String param1, String param2) { 
    Logger logger=Logger.getAnonymousLogger(); 
    logger.setLevel(Level.OFF); 
    return pureMethod(param1, param2, logger, (s,i)->{}, i->true); 
} 
public Result nonPureMethod(String param1, String param2) { 
    return pureMethod(param1, param2, this.logger, 
     (s,i)-> { switch (i) { 
      case 0: this.current_status = "running"; break; 
      case 3: this.notifyOtherObject(s); break; 
      case 4: this.current_status = "completed"; this.saveFile(s); break; 
     }}, i -> { 
      if(i==3 && this.UserPressedEmergencyStop) { 
       this.current_status = "stopped"; 
       return false; 
      } 
      else return true; 
     }); 
} 

, но она по-прежнему плотно прилегает к использованию при nonPureMethod в некотором роде ...