Scala代码编写中常见的十大陷阱
对于支持并发和分布式处理 高可扩展 基于组件的应用程序来说 Scala的功能是很强大的 它利用了面向对象和函数式程序设计的优点 这种基于Java虚拟机的语言在宣布Twitter正使用它时受到了最多的冲击(相关 CTO评论 从Scala进驻Twitter看多语言混杂系统的前景) 如果使用正确 Scala可以大量减少应用程序对代码的需求
对于Scala编程 我们收集了这些常见代码编写中的陷阱 这些技巧来自于Daniel Sobral 一个曾参加过FreeBSD项目和Java软件开发工程的Scala狂热爱好者
语法错误
认为 yield 像 return 一样 有人会这样写
for(i < to ) {
if (i % == )
yield i
else
yield i
}
正确的表示应该是
for(i < to )
yield {
if (i % == )
i
else
i
}
误用和语法错误
滥用scala xml XML loadXXX 这个的语法分析器试图访问外部的DTD strip组件或类似的东西 在scala xml parsing ConstructingParser fromXXX中有另一个可选的语法分析器 同时 在处理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 won t 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( Won t 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( Won t match )
}
用法错误
把var和val认为是字段(fields)
Scala强制使用统一访问准则(Uniform Access Principle) 这使得我们无法直接引用一个字段 所有对任意字段的访问只能通过getters和setters val和var事实上只是定义一个字段 getter作为val字段 对于var则定义一个setter
Java程序员通常认为var和val是字段 而当发现在他们的方法中它们共享相同的命名空间时 常常觉得惊讶 因此 不能重复使用它们的名字 共享命名空间的是自动定义的getter和setter而不是字段本身 通常程序员们会试图寻找一种访问字段的方法 从而可以绕过限制 但这只是徒劳 统一访问准则是无法违背的 它的另一个后果是 当进行子类化时val会覆盖def 其它方法是行不通的 因为val增加了不变性保证 而def没有
当你需要重载时 没有任何准则会指导你如何使用私有的getters和setters Scala编译器和库代码常使用私有值的别名和缩写 反之公有的getters和setters则使用fullyCamelNamingConventions(一种命名规范) 其它的建议包括 重命名 实例中的单元化 甚至子类化 这些建议的例子如下
重命名
class User(val name: String initialPassword: String) {
private lazy var encryptedPassword = encrypt(initialPassword salt)
private lazy var salt = scala util Random nextInt
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 = fields name
def name_=(newName: String) = fields name = newName
def password = fields password
def password_=(newPassword: String) = fields password = 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 = fields name
def name_=(newName: String) = fields name = newName
def password = fields password
def password_=(newPassword: String) = fields password = newPassword
}
子类化
case class Customer(name: String)
class ValidatingCustomer(name : String) extends Customer(name ) {
require(name length < )
def name_=(newName : String) =
if (newName length < ) error( too short )
else super name_=(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: scala reflect Manifest[A]) = m toString match {
case int => println( List of Ints )
case java lang String => println( List of Strings )
case _ => println( Something else )
}
设计错误
Implicit关键字的使用不小心 Implicits非常强大 但要小心 普通类型不能使用隐式参数或者进行隐匿转换
例如 下面一个implicit表达式
implicit def string Int(s: String): Int = s toInt
这是一个不好的做法 因为有人可能错误的使用了一个字符串来代替Int 对于上面的这种情况 更好的方法是使用一个类
case class Age(n: Int)
implicit def string Age(s: String) = Age(s toInt)
implicit def int Age(n: Int) = new Age(n)
implicit def age Int(a: Age) = a n
这将会使你很自由的将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时a hashCode==b hashCode(反之不一定成立)
◆不可以这样做交换 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 string length)
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) < s zipWithIndex
if c == sc
lishixinzhi/Article/program/Java/hx/201311/26022
车牌识别系统算法代码怎么编写?
答:立林智能网络车牌自动识别还采用先进的目标跟踪,以及识别结果最佳化等方法,来确保从车流中一个一个地甄别出序列化的车牌。要实现对视频流逐帧识别,必须采用行之有效的高速识别算法,即神经网络算法和模糊算法相结合,否则无法达到实用的效果。对于常用的768×288高分辨率图像,立林智能网络车牌自动识别可以...
《编写高质量 Python代码的59个有效方法》txt下载在线阅读全文,求百 ...
答:用Python编写程序,是相当容易的,所以这门语言非常流行。但若想掌握Python所特有的优势、魅力和表达能力,则相当困难,而且语言中还有很多隐藏的陷阱,容易令开发者犯错。本书可以帮你掌握真正的Pythonic编程方式,令你能够完全发挥出Python语言的强大功能,并写出健壮而高效的代码。Scott Meyers在畅销书《...
中国年龄最小黑客,8岁写代码,1元买2500元东西,现在怎样?
答:刚开始,他的家中并没有电脑,汪正扬和我们大多数人一样,只好去网吧学习编写代码。经常去网吧自然容易被父母发现,可是在得知自己孩子并非为了玩游戏而是在学习编程后。汪正扬的父母为不打消他的积极性,并没有阻止他,反而专门为其购买了一台顶配的笔记本电脑和众多有关编程的书籍。在父母的支持下,汪正扬...
在软件测试中人工代码评审属于
答:人工代码评审属于静态测试。在软件测试中,人工代码评审是一种重要的质量保证活动,通过阅读、检查、分析和讨论代码来评估其质量。这个过程旨在发现潜在的缺陷、漏洞、性能问题以及不符合编码标准或最佳实践的情况。人工代码评审在代码编写完成后进行,也可以在提交到版本控制系统之前或之后进行。这个过程有助于...
建筑工程中十大常见重大事故隐患?
答:施工现场重大事故隐患是指房屋建筑和市政工程施工过程中,存在危险程度较大、可能导致群死群伤或造成重大经济损失等生产安全事故的危险源(包括物的危险状态、人的不安全行为和管理上的缺陷)。结合实际,将下列10种情形列为重大事故隐患:1.工程项目不具备基本安全生产条件,施工项目负责人长期脱岗、缺岗,...
求职中常见的十大骗局
答:由于缺乏社会经验,大学生在求职过程中总会遇到这样或那样的骗局。例如不靠谱的虚假招聘,将招聘岗位进行“美化”,将推销员变成“市场部经理”,将保险业务员称为“保险经理”等等。从“校园人”转型为“职场人”,应届毕业生如何才能有效规避求职陷阱?本文对求职中常见的十大骗局进行了专题盘点,并列出对策,毕业生求职需要...
浅谈持续集成在软件项目管理中的作用
答:在一般的项目中,编写测试代码都至少会额外增加30%的工作量初看.在时间和资金上这也许是很大的开销,然而,在持续集成过程中,编写测试代码是必要的,而且这样也省去了人工测试的时间.确保了软件产品的质量.对软件项目的质量管理有利。4、对项目风险管理的作用 持续集成过程通常在开发人员提交代码后开始....
中国常见十大名贵猫种
答:1、山东狮子猫 山东狮子猫站姿犹如雄狮,比较耐寒,杭病力强,善于捕鼠。是我国的比较名贵的猫种。2、狸花猫 狸花猫于2004年经过CFA中国长城俱乐部整理、编写后成为个纯种猫品种标准,并且得到了国际猫界权威组织CFA的认可。3、黄狸 黄狸因身上有纯的斑纹图案,又被称为中国虎斑猫。目前正在申请国际...
软件测试的目的是尽可能发现软件中的错误,通常( )是代码编写阶段...
答:单元测试的目的是尽早地发现和修复软件中的错误和缺陷,避免错误和缺陷在后续的开发过程中扩散和累积,从而提高软件开发的效率和质量。在单元测试中,测试人员可以针对代码中的每个函数、方法和类进行测试,验证它们的功能和性能是否符合要求。这可以通过手动测试、自动化测试、覆盖率测试等方式来实现。单元测试...
如何防范应届毕业生求职中的十大骗局
答:由于缺乏社会经验,大学生在求职过程中总会遇到这样或那样的骗局。例如不靠谱的虚假招聘,将招聘岗位进行“美化”,将推销员变成“市场部经理”,将保险业务员称为“保险经理”等等。从“校园人”转型为“职场人”,应届毕业生如何才能有效规避求职陷阱?向阳生涯专家团队对求职中常见的十大骗局进行了专题盘点,并列出对策,...