テックリードがどんな活動したらよいのか考えて行動してみた話

ogp

2022年6月に、Androidテックリードになった いわたん です。最近、某モンスターを育てたり図鑑を埋めたりするゲームで社内大会をやったらフルボッコにされて涙目でした。悔しくて最近は不思議な力でクラフトしたり空飛んだりして王国を救うゲームやってます。

今回はAndroidテックリードとして1年間やってみた施策の紹介と、それぞれの成果や反省点を紹介したいと思います。これからテックリードになろうとしている方やテックリードをしている方の参考になったり、こんな施策もいいよというアドバイスをもらえたら幸いです。

ZOZOのテックリードの役割と責任

Qiitaで弊社VPoEの瀬尾が紹介しているように会社としてのテックリードの役割定義があります。

Qiitaより抜粋したテックリードの役割です。

チームの技術的な方向性、設計や開発手法、実装や品質などプロダクトの技術面での定義に対して責任を持つ 全社横断で、技術施策の策定と普及、スキルアップ支援、採用活動への協力など技術面での貢献を行う

具体的にチームに対してどういった活動をするかはテックリードに任されている状態でした。

そこで、エンジニアのためのマネジメントキャリアパスという書籍を参考に、活動内容を考えました。

また、ZOZOの組織体制として、チームのマネジメントを行うブロック長がいます。ZOZOTOWN Androidチームでのブロック長の活動は過去の記事があるので、そちらも興味があれば読んでいただければ幸いです。

チーム内にテックリードとブロック長と似ているような似ていないような役職が存在することで、役割が重なりチームの二重管理とならないよう、役割の棲み分けを行いました。

  • メンバーのサポート
    • テックリードは個人、チームの技術面の成長をサポートする
    • ブロック長は、企業理念に上げているZOZOらしさの成長をサポートする
  • プロダクト開発
    • テックリードは技術的負債の返還や新たに発生させないよう責務を負う
    • ブロック長はアサイン管理を実施する

テックリードが目指すべき具体的なタスクの方向性が見えてきたので、次のような施策を実施しました。

実施した施策

チームとして成長を続けるため、さまざまな施策を試行しました。具体的にはテックリード1on1、読書会、アーキテクチャ座談会、ネーミングセンスを鍛える会など、様々な形の活動を通じて、チーム全体の技術力の向上を目指しました。

これら以外にも試した施策は多数ありますが、本記事では記憶に残っていたり、特に効果的だったものをいくつかピックアップし、それらの施策について詳しく紹介します。それぞれの施策は、テックリードとしてチームの成長を支援するために重要だと考えています。またチームとして、常に新たな取り組みに挑戦することは大切だと考えており、新たな試みから学んだことを積極的にフィードバックに活かすことで、より良いチーム作りを目指しています。

テックリード1on1

前述の『エンジニアのためのマネジメントキャリアパス』にも書かれていたのですが、メンバー個別の成長をサポートするためにテックリード1on1を実施しました。テックリード1on1は、個々のメンバーと直接対話し、具体的な技術的課題や目標に対するサポートを提供するための時間として設定しました。ZOZOでは定期的に上長との1on1を実施していますが、その場とは異なり、より技術的な内容に焦点を当てる時間としました。

テックリード1on1の主な目的は次のとおりです。

  1. 技術的な要素が絡む目標で手伝えることは何かを明確にする。
  2. 伸ばしたい技術が何か、個々のメンバーと相談する場を提供する。
  3. チーム内で技術知識を横展開し、共有する。
  4. 出しているPull Requestのレビューや、一緒にタスク整理をする。

テックリード1on1は最低月1回開催し、半期ごとの目標設定で設定した技術的な目標に対して手伝えることがないかを確認します。また、評価時には、これまでのテックリード1on1で話し合った内容をもとに、個々のメンバーが自己アピールに使えるポイントを明示しました。

ZOZOTOWN Androidのメンバーは10人以上おり、テックリード1on1は全体で毎週半日ほどの時間をかけるようにしました。テックリードになってからの負担としては一番大きい施策ですが、テックリード1on1の取り組みにより、メンバーそれぞれから様々な課題感を聞くことができました。

出てきた課題感として、次のような技術的な課題が出ました。

