Techouse Developers Blog

テックハウス開発者ブログ|マルチプロダクト型スタートアップ|エンジニアによる技術情報を発信|SaaS、求人プラットフォーム、DX推進

インターン生が突如現れた最強CTOとペアプロした話

ogp

はじめに

この記事は、Techouse Advent Calendar 2024 12日目です。
昨日は sakaidubz さんによる エンジニアインターンオンボーディングの効率化を目指して でした。 12日目は、2024年秋の新卒として入社して、現在クラウドハウス労務でエンジニアをしているショーンが担当します。

私事ですが、この秋に、2ヶ月ほど「ペアプログラミング」なるものを初めて行いました。今回はその経験をまとめるとともに、ペアプロの魅力について自分なりに語って参ります。

クラウドハウス労務

我々が開発している「クラウドハウス労務」というプロダクトは労務業務の電子化を推進するためのクラウドサービスです。
人事労務担当と従業員との間のあらゆる手続きを電子化する手続き機能、複雑な年末調整の業務を簡単にする、年末調整機能などの様々な機能を持っています。

私がこの秋に取り組んだのは、年末調整のプロジェクトでした。

ペアプロをすることに

24年10月に新卒入社する直前までインターンとして働いていた私ですが、年末調整で重要な機能部分を幾つか任されることとなりました。苦労していた8月ごろ、突如とんでもないチャンスが降ってきました。

我々の会社では、1年ほど前から業務委託のエンジニアの方に参画いただいて、一緒に開発をして頂いております。
基本的に業務委託の方は、重要かつ緊急な案件を行っている先輩社員とペアを組んで開発する事がほとんどでした。

ある日、契約をしていたエンジニアの1名がご退職となり、新しい方に参画していただくことになりました。

朝会でそれを最初に聞いた時は、自分には直接関係ないことと完全に油断していました。
次の日、開発チームのリーダーが「新しく入られる方はショーン(私)とペアプロさせようと思っている」と言っていることを小耳に挟んでしまいました。

慌ててその方のプロフィールを調べてみると、なんの間違いか分かりませんが、その方は先方の会社のCTOだったのです。

仲間のインターンや開発チームのメンバーも「ショーン×CTO」の組み合わせをイジってきます。
「ペアプロやるからにはちゃんと価値を出せよ!」「最高の福利厚生だぞ!」などの暗黙的な圧力も感じます。

ペアプロとは

そもそも、まずはペアプロとは何かを理解することが重要です。

そもそもペアプロとは「ペアプログラミング」の略称で、名前の通り2人のプログラマーが共同で同じプログラムを書くプログラミング手法です。
ペアプロの目的は、プログラムの品質・コードの理解度向上、開発速度の向上、知識の共有、コミュニケーションの向上などがあるそうです。

「ドライバー」「ナビゲーター」という役割を交互にスイッチしながら行うことが一般的です。
「ドライバー」が実際に手を動かしてコードを書く役割、「ナビゲーター」がドライバーをサポートし、レビューする役割を担います。

コードレビューのプロセスを軽減できたり、ドメイン知識の不足をすぐに共有できる、お互いに詰まった時すぐに相談できるなどのメリットがあるそうです。

ペアプロの魅力

程なくして、CTO氏とのペアプロが始まりました。結論から言うと、このペアプロは私にとって最高の経験でした。 以下では私が感じたペアプロの魅力と、それによって自分が成長できたと感じたポイントをまとめてみました。

①:詰まった時の進め方

エンジニアとして未熟なうちは、開発をしている中で詰まる場面が非常に多いです。これを読んでいる皆様も、あの頃のことを思い出してください。

当然、ペアプロをしていても、時に立ち止まる必要が生じます。しかし、その時の立ち回り方や進め方については、大きな変化がありました。

ペアプロをしたことで、開発中に悩み、詰まるポイントを自分なりに分類できました。

パターン1: 一般的な技術的回答が存在しているが、自分の知識不足で詰まる場合

このパターンの大抵の場合は、CTO氏が答えを持っていることが多かったです。
ペアプロの場では、それをその場で吸収できる上、自分に不足している知識が何か、何を勉強すべきかが明確になります。

何かを調べる際のソースの選び方や、答えに辿り着くまでのスピードは圧巻でした。元となるソースコードを読みにいくことの重要性など、開発において非常に重要なスキルを身につけることができました。

パターン2: プロダクトの要件や背景に依存して答えが決まる問題の場合

このパターンではCTO氏も一緒に悩んでくれます。プロダクトのドメイン固有の知識などは自分しか持っていないことも多いので、それを共有してCTO氏と一緒に考えることで、一人で考えるのとは違った体験になります。他の仕様や、全体から芋づる式に答えが導き出される時は気持ちがいいです。

CTO氏の深い経験と広い視野から、自分が考慮できていなかった問題にも気がつくことがありますし、思考プロセスを間近で観察できることによる学びは多いです。

強い人と一緒になって云々と唸るのは、非常に奥深い楽しみがあります。

パターン3: 答えがない問題であり、決めの問題の場合

