對於支持並發和分布式處理高可擴展基於組件的應用程序來說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
}
誤用和語法錯誤
濫用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
From:http://tw.wingwit.com/Article/program/Java/hx/201311/26022.html