重構出更加swifty的程式碼

weixin_33895657發表於2016-12-19

原文
不小心在推特上瀏覽到一個文章,很好的詮釋了什麼是swifty的swift程式碼。
如何檢測一個信用卡型別呢,引數如下表格所示(主要的4個,還有各種亂七八糟的)

Issuing Network IIN ranges Card Number Length
American Express 34, 37 15
MasterCard 51–55,2221–2720 16
Visa 4 13, 16, 19
Discover 65,6011,644–649,622126–622925 16, 19

Option 1: if大法

先來試試最粗暴的演算法:

如果是4開頭就是visa,如果是3開頭則可能是Amex, Diners, or JCB,如果是6開頭則可能是Discover

這裡有段現成的程式碼 PaymentKit,翻譯成swift大概就是這樣

// ...
// Discover
if cardString.hasPrefix("6011") || cardString.hasPrefix("65") {
    return .discover
}
else if cardString.hasPrefix("622") {
    // If the number has a prefix in the range 622126-622925, it's Discover
    let prefixLength = 6;
    if (cardString.characters.count >= prefixLength) {
        let prefixIndex = number.index(cardString.startIndex, offsetBy: prefixLength)
        let sixDigitPrefixString = cardString.substring(to: prefixIndex)
        let sixDigitPrefixInt = Int(sixDigitPrefixString)!
        if sixDigitPrefixInt >= 622126 && sixDigitPrefixInt <= 622925 {
            return .discover;
        }
    }
    return .unknown
}
else if cardString.hasPrefix("64") { ... }
// ...

這特麼是什麼鬼程式碼,別人維護起來得多痛苦,尤其是銀行玩點么蛾子多改下的時候,修改這些程式碼簡直是噩夢

Option 2:正則大法

現在我們用正則來替代if大法

//amex starts with 34 or 37 and is 15 digits
case .amex:
    return "^3[47][0-9]{13}$"

很cooool 很簡潔是吧,現在來看看.masterCard:

//MasterCard starts with 51 through 55 
//or 2221 through 2720. All have 16 digits.
case .masterCard:
   return "^(?:5[1-5][0-9]{2}|" +
            "222[1-9]|" +
            "22[3-9][0-9]|" +
            "2[3-6][0-9]{2}|" +
            "27[01][0-9]|" +
            "2720)" +
            "[0-9]{12}$"
...

黑人問號???
正規表示式是個很強大的工具,但是正則在表示式在表達數值範圍時候就顯得不那麼優雅了,更別說是這種可變長度的數字,而且還很不好懂,這也pass。

一個更簡潔,具有變現力點程式碼:

我們用的是swift,肯定有一種更好的方式來處理類似的情況,先別換編譯器的嘲笑,我們先寫點虛擬碼,

case .amex:         prefix = ["34", "37"]
                    length = [15]
 
case .diners:       prefix = ["300"..."305", "309", "36", "38"..."39"]
                    length = [14]
 
case .discover:     prefix = ["6011", "65", "644"..."649", "622126"..."622925"]
                    length = [16]
 
case .jcb:          prefix = ["3528"..."3589"]
                    length = [16]
 
case .masterCard:   prefix = ["51"..."55", "2221"..."2720"]
                    length = [16]
 
case .visa:         prefix = ["4"]
                    length = [13, 16, 19]

現在判斷的規則是不是變的相當的簡潔明瞭,如何做到呢,你可以先看程式碼 CardParser.swift.

強大的ENUM:

CardType是swift的中列舉型別,swift列舉是個強大的武器不但可以進行模式匹配還可以驗證規則:

enum CardType {
    case visa
    case masterCard
    ...
 
    var segmentGroupings: [Int] {...}
    var cvvLength: Int {...}
    ...
 
    func isValid(_ accountNumber: String) -> Bool {...}
    func isPrefixValid(_ accountNumber: String) -> Bool {...}
}

visa.cvvLength          // 3
visa.isPrefixValid("4") // true
visa.isValid("4")       // false

強大的協議

待續

相關文章