その場で議論することで、意思決定が早くなり無駄な時間を過ごすことがなくなります。

最悪の場合、後々他の人のツッコミを入れられたとしても、CTO氏の威光によって薙ぎ払います。

このように、ペアプロ中の場合、悩みの種類によって考え方や対処の仕方が明確に違いました。 これによって、一人で開発をしている時も、詰まった際に、どのように対処するべきかの方向性が見えやすくなったと感じています。

②:基本動作

ペアプロをすると言うことは、相手のエンジニアの所作を間近で見ることができる、そして逆に自分の所作を晒すことでもあります。

僕はペアプロ開始の初日に、速攻でCTO氏に弟子入りを宣言していたので、 基本動作が未熟だったらすぐにツッコミを入れてもらえるようになりました。

普段なら調べても、継続的に使わず、結局頭から抜けるようなコマンドや操作も、ペアプロの場では常に見られています。

したがって、次に同じ無様な姿を見せる訳にはいかないのです。その適度な緊張感によって自然と定着するようになります。

他にも、手作業で行っていたことをスクリプトに実行させるなどの、後の自分の作業を効率化するための作業も、自分では思いつかないくらいの幅があることに気が付かされました。
いわゆるプログラマーの三大美徳というものですね。

③:将来の開発を意識して、今の開発をする

特に大きな学びになったのが、このポイントでした。

具体的に我々が行ったことを元に、幾つが例をあげます。

詳細については割愛しますが、我々がペアプログラミングで行った開発の1つに「Rails・I18nを用いた多言語化」というものがありました。(参考: Rails I18n)

継続改善1: 日本語リテラルの直接埋め込みをCIで検知できるようにする

I18nでの多言語対応をする際は、日本語のリテラルを直接viewファイルに書かないことが必要です。プロダクト開始当初に多言語化を考慮していなかったがために、残念ながらこうした箇所が多く見られました。

単細胞な私は、いきなり日本語のリテラルをgrepして、YAMLに移す作業を始めようとしました。しかし、CTO氏はそれを止めました。

「まず、テストコードを書いて、問題のある該当箇所を洗い出す方が合理的ではないか」ということです。

そこで以下のようなRSpecによって、viewファイルに日本語リテラルが含まれていないことを検証するテストを書きました。

REGEX_JAPANESE = /[\p{Hiragana}\p{Katakana}\p{Han}]+/.freeze

