溫德里奇規(guī)范
本規(guī)范原文鏈接.
規(guī)范只是一些最佳實踐, 請酌情使用.
1 正確性
第一條原則, 也是最根本的原則: 應努力使代碼在編譯時沒有任何的警告. 這就涉及到諸多的設計決定和實現(xiàn)途徑的選擇.
2 命名規(guī)則
命名時應做到見名知意, 且必須保證意義一致性.
下面列出的命名規(guī)則摘錄自蘋果的 API design guideline, 是其中一些主要的規(guī)則:
- 函數(shù)或方法名稱必須能夠清晰描述該函數(shù)或者方法的內(nèi)容或功能
- 為了表達更加清楚, 可以適當放寬簡潔性要求.
- 使用駝峰命名法.
- 使用駝峰命名法的時候就兩種規(guī)則: 除了類型的名稱開頭是大寫字母外, 其他的都是以小寫字母開頭.
- 在滿足上述規(guī)則的前提下, 盡量做到表達簡潔.
- 命名的時候是基于實體的抽象含義, 而非基于其類型.
- 保證抽象含義表達清楚的基礎上, 適當增加類型信息.
- 保證命名后, 實體在使用上的流暢性. (這個需要技巧, 比如命名了一個東西, 找好久找不到, 想好久想不起來叫什么, 這樣就會造成在使用上的不流暢)
- 工廠方法以單詞
make
開頭. - 方法名稱中需要包含描述其功能的附加效應的詞:(這個不易理解, 需參考 API 設計準則再加以詳細描述)
- 使用專業(yè)術(shù)語或名詞的時候, 盡量做到不要 "讓知道的人笑掉大牙, 讓不知道的人不知所云".
- 命名時盡量不要使用縮寫, 特別是那些比較生僻的縮寫或者是自己發(fā)明的縮寫.
- 命名時做到有據(jù)可依, 要有先例最好?. (這個需參考 API 設計準則再加以詳細描述)
- 盡量使用屬性和方法, 不要使用沒有處于特定命名空間中的變量或函數(shù).
- 縮略語的大小寫方針要統(tǒng)一, 要么統(tǒng)一大寫, 要么統(tǒng)一小寫.
- 具有相似含義的若干方法, 當應用場景不同的時候, 它們的名稱需要附加描述含義的相同的前綴或后綴.
- swift中支持基于不同返回值時候的重載, 但是需要極力避免這樣的重載發(fā)生.
- 使用文檔化的參數(shù)命名.
- 為閉包或元組的參數(shù)提供標簽.
- 盡量利用參數(shù)的默認值, 能省很多事.(比如可選參數(shù)的默認值是nil, 就不用再在外界傳參時候再傳入一個nil了.)
2.1 Prose
略
2.2 不要添加類型名前綴
由于 swift 中使用 module 作為一個名字空間, 而 module 名字唯一, 所以類名沖突是不存在的. 即使兩個 module 中有相同的類名, 也可以通過 module名.類名
加以區(qū)分, 并且只有在有相同類名的時候才需要使用這樣方式.
import SomeModule
let myClass = MyModule.UsefulClass()
基于上述事實, swift 中不要再使用類名前綴了.
2.3 代理方法命名
當自定義代理時, 代理方法的第一個參數(shù)應該是代理源, 且沒有參數(shù)標簽, 保證和原生代碼中的代理方法命名方式的一致性:
應該:
func namePickerView(_ namePickerView: NamePickerView, didSelectName name: String)
func namePickerViewShouldReload(_ namePickerView: NamePickerView) -> Bool
不應:
func didSelectName(namePicker: NamePickerViewController, name: String)
func namePickerShouldReload() -> Bool
2.4 利用swift編譯器的類型推導機制(寫代碼的時候的東西了這個, 需要移動位置)
充分利用編譯器提供的類型上下文推導機制, 寫更加短小且清晰易懂的代碼.
應該:
let selector = #selector(viewDidLoad)
view.backgroundColor = .red
let toView = context.view(forKey: .to)
let view = UIView(frame: .zero)
不應:
let selector = #selector(ViewController.viewDidLoad)
view.backgroundColor = UIColor.red
let toView = context.view(forKey: UITransitionContextViewKey.to)
let view = UIView(frame: CGRect.zero)
2.5 泛型類型參數(shù)命名規(guī)則
泛型類型參數(shù)名稱應該是有意義的, 且是大寫字母開頭的駝峰命名. 只有當泛型類型參數(shù)沒有確切意義或角色時, 才使用傳統(tǒng)的單個大寫字母命名方式.
應該:
struct Stack<Element> { ... }
func write<Target: OutputStream>(to target: inout Target)
func swap<T>(_ a: inout T, _ b: inout T)
不應:
struct Stack<T> { ... }
func write<target: OutputStream>(to target: inout target)
func swap<Thing>(_ a: inout Thing, _ b: inout Thing)
2.6 關于美式還是英式英語單詞
蘋果公司的API都全部是美式的, 你說你整些英式在里面也不合適吧.
: ]
應該:
let color = "red"
不應:
let colour = "red"
3 代碼組織(還沒有表達清楚, 需要修改)
3.1 extension 的使用
利用 extension
將代碼按照邏輯功能進行組織. 每一個 extension
應該有對應的 //MARK -
注釋來分隔.
比如 UIViewController
的子類代碼中, 可以將 view 的生命期回調(diào), 自定義訪問方法(setter或getter), IBAction等分別放到不同的 extension 中去實現(xiàn).
3.2 協(xié)議的實現(xiàn)
當既有繼承關系, 又有協(xié)議(或通俗地說: 接口)實現(xiàn)的情況下, 應將接口的實現(xiàn)放到 extension
中去. 這樣可以保證相關功能的代碼都位于同一片代碼區(qū)域中.
應該:
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 {
// 所有代碼都被放這里...
}
例外情況:
父類通過某種途徑聲明遵守某協(xié)議后(比如在類定義時聲明遵守協(xié)議, 或是使用 extension 實現(xiàn)協(xié)議), 子類中都無法再次聲明遵守某協(xié)議.
且如果父類中使用
extension
實現(xiàn)了協(xié)議, 子類中是無法重寫任何協(xié)議方法的.(目前不支持重寫 extension 中的方法)這樣就引出了幾個選擇:
- 父類類定義時實現(xiàn)協(xié)議, 子類可以直接重寫協(xié)議方法. 這樣沒有利用任何 extension 的功能.
- 父類通過
extension
實現(xiàn)協(xié)議, 則子類中無法對方法進行重寫.- 將協(xié)議放在子類中去實現(xiàn).
總結(jié)上述三點, 目前要利用
extension
來組織協(xié)議的實現(xiàn)代碼的話, 則這個類要么是終端類, 要么其子類沒有重寫協(xié)議方法的需求.故在繼承鏈的什么位置來實現(xiàn)協(xié)議, 使用何種方式實現(xiàn)協(xié)議, 這些都需要開發(fā)者根據(jù)實際情況來決定了.
3.3 未使用的代碼的處理
對于未使用的(或者是廢棄的)代碼, 包括Xcode自動生成的代碼或者是占位用的注釋等, 都應該移除掉. 當然如果是你需要保留的一些注釋除外, 比如未實現(xiàn)的代碼的注釋等.
另外就是一些自動生成的代碼塊, 實際并沒有做任何工作, 只是調(diào)用父類的實現(xiàn), 這類代碼也應該移除.
應該:
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return Database.contacts.count
}
不應:
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
override func numberOfSections(in tableView: UITableView) -> Int {
// #warning Incomplete implementation, return the number of sections
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of rows
return Database.contacts.count
}
3.4 最小化引用原則
保證代碼中最小限度地引入外部庫. 比如, 當可以只引入 Foundation
就能完成工作的時候, 就不要引入更大的 UIKit
了.
4 空格和縮進
- 使用兩個空格來代替
tab
.(非強制, 討論2個空格還是4個空格沒有任何意義, 唯一的要求是一個公司中必須使用同一種縮進策略, 比如統(tǒng)一2個空格.) - 代碼塊的起始大括號, 應和塊開頭的代碼位于同一行. 不要另起一行寫.(拋棄C語言的那一套習慣先)
- 利用 Xcode 自帶的格式化快捷鍵
control + I
來格式化代碼.
應該:
if user.isHappy {
// Do something
} else {
// Do something else
}
不應:
if user.isHappy
{
// Do something
}
else {
// Do something else
}
方法與方法之間應該用一個空行隔開.
方法內(nèi)部代碼應該使用空行來對不同邏輯功能的代碼塊進行分隔, 但不可過多, 過多的分隔意味著這個方法可以被再次重構(gòu)分解為多個方法.
-
冒號的使用原則: 冒號始終靠近左邊, 并且和左邊的詞之間沒有空格, 而右邊則需要一個空格:
應該:
class TestDatabase: Database { var data: [String: CGFloat] = ["A": 1.2, "B": 3.2] }
不應:
class TestDatabase : Database { var data :[String:CGFloat] = ["A" : 1.2, "B":3.2] }
每行的最大長度應該控制在一個規(guī)定值范圍內(nèi). 而這個值根據(jù)實際需要確定. 暫定120. 可以在Xcode中設置.
避免在行尾出現(xiàn)空白符. (為什么?)
每個文件的末尾添加一個空行. (為什么?)
5 注釋
寫注釋的目的是說明: "為什么這段特定代碼會做這樣的事情".
必須時刻保持注釋與相關代碼同步更新. 所以不能濫用注釋, 但也別不寫注釋. 最好的代碼是自解釋的.
這里所說的注釋指的是代碼注釋, 如果是用于生成文檔的文檔注釋, 則需要詳盡.
6 類和結(jié)構(gòu)
Struct 是值類型的, 故 struct 適用于不需要保持其狀態(tài)的對象. 例如: 一個包含 [啊, 波, 坡]
的數(shù)組和另外一個包含 [啊, 波, 坡]
的數(shù)組, 除了所處存儲空間不同外, 實際上表達的邏輯功能是一樣的, 二者可以隨意互換. 正因為如此, swift 中的 array, string 等等實際都是 struct, 即是值類型的.
Class 是引用類型的. 如果要使用的對象具有一定的特殊性, 并且需要保持其狀態(tài)(即具有一定的生命期), 故需要使用 Class作為該對象的類型. 比如需要建立兩個人的模型, 就需要使用 class 來表達這兩個人, 因為他們兩個是不一樣的, 但是兩個人的生日可以用 struct 來表達, 因為一個2000年1月1日和另外一個2000年1月1日表達的是同一個值.
需要根據(jù)實體的抽象意義, 合理選擇使用值類型還是引用類型.
應該:
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)
}
override func area() -> Double {
return Double.pi * radius * radius
}
}
extension Circle: CustomStringConvertible {
var description: String {
return "center = \(centerString) area = \(area())"
}
private var centerString: String {
return "(\(x),\(y))"
}
}
6.1 self的使用
不是必須的情況下, 盡量省略self.
6.2 計算屬性
當寫只讀屬性的 getter 時, 應省略 get塊:
應該:
var diameter: Double {
return radius * 2
}
不應:
var diameter: Double {
get {
return radius * 2
}
}
6.3 合理使用 final 關鍵字
7 閉包表達式
只有當閉包是方法的最后一個參數(shù)時, 才考慮寫尾隨閉包.
盡量給閉包中的參數(shù)賦予一個具體名稱來表達其含義.
應該:
UIView.animate(withDuration: 1.0) {
self.myView.alpha = 0
}
UIView.animate(withDuration: 1.0, animations: {
self.myView.alpha = 0
}, completion: { finished in
self.myView.removeFromSuperview()
})
不應:
UIView.animate(withDuration: 1.0, animations: {
self.myView.alpha = 0
})
UIView.animate(withDuration: 1.0, animations: {
self.myView.alpha = 0
}) { f in
self.myView.removeFromSuperview()
}
當上下文意思清晰的情況下, 可以省略單行閉包中的return:
attendeeList.sort { a, b in
a > b
}
另外就是閉包的一個格式問題, 由開發(fā)者按實際情況決定:
let value = numbers.map { $0 * 2 }.filter { $0 % 3 == 0 }.index(of: 90)
let value = numbers
.map {$0 * 2}
.filter {$0 > 50}
.map {$0 + 10}
8 類型
應盡可能使用 swift 中的原生類型.
應該:
let width = 120.0 // Double
let widthString = (width as NSNumber).stringValue // String
不應:
let width: NSNumber = 120.0 // NSNumber
let widthString: NSString = width.stringValue // NSString
只有當對性能改善更加明顯的情況下, 才考慮使用 OC 中的類型.(比如在 Sprite Kit 中使用 CGFloat)
8.1 常量
如果一個量的值不會發(fā)生變化的情況下, 應使用 let 聲明這個量, 而不是 var.
小貼士: 將所有的量都先用 let 聲明, 只有當編譯器發(fā)出警告時, 才替換為 var.
另外就是應盡量將所有使用到的常量按照功能不同放置在不同名字空間中(如enum).
應該:
enum Math {
static let e = 2.718281828459045235360287
static let root2 = 1.41421356237309504880168872
}
let hypotenuse = side * Math.root2
不應:
let e = 2.718281828459045235360287 // pollutes global namespace
let root2 = 1.41421356237309504880168872
let hypotenuse = side * root2 // what is root2?
上面的 enum 中沒有 case, 這樣的好處是讓其僅僅作為一個名字空間使用.
8.2 類方法和類屬性
類方法和類屬性的作用類似于全局變量, 不過由于它們處于某些名字空間下, 更加合理安全.
8.3 可選類型
當方法或函數(shù)的返回值可能不存在時, 則將返回值類型聲明為可選類型.
只有當確定可選值在當前時刻之前的確會存在的情況下, 才考慮用隱式可選類型 !
.
當值只會被訪問一次的情況下, 使用可選鏈來獲取這個值:
self.textContainer?.textLabel?.setNeedsDisplay()
當值會被多次用到時, 則使用可選綁定:
if let textContainer = self.textContainer {
// do many things with textContainer
}
由于在類型中已經(jīng)隱式說明了變量是可選類型的, 故在命名時盡量不要使用 optionalString
, maybeView
這樣形式的名字.
在使用可選綁定的時候, 盡量使用名字覆蓋的辦法, 不要再次將解包后的變量命名為諸如 unwrappedXXX
的形式.
應該:
var subview: UIView?
var volume: Double?
// later on...
if let subview = subview, let 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
}
}
8.4 懶加載
利用懶加載, 可以精確控制一個變量的生命期.
實現(xiàn)的時候可以使用一個直接執(zhí)行的閉包 { }()
或一個私有工廠方法進行對象創(chuàng)建, swift 會在使用到這個對象的時候才執(zhí)行那個閉包或者調(diào)用私有工廠方法.
lazy var locationManager: CLLocationManager = self.makeLocationManager()
private func makeLocationManager() -> CLLocationManager {
let manager = CLLocationManager()
manager.desiredAccuracy = kCLLocationAccuracyBest
manager.delegate = self
manager.requestAlwaysAuthorization()
return manager
}
注意:
- 利用閉包實現(xiàn)懶加載時, 不需要
[unowned self]
, 因為這里不會出現(xiàn)引用循環(huán). - 上述代碼中的 locationManager 首次創(chuàng)建的時候會請求用戶權(quán)限, 所以這里使用懶加載來加載它是非常合適的.
8.5 類型推導
盡量利用類型推導來寫更加短小簡潔的代碼. 只有當類型推導的默認類型不滿足要求時, 才考慮顯式指定類型. 另外就是對于短小的數(shù)組, 盡量直接使用數(shù)組的字面量來表達數(shù)組.
應該:
let message = "Click the button"
let currentBounds = computeViewBounds()
var names = ["Mic", "Sam", "Christine"]
let maximumWidth: CGFloat = 106.5
不應:
let message: String = "Click the button"
let currentBounds: CGRect = computeViewBounds()
let names = [String]()
8.5.1 空數(shù)組或字典的類型標識
如果新建空數(shù)組或字典供之后使用, 則應該使用類型標識.(另外如果字典或數(shù)組字面量表達式過長時, 也可以考慮添加類型標識)
應該:
var names: [String] = []
var lookup: [String: Int] = [:]
不應:
var names = [String]()
var lookup = [String: Int]()
8.5.2 類型標識的寫法
盡量使用 swift 的語法糖來寫類型標識, 而不要去用原始的冗長方式.
應該:
var deviceModels: [String]
var employees: [Int: String]
var faxNumber: Int?
不應:
var deviceModels: Array<String>
var employees: Dictionary<Int, String>
var faxNumber: Optional<Int>
9 函數(shù)和方法之間的選擇
只有當無法將這個功能歸入某個類型或子名字空間內(nèi)時, 才考慮使用函數(shù).
let tuples = zip(a, b) // feels natural as a free function (symmetry)
let value = max(x, y, z) // another free function that feels natural
其余的情況都應該將這些功能作為某個類型的類方法或?qū)ο蠓椒?
應該:
let sorted = items.mergeSorted() // easily discoverable
rocket.launch() // acts on the model
不應:
let sorted = mergeSort(items) // hard to discover
launch(&rocket)
10 內(nèi)存管理
內(nèi)存管理進入半自動時代, 所以唯一要記住的原則是: 不要制造引用循環(huán)!
在可能出現(xiàn)循環(huán)引用的情況下, 將變量類型標記為weak
或 unowned
. 另外, 也可以使用值類型 struct
或 enum
來避免引用循環(huán).(為什么? 這些對象的存儲空間還是在堆內(nèi)存中, 只是指針變量是放在棧中的)
10.1 閉包中的self
當在閉包中需要使用 self, 且會形成引用循環(huán)時, 考慮使用 weak
標記 self, 盡量不要使用 unowned
, 除非 self 在執(zhí)行這個閉包時很明顯是還存在的情況.
應結(jié)合 guard
來安全地使用 weak self
.
應該:
resource.request().onComplete { [weak self] response in
guard let strongSelf = self else {
return
}
let model = strongSelf.updateModel(response)
strongSelf.updateUI(model)
}
不應:
// might crash if self is released before response returns
resource.request().onComplete { [unowned self] response in
let model = self.updateModel(response)
self.updateUI(model)
}
也不應:
// deallocate could happen between updating the model and updating UI
resource.request().onComplete { [weak self] response in
let model = self?.updateModel(response)
self?.updateUI(model)
}
11 訪問控制
合理使用 private
和 fileprivate
, 這樣代碼會更加清晰, 且提高了封裝的質(zhì)量. 一般來說, 能用 private
的地方盡量用它. 但如果某些成分需要要 extension 中使用的時候, 則需要使用 fileprivate
.
另外的 open
, public
和 internal
, 就需要按情況來使用了.
訪問控制修飾符應該放在最開始的位置. 除了有 static
以及注解的時候.
應該:
private let message = "Great Scott!"
class TimeMachine {
fileprivate dynamic lazy var fluxCapacitor = FluxCapacitor()
}
不應:
fileprivate let message = "Great Scott!"
class TimeMachine {
lazy dynamic fileprivate var fluxCapacitor = FluxCapacitor()
}
12 流程控制
能夠使用 for-in
的時候就不要用 while
方式的循環(huán).
應該:
for _ in 0..<3 {
print("Hello three times")
}
for (index, person) in attendeeList.enumerated() {
print("\(person) is at position #\(index)")
}
for index in stride(from: 0, to: items.count, by: 2) {
print(index)
}
for index in (0...3).reversed() {
print(index)
}
不應:
var i = 0
while i < 3 {
print("Hello three times")
i += 1
}
var i = 0
while i < attendeeList.count {
let person = attendeeList[i]
print("\(person) is at position #\(i)")
i += 1
}
13 最佳側(cè)原則
當編寫條件判斷代碼的時候, 整個代碼對齊的左側(cè)稱為"最佳側(cè)", 意思是如果代碼越遠離左側(cè), 表示嵌套的層次越深, 代碼的質(zhì)量也就越低. 當有多層嵌套的時候, 盡量使用 guard
來簡化嵌套.
應該:
func computeFFT(context: Context?, inputData: InputData?) throws -> Frequencies {
guard let context = context else {
throw FFTError.noContext
}
guard let inputData = inputData else {
throw FFTError.noInputData
}
// use context and input to compute the frequencies
return frequencies
}
不應:
func computeFFT(context: Context?, inputData: InputData?) throws -> Frequencies {
if let context = context {
if let inputData = inputData {
// use context and input to compute the frequencies
return frequencies
} else {
throw FFTError.noInputData
}
} else {
throw FFTError.noContext
}
}
另外就是 guard
后面可以寫多條可選綁定語句, 這樣寫也可以簡化很多嵌套代碼.
應該:
guard let number1 = number1,
let number2 = number2,
let number3 = number3 else {
fatalError("impossible")
}
// do something with numbers
不應:
if let number1 = number1 {
if let number2 = number2 {
if let number3 = number3 {
// do something with numbers
} else {
fatalError("impossible")
}
} else {
fatalError("impossible")
}
} else {
fatalError("impossible")
}
13.1 guard 中失敗的處理
在 guard 后的 else 語句中寫的一般是某種形式的退出代碼, 且一般都是單行代碼如 return
, throw
, break
, continue
或者 fatalError()
等.
在實際編碼的時候, 盡量避免在里面寫大段的代碼. 如果真的需要寫一些清理功能的代碼, 則可以放到 defer
中去寫, 而不是在 guard
的 else
塊中.
14 分號的使用
swift 不強制使用分號, 且一般應該不去寫表達式末尾的分號. 只有當想在一行寫多條語句的時候, 語句間才用分號分隔.
但請記住: 不應將多條語句寫在一行上, 不應在表達式末尾添加分號.
不要把 swift 當做一種簡單的腳本語言看待.
應該:
let swift = "not a scripting language"
不應:
let swift = "not a scripting language";
15 括號的使用
盡量避免在條件語句中使用括號. 只有當語句比較復雜的時候, 才考慮用括號來組織.
應該:
if name == "Hello" {
print("World")
}
let playerMark = (player == current ? "X" : "O")
不應:
if (name == "Hello") {
print("World")
}
16 Xcode中的組織名稱以及 Bundle ID 的命名(可選)
Xcode 中的組織名稱(organization)需要符合英文書寫習慣, 即名字首字母大寫, 各單詞用空格分開, 比如 Ray Wenderlich
.
Bundle ID 需要設置為如 com.ray.TutorialName
的形式, 其中 TutorialName
即為工程名稱.
17 版權(quán)聲明的寫法
版權(quán)聲明需要在每個源文件的開頭都寫上. 這個就各有不同了.
下面是 MIT 開源協(xié)議的版權(quán)聲明文本模板, 僅作為參考.
/**
* Copyright (c) 2017 XXXXX公司 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.
*
* Notwithstanding the foregoing, you may not use, copy, modify, merge, publish,
* distribute, sublicense, create a derivative work, and/or sell copies of the
* Software in any work that is designed, intended, or marketed for pedagogical or
* instructional purposes related to programming, coding, application development,
* or information technology. Permission for such use, copying, modification,
* merger, publication, distribution, sublicensing, creation of derivative works,
* or sale is expressly withheld.
*
* 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.
*/
18 對待生活的態(tài)度
保持樂觀積極的態(tài)度, 笑也要開心地笑:
應該:
:] //這個是開心笑
不應:
:) //這個是勉強笑