こんにちは、ACS事業部の髙井です。先日、3か月の育休から復帰しました。
3か月も席を空けたのに復帰直後から育休前となんら変わらぬトーンで接してもらってます(これが人権か)。おかげさまで次女のみならず長女も前より自分に懐いた気がします。
さて、今回はタイトルにもある通りペアプログラミング、いわゆるペアプロの体験記です。 開発現場だと特段めずらしくもない営みですが、インフラエンジニアだとあまりやる機会が多くないと思います。
しかし、実際にインフラエンジニアの立場でやってみても得るものが多かったので、この流れを広めるべくブログにしました。
ちなみに私はインフラエンジニアでありつつもプログラミングは好きで、仕事以外に個人開発もやっていたりするのですが、ペアプロはやったことがありませんでした。
それでは、開発者もすなるペアプロといふものをインフラエンジニアもしてみむとてするなり。
ペアプロとは
ペアプロとは、ペアプログラミングという名前からも分かるとおり、ペアになってプログラミングをする営みです。
もちろん、その2人が協調せず各々が好き放題にプログラミングをしていても何の意味もないので、ペアプロには役割分担があります。「ドライバ」と「ナビゲータ」です。
実際にキーボードを操作してコードを書くのが「ドライバ」で、ドライバの書いたコードをリアルタイムでレビューしながらヒントを与えるなどして完成まで導く役を「ナビゲータ」と呼びます。
一般的に、ペアプログラミングの生産性は1人で作業した場合の2倍以上、要するにペアプロしたほうが各自で書くより生産性が高いと言われています。
なんでやろうと思ったか
きっかけは取締役のアドバイスでした。
エンジニアとしてさらなる成長を遂げたいと思ったとき、たとえばプリセールスのようなセールス・マーケティング能力であったり、PMのようなマネジメント能力であったり、純粋な技術のほかにも鍛え上げるべきポイントがたくさんあります。
しかし、これまであまり経験のない種類の業務をこなそうと思うと、やはり効率が悪いです。
そもそも経験がないのでうまく見通しが立ちません。どうしても尻込みしてしまいます。人間も生き物なので本能的に未知への恐怖を抱くようにできています。
そんなとき取締役が「ペアプロみたいにやって鍛えればいいんじゃない?」とアドバイスをくれたというわけです。
私は思いました。
なるほど完璧な作戦っスねーーーっ
私がペアプロ未経験だという点に目をつぶれば
ということで、チーム内でペアプロを経験したことのある方々に教えを乞うたのでした。
FizzBuzzを書こう
ということでペアプロの師匠を2人召喚しました。KさんとWさんです。結局ペアじゃなくて3人になったのでモブプロっぽくなってきましたが、まあよしとします。
ちなみにKさんとWさん、普段は業務でプロダクト開発をされている方々です。
では、ここからは臨場感を出すために対話形式で書いていきます(初の試み)。
~オンラインミーティング開始~
「先生……!ペアプロがしたいです……」
「ナビゲータとドライバがありますよ」
「指示とか大変そうなんでまずはドライバやりたいです」
「やはりですか……俺ペアプロやるときいつもナビゲータなんだよなあ……たまには俺にキーボードを叩かせてくれよ……」
「知らずに空気読んじゃいましたわ」
「今回は体験だし、題材は簡単なほうがいいかな」
「じゃFizzBuzzでいいですかね、言語はどうしますかね」
「指定の言語で書きますよ、でも毎度コンパイルするの面倒なんでインタプリタ言語がいいですかね」
「君はずいぶんとカッコイイことを言うね、まあでもJSにしておきましょうか」
「どうすればいいですか」
「まずはエディタを開いて、1から100まで標準出力するコードを書こう」
「ふん、容易い……じゃなかった、愚直に書いちゃいますね」
const main = () => { for (let i = 1; i <= 100; i++) console.log(i); };
「では実行!……あれ何も出ない。まさかこんなところで詰まるとは、この私が?」
(十数秒後)「あ、main関数を定義だけして実行してないですね」
「こういうとき、詰まる時間が長いようだったらナビゲータが助言するわけだね」
「次に、条件分けを書いていきましょうか」
「いろんな実装方針がありますね、好きに書いちゃっていいですか?」
「まずは書いてみてください」
const main = () => { for (let i = 1; i <= 100; i++) { let str = ''; if (i % 3 === 0) str += 'fizz'; if (i % 5 === 0) str += 'buzz'; if (str === '') str = i; console.log(str); } } main();
「なるほど、この実装にした理由はありますか?」
「if-elseは読みにくいので避けたくて、15で割れるときにfizzbuzzにするみたいなロジックは同じようなこと何度も書くのが嫌なのでこうしました」
「たとえばstr
の比較ってstr === ''
が最適でしょうか?何を重視するかによりますけど」
「あー、短く書くことを重視するならif (!str)
のほうがよかったかもですね」
「str.length === 0
とかもありますし、観点によってベストな書き方は変わります」
「こうやって自分一人では見逃してしまったり、あまり深く考えないところもリアルタイムでレビューが進むのがペアプロですね」
「ペアプロが終わった段階でPRのレビューが済んでいるのと同じ状態になるから同期的なコミュニケーションにはなるけどトータル時間の圧縮にはつながったりする」
「時間がかかるようでトータルでは得るものが多そうですね、教育的な観点からもよさそうです」
FizzBuzzを改良しよう
「じゃあ、ガラッと実装方針を変えてみようか」
「for文を使わずに書くとかですか?」
「そうですね、とりあえず書いてみてください」
// 関数に切り出し const solve = (i) => { let str = ""; if (i % 3 === 0) str += "fizz"; if (i % 5 === 0) str += "buzz"; if (!str) str = i; console.log(str); }; // 変更 const main = () => { [...Array(100).keys()].forEach((i) => solve(i + 1)); }; main();
「うーん、いまいちイケてないですね。.map(i => i + 1)
をはさんで1~100の配列を得たいですが、配列が無駄に生成されるのが気持ち悪い……」
「副作用のある処理にmapを使わずforEachを選択しているのはいいですね」
「配列の生成って観点だとそもそも[...Array(100).keys()]
だと最初にメモリ確保しちゃうんだよね、1000万個とか確保したくないよね」
「それはたしかにそうですね」
さらなる深みへ……
「ならば遅延評価にしますか。なーに、関数型言語も書ける私に遅延評価など容易いですよ」
(また調子に乗ってる……)
// 追加 const range = (start = 0, end, step = 1) => { let i = start; return { next: () => { let res = i; if (i >= end) return undefined; i += step; return res; }, }; }; const solve = (i) => { let str = ""; if (i % 3 === 0) str += "fizz"; if (i % 5 === 0) str += "buzz"; if (!str) str = i; console.log(str); }; // 変更 const main = () => { const rg = range(1, 101); while ((i = rg.next())) solve(i); }; main();
「どうですか、素晴らしいでしょう」
「なるほどねー、これならrange
のなかで重い処理があっても逐次的に処理できるからいいね」
「fizzbuzzからは離れてしまいますが、それも模してみましょうか」
「そうですね、ではsleep
を定義してやってみます」
// 追加 const sleep = async (ms) => new Promise((resolve) => setTimeout(resolve, ms)); const range = (start = 0, end, step = 1) => { let i = start; return { next: async () => { // 非同期化 await sleep(1000); // 追加 let res = i; if (i >= end) return undefined; i += step; return res; }, }; }; const solve = (i) => { let str = ""; if (i % 3 === 0) str += "fizz"; if (i % 5 === 0) str += "buzz"; if (!str) str = i; console.log(str); }; const main = async () => { // 非同期化 const rg = range(1, 101); while ((i = await rg.next())) solve(i); // 変更 }; main();
(できた……やはり俺は天才……この世の全てを掌握するにふさわしい男……)
「while ((i = await rg.next()))
のヨーダ記法はどうにかならないかな」
「ぐぬぬ!まあ冗長に書けば消せますが……あ、そういえばjsってジェネレーターの構文があった気がしますね」
「そんなものもあったっけね、せっかくだし非同期ジェネレーターでrangeを再実装してみたら?」
(fizzbuzzはどこに……)
const sleep = async (ms) => new Promise((resolve) => setTimeout(resolve, ms)); // 変更 async function* range(start = 0, end, step = 1) { for (let i = start; i < end; i += step) { await sleep(1000); yield i; } } const solve = (i) => { let str = ""; if (i % 3 === 0) str += "fizz"; if (i % 5 === 0) str += "buzz"; if (!str) str = i; console.log(str); }; const main = async () => { const rg = range(1, 101); for await (const i of rg) solve(i); // 変更 }; main();
「ジェネレーター構文なんて存在自体を忘れてましたけど、while
がfor await ... of
で書き直せてスッキリしましたね、うーんただのfizzbuzzが非同期ジェネレーターまで発展するとは……」
「こんな感じでペアプロをやるといろいろな気付きや、よりよいコードのために深く考えることにつながるのがわかったのではないでしょうか」
「しかもまだまだある。このコードで例外はどこでキャッチするかとか、さらに考えられるポイントがあったりするよ」
ペア「プロ」だけじゃない
「いやー、時間を割いていただきありがとうございました、おかげでペアプロのなんたるかが分かった気がしました」
「それこそ、こうやって対話しながら思考を深めていくのはプログラミングに限らずできることですよね」
「それこそプリセールスが書く提案書の作成とか、そういうシーンでも同じような営みができると思うよ」
「たしかに!今度から慣れない業務はできる人にお願いしてペアプロチックにやってみようと思います」
ペアプロは楽しい
リモート主体で働いてると打ち合わせ以外なかなか同期コミュニケーションを取る機会がないので、雑談……ではないですが、打ち合わせよりもフランクにコミュニケーションが取れるペアプロという形式は非常によいと感じました。
たとえば、上の対話形式では私がけっこうふざけ倒していますが、実際にほぼこれに近く、和気あいあいと体験しています。シンプルに楽しいですね。
そして、ナビゲータが作成過程を見ながらレビューできるので、思考過程や抜けている観点を伝えられますし、終わったときにはレビューが完了している状態になっているのもよいです。
まさに「息抜きしつつ仕事も進む」といういい感じの営みであることがわかりましたので、積極的に取り入れていこうと思います。
おわりに
インフラエンジニアのペアプロ(モブプロ)体験記を紹介しました。
教育的観点で非常によい営みだと感じたので、ぜひ開発現場だけのものと捉えずに試してみていただければと思います。