My External Storage

Feb 28, 2020 - 10 minute read - Comments - go

[冒頭公開]技術書典 応援祭にgolang.tokyo新刊「Gopherの休日2020冬」で参加します #技術書典

技術書典8は中止になってしまいましたが、オンラインで開催される技術書典 応援祭にgolang.tokyoも参加します。
私は、今回の新刊である「Gopherの休日2020冬」に「GoにおけるSOLID原則」という内容で寄稿しました。
また、その冒頭部分も公開します。気になる方はどうぞ次のリンク先よりご購入ください。

golang.tokyo 技術書典 応援祭 新刊「Gopherの休日2020冬」について

今回の新刊は私以外にも6名以上の方々が執筆しており、以下のような内容になっています(敬称略)。

Gopherの休日2020冬

  • GoにおけるSOLIDの原則 / @budougumi0617
  • Go本体にコントリビュートする方法 / @hajimehoshi
  • TUIツールを作ろう / @gorilla0513
  • GoとコンセンサスアルゴリズムRaftによる分散システム構築入門 / @po3rin
  • Goにおける初期化処理 / @kaneshin0120
  • text/template の linter を作ろう / @knsh14
  • Goによるコンテナランタイム自作入門 / @_moricho_
  • and more…

今回もすべての内容が技術書典 応援祭のための書き下ろしになっています。
Go本体の解説やコントリビューション方法、分散システム、linter・TUI、コンテナランタイムの自作まで多種多様です。
ページ数は現時点で計150ページ以上になっており、販売価格1,000円となります。 (なお、売上はすべてgolang.tokyoのノベルティ作成などに使われます)
また、前回同様、@tottie_designerさんに表紙・裏表紙を作成していただきました。

寄稿内容について

私はオブジェクト指向設計の重要な原則であるSOLIDの原則を話題に「GoにおけるSOLID原則」という章を書きました。
抽象クラス・具象クラスなどが存在しないGoにおいてどのようにSOLIDの原則の思想を生かしていくか、を(自分なりに解釈して)まとめています。

以下、試し読み代わりに冒頭を転載します(脚注などはブログ用に編集)。
内容に興味がでた方はぜひ応援祭でご購入ください。冒頭に記載したとおり、この他にも様々なテーマで寄稿されています。


GoにおけるSOLIDの原則

@budougumi0617です。
オブジェクト指向設計の原則を5つまとめたSOLIDの原則the SOLID principles)という設計指針があることはみなさんご存じでしょう。
本章ではSOLIDの原則にのっとったGoの実装について考えます。

SOLIDの原則the SOLID principles)とは

