Slide 1

Slide 1 text

事業成長を加速させるGoの
 コード品質改善の取り組み
 ZOZO Tech Talk #8 - Go 
 
 株式会社ZOZO
 ブランドソリューション本部 バックエンド部 FAANSバックエンドブロック
 バックエンドエンジニア
 田島 太一 Copyright © ZOZO, Inc. 1

Slide 2

Slide 2 text

© ZOZO, Inc. 株式会社ZOZO
 ブランドソリューション開発本部バックエンド部FAANSバックエン ドブロック
 田島太一
 2020年4月中途入社
 MLOpsチームでZOZOTOWN, WEARのML機能の開発・運用に従 事後、2022年6月よりFAANSの開発チームでバックエンドシステ ムの開発・運用に従事
 最近はトマトにハマって毎日2玉食べている 🍅🍅
 
 


Slide 3

Slide 3 text

© ZOZO, Inc. 3 ショップスタッフの販売サポートツール「FAANS」とは ● WEAR, ZOZOTOWN, Yahoo!ショッピング, アパレルブランドの自社 ECへのコーディネート投稿やその成 果の可視化など、ショップスタッフのオンライン接客を支援する業務支援ツール ● ZOZOTOWN上でブランド実店舗の在庫取り置きを希望した際に、ショップスタッフが FAANS上での簡単 操作で取り置き対応を完結できるといった実店舗業務のサポートにも対応

Slide 4

Slide 4 text

© ZOZO, Inc. 4 FAANSのバックエンドシステムは全てGoで書かれている ● WebAPIサーバやバッチ処理も含め全て Goで実装している。 ● WebAPIサーバはClean Architectureのポリシーに則った、ドメイン層をレイヤー間の依存関係における 最上位に置くレイヤードなアーキテクチャとなっている。

Slide 5

Slide 5 text

© ZOZO, Inc. 5 FAANSはまだローンチまもない新規プロダクト ● FAANSは正式ローンチからまだ 1年の新しいプロダクト ● 0 → 1の立ち上げフェーズから 1 → 100の成長フェーズに移行 ● 新機能の開発や既存機能の改善に取り組む日々 ● 一方で、技術的負債も溜まってきているため、負債を改善していく取り組みも行っている。

Slide 6

Slide 6 text

© ZOZO, Inc. 6 新規事業でも技術的負債と向き合っていくことが大事 https://speakerdeck.com/shinden/living-with-technical-debt

Slide 7

Slide 7 text

© ZOZO, Inc. 7 機能開発の傍、様々な技術的負債の解消に取り組んでいます ● ソフトウェアのドメインモデル ● データベースのデータモデル ● Web APIのスキーマ定義 ● データベースや外部 APIといった外部コンポーネントとの連携処理 ● レイヤー構造やパッケージ構成、それらの依存管理といったサーバサイドのアプリケーショ ンアーキテクチャ ● コード品質 ← 今日はこのお話 ● …

Slide 8

Slide 8 text

© ZOZO, Inc. 8 本日お話しすること ● 今日は技術的負債の中で Goのコード品質に関する部分にフォーカスしてその改善の取り組みとその際 の工夫についてお話します。 ● Goでチーム開発する上で入門向けの優しい内容となっています。

Slide 9

Slide 9 text

© ZOZO, Inc. 9 本日お話ししないこと ● 我々が採用しているレイヤードなアーキテクチャに関する解説 ● テストコードの品質改善の取り組み ● データベースなどの特定技術を扱う処理の改善の取り組み

Slide 10

Slide 10 text

© ZOZO, Inc. 10 コード品質とは ● 可読性 ● 保守性 ● 安全性 ● 再利用性 ● パフォーマンス ● …

Slide 11

Slide 11 text

© ZOZO, Inc. 11 コード品質は事業の成長スピードにとって重要な因子 ● 開発の生産性の向上 ● メンテナンスの容易性の向上 ● 長期的なバグの減少

Slide 12

Slide 12 text

© ZOZO, Inc. 12 Goでも書き方にばらつきは生まれる ● Goはシンプルな言語仕様 ● とはいえ、(むしろだからこそ?)開発者によって書き方にばらつきは生まれる。 ● コーディングの一般的な作法、 Goらしい書き方を学ぶ必要はある。

Slide 13

Slide 13 text