context 'viewファイルの検証' do
  target_globs = [
    './app/views/**/*.slim'
  ]

  target_files = target_globs.flat_map { |pattern| Dir[pattern] }
  target_files.each do |target|
    it "#{target} に日本語リテラルが含まれていないこと" do
      File.open(target).each.with_index do |line, i|
        # コメント行を回避
        next if line.match? %r{^\s*[/#]}
        
        expect(line).not_to match(REGEX_JAPANESE), -> { "#{target}:#{i + 1} に日本語が含まれています" }
      end
    end
  end
end

行っていることは単純で、viewファイルを1行ずつ読み込んで、日本語の固定の文字列を直接埋め込んでいる箇所のファイル名と該当行をエラーとして返すというものです。

確かに、これによって我々のその後の作業効率が向上したというのもありますが、もう1つ重要な側面があることに気がつきました。

これが、CIに乗ることで、今後の開発者が追加開発をする際に新しく画面を作る際に、日本語リテラルを直接埋め込むことを防ぐことができるということです。

継続改善2: 専門用語リストを作って表記揺れをCIで防ぐ

上の対応によって、日本語リテラルの直接埋め込みを防ぐことができましたが、それだけでは不十分でした。

我々のプロダクトは、労務業務や年末調整の法律に関わる複雑な専門用語を多く含んでいます。単語の翻訳作業などは、翻訳会社様にお任せしましたが、それでも表記揺れが発生することはあります。

例えば「住宅ローン」は「Mortgage」とか「Housing loan」とか、いろいろな表記が考えられます。このような、表記揺れを防ぎたいという課題がありました。

上の対応で学んだ僕は、今回もCIを使って何かできるのではないかということで、以下のような用語に対する辞書を作成することになりました。

# spec/i18n/glossary.yml
- ja: クラウドハウス労務
  en: CloudHouse Workforce
- ja: 年末調整
  en: Year-end adjustment
- ja: 給与所得者の扶養控除等(異動)申告書
  en: Application for (Change in) Exemption for Dependents of Employment Income Earner
- ja: 源泉徴収票
  en: Withholding tax certificate
- ja: 源泉控除対象配偶者
  en: Spouse qualified for withholding deduction
- ja: 障害者控除
  en: Deductions for disabled person
- ja: 障害者手帳
  en: Disability certificate
- ja: 障害者区分
  en: Disability classification
- ja: 寡婦
  en: Widow

そして、以下のようなRSpecを書きました。

context '用語の表記揺れの検出' do
    dictionary = YAML.load_file('spec/i18n/glossary.yml')

    target_files = %w[]

    target_files.each do |target|
      locales = {}
      paths = {}
      %w[ja en].each do |lang|
        paths[lang] = "config/locales/#{lang}/#{target}.#{lang}.yml"
        flatten_locale(YAML.load_file(paths[lang])[lang]).each do |key, value|
          k = key.join('.')
          locales[k] ||= {}
          locales[k][lang] = value
        end
      end

      locales.each do |key, translations|
        describe "#{paths['en']} について" do
          dictionary.each do |dict_entry|
            next unless translations['ja'].include?(dict_entry['ja'])

            it "日本語の用語【#{dict_entry['ja']}】に対して、対応する英語【#{dict_entry['en']}】が含まれていること" do
              message = "#{key} #{translations}"
              expect(translations['en']&.downcase).to include(dict_entry['en'].downcase), message
            end
          end
        end

        describe "#{paths['ja']} について" do
          dictionary.each do |dict_entry|
            next unless translations['en'].include?(dict_entry['ja'])

            it "英語の用語【#{dict_entry['en']}】に対して、対応する日本語【#{dict_entry['ja']}】が含まれていること" do
              message = "#{key} #{translations}"
              expect(translations['ja']&.downcase).to include(dict_entry['ja'].downcase), message
            end
          end
        end
      end
    end
end

翻訳を管理するYAMLファイルを1行ずつ読み込み、各行に辞書登録した用語がある場合、対応する反対の言語のYAMLを参照し、辞書通りの翻訳がなされているかを検知するものです。

このように、CIに乗せることで、今後の開発者が追加開発をする際に、表記揺れを防ぐことができるということです。

さらに言うと、この辞書は、拡張して、育てていくことができるものになっています。したがって、プロダクトが複雑化する上で、チーム全体での共通言語を、多言語を跨いで確立していくという大きなテーマにも寄与できる可能性があります。

数行のテストでこのようなことまで考えるという経験は、自分にとって非常に大きな学びでした。

継続改善3: 環境によってあるべき振る舞いを分ける

時間が進んで、検証環境にデプロイする時がやってきます。大変だった実装が終わり、ウッキウキでデプロイをしようとしている私を、CTO氏が止めます。

「環境によってraiseするような設定を入れて、検証環境で検出しやすくしよう」

プロダクトの画面数は沢山あります。したがって、上のような対応をしていてもまだ対応漏れはあり得るものです。そして、漏れがある場合は、なるべく早く検知したいものです。

Railsにはconfigに以下のような設定が可能です。

# Raises error for missing translations.
config.i18n.raise_on_missing_translations = true

こうすると、画面をレンダリングする際に、翻訳が見つからない場合にエラーを発生させることができます。この設定がfalseの場合には、フォールバックされて画面上にはYAMLのキーが文字列としてそのまま表示されます。

本番環境では、翻訳漏れ1つでエラーを発生させるのは避けたいです。一方で検証環境では、翻訳漏れを早く検知することが重要であり、目立たない場所に未翻訳の文言が表示されるなどして漏れが見落とされる可能性があります。

したがって、開発環境と検証環境ではこの設定を有効化し、本番では有効化しないという対応をしました。実際にそれによって検証環境で2箇所ほど、対応漏れを検知でき、早めの修正ができました。

蛇足

Rails7.0では、

Model.name.human # モデル名
Model.human_attribute_name(:column_name) # カラム名

のようなActiveModelのメソッドを使って間接的にI18n.translationを呼び出す場合に上記の設定が適応されませんが、Railsの最新のmainブランチでは、このような呼び出しに対しても有効になるような修正が施されていました。
なので、それを参考にしたモンキーパッチを施しました。あと2ヶ月早かったら、Railsへのコントリビュートのチャンスだったかもしれません…

まとめ

ペアプロをすることで、開発における様々なスキルを向上させることができました。自分自身最も成長できた数ヶ月だったような気がします。

10月に、ペアを解消して、CTO氏は別の僕の同期とペアプロをすることになりました。(涙が止まりません)

そのタイミングで振り返りを行いました。

僕の取り組んでいた内容は、ドメイン知識に依存する部分や、過去の負債解消の部分が多く、初めはペアプロの真の力が発揮できないのではないかと心配していました。
しかし、そんなことは全くなく、CTO氏とのペアプロを通じて、自分は成長を実感できました。

同じタイミングで別のメンバーとペアプロをしていた僕の1年先輩のメンバーは、Hotwireを新しく導入するような取り組みをしていました。僕とは真逆に新しい技術の導入に取り組んでいましたが、僕とは全く別の角度から成長を実感していました。

多様なフェーズで、多様な成長の可能性があるのも、ペアプロの魅力なのではないかと感じました。

今後も、この経験を活かした良い開発ができるように精進して参りたいと思っています。ありがとう!CTO氏!

明日のTechouse Advent Calendar 2024は Higashiji さんによる Ruby のファイル操作で覚えておきたいバッファリングと flush / fsync です。


Techouseでは、社会課題の解決に一緒に取り組むエンジニアを募集しております。 ご応募お待ちしております。

jp.techouse.com