eyecatch
Thu, Apr 11, 2019

アジャイル開発の真髄 ― DRY

今携わっているプロジェクトではScrumで開発していて、私自身2年ほどスクラムマスタを経験した。 うちの会社はかなり保守的で、ごく最近までウォータフォールで開発するのがあたりまえだったので、そこから文化を変え、マインドシフトし、アジャイルなプロセスに順応していくにはそれなりに苦労があった。 今でも、アジャイルに慣れていないエンジニアがアジャイルなチームに入ってくると、やはりいろいろな違いに戸惑っているように見えるし、こちらとしても期待するアウトプットがなかなか出てこなくて困ることが多い。 私はスクラムマスタというロールを任されてはいるが、どちらかと言えばテックリードやアーキテクト的な役割に期待されている気がしていて、そっちに力が入ってしまうのが実情。 そんな状況なので、コードレビューには結構時間を割いているんだけど、アジャイルなエンジニアとそうでないエンジニアが書くコードにはなんだかとても重大な差異があるような気がずっとしていた。 で、最近それを説明できるまでに考えがまとまってきたので、ここに書き残しておく。 (adsbygoogle = window.adsbygoogle || []).push({}); ウォータフォールの問題 ウォータフォール開発は以下のような特徴がある。 綿密な計画を事前に立てて、計画通りに開発を進めることを重視する。 機能設計 ⇒ 詳細設計 ⇒ コーディング ⇒ テストといった感じに、全体の計画を工程でフェーズ分けして、手戻りなく、一方向に進むことを重視する。 各フェーズでは全開発項目を並行して進め、ソフトウェア全体の整合性を取ることを重視する。 各フェーズで、包括的で完成されたアウトプットを作ることを重視する。アウトプットは各フェーズで詳細にレビューする。 綿密な設計ドキュメントを整備し、適時アップデートしていくことを重視する。理想的には、コーディングは設計ドキュメントをプログラミング言語に射影するだけの単純作業になる。 これはこれで、バグの少ないソフトウェアを、大人数で、非属人的に、低リスクで開発するのに向いた開発手法ではある。 しかし、設計ドキュメントの整備にかなりの時間がかかり、開発工程全体が長くなりがちなのと、動くソフトウェアが出来てくるのが開発終盤になってしまうのが欠点。 ソフトウェアがビジネスにおいて果たす役割がかなり大きくなった昨今では、ウォータフォール開発はビジネスのスピードや変化についていけない時代遅れの手法という評価になった。 代わりに台頭したのがアジャイル開発だ。 現代では欧米のソフトウェアプロジェクトの9割以上がアジャイルなプロセスを採用しているというのをHPEのカンファレンスで聞いた覚えがある。 いまだにウォータフォールを採用するのは、例えばNASAのロケット制御ソフトウェアみたいな、バグがあると数十億ドルと世界トップレベルの人材が吹っ飛ぶようなソフトウェアの開発プロジェクトだ。 (因みにNASAのコーディング規約は、再起関数禁止、静的に回数が決まらないループ禁止など、常軌を逸した厳しさ。) アジャイル開発のDRY ウォータフォールとアジャイルの違いは大量にあるが、パッと見て分かりやすい違いは設計ドキュメントの有無であろう。 アジャイルな開発では設計ドキュメントを排除するのが基本。 ウォータフォールなプロセス: ウォータフォール開発では、こんな感じにいろんなドキュメントを作ることになる。 加えて、ソースコードには大量のコメントを書くのが良しとされていた。(うちの会社だけ?) また、テストフェーズでは、何をどう操作して何をどう確認するという詳細なチェックリストを作り、経験の有無にかかわらずだれでも機械的に同じテストができるようになっているのが理想とされていた。(うちの会社だけ?) 沢山設計ドキュメントを書いても、結局顧客が手にするのはソフトウェアだけ。(ユーザマニュアルは別として。) 顧客が目にする唯一の「真実 (Truth)」は、インストールされて手元で動いているソフトウェアだ。 この「真実」の挙動や構造を説明するもの、つまり「真実の情報源 (Source of Truth)」はどこにあるか。 機能設計書や詳細設計書は言うまでもなく「真実の情報源」。 ソースコードのコメントも、どのような処理をしているのかを説明するものが多いだろうから、「真実の情報源」と言える。 ソースコード自体も、「真実」の挙動を決定づけるものなので「真実の情報源」だ。 テスト設計書やチェックリストも、「何をしたとき、どうなる」ということを記述しているので、「真実の情報源」になる。 このようにウォータフォール開発では、唯一の「真実」を様々な形で何度も表現することになるので、開発工数が膨らむことになる。 アジャイル開発では、これをプロセスにおけるDRY (Don’t Repeat Yourself)と断罪する。 つまり、「真実の情報源」が複数になることを悪として、「真実の単一情報源 (Single Source of Truth)」にしなさいと言う。 これには、ソフトウェアの挙動を変更したいときに修正すべき「情報源」を少なくして、修正工数を下げつつ、「情報源」間のずれによるバグを防ぐ狙いがある。 アジャイル開発プロセスにおける真実の単一情報源 アジャイル開発プロセスにおける「真実の単一情報源」とは何か。 それは、ウォータフォール開発における「真実の情報源」から何を削減するかという話になるわけだけど、結論は明らかだ。 絶対に捨てられないものが一つだけあるので、それ以外を排除することになる。 残すのは当然ソースコード(自動テストのコードも含む)。 ソースコードが無ければソフトウェアは動き得ないので。 アジャイルなプロセス: ソースコードが「真実の単一情報源」なので、それ以外の「真実の情報源」はDRY違反。 これがアジャイル開発で設計ドキュメントやテストドキュメントの作成を避ける理由だ。 ただし、注意すべきなのは、設計そのものを否定しているわけでも、設計の情報をチームで共有をすることを不要としているわけではないこと。 アジャイル開発は、それらを重要視しつつも、それらのためにドキュメントを作るという作業を排除した。 ドキュメントが無ければ、設計しても人に伝えられず、すぐに立ち消えてしまうように思われるが、アジャイル開発ではそれを別の媒体に記録する。 この媒体が何かというのも明らかで、ソースコードしか作らないんだからソースコードそのものということになる。 要するに、ソースコードを動く仕様書兼設計書にするということ。 日本人にとっては英語が母国語ではないので実感がわき辛い気がするが、ソースコードは割と自然言語を駆使して書いている。 プログラミング言語の機能やライブラリやフレームワークが強力になって、プログラミングパラダイムやコーディング手法の進化もあって、ソースコードは設計を伝達する媒体として十分な表現力を持つようになった。 アジャイル開発の現場でよく、ソースが仕様書みたいなことを言うのを聞くが、それは伊達ではなく、言葉通りの意味ということだ。 「アジャイル開発という名目を掲げて仕様書作成をさぼっているから、ソースコードを見て仕様を読み解け」などという開発者の怠慢・傲慢ではなく、「仕様書のように美しく読みやすく体系立ったソースコードを書きます」という決意表明なのである。 (因みに現実的には、ソースコード以外の「真実の情報源」を完全に排除できるわけではない。ユーザマニュアルが最たるものだし、設計ドキュメントに関しても、大域的な設計は別途簡単にでもまとめておいたほうがいい場合が多い。結局は理想のDRYと現実との間でバランスをとることが大事。) ソースコードを動く仕様書兼設計書にするには ソースコードを動く仕様書兼設計書にするために実践すべきことはたくさんある。 フォーマッタをかける 体裁が整っていないコードは読み辛い。 フォーマッタは、アジャイル開発の普及につれて、IDEが提供する補助的な機能の一つから独立したツールへ、さらにプログラミング言語の一部へと昇格を遂げた。 変数名、関数名を説明的にする 変数名や関数名などの識別子は、仕様書における用語であったり、章節のタイトルであったり、文の主語・目的語にあたるものなので、明確で、正確で、曖昧さが無く、一貫性があるべき。 例えば、tmpとかlistとかの何が入るかわからない変数名はよくないし、HTTPでデータを取得する関数にはgetXxxよりもfetchXxxの方がいい。 また、なでしこで書かない限りは英語で名付けることになるので、英語をしっかりリスペクトした名前にすること。 よく、複数の要素を保持する変数(i.e.
eyecatch
Fri, Mar 8, 2019

ズンドコキヨシ with Kubernetes Operator - KubebuilderでKubernetes Operatorを作ってみた

Javaの講義、試験が「自作関数を作り記述しなさい」って問題だったから 「ズン」「ドコ」のいずれかをランダムで出力し続けて「ズン」「ズン」「ズン」「ズン」「ドコ」の配列が出たら「キ・ヨ・シ!」って出力した後終了って関数作ったら満点で単位貰ってた — てくも (@kumiromilk) 2016年3月9日 久しぶりにズンドコしたくなったので、Kubebuilderを使って、KubernetesのOperatorとして動くZundoko Operatorを作ってみた。 (adsbygoogle = window.adsbygoogle || []).push({}); Kubernetes Operatorとは KubernetesのOperatorというのはCoreOS社(現Red Hat)によって提唱された概念(実装パターン)で、KubernetesのAPIで登録されるKubernetesオブジェクトの内容に従って何らかの処理をするController (e.g. Deployment Controller)の一種。 Controllerが汎用的なのに対して、特定のアプリケーションに特化しているのが特徴。 アプリケーションごとの細かな設定をKubernetesオブジェクトで表現するために、KubernetesのAPIを拡張する。 APIを拡張するにはAPI Aggregationを使う方法とCustom Resource Definition (CRD)を使う方法がある。 API Aggregationは、Kubernetesオブジェクトをetcd以外で管理したり、WebSocketを使ったり、Kubernetesクラスタ外のAPIサーバを使う場合など、特殊な場合にも対応できる高度なやりかたで、大抵のユースケースではCRDで事足りる。 Operatorも普通はCRDを使う。(というかCRDを使うのがOperatorという人もいる。) CRDとは KubernetesのAPIを簡単に拡張できる仕組みで、Kubernetesオブジェクト(リソース)を定義するKubernetesオブジェクト。 YAMLで、定義したいリソースの名前や型やバリデーションなんかを書いてkubectl applyすれば、そのリソースをKubernetesのREST APIとかkubectlで作成したり取得したりできるようになる。 Operatorの仕組み Operatorは、CRDで定義されたリソース(など)の作成、更新、削除を監視(watch)して、リソースの内容に応じた何らかの処理をするReconciliationループを回すPod。 普通、リソースはOperatorの管理対象のアプリケーションの状態を表す。 で、Operatorはリソースの内容とアプリケーションの状態が同じになるように、Reconciliationループ内でDeploymentを作ったりアプリケーションのAPIを叩いたりする。 ユーザとしては、アプリケーションの構成や設定をKubernetesのAPIで宣言的に統一的に管理できるようになって幸せになれる。 Operator作成ツール Operatorを作るツールとして以下がある。 ツール Operator SDK Kubebuilder Metacontroller 開発元 Kubernetesコミュニティ製 CoreOS社製 GKEチーム製 GitHubスター数 1459 1009 506 開発言語 Go、Ansible、Helm Go 任意 特徴 プロジェクトテンプレート生成、ビルド、デプロイをするCLIツール。AnsibleでもOperatorを書けるのが面白い。Operator FrameworkとしてLifecycle Managerなどが提供されていたり、OperatorHub.ioというコミュニティサイトがあったり、エコシステムが充実している。 プロジェクトテンプレート生成、ビルド、デプロイをするCLIツール。3つの中で一番シンプル。Goでしか開発できない。 他の2つと毛色が違って、Metacontroller自体が汎用的にOperatorを管理するKubernetesアプリ。Operatorの定義をJSONを投げて登録すると、Reconciliationループを回してその中でWebフックを実行してくるので、それを受けて任意の処理をするサーバを任意の言語で書ける。 この中では、Operator SDKが数歩リードしている感じ。 (CoreOS社を買収した)Red Hatが後ろ盾ているし、OperatorHub.ioはGCPとAWSとAzureが協力している。 けど、この記事のネタを書き始めたときにはまだOperatorHub.ioが発表されていなくて、単純にKubebuilderがシンプルでいいと思って採用してしまった。 まあOperator SDKもKubebuilderも下回りのライブラリは同じものを使っているので、だいたい同じだろうし、Operator Frameworkへの移行も難しくなかろう。 Zundoko Operator Kubebuilderで今回作ったのはZundoko Operator。 CRDで定義したリソースは以下。 Hikawa: 作るとズンドコきよしを開始する。 Zundoko: 「ズン」と「ドコ」を表す。Hikawaに管理される。 Kiyoshi: 「キ・ヨ・シ!」を表す。Hikawaに管理される。 Zundoko Operatorは、HikawaとZundokoをwatchする。 Hikawaが作成されると、一定間隔で、ランダムに「ズン」か「ドコ」をセットしたZundokoを作成する。 「ズン」を4つ作ったあとに「ドコ」を作ったら、Kiyoshiを作成して、Zundokoの作成を止める。 Kubebuilderの使い方 Quick Startを参考に。 Kuberbuilderを使うにはGo、depとkustomizeとDockerが必要で、Linuxしかサポートしていない。 自分のPCがWindows 10なので、WSL (Ubuntu 18.04)で環境を作ったんだけど、結局Dockerビルドとかテストとかが上手く動かなかったので、VMとかのLinuxで動かしたほうがよさそう。 Kubebuilderセットアップ Goインストール Goは公式サイトからLinux用アーカイブをダウンロードして展開して、そのbinディレクトリにPATH通すだけでインストールできる。 $ go version go version go1.11.4 linux/amd64 あと、作業ディレクトリを作ってGOPATHを設定しておく。 ~/go/を作業ディレクトリとする。 $ export GOPATH=$HOME/go $ echo 'export GOPATH=$HOME/go' >> ~/.profile $ mkdir $GOPATH/bin $ mkdir $GOPATH/src で、$GOPATH/binにもPATH通しておく。 depインストール Go公式の依存ライブラリ管理ツール。 コマンド一発でインストールできる。 $ curl https://raw.githubusercontent.com/golang/dep/master/install.sh | sh kustomizeインストール バイナリをPATHの通ったところにダウンロードするだけ。 $ curl -L https://github.com/kubernetes-sigs/kustomize/releases/download/v2.0.3/kustomize_2.0.3_linux_amd64 -o /usr/local/bin/kustomize $ chmod +x /usr/local/bin/kustomize kubebuilderインストール GitHubのReleasesからアーカイブをダウンロードして展開してPATH通すだけ。 $ version=1.0.6 $ arch=amd64 $ curl -LO https://github.com/kubernetes-sigs/kubebuilder/releases/download/v${version}/kubebuilder_${version}_linux_${arch}.tar.gz $ tar -zxvf kubebuilder_${version}_linux_${arch}.tar.gz $ sudo mv kubebuilder_${version}_linux_${arch} /usr/local/kubebuilder $ export PATH=$PATH:/usr/local/kubebuilder/bin $ echo 'export PATH=$PATH:/usr/local/kubebuilder/bin' >> ~/.profile Dockerインストール は適当に… Kubebuilderプロジェクト生成 Zundoko Operatorのプロジェクトを生成する。 $ mkdir -p $GOPATH/src/github.com/kaitoy/zundoko-operator $ cd $GOPATH/src/github.com/kaitoy/zundoko-operator $ kubebuilder init --owner kaitoy dep ensureを実行するかを聞かれるのでyesで回答すると、依存ライブラリがダウンロードされ、プロジェクトのビルドが走る。 デフォルトではCRDなどの名前空間がk8s.ioになっているので、kaitoy.github.comに変えるべく、zundoko-operator/PROJECTを編集する。 zundoko-operator/PROJECT: version: "1" -domain: k8s.io +domain: kaitoy.github.com repo: github.com/kaitoy/zundoko-operator CRDとController生成 HikawaとZundokoとKiyoshiのCRDを生成する。 $ kubebuilder create api --group zundokokiyoshi --version v1beta1 --kind Hikawa $ kubebuilder create api --group zundokokiyoshi --version v1beta1 --kind Zundoko $ kubebuilder create api --group zundokokiyoshi --version v1beta1 --kind Kiyoshi それぞれ、リソースを作成するか (Create Resource under pkg/apis [y/n]?) と、Controllerを作成するか (Create Controller under pkg/controller [y/n]?) を聞かれる。 リソースはそれぞれ作成して、ControllerはHikawaにだけ作成した。 生成されたのは以下のファイル。 API定義とそのテスト: zundoko-operator/pkg/apis/zundokokiyoshi/v1beta1/*.go CRD: zundoko-operator/config/crds/config/crds/*.yaml Hikawa Controllerとそのテスト: zundoko-operator/pkg/controller/hikawa/*.go リソースのマニフェストのサンプル: zundoko-operator/config/crds/config/samples/*.yaml これらの内、CRDと zundoko-operator/pkg/apis/zundokokiyoshi/v1beta1/zz_generated.deepcopy.go はAPI定義をもとに生成されるので、API定義を書いた後生成しなおすことになる。 API定義記述 リソースがどのような属性をもつかをGoで定義する。 テンプレートは生成されているので、ちょっと書き足すだけでできる。 以下はHikawaのAPI定義。 zundoko-operator/pkg/apis/zundokokiyoshi/v1beta1/hikawa_types.go: package v1beta1 import ( "time" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) // HikawaSpec defines the desired state of Hikawa type HikawaSpec struct { IntervalMillis time.Duration `json:"intervalMillis"` NumZundokos int `json:"numZundokos,omitempty"` SayKiyoshi bool `json:"sayKiyoshi,omitempty"` } // HikawaStatus defines the observed state of Hikawa type HikawaStatus struct { NumZundokosSaid int `json:"numZundokosSaid"` Kiyoshied bool `json:"kiyoshied"` } // +genclient // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object // Hikawa is the Schema for the hikawas API // +k8s:openapi-gen=true type Hikawa struct { metav1.TypeMeta `json:",inline"` metav1.ObjectMeta `json:"metadata,omitempty"` Spec HikawaSpec `json:"spec,omitempty"` Status HikawaStatus `json:"status,omitempty"` } // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object // HikawaList contains a list of Hikawa type HikawaList struct { metav1.TypeMeta `json:",inline"` metav1.ListMeta `json:"metadata,omitempty"` Items []Hikawa `json:"items"` } func init() { SchemeBuilder.Register(&Hikawa{}, &HikawaList{}) } 自分で書いたのはHikawaSpecとHikawaStatusの中だけ。 Specの方には期待する状態、Statusの方には現在の実際の状態を表すフィールドを定義するのがパターン。 例えば、HikawaSpec.NumZundokosが期待するZundokoの数で、HikawaStatus.NumZundokosSaidが実際に作成されたZundokono数。 Hikawa ControllerはReconciliationループの中で、SpecとStateが同じになるように処理をすることになる。 ZundokoとKiyoshiのAPI定義は、Specに「Zun」、「Doko」、または「Kiyoshi!」を入れるためのSayフィールドだけを書いた。 Hikawa Controller記述 Hikawa Controllerもテンプレートが生成されているので、それを参考に書ける。 まずはどのリソースをwatchするかを書く。 zundoko-operator/pkg/controller/hikawa/hikawa_controller.go前半抜粋: func add(mgr manager.Manager, r reconcile.Reconciler) error { // Create a new controller c, err := controller.New("hikawa-controller", mgr, controller.Options{Reconciler: r}) if err != nil { return err } // Watch for changes to Hikawa err = c.Watch(&source.Kind{Type: &zundokokiyoshiv1beta1.Hikawa{}}, &handler.EnqueueRequestForObject{}) if err != nil { return err } err = c.Watch(&source.Kind{Type: &zundokokiyoshiv1beta1.Zundoko{}}, &handler.EnqueueRequestForOwner{ IsController: true, OwnerType: &zundokokiyoshiv1beta1.Hikawa{}, }) if err != nil { return err } return nil } Hikawaは普通にwatchして、Zundokoはownしているリソースとしてwatchしている。 Reconciliationループは以下のように書いた。 zundoko-operator/pkg/controller/hikawa/hikawa_controller.go後半抜粋: // +kubebuilder:rbac:groups=zundokokiyoshi.kaitoy.github.com,resources=hikawas,verbs=get;list;watch;create;update;patch;delete // +kubebuilder:rbac:groups=zundokokiyoshi.kaitoy.github.com,resources=hikawas/status,verbs=get;update;patch // +kubebuilder:rbac:groups=zundokokiyoshi.kaitoy.github.com,resources=zundokos,verbs=get;list;watch;create;update;patch;delete // +kubebuilder:rbac:groups=zundokokiyoshi.kaitoy.github.com,resources=zundokos/status,verbs=get;update;patch // +kubebuilder:rbac:groups=zundokokiyoshi.kaitoy.github.com,resources=kiyoshis,verbs=get;list;watch;create;update;patch;delete // +kubebuilder:rbac:groups=zundokokiyoshi.kaitoy.github.com,resources=kiyoshis/status,verbs=get;update;patch func (r *ReconcileHikawa) Reconcile(request reconcile.Request) (reconcile.Result, error) { instanceName := request.NamespacedName.String() log.Info("Reconciling a Hikawa: " + instanceName) // Fetch the Hikawa instance instance := &zundokokiyoshiv1beta1.Hikawa{} err := r.Get(context.TODO(), request.NamespacedName, instance) if err != nil { if errors.IsNotFound(err) { // Object not found, return.
eyecatch
Mon, Nov 26, 2018

React + Reduxアプリケーションプロジェクトのテンプレートを作る ― その11: FlowからTypeScriptへ移行

ReactとReduxを学ぶために、開発環境というかプロジェクトテンプレートをスクラッチから作っている。 (最終的な成果はGitHubに置いた。) 前回はCode Splitting、Flow、Jest、Enzymeをセットアップした。 前回でこのシリーズを終わりにするつもりだったけど、型システムをFlowからTypeScriptに移行したのでそれについて書く。 (adsbygoogle = window.adsbygoogle || []).push({}); TypeScript TypeScriptはMicrosoft製のAltJS。 もともとはCoffeeScriptのように言語の機能面(e.g. class構文やアロー関数)を補強しつつ、静的型付けをサポートする言語だったが、最近はECMAScriptが前者をカバーしてるので、後者を主な目的として使う人が多い。 2012年に誕生した言語で、同様に静的型付けをサポートするFlowよりも2歳ほど年上。 TypeScript vs Flow 個人的には、静的型付けだけを目的にするならAltJSである必要はなく、静的型付けだけを補完するFlowのほうが筋がいいような気がする。 TypeScriptはECMAScriptの進化に追従すべく、追加される機能や構文をサポートするためのエンハンスを繰り返しているが、そこはBabelに任せて静的型付けに注力したらいいような。 とはいえ、以下のような点を鑑み、結局TypeScriptを選択した。 TypeScriptの方が人気 GitHubのプロジェクトのスター数はTypeScriptが4万超えでFlowが2万弱。 観測している限り、FlowからTypeScriptへ移行したというのは聞くが、逆は聞かない。 人気があるということはコミュニティやエコシステムが大きいということ。 TypeScriptがノってる BabelやCreate React AppがTypeScriptをサポートして来ていて、なんだか時流にのっている。 Flowは型定義ファイルの管理方法が微妙 Flowはflow-typedという専用のツールを使ってファイルをダウンロードし、ダウンロードしたものをGitとかのVCSでバージョン管理するというやりかた。 TypeScriptはnpmで管理されてるので、Yarnでダウンロードもバージョン管理もできる。VCSのリポジトリに自前のコードしか入れないで済むのもいい。 TypeScriptの方が型定義ファイルが沢山提供されてる Flowの10倍くらいある。 TypeScriptの方がエラーメッセージが分かりやすい というのをどこかで聞いた。 Flowの方が段階的に型を導入できる、というのは昔の話 今はTypeScriptもオプションによって段階的に導入できるというのが定評。 そもそも最初から型付けするならどうでもいい。 Flowの方が厳密な型チェックしてくれる、というのも昔の話 TypeScriptが追い付いてきて、今はほぼ同程度らしい。 TypeScript+VSCodeの開発体験が最高すぎるらしい どっちもMicrosoft製なので。 TypeScriptの方がドキュメントが充実してる TypeScriptの方が、いざというときにソースが読みやすい TypeScriptはTypeScriptで実装されてて、FlowはOCamlで実装されてる。 参考: https://github.com/niieani/typescript-vs-flowtype https://texta.pixta.jp/entry/2018/06/07/120000 https://narinymous.hatenablog.com/entry/2018/03/02/032130 https://base.terrasky.co.jp/articles/zuUtT 前回の記事ではFlowを導入したんだけどTypeScriptに移行する羽目に。 FlowとTypeScriptとで型の表現方式や表現力にあまり差はなかったのでそこはまあ手間ではなかったんだけど、以下のような問題に対応する必要があった。 ビルド時にTypeScriptの方が時間がかかる。 TypeScriptのリンタであるTSLintが、FlowのESLintよりルールが貧弱 TypeScriptのコンパイラがチェックしてくれるからいいのかもしれないけど。 TypeScriptはAltJSなので、何かと連携するときに何かと面倒 Jestでユニットテストするときはどうするんだっけとか プレーンJavaScriptと混在した環境ではTSLintとESLint併用しなければいけないんだっけとか FlowからTypeScriptへの移行 脱Flow とりあえずFlowを取り除く。 $ yarn remove flow-bin flow-typed @babel/preset-flow eslint-plugin-flowtype babel-eslint $ rm -f .flowconfig .babelrc: { "presets": [ [ "@babel/preset-env", { "useBuiltIns": "usage" } ], - "@babel/preset-flow", "@babel/preset-react" ], "plugins": ["styled-components", "@babel/plugin-syntax-dynamic-import"] } .eslintrc.js: module.exports = { env: { browser: true, 'jest/globals': true, }, - parser: 'babel-eslint', - extends: ['airbnb', 'plugin:flowtype/recommended', 'prettier'], - plugins: ['flowtype', 'jest'], + extends: ['airbnb', 'prettier'], + plugins: ['jest'], }; 環境はこれでよくて、あとは各.jsファイルと.jsxファイルから// @flowを消して、型情報も消す。 (型情報はTypeScriptでも同じようなのを書くので残しておいてもいい。) TypeScript導入 パッケージインストール 以下のパッケージを入れる。 typescript: TypeScript本体。コンパイラ(tsc)等を含む。 @types/*: 各3rdパーティライブラリの型定義ファイル(DefinitelyTyped)。(型定義はライブラリ本体のパッケージに含まれている場合もある。) awesome-typescript-loader: TypeScriptを処理するためのwebpackのローダ。他の選択肢としてts-loaderがあるが、公式のチュートリアルがawesome-typescript-loaderをメインで紹介してるのでこっちにする。 $ yarn add -D typescript @types/react @types/react-dom @types/react-redux @types/redux-logger @types/history @types/react-router-dom @types/uuid @types/styled-components awesome-typescript-loader TypeScriptはv3.1.6、awesome-typescript-loaderはv5.2.1が入った。 TypeScriptの設定 TypeScriptの設定ファイルであるtsconfig.jsonはtscコマンドでテンプレートを生成できる。 $ yarn tsc --init 生成されたファイルをプロジェクトルートに置いて、ちょっといじって以下の感じに。 (jsonなのにコメント書ける…) tsconfig.json: { "compilerOptions": { /* Basic Options */ "target": "es5" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017','ES2018' or 'ESNEXT'.
eyecatch
Wed, Nov 7, 2018

React + Reduxアプリケーションプロジェクトのテンプレートを作る ― その10: Code Splitting、Flow、Jest、Enzyme

ReactとReduxを学ぶために、開発環境というかプロジェクトテンプレートをスクラッチから作っている。 (最終的な成果はGitHubに置いた。) 前回はReact Routerをセットアップした。 今回は残りの要素をまとめてかたづける。 (2018/11/21更新) (adsbygoogle = window.adsbygoogle || []).push({}); Code Splitting webpackでリソースをバンドルすると、一回の通信でアプリの要素全てをロードできるので効率いいような気がするけど、アプリの規模が大きくなってくるとバンドルサイズが大きくなって、初期ロード時間が長くなり、つまり初期画面の表示に時間がかかるようになってしまう。 そもそも、いつもアプリの全画面をみるとは限らないので、いつもアプリの全要素をロードするのは無駄。 そんな問題に対応する技術がCode Splitting。 バンドルを分割し、(理想的には)必要な時に必要な分だけロードする技術。 Code Splittingのやりかたはいくつかあるが、ダイナミックインポートとReact.lazyとReact Suspenseとwebpackのプリフェッチディレクティブを使ったやつを、フォントモジュールに適用してみる。 src/components/App.jsx: -import React from 'react'; +import React, { Suspense } from 'react'; import { Route, Redirect } from 'react-router-dom'; import Home from './Home'; -import Fonts from '../fonts'; +const Fonts = React.lazy(() => import(/* webpackPrefetch: true */ '../fonts')); const App = () => ( <div> <Route exact path="/" render={() => <Redirect to="/home" />} /> <Route exact path="/home" component={Home} /> - <Fonts /> + <Suspense fallback={<div />}> + <Fonts /> + </Suspense> </div> ); export default App; コード変更はこれだけ。 import()がダイナミックインポートで、ECMAScriptで現在策定中の機能。 これを使えるようにするためには、Babelのプラグインを追加する必要がある。 yarn add -D @babel/plugin-syntax-dynamic-import .babelrc: { "presets": [ [ "@babel/preset-env", { "useBuiltIns": "usage" } ], "@babel/preset-react" ], - "plugins": ["styled-components"] + "plugins": ["styled-components", "@babel/plugin-syntax-dynamic-import"] } ダイナミックインポートの設定も完了。 これでフォントモジュールはメインのバンドルとは別ファイルになり、初期画面の表示時にはロードされず、ブラウザの空き時間に非同期にロードされるようになる。 Flow Reactに限らない話だけど、JavaScriptは動的型付け言語なので、特に規模が大き目なアプリを開発するとなると保守性が悪くなりがちで辛い。 ので、できれば静的型付けでやりたい。 JavaScriptを静的型付けにするには、TypeScriptとFlowという二つの選択肢がある。 今回、FlowがReactと同じくFacebook製なので、Reactと相性がいいかと思ってFlowを選択したけど、人気やエコシステムの充実度から見るとTypeScriptのほうがよかった気がする。 ので、Flowについてはさらっと書く。 Flow導入 Flowは、ソースに型情報を付けて静的型チェック可能にしつつ、実行時には型情報を取り去って普通のJavaScriptとして実行できるようにする仕組み。 型チェックするツールはflow-binパッケージで配布されていて、型情報の除去は@babel/preset-flowを使ってBabelでできる。 yarn add -D flow-bin @babel/preset-flow .babelrc: { "presets": [ [ "@babel/preset-env", { "useBuiltIns": "usage" } ], + "@babel/preset-flow", "@babel/preset-react" ], "plugins": ["styled-components", "@babel/plugin-syntax-dynamic-import"] } これで、yarn flowでFlowを実行できるようになった。 $ yarn flow version yarn run v1.7.0 $ C:\Users\kaitoy\Desktop\bin\pleiades\workspace\react-redux-scaffold\node_modules\.bin\flow version Flow, a static type checker for JavaScript, version 0.77.0 Done in 0.38s.