Scala代碼編寫中常見的十大陷阱

很多Java開發者在學習Scala語言的時候,往往覺得Scala的語法和用法有些過于復雜,充滿語法糖,太“甜”了。在使用Scala編寫代碼時,由于語法和編寫習慣的不同,很多開發者會犯相同或相似的錯誤。一位Scala狂熱愛好者近日總結了十大這樣的錯誤,以供參考。

【51CTO精選譯文】對于支持并發和分布式處理、高可擴展、基于組件的應用程序來說,Scala的功能是很強大的。它利用了面向對象和函數式程序設計的優點。這種基于Java虛擬機的語言在宣布Twitter正使用它時受到了最多的沖擊(相關51CTO評論:從Scala進駐Twitter看多語言混雜系統的前景)。如果使用正確,Scala可以大量減少應用程序對代碼的需求。
對于Scala編程, 我們收集了這些常見代碼編寫中的陷阱。這些技巧來自于Daniel Sobral,一個曾參加過FreeBSD項目和Java軟件開發工程的Scala狂熱愛好者。
1. 語法錯誤
認為 “yield” 像 ”return” 一樣。有人會這樣寫:

for(i <- 0 to 10) {  
  if (i % 2 == 0)  
    yield i  
  else 
    yield -i  
} 

正確的表示應該是:

for(i <- 0 to 10)   
yield {  
  if (i % 2 == 0)  
    i  
  else 
    -i  
} 

2. 誤用和語法錯誤
濫用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/> 

3. 用法錯誤
為那些根本不是無關緊要的應用加入Application特征。

object MyScalaApp extends Application {    
  // ... body ...  
} 

示例部分的問題在于,body部分在單元對象初始化時執行。首先,單元初始化的執行是異步的,因此你的整個程序不能與其它線程交互;其次,即時編譯器(JIT)不會優化它,因此你的程序速度慢下來,這是沒有必要的。
另外,不能與其它線程的交互也意味著你會忘記測試應用程序的GUI或者Actors。
4. 用法錯誤
試圖模式匹配一個字符串的正則表達式,而又假定該正則表達式是無界的:

val r = """(\d+)""".r  
val s = "--> 5 <---" 
s match {  
  case r(n) => println("This won't match")  
  case _ => println("This will")  
} 

此處的問題在于, 當模式模式匹配時, Scala的正則表達式表現為如同開始于”^”,結束于”$”。使之工作的正確寫法是:

val r = """(\d+)""".r  
val s = "--> 5 <---" 
r findFirstIn s match {  
  case Some(n) => println("Matches 5 to "+n)  
  case _ => println("Won't match")  
} 

或者確保模式能匹配任意前綴和后綴:

val r = """.*(\d+).*""".r  
val s = "--> 5 <---" 
s match {  
  case r(n) => println("This will match the first group of r, "+n+", to 5")  
  case _ => println("Won't match")  
} 

5. 用法錯誤
把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(name0: String, password0: String) {  
  private case class Fields(var name: String, var password0: String)  
  private object fields extends Fields(name0, password0)  
 
 
  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(name0: String) extends Customer(name0) {  
  require(name0.length < 5)  
 
  def name_=(newName : String) =  
    if (newName.length < 5) error("too short")  
    else super.name_=(newName)  
}  
 
val cust = new ValidatingCustomer("xyz123") 

6. 用法錯誤
忘記類型擦除(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")  
} 

7. 設計錯誤
Implicit關鍵字的使用不小心。Implicits非常強大,但要小心,普通類型不能使用隱式參數或者進行隱匿轉換。
例如,下面一個implicit表達式:

implicit def string2Int(s: String): Int = s.toInt 

這是一個不好的做法,因為有人可能錯誤的使用了一個字符串來代替Int。對于上面的這種情況,更好的方法是使用一個類。

case class Age(n: Int)  
implicit def string2Age(s: String) = Age(s.toInt)  
implicit def int2Age(n: Int) = new Age(n)  
implicit def age2Int(a: Age) = a.n 

這將會使你很自由的將Age與String或者Int結合起來,而不是讓String和Int結合。類似的,當使用隱式參數時,不要像這樣做:

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

這不僅因為它容易在隱式參數間產生沖突,而且可能導致在毫無提示情況下傳遞一個隱式的age, 而接收者需要的只是隱式的Int或者其它類型。同樣,解決辦法是使用一個特定的類。
另一種可能導致implicit用法出問題的情況是有偏好的使用操作符。你可能認為”~”是字符串匹配時最好的操作符,而其他人可能會使用矩陣等價(matrix equivalence),分析器連接等(符號)。因此,如果你使用它們,請確保你能夠很容易的分離其作用域。
8. 設計錯誤
設計不佳的等價方法。尤其是:
◆試著使用“==”代替“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。

9. 用法錯誤
在Unix/Linux/*BSD的系統中,對你的主機進行了命名卻沒有在主機文件中聲明。特別的,下面這條指令不會工作:

ping `hostname`  

在這種情況下,fsc和scala都不會工作,而scalac則可以。這是因為fsc運行在背景模式下,通過TCP套接字監聽連接來加速編譯,而scala卻用它來加快腳本的執行速度。
10.風格錯誤
使用while。雖然它有自己的用處,但大多數時候使用for往往更好。在談到for時,用它們來產生索引不是一個好的做法。
避免這樣的使用:

def matchingChars(string: String, characters: String) = {  
  var m = "" 
  for(i <- 0 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 2.7)和視圖(Scala 2.8)中。

def indicesOf(s: String, c: Char) = for {  
  (sc, index) <- s.zipWithIndex  
  if c == sc  
} yield index   

【51CTO.com譯稿,合作站點轉載請注明原文譯者和出處為51CTO.com
,且不得修改原文內容。】
原文:10 Scala Programming Pitfalls 作者:mitchp

Scala講座:函數式語言的體驗
Scala講座:類型系統和相關功能
Adobe架構師談Scala:功能強大但令人困惑
Scala 2.8的for表達式:性能與運行順序的
Scala Actor與底層并發編程機制異同之探

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,702評論 6 534
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,615評論 3 419
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 176,606評論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,044評論 1 314
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,826評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,227評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,307評論 3 442
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,447評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,992評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,807評論 3 355
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,001評論 1 370
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,550評論 5 361
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,243評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,667評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,930評論 1 287
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,709評論 3 393
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,996評論 2 374

推薦閱讀更多精彩內容