GoFデザインパターン ~Template Methodパターン~
こちらを読みながら進める。
- 作者: 結城浩
- 出版社/メーカー: ソフトバンククリエイティブ
- 発売日: 2004/06/19
- メディア: 大型本
- 購入: 51人 クリック: 762回
- この商品を含むブログ (397件) を見る
Template Methodパターンとは
抽象クラスで処理の枠組みを決め、実処理は具象クラスで行うというのがTemplate Methodパターン。下図でいうと、抽象クラスであるTemplateのexec()でmethodA(), methodB(), methodC()を利用するが、その具体的な実装はConcrete1, Concrete2クラスの責務となる。骨組みは抽象クラスで、肉付けは具象クラスでというイメージ。
実装
増補改訂版Java言語で学ぶデザインパターン入門で使われてる例をGoで実装してみる。
Template Methodパターンは抽象クラスを利用するが、Goには継承が存在しない(埋め込みで継承っぽいこともできるが、親構造体から子構造体のメソッドを呼べないなど厳密には継承とはいえない)ため、Javaなどの言語とは異なる実装になる。
ディレクトリ構成は以下
./3_template_method ├── main.go └── printer ├── abstract_printer.go ├── char_display.go ├── printer.go ├── string_display.go
抽象クラス
package printer type AbstractPrinter struct { } func (*AbstractPrinter) Display(printer Printer) string { result := printer.Open() result += printer.Print() result += printer.Close() return result }
テンプレートメソッドである Display()
の引数でPrinterインターフェース(を実装した子クラスのインスタンス)を受け取る。
継承は行なっていないが、こうすることで骨組みであるAbstractPrinterクラスのテンプレートメソッド(Display()
)で具象クラスのメソッドを呼ぶことができる。
具象クラス
具象クラスのインターフェース。継承のある言語では継承の機能を使って子クラスの実装を担保できるが、Goではインターフェースによって具象クラスの実装を担保する。
具象クラスに要求されるのは open()
print()
close()
の実装。
package printer type Printer interface { Open() string Print() string Close() string }
AbstractPrinterの具象クラスであるCharDisplay, StringDisplay
package printer type charDisplay struct { char string } func NewCharDisplay(char string) Printer { return &charDisplay{char: char} } func (*charDisplay) Open() string { return "<<" } func (cd *charDisplay) Print() string { return cd.char } func (*charDisplay) Close() string { return ">>" }
package printer type stringDisplay struct { char string } func NewStringDisplay(char string) Printer { return &stringDisplay{char: char} } func (*stringDisplay) Open() string { return "+--+" } func (cd *stringDisplay) Print() string { return "|" + cd.char + "|" } func (*stringDisplay) Close() string { return "+--+" }
Main
Main実装
package main import ( "fmt" "github.com/saekis/go-design-pattern/3_template_method/printer" ) func main() { ad := printer.AbstractPrinter{} fmt.Println(ad.Display(printer.NewCharDisplay("char_display"))) fmt.Println(ad.Display(printer.NewStringDisplay("string_display"))) }
実行結果
$ go run main.go <<char_display>> +--+|string_display|+--+
クラス図
実装したもの
GoFデザインパターン ~Adapterパターン~
こちらを読みながら進める。
- 作者: 結城浩
- 出版社/メーカー: ソフトバンククリエイティブ
- 発売日: 2004/06/19
- メディア: 大型本
- 購入: 51人 クリック: 762回
- この商品を含むブログ (397件) を見る
Adapterパターンとは
すでに存在するモジュールと必要なAPIとの間にAdapterを挟むことで、元のコードに手を加えることなくAPIを満たすことできる、というがAdapterパターン。
Adapterは必要となるAPIであるインターフェースを実装し、継承または委譲によってメソッドの中でAdapteeであるモジュールを使用する。
実装
増補改訂版Java言語で学ぶデザインパターン入門で使われてる本棚の例をGoで実装してみる。
Adapteeであるbannerを使って、Adapterインターフェースを満たすためにBannerAdapterを実装する
ディレクトリ構成は以下
├── banner │ ├── banner.go ├── banner_adapter │ ├── banner_adapter.go └── main.go
Adaptee
package banner import "fmt" type Banner struct { Str string } func (b *Banner) WithParen() string { return fmt.Sprintf("(%s)", b.Str) } func (b *Banner) WithAster() string { return fmt.Sprintf("*%s*", b.Str) }
Adapter
Clientが必要とするAdapterインターフェース
type Adapter interface { GetWeak() string GetStrong() string }
BannerAdapterの実装
package banner_adapter import "github.com/saekis/go-design-pattern/2_adapter/banner" type Adapter interface { GetWeak() string GetStrong() string } type bannerAdapter struct { banner banner.Banner } func NewBannerAdapter(str string) Adapter { return &bannerAdapter{banner: banner.Banner{Str: str}} } func (b *bannerAdapter) GetWeak() string { return b.banner.WithParen() } func (b *bannerAdapter) GetStrong() string { return b.banner.WithAster() }
Main
Main実装
package main import ( "fmt" "github.com/saekis/go-design-pattern/2_adapter/banner_adapter" ) func main() { b := banner_adapter.NewBannerAdapter("Hello") fmt.Println(b.GetWeak()) fmt.Println(b.GetStrong()) }
実行結果
$ go run main.go (Hello) *Hello*
クラス図
実装したもの
GoFデザインパターン ~Iteratorパターン~
こちらを読みながら進める。
- 作者: 結城浩
- 出版社/メーカー: ソフトバンククリエイティブ
- 発売日: 2004/06/19
- メディア: 大型本
- 購入: 51人 クリック: 762回
- この商品を含むブログ (397件) を見る
Iteratorパターンとは
コレクションに対する繰り返し処理で使われるパターン。コレクションオブジェクトとイテレータを分離し、イテレータをinterfaceで抽象化することで、利用側がコレクションのデータ構造に依存することなくiterate処理を行えるようになるというもの。
登場人物の関係をシンプルに書くとこんな感じ。
集約とイテレータを分離するために集約の実装には Iterator()
が要求され、イテレーションにおける具体的なコレクション操作はIteratorインターフェースを実装したImplementIterator
に委譲される。ので、利用側は集約への依存ではなくIteratorインターフェースへの依存によってiterate処理を行うことができる。
実装
増補改訂版Java言語で学ぶデザインパターン入門で使われてる本棚の例をGoで実装してみる。
要件は、本(Entity)を格納する本棚(Aggregate)を作り、main処理で本棚に入っている本の名前を順番に表示するというもの。
ディレクトリ構成は以下
├── bookshelf │ ├── Aggregate.go │ ├── Book.go │ ├── Bookshelf.go │ ├── Bookshelfiterator.go │ ├── Iterator.go │ └── Product.go └── main.go
Aggregate
Aggregateインターフェース。
package bookshelf type Aggregate interface { Iterator() *Iterator }
ImplementAggregate。簡単な配列操作とIterator()の実装
package bookshelf type bookshelf struct { books []Product last int } func NewBookShelf(maxsize int) *bookshelf { return &bookshelf{books: make([]Product, maxsize), last: 0} } func (bs *bookshelf) GetByIndex(index int) Product { return bs.books[index] } func (bs *bookshelf) AppendBook(b Product) { bs.books[bs.last] = b bs.last++ } func (bs *bookshelf) GetLength() int { return bs.last } func (bs *bookshelf) Iterator() Iterator { return NewBookshelfIterator(bs) }
Iterator
Iteratorインターフェース。このあたりは要求に合わせて変えていけば良さそう
package bookshelf type Iterator interface { HasNext() bool Next() Product }
ImplementIteratorの実装。
package bookshelf type bookshelfIterator struct { bookshelf Aggregate index int } func NewBookshelfIterator(bookshelf Aggregate) Iterator { return &bookshelfIterator{bookshelf: bookshelf, index: 0} } func (it *bookshelfIterator) HasNext() bool { return it.index < it.bookshelf.GetLength() } func (it *bookshelfIterator) Next() Product { book := it.bookshelf.GetByIndex(it.index) it.index++ return book }
Entity
Entityを表すProductインターフェース。ここまで厳密な型を用意しなくていいかも
package bookshelf type Product interface { GetName() string }
package bookshelf type book struct { name string } func NewBook(name string) *book { return &book{name} } func (b *book) GetName() string { return b.name }
main
main処理
package main import ( "fmt" "github.com/saekis/go-design-pattern/1_iterator/bookshelf" ) func main() { bs := bookshelf.NewBookShelf(3) bs.AppendBook(bookshelf.NewBook("リーダブルコード")) bs.AppendBook(bookshelf.NewBook("達人プログラマ")) bs.AppendBook(bookshelf.NewBook("Team Geek")) it := bs.Iterator() for it.HasNext() { book := it.Next() fmt.Println(book.GetName()) } }
実行結果
$ go run main.go リーダブルコード 達人プログラマ Team Geek
サンプル実装したクラス図
実装したもの置き場
MySQLの排他ロックの挙動を確認する
MySQLの排他ロックについて、業務では使っているもののふわっとした理解だったので調べたメモを残しておく
ACIDモデル
トランザクションのことを調べているとよく目にするACIDというワード。こちらもふわっとしてたのでまとめてみる。
ACIDについてはMySQLの公式ドキュメントで以下のように説明されていた
ACID モデルは、ビジネスデータおよびミッションクリティカルなアプリケーションで重要となる信頼性の側面が強調されたデータベース設計原則のセットです。
信頼性の高いトランザクションシステムを構築するにあたって大切な原則、という感じか。
ACIDは4つの原則の頭文字を取った略語で、その内訳は以下のようになる。
Atomicity(原子性)
トランザクション内の処理が、全て実行されるか全て実行されないかを保証すること。一つのトランザクションそのものが原子的であるべき、ということ。
Consistency(一貫性)
トランザクション開始時点から終了時点まで、常に同じ状態のデータを参照することを保証すること、という理解。トランザクションの処理中に扱うデータが外部の影響を受けたらよくないよね、一貫性がなくなるよね、という感じ。
Isolation(独立性)
Consistencyとも似てる気がするけど、これは扱うデータが他のトランザクションから独立していることを保証すること。
Durability(永続性)
トランザクションの結果が失われないことを保証すること。
トランザクション分離レベル
トランザクション分離レベルとは、RDBMSで発生しえる不都合な読み込み(ダーティリード、ファジーリード、ファントムリード)をどこまで許容するかの線引きのこと。
MySQLでデフォルトの設定はREPEATABLE READ
だが、MySQLのREPEATABLE READ
はUPDATEとDELTEではREAD COMMITTED
の挙動をする。
設定の確認
mysql> SELECT @@GLOBAL.transaction_isolation; +--------------------------------+ | @@GLOBAL.transaction_isolation | +--------------------------------+ | REPEATABLE-READ | +--------------------------------+
以下の検証ではトランザクション分離レベルの設定がREPEATABLE-READ
であることを前提に進める。
調査環境
$ mysql --version mysql Ver 8.0.13 for osx10.13 on x86_64 (Homebrew)
準備
まずは検証で使うテーブルとデータを用意する。適当にuserのデータを作る。
mysql> create table users (id int unsigned not null auto_increment, name varchar(255) not null, is_active boolean not null, primary key(id)) ENGINE=InnoDB DEFAULT CHARSET=utf8; mysql> desc users; +-----------+------------------+------+-----+---------+----------------+ | Field | Type | Null | Key | Default | Extra | +-----------+------------------+------+-----+---------+----------------+ | id | int(10) unsigned | NO | PRI | NULL | auto_increment | | name | varchar(255) | NO | | NULL | | | is_active | tinyint(1) | NO | | NULL | | +-----------+------------------+------+-----+---------+----------------+ mysql> insert into users (id, name, is_active) values (1, "一郎", 1), (2, "二郎", 1), (3, "三郎", 1), (10, "太郎", 1), (20, "たかし", 1); mysql> select * from users; +----+-----------+-----------+ | id | name | is_active | +----+-----------+-----------+ | 1 | 一郎 | 1 | | 2 | 二郎 | 1 | | 3 | 三郎 | 1 | | 10 | 太郎 | 1 | | 20 | たかし | 1 | +----+-----------+-----------+
排他ロック(SELECT ~ FOR UPDATE
)
排他ロックでは、ロックされたレコードは他での単純なSELECT文以外を許容しない。
つまり、排他ロックがかかったレコードに対しては、他のトランザクションからUPDATEやDELETEができない。ロックを取ることもできない。
レコードロック
-- 他のトランザクションからSELECT ~ FOR UPDATE Transaction A> begin; Transaction B> begin; A> select * from users where id=1 for update; -- Aでロック取得 1 row in set (0.00 sec) B> select * from users where id=1; -- Bからシンプルなselectは可能 1 row in set (0.00 sec) B> select * from users where id=1 for update; -- ここで止まる A> commit; B> 1 row in set (6.01 sec) -- Aのコミット後にBで結果が返ってくる -- UPDATE A> begin; B> begin; A> select * from users where id=1 for update; -- Aでロック取得 1 row in set (0.00 sec) B> UPDATE users SET is_active=0 where id=1; -- ここで止まる A> select * from users where id=1; +----+--------+-----------+ | id | name | is_active | +----+--------+-----------+ | 1 | 一郎 | 1 | +----+--------+-----------+ A> commit; B> Query OK, 2 rows affected (20.69 sec) -- Aのコミット後にBで結果が返ってくる B> commit; A> select * from users where id=1; -- Bのコミット後 +----+--------+-----------+ | id | name | is_active | +----+--------+-----------+ | 1 | 一郎 | 0 | +----+--------+-----------+ -- DELTE A> begin; B> begin; A> select * from users where id=1 for update; 1 row in set (0.00 sec) B> delete from users where id=1; -- ここで止まる A> commit; B> Query OK, 2 rows affected (20.69 sec) -- Aのコミット後にBで結果が返ってくる Rows matched: 3 Changed: 2 Warnings: 0 A> begin; A> select * from users where id=1; -- BのコミットはまだなのでAのレコードはまだ存在する +----+--------+-----------+ | id | name | is_active | +----+--------+-----------+ | 1 | 一郎 | 0 | +----+--------+-----------+ 1 row in set (0.00 sec) B> commit; A> select * from users where id=1; -- Bはコミットされたが、その前にAのトランザクションが開始したのでレコードはまだ存在している(Isolation(隔離性)の担保) +----+--------+-----------+ | id | name | is_active | +----+--------+-----------+ | 1 | 一郎 | 1 | +----+--------+-----------+ 1 row in set (0.00 sec) A> begin; A> select * from users where id=1; -- Bのコミット後に始まったトランザクションではid=1のレコードは存在しない Empty set (0.00 sec)
ギャップロック
ギャップロックとは、インデックスのレコードとレコードの間にかかるロックのこと。ファントムリードを防ぐのが目的。
A> begin; A> select * from users where id=4 for update; -- 空振りだがギャップロックがかかる Empty set (0.00 sec) B> begin; B> insert into users (id, name, is_active) values (5, 'しんご', 1); -- ここで止まる A> commit; B> Query OK, 1 row affected (31.32 sec) -- Aのコミット後にBで結果が返ってくる
なおprimary keyでの一行指定だけじゃなく範囲指定でも同様に存在しないレコードにロックがかかる。
ネクストキーロック
インデックスレコードのロックと、そのレコードの直前にあるギャップのロックを組み合わせたもの。
公式の説明
ネクストキーロックは、インデックス行ロックとギャップロックを組み合わせたものです。InnoDB は、テーブルインデックスを検索またはスキャンするときに、生成されたインデックスレコード上に共有ロックまたは排他ロックを設定するという方法で、行レベルロックを実行します。したがって、行レベルロックは、実際にはインデックスレコードロックです。さらに、あるインデックスレコードに対するネクストキーロックによって、そのインデックスレコードの前の「ギャップ」も影響を受けます。つまり、ネクストキーロックは、インデックスレコードロックと、そのインデックスレコードの前のギャップに対するギャップロックとを組み合わせたものです。
とりあえず今はそういうもんだと思っておく。実際にネクストキーロックがかかるか試してみる
A> select * from users; +----+-----------+-----------+ | id | name | is_active | +----+-----------+-----------+ | 1 | 一郎 | 1 | | 2 | 二郎 | 1 | | 3 | 三郎 | 1 | | 10 | 太郎 | 1 | | 20 | たかし | 1 | +----+-----------+-----------+ A> select * from users where id >= 11 and id <= 19 for update; -- 11~19でギャップロックがかかる B> update users set is_active=0 where id=20; -- ここで待たされるのでid=20に排他ロックがかかってる。これがネクストキーロックっぽい
ちなみにこれid範囲指定のロックでネクストキーロックを取れたけど、レコード指定で試してみたらギャップロックは取れたけどネクストキーロックは取れなかった...
このあたりちゃんと調べたい。
ちなみに共有ロックは
共有ロックは他のトランザクションのupdate, delete, 排他ロック取得を許容しない。
ただ、共有ロックが取得されたレコードの共有ロックを他のトランザクションでも取得することは可能。
今回は排他ロックの挙動を調べる回なので検証はしなかった。業務で必要になったら調べる。
参考にさせていただきました
MySQLのデフォルトトランザクション分離レベルはREPEATABLE READだけど… - Qiita
[RDBMS][SQL]トランザクション分離レベルについて極力分かりやすく解説 - Qiita
MySQL InnoDBのロックの挙動 - sambaiz-net
MySQL :: MySQL 8.0 Reference Manual :: 15.7.1 InnoDB Locking
MySQL :: MySQL 5.6 リファレンスマニュアル :: 14.2.6 InnoDB のレコード、ギャップ、およびネクストキーロック
2019年の目標とかやりたいことリスト
2018年が終わって2019年を迎えたので、ざっくりと今年やりたいことを列挙してみる。
仕事
- フリーのバックエンドエンジニアとして仕事を続ける
- 年収は現状+50万くらいを目指す
- Golangで仕事をする
- マイクロサービス, コンテナ, gRPCとかそのあたりの仕事をする
- 海外から小さい案件を受注する
- 日本以外でも稼げるようになるために海外の会社との仕事を経験する
- 案件探し, 受注, 契約, 開発, 納品まで一通り英語でできるようになる
- 自分が作ったサービスで1円以上の収入を得る
- オーバーワークしない
技術
- Golang
- Scala, Play Framework
- Docker, Kubernetes
- gRPC
- CI/CD
- MySQL, Cloud Spanner
- CloudSQL
- GCP, AWS
勉強
- アルゴリズムを勉強し直す
- 数学を勉強し直す
- 機械学習やる
- デザインパターン
- DDD
- 英語やる
- テック系の記事はなるべく英語で書かれたものを読む
- オンライン英会話やる
- オンラインで海外大学のCSの学位を取る
- 2019年のうちに取るのは難しそう...
- まずは英語...
プライベート
- インドア人間なのでアウトドアな経験をしてみる
- 何らかのスポーツ観戦を現地に行ってする
- 釣り(オフラインの方)を経験する
- 麻雀の牌効率を勉強する
- 麻雀の役判定サービスを作る
- 雀荘に行ってみる
- 買ったまま放置してるギターを練習して何か一曲弾けるようになる
- 作曲してどこかに公開する
- 貯金
- 勉強会で1回以上LTをする
- 100冊くらい本を読む
- 3日で1冊ペース...
- ブログを書く
- 彼女と仲良く
- 週に一回は走る
- 睡眠時間は少なくとも7時間, 平均8時間くらいを目指す
- ちゃんと野菜を食べる
- 自分がストレスに感じることは極力しない(コンフォートゾーンを抜ける活動を除いて)
- 自分の事が嫌いになるようなことは極力しない
- ドイツ旅行する
- 温泉旅行する
- 知り合いから飲みに誘われたらできるだけ断らない
2018年を振り返る
1月
このあたり、実はあまり記憶がないけどとにかく忙しかったのを覚えてる。忙しかったから記憶にあまり残ってないともいえる。
転職がちらつき始めたのもたしかこの頃で、自分のやりたい仕事と当時やっていた仕事との間に大きなギャップを感じていて、その焦りが日々の忙しなさの中でどんどん肥大化していた。精神的にかなりしんどかったと思う。秒速5センチメートルのセリフを借りれば、心の弾力を失っていた状態だったと思う。
とにかく前に進みたくて、届かないものに手を触れたくて、それが具体的に何を指すのかも、ほとんど強迫的とも言えるようなその思いが、どこから湧いてくるのかも解らずに僕はただ働き続け、気付けば、日々弾力を失っていく心がひたすら辛かった。
特に転職のために何かしたということもなく、仕事が忙しいことを理由に仕事以外はとても堕落した日々を送っていた。
2月
この月も1月以上に忙しかった気がする。何にも覚えてない。
3月
このあたりもかなり忙しかったような気がする。
たしかこの月だったと思うが同僚だった@furu8maさんと若手とおっさんというpodcastを始めた。podcastは継続的にインターネットに対して何かを出力する装置としてとても優れていると思う。文章を書くのは孤独な作業で、慣れていないと時間も体力も使う。だからとっつきやすさがある一方で、継続することのハードルは意外と高い。その点podcastは継続しやすい。配信環境を整える最初の段階こそハードルが高いように見えるが、環境が整ってしまえばあとは喋るだけだ。一人で配信する場合を除いて、相方がいるというのはそれだけで継続するモチベーションが高くなる。文章を書くときのような推敲も必要ない。パッと1時間くらい喋ってそのまま配信するだけである。それに音声は文章と違って、ストックされずにフロー情報として受け手に届く。その気楽さから出力すること自体のハードルも一気に下がる気がする。みんなpodcastやってほしい。
4月
覚えてない。休みの日にRubyでプロダクトをいくつか作る活動をしていた気がするが、結局全部日の目を見ることなくボツになった。プロダクトとして完成していないものを作っては壊す、ということをここ数年なんども繰り返している気がする。良いコードを書くことで満足してしまってリリースまでモチベーションが保てないのが原因だろう。2019年に作るものはしっかりリリースしてしっかり供養してあげたいと思う。
あとここで本格的に転職活動を始めた気がする。今の食い扶持であるフリーランスとしての仕事もこのあたりで探し初めていた気がする。
5月
4月だったような気もするが、このあたりで会社に退職の意向を伝える。伝えた時点では次どうするか決めていなかった。ただ休みたかった。
退職を伝えた次の週あたりでフリーランスとして参画する会社が決まった。参画する会社は大きいベンチャーで、その会社が扱っている業務のドメインに興味があったのと、優秀なエンジニアが多いイメージがあり自分の成長が見込めると思った。今もその会社でバックエンドエンジニアとして参画している。
6月
前職の最終出社日が6月初頭、次の会社の参画日が7月初頭だったのでこの月はほぼ丸々一ヶ月仕事をしなかった。
シュタゲを1期から見返して泣いたり、コードを書いたり、積ん読してた分厚い技術書を読んだり、忙しくてなかなか会えなかった人に会いに行ったり、高尾山のビアガーデンでビールクズになったりした。とても充実していた。
人間には年に一ヶ月くらい、こういった忙しさを理由に出来ないでいることを消化する期間が必要だと思った。少なくとも自分には必要だと確信した。
7月
充実した6月が終わり、7月から新しい環境での労働が始まった。6月の余韻で7月の労働は厳しいかなと思っていたが、案外スムーズに新しい環境で労働を始められて人間の順応力の高さに驚いた。
フリーランスとして本格的に働き始めた月で、そういう意味ではわりと自分の人生にとってここが分岐点になる気がする。文章書くの飽きてきた...
8月
9月
10月
11月
特筆すべきこと特になし。
12月
というわけで12月だが、ここも特筆すべきことがあまりない。しいていえば仕事、7月からフリーランスとして働いてきて半年が経ったことか。
正直、心境としてあまり大きな変化があったという実感はない。ただ客観的に見れば、個人事業主になったこと、自分のお金の管理が煩雑になったこと、働く環境、関わるビジネス、会社へのコミットの仕方など、いろいろ大きく変
化しているようには思える。...ということを書いてるうちに、なんだが実感が湧いてきた。
おわり
書くの疲れたからこのへんで。我ながらひどい文章だ... 来年もよろしくお願いします m(__)m
2018年9月5日
仕事
業務委託でお世話になってる会社では主に昔から動いてるシステムの保守や追加開発をやっている。
どこかで「保守しかできないエンジニアはゆるやかに死んで行く」というような内容のエントリを読んだことを雑に思い出してこのままじゃゆるやかに死ぬなおれ、という気持ちになった。
やっぱ個人開発かー。個人開発だよな。進めるぞ。
本を読むスピードが遅い
のだ。かなり遅い方だと思う。例えば300ページくらいの小説なら読み終えるのに2, 3日かかる。1日30分で2, 3日、ということではない。だいたい1日3, 4時間くらい費やして2, 3日である。
活字を追うのは好きなので読書それ自体にストレスはないし、むしろ現実逃避の手段として、ストレスから逃げるために読書することも多い。
自分がノロノロと一冊の本を読んでいる間に、優秀な人がより多くの本に出会い、人生観を揺さぶられて、知識が増えて、仕事の幅が広がって、どんどん優秀になっていくことを想像すると、自分は世界に取り残された気分になる。こういうことを感じるようになったのも、新しい職場の周りの人が異常に優秀な方ばかりで、自分は本当に知らないことが多いしできないことも多いということを日々感じているからだと思う。