ライブラリのリファクタリング実践

# はじめに

自作で公開していたライブラリがあったのですが、ずいぶん前に公開したライブラリなので今回リファクタリングしました。
リファクタリングをしたポイントを記事にまとめましたので、他にも良さげな書き方等あれば気軽にコメントいただけると幸いです。

今回リファクタリングしたライブラリはUI/UX系アニメーションのライブラリです。

![qiitaSample.gif](https://qiita-image-store.s3.amazonaws.com/0/62451/e0aa4b0e-7452-973f-1823-4fd82f643d2d.gif)

[SYBlinkAnimationKit](https://github.com/shoheiyokoyama/SYBlinkAnimationKit)
[CocoaControls](https://www.cocoacontrols.com/controls/syblinkanimationkit)

## 参考にしたスタイルガイドなど

- [github/swift-style-guide](https://github.com/github/swift-style-guide)
- [エウレカ流Swift Style Guide](https://developers.eure.jp/tech/swift-style-guide/)
- [raywenderlich/swift-style-guide](https://github.com/raywenderlich/swift-style-guide)



## 適切なアクセス修飾子をつける

- `Public` 外部からアクセス可能
- `Internal` モジュール内(ライブラリ内)のみアクセス可能
- `Private` ファイル内のみアクセス可能

ライブラリは特にアクセス修飾子を意識する必要があります。
外部からアクセスできるクラス、定数、変数、関数は`public`修飾子、ライブラリのファイル間で利用するものは`internal`修飾子(自動的に付与されるので基本的に`internal`修飾子はつけない)。それ以外は`private`修飾子をつけるようにしています。
通常の開発では`public`修飾子はあまり使わず、`private`修飾子をつけるかつけないかになってくると思いますが、意図しない変数や関数の呼び出しはバグの温床です。
ファイル(クラス)間でのインタフェースはパッと見てわかるように、基本的には`private`修飾子をつけ、外部からのインタフェースの役割をするもののみ`private`修飾子をつけないようにするべきだと思います。

## 継承を許容しない`Class`は常に`final`をつける

###### Before
```swift
public class SYButton: UIButton {
...
```
###### After

```swift
public final class SYButton: UIButton {
...
```

継承を許容させないために`class`につける`final`ですが、継承を許容させてる意図がわかりやすいように、基本的に`class`には`final`をつけ、継承を許容させてる`class`のみ`final`をつけないというスタイルにしました。
今回では継承して使用することを想定とした`SYTableViewCell`クラス以外はfinalをつけました。
githubのスタイルガイドでは、基本的に`class`に`final`をつけることを推奨しています。

参考： [github/swift-style-guide](https://github.com/github/swift-style-guide)


## 条件分岐の処理

###### Before

```swift
switch animationType {
case .border:
    syLayer.animationType = .border
case .borderWithShadow:
    syLayer.animationType = .borderWithShadow
case .background:
    syLayer.animationType = .background
case .ripple:
    syLayer.animationType = .ripple
case .text:
    syLayer.animationType = .text
}

```

###### After

```swift
syLayer.animationType = {
            switch animationType {
            case .border:
                return .border
            case .borderWithShadow:
                return .borderWithShadow
            case .background:
                return .background
            case .ripple:
                return .ripple
            case .text:
                return .text
            }
}()
```

条件分岐の処理を関数型っぽく書き直しました。条件によっていろいろな処理や挙動を行うものならbeforeのままでいいと思いますが、このように関数型っぽく書いたほうが、条件によって`syLayer.animationType`になにかを代入するということが一目見て分かりやすくなると思います。この書き方なら`syLayer.animationType =`というコピペ作業もいらないですね。優秀なエンジニアはコピペ作業をしないものです。

参考： [Swift リファクタリング実践 Tips1](http://qiita.com/ezura/items/b34537343869d4cc5f76)


## Extensionで実装を分割する(`// MARK: -` を使用する)

```swift
// MARK: - Private Methods -

private extension SYButton {

    private func setup() {
    ...

```
今回のライブラリでは、`private methods`が多くなりがちなので`extension`で`private`な関数を分割しています。また`delegate`などの処理を`extension`で分割すると処理を一箇所にまとめることができるのでわかりやすいかと思います。

###### e.g.
```swift

// MARK: - UITableViewDataSource -

extension UIViewController: UITableViewDataSource {
...

// MARK: - UITableViewDelegate -

extension UIViewController: UITableViewDelegate {

```

### `// MARK: -`

関数ジャンプを使ってる人も多いと思いますが、`// MARK: -`を使用することで、その場所にジャンプすることができます。実装が大きくなった時には相当便利になるので処理のまとまりごとに`// MARK: -`を使うようにしています。

<p align="center"><img width="200" alt="スクリーンショット 2016-07-27 11.38.42.png" src="https://qiita-image-store.s3.amazonaws.com/0/62451/4abb4c28-16c9-26ba-f3be-ea2f4a739621.png"></p>

参考：　[エウレカ流Swift Style Guide](https://developers.eure.jp/tech/swift-style-guide/)

## Swift実装のstruct initializersを使う

###### Before

```swift
let rect = CGRectMake(0, 0, 100, 100)
let point = CGPointMake(0, 0)
let size = CGSizeMake(100, 100)

let height = CGRectGetHeight(frame)
let width = CGRectGetWidth(frame)
```

###### After

```swift
let rect = CGRect(x: 0, y: 0, width: 100, height: 100)
let size = CGSize(width: 100, height: 100)
let point = CGPoint(x: 0, y: 0)

let height = frame.height
let width = frame.width
```

Swiftからは`CGRect`や`CGSize`などの構造体が定義され、便利なメソッドがいくつか追加されました。Swift3からは`CGRectMake`などのObjective-Cの実装は使えなくなるので、Swiftからは`CGRect`などのstruct initializersを使うことが推奨されています。

e.g.

```swift
let frame = CGRect(x: 0, y: 0, width: 100, height: 100)
let maxX = frame.maxX
let maxY = frame.maxY

let rect = CGRect.zero
let size = CGSize.zero
let point = CGPoint.zero
```

参考
- [CGRectMake , CGPointMake, CGSizeMake, CGRectZero, CGPointZero is unavailable in Swift](http://stackoverflow.com/questions/37946990/cgrectmake-cgpointmake-cgsizemake-cgrectzero-cgpointzero-is-unavailable-in)
- [Swift: CGRect, CGSize & CGPoint](https://medium.com/swift-programming/swift-cgrect-cgsize-cgpoint-5f4196da9cf8#.meuszdteh)

## 必要な時以外は、`ClearColor`は使用しない

このライブラリではアニメーションする初期の色をいくつか設定しているのですが、透明色`UIColor.clearColor()`を使用する場合は透過処理が入るため、描画パフォーマンスは通常の色と比較して落ちます。本当に必要な時以外はむやみに`UIColor.clearColor()`を使用しない方がいいと思います。

## 定数を定義する

###### Before

```swift
private var animationBorderColor = UIColor(red: 54/255, green: 215/255, blue: 183/255, alpha: 1)
private var animationDuration: CFTimeInterval = 1.5
```

###### After

```swift
struct AnimationDefaultColor {
    static let border     = UIColor(red: 54/255, green: 215/255, blue: 183/255, alpha: 1)
    static let background = UIColor(red: 248/255, green: 148/255, blue: 6/255, alpha: 1)
    static let text       = UIColor(red: 214/255, green: 69/255, blue: 65/255, alpha: 1)
    static let ripple     = UIColor(red: 171/255, green: 183/255, blue: 183/255, alpha: 1)
}


private var animationBorderColor = AnimationDefaultColor.border

//必要に応じてアクセスコントロールをprivateにする
private struct AnimationConstants {
        static let borderWidth: CGFloat = 1
        static let defaultDuration: CFTimeInterval = 1.5
        static let rippleDiameterRatio: CGFloat = 0.7
        ...
}

private var animationDuration: CFTimeInterval = AnimationConstants.defaultDuration
```

マジックナンバーを扱う場合は後から見て意図がわかりにくかったり、同じ値を多用してる時に変更しにくいなどのデメリットがあるかと思います。
このように構造体で定数を定義することにより、名前空間を汚染しにくくまとまった定数を構造体に収納することができます。

## `enum`と`extension`を使用して初期化処理を便利にする

###### Before

```swift
let borderColorAnimtion = CABasicAnimation(keyPath: "borderColor")

let backgroundColorAnimtion = CABasicAnimation(keyPath: "backgroundColor")
```

###### After

```swift
extension CABasicAnimation {
    convenience init(type: AnimationKeyType) {
        self.init(keyPath: type.rawValue)
    }
}

enum AnimationType: String {
        case borderColor
        case backgroundColor
        ..


//使用時
let borderColorAnimtion = CABasicAnimation(type: .borderColor)

let backgroundColorAnimtion = CABasicAnimation(type: .backgroundColor)

```

`CABasicAnimation`は`keyPath`に`String`でアニメーションを指定して使用します。
今回は`AnimationType`という`enum`を定義し`extension`で初期化処理を追加することによって、`enum`のケースでアニメーションを指定するようにしました。
`AnimationType`に定義することで今回使用しているアニメーションの種類をまとめて管理できることと、補完でアクセスできるので楽に`CABasicAnimation`を定義することができます。
`enum`で管理しているので、ケースごとによる挙動もここで管理できます。`extension`のアクセスレベルは`internal`なので、実際にライブラリを使用する側にも影響はありません。


## `protocol`を使用する

### ケース1　共通化

```swift
private func setTextLayer() {
        guard let font = titleLabel?.font, text = currentTitle else {
            return
        }

        var attributes = [String: AnyObject]()
        attributes[NSFontAttributeName] = font
        
        let size  = text.sizeWithAttributes(attributes)
        let x     = ( self.frame.width - size.width ) / 2
        let y     = ( self.frame.height - size.height ) / 2
        let frame = CGRect(origin: CGPoint(x: x, y: y), size: CGSize(width: size.width, height: size.height + layer.borderWidth))

        textLayer.font            = font
        textLayer.string          = text
        textLayer.fontSize        = font.pointSize
        textLayer.foregroundColor = textColor.CGColor
        textLayer.contentsScale   = UIScreen.mainScreen().scale
        textLayer.frame           = frame
        textLayer.alignmentMode   = kCAAlignmentCenter
    }
```

テキストのアニメーション処理のため、`TextLayer`の処理が書かれたクラスがいくつかあり、似たような処理がいくつかのクラスに書かれていました。これはDryの原則にも反していますし、どこかで共通化したいですよね。
今回はこれを`protocol`実装で共通化しました。

```swift
protocol TextConvertible {
    var textLayer: CATextLayer { get set }
    
    func configureTextLayer(text: String?, font: UIFont?, textColor: UIColor)
}

extension TextConvertible where Self: UIView {
    func configureTextLayer(text: String?, font: UIFont?, textColor: UIColor) {
        guard let text = text, font = font else { return }
        
        var attributes = [String: AnyObject]()
        attributes[NSFontAttributeName] = font
        
        let size  = text.sizeWithAttributes(attributes)
        let x     = ( self.frame.width - size.width ) / 2
        let y     = ( self.frame.height - size.height ) / 2
        let frame = CGRect(x: x, y: y, width: size.width, height: size.height + layer.borderWidth)
        
        textLayer.font            = font
        textLayer.string          = text
        textLayer.fontSize        = font.pointSize
        textLayer.foregroundColor = textColor.CGColor
        textLayer.contentsScale   = UIScreen.mainScreen().scale
        textLayer.frame           = frame
        textLayer.alignmentMode   = kCAAlignmentCenter
    }
}

//使用時
class SYButton: UIButton, TextConvertible {
...

func resetTextLayer() {
configureTextLayer(currentTitle, font: titleLabel?.font, textColor: textColor)
...
```

`protocol`実装にすることで複数のクラスでこの処理を共通化することができます。

### ケース2　処理の分割化

今回`SYLayer`というクラスに、`CALayer`の管理とアニメーションの処理の二つが実装されていたためクラスが肥大化していました。
原則として***1つのクラスに1つの役割***というものがあると思いますが、ここもどうにかしたいと思っていたので`protocol`を利用して処理の分割化を図りました。

<p align="center"><img width="494" alt="スクリーンショット 2016-07-29 17.48.09.png" src="https://qiita-image-store.s3.amazonaws.com/0/62451/65c6a45e-90d0-d40f-00dc-31deff25060b.png"></p>


###### Before


```swift
class SYLayer {
...

// CALayerを管理する処理
func setLayer() {
...

func resizeLayer() {
...

// アニメーションの処理
func startAnimation() {
...

func stopAnimation() {
...
```

###### After

```swift

// CALayerを管理するクラス Animator protocolに準拠
class SYLayer: Animator {
...

func setLayer() {
...

func resizeLayer() {
...


// アニメーションを管理するprotocol
protocol Animatable {
..

extension Animatable {

func startAnimation() {
..

func stopAnimation() {
...


//使用時
class SYLabel {
..

let layer = SYLayer(self.layer)
layer.startAnimation()
```

`SYLayer`クラスのアニメーションの処理を`Animatable protocol`に任せることで、処理が分割され`SYLayer`クラスの肥大化を防ぐことができたので、構成が理解しやすくなったと思います。


# 終わりに

今回はリファクタリングした大きなポイントをまとめました。
今後も随時更新していこうと思います。