scala 更好的代码,一种方法,一种责任[已关闭]

iyr7buue  于 2022-11-09  发布在  Scala
关注(0)|答案(2)|浏览(197)

已关闭。这个问题是opinion-based。它目前不接受答案。
**想要改进这个问题吗?**更新问题,以便editing this post可以用事实和引用来回答它。

昨天关门了。
社区正在评估是否在18小时前重新讨论这个问题。
Improve this question
假设您正在为某个集合编写一个算法,例如,为某个List编写一个方法‘CONTAINS’。代码可能如下所示(出于示例的目的,这非常简单):

def betterContains(list: List[String], element: String): Boolean = {
   if (list.isEmpty) false 
   else if (list.head == element) true
   else betterContains(list.tail, element)
}

想象一个更复杂的算法,例如在树、图等中搜索元素。出于某种原因,您添加了用于日志记录的代码:

def betterContains(list: List[String], element: String): Boolean = {
   if (list.isEmpty) {
      log.info("The element was not found in the list.")
      false
   } 
   else if (list.head == element) {
      log.info("Yes! found it!")
      true
   }
   else {
      log.info(s"Still searching, ${list.tail.size} elements pending")
      betterContains(list.tail, element)
   }
}

然后,假设您正在添加用于将一些进度数据保存在文本文件中的代码。最后,您将拥有一个做3件事的方法:

  • 在列表中搜索元素
  • 将信息记录到控制台
  • 将数据(与进度相关)添加到文本文件

如果开发人员决定使用新的日志库,他将不得不对方法实现进行更改。此外,如果他决定更改将数据保存在文本文件中的方式,他也必须对方法实现进行更改。
有什么方法可以避免这种情况吗?我的意思是,如果我找到了更好的方法(更好的算法)来找到列表中的元素,我只想对方法进行更改。在我看来,该算法不符合单一责任原则,它正在做的事情不止一件。

r8uurelv

r8uurelv1#

在我看来,该算法不符合单一责任原则,它正在做的事情不止一件。
正确的。这就是为什么对于日志记录、审计、安全检查、性能监视、异常处理、缓存、事务管理、持久性、验证等,即对于不同种类的附加正交行为,人们使用代码插装的原因之一
What are the possible AOP use cases?
插装可以是运行时(运行时反射、运行时注解、面向方面的编程、Java代理、字节码操作)、编译时(宏、编译时注解处理器、编译器插件)、预编译时/构建时(源代码生成、Scalameta/语义DB、SBT源代码生成器、样板模板)等。
例如,您可以在编译时使用macro annotation日志记录分支(“if-Else”)检测代码。

import scala.annotation.{StaticAnnotation, compileTimeOnly}
import scala.language.experimental.macros
import scala.reflect.macros.blackbox

@compileTimeOnly("enable macro annotations")
class logBranching extends StaticAnnotation {
  def macroTransform(annottees: Any*): Any = macro LogBranchingMacro.impl
}

class LogBranchingMacro(val c: blackbox.Context) {
  import c.universe._

  val printlnT = q"_root_.scala.Predef.println"
  def freshName(prefix: String) = TermName(c.freshName(prefix))

  val branchTransformer = new Transformer {
    override def transform(tree: Tree): Tree = tree match {
      case q"if ($cond) $thenExpr else $elseExpr" =>
        val condStr = showCode(cond)
        val cond2   = freshName("cond")
        val left2   = freshName("left")
        val right2  = freshName("right")

        val (optLeft1, optRight1, cond1, explanation) = cond match {
          case q"$left == $right" =>
            (
              Some(this.transform(left)),
              Some(this.transform(right)),
              q"$left2 == $right2",
              q""" ", i.e. " + $left2 + "==" + $right2 """
            )
          case _ =>
            (
              None,
              None,
              this.transform(cond),
              q""" "" """
            )
        }

        val backups = (cond, optLeft1, optRight1) match {
          case (q"$_ == $_", Some(left1), Some(right1)) =>
            Seq(
              q"val $left2  = $left1",
              q"val $right2 = $right1"
            )
          case _ => Seq()
        }

        val thenExpr1 = this.transform(thenExpr)
        val elseExpr1 = this.transform(elseExpr)

        q"""
          ..$backups
          val $cond2 = $cond1
          $printlnT("checking condition: " + $condStr + $explanation + ", result is " + $cond2)
          if ($cond2) $thenExpr1 else $elseExpr1
        """

      case _ => super.transform(tree)
    }
  }

  def impl(annottees: Tree*): Tree = annottees match {
    case q"$mods def $tname[..$tparams](...$paramss): $tpt = $expr" :: Nil =>
      val expr1 = branchTransformer.transform(expr)
      q"$mods def $tname[..$tparams](...$paramss): $tpt = $expr1"

    case _ => c.abort(c.enclosingPosition, "@logBranching can annotate methods only")
  }
}
// in a different subproject

@logBranching
def betterContains(list: List[String], element: String): Boolean = {
  if (list.isEmpty) false
  else if (list.head == element) true
  else betterContains(list.tail, element)
}

  //   scalacOptions += "-Ymacro-debug-lite"
//scalac: def betterContains(list: List[String], element: String): Boolean = {
//  val cond$macro$1 = list.isEmpty;
//  _root_.scala.Predef.println("checking condition: ".$plus("list.isEmpty").$plus("").$plus(", result is ").$plus(cond$macro$1));
//  if (cond$macro$1)
//    false
//  else
//    {
//      val left$macro$5 = list.head;
//      val right$macro$6 = element;
//      val cond$macro$4 = left$macro$5.$eq$eq(right$macro$6);
//      _root_.scala.Predef.println("checking condition: ".$plus("list.head.==(element)").$plus(", i.e. ".$plus(left$macro$5).$plus("==").$plus(right$macro$6)).$plus(", result is ").$plus(cond$macro$4));
//      if (cond$macro$4)
//        true
//      else
//        betterContains(list.tail, element)
//    }
//}
betterContains(List("a", "b", "c"), "c")

//checking condition: list.isEmpty, result is false
//checking condition: list.head.==(element), i.e. a==c, result is false
//checking condition: list.isEmpty, result is false
//checking condition: list.head.==(element), i.e. b==c, result is false
//checking condition: list.isEmpty, result is false
//checking condition: list.head.==(element), i.e. c==c, result is true

例如,Scastie仪器使用Scalameta用户输入的代码
https://github.com/scalacenter/scastie/tree/master/instrumentation/src/main/scala/com.olegych.scastie.instrumentation
在函数式编程中添加额外行为的另一种方法是效果,例如Monad。阅读关于记录Monad、Writer Monad、使用免费Monad进行记录等内容。

cygmwpex

cygmwpex2#

对于这个以意见为基础的问题,我将给出基于意见的回答。
有什么方法可以避免这种情况吗?
有一个解决方案,当然是通过算法。但您应该与我们分享以下代码:

  • 最初保存数据的方式(您已在第二个片段中与我们共享)
  • 数据最终保存在文本文件中的方式(您尚未共享此内容)
  • 开发者使用新日志库的方式(您还没有分享)

你没有澄清你问题的以下部分:
“如果开发人员决定使用新的日志库...”
“此外,如果他决定更改数据在文本文件中的保存方式...”

相关问题