$30 off During Our Annual Pro Sale. View Details »

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

TajimaTheMemer
September 20, 2023

事業成長を加速させる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

    View Slide

  2. © ZOZO, Inc.
    株式会社ZOZO

    ブランドソリューション開発本部バックエンド部FAANSバックエン
    ドブロック

    田島太一

    2020年4月中途入社

    MLOpsチームでZOZOTOWN, WEARのML機能の開発・運用に従
    事後、2022年6月からFAANSの開発チームでバックエンド開発に
    従事

    最近はトマトにハマって毎日2玉食べている



    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  25. © 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章全てをチームが従うべきスタイルガイドとして一先ず導入した

    View Slide

  26. © ZOZO, Inc.
    26
    「Style Guide」の5原則
    ● 個別ケースだけでなく全体に通底する考え方も説明されていて Good
    ● 5原則 (上から重要度順)
    ○ Clarity (明白さ) : 目的と根拠が読み手にとって明白か
    ○ Simplicity (シンプル) : できる限りシンプルなやり方で目的を達成しているか
    ○ Concision (簡潔さ): 高いS/N比を有しているか
    ○ Maintainability (保守性): 保守は容易か
    ○ Consistency: 広範なGoogleのコードベースと一貫しているか

    5原則という優先度付きの判断軸をチームの共通認識として持つことでスタイルガイドに具体的な書き方が記載
    されていないようなケースにおいても実装時やレビュー時に判断しやすくなり、チームとして合意形成もしやすく
    なった

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  31. © ZOZO, Inc.
    31
    After: 関数/メソッドの”呼び先側”でエラーをラップするようにした
    ● エラーが発生した呼び先の関数 /メソッドの情報を呼び元側ではなく呼び先側で fmt.Errorf()を使ってエラー
    をラップして上流に伝播させるようにした
    ● errwrapperというpackageを用意してエラーのラップ処理が関数 /メソッドの内部実装の文頭 1行で済むよ
    うに簡易化した

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  53. © ZOZO, Inc.
    53
    ボーイスカウトルールは実効的なアプローチ
    ● 新規事業で機能開発に追われる中で継続的に既存コードを改善していく上でボーイスカウトルールは実
    効的なアプローチ
    ● ただし、ボーイスカウトルールだけに頼るのではなく、既存コードにおいてセキュリティリスクや後からの負
    債回収コストが高くつく負債は独立したタスクとして切り出して優先度を上げてしっかり工数を割いて取り
    組むことも大事

    View Slide

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

    View Slide

  55. View Slide