电脑故障

位置:IT落伍者 >> 电脑故障 >> 浏览文章

Scala代码编写中常见的十大陷阱


发布日期:2019/10/8
 

对于支持并发和分布式处理高可扩展基于组件的应用程序来说Scala的功能是很强大的它利用了面向对象和函数式程序设计的优点这种基于Java虚拟机的语言在宣布Twitter正使用它时受到了最多的沖击(相关CTO评论从Scala进驻Twitter看多语言混杂系统的前景)如果使用正确Scala可以大量减少应用程序对代码的需求

对于Scala编程 我们收集了这些常见代码编写中的陷阱这些技巧来自于Daniel Sobral一个曾参加过FreeBSD项目和Java软件开发工程的Scala狂热爱好者

语法错误

认为 yieldreturn 一样有人会这样写

for(i < to ) {

if (i % == )

yield i

else

yield i

}

正确的表示应该是

for(i < to )

yield {

if (i % == )

i

else

i

}

误用和语法错误

滥用scalaxmlXMLloadXXX这个的语法分析器试图访问外部的DTDstrip组件或类似的东西在scalaxmlparsingConstructingParserfromXXX中有另一个可选的语法分析器同时在处理XML时忘记了等号两端的空格比如

val xml=<root/>

这段代码真正的意思是

val xml$equal$less(root)$slash$greater

这种情况的发生是由于操作符相当随意而且scala采用这样一种事实字母数字字符与非字母数字字符通过下划线可以结合成为一个有效的标识符这也使得x+y这样的表达式不会被当成一个标识符而应该注意 x_+是一个有效的标识符所以赋值标识符的写法应该是

val xml = <root/>

用法错误

为那些根本不是无关紧要的应用加入Application特征

object MyScalaApp extends Application {

// body

}

示例部分的问题在于body部分在单元对象初始化时执行首先单元初始化的执行是异步的因此你的整个程序不能与其它线程交互其次即时编译器(JIT)不会优化它因此你的程序速度慢下来这是没有必要的

另外不能与其它线程的交互也意味着你会忘记测试应用程序的GUI或者Actors

用法错误

试图模式匹配一个字符串的正则表达式而又假定该正则表达式是无界的

val r = (\d+)r

val s = > <

s match {

case r(n) => println(This wont match)

case _ => println(This will)

}

此处的问题在于 当模式模式匹配时 Scala的正则表达式表现为如同开始于^结束于$使之工作的正确写法是

val r = (\d+)r

val s = > <

r findFirstIn s match {

case Some(n) => println(Matches to +n)

case _ => println(Wont match)

}

或者确保模式能匹配任意前缀和后缀

val r = *(\d+)*r

val s = > <

s match {

case r(n) => println(This will match the first group of r +n+ to )

case _ => println(Wont match)

}

用法错误

把var和val认为是字段(fields)

Scala强制使用统一访问准则(Uniform Access Principle)这使得我们无法直接引用一个字段所有对任意字段的访问只能通过getters和settersval和var事实上只是定义一个字段getter作为val字段对于var则定义一个setter

Java程序员通常认为var和val是字段而当发现在他们的方法中它们共享相同的命名空间时常常觉得惊讶因此不能重复使用它们的名字共享命名空间的是自动定义的getter和setter而不是字段本身通常程序员们会试图寻找一种访问字段的方法从而可以绕过限制但这只是徒劳统一访问准则是无法违背的它的另一个后果是当进行子类化时val会覆盖def其它方法是行不通的因为val增加了不变性保证而def没有

当你需要重载时没有任何准则会指导你如何使用私有的getters和settersScala编译器和库代码常使用私有值的别名和缩写反之公有的getters和setters则使用fullyCamelNamingConventions(一种命名规范)其它的建议包括重命名实例中的单元化甚至子类化这些建议的例子如下

重命名

class User(val name: String initialPassword: String) {

private lazy var encryptedPassword = encrypt(initialPassword salt)

private lazy var salt = scalautilRandomnextInt

private def encrypt(plainText: String salt: Int): String = { }

private def decrypt(encryptedText: String salt: Int): String = { }

def password = decrypt(encryptedPassword salt)

def password_=(newPassword: String) = encrypt(newPassword salt)

}

单例模式(Singleton)

class User(initialName: String initialPassword: String) {

private object fields {

var name: String = initialName;

var password: String = initialPassword;

}

def name = fieldsname

def name_=(newName: String) = fieldsname = newName

def password = fieldspassword

def password_=(newPassword: String) = fieldspassword = newPassword

}

