最近、あまりプログラミングが得意でない人のサポートをする形で、長い時間にわたってペアプログラミングを行っている。そのなかで、気がついた悪い習慣と成長するための良い習慣というものをまとめてみる。
この記事のバックグラウンドとなる体系的知識が本になりました。
エンジニアリング組織論への招待
~不確実性に向き合う思考と組織のリファクタリング
あわせて読みたい
- 経営者マインドが足りない!vs. 現場に任せてくれない!の対立をなくすカードゲームをつくった話
- 新人プログラマに知ってもらいたいメソッドを読みやすく維持するいくつかの原則
- 新人プログラマに知っておいてもらいたい人類がオブジェクト指向を手に入れるまでの軌跡
- ペアプログラミングして気がついた新人プログラマの成長を阻害する悪習
-
あきらめるにはまだ早い!ソースコードの品質向上に効果的なアプローチ
心理的安全性ガイドライン(あるいは権威勾配に関する一考察)
メンタリングの方法について基礎をまとめました。ペアプロの進め方としてもご一読を。 -
新入社員が来てメンターになれって言われたけど、どうすればいいのかという対話テクニック
*半年で40kg痩せた!ダイエットでわかるリーンなプロジェクトマネジメント手法
習慣の力
常々、プログラミングの力を決めているものはなにかということについて気になっていた。学習効率が良い人も入れば、悪い人もいる。これらをセンスと言って切り捨ててしまうのは簡単だが、頭の善し悪しとは別にこれらを決める要素があるのではないかと考えていた。
数年前のあるとき、隣の同僚の画面を眺めていて、あるいはオフィスの様々なエンジニアを見ていてはっと気がついた。なぜかアウトプットのスピードが低い人ほど、「Webアプリケーションフレームワークの出力するスタックトレース画面」が表示されている傾向がある気がしたのだ。
これは統計を取っている訳でもないので、はっきりしたことは言えないのだが、その隣の同僚の開発サイクルは次のようになっていた:
- コードを書く
- コードを保存する
- 画面をブラウザに切り替える
- リロードする
- エラー画面が出力される
- すぐにコードの画面に戻る
これでは、非常に効率が悪いように思った。彼が書いているのはモデル層に近い部分の実装であったこともある(当然のようにコントローラにその処理は書いてあったが。)。
すぐさま、私は「シンタクスチェックの仕方」と「画面上でのモジュールの実行」の仕方を教えた。また、それを利用した「小さなテストコードの書き方」も教えた。ところが、彼はその方法は知っていたのだ。しかし、むしろめんどくさいことのように感じていた。なぜかと問うと応えには窮していたが、要するに「最終的に画面が表示されるのだから画面を見た方が完成に近いだろう」と言うことだった。
これは習慣の問題だった。当時、私たちのシステムにCIはなかった。多くのエンジニアはテストを書く習慣がなかった。
その後、CIは導入され、テストコードを書くことが必須となっていったあとになると彼はブラウザ上で動作するのを確認したあとに、めんどくさそうにテストを書いていた。
確認のサイクルが長くなり、結合されたあとであるので問題は複雑化した形でしか、エラーメッセージは出力されていなかった。あまり読んでいなかったので気にならなかったかもしれないが。
単体でのテストを意識せずに書くからか、コードの結合度は高く、テストの難易度も上がっていった。
生産性をサポートするためのシステムであっても、悪習によって生産性を下げる結果となってしまっていた。
悪い習慣
最近、数名の若手のエンジニアとペアプログラミングをしている。
一日で数時間規模と比較的長い時間触れている。
その中で指摘したり、発見してきた悪習を紹介する。
コードリーディングの比率が低い
プログラミングをする中で、プロジェクトの種類にもよるが、コードリーディングの占める割合は高い傾向がある。ものによっては8割読み、2割書くといった具合だ。フレームワークや周辺コード、必要な箇所を正しく理解するのにコードリーディングは必要となる。
しかし、アウトプットが滞るエンジニアの場合、ソースを読むという行為の比率が著しく低い。
例としては、フレームワークのある機能を使おうとしているのだが、その結果が正しく動作していない。フレームワーク側のエラーメッセージは正しくない入力である旨を伝えていた。
ところが、彼は、フレームワークの該当箇所を読むことをしなかった。
では、何をしているのかというと
- 自分の書いたところを眺めて、ミスがないか漠然と探す
- 正しく動作していそうなコピペ元コードを漠然と見る
ということをしていた。そしてわからなくなると、自分にこれの何が間違っているかと聞くというスタイルだった。
改善方法
フレームワークの該当するコードを探す方法を教え、探せるようにした。そこで、どのような処理をしているかを読んでもらい、なぜ自分のコードが動かないか確かめられるようにした。
心理的な問題
なぜ、フレームワークのコードを調べないのか、問うたところ、「むずかしそうだから。早く終わらせたいから」ということらしかった。確かにフレームワークは複雑化していたり、全体を把握するのが難しいところはある。
しかし、それを読んでいくなかで、言語機能やライブラリを理解したり、知識が広がっていく。
この心理的抵抗を取っ払いコードを読んでいくことで、問題が早く解決するという経験をさせることが重要だ。
なので、メンターとなる人は、「これは理解しなくてもいい」であるとか、「読まないでもとにかく動かせ」というような指導をしないことも重要である。
コードを深追いする深さが、プログラマの成長限界をどこにしてしまうかという基本的なパラメータではないかと私は思っている。
難しい処理をテスト可能に切り出さない
新人プログラマに知ってもらいたいメソッドを読みやすく維持するいくつかの原則
こちらの記事でも書いたが、関数の分割というのは重要なテクニックだ。なぜ、分割するのかと言えば、「その方が簡単になるから」であるのだが、練度の低いプログラマほど「その方が難しい」と感じる傾向がある。
たとえば、
function process(list){
for(var i=0,l=list.length;i<l;i++){
:
:
// ここに処理をたしたい
}
:
:
// ここに処理を足したい
}
このような関数があったときに、それぞれ処理を追加したいとする。
練度の低いプログラマほど、「処理をしたいところにそのまま処理を足す」傾向がある。
本来であれば、
function process(list){
var next = []:
for(var i=0,l=list.length;i<l;i++){
:
var elem = doSomethingForElement(list[i]);
if ( elem ) {
next.push(elem):
}
}
:
:
if( next.length == 0 ){
return doSomethingWhenNoResponse(args);
}
:
}
function doSomethingForElement(){
// ここに追加したい処理
}
function doSomethingWhenNoResponse(){
// ここに追加したい処理
}
このように追加したい処理を元の関数から独立した形で、切り出す。(スプラウトメソッド)
こうすれば、元の関数の複雑さから逃れ、追加したい処理を独立してテストできるようになるはずなのだ。
改善策
元の関数自体に手を加える前に、やりたい処理のみを切り出した関数を埋め込み、その後、その関数を実装しテストするというサイクルを身につけさせ、結果的にその方がはやく問題解決することを体験させる。その際に、追加したい処理の入出力はなにか、副作用はなにか、を意識させる。
心理的問題
関数の切り出しをむずかしいと感じる心理の根底には「はやく終わらせたい」と焦る気持ちがあるようだ。該当箇所を見つけたのだから、そこになにかをうまく埋め込めば、うまく行くはずだ。だから切り離すのは「難しいこと」で、「余裕があるからすること」だと思っている。
だが、実際には結合した状態でコードの実行を繰り返し、コードの把握が難しい状態のままトライ&エラーを行ってしまい、難易度の高い状態の課題を解きつづけてしまうほうが、時間のかかる難しいことになってしまう。
算数の問題でいうところの補助線のようなもので、その補助線の引き方を教えることが大事だ。
小さなサイクルの確認をしない
一番最初の習慣の力の例でもあったように、できる限りの高速な確認サイクルを手に入れることが重要だ。つまり最終的には自動化されていることでもある。
ペアプロをして初めて、彼はコードを書いた後に「まじまじとコードを眺めている」ことに気がついた。何をしているのかよくわからなかったが、聞いてみると「確認しています」と言った。何か間違いがないか書いたコードを一行ずつ見ている。
とりあえず、シンタクスチェックしたら?と言うと「シンタクスチェックってなんですか?」と答えた。そのやり方を教えてから、しばらくしても「大丈夫ですかね。」「あってますか?」と聞いてきた。
私はとりあえず、確認してみなよと言ったのだが、何のことだか理解していないようだった。
テストと結合された動作確認以外の確認というものが習慣づいていないのだと、そのとき初めて気がついた。Unitテストは、恒真性を持つように設計しなければ行けないため、多少複雑な準備が必要になったり、テストファーストの実践者でなければ、作りはじめるフェーズが遅かったりする。
改善策
シンタクスチェックを行う処理をエディタに登録し、適宜実行させる。その後、必要であれば、保存時に自動的に動くようにする。
エディタがvimであったので、quickrunやsyntasticのようなプラグインを入れ、適宜実行させるようにする。
https://github.com/thinca/vim-quickrun
https://github.com/scrooloose/syntastic
どこでもいいので、そのクラス・関数単体で実行させる環境を作り、動作を確認する。
正常な動作をいくつか確認したら、それをテストファイルに移す。
という流れを習慣づける。
「これでいいか?」と自分に確認する前にそれらのフェーズを遂行するように指導する。
なれてきたら、デバッガの使い方やfswatchなどによる小さなCI環境をつくり、保存のたびに実行するようにさせるのがいいだろう。
心理的問題
こういった環境面の問題は、知識の話であって心理的な問題は関係ないように思うかもしれないが、細かく見ていくと「正しいコード」と「なぜかわからないエラー」の2値的な問題の捉え方をしているという部分が見え隠れする。
実際のところ、唯一無二の正しいコードというのがあるわけではない。
しかし、これはコピペを多用している人にありがちな考え方のようで、コピペしても動かないのはなにか間違ったことをしていて、正確にコピペできていないところがないかを探すというプログラミングサイクルが習慣化していることが背景にはあるのではないかと感じられた。
そのため、一度書き終わったコードは記号の羅列のように見えてしまい、そこに何か間違いがないか目で探すのだ。自分で書いたものもコピペで始まっているため、それは記号の羅列であって、理解していないものになってしまう。
一行一行理解をしながら確かめていくことは、コピペより効率の悪いことのように思えてしまう。
エラーメッセージ・ログを読まない
プログラミング言語のエラーメッセージや、ライブラリの出力するエラーメッセージは問題箇所を特定するために、人間が人間のために記述した情報だ。ところが、成長しづらいプログラマはそれを読まない。何か問題があったという情報として2値化してとらえてしまう。
IDEであれば、せめて、該当モジュールの該当箇所まですぐさまジャンプできる。これが生のvimやWebブラウザ経由で出力されたログメッセージであれば、理解してjumpするのは自分の仕事になる。
読まない、該当箇所にジャンプしない、そのためエラー画面を表示する時間が極端に短い。ペアプロしていて、すごいスピードでエラー画面が消えるので、動体視力を試されているのかと思ったが、どうやら彼は読んでいないようだった。
その結果何をしているのかというと、自分の先ほど書いたところをまじまじと見つめている。typoがないかどうか、メソッド名のところを行ったり来たりしている。
しかし、そのとき出ているメッセージは、存在しないメソッドをたたいたことによるメッセージではなかった。
エラーメッセージを読まない場合、バグの発生箇所の可能性は無限に存在しうる。しかし、自分が書いた何かが「間違えた」のだから直さなくてはと思ったのだと言う。
改善策
ともにエラーメッセージを読み、意味をひとつひとつ確認する。
そして、エラーメッセージと問題の対応関係を理解させる。また、それを引き起こす最小のsnippetを作り、様々なケースについて想定させる。
心理的問題
「英語は怖いので記号にしか見えない。」といっているが、中学高校レベルの英語しかエラーメッセージには使われていないし、中学高校レベルの英語は彼にでも理解できる。
実のところ、言語のエラーやフレームワークのエラーは、言語やフレームワークの用語や構造を理解していないと正しく意味をとらえることが難しい。
それは、言語が不親切だからとかフレームワークが不親切だからではなく、エラーメッセージは「実装上の問題自身」を表してはいないからだ。
なので、フレームワーク、言語の知識、そして実装の追跡、
それらをして初めて問題とエラーを結びつけることができる。
その行程を自分自身でできるようになるまで、習慣づけなくてはいつまでたってもエラーは記号のままだ。
問題の探索の仕方を知らない
たとえば、エラーが発生したときにそのエラーメッセージに書かれた該当箇所で初めて問題が露見したとする。
それが発生するということは、少なくともそこまではコードが動作したということでもある。
また、エラーが発生したということは、自分が入力したものが影響し、そのことが起きている可能性が高い。
たとえば、printfデバッグであれば、何がモジュールに入力されて、どのように変化し、エラーを引き起こしたのかを調べることで、問題の概要を理解し、コールスタックを順番にあがっていくことで、問題箇所の特定をすることができる。
しかし、探索の仕方を知らない場合、正常に動いているだろうと予想されるところから確認をはじめたり、コールスタックの順序関係を把握していない状態で闇雲に情報の出力をする。
場合によっては、問題箇所がわからずに「立ち尽くしてしまう」。問題を特定する戦略を持たないといとも簡単に「はまる」という現象に入ってしまう。
改善策
問題が発生してから、行動し始める前に「さて、どうしようか」と問題特定の戦略を話してもらう。そのために必要な情報収集をどのようにするか、現時点でどこまでは正常だろうか、など質問をしていく中で戦略を立てさせる。また、情報を一つ得たら、これはどういう意味があるか?戦略は変わるか?などを意識させて、闇雲な探索ではなくしっかりと意志を持った探索に誘導していく。
心理的問題
プログラムのエラーが発生したということは、本来「完成に近づいた」一つの証跡であるはずだ。なぜなら、今まで意識されていなかった情報が一つ明らかになり、間違った仮説をたててしまっていた部分が棄却されて、それを修正するという明確なネクストアクションが手に入るからだ。
しかし、多かれ少なかれ、問題の探索が得意でないプログラマは、自分で引き起こしたエラーメッセージに対して「パニック」を起こす傾向がある。「きっと動くはず」なのに動いてくれない。やばい「間違った」と思うのである。
その一つの例としては、「失敗するはずのテストをなかなか動かそうとしない」という現象としても現れる。テスト最大の価値は、それが失敗して情報を提供してくれるところにある。
「エラーになりますよ」と言ってなかなか動かさないのだが、それでも動かすように言うと実際に動作せずに「ほらね」と言った具合だ。
実際には、本番のエラーはさておき、開発環境上でのエラーなんて起きたところで誰も困らない。むしろ有益なことだ。
このあたりをしっかり体験させて理解させることが有益だろう。
良い習慣
今までの悪習をひっくり返していくことで、良い習慣を得ることができる。
- よくコードを読み、理解することに時間をかける
- 常に目の前の問題を単純化することに知恵を使う
- エラーを恐れず、重要な情報として理解するために最小ケースを作る
- 問題の追跡のために明確な行動指針をたてる
他にもいくつか、良い習慣だと思うことを並べると
- 道具の改善および自動化をおこなう
- ボーイスカウトポリシー
- 複数の言語を学ぶ
- 体系的な知識を学ぶ
というところだろうか。
道具の改善および自動化をおこなう
良い習慣は良い道具によって定着しやすくなる。たとえば、私は新しい言語を書くときにできるかぎり、標準的な出力をするコードフォーマッタを探して組み込む。
IDEであれば、自然と必要なスペースなどは補ってくれる。コーディングスタイルを調整するのに貴重なキーストロークを使いたくない。
ボーイスカウトポリシー
通りかかったところにあるゴミは拾うというのがボーイスカウトのポリシーだ。これになぞらえて、利用した周辺コードのゴミやエラーは修正していくというポリシーだ。しっかりと読まなければコードの修正はできないが、いずれにせよ必要な行程であるので、その際に警告などは修正してしまう。
これをやっていくと、自然とより理解が深まるし、全体的なコードもきれいになる。
複数の言語を学ぶ
一つの言語だけを学んでも、理解しづらいポイントは複数の言語を学ぶことで理解が進む。新しい概念も他の言語から取り入れたものにすぎない場合が多いので、習熟が早くなる。
体系的知識を身につける
新人プログラマに知っておいてもらいたい人類がオブジェクト指向を手に入れるまでの軌跡
こちらの記事で書いたように、背景や歴史、その時々の課題などを知っていくことで、プログラミングの質も変わってくる。
知識についておいしいところをつまみ食いしようとしたり、文法やパラダイムについての理解を自分なりに整理できていない場合、知識ではなく、tipsになってしまい応用が利かず、無限にtipsを追い求めてしまう。
さいごに
能力は習慣の積分であると思うので、習慣を改善することで能力の向上をすることができるだろうというのが私の考えだ。
そして、それらには心理的な障壁があり、結果として習慣の改善が進まないことも往々にしてあるだろうし、習慣が改善しても一足飛びにスーパーエンジニアになれる訳ではない。
それが無力感ではなく、新たな学びを得る道中を楽しいと思えるかどうかが最も結果を左右するのだろう。