コードメトリクスの計測やAndroid Vitalsの活用など、メンバーが抱えている課題に対応する時間を、テックリード1on1や個別の対応時間で取り組むことができました。それにより、メンバーが得た知識や解決経験をテックブログへの投稿やDroidKaigiでの登壇に繋げることが出来ました。積読の消化やプログラミング知識に関する相談は次に紹介をする、読書会を開催するようにしていきました。

読書会

私たちのチームは多様なバックグラウンドを持つメンバーで構成されています。未経験からスタートしたエンジニア、他業種から転職してきたエンジニア、そして経験豊富なエンジニアと、そのレベルと経験は様々です。その一方で、リモート環境下では自然と得られるメンバー間での学びの機会が減少し、特に未経験者や他業種から来たエンジニアは基礎的な知識や技術を学ぶのに苦労していました。

ZOZOでは書籍購入支援の制度があり電子書籍での購入も認められています。制度を利用し皆で本を購入し読書会を行うことにしました。

読書会で取り上げる書籍は、参加者全員が理解を深められるような基礎的な内容から、より高度で技術的な話題まで幅広く選んでいます。この読書会の目的は、メンバー全員の技術力向上と知識の共有です。未経験者や他業種からの転職者は基礎を固め、経験者は新たな視点を得ることができます。また、それぞれの学びを共有することで、チーム全体としての知識も向上します。

これまでに、『Clean Architecture』や『読みやすいコードのガイドライン』、そしてUML関連の本を取り上げてきました。特に『Clean Architecture』は、アーキテクチャに興味のあるメンバーには好評でしたが、SOLIDの原則以降は難易度が高く、チーム全員が内容を把握するにはハードルが高いと感じました。

一方で、『読みやすいコードのガイドライン』はコスパがよく、コードの品質改善に大きく貢献しました。読書会後にPull Requestを見ていると、メンバーがコードにコメントを書く量が全体的に増えたことからもその影響が見て取れました。

技術的な議論においても明確な根拠を示せるようになりました。具体的には、Pull Requestのレビューの際に、『読みやすいコードのガイドライン』や『Clean Architecture』など、読書会で取り上げた本から出典を示すことが容易になりました。このことにより、指摘の際の説明負荷が軽減し、コミュニケーションがより円滑になりました。

歴史的経緯があるアプリのアーキテクチャ整理へのアプローチ

ZOZOTOWNのAndroidアプリは、リリースが2012年5月、現在のコードベースの初回コミットが2015年2月2日と、かなりの歴史があります。長い間に渡って開発が続けられてきた結果、異なるアーキテクチャの思想や実装上の思想が混ざり合い、保守コストが高まり、現在採用しているアーキテクチャや実装方針が見えにくくなるという課題が生じました。

ZOZOTOWN Androidチームはメンバーも多く、同時並行で稼働している案件もある程度の数があります。その為、自分一人でアーキテクチャの選定、メンバーへの普及や教育等を実施するのは難しいと考えました。そこで、将来テックリードになる可能性のある候補者と現テックリードでアーキテクチャについて議論を深めるアーキテクチャ座談会を週に1時間開催しています。

座談会の結果、新しいアーキテクチャとしてGoogleのModern Android App Architecture(以下、MAD)に従うことを決めました。さらに、その中で、ZOZOTOWNのAndroidアプリがMADに対して持つ独自の部分は何かという問いについて明確化できました。

具体的には、MADではRepository間の依存を認めていますが、ZOZOTOWN Androidではこれを認めずUseCaseを使用すると決めました。他にも決めたことに関してはガイドライン化してドキュメントとしてメンバーが確認出来るようにしました。

また、アーキテクチャ座談会でメンバーを増やしたことで、新しいアーキテクチャの導入に伴う問題点を早期に把握できました。座談会の参加者がそれぞれの担当案件ごとに新しく決めたアーキテクチャを採用することで、その問題点や運用上の問題が明確になりました。

具体的な問題点としては、新旧の実装方針が混在してしまうケースや、同様の責務を持っているが別の名前を付けられているような命名規則の問題が発生しました。原因分析もアーキテクチャ座談会の中で実施できました。結果として原因は、新しい実装方針がチームに十分に浸透しておらず、「なぜ新しい方針に変えるのか」という理由が共有されていなかったと結論づけました。

この記事の執筆時点ではまだ実施が出来ていない状況なのですが、対策として各案件にアーキテクチャの責任者を割り当て、設計やレビューを通じて案件メンバーにアーキテクチャを浸透させる計画をしています。