或者对于一个类来说可以为相等关系或hashCode自动定义可被重用的方法

class User(name: String password: String) {

private case class Fields(var name: String var password: String)

private object fields extends Fields(name password)

def name = fieldsname

def name_=(newName: String) = fieldsname = newName

def password = fieldspassword

def password_=(newPassword: String) = fieldspassword = newPassword

}

子类化

case class Customer(name: String)

class ValidatingCustomer(name: String) extends Customer(name) {

require(namelength < )

def name_=(newName : String) =

if (newNamelength < ) error(too short)

else supername_=(newName)

}

val cust = new ValidatingCustomer(xyz)

用法错误

忘记类型擦除(type erasure)当你声明了一个类C[A]一个泛型T[A]或者一个函数或者方法m[A]后A在运行时并不存在这意味着对于实例来讲任何参数都将被编译成AnyRef即使编译器能够保证在编译过程中类型不会被忽略掉

这也意味着在编译时你不能使用类型参数A例如下面这些代码将不会工作

def checkList[A](l: List[A]) = l match {

case _ : List[Int] => println(List of Ints)

case _ : List[String] => println(List of Strings)

case _ => println(Something else)

}

在运行时被传递的List没有类型参数 而List[Int]和List[String]都将会变成List[_] 因此只有第一种情况会被调用

你也可以在一定范围内不使用这种方法而采用实验性的特性Manifest 像这样

def checkList[A](l: List[A])(implicit m: scalareflectManifest[A]) = mtoString match {

case int => println(List of Ints)

case javalangString => println(List of Strings)

case _ => println(Something else)

}

设计错误

Implicit关键字的使用不小心Implicits非常强大但要小心普通类型不能使用隐式参数或者进行隐匿转换

例如下面一个implicit表达式

implicit def stringInt(s: String): Int = stoInt

这是一个不好的做法因为有人可能错误的使用了一个字符串来代替Int对于上面的这种情况更好的方法是使用一个类

case class Age(n: Int)

implicit def stringAge(s: String) = Age(stoInt)

implicit def intAge(n: Int) = new Age(n)

implicit def ageInt(a: Age) = an

这将会使你很自由的将Age与String或者Int结合起来而不是让String和Int结合类似的当使用隐式参数时不要像这样做

case class Person(name: String)(implicit age: Int)

这不仅因为它容易在隐式参数间产生沖突而且可能导致在毫无提示情况下传递一个隐式的age 而接收者需要的只是隐式的Int或者其它类型同样解决办法是使用一个特定的类

另一种可能导致implicit用法出问题的情况是有偏好的使用操作符你可能认为~是字符串匹配时最好的操作符而其他人可能会使用矩阵等价(matrix equivalence)分析器连接等(符号)因此如果你使用它们请确保你能够很容易的分离其作用域

设计错误

设计不佳的等价方法尤其是

◆试着使用==代替equals(这让你可以使用!=)

◆使用这样的定义

def equals(other: MyClass): Boolean

而不是这样的

override def equals(other: Any): Boolean

◆忘记重载hashCode以确保当a==b时ahashCode==bhashCode(反之不一定成立)

◆不可以这样做交换 if a==b then b==a特别地当考虑子类化时超类是否知道如何与一个子类进行对比即使它不知道该子类是否存在如果需要请查看canEquals的用法

◆不可以这样做传递 if a==b and b ==c then a==c

用法错误

在Unix/Linux/*BSD的系统中对你的主机进行了命名却没有在主机文件中声明特别的下面这条指令不会工作

ping `hostname`

在这种情况下fsc和scala都不会工作而scalac则可以这是因为fsc运行在背景模式下通过TCP套接字监听连接来加速编译而scala却用它来加快脚本的执行速度

风格错误

使用while虽然它有自己的用处但大多数时候使用for往往更好在谈到for时用它们来产生索引不是一个好的做法

避免这样的使用

def matchingChars(string: String characters: String) = {

var m =

for(i < until stringlength)

if ((characters contains string(i)) && !(m contains string(i)))

m += string(i)

m

}

而应该使用

def matchingChars(string: String characters: String) = {

var m =

for(c < string)

if ((characters contains c) && !(m contains c))

m += c

m

}

如果有人需要返回一个索引可以使用下面的形式来代替按索引迭代的方法如果对性能有要求它可以较好的应用在投影(projection)(Scala )和视图(Scala )中

def indicesOf(s: String c: Char) = for {

(sc index) < szipWithIndex

if c == sc

} yield index

上一篇:如何把swt项目打jar包

下一篇:Tapestry组件使用总结