© ZOZO, Inc. 13 Goのコード品質改善の取り組み 1. 徐々に厳しくするLinter設定 2. スタイルガイド「Google Go Style Guide」の導入 3. エラーハンドリングの改善 4. 凝集度を高める改善 5. ボーイスカウトルールによる既存コードの継続的改善 ※ 他にも色々やってますが時間の都合上割愛

Slide 14

Slide 14 text

© ZOZO, Inc. 14 Goのコード品質改善の取り組み 1. 徐々に厳しくするLinter設定 2. スタイルガイド「Google Go Style Guide」の導入 3. エラーハンドリングの改善 4. 凝集度を高める改善 5. ボーイスカウトルールによる既存コードの継続的改善

Slide 15

Slide 15 text

© ZOZO, Inc. 15 Linterで縛れるものは縛る ● Pull RequestのCIでバグ検知や不適切な書き方を自動検知できるので、 Linterで縛れるものは縛って コード品質を仕組みで担保する。 ● Goの代表的なLinterツール ○ govet ○ Staticcheck ○ golangci-lint ○ …

Slide 16

Slide 16 text

© ZOZO, Inc. 16 golangci-lint ● Goの代表的なLinterツールの一つ ● govetのようなgo標準のLinterやStaticcheckのようなサードバーティの Linterをまとめて実行できる runner ● yamlファイルでどのLinterツールを使うか、使わないかといった細やかな設定が可能 ● ホワイトリスト型、ブラックリスト型の設定どちらにも対応

Slide 17

Slide 17 text

© ZOZO, Inc. 17 当初の弊チームのgolangci-lintの設定 ● golangci-lintはプロダクト立ち上げ当初から CIに導入していた。 ● yaml上では実行するLinterツールを何も指定していない、つまりデフォルトで有効化される Linterのみを実 行する設定となっていた。 ● golangci-lintでデフォルトで有効化されている Linter一覧 ○ errcheck ○ gosimple ○ govet ○ ineffassign ○ Staticcheck

Slide 18

Slide 18 text

© ZOZO, Inc. 18 Linterの設定を徐々に厳しくしていく手順 ● golangci-lintの設定ファイルにおいて disable-all: trueの上でenable配下にLinterを列挙していくホワイトリス ト型を採用した。 ● まずは、enable配下にデフォルトで有効化されていた Linterを明示的に指定して実質的に設定に差分がな い状態とした。 ● その際、デフォルトで有効ではない、つまり未導入の golangci-lintのLinter一覧をコメントアウトする形で列挙 しておく。

Slide 19

Slide 19 text

© ZOZO, Inc. 19 Linterの設定を徐々に厳しくしていく手順 ● 続いて、有効化したい Linterをコメントアウトして1つずつPull Requestを分けて有効化していく。 ● 有効化したLinterにより変更していない既存コードの部分で CIがコケるようになると開発体験が下がるた め、導入時にプロジェクトコードの全範囲においてその Linterでエラーが出る箇所を修正する。腕力! 💪

Slide 20

Slide 20 text

© ZOZO, Inc. 20 徐々に設定を厳しくしていった結果 ● golangci-lintでデフォルトで有効化されていて既に導入済みだった Linterを除いて新たに20個以上の Linterがこれまで導入された。 ● 今後も優先度を精査しながら適切なタイミングで Linterの導入作業を進めていく予定。

Slide 21

Slide 21 text

© ZOZO, Inc. 21 Goのコード品質改善の取り組み 1. 徐々に厳しくするLinter設定 2. スタイルガイド「Google Go Style Guide」の導入 3. エラーハンドリングの改善 4. 凝集度を高める改善 5. ボーイスカウトルールによる既存コードの継続的改善

Slide 22

Slide 22 text

© ZOZO, Inc. 22 Goのコード品質改善の取り組み 1. 徐々に厳しくするLinter設定 2. スタイルガイド「Google Go Style Guide」の導入 3. エラーハンドリングの改善 4. 凝集度を高める改善 5. ボーイスカウトルールによる既存コードの継続的改善

Slide 23

Slide 23 text

© ZOZO, Inc. 23 スタイルガイドの導入 ● コーディング規約はドキュメントにまとめ、チームで認識合わせできるようにしておくことはチーム開発の 基本 ○ コミュニケーションコストの削減 ○ 新メンバーのオンボーディングの円滑化 ● 0から自分たちでコーディング規約を定めていくのは開発組織の規模に見合わない労力がかかり非現実 的なので、一般に公開されていて信頼できるスタイルガイドにできる限り乗っかる戦略 ● 導入したGoのスタイルガイド ○ Effective Go (Go言語公式) ○ Go Code Review Comments (Go言語公式) ○ Google Go Style Guide (Google社製)