SOLIDの原則は次の用語リストに挙げた5つのソフトウェア設計の原則の頭文字をまとめたものです(アジャイルソフトウェア開発の奥義 第2版より引用)。

  • 単一責任の原則SRP, Single responsibility principle
    • クラスを変更する理由は1つ以上存在してはならない。
  • オープン・クローズドの原則OCP, Open–closed principle
    • ソフトウェアの構成要素構成要素(クラス、モジュール、関数など)は拡張に対して開いて(オープン: Open)いて、修正に対して閉じて(クローズド: Closed)いなければならない。
  • リスコフの置換原則LSP, Liskov substitution principle
    • S型のオブジェクトo1の各々に、対応するT型のオブジェクトo2が1つ存在し、Tを使って定義されたプログラムPに対してo2の代わりにo1を使ってもPの振る舞いが変わらない場合、STの派生型であると言える。
  • インターフェイス分離の原則(ISP, Interface segregation principle
    • クライアントに、クライアントが利用しないメソッドへの依存を強制してはならない。
  • 依存関係逆転の原則DIP, Dependency inversion principle
    • 上位のモジュールは下位のモジュールに依存してはならない。どちらのモジュールも「抽象」に依存すべきである。「抽象」は実装の詳細に依存してはならない。実装の詳細が「抽象」に依存すべきである。

これらの原則は、ソフトウェアをより理解しやすく、より柔軟でメンテナンス性の高いものにする目的で考案されました。 各々の原則の原案者や発表時期は異なりますが、この5つの原則から頭文字のアルファベットを1つずつ取りSOLIDの原則としてまとめたのがRobert C. Martin氏です。

メーリングリストに投稿されたRobert C. Martin氏のコメントを見ると、1995年時点でオープンクローズドの原則(The open/closed principle)などの命名がされていることがわかります。

また、SOLIDの原則として5つの原則がまとめて掲載された日本語書籍は、「アジャイルソフトウェア開発の奥義」になります。 SOLIDの原則の成り立ち自体についてもっと知りたい場合は、@nazonohito51さんの「「SOLIDの原則って何ですか?」って質問に答えたかった」を読んでみるとよいでしょう。

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

本題へ入る前に、Goとオブジェクト指向プログラミングの関係を考えてみます。 そもそもオブジェクト指向に準拠したプログラミング言語であることの条件とは何でしょうか。 さまざまな主張はありますが、本章では次の3大要素を備えることが「オブジェクト指向に準拠したプログラミング言語であること」とします。

  1. カプセル化(Encapsulation
  2. 多態性(ポリモフィズム)(Polymorphism
  3. 継承(Inheritance)

ではGoはオブジェクト指向言語なのでしょうか。Go公式サイトにはFrequently Asked Questions (FAQ)という「よくある質問と答え」ページがあります。 この中のIs Go an object-oriented language?Goはオブジェクト指向言語ですか?)という質問に対する答えとして、次の公式見解が記載されています。

Yes and no. Although Go has types and methods and allows an object-oriented style of programming, there is no type hierarchy. The concept of “interface” in Go provides a different approach that we believe is easy to use and in some ways more general. There are also ways to embed types in other types to provide something analogous—but not identical—to subclassing. Moreover, methods in Go are more general than in C++ or Java: they can be defined for any sort of data, even built-in types such as plain, “unboxed” integers. They are not restricted to structs (classes).

Also, the lack of a type hierarchy makes “objects” in Go feel much more lightweight than in languages such as C++ or Java.

あいまいな回答にはなっていますが、「Yesであり、Noでもある。」という回答です。 Goはオブジェクト指向の3大要素を一部しか取り入れていないため、このような回答になっています。

Goはサブクラシング(subclassing)に対応していない

多くの方がオブジェクト指向言語に期待する仕組みの1つとして、先ほど引用した回答内にもあるサブクラシングsubclassing)が挙げられるでしょう。 もっと平易な言葉で言い直すと、クラス(型)の階層構造(親子関係)による継承です。 代表的なオブジェクト指向言語であるJavaでサブクラシングの例を書いたコードが次のコードです。 このコードは、親となるPersonクラスと子となるJapaneseクラスの定義と、Personクラスを引数に取るメソッドを含んでいます。

class Person {
  String name;
  int age;
}

// Personクラスを継承したJapaneseクラス
class Japanese extends Person {
  int myNumber;
}

class Main {
  // Personクラスを引数にとるメソッド
  public static void Hello(Person p) {
    System.out.println("Hello " + p.name);
  }
}

Personクラスを継承したJapaneseクラスのオブジェクトは、ポリモフィズムによってPerson変数に代入できます。 また、同様にPersonクラスのオブジェクトを引数にとるメソッドに対して代入することもできます。

Japanese japanese = new Japanese();
person.name = "budougumi0617";
Person person = japanese;
Main.Hello(japanese);

このようなポリモフィズムを目的とした継承関係を表現するとき、Goではインターフェースを使うでしょう。 しかし、Goは上記のような具象クラス(あるいは抽象クラス)を親とするようなサブクラシングによる継承の仕組みを言語仕様としてサポートしていません。

Goでは埋込み(Embedding)を使って別の型に実装を埋め込むアプローチもあります。

しかし、これは多態性や共変性・反変性を満たしません。

よって、埋め込みはオブジェクト指向で期待される継承ではなくコンポジションにすぎません。 次のコードは前述JavaのコードをGoで書き直したものです。 Goの例では、Japanese型のオブジェクトはHello関数に利用することはできません。

type Person struct {
  Name string
  Age  int
}

// Personを埋め込んだJapanese型。
type Japanese struct {
  Person
  MyNumber int
}

func Hello(p Person) {
  fmt.Println("Hello " + p.Name)
}

以上の例以外にも、リスコフの置換原則などの一部のSOLIDの原則はそのままGoに適用することはできません。 しかし、SOLIDの原則のベースとなる考えを取り入れることでよりシンプルで可用性の高いGoのコードを書くことは可能です。

それでは、次節よりGoのコードにSOLIDの原則を適用していくとどうなっていくか見ていきます。 なお、GoとSOLIDの原則については、Dave Cheney氏も2016年にGolangUKSOLID Go Designというタイトルで発表されています。

[コラム]実装よりもコンポジションを選ぶ

あるクラスが他の具象クラス(抽象クラス)を拡張した場合の継承を、Javaの世界では実装継承implemebtation inheritance)と呼びます。 あるクラスがインターフェースを実装した場合や、インターフェースが他のインターフェースを拡張した場合の継承をインターフェース継承interface inheritance)と呼びます。 Goがサポートしている継承はJavaの言い方を借りるならばインターフェース継承のみです。 クラスの親子関係による実装継承はカプセル化を破壊する危険も大きく、深い継承構造はクラスの構成把握を困難にするという欠点もあります。 このことは代表的なオブジェクト指向言語であるJavaの名著、「Effective Java」の「項目16 継承よりコンポジションを選ぶ」でも言及されています(筆者が所有しているEffective Javaは第2版ですが、2018年にJava 9対応のEffective Java 第3版が発売されています)。 Go実装継承をサポートしなかった理由は明らかになっていませんが、筆者は以上の危険性があるためサポートされていないと考えています。


転載は以上です。頒布物ではこの後個別の原則をGoでどう活かすかを20ページ弱にまとめています。

おわりに

まだ私自身応援祭の具体的な形式がわかっておりませんが、ぜひご購入ください。 私自身日々学びながら設計・実装しているので、読後「これはこうじゃない?」みたいな感想などもいただけると幸いです。

2020/02/28 21:00 追記

応援際の詳細が公開されました。

2020/03/10 追記

販売ページが公開されています。

技術書典 応援祭(技術書典8)用に執筆された新刊です。

技術書典7で頒布した既刊も販売しています。

関連情報

関連記事