ネーミングセンスを鍛える会の取り組み

ネーミングは必ずしも1つの正解があるわけではなく、各メンバーの感性や背景、経験が反映されます。そのため、同じチーム内でもネーミングやインタフェース定義にはばらつきが生じてしまうことがありました。

この課題を解決するために、「ネーミングセンスを鍛える会」を実施してみました。この会の運営は、AIベースの言語モデルであるChatGPTを用いて行いました。具体的な手順は次の通りです。

  1. ChatGPTを用いて様々なお題を作成します。具体的なプロンプト例は後述します。
  2. そのお題をSlackに投稿し、チームメンバー全員に共有します。
  3. メンバーはそれぞれの解答をSlackに投稿します。
  4. 投稿された解答に対して、ChatGPTを用いて添削をします。

実際のSlackでのやり取りはこんな感じでした。

slack

この活動は初めての試みでしたが、参加者からは「面白い」という声が上がり、また「ハードルが低くて参加しやすい」との評価も得ることができました。しかし、この活動はSlack上で自由に参加できる形式を取っていたため、積極的に回答を投稿するメンバーと、そうでないメンバーとの間に差が生じてしまいました。その結果、活動はいつしか自然消滅してしまいました。

実際に使用したプロンプトは下記のとおりです。

制約事項を元に関数の仕様を決め、説明文を生成し出力の書式に沿って生成してください

# 制約事項
- 関数は1件とする
- 出力は要約と説明文のみにする
- 説明文は処理内容の重要な点のみを記述する
- 説明文は箇条書きで出力すること
- 関数名は出力しない
- 説明文はですます調とする
- 関数は循環複雑度が3〜5程度の内容とする

# 出力
## 要約
{機能の要約を出力する}

## 説明文
{箇条書きで機能の説明文を出力する}

## 使用例
    入力:{ここに関数への入力値が入る}
    出力:{ここに関数の出力値が入る}

案件への関わり方

『エンジニアのためのマネジメントキャリアパス』ではテックリードの定義は次のように紹介されていました。

(ソフトウェアの)開発チームに対する責任を担い、最低でも自身の職務時間の3割はチームと共にコードを書く作業に充てているリーダーのこと。

ZOZOTOWN Androidは複数案件が同時並行で走り、かつメンバーも全体で10人以上の規模です。チームとともにコードを書くためには自分は案件に専任で入るのではなくチーム全体に横断的な関わり方をするようにしました。具体的には次のような関わり方をしました。

  • 横断的なコードレビュー
  • 横断的に使う機能の実装

横断的なコードレビュー

ZOZOTOWN Androidチームでは、各メンバーがアサインされた案件内でPull Requestを相互でレビューするという体制をとっています。私自身はテックリードになってからは特定の案件にはアサインされない体制にし、時間が許す限りほぼ全てのPull Requestを確認していました。しかし、成長するチームとともにPull Requestの量も増え、全てを見るのが難しくなってきました。

そこで、私の役割を再定義し、重要案件の立ち上がり時のPull Requestや自分の興味があるPull Requestなどで優先順位を付けて確認するように変更しました。これにより、効率的に重要な箇所を見ることができるようになりました。

また、過去のアーキテクチャでの実装箇所やリファクタリング対象で今後は新規実装しない方針のモジュールに変更があった場合、その確認を促すようなBotを作成し導入しました。具体的には、Pull RequestのDiffに該当するファイルがあった際にBotが自分にメンションを投げ、該当のPull Requestを確認するように促します。これにより、重要なモジュールの変更確認が漏れることなく、品質を確保できています。

review

1年間のPull RequestをGitHubの活動を見える化するFindy Team+で確認してみたところ、ほぼ全員のPull Requestを確認していました。

横断的に使う機能の実装

横断的な機能の開発と整備を実施しました。具体的には、Loggerの統一、Feature Flagの導入、そしてGitHub Actionsの整備などを行いました。

テックリードが横断的な機能開発に関与する理由は、各案件で横断的に使う機能を実装すると、その実装が特定の用途に過度に特化してしまう可能性があると考えたからです。この特化は、その機能の汎用性が低下する可能性を生み出します。そのため、テックリードがチーム全体を見渡して汎用性の高い実装をすることで、チーム全体の効率と品質を向上させることを目指しました。