Slide 24

Slide 24 text

© ZOZO, Inc. 24 Google Go Style Guide ● 2022年11月に公開されたGoogle社によるGoのスタイルガイド。 ● Effective Goを前提としているのでスタイルガイド間のバッティングもない。 ● 読みやすく慣用的な Goのコーディングスタイルを示している。 ● 可読性が高いコードを書く上での迷いを最小化し、初心者にありがちなミスを避けられるようにすることが 目的。 ● https://google.github.io/styleguide/go/

Slide 25

Slide 25 text

© ZOZO, Inc. 25 Google Go Style Guideは3章構成 規範的(canonical): 規範的かつ永続的なルール 標準的(normative): 一貫性を持たせるためのルール 章 主な対象者 規範的 標準的 Style Guide EveryOne Yes Yes Style Decisions Readability Mentors Yes No Best Practices Anyone Interested No No ● 3章全てをチームが従うべきスタイルガイドとして一先ず導入した。

Slide 26

Slide 26 text

© ZOZO, Inc. 26 「Style Guide」の5原則 ● 個別ケースだけでなく全体に通底する考え方も説明されていて Good! ● 5原則 (上から重要度順) ○ Clarity (明白さ) : 目的と根拠が読み手にとって明白か ○ Simplicity (シンプル) : できる限りシンプルなやり方で目的を達成しているか ○ Concision (簡潔さ): 高いS/N比を有しているか ○ Maintainability (保守性): 保守は容易か ○ Consistency: 広範なGoogleのコードベースと一貫しているか ↓ 5原則という優先度付きの判断基準 をチームの共通認識として持つことで、スタイルガイドに具体的な書き方が 記載されていないようなケースにおいても実装時やレビュー時に判断しやすくなり、チームとして合意形成もし やすくなった。

Slide 27

Slide 27 text

© ZOZO, Inc. 27 Goのコード品質改善の取り組み 1. 徐々に厳しくするLinter設定 2. スタイルガイド「Google Go Style Guide」の導入 3. エラーハンドリングの改善 4. 凝集度を高める改善 5. ボーイスカウトルールによる既存コードの継続的改善

Slide 28

Slide 28 text

© ZOZO, Inc. 28 Goのコード品質改善の取り組み 1. 徐々に厳しくするLinter設定 2. スタイルガイド「Google Go Style Guide」の導入 3. エラーハンドリングの改善 4. 凝集度を高める改善 5. ボーイスカウトルールによる既存コードの継続的改善

Slide 29

Slide 29 text

© ZOZO, Inc. 29 Goにおけるエラーハンドリング ● 業務アプリケーションでは関数 /メソッドの呼び先で発生したエラーを fmt.Errorf() (Go 1.13〜)を使って呼 び先の関数/メソッドの名前や引数の値といった情報を付与する形でエラーをラップしてから上流に伝播さ せるのが一般的。 ○ そのまま返してしまうとエラーログからエラーの発生箇所が分かりにくくなりトラブルシューティング の難易度が上がる。 ○ なお、サードパーティライブラリを使ってスタックトレースを出す方法もある。 ※標準パッケージにス タックトレースの機構は現状存在しない。

Slide 30

Slide 30 text

© ZOZO, Inc. 30 Before: 適切にラップせずにそのままのエラーを伝播させていた ● 我々のシステムでは呼び先の関数やメソッドで発生したエラーを適切にラップすることなく呼び元にそのま ま返してしまっていたため、エラー調査の難易度が上がっていた。

Slide 31

Slide 31 text

© ZOZO, Inc. 31 After: 関数/メソッドの”呼び先側”でエラーをラップするようにした ● エラーが発生した呼び先の関数 /メソッドの情報を呼び元側ではなく呼び先側で fmt.Errorf()を使ってエラー をラップして上流に伝播させるようにした。 ● errwrapperというpackageを用意してエラーのラップ処理が関数 /メソッドの内部実装の文頭 1行で済むよ うに簡易化した。 “err”だとエラーハンドリングが抜けている箇所を Linterで 検知できなくなるため ”err”以外の命名とする

Slide 32

Slide 32 text

