Upgrade to Pro — share decks privately, control downloads, hide ads and more …

事業成長を加速させるGoのコード品質改善の取り組み / Code quality improv...

事業成長を加速させるGoのコード品質改善の取り組み / Code quality improvement for Go language

2023/09/20の「ZOZO Tech Talk #8 - Go」にて発表した登壇資料です。
イベント詳細: https://zozotech-inc.connpass.com/event/293668/

TajimaTheMemer

September 20, 2023
Tweet

More Decks by TajimaTheMemer

Other Decks in Programming

Transcript

  1. 事業成長を加速させるGoの
 コード品質改善の取り組み
 ZOZO Tech Talk #8 - Go 
 


    株式会社ZOZO
 ブランドソリューション本部 バックエンド部 FAANSバックエンドブロック
 バックエンドエンジニア
 田島 太一 Copyright © ZOZO, Inc. 1
  2. © ZOZO, Inc. 3 ショップスタッフの販売サポートツール「FAANS」とは • WEAR, ZOZOTOWN, Yahoo!ショッピング, アパレルブランドの自社

    ECへのコーディネート投稿やその成 果の可視化など、ショップスタッフのオンライン接客を支援する業務支援ツール • ZOZOTOWN上でブランド実店舗の在庫取り置きを希望した際に、ショップスタッフが FAANS上での簡単 操作で取り置き対応を完結できるといった実店舗業務のサポートにも対応
  3. © ZOZO, Inc. 4 FAANSのバックエンドシステムは全てGoで書かれている • WebAPIサーバやバッチ処理も含め全て Goで実装 • WebAPIサーバはClean

    Architectureのポリシーに則った、ドメイン層をレイヤー間の依存関係における 最上位に置くレイヤードなアーキテクチャとなっている
  4. © ZOZO, Inc. 5 FAANSはまだローンチまもない新規プロダクト • FAANSは正式ローンチからまだ 1年の新しいプロダクト • 0

    → 1の立ち上げフェーズから 1 → 100の成長フェーズに移行 • 新機能の開発や既存機能の改善に取り組む日々 • 一方で、技術的負債も溜まってきているため、負債を解消していく取り組みも行っている
  5. © ZOZO, Inc. 7 機能開発の傍、様々な技術的負債の解消に取り組んでいます • アプリケーションのドメインモデル • データベースのデータモデル •

    Web APIのスキーマ定義 • データベースや外部 WebAPIといった外部コンポーネントとの連携部分の処理の実装 • レイヤー構造やパッケージ構成、それらの依存管理といったサーバサイドのアプリケーショ ンアーキテクチャ • コード品質 ← 今日はこのお話 • …
  6. © ZOZO, Inc. 9 本日お話ししないこと • 我々が採用しているレイヤードなアーキテクチャに関する解説 • テストコードの品質改善の取り組み •

    データベースなどの特定技術を扱う処理の Go実装に関する工夫や技術的負債の改善の取り組み
  7. © ZOZO, Inc. 10 コード品質とは • 可読性 • 保守性 •

    安全性 • 再利用性 • パフォーマンス • …
  8. © ZOZO, Inc. 13 Goのコード品質改善の取り組み 1. 徐々に厳しくするLinter設定 2. スタイルガイド「Google Go

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

    Style Guide」の導入 3. エラーハンドリングの改善 4. 凝集度を高める改善 5. ボーイスカウトルールによる既存コードの継続的改善
  10. © ZOZO, Inc. 16 golangci-lint • Goの代表的なLinterツールの一つ • govetのようなgo標準のLinterやStaticcheckのようなサードバーティの linterをまとめて実行できる

    runner • yamlファイルでどのLinterツールを使うか、使わないかといった細やかな設定が可能 • ホワイトリスト型、ブラックリスト型の設定どちらにも対応
  11. © ZOZO, Inc. 17 当初の弊チームのgolangci-lintの設定 • golangci-lintはプロダクト立ち上げ当初から CIに導入済していた • yaml上では実行するLinterツールは何も指定されていない、つまりデフォルトで有効化されている

    Linter のみを実行する設定となっていた • golangci-lintでデフォルトで有効化されている Linter一覧 ◦ errcheck ◦ gosimple ◦ govet ◦ ineffassign ◦ Staticcheck
  12. © ZOZO, Inc. 18 Linterの設定を徐々に厳しくしていく手順 • golangci-lintの設定ファイルにおいて disable-all: trueの上でenable配下にLinterを列挙していくホワイトリス ト型を採用

    • まずは、enable配下にデフォルトで有効化されていた Linterを明示的に指定して実質的に設定に差分がな い状態とした • デフォルトで有効ではない、つまり未導入の golangci-lintのLinter一覧をコメントアウトする形で列挙しておく
  13. © ZOZO, Inc. 19 Linterの設定を徐々に厳しくしていく手順 • その上で有効化したい Linterをコメントアウトして1つずつPull Requestを分けて有効化していく •

    有効化したlinterにより変更していない既存コードの部分で CIがコケるようになると開発体験が下がるため 導入時にプロジェクトコードの全範囲においてその Linterでエラーが出る箇所を修正することが重要
  14. © ZOZO, Inc. 21 Goのコード品質改善の取り組み 1. 徐々に厳しくするLinter設定 2. スタイルガイド「Google Go

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

    Style Guide」の導入 3. エラーハンドリングの改善 4. 凝集度を高める改善 5. ボーイスカウトルールによる既存コードの継続的改善
  16. © ZOZO, Inc. 23 スタイルガイドの導入 • コーディング規約はドキュメントにまとめ、チームで認識合わせできるようにしておくことはチーム開発の 基本 ◦ コミュニケーションコストの削減

    ◦ 新メンバーのオンボーディングの円滑化 • 0から自分たちでコーディング規約を定めていくのは開発組織の規模に見合わない労力がかかり非現実 的なので一般に公開されていて信頼できるスタイルガイドにできる限り乗っかる戦略 • 導入したGoのスタイルガイド ◦ Effective Go (公式) ◦ Go Code Review Comments (公式) ◦ Google Go Style Guide (Google社製)
  17. © ZOZO, Inc. 24 Google Go Style Guide • 2022年11月に公開されたGoogle社によるGoのスタイルガイド

    • Effective Goを前提としているのでスタイルガイド間のバッティングもない • 読みやすく慣用的な Goのコーディングスタイルを示している • 可読性が高いコードを書く上での迷いを最小化して初心者にありがちなミスを避けられるようにすることが 目的 • https://google.github.io/styleguide/go/
  18. © 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章全てをチームが従うべきスタイルガイドとして一先ず導入した
  19. © ZOZO, Inc. 26 「Style Guide」の5原則 • 個別ケースだけでなく全体に通底する考え方も説明されていて Good •

    5原則 (上から重要度順) ◦ Clarity (明白さ) : 目的と根拠が読み手にとって明白か ◦ Simplicity (シンプル) : できる限りシンプルなやり方で目的を達成しているか ◦ Concision (簡潔さ): 高いS/N比を有しているか ◦ Maintainability (保守性): 保守は容易か ◦ Consistency: 広範なGoogleのコードベースと一貫しているか ↓ 5原則という優先度付きの判断軸をチームの共通認識として持つことでスタイルガイドに具体的な書き方が記載 されていないようなケースにおいても実装時やレビュー時に判断しやすくなり、チームとして合意形成もしやすく なった
  20. © ZOZO, Inc. 27 Goのコード品質改善の取り組み 1. 徐々に厳しくするLinter設定 2. スタイルガイド「Google Go

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

    Style Guide」の導入 3. エラーハンドリングの改善 4. 凝集度を高める改善 5. ボーイスカウトルールによる既存コードの継続的改善
  22. © ZOZO, Inc. 29 Goにおけるエラーハンドリング • 業務アプリケーションでは関数 /メソッドの呼び先で発生したエラーをそのまま呼び出し元には返さずに fmt.Errorf() (Go

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

    をラップして上流に伝播させるようにした • errwrapperというpackageを用意してエラーのラップ処理が関数 /メソッドの内部実装の文頭 1行で済むよ うに簡易化した “err”だとエラーハンドリングが抜け ている箇所をLinterで検知 できなくなるため”err”以外の 命名とする
  24. © ZOZO, Inc. 34 errwrapperパッケージのPros/Cons • Pros ◦ 呼び先側の関数/メソッドの先頭で一度だけエラーラッピングのロジックを記述するだけで、その関 数/メソッド内で発生するエラーは自動的にラッピングされるのでラク

    ◦ 呼び先の関数/メソッドの情報以外の情報を付与したい時を除けば呼び元ではエラーをラップする 必要がなくなったのでコードがシンプルになった • Cons ◦ 標準的なやり方ではないため外部ライブラリの関数 /メソッドでは呼び元でエラーを適切にラップす る必要があり、その分の認知負荷がかかる
  25. © ZOZO, Inc. 35 Goのコード品質改善の取り組み 1. 徐々に厳しくするLinter設定 2. スタイルガイド「Google Go

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

    Style Guide」の導入 3. エラーハンドリングの改善 4. 凝集度を高める改善 5. ボーイスカウトルールによる既存コードの継続的改善
  27. © ZOZO, Inc. 37 凝集度と結合度 • 保守性と拡張性が高いソフトウェアには高い凝集度と低い結合度が重要 ◦ 凝集度 ▪

    モジュール内の機能がどれだけ密接に関連しているかを示す尺度 ◦ 結合度 ▪ 二つのモジュール間の依存関係の強さを示す尺度
  28. © ZOZO, Inc. 39 Before: ファクトリ関数を使わずにDomain層のEntityの構造体を初期化 していた • アプリケーション固有のビジネスロジックを実装する UseCase層でDomain層に実装されたEntityを表す

    構造体をファクトリ関数を使わずに初期化していた ◦ 属性値のバリデーションチェックなどのドメイン層の知識が Domain層ではなくUseCase層で実装さ れて凝集度が下がってしまう → ドメインモデル貧血症 ◦ 属性値の指定忘れやバリデーションチェックのし忘れによる中途半端に初期化された状態の構造 体の存在を許容してしまう恐れ → バグの温床
  29. © ZOZO, Inc. 42 After: Entityを表す構造体のファクトリ関数を定義して凝集度を高める • ファクトリ関数内で属性値のバリデーションや変換処理が行われるため、不完全な状態の構造体が生成 されることがなくなった •

    ドメイン固有の知識が UseCase層に散らからずにDomain層に集約されたことで凝集度が高まった • 例えば複数のEntity間で共通の属性などは Defined Typeで適切に「値オブジェクト」化し、その属性のバ リデーション処理を値オブジェクトの生成処理内で行うことで凝集度を高めることも有効
  30. © ZOZO, Inc. 43 ② Defined Typeによるファーストクラスコレクション • ファーストクラスコレクション ◦

    配列などのコレクションをラップする専用のクラスを持ち、そのコレクションのビジネスルールや振る 舞いをそのクラス内に実装するオブジェクト指向プログラミングの実装パターン • Defined Type ◦ GoではDefined Typeを使用することで独自の新しい型を定義可能 ◦ Slice型に対してDefined Typeで新たな型を定義しその型でメソッドを定義することで簡単にファー ストクラスコレクションが実現できる
  31. © ZOZO, Inc. 48 Goのコード品質改善の取り組み 1. 徐々に厳しくするLinter設定 2. スタイルガイド「Google Go

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

    Style Guide」の導入 3. エラーハンドリングの改善 4. 凝集度を高める改善 5. ボーイスカウトルールによる既存コードの継続的改善