Swift程式設計規範:保持程式碼優美的10個方法

swift.gg發表於2015-08-24

  這篇Swift風格指南與你看到的其他的指南有所不同,此篇指南主要焦點集中在列印和Web展示的可讀寫上。我們建立此篇風格指南的目的,是為了讓我們的圖書、教程以及初學者套件中的程式碼保持優美和一致,即使我們有很多不同的作者共同編寫這些圖書。

  我們的首要目標是一致性,可讀性和簡潔性。

  還在使用Objective-C?也可以參考我們的Objective-C風格指南中譯版)。

 命名(Naming)

  使用駝峰式的描述性命名方式,為類,方法,變數等命名。類名的首字母應該大寫,而方法和變數的首字母使用小寫字元。

  推薦做法:

private let maximumWidgetCount = 100
class WidgetContainer {
  var widgetButton: UIButton
  let widgetHeightPercentage = 0.85
}

  不推薦做法:

let MAX_WIDGET_COUNT = 100
class app_widgetContainer {
  var wBut: UIButton
  let wHeightPct = 0.85
}

  對於函式和初始化方法,推薦對所有的引數進行有意義的命名,除非上下文已經非常清楚。如果外部引數命名可以使得函式呼叫更加可讀,也應該把外部引數命名包含在內。

func dateFromString(dateString: String) -> NSDate
func convertPointAt(#column: Int, #row: Int) -> CGPoint
func timedAction(#delay: NSTimeInterval, perform action: SKAction) -> SKAction!
// 呼叫方式如下:
dateFromString("2014-03-14")
convertPointAt(column: 42, row: 13)
timedAction(delay: 1.0, perform: someOtherAction)

  對於方法來說,參照標準的蘋果慣例,方法命名含義要引用到第一個引數:

class Guideline {
  func combineWithString(incoming: String, options: Dictionary?) { ... }
  func upvoteBy(amount: Int) { ... }
}

 列舉(Enumerations)

  使用首字母大寫的駝峰命名規則來命名列舉值:

enum Shape {
  case Rectangle
  case Square
  case Triangle
  case Circle
}

 文章(Prose)

  當我們在文章中(教程,圖書,註釋等)需要引用到函式時,需要從呼叫者的視角考慮,包含必要的引數命名,或者使用_表示不需要命名的引數。

從你自身實現的init中呼叫convertPointAt(column:row:)。
如果你呼叫dateFromString(_:),需要保證你提供的輸入字串格式是”yyyy-MM-dd”。
如果你需要在viewDidLoad()中呼叫timedAction(delay:perform:),記得提供調整後的延遲值和需要處理的動作。
你不能直接呼叫資料來源方法tableView(_:cellForRowAtIndexPath:)

  當你遇到疑問時,可以看看Xcode在jump bar中是如何列出方法名的 —— 我們的風格與此匹配。

Swift程式設計規範:保持程式碼優美的10個方法

 類的字首(Class Prefixes)

  Swift型別自動被模組名設定了名稱空間,所以你不需要加一個類的字首。如果兩個來自不同模組的命名衝突了,你可以附加一個模組名到型別命名的前面來消除衝突。

import SomeModule  

let myClass = MyModule.UsefulClass()

 空格(Spacing)

  • 使用2個空格的縮排比使用tabs更好,可以減少佔用空間和幫助防止多次換行。確保在Xcode進行了下圖的偏好設定:

Swift程式設計規範:保持程式碼優美的10個方法

 

  • 方法定義的大括號或者其他大括號(if/else/switch/while等)—— 般都放在定義名稱的同一行,並且使用一個新的行來結束。
  • 提示:你可以通過以下方法重新進行縮排:選擇一些程式碼(或者使用⌘A選擇所有),然後按Control-I(或者點選選單欄 Editor\Structure\Re-Indent)。一些Xcode模板程式碼使用的縮排是4個空格,所以這種方法可以很好的修復縮排。

  推薦做法:

if user.isHappy {
  // Do something
} else {
  // Do something else
}

  不推薦做法:

if user.isHappy
{
    // Do something
}
else {
    // Do something else
}
  • 應該在方法之間空出一行,從視覺上有更好的區分和組織。方法內的空白行隔開不同的功能,但是當一個方法中有很多段落時,也意味著你應該將該方法重構成幾個方法。

 註釋(Comments)

  當你需要時,使用註釋來解釋一段特定的程式碼段的作用。註釋必須保證更新或者及時刪除。

  避免在程式碼中使用塊註釋,程式碼儘可能自己能表達含義。以下情況除外:當使用註釋來生成文件時。

 類和結構體(Classes and Structures)

 選擇使用誰?(Which one to use?)

  請記住,結構體是值型別。使用結構體並沒有一個標識。一個陣列包含[a, b, c]和另外一個陣列同樣包含[a, b, c]是完全一樣的,它們完全可以交換使用。使用第一個還是使用第二個無關緊要,因為它們代表的是同一個東西。這就是為什麼陣列是結構體。

  類是引用型別。使用類是有一個標識或者有一個特定的生命週期。你需要對一個人類建模為一個類,因為兩個不同的人的例項,是兩個不同的東西。只是因為兩個人有同樣的名字和生日,也不能斷定這兩個人是一樣的。但是人的生日是一個結構體,因為日期1950-3-3和另外一個日期1950-3-3是相同的。日期不需要一個標識。

  有時,一些事物應該定義為結構體,但是需要相容AnyObject或者已經在以前的歷史版本中定義為類(NSDate,NSSet)。儘可能的嘗試遵守這些規則。

 定義的案例(Example definition)

  以下是一個風格很好的類定義:

class Circle: Shape {
  var x: Int, y: Int
  var radius: Double
  var diameter: Double {
    get {
      return radius * 2
    }
    set {
      radius = newValue / 2
    }
  }
  init(x: Int, y: Int, radius: Double) {
    self.x = x
    self.y = y
    self.radius = radius
  }
  convenience init(x: Int, y: Int, diameter: Double) {
    self.init(x: x, y: y, radius: diameter / 2)
  }
  func describe() -> String {
    return "I am a circle at \(centerString()) with an area of \(computeArea())"
  }
  override func computeArea() -> Double {
    return M_PI * radius * radius
  }
  private func centerString() -> String {
    return "(\(x),\(y))"
  }
}

  以上例子遵循了以下風格規範:

  • 指定屬性、變數、常量、引數定義或者其他定義的型別,在冒號後面,緊跟著一個空格,而不是把空格放在冒號前面。比如:x: Int和Circle: Shape。
  • 如果能表示相同的目的和上下文,可以在同一行定義多個變數和結構體。
  • 縮排getter,setter的定義和屬性觀察器的定義。
  • 不需要新增internal這樣的預設的修飾符。同樣的,不需要在重寫一個方法時新增訪問修飾符。

 Self的使用(Use of Self)

  為了保持簡潔,避免使用 self 關鍵詞,Swift 不需要使用 self 來訪問物件屬性和呼叫物件方法。

  必須使用 self 來區分構造器中屬性命名和引數命名,還有在閉包表示式中引用屬性值(編譯器需要區分):

class BoardLocation {
  let row: Int, column: Int
  init(row: Int, column: Int) {
    self.row = row
    self.column = column
    let closure = {
      println(self.row)
    }
  }
}

 協議遵守(Protocol Conformance)

  當我們對一個類新增協議時,推薦使用一個單獨的類擴充套件來實現協議的方法。這可以保持協議相關的方法聚合在一起,同時也可以簡單的標識出一個協議對應類中需要實現哪些對應的方法。

  同時,別忘了新增// MARK:,註釋可以使得程式碼組織的更好!

  推薦做法:

class MyViewcontroller: UIViewController {
  // class stuff here
}
// MARK: - UITableViewDataSource
extension MyViewcontroller: UITableViewDataSource {
  // table view data source methods
}
// MARK: - UIScrollViewDelegate
extension MyViewcontroller: UIScrollViewDelegate {
 // scroll view delegate methods
}

  不推薦做法:

class MyViewcontroller: UIViewController, UITableViewDataSource, UIScrollViewDelegate {
  // all methods
}

 計算屬性(Computed Properties)

  為了保持簡潔,如果一個計算屬性是隻讀的,請忽略掉get語句。只有在需要定義set語句的時候,才提供get語句。

  推薦做法:

var diameter: Double {
  return radius * 2
}

  不推薦做法:

var diameter: Double {  
  get {  
    return radius * 2  
  }  
}  

 函式宣告(Function Declarations)

  保證短的函式定義在同一行中,並且包含左大括號:

func reticulateSplines(spline: [Double]) -> Bool {
  // reticulate code goes here
}

  在一個長的函式定義時,在適當的地方進行換行,同時在下一行中新增一個額外的縮排:

func reticulateSplines(spline: [Double], adjustmentFactor: Double,
    translateConstant: Int, comment: String) -> Bool {
  // reticulate code goes here
}

 閉包表示式(Closure Expressions)

  如果閉包表示式引數在引數列表中的最後一個時,使用尾部閉包表示式。給定閉包引數一個描述性的命名。

  推薦做法:

UIView.animateWithDuration(1.0) {
  self.myView.alpha = 0
}
UIView.animateWithDuration(1.0,
  animations: {
    self.myView.alpha = 0
  },
  completion: { finished in
    self.myView.removeFromSuperview()
  }
)

  不推薦做法:

UIView.animateWithDuration(1.0, animations: {
  self.myView.alpha = 0
})
UIView.animateWithDuration(1.0,
  animations: {
    self.myView.alpha = 0
  }) { f in
    self.myView.removeFromSuperview()
}

  當單個閉包表示式上下文清晰時,使用隱式的返回值:

attendeeList.sort { a, b in
  a > b
}

 型別(Types)

  儘可能使用 Swift 原生型別。Swift 提供到 Objective-C 型別的橋接,所以你仍然可以使用許多需要的方法。

  推薦做法:

let width = 120.0                                    // Double
let widthString = (width as NSNumber).stringValue    // String

  不推薦做法:

let width: NSNumber = 120.0                          // NSNumber
let widthString: NSString = width.stringValue        // NSString

  在Sprite Kit程式碼中,使用CGFloat可以使得程式碼更加簡明,避免很多轉換。

 常量(Constants)

  常量定義使用 let 關鍵字,變數定義使用 var 關鍵字,如果變數的值不需要改變,請儘量使用 let 關鍵字。

  提示:一個好的技巧是,使用 let 定義任何東西,只有在編譯器告訴我們值需要改變的時候才改成 var 定義。

 可選型別(Optionals)

  當nil值是可以接受的時候時,定義變數和函式返回值為可選型別(?)。

  當你確認變數在使用前已經被初始化時,使用!來顯式的拆包型別,比如在viewDidLoad中會初始化subviews。

  當你訪問一個可選值時,如果只需要訪問一次或者在可選值鏈中有多個可選值時,請使用可選值鏈:

self.textContainer?.textLabel?.setNeedsDisplay()

  當需要很方便的一次性拆包或者新增附加的操作時,請使用可選值繫結:

if let textContainer = self.textContainer {
  // do many things with textContainer
}

  當我們命名一個可選變數和屬性時,避免使用諸如optionalString和maybeView這樣的命名,因為可選值的表達已經在型別定義中了。

  在可選值繫結中,直接對映原始的命名比使用諸如unwrappedView和actualLabel要好。

  推薦做法:

var subview: UIView?
var volume: Double?
// later on...
if let subview = subview, volume = volume {
  // do something with unwrapped subview and volume
}

  不推薦做法:

var optionalSubview: UIView?
var volume: Double?
if let unwrappedSubview = optionalSubview {
  if let realVolume = volume {
    // do something with unwrappedSubview and realVolume
  }
}

 結構體構造器(Struct Initializers)

  使用原生的 Swift 結構體構造器,比老式的幾何類(CGGeometry)的構造器要好。

  推薦做法:

let bounds = CGRect(x: 40, y: 20, width: 120, height: 80)
let centerPoint = CGPoint(x: 96, y: 42)

  不推薦做法:

let bounds = CGRectMake(40, 20, 120, 80)
let centerPoint = CGPointMake(96, 42)

  推薦使用結構體限定的常量CGRect.infiniteRect,CGRect.nullRect等,來替代全域性常量CGRectInfinite,CGRectNull等。對於已經存在的變數,可以直接簡寫成 .zeroRect。

 型別推斷(Type Inference)

  推薦使用更加緊湊的程式碼,讓編譯器能夠推斷出常量和變數的型別。除非你需要定義一個特定的型別(比如CGFloat和Int16),而不是預設的型別。

  推薦做法:

let message = "Click the button"
let currentBounds = computeViewBounds()
var names = [String]()
let maximumWidth: CGFloat = 106.5

  不推薦做法:

let message: String = "Click the button"
let currentBounds: CGRect = computeViewBounds()
var names: [String] = []

  注意:遵守這條規則意味選擇描述性命名比之前變得更加重要。

 語法糖(Syntactic Sugar)

  推薦使用型別定義簡潔的版本,而不是全稱通用語法。

  推薦做法:

var deviceModels: [String]
var employees: [Int: String]
var faxNumber: Int?

  不推薦做法:

var deviceModels: Array<String>
var employees: Dictionary<Int, String>
var faxNumber: Optional<Int>

 控制流(Control Flow)

  推薦迴圈使用for-in表示式,而不使用for-condition-increment表示式。

  推薦做法:

for _ in 0..<3 {
  println("Hello three times")
}
for (index, person) in enumerate(attendeeList) {
  println("\(person) is at position #\(index)")
}

 不推薦做法:

for var i = 0; i < 3; i++ {  
  println("Hello three times")  
}  
for var i = 0; i < attendeeList.count; i++ {  
  let person = attendeeList[i]  
  println("\(person) is at position #\(i)")  
}  

 分號(Semicolons)

  Swift不需要在你程式碼中的每一句表示式之後新增分號。只有在你需要在一行中連線多個表示式中,使用分號來區隔。

  不要在同一行編寫多個使用分號區隔的表示式。

  唯一的例外是在使用for-conditional-increment 架構。然而,儘可能使用for-in架構來替代它。

  推薦做法:

let swift = "not a scripting language"

  不推薦做法: 

let swift = "not a scripting language";

  注意:Swift與JavaScript有很大的不同,JavaScript認為忽略分號通常認為是不安全的。

 語言(Language)

  使用美式英語拼音符合Apple API的標準。

  推薦做法:

let color = "red"

  不推薦做法:

let colour = "red"

 版權宣告(Copyright Statement)

  以下的版權宣告應該被包含在所有原始檔的頂部:

/*
 * Copyright (c) 2015 Razeware LLC
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

  笑臉(Smiley Face)

  笑臉是raywenderlich.com網站非常重要的風格特性!擁有一個正確的笑臉,表達程式碼文章中開心和激動,是非常重要的。使用]來代表笑臉,因為這代表ASCII中最大的笑臉。)只能建立半個心型笑臉,所以不推薦使用。

  推薦做法:

:]

  不推薦做法:

:)

 作者(Credits)

  這篇風格指南是所有raywenderlich.com團隊成員共同的努力:

  向Nicholas WaynikObjective-C Style Guide團隊脫帽致敬。

  我們同時也從蘋果的官方Swift資料中尋找靈感:

  本文出自:The Official raywenderlich.com Swift Style Guide,譯文出自:SwiftGG

相關文章