はじめに
先日ソースコードはしゃべるように書けという記事を公開しました。
その続編というわけではありませんが、僕が新人のころに先輩からよく言われた言葉がもうひとつありますので、前回に引き続きご紹介したいと思います。(前回の記事の先輩とは別の方ですが、この方も辞め次のステップに(ry )
それが、__「ソースコードの一行一行に意味を込めろ」__です。
この記事では、「ソースコードの一行一行に意味を込めろ」という言葉の意図について考えていきたいと思います。
本文
同じ処理をするための書き方はいくらでもある
「ソースコードの一行一行に意味を込めろ」という言葉は、わかりやすくいうと「すべての行について、なぜそのように書いたのかを自分の言葉で説明できるよう考えながらコーディングしろ」ということです。
例えば、前回のソースコードを少し拝借して、「記事一覧を20件を上限にAPIから取得してそのタイトルを出力する」という処理を考えてみます。
public static void main(String args[]) {
Main main = new Main();
List<Article> articleList = main.requestArticleList(20);
main.printTitles(articleList);
}
private List<Article> requestArticleList(int limit) {
return new ArrayList<Article>();
}
private void printTitles(List<Article> articleList) {
}
この処理は、以下のように書く事も可能です。
public static void main(String args[]) {
Main main = new Main();
int limit = 20; // 記事の上限は20件
List<Article> articleList = main.requestArticleList(limit);
main.printTitles(articleList);
}
private List<Article> requestArticleList(int limit) {
return new ArrayList<Article>();
}
private void printTitles(List<Article> articleList) {
}
変更点は取得上限の20
を、事前に変数で宣言するようにしたことだけです。
「たったこれだけ?どっちでもいいじゃん」と思った方は、ぜひ以下の説明を読みながら考えてみていただければと思います。
int limit = 20;
を先に宣言することで、どのような影響があるのでしょうか。そのメリットとデメリットに分けて考えてみたいと思います。
-
メリット
-
20
が示すものが取得上限数であることがmain
メソッドの中の処理を見るだけでわかりやすくなる
-
-
デメリット
-
limit
はそのメソッドが終了するまでは参照可能なため、今後処理が長くなった時にそのlimit=20
がどこで、どのような用途で使われているのか見通しづらい。
-
メリット・デメリットがそれぞれひとつずつありますね。ではどちらの書き方が良いのでしょうか。
結論から言うと、僕は前者(変数宣言しない方)を採用します。理由は以下です。
まず、メリットである「20の意味が分かりやすい」について、確かにその通りではあるのですが、それはrequestArticleList
のシグネチャを見ればすぐにわかる話だったりします。そして、シグネチャはIDEによってはマウスカーソルを当てるだけで見ることができますので、シグネチャが分かりやすく書かれている限り確認のコストはほとんどありません。
つまり、「20
ってなんの数字だろう?」の答えを分かりやすく伝える手段として、変数で一度宣言することはあまり大きなメリットにはならないのです。
一方で、デメリットの方に目を向けてみます。
値を一度変数で宣言することは、特に1行の長さを短くする目的でない限り「この変数はきっと2回以上使われているのだな」という推測を読む側にさせてしまう、という影響があります。
すると、ひとたび上限数に仕様変更があって20
ではなく30
になった場合、requestArticleList
の行だけでなくmain
メソッド全体を一応検索しなければならない、という手間が発生します。
上の例は値型なので検索も楽ですが、もしこれが参照型(オブジェクト)だった場合、main
メソッド内だけでなく、その参照値を引数として渡している可能性と、実際に渡されていた場合にその先までを全部調べなければならい、という手間が発生します。そうなった時の調査コストはとても重いものになってしまいますので、極力細かな変数宣言は減らし、メソッドに直接値を書く、というのが上記の例で僕が出した結論です。
たった1行2行の違いではありますが、このようなことを考えた上で最終的にコミットするコードを書くことが、「ソースコードの一行一行に意味を込めろ」の例になります。
デザイナーの仕事に学ぶ
少し話は変わって、「ソースコードの一行一行に意味を込める」ことを考えるとき、勉強になるのがデザイナーの仕事だと考えています。
例えばデザイナーが企業のロゴを制作する、という仕事を考えた場合、彼らの仕事は「カッコよくてオシャレなロゴを作る」ではありません。その企業の強みを理解し、消費者のその企業に対する現在のイメージを理解し、その企業がこれから発信したいメッセージを理解した上で、そのメッセージを正しく直感的に消費者に伝えるロゴを制作することが彼らの仕事になります。場合によってはエンジニアや企画・営業担当のように「ロゴを作ることでこの数値をここまで上げる」というような数値目標がある場合もあるかもしれません。
そして、その目的を達成するためには「ロゴに含める要素はこれで、色はこれで、このフォントを使って、なぜなら消費者の現状の認識はこうで、それをこう変えたいから」、というように理詰めで内容を検討していきます。そして、完成させる際にはそのすべての決定事項に対して理由を説明できることが求められます。
「なぜそのロゴデザインにしたのか」をロゴの納品先に説明できなければ、消費者という、直接合って説明することすらできない人たちにその意図を伝えることは不可能である、という考え方なのでしょう。
例えばGoogleのロゴを見てみましょう。
このトップページのロゴを見ていると、いくつも疑問がわき上がってきます。
- なぜ青、赤、黄、緑の4色なのか
- なぜ"G"と"g", "o"と"e"が同じ色なのか
- なぜこの文字幅なのか
- なぜ最後の"e"が少し傾いているのか
- なぜ「日本」という文字はこの位置なのか
- なぜこのフォントなのか
などなど、一度疑問の目で見始めたらいくらでも質問が出てきます。
そしておそらく、このロゴをデザインしたデザイナーはそのすべての質問に対して論理的に「このような効果を狙ってこのようにした」を答えられるのではないかと推測しています。少なくとも、「なんとなく良さそうだったから」とはならないでしょう。
我々エンジニアも同様に、自分のソースコードに対して「なぜこのように書いたのか」を人に論理的に説明できることが、良いシステムを作り上げる上で求められるのではないかと思います。
なぜ一行一行に意味を込める必要があるのか
ここまで書くと、「いやいやそんなに神経質にならなくても仕様通り動いてテストが通ればいいじゃん。そんなゆっくり考えている時間もないしね」という意見が聞こえてくるような気がします。
たしかに、時間がないというのは仕事においてよくある状況です。そんな中でも一行一行ちゃんと説明できなければリリースできない、とは僕も思いません。
しかし、可能な限り、一行一行に意味を込めてコーディングすることは以下の2つの点でメリットがあります。
1. レビュワーや後でメンテナンスする人がとても助かる
「そのコードをなぜそう書いたのか」がちゃんと考えられており、説明できるようになっていると、まず最初にレビュワーが助かります。そのコードの妥当性について判断するとき、「この行、どんなことに気をつけて書いた?」と聞けばどこまでの事情が考慮されていて、どのような懸念点が残っていて、どのような工夫をして最終的にそのコードになったのかがスムーズに共有できるからです。
レビュワーもソースコードだけを見てすべてを判断するなんて無理な話ですので、先に検討した内容を共有されることで、そのコードの意図をいろいろ推測して時間を無駄にしてしまったり、推測が外れて不要な認識齟齬を生んでしまったり、というリスクを回避することができます。
逆に、「この行、どんなことに気をつけて書いた?」と聞かれて、「いや、これで動いたからこう書いてあるだけです。。。」と答えられてしまうと、そう書くことの懸念点や改善方法などをレビュワーも一緒になって考えなくてはならなくなり、時間がかかってしまいます。
また、後でメンテナンスする人にとっても、ちゃんと考えられて書かれたソースコードは助けになります。
考えた内容をコメントや資料などで残しておいてくれれば、後々その処理を拡張したり、不具合が見つかって修正したりする場合に、「こういう事情があってこう書かれてるのか、じゃあ今回はこの部分が変更になったからこう修正して、とすると前回懸念として残っていた問題が表面化するからここはなんとかしなければ」と考えを進められ、修正方法の妥当性が正確に判断できるようになります。
最悪、コメントや資料が残っておらず、その人もその場(会社)にはいなかったとしても、似たような処理をしている部分との比較やコミット履歴、考え得る他の書き方との比較から「なぜそう書くのが妥当と判断されたか」をある程度推測できる場合が少なくありません。
その処理を知らない他の人の仕事を助ける意味でも、可能な限り一行一行に意味を込めて書くことは有効です。
2. 自分の成長になる
2つ目のメリットは、考えて書く事自体が自分の成長につながる、ということです。
長いこと同じような部署で同じようなプラットフォームを担当し続けていると、経験の蓄積に伴って「あ、この処理以前やったのと同じだ、じゃあ同じように書いておくか」となってしまい、別の書き方とそのメリット・デメリットを考える、ということがおろそかになってしまいがちです。(僕だけかもしれませんが、、、)
もしかしたら言語のバージョンが上がってより効率的な書き方ができるようになっているかもしれませんし、周囲の事情が前回とは違い、考慮しなければならない仕様や制限事項が違うかもしれませんし、そもそも前回の書き方自体があまり良くない書き方だったりするかもしれません。
毎回同じように書いてしまっていては、確かに「動く」ソースコードを書く事はできるのですが、その時最適なソースコードにはならない可能性が高いですし、自分の引き出しを増やすこともできません。
逆に、毎回「この行は本当にこう書けば大丈夫なのだろうか、もし他に書き方があるとすれば、どのように書けるだろうか」を考えることで、実はもっと効率の良い書き方が見つかるかもしれませんし、似ているけどちょっとだけ振る舞いが違う別のメソッドを見つけることができるかもしれませんし、懸念点を洗い出すために仕様自体について改めて考え直し、仕様の問題点を見つけることにつながるかもしれません。
そして、そうやって蓄積してきた知識や経験こそ、後々同じような処理が出てきた時に「あ、この処理以前やったのと同じだ。じゃあ確かあのときはこんな手とこんな手とこんな手があって、あんな事情でこの手を選んだけど、今回は少し事情が違うからこっちだな」と即座に適切な判断をする力になるのではないかと思います。
自分の実力を高めるためにも、常に「何か懸念点はないか、別の手はないか」を自分自身に問いかけながらコーディングすることは有効です。
メリットとデメリットに着目する
さて、ソースコードの一行一行に意味を込めろ、と繰り返しましたが、具体的にコーディング時にどのようなことを考えれば良いのでしょうか。
正解が特にあるわけではなないのですが、僕は「別の手を探してみる」ことと、「メリットとデメリットを整理する」ことをやってみています。(最初の例でも少しやっていましたね)
ここではその2つについて書いてみます。
別の手を探してみる
まずは、「他に書き方があるのだとしたら、どんな書き方があるんだろう」を考えます。
例えば、上記の例のような変数宣言する、しないであればIDE上でいろいろ書いてみながら検討できますし、ファイルアクセスなどJava SEやライブラリ、フレームワークで用意されているAPIを使うような処理であれば、ググっていくつかサイトを比較して選択肢を増やしたいり、そこで発見したクラスの公式リファレンスを一通り読んだりしてみます。社内のコードやライブラリであれば、同じようなことをしている別のクラスのコードをいくつか読んでみる、というところでしょうか。
(余談)
ちなみにJavaのファイルアクセスはJavaのバージョンによって大きく方法が異なります。そのあたりは以下の記事が参考になります。
Javaで1行ずつテキストデータを読み込むイディオムの変遷
(/余談)
とにかく、一つの目的を達成するコーディング方法はひとつではないことを常に考えながら、様々な方法で他の手段を見つけ出そうと頭を働かせることが、別の手を見つける助けになります
メリットとデメリットを整理する
いくつかの選択肢を挙げることができたら、次はその中のどれを最終的に採用するかの判断です。
しかし、ただ「判断しろ」と言われてもどのように判断すれば良いのかわからない場合もあるかと思います。
そのような時、僕はメリットとデメリット、という切り口でそれぞれの選択肢の良し悪しを整理し、判断の材料にします。
どのような選択肢でもメリット(もしくはデメリット)しかない、ということはありません。細かなところまで含めれば必ずメリットもデメリットもあるものです。
例えば、先ほど紹介した「1行ずつテキストデータを読み込む」方法について考えてみます。
(具体的なソースコードは元記事を参照してください)
確かに、新しいバージョンになるにつれてどんどんソースコードが簡潔になっていくのがわかります。ソースコードの行数も減って、バグの入り込む余地も少なくなっていきます。これが新しいバージョンの書き方についてのわかりやすいメリットですね。
一方で、新しい書き方を取り入れる、ということはチームのメンバーの学習コストを必要とします。特にJava8のLambdaとStreamを使った書き方では、チームメンバー全員がLambdaとStream、というJava7までは登場しなかった概念を理解する必要が出てきます。また、一箇所だけそれを取り入れることはプロジェクトの一貫性を崩す結果にもなりますので、Java7で書かれていたプロジェクトをJava8に対応し、新たにLambda&Streamを使ってファイル入出力を実装しよう、となったらそれまで書いたファイル入出力処理すべてを修正しなければならなくなるでしょう。
そうなると、数ステップの節約のために既存の機能にバグを発生させるリスクを負うのか、大切な工数を新しい概念の習得に割くのか、といった議論が発生します。(エンジニアなんだから新しい技術くらいキャッチアップしてよ、と思うかもしれませんが、それは人それぞれ、学習の優先度もあったりして強制することができないのが現実です)
一見メリットしかないように見えても、状況によっては小さなデメリットが大きな問題になる、というのがこの例からも分かるのではないかと思います。
ネットで「これが良い!」と書かれている書き方だろうと、社内で当たり前のように書かれている書き方だろうと、一歩立ち止まってメリットとデメリットを整理し、現状と照らし合わせてみることで、たどり着く結論は常に同じにはならないことがわかってきます。そこまで考えた上でコーディングできれば、「ソースコードの一行一行に意味を込めろ」を少しは実践できたことになるのではないかな、と思います。
まとめ
今回ご紹介した「ソースコードの一行一行に意味を込めろ」とセットでよく言われたのが、「動くだけのプログラムなんて誰でもすぐ書けるんですよ」でした。
プログラムが動いてやっと30%完成で、そのコードが本当に最適なのか、将来発生し得る修正をどこまで考えてどこまで対応すると決めたのか(どこからは考慮しない、と判断したのか)、他の同じような処理との一貫性はとれているか、それらを他の書き方と比較してためしたか、というようなことを試行錯誤し、自分で説明できるようになって始めてリリースできるレベルである、と、その先輩によく叱られた記憶があります。
そして、今自分が運用・保守を主な担当として過去のプログラマが書いたソースコードをよく読むようになり、改めてその言葉の重要さを実感しているところでもあります。
「考える」は漠然としていて巷の自己啓発本の「これをこうすればすべてOK!」のような方法は全く示せないのですが、「どう考えるかを考える」ことまで含めてあれこれと試行錯誤する習慣を身につけることこそが、「考える」力を養うことにつながり、それが「ソースコードの一行一行に意味を込める」ことができるための力になるのではないかと思います。