© ZOZO, Inc. 32 errwrapperパッケージの実装 ※一部抜粋

Slide 33

Slide 33 text

© ZOZO, Inc. 33 errwrapperパッケージの実装 ● https://github.com/golang/pkgsite/blob/master/internal/derrors/derrors.go を参考に実装した。 ● 全体の実装は↑をご参照ください。

Slide 34

Slide 34 text

© ZOZO, Inc. 34 errwrapperパッケージのPros/Cons ● Pros ○ 呼び先側の関数/メソッドの先頭で一度だけエラーラッピングのロジックを記述するだけで、その関 数/メソッド内で発生するエラーは自動的にラッピングされるのでラク。 ○ 呼び先の関数/メソッドの情報以外の情報を付与したいケースを除けば、呼び元ではエラーをラップ する必要がなくなったのでコードがシンプルになった。 ● Cons ○ 標準的なやり方ではないため外部ライブラリの関数 /メソッドでは呼び元でエラーを適切にラップす る必要があり、その分の認知負荷がかかる。

Slide 35

Slide 35 text

© ZOZO, Inc. 35 Goのコード品質改善の取り組み 1. 徐々に厳しくするLinter設定 2. スタイルガイド「Google Go Style Guide」の導入 3. エラーハンドリングの改善 4. 凝集度を高める改善 5. ボーイスカウトルールによる既存コードの継続的改善

Slide 36

Slide 36 text

© ZOZO, Inc. 36 Goのコード品質改善の取り組み 1. 徐々に厳しくするLinter設定 2. スタイルガイド「Google Go Style Guide」の導入 3. エラーハンドリングの改善 4. 凝集度を高める改善 5. ボーイスカウトルールによる既存コードの継続的改善

Slide 37

Slide 37 text

© ZOZO, Inc. 37 凝集度と結合度 ● 保守性と拡張性が高いソフトウェアには高い凝集度と低い結合度が重要。 ○ 凝集度 ■ モジュール内の機能がどれだけ密接に関連しているかを示す尺度 ○ 結合度 ■ 二つのモジュール間の依存関係の強さを示す尺度

Slide 38

Slide 38 text

© ZOZO, Inc. 38 ① ファクトリ関数による構造体(struct)の初期化 ● Goの構造体にはJavaのクラスコンストラクタのような初期化用メソッドは用意されていないし、初期化時 にそのようなメソッドによる初期化処理を強制することもできない。 ● GoではNewXXX()という命名でファクトリ関数を用意して初期化するのが慣例。

Slide 39

Slide 39 text

© ZOZO, Inc. 39 Before: ファクトリ関数を使わずにDomain層のEntityの構造体を初期化 していた ● アプリケーション固有のビジネスロジックを実装する UseCase層でDomain層に実装されたEntityを表す構 造体をファクトリ関数を使わずに初期化していた。 ○ 属性値のバリデーションチェックなどのドメイン層の知識が Domain層ではなくUseCase層で実装さ れて凝集度が下がってしまう。 → ドメインモデル貧血症 ○ 属性値の指定忘れやバリデーションチェックのし忘れによる中途半端に初期化された状態の構造 体の存在を許容してしまう恐れ。 → バグの温床

Slide 40

Slide 40 text

© ZOZO, Inc. 40 After: Entityを表す構造体のファクトリ関数を定義して凝集度を高める (Domain層の実装) 属性値のバリデーションやド メイン固有の変換処理はファ クトリ関数内で実行

Slide 41

Slide 41 text

© ZOZO, Inc. 41 After: Entityを表す構造体のファクトリ関数を定義して凝集度を高める (UseCase層の実装) テストしやすいように 日時は外から渡す

Slide 42

Slide 42 text

© ZOZO, Inc. 42 After: Entityを表す構造体のファクトリ関数を定義して凝集度を高める ● ファクトリ関数内で属性値のバリデーションや変換処理が行われるため、不完全な状態の構造体が生成さ れることがなくなった。 ● ドメイン固有の知識が UseCase層に散らからずにDomain層に集約されたことで凝集度が高まった。 ● 例えば、複数のEntity間で共通の属性などは Defined Typeで適切に「値オブジェクト」化し、その属性のバ リデーション処理を値オブジェクトの生成処理内で行うことで凝集度を高めることも有効。

Slide 43

Slide 43 text

