「リファクタリング 第2版」Swiftでコーディング 第1章完了

リファクタリング 第2版

第1章完了

全ソース掲載。
Swiftとしてリファクタリングした方がいい箇所があるかは見直し。
例えば「Result使用」「ファイル分割」

Swift版 main.swift

import Foundation

makeData()

func statement(invoice:Invoice, plays:Dictionary<String, Play>) -> String {
    return renderPlainText(data: createStatementData(invoice: invoice, plays: plays))
}

func renderPlainText(data:StatementData) -> String {
    var result = "Statement for \(data.customer)\n"

    for perf in data.performances {
        result += "  \(perf.play.name): " + usd(aNumber: perf.amount) + " (\(perf.performance.audience) seats)\n"
    }
    result += "Amount owed is " + usd(aNumber: data.totalAmount) + "\n"
    result += "You earned \(data.totalVolumeCredits) credits\n"
    return result
}

func htmlStatement(invoice:Invoice, plays:Dictionary<String, Play>) -> String {
    renderHtml(data: createStatementData(invoice: invoice, plays: plays))
}

func renderHtml(data:StatementData) -> String {
    var result = "<h1>Statement for \(data.customer)</h1>\n"
    result += "<table>¥n"
    result += "<tr><th>play</th><th>seats</th><th>cost</th></tr>\n"
    for perf in data.performances {
        result += "  <tr><td>\(perf.play.name)</td><td>\(perf.performance.audience)</td>"
        result += "<td></td>" + usd(aNumber: perf.amount) + "</td></tr>\n"
    }
    result += "</table>\n"
    result += "<p>Amount owed is <em>" + usd(aNumber: data.totalAmount) + "</em></p>\n"
    result += "<p>You earned <em>\(data.totalVolumeCredits)</em> credits</p>\n"
    return result
}

let resultPlain = statement(invoice: invoices[0], plays: plays)
print("--- Plain Text ---")
print(resultPlain)

let resultHtml = htmlStatement(invoice: invoices[0], plays: plays)
print("---  HTML ---")
print(resultHtml)

Swift版 createStatementData.swift

import Foundation

struct PerormanceMapPlay {
    let performance: Performance
    let play: Play
    var amount: Int = 0
    var volumeCredits: Int = 0
}

struct StatementData {
    let customer: String
    let performances: [PerormanceMapPlay]
    var totalAmount: Int = 0
    var totalVolumeCredits: Int = 0
}

enum ResultError : Error {
    case failure(String)
}

public class PerformanceCalculator {
    let aPerformance: Performance
    let play: Play

    init(aPerformance: Performance, play: Play) {
        self.aPerformance = aPerformance
        self.play = play
    }

    func amount() throws -> Int {
        throw ResultError.failure("サブクラスの責務")
    }
    
    func volumeCredits() -> Int {
        return max(self.aPerformance.audience - 30, 0)
    }
}

class TragedyCalculator : PerformanceCalculator {
    override func amount() -> Int {
        var result = 40000
        if self.aPerformance.audience > 30 {
            result += 1000 * (self.aPerformance.audience - 30)
        }
        return result
    }
}

class ComedyCalculator : PerformanceCalculator {
    override func amount() throws -> Int {
        var result = 30000
        if self.aPerformance.audience > 20 {
            result += 10000 + 500 * (self.aPerformance.audience - 20)
        }
        result += 300 * self.aPerformance.audience
        return result
    }

    override func volumeCredits() -> Int {
        return super.volumeCredits() + Int(self.aPerformance.audience / 5)
    }
}

func createPerformanceCalculator(aPerformance: Performance, play: Play) throws -> PerformanceCalculator {
    switch play.type {
    case "tragedy":
        return TragedyCalculator(aPerformance: aPerformance, play: play)
    case "comedy":
        return ComedyCalculator(aPerformance: aPerformance, play: play)
    default:
        throw ResultError.failure("未知の演劇の種類:\(play.type)")
    }
}

func playFor(aPerformance:Performance) -> Play {
    return plays[aPerformance.playID]!
}

func usd(aNumber:Int) -> String {
    let format = NumberFormatter()
    format.numberStyle = .currency
    format.locale = Locale(identifier: "en_US")
    return format.string(from: NSNumber(value: aNumber / 100))!
}

func totalVolumeCredits(data:StatementData) -> Int {
    return data.performances.reduce(0) { (num: Int, perf: PerormanceMapPlay) -> Int in
        num + perf.volumeCredits
    }
}

func totalAmount(data:StatementData) -> Int {
    return data.performances.reduce(0) { (num: Int, perf: PerormanceMapPlay) -> Int in
        num + perf.amount
    }
}

func enrichPerfoemance(aPerformance:[Performance], plays:Dictionary<String, Play>) -> [PerormanceMapPlay] {
    var result: [PerormanceMapPlay] = []
    for perf in aPerformance {
        do {
            let calculator = try createPerformanceCalculator(aPerformance: perf, play: playFor(aPerformance: perf))
            var perormanceMapPlay = PerormanceMapPlay(performance: perf, play: calculator.play)
            perormanceMapPlay.amount = try calculator.amount()
            perormanceMapPlay.volumeCredits = calculator.volumeCredits()
            result.append(perormanceMapPlay)
        }
        catch {
            print(error.localizedDescription)
        }
    }
    return result
}

func createStatementData(invoice:Invoice, plays:Dictionary<String, Play>) -> StatementData {
    var result = StatementData(customer: invoice.customer, performances: enrichPerfoemance(aPerformance: invoice.performances, plays: plays))
    result.totalAmount = totalAmount(data: result)
    result.totalVolumeCredits = totalVolumeCredits(data: result)
    return result
}

Swift版 data.swift

import Foundation

let json_plays = """
{
"hamlet": {"name": "Hamlet", "type": "tragedy"},
"aslike": {"name": "As You Like It", "type": "comedy"},
"othello": {"name": "Othello", "type": "tragedy"}
}
"""

let json_invoices = """
[
{
"customer": "BigCo",
"performances": [
{
"playID": "hamlet",
"audience": 55
},
{
"playID": "aslike",
"audience": 35
},
{
"playID": "othello",
"audience": 40
}
]
}
]
"""

struct Play: Codable {
    let name: String
    let type: String
}

struct Plays: Codable {
    let hamlet: Play
    let aslike: Play
    let othello: Play
}

struct Performance: Codable {
    let playID: String
    let audience: Int
}

struct Invoice: Codable {
    let customer: String
    let performances:[Performance]
}

let json_plays_data: Data =  json_plays.data(using: String.Encoding.utf8)!
let json_invoices_data:Data = json_invoices.data(using: String.Encoding.utf8)!

var plays:Dictionary<String, Play> = [:]
var invoices:[Invoice] = []

func makeData() {
    let decoder: JSONDecoder = JSONDecoder()

    do {
        let json: Plays = try decoder.decode(Plays.self, from: json_plays_data)
        plays["hamlet"] = json.hamlet
        plays["aslike"] = json.aslike
        plays["othello"] = json.othello
        
    } catch {
        print("error:", error.localizedDescription)
    }

    do {
        let json = try decoder.decode([Invoice].self, from: json_invoices_data)
        invoices = json
    } catch {
        print("error:", error.localizedDescription)
    }
}

Swift

Posted by shi-n