今回は具体的な例としてLoggerの統一を紹介します。

ZOZOTOWN Androidではデータを集めたい複数の部署や開発の効率化のためなどの理由で複数のLoggerが使われています。そのため、ログを仕込む箇所での実装も煩雑になっていました。この問題を解決するために、用途ごとに違う色々なLoggerを統一的に使用できる新しいLoggerを設計、実装しました。

結果として次のような実装をしました。

// Logのパラメータ
interface LogEvent {
    // Log送信を行なった画面の情報
    val screenName: String?

    // interfaceのデフォルト実装を利用することで未実装時はnullを返し、nullを返した場合はZozoLoggerは対応するLoggerに対して送信は行わない
    val parameterA: ParameterA? // LoggerA用のパラメータ
        get() = null
    val parameterB: ParameterB? // LoggerB用のパラメータ
        get() = null
    val parameterC: ParameterC? // LoggerC用のパラメータ
        get() = null
}

// 統一的に使えるLogger
object ZozoLogger {
    // 各種ロガー
    private var loggerA: LoggerA? = null
    private var loggerB: LoggerB? = null
    private var loggerC: LoggerC? = null

    // App.kt等でロガーをBindする
    fun bind(
        loggerA: LoggerA,
        loggerB: LoggerB,
        loggerC: LoggerC,
    ) {
        this.loggerA = loggerA
        this.loggerB = loggerB
        this.loggerC = loggerC
    }

    // ログの送信処理
    @JvmStatic
    fun log(event: LogEvent) {
        event.parameterA?.let { param ->
            loggerA.send(param)
        }

        event.parameterB?.let { param ->
            loggerB.push(param)
        }

        event.parameterC?.let { param ->
            loggerC.post(param)
        }

        Log.d(event.screenName, event.toString())
    }
}

ZozoLoggerはSingletonにしてHiltでDIする方法も考えました。しかし、ログ送信箇所が古い実装であったりDIが難しい場合に、使えない可能性を考慮しobjectで実装することを選択しました。

次のようにLogEventインタフェースの実装で各Logger用のパラメータを生成することで、各種Logger毎にパラメータを用意して別々にLogを送信する必要性をなくしました。

data class MyLogEvent(
    override val screenName: String,
    private val data1: Data,
    private val data2: Data,
) : LogEvent {
    // 送信を行いたいLogger用のパラメータだけを実装する
    override fun parameterA() = ParameterA(data1.value)
    override fun parameterB() = ParameterB(data1.value, data2.value)
    // parameterCについては実装していないのでLoggerCには送信されない
}

実際にログを送信する際の実装は以下のような形になります。

class MyViewModel() : ViewModel() {
    fun logic() {
        // 何か処理をする

        // ログの送信処理
        ZozoLogger.log(
            MyLogEvent(
                screenName = "my_screen",
                data1 = Data1(...),
                data2 = Data2(...),
            )
        )
    }
}

このような実装により、新たなLoggerの追加や削除、さらには1つのLoggerで送られていなかったイベントを別のLoggerで送るといった要件変更が容易になります。この変更は送信処理の修正を必要とせず、各LogEventの修正だけで対応可能となり変更コストを下げることが出来ました。

まとめ

私たちのチームでは、チームの成長を実現するために、様々な改善活動を積極的に試行しています。私たちは皆で提案を歓迎し、改善を推進する文化を育ててきました。

その結果、私たちは常にチームの改善に向けて努力を続けることができています。しかし、まだ改善できる点はたくさんあり、私たちは常に更なる成長を目指しています。

また、私たちのチームでは、マネジメントラインとは別にテックリードが存在しています。これにより、テックリードはチームへの技術的なサポートに専念でき、チームの技術力や生産性の向上に集中できています。

一方で、会社としてテックリードに期待されている全社横断の活動については、まだ十分に取り組めていないと感じています。今後は、チーム内で培った知識や経験を、会社全体で共有し、全社の成長に寄与することを目指していきます。

以上の取り組みを通じて、私たちはチームとしての成長を促進し、より良いアプリを提供することを目指しています。これからも私たちは、新たな試みを恐れず、チーム全体での技術力の向上に向けて邁進していきます。

最後に

ZOZOではAndroidエンジニアを募集しています。ご興味のある方は下記リンクからぜひご応募ください。

hrmos.co

カテゴリー