© ZOZO, Inc. 43 ② Defined Typeによるファーストクラスコレクション ● ファーストクラスコレクション ○ 配列などのコレクションをラップする専用のクラスを持ち、そのコレクションのビジネスルールや振る 舞いをそのクラス内に実装するオブジェクト指向プログラミングの実装パターン ● Defined Type ○ GoではDefined Typeを使用することで独自の新しい型を定義可能 ○ Slice型に対してDefined Typeで新たな型を定義しその型でメソッドを定義することでファーストクラス コレクションが容易に実現

Slide 44

Slide 44 text

© ZOZO, Inc. 44 Before: Entityのコレクション操作がUseCase層に実装されていた

Slide 45

Slide 45 text

© ZOZO, Inc. 45 After: ファーストクラスコレクションを使ってDomain層にコレクション操作 のロジックを持たせる (Domain層)

Slide 46

Slide 46 text

© ZOZO, Inc. 46 After: ファーストクラスコレクションを使ってDomain層にコレクション操作 のロジックを持たせる (UseCase層) データアクセス時にentity.DocumentのSlice 型ではなくentity.DocumentList型で 返すようにする

Slide 47

Slide 47 text

© ZOZO, Inc. 47 After: ファーストクラスコレクションを使ってDomain層にコレクション操作 のロジックを持たせる ● ファーストクラスコレクションによってコレクション操作が Domain層の振る舞いとして実装されて凝集度が 高まった。

Slide 48

Slide 48 text

© ZOZO, Inc. 48 Goのコード品質改善の取り組み 1. 徐々に厳しくするLinter設定 2. スタイルガイド「Google Go Style Guide」の導入 3. エラーハンドリングの改善 4. 凝集度を高める改善 5. ボーイスカウトルールによる既存コードの継続的改善

Slide 49

Slide 49 text

© ZOZO, Inc. 49 Goのコード品質改善の取り組み 1. 徐々に厳しくするLinter設定 2. スタイルガイド「Google Go Style Guide」の導入 3. エラーハンドリングの改善 4. 凝集度を高める改善 5. ボーイスカウトルールによる既存コードの継続的改善

Slide 50

Slide 50 text

© ZOZO, Inc. 50 コード品質向上のためのチーム方針 ● Linterで検知できるものは Linter導入時に一括で修正する。 ● 新規で書くコードはLinterやチームの規約に沿って実装する。 ● 機能開発に追われる中で既存コードはいつ、どのように改善していくか。 → ボーイスカウトルール

Slide 51

Slide 51 text

© ZOZO, Inc. 51 ボーイスカウトルール ● 「キャンプ場は、自分が訪れた時よりも綺麗にして去るべきだ」 ● ソフトウェアのコードに変更を加える際、関連する部分のコードを少しでも改善してからコミット・マージする ソフトウェア開発のプラクティス → 時間とともにコードベース全体の品質が徐々に向上

Slide 52

Slide 52 text

© ZOZO, Inc. 52 ボーイスカウトルールによるコード品質改善のチーム方針 ● 既存のコードに変更を加える際はその周辺がチームの規約に沿ってなければついでに直す。 ● 利用する既存の定数 /変数/構造体/関数/メソッドも規約に沿ってなければついでに直す。 ● ただし、既存コードの品質改善のための変更により Pull Requestのレビュー負荷が高くつく場合は、改善 部分を適切な粒度で別 Pull Requestとして切り出して出す。

Slide 53

Slide 53 text

© ZOZO, Inc. 53 ボーイスカウトルールは実効的なアプローチ ● 新規事業で機能開発に追われる中で継続的に既存コードを改善していく上でボーイスカウトルールは実 効的なアプローチ。 ● ただし、限界があるのでボーイスカウトルールだけに頼らない。 ○ セキュリティやシステム障害のリスク、後手になると負債回収コストが増大する類の負債は、独立し たタスクとして切り出し優先度を上げてしっかり工数を割いて取り組むことも大事。工数を確保する 交渉力もエンジニアに求められるスキル! 💪 ○ 定期的にチーム全員が改善タスクに専念する日を作るのも有効。

Slide 54

Slide 54 text

© ZOZO, Inc. 54 まとめ ● スピーディーな事業成長にとって高いコード品質は重要な因子。 ● 新規事業で機能開発に追われる中でも途中からでも着実に改善していける。 ● 仕組み化やルール化によって高品質で Goらしいコードをチームで実践していきましょう。

Slide 55

Slide 55 text

No content