Skip to the content.

現状の問題点

なぜか?

開発スピードをあげるにはどうするか?

なにができるか?

どう実践すればいいのか?

テストを書く目的とは?(前フリ)

書いたコードにバグがないことを保証するため?

「テストの書きやすいコードを書く」とは、テストを書くことが目的ではない

テストを書く目的とは?(本題)

追加・変更・リファクタリングが容易になっていれば、テストなしでも担保できる強度がある

-> 設計する

なぜ将来変更されるコードを設計をするのか

リファクタリング の目的とは?

プログラムは完成しない

ソフトウェア開発環境は常に変化している

機能変更や追加があるかぎり、リファクタリングを続ける

-> リファクタリングを続けるためには、リファクタリングしやすいコードにしておくべき

リファクタリングしやすいコード

リファクタリングしやすいコードを書くためにいくつかの原則を知っておくとやりやすい

SOLID の原則

S: Single Pesponsibility Principle(単一責任の原則)

単一のアクター(ユーザー・クラス・データベース・etc…)に対してだけ機能を提供できるように分割する

変更する理由が同じものは集める、変更する理由が違うものは分ける。

参考: 単一責任原則

// NG: 飲み物・乗り物・書類形式が増えるたびに変更する
class Operator {
  func serveTea() -> String { "Tea" }
  func serveMilk() -> String { "Milk" }
  func driveCar() {}
  func printPDF() {}
}

// OK: 変更する理由が飲み物が増える時だけ
class DrinkServer {
  func serveTea() -> String { "Tea" }
  func serveMilk() -> String { "Milk" }
}

class Driver {
  func driveCar() {}
}

class Printer {
  func printPDF() {}
}

O: Open/Closed Principle(オープン/クロースドの原則)

拡張に対してオープン、変更に対してクローズド

主に機能追加時には、クラスの内部実装を変更するのではなく拡張する

// NG: 変更にオープン
class NGClass {
  func print(type: SomeType) -> String {
    switch type {
      case aType: "a"
      case bType: "b"
      case cType: "c"
    }
  }
}

// OK: 拡張にオープン
protocol printable {
  func print() -> String
}

// OK: 変更にクローズド
class AClass: printable {
  func print() -> String { "a" }
}

class BClass: printable {
  func print() -> String { "b" }
}

class CClass: printable {
  func print() -> String { "c" }
}

L: Liskov Substitution Principle(リスコフの置換原則)

型 T と 派生型 S がある時、プログラム内で T 型のオブジェクトが使われている箇所は全て S 型のオブジェクトで置換可能

protocol T {
  func call()
}

struct S: T {
  func call()
}

class C {
  func function(t: T) { t.call() }
}

// OK: 置換可能であること
class C {
  func function(t: S) { t.call() }
}

T 型のオブジェクトが使われている箇所では T の振る舞いだけを許可する

protocol T {
  func call()
}

struct S: T {
  func call()
  func aCall()
}

// NG: T の宣言箇所で S 固有のメソッドを使ってはいけない
class C {
  func function(t: T) { (t as S).aCall() }
}

I: Interface Segregation Principle(インターフェース分離の原則)

インターフェースの利用者(実装クラス)にとって不要なメソッドに依存させてはいけない

protocol DrinkServerable {
  func serveTea()
  func serveMilk()
}

// NG: 不要なメソッドを実装させている
class TeaServer: DrinkServerable {
  func serveTea() -> String { "Tea" }
  func serveMilk() -> String { "" }
}

protocol TeaServerable {
  func serveTea()
}

protocol MilkServerable {
  func serveMilk()
}

// OK: 必要なメソッドのみを実装させる
class TeaServer: TeaServerable {
  func serveTea() -> String { "Tea" }
}

D: Dependency Inversion Principle(依存性逆転の原則)

依存対象の実装を知るのではなく、抽象に依存する


// NG: Some の実装に依存している
class C {
  func call() {
    let some = Some()
    some.aCall()
  }
}

protocol Someable {
  func aCall()
}

// OK: 抽象に依存している
class C {
  func call(someImpl: Someable) {
    someImpl.aCall()
  }
}

オブジェクト指向プログラミング

クラスはデータとデータを扱うメソッドをもつ

カプセル化

class User {
  private let firstName: String
  private let lastName: String

  var fullName: String { lastName + firstName }

  init(first: String, last: String) {...}
}

let user = User("Aaa", "Bbb")
user.firstName = "Ccc" // NG: 外部からデータを変更させない
user.fullName

継承

class Parent {
  func 暗黙的なメソッド() {}
}

class Chilld: Parent {
  func child固有メソッド() {}
}

class Brother: Parent {}

let child = Chilld()
chlid.暗黙的なメソッド()
child.child固有メソッド()

let bro = Brother()
bro.暗黙的なメソッド()

多態性(ポリモーフィズム)

アドホック多相

class C {
  func function(args: String)
  func function(args: Int)
}

C().function(args: "Hello")
C().function(args: 1000)

パラメータ多相

class C {
  func function<T>(args: T) {}
}

C().function(args: "Hello")
C().function(args: 1000)

部分型付け

メッセージパッシング

遅延バインディング

プロトコル指向プログラミング