RubyKaigi 2013で発表したらインターンシップの応募があり、6/17から7/29まで1ヶ月半くらい週3日でインターンシップを実施しました。インターンシップでやったことはメモに残して公開していました。ただメモに残すだけではなく、まとめておき、次のインターンシップや新しくクリアコードのメンバーになった人に伝えるために役立てようと試みています。
クリアコードではインターンシップをクリアコード側もいろいろ学ぶ機会として捉えています。今回のインターンシップでクリアコードが得られたものは以下のようなものです。やった価値がありました。
- 自分たちがクリアなコードを書くために大事にしていることのいくつかを明文化できた。
- こうすれば伝わりやすいのではないかと考えた方法のうち、効果があるものとないものがわかった。
- 新しい人とコミュニケーションをとりやすくする方法を試して、効果があることがわかった。
これらは大きな項目なので、少しずつもう少し細かい単位でまとめていきます。今回まとめるのは、明文化できたクリアなコードを書くために大事にしていることの1つです。
背景
インターンシップ1日目では、作ろうとしているWebアプリケーションが技術的に簡単に実現できそうかどうかを試しました。具体的にいうと、コアとなる機能を使ったコマンドラインツールを作りました。コマンドラインツールを作ることで簡単に実現できそうだということがわかりました。
作ったコマンドラインツールを見ると気になるところがあったので、インターンシップ2日目は気になるところを説明しながら整理しました。その中で明文化できたクリアなコードを書くために大事にしていることの1つが「コメントを書きたくなるときはコードを見直す機会とすること」でした。
コードの中のコメント
クリアコードの人が書くプログラムにはコメントが少ないです。これは、「コメントで書いていることを本当にコードで表現できないか」を考えてコードを書いているからです。多くの場合はコードで表現することができるので、コメントを書くことが少なくなります。
コメントが少ないからといって「絶対コメントを書くな!」と思っているわけではありません。コメントを書くことでコードだけよりも理解が進むと判断したらコメントを書きます。
例えば、groongaで高速な位置情報検索をしているコードには以下のようなコメントを書いています。リンク先の記事で「例なので、実際の処理よりも大雑把に説明します。」と書いていることの「実際の処理」の部分です。
/*
b: base_point
x: geo_base
0-83: sub meshes. 0-83 are added order.
j: -5 -4 -3 -2 -1 0 1 2 3 4
+---+---+---+---+---+---+---+---+---+---+
|74 |75 |76 |77 |78 |79 |80 |81 |82 |83 | 4
+---+---+---+---+---+---+---+---+---+---+
|64 |65 |66 |67 |68 |69 |70 |71 |72 |73 | 3
+---+---+---+---+---+---+---+---+---+---+
|54 |55 |56 |57 |58 |59 |60 |61 |62 |63 | 2
+---+---+---+---+---+---+---+---+---+---+
|48 |49 |50 | b | |51 |52 |53 | 1
+---+---+---+ | +---+---+---+
|42 |43 |44 | |x |45 |46 |47 | 0
+---+---+---+-------+-------+---+---+---+
|36 |37 |38 | | |39 |40 |41 | -1
+---+---+---+ base meshes +---+---+---+
|30 |31 |32 | | |33 |34 |35 | -2
+---+---+---+---+---+---+---+---+---+---+
|20 |21 |22 |23 |24 |25 |26 |27 |28 |29 | -3
+---+---+---+---+---+---+---+---+---+---+
|10 |11 |12 |13 |14 |15 |16 |17 |18 |19 | -4
+---+---+---+---+---+---+---+---+---+---+
| 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -5
+---+---+---+---+---+---+---+---+---+---+
i
*/
1日目に書いたスクリプトにはいくつかコメントがありました。これを見て、「自分たちはコメントを書くときにこのコメントは本当にコードで表現できないかを考えているなぁ」と気づきました。ということで、一緒にコメントを1つずつ確認し、コードで表現できないか考えることにしました。
コードと同じ内容のコメントか
最初に確認したコメントは以下のコメントです。
def show_html_content(io)
# Nokogiri でパースし、内部テキストを出力する
content = Nokogiri::HTML(io)
puts content.text
end
このコメントはメソッド内の処理していることそのものについて書いています。つまり、コードの内容とコメントの内容が同じです。コードと同じ内容のコメントは削除します。
def show_html_content(io)
content = Nokogiri::HTML(io)
puts content.text
end
削除する理由は以下のとおりです。
- 重複した情報なので必要ないから。
- 「コメントに書くくらいだから何か特別な情報が入っているのではないか?」と読む人に勘違いさせないため。
後者の「読む人に勘違いさせないため」ということを少し補足します。「書いた人は読む人の助けとなるようにコードもコメントも書いているはず」という前提にたって読んでいる人は、本当は意味のないコメントにも「一見意味がなさそうだが、実はなにか意味があるのではないか?」という視点で読みます。そのため、「本当は必要のないコメント」を「必要なコメントではないか?」と勘違いしてしまい、コメントの必要性を探ってしまいます。そして、読んだ後に「なんだ、必要のないコメントだったのか」と気づきます。
参考:
処理の塊を説明しているコメントはメソッド名で表現する
次に検討したコメントは以下のコメントです。
def open_epub(filename)
uri_array = Array.new
epub_book = EPUB::Parser.parse(filename)
metadata = epub_book.metadata
# xhtml ドキュメントがどの順番で出てくるか記録する
epub_book.each_page_on_spine do |item|
if item.media_type == "application/xhtml+xml"
uri = item.href
uri_array << uri.to_s
end
end
# ...
end
このコメントは処理の塊で何をしているかを説明しています。コメントの内容の方が少し抽象度の高い説明になっており、コードと同じ内容というわけではありません。このように処理の塊に抽象度の高い説明をつけている場合は処理をメソッドに切り出してコメントで表現していたことをメソッド名で表現します。
def extract_xhtml_uri_array(epub_book)
uri_array = Array.new
epub_book.each_page_on_spine do |item|
if item.media_type == "application/xhtml+xml"
uri = item.href
uri_array << uri.to_s
end
end
uri_array
end
def open_epub(filename)
epub_book = EPUB::Parser.parse(filename)
metadata = epub_book.metadata
uri_array = extract_xhtml_uri_array(epub_book)
# ...
end
「どの順番で出てくるか」という情報が失われているので、コメントの情報をすべてをメソッド名で表現できていませんが、処理の塊にコメントで説明していた情報をメソッド名で表現するということはこういうことです。
まとめ
インターンシップでクリアコードが学んだことをまとめはじめました。今回は、自分たちがクリアなコードを書くために「コメントを書きたくなるときはコードを見直す機会」と捉えていることについてまとめました。
今回でてきたコードはコメントにだけ着目したもので、その後、他のところも整理されよりクリアなコードになっていきました。気になる人はranguba/epub-searcherを確認してみてください。