Refactoring to Functions

OO makes code understandable by encapsulating moving parting, but FP makes code understandable by minimizing moving parts. -Michael Feathers
Conditional Deferred Execution
로그 Logger
if (logger.isLoggable(Level.INFO)) {
  logger.info("problem:" + getDiagnostic());
}

이 실현 에는 다음 과 같은 나 쁜 맛 이 존재 한다.
  • 중복 되 는 모델 코드 는 사용자 의 구석구석 에 흩 어 져 있다.
  • logger.debug 이전에 먼저 logger.isLoggable, logger 상태 논 리 를 너무 많이 노출 하여 LoD(Law of Demeter)
  • 을 위반 했다.
    Eliminate Effects Between Unrelated Things.
    Apply LoD
    logger.info("problem:" + getDiagnostic());
    public void info(String msg) {
      if (isLoggable(Level.INFO)) {
        log(msg)
      }
    }

    이러한 디자인 은 상태의 조 회 를 봉인 하고 LoD 원칙 을 따 랐 지만 심각 한 성능 문제 가 존재 한다.어쨌든 getDiagnostic 은 시간 이 걸 리 고 비 싼 조작 이 라면 시스템 의 병목 이 될 수 있다.
    Apply Lambda Lambda 타성 구 치 의 특성 을 유연 하 게 응용 하면 이 문 제 를 예 쁘 게 해결 할 수 있다.
    public void log(Level level, Supplier supplier) {
      if (isLoggable(level)) {
        log(supplier.get());
      }
    }
    
    public void debug(Supplier supplier) {
      log(Level.DEBUG, supplier);
    }
    
    public void info(Supplier supplier) {
      log(Level.INFO, supplier);
    }
    
    ...

    사용자 의 코드 도 더욱 간결 하여 중복 되 는 모델 코드 를 생략 했다.
    logger.info(() -> "problem:" + getDiagnostic());

    Apply Scala: Call by Name lambda 을 사용 할 때 불필요 한 () 은 약간 지루 해 보이 고 by-name 매개 변 수 를 사용 하여 표 현 력 을 한층 높 일 수 있다.
    def log(level: Level, msg: => String) {
      if (isLoggable(level)) {
        log(msg)
      }
    }
    
    def debug(msg: => String) {
      log(DEBUG, msg)
    }
    
    def info(msg: => String) {
      log(INFO, msg)
    }
    logger.info("problem:" + getDiagnostic());
    "problem:" + getDiagnostic() 문 구 는 logger.info 계산 을 전개 하 는 것 이 아니 라 apply 계산 이 지연 되 었 을 때 까지 진정 으로 평가 되 고 계산 되 었 다.
    Execute Around
    우 리 는 작업 을 수행 하기 전에 환경 을 준비 한 다음 에 환경 을 철거 하 는 장면 을 자주 만난다.예 를 들 어 XUnit 중의 setUp/tearDown;데이터 베 이 스 를 조작 할 때 먼저 데이터 베 이 스 를 연결 하고 데 이 터 를 조작 한 후에 연결 을 해제 하도록 확보한다.파일 을 조작 할 때 먼저 파일 흐름 을 열 고 파일 을 조작 한 후 파일 흐름 을 닫 도록 합 니 다.
    Apply try-finally
    이상 안전성 을 확보 하기 위해 Java7 이전에 try-finally 의 실현 모델 을 자주 사용 하여 이런 문 제 를 해결한다.
    public static String process(File file) throws IOException {
      BufferedReader bf = new BufferedReader(new FileReader(file));
      try {
        return bf.readLine();
      } finally {
        if (bf != null) 
          bf.close();
      }
    }

    이러한 설계 와 실현 에는 몇 가지 문제 가 존재 한다.
  • if (bf != null) 은 필수 적 이지 만 잊 혀 지 는 경우 가 많다.
  • try-finally 의 모델 코드 가 사용자 프로그램 에 널리 퍼 져 대량의 중복 디자인 을 초래 했다.

  • Apply try-with-resources Java7 부터 AutoCloseable 의 자원 류 만 실현 되면 try-with-resources 의 실현 모델 을 사용 하여 상례 의 모델 코드 를 더욱 간소화 할 수 있다.
    public String process(File file) throws IOException {
      try(BufferedReader bf = new BufferedReader(new FileReader(file))) {
        return bf.readLine();
      }
    }

    그러나 일부 장면 에서 코드 를 최대 화하 기 어렵 기 때문에 실현 에 대량의 중복 코드 가 존재 한다.예 를 들 어 파일 의 모든 줄 을 옮 겨 다 니 며 패턴 을 다른 문자열 로 바 꿉 니 다.
    public String replace(File file, String regex, String i) throws IOException {
      try(BufferedReader bf = new BufferedReader(new FileReader(file))) {
        return bf.readLine().replaceAll(regex, replace);
      }
    }

    Apply Lambda
    코드 를 최대 화하 고 사용자 모델 코드 를 최소 화하 기 위해 자원 작업 전후의 코드 를 폐쇄 하고 lambda 구체 적 인 문제 와 관련 된 처리 논 리 를 맞 춤 형 으로 사용한다.process 사용 BufferedProcessor 행위 의 매개 변수 화.
    public static String process(File file, BufferedProcessor p) throws IOException {
      try(BufferedReader bf = new BufferedReader(new FileReader(file))) {
        return p.process(bf);
      }
    }

    그 중에서 BufferedProcessor 는 함수 식 인터페이스 로 lambda 의 원형 정 보 를 묘사 하 는 데 사용 된다.
    @FunctionalInterface
    public interface BufferedProcessor {
      String process(BufferedReader bf) throws IOException;
    }

    사용자 가 lambda 표현 식 을 사용 하여 코드 를 더욱 간단 하고 예 쁘 게 합 니 다.
    process(file, bf -> bf.readLine());

    사용 Method Reference 하면 표 현 력 이 향상 된다.
    process(file, BufferedReader::readLine);

    Apply Scala: Structural Type, Call by Name, Currying
    자원 방출 의 실현 을 극 대화 하기 위해 사용 Scala 은 신기 하 게 간단 한 DSL 을 구성 하여 사용자 가 더욱 잘 재 활용 할 수 있 도록 할 수 있다.
    Make it Easy to Reuse.
    import scala.language.reflectiveCalls
    
    object using {
      def apply[R <: def="" close="" unit="" t=""> R)(f: R => T) = {
        var res: Option[R] = None
        try {
          res = Some(resource)
          f(res.get)
        } finally {
          if (res != None) res.get.close
        }
      }
    }
    R <: def="" close="" unit=""> Rclose 방법 을 가 진 유형 이다.resource: => RresourceCall by Name 로 성명 하고 계산 을 지연 시 킬 수 있다.apply 두 개의 인 자 를 사용 하고 Currying 화 했다.Currying 덕분에 사용자 의 맞 춤 형 함 수 는 큰 괄호 로 표 현 력 을 강화 할 수 있 고 using 내 장 된 언어 특성 처럼 추상 적 인 통제 구 조 를 얻 을 수 있다.
    using(Source.fromFile(file)) { source =>
      source.getLines 
    }

    매개 변수 source 는 한 번 만 사 용 했 기 때문에 자리 차지 문 자 를 통 해 표 현 력 을 더욱 강화 할 수 있 습 니 다.
    using(Source.fromFile(file)) { _.getLines }

    좋은 웹페이지 즐겨찾기