Feature Flagを用いたA/B TestingツールをGCPで設計する
先日、AbemaTVが開催する「AbemaTV x A/B Testing」にてFeature Flagを用いたA/B Testingツールのインフラアーキテクチャ設計を行いました。
その時の学びをまとめていきます。
要件
Feature Flagを用いてA/Bテストできれば良いのですが、以下は守る必要がありました。
- Uiの表示速度に影響しない
- データに対して柔軟に素早く集計・分析できるようにする
- スケーラブル
アーキテクチャ構成図
僕達の最終的なアウトプットはこのようになりました。
色々書いてありますが、大事なのは以下の2つです。
- 構成図下部のclientアプリがフォアグラウンドに切り替わったタイミングでfeature flagを受け取る。
- test対象のclientは定期的にaction logをlogging service(構成図右下)にpostする。
Feature Flag取得時のキャッシュ戦略
上図の通り、とりあえずUIを表示し非同期で取得し次回起動時に反映するという方式をとりました。
こうすることで、「UIの表示速度に影響しない」という要件を満たします。
しかしデメリットもあります。
それはFeature Flag取得までにラグがあることです。
すぐにFlagを切り替えたい時に、二回のアプリ起動が必要になります。
そのためアプローチとして以下の措置をとりました。
- プッシュ通知でアップデートフラグをローカルに立てる
- クライアントが次回アクティブになった時に強制的に反映(ロードが生じる)
わざわざローカルにフラグを立てるのは、プッシュ通知時に一斉にアップデートするというモデルはスケーラブルではないからです。
スケーラブル戦略
最も瞬間的にアクセスが集中する部分はflag取得前のtestユーザーチェックだと考えました。
(↓緑で囲われてる部分)
そこで、MySQLの前段にRedisを置くことを決断しました。
set型の集合にuser_idをkeyにして、testユーザーチェック等の処理に必要な情報を置いておきます。
こうすることで、O(1)でのreadが可能になります。
また、Redis Clusterを使用することで複数ノードに自動的に分散することができます。
永続化とクラスタリングにはデータ更新時に整合性の問題がありますが、今回はtest終了時まで更新はないので問題ありません。
各micro serviceの仕事
lab receive service(構成図左下)
- Labからセグメント情報もらう
- ユーザーがアンケート等で直接入力した値と比較し、いい感じに挿入
logging service(右下)
- clientから生のaction logを受け取る
- redisからuserが参加中のtest情報を受け取り、BigQueryに挿入
aggregate service(右上)
- バッチでBigQueryに対してクエリを実行
- 管理画面からの呼び出しによって人為的にクエリ実行
cache service(左上)
- テスト実行者が設定した情報をもとに、前段に置く値を決定する
フィードバック
構成図右部のlog集計部分をLambda Architectureに従って設計するべきだとのフィードバックを受けました。
Lambda Architecture
Lambda Architectureとは、以下3つのレイヤーから構成される設計指針です。
バッチレイヤー:ログをバッチで集計するので正確だけど、リアルタイムではない
サービスレイヤー:バッチレイヤーの集計結果を提供する
スピードレイヤー:データはリアルタイムだが、データは少し不正確の可能性ある
スピードレイヤーの集計で得た最新の値とバッチレイヤーの集計によって得た確定した値をマージすることで、素早く推定することが出来るというモデルらしいです。
まだあまり理解していないので、詳しくは以下書籍を読んでみます。
まとめ
無知は罪だということを痛感しました。引き続きインプットの質を高めていこうと思います。
GoでDDD設計する際のrepositoryをどう定義するか
GoDDDでrepositoryを設計する際に色々考えたのでメモ
アーキテクチャ
この記事では、レイヤードアーキテクチャを使用します。
しかし正式なレイヤードでなく、以下のようにinfra層がdomain層に依存する形で設計します。
ディレクトリ構成
. ├── app ├── domain │ └── user │ ├── user.go │ └── user_repository.go ├── infra │ └── mongo │ └── user_repository.go └── ui
repository定義
今回は以下のUserエンティティを永続化することを想定して勧めていきます。
package user type User struct { ID uint Name string }
repositoryはinfra層とdomain層の2層に定義します。
domain層
domainディレクトリ配下に、
以下のようなRepositoryをインターフェースとして定義しておき、このRepositoryを実装すればUserエンティティが返ってくることを保証しておきます。
domain/user/user_repository.go
type UserRepository interface { Find(id int64) (User, error) FindAll(limit int) ([]User, error) }
infra層
infraディレクトリ配下に、
domain層のRepositoryインターフェースを実装したRepositoryを定義します。
infra/mongo/user_repository.go
package mongo type UserRepository struct { Context context.Context } func (r *UserRepository) Find(id int64) (user.User, error) { return r.find(r.Context, id) } func (r *UserRepository) FindAll(limit int) ([]user.User, error){ return r.findAll(r.Context, limit) }
repositoryの使用
package main import ( "fmt" "../domain/user" "../infra/mongo" ) func main() { // Userエンティティが取得できることを保証 var repo user.UserRepository repo = getUserRepoFromInfra() user, _ := repo.Find(1) fmt.Printf("type is %T", user) // => type is user.User } func getUserRepoFromInfra() user.UserRepository { return &mongo.UserRepository{} }
窓口の抽象化
以上のように、domain層とinfra層の窓口を抽象化しておくことでインフラの差し替えを容易にする事が出来ます。
2018年のテーマは「持ちつ持たれつ」
2017年は4皮くらい剥けた年でした。 まずはそんな2017年のテーマを振り返ります。
2017年は決める年
去年正月に書いたとおり、2017年は決める年でした。
人生哲学を決める
決まりました。
どんどん見える世界が変わっているので確定ではありませんが、毛穴から手が出そうなくらいなりたい姿があります。
29歳までにカリスマのソフトウェアエンジニアになる
まつもとゆきひろさん、宮川達彦さん、中島聡さん、小飼弾さん、伊藤直也さん、笹田耕一さん、ひげぽんさん、、、、
29までに彼らのようなカリスマになって各分野のカリスマ達と対等に情報交換をし、今見えない世界を見たい。
それから30代の生き方を決めたい。
大きなOSSにコミットして、各地で講演会して、技術書書いて、WEB+DBに連載載せる29歳になりたい。
中学の時は、グラブ職人に憧れていました。知る人ぞ知る存在になりたい気持ちが昔から強かったのです。
この夢に全身が納得していますが、最初は上手くいきませんでした。
このように、人生ピラミッドを作りまくってた時もありました。
外に説明するには非常に筋の通った綺麗なものにはなりましたが、自分は納得していませんでした。
なんでも筋を通せばいいってもんじゃないですね。
決めるスピードと精度を高める
「意思決定」が苦手だったので、この一年はそこを意識しました。
インターン先の上司との面談を繰り返して、自分は意思決定が苦手なのではなく、捨てることが苦手だということが分かりました。
何を捨てるか、どんな情報が集まれば決定出来るかを意識した結果後悔ない意思決定を素早く出来るようになりました。
逐一何時までに何をするかを決める
何時までに何をするか、小さな意思決定を毎日した一年でした。
12月にはサイバーエージェントにて、約2週間でマッチングアプリのサーバーサイド開発を完了させました。
時間密度を濃くすることを意識した成果がはっきりと見えました。
2018年は持ちつ持たれつ
冒頭で申したとおり、2017年は4皮くらい剥けた年でした。
それも、多くの人の助けがあったおかげです。
しかし、自分はヒトに頼るのが苦手な一匹狼体質です。(誕生時に姓名判断したところ、唯一心配だったのがこれだったらしい)
困っても一人で解決しようとする。ヒトにもあまり干渉しない。それが自分のアイデンティティであるとも思っていました。
しかしやっぱり一人の力には限界がある。そして寂しい。
2017年の終盤は、1人では乗り越えられない壁がたくさん現れました。そこではちきれそうになった自分を救ってくれたのは、いつもヒトでした。
助けてもらうと、助け返したくなります。
助け合うと、二人の間に暖かい信頼関係が生まれることに気づきました。
この「暖かさ」を感じてる時が一番幸せです。
この形容し難い「暖かさ」を大切にしたい。それは必然的にヒトを大切にするということです。
今年は色々大きな勝負があると思います。ヒトを頼ってヒトに頼ってもらう一年にします。
サブテーマは「自信」
自信がある分野に関わってると楽しいですね。
自信がある時は、余裕があるので楽しいです。
逆に自分が生きづらいと感じる時は大体自信がない時です。
この生きづらさを潰すために、「自信」にフォーカスしていきたいと思います。
結果を出し、評価されないと自信を持てないタイプなので、勝負の結果にこだわっていきます。
勝負が苦手だった自分が去年一年で相当勝負強くなったので、引き続き1位にこだわっていきたいと思います。
持ちつ持たれつ壁を超えていって、ヒトとして自信をつける一年にします。
GAE/Goで位置情報マッチングAPIの設計を考える
先日、サイバーエージェントさんの学生版ガレスタというインターンに参加しました。
6週間で企画からサービス完成までを4人(ビジネス1人, デザイナー1人, エンジニア2人)で行うというものでした。
今記事ではこのインターンで得た技術的なことを、つらつら書いていきたいと思います(´・ω・`)
要件
位置情報を用いたマッチングモバイルアプリのAPI
技術選定
言語
- チャット実装時のマルチスレッド処理が簡潔
- 薄くAPI作れるから短期間開発○
- 丁度いい型安全
- 開発効率を上げるエコシステムが整ってる
- モバイルAPIの実例多い
- インターン後仲間集める時に、若く優秀なエンジニアを集めやすい
http通信
- 薄い
- 慣れてる
- 標準なのでバージョン管理安心
ORマッパー
RDB
- 慣れてる
KVS
- 3.2から追加された"Geo"系コマンドを使って位置情報管理したい
インフラ
- サーバーサイドは一人なのでとにかく開発に集中したい
- PaaSの中で最も妥当な柔軟性、料金と感じた
アプリケーション設計
$GOPATH ├── app │ ├── app.yaml │ ├── config.go │ ├── handler.go # Application層 │ └── main.go # UI層 └── src ├── domain # Domain層 │ ├── config.go │ ├── detail │ ├── feed │ ├── location │ └── registration ├── infra # Infrastructure層 │ ├── cache │ ├── config │ ├── objstorage │ └── orm └── middleware
レイヤードアーキテクチャを用いた軽量DDDを採用しました。
DDDを完璧に実現しようとすると、分析、設計、実装というフローを踏まないといけないのですが、今回は短時間且つ仕様も変わっていくため、分析はせず軽量なDDDを実現しました。
DDDについてはまとまった知識がなかったので、今回エリック・エヴァンスのDDD本を初めて読みました。
GAEは、app.yamlが置いてあるディレクトリ以下を全てビルドしてしまうので、Domain層以下は別packageとして管理しています。
集約毎にディレクトリを分け、その中でentity、value object、repositoryを定義しています。
infra配下ではDBに依存するコードをまとめ、出入りするデータをrepositoryにinterfaceとして定義しておきます。
このようにすることで、ミドルウェアの差し替えを容易にしています。
GAE/GoのDDD設計はまだまだデファクトスタンダードが定まっていないようなので、これからも熟考していこうと思います。
インフラ設計
全体アーキテクチャ
ほとんどのデータはCloudSQLにMySQLを乗せて管理しました。
CloudSQLはフェイルオーバー時に数分のダウンタイムが発生してしまうというデメリットがありますが、短期開発ということで立ち上げ時の設定が楽であるという最大のメリットを汲み採用しました。
CloudStorageでは画像中心に、サイズの大きいオブジェクトを管理しています。
特筆すべきはredisです。
redisによる位置情報管理
位置情報機能の要件は"近い順にアクティブユーザーを返す"でした。
そこで最初に考えたアーキテクチャはこんなものでした
列志向のBigtableに位置情報を溜めていき、アクティブユーザーを返す度にBigQueryを回すというものです。
ありえませんね。
ユーザー毎にクエリを回すというこのモデルではパフォーマンスにもお財布にも優しくないですね。
サイバーエージェントの社員さんに聞いたところ、redis3.2から位置情報を扱うコマンドが追加されたらしく、使ってみることにしました。
>>Aさんから半径100km以内のユーザーを近い順に頂戴!<<
わがままですね。
でもこんなわがままを、ジェントルマンなredisは1コマンドで叶えてくれます。
GEORADIUSBYMEMBERという夢のコマンドをredisにささやきます。
// GEOADDで追加 > GEOADD location 13.583333 37.316667 "Aさん" 13.361389 38.115556 "Bさん" (integer) 2 // GEORADIUSBYMEMBERで取得 > GEORADIUSBYMEMBER location "Aさん" 100 km 1) "Aさん" 2) "Bさん"
GEOADDでsorted set型の集合にぶっこみ、ソートします。
GEORADIUSBYMEMBERを使用すればO(N+log(M))で検索することが可能です。
*N=指定範囲内の要素数, M=インデックス内の項目数
これで当初よりシンプル&ハイパフォーマンス&低料金で要件を実現することが出来ました。
社員さんありがとうございます〜〜
テーブル設計
ゼロイチでDB設計したことなかったので、しっかり以下3つ読みました。
プライマリーキーをとりあえず"id"というサロゲートキーにしてしまうアンチパターン「idリクアイアド」を避け、
なるべく自然キーを用いた設計を心がけました。
API設計
ゼロイチでAPI設計したことなかったので、しっかりこれを読みました。
リソースのページネーションを実現する際、最初は相対位置を利用していましたが、絶対位置を利用することでパフォーマンスと整合性の改善をしました。
まとめ
何が言いたいのかまとまっていませんが、とりあえず多くの技術的学びがあったということです。
痛感したのは、どんなに短期開発でも時間かけるべき部分には大いに時間をかけるべきだということです。
どこを丁寧に行うか適切に判断することが出来れば、スピードと品質は必ずしもトレードオフにならないと感じました。
このアプリは近いうちにリリースするので、その時にまた報告致します。
Goで軽量なスクレイピングライブラリを作ってみた
リポジトリ
はじめに
GoでWebクローラーを開発する際、皆さんどうされてますか??
フルスクラッチで作るのは少し面倒だし、asciimoo/collyのようなフルスタックなのはいらない、、という時に丁度いいライブラリがなかったので作りました!
本当に最低限の機能のみ搭載しました。
機能
主な3つの機能を紹介します。
オーガニック検索
キーワードを入れて自然検索結果画面に表示されるページURLを返します。
import "github.com/ryonakao/netsurfer" urls, err := netsurfer.OrganicSearch("キーワード", 1)
順位調査
指定キーワードで検索した時、指定ページがオーガニックで何位に掲載されるかを返します。
import ( "net/url" "github.com/ryonakao/netsurfer" ) u, _ := url.Parse("https://qiita.com/ryonakao") rank, _ := netsurfer.GetRank(u, "ryonakao", 2)
HTML取得
指定ページの静的ページを返します。
import "github.com/ryonakao/netsurfer" html, err := netsurfer.GetHTML("https://qiita.com/ryonakao")
実装一部紹介
SERPsのurlスライスを返す以下の関数を御覧ください。
ユーザーにdepthを指定してもらうことで、一回のスクレイピングで全SERPsを取得しています。
*ryonakao/netsurderはPuerkitoBio/goqueryに依存しています。
まとめ
ライブラリ開発めちゃんこ楽しい♪
どんどん作っていこうと思います!!
GoのcliツールUIライブラリを試す ~ 今日のGithub Trending ~
今日のGithub Trending
カリスマエンジニアになりたい!!
そう願う大学二年生の私は新たな企画を始めます\(^o^)/
題して、 >>今日のGithub Trending<<
Github Trendingを眺めて、面白そうなリポジトリがあったらガンガン試していきます。
Github Trendingとは、盛り上がってるリポジトリを探すことが出来るというGithubが提供するサービスです。
第一回のリポジトリは、今日だけで200スター以上ついているmarcusolsson/tui-goを試していこうと思います!!
チャットクライアント風なcliツールを作ってみる
完成図
実装
基本的に以下の2つの構造体を使って作っていきます。
box.go
ボックスはウィジェットを水平または垂直に配置するためのレイアウトです。 もし 水平にすると、すべてのウィジェットは同じ高さになります。 縦にすると、すべて同じ幅になります。
// Box is a layout for placing widgets either horizontally or vertically. If // horizontally, all widgets will have the same height. If vertically, they // will all have the same width. type Box struct { WidgetBase children []Widget border bool title string alignment Alignment }
entry.go
エントリは、1行のテキストエディタです。 これによってユーザはアプリケーションにテキストを供給することができます。例えば、ユーザおよびパスワード情報を入力することができます。
// Entry is a one-line text editor. It lets the user supply the application // with text, e.g., to input user and password information. type Entry struct { WidgetBase text string onTextChange func(*Entry) onSubmit func(*Entry) }
サイドバー作成
sidebar := tui.NewVBox( tui.NewLabel(" <CHANNELS>"), tui.NewLabel(" general"), tui.NewLabel(" random"), tui.NewLabel(""), tui.NewLabel(" <DIRECT MESSAGES>"), tui.NewLabel(" maria"), tui.NewLabel(" john"), tui.NewSpacer(), ) sidebar.SetBorder(true)
- NewVBox関数で、縦方向のBoxを作成できます。
- 任意数のWidgetを与えることが出来ます。
- SetBorderメソッドで枠を表示するかを決めます。
送信済みメッセージを表示
type post struct { username string message string time string } // 送信済みPostのスライス var posts = []post{ {username: "ryo", message: "Hello!", time: "14:41"}, } history := tui.NewVBox() history.SetBorder(true) history.Append(tui.NewSpacer()) for _, m := range posts { history.Append(tui.NewHBox( tui.NewLabel(m.time), tui.NewPadder(1, 0, tui.NewLabel(fmt.Sprintf("<%s>", m.username))), tui.NewLabel(m.message), tui.NewSpacer(), )) }
NewVBox関数で縦方向のBoxを作成し、Appendメソッドでメッセージを追加していきます。
Appendメソッドの実装を覗いてみましょう。
box.go
// Append adds the given widget at the end of the Box. func (b *Box) Append(w Widget) { b.children = append(b.children, w) }
Appendメソッドは、Widgetインターフェイスの値をBoxのchildrenというスライスに追加していきます。
入力ボックス作成
input := tui.NewEntry() input.SetFocused(true) input.SetSizePolicy(tui.Expanding, tui.Maximum) inputBox := tui.NewHBox(input) inputBox.SetBorder(true) inputBox.SetSizePolicy(tui.Expanding, tui.Maximum)
Entry構造体を作ってNewHBoxの引数に渡すことで、Entry構造体を子要素として持ったBoxを作ることが出来ます。
NewHBoxメソッドの実装を覗いてみましょう。
box.go
// NewHBox returns a new horizontally aligned Box. func NewHBox(c ...Widget) *Box { return &Box{ children: c, alignment: Horizontal, } }
NewHBox関数はWidgetインターフェイスを子要素に含めることが出来ます。
そのためBox構造体だけではなく、Widgetインターフェイスが実装されているEntry構造体も渡すことができます。
投稿する
import "flag" var name = flag.String("u", "kazuki", "Set your name") flag.Parse() input.OnSubmit(func(e *tui.Entry) { history.Append(tui.NewHBox( tui.NewLabel(time.Now().Format("15:04")), tui.NewPadder(1, 0, tui.NewLabel(fmt.Sprintf("<%s>", *name))), tui.NewLabel(e.Text()), tui.NewSpacer(), )) input.SetText("") })
Entry構造体のOnSubmitメソッドで、Enterを押した時の挙動を設定出来ます。
historyにメッセージを追加します。
flagパッケージを使用して、実行時にユーザー名を指定出来るようにします。
UI作成
chat := tui.NewVBox(history, inputBox) chat.SetSizePolicy(tui.Expanding, tui.Expanding) root := tui.NewHBox(sidebar, chat) ui := tui.New(root) ui.SetKeybinding("Esc", func() { ui.Quit() })
- Boxの、Widgetインターフェイスを子要素に出来る機能を利用して、今まで作成したBoxをrootにまとめていきます。
- tcellUI構造体を作成し、Esc押下時にuiを終了するよう、キーバインディングを設定します。
実行
if err := ui.Run(); err != nil { panic(err) }
Runメソッドを覗いてみましょう。
ui_tcell.go
func (ui *tcellUI) Run() error { if err := ui.screen.Init(); err != nil { return err } if w := ui.kbFocus.chain.FocusDefault(); w != nil { w.SetFocused(true) ui.kbFocus.focusedWidget = w } ui.screen.SetStyle(tcell.StyleDefault) ui.screen.EnableMouse() ui.screen.Clear() go func() { for { switch ev := ui.screen.PollEvent().(type) { case *tcell.EventKey: ui.handleKeyEvent(ev) case *tcell.EventMouse: ui.handleMouseEvent(ev) case *tcell.EventResize: ui.handleResizeEvent(ev) } } }() for { select { case <-ui.quit: return nil case ev := <-ui.eventQueue: ui.handleEvent(ev) } } }
- 別ゴルーチンを立ててPollEventメソッドを呼ぶことで、イベント発生を監視します。
- イベントを検知したら、eventQueueというチャネルにenqueueしていきます。
- eventQueueの状態をメインゴルーチンで監視し、受信を確認したら実行します。
*PollEventはgdamore/tcellという別パッケージのメソッドです。
コード全貌
長くなるのでGIstに記載しました。
https://gist.github.com/ryonakao/6247637a39f32ba0543f5b0421297357
まとめ
評価されてるライブラリを試すことで以下のメリットがあることに気づきました。
- コードを多読することになるため、コーディングスキルがUP(今回は、marcusolssonさんのinterface設計がとても勉強になりました。)
- OSS界隈での需要が分かる
- ワクワクする
また時間があったらやってみたいと思います!!
オレ流学習フレームワーク
はじめに
今記事では、大学生の私が独学でコンピュータサイエンスを学習する際に使用している独自の学習フレームワークを紹介していきます。
あくまで持論なので、すべての人に最適であるとは限りません。
これから紹介するのは、いかに楽して知識を得られるかを念頭に置いたフレームワークです。
ここで言う"楽"というのは、無駄な体力を使わないという意味です。
他にこんな学習の仕方してるよ〜というのがあれば、コメントして頂けると幸いです!
主な学習フレームワーク
発信ドリブン型学習
ブログ等で発信したり、人に教えることを前提としたインプットを行います。
インプットしながらどうアウトプットするかを同時に考えることで、高速な理解が可能になります。
なぜ高速な理解が可能になるのでしょうか?
理解とは、脳の棚に整理された形で分けられている状態だと私は考えています。
人に伝えるためには、内容を整理しないといけません。
インプット中に棚に振り分けていくことで、インプット終了後には既に"理解した"という状態になっているという考え方です。
これを習得すると、書籍を読んでいる時に文章が構造的に見えてくるようになります。
遅延評価型学習
これは有名な遅延評価勉強法のことで、その知識が必要になった時に初めて勉強する方法です。
遅延評価とは、元々はプログラミング手法の一つで、必要になるまで値の評価をしないという意味の言葉です。
"必要は発明の母"という言葉がある通り必要性を感じた時の吸収力はかなり高いため、必要度に比例して吸収力も上昇します。
「直近で必要ではないけど、今学習しておきたいことだってあるぞ!」
そう思う方も少なくないと思います。
実際私も、ネットワークの仕組みやミドルウェアの特性等、学生生活で必要になることがほとんどない学習をしています。
別に必要になるのを待つ必要は無いのです。
必要を作り出せばいいのです。
先日私は、ネットワークの仕組みやミドルウェアの特性を理解しないと勝てない大会に出場することを決め、必要を作り出しました。
その時のレポート↓
CyberAgentのアドテクチャレンジで正確なDMPを作る - 爆速でGo!
このように、遅延評価型学習では必要をコントロールしていくことがキーとなってくると考えています。
フレームワークをどう組み合わせるか
爆発的な吸収力を生む遅延評価型でインプットするところから始めます。
しかし遅延評価型でバラバラに集めた情報はTipsにはなるのですが、知識にはならないと思います。
知識にするためには、ある程度網羅的なインプットが必要です。
そこで発信ドリブン型を使います。
網羅的なインプットというと、分厚い書籍を1ページ目から最後まで順に読むという印象がありますが、これは中々体力を使いますね💦
これでは冒頭で述べた"楽して"という言葉に反します。
ここで私は、独自の読書フレームワークであるgoto文型リーディングを使います。
goto文型リーディング
goto文とはプログラミングの制御構造の一つで、指定された場所に処理をジャンプさせるための文です。
このフレームワークは、各章が独立しておらず順に読まないと意味が分からないタイプの書籍に有効です。
手順は以下の通りです。
目次を見て、この書籍ではどんな内容に触れているかにしっかり目を通す。これは毎回読む前に行う
一番興味ある部分から読む
興味が無くなるまで以下を繰り返す
意味が分からないワードや表現が出てくる
目次が頭に入っていれば、その書籍のどこかで説明されていることが分かる
該当箇所にジャンプする
書籍によりますが、これで大半は読むことが出来ます。
大半を読めばその書籍の内容に関してはかなり理解度があるため、残りの部分は小見出しに魅力がなくても興味が湧くことが多いです。
それでも興味がない部分は無理に読む必要ないと思います。
そっと本棚にしまいましょう。
モチベーション・マネジメント
上記を読んで正直面倒くさいと思った方もいると思います。
私も同感で、上記を完遂するためには一定のモチベーションが必要です。
上記を行うためにはモチベーション・マネジメントを常に行う必要があります。
私は、学習をしていく上でこれが一番重要だと思います。
夢や目標が遠すぎると挫折しがちですよね、、
私は、ちょっと頑張れば届く中期目標向かって進んでいる時が一番モチベーションを高く保つことが出来ます。
この中期目標の距離がどのくらい近いと良いのかは人によって違うと思います。
中期目標を最適な場所に置き続けることが大事だと思います。
これはスキルだと思っています。
環境が変化し続ける中で、常に適切な場所に中期目標を置けるスキルがある人の成長は止まらないと思います。
まとめ
最後まで読んで頂きありがとうございます。
冒頭でも申し上げましたが、あくまで自分の中でしっくりきているフレームワークなので、理解し難い部分があると思います。
学習の質をもっとあげていきたいと思っているので、他に実践されているものがあれば、教えて頂けますと幸いです!