この記事は第18回全国ロボコン交流会向けに書いたものになります。
「とりあえず動く」ではなく、「読みやすい」
「とりあえず動く」とは
現状で求められている動作をするけど、仕様変更や機能追加が難しいようなものを指しています。よく分からないけど何故か動いているというような言い方もできます。
なぜ、読みやすいことが重要なのか?
**「保守性」という言葉を理解する必要があります。言い換えれば「メンテナンスのしやすさ」**です。保守性は以下のような要素で評価できます。
- 不具合の発見・修正のしやすさ
- 仕様変更や機能追加などの行いやすさ
- ソースコードの読みやすさ
ソースコードの読みやすさを意識していれば、自然と他も達成されると思います。
読みやすいコードは保守性を高めることに繋がり、不具合の発見や修正がしやすくなったり、仕様変更や機能追加が行いやすくなったりするわけです。
「読みやすい」とは
「読みにくくない」を意識する
読みやすいということは、読みにくくないということです。分かりにくいことを避けることも同じです。そういった問題を潰していくことが読みやすいコードをかくことに繋がります。
1. 読み上げてみる
「赤巻紙 青巻紙 黄巻紙」
プログラミングから離れて、早口言葉をしてみましょう。
「あかまきがみあおまきがみきまきがみ」
さて、この早口言葉に読点を打ってみましょう。
「あかまきがみ、あおまきがみ、きまきがみ」
ひらがなの羅列だと何と書いてあるかすら分かりにくいですが、読点をつけるだけで大幅に改善されます。また、最初と比べて読み上げやすくなったはずです。
羅列された文章は分かりにくい・読点をつけると分かりやすくなったり読みやすくなったりする ということを理解できれば問題ないです。
ソースコードに置き換えて考える
空白が何もないソースコードがあります。どういった処理や計算が行われているのか分かりにくいです。
#include <iostream>
int main() {
int a=1,b=2,c=3;
a+1;
b+=2;
c=3;
std::cout<<a<<" "<<b<<" "<<c<<std::endl;
}
カンマの後・演算子の前後に空白をつけるというルールを適用して書き換えてみます。
#include <iostream>
int main() {
int a = 1, b = 2, c = 3;
a + 1;
b += 2;
c = 3;
std::cout << a << " " << b << " " << c << std::endl;
}
先ほどと比べて分かりやすくなったはずです。a
とc
に対する処理が違うことも一瞬で分かると思います。空白によって分割して考えることができるようになるので、書き間違えや処理の違いを知る為に効果的です。
2. マジックナンバーはあってはいけない
プログラムの中で唐突に出てくる数字のことをマジックナンバーといいます。
以下の関数において 0.08 というのがマジックナンバーです。
int getTax(int price) {
return price * 0.08;
}
消費税抜きの価格から消費税を計算する関数なので、0.08 が消費税率であることは推測できますが、もしも消費税が変わったらどうするのでしょうか?プログラムに存在する 0.08 を全て書き換えるのでしょうか?消費税という意味以外で 0.08 を使っている処理があったら間違えて書き換えてしまいそうです。
マジックナンバーとせず変数やマクロとして定義しておくことで対策することができます。
constexpr double TAX_RATE = 0.08;
int getTax(int price) {
return price * TAX_RATE;
}
0.08 を掛けるという処理では曖昧でしたが、消費税率を掛けるという処理であれば何をしているのか分かりやすくなります。同時に変更のミスが生じにくくなるので、保守性の向上にも繋がります。
3. 関数やクラスを有効活用する
一つの関数の中に処理を羅列していると変数の管理が出来なくなったり、処理を変更するのが難しくなったりします。それを防止するために適度に関数やクラスに分けましょう。
「規模は小さく、名前に合った処理をする」ということを意識すると、とりあえず処理をまとめた関数や処理のお得パックのようなクラスといった謎関数や謎クラスを作ることは避けられると思います。
4. 省略すると分からない
例と共に考える
以下のようなプログラムがあるとします。ここでは、 s
という関数を実行した結果を出力しています。さて、 s
とは何でしょうか?
#include <iostream>
int s(int* a, int* b);
int main() {
int a[] = {3, 5, 1};
std::cout << s(a, a + 3) << std::endl;
}
s
という関数名が sum
であれば分かるでしょうか?
#include <iostream>
int sum(int* a, int* b); // s -> sum
int main() {
int a[] = {3, 5, 1};
std::cout << sum(a, a + 3) << std::endl; // s -> sum
}
sum
は「和」という意味の英語です。その為、和を求める関数であると考えることができます。
C, C++ を多少知っている人間からすれば、sum
という名前で int*
2つを引数とする関数であれば、それぞれ begin
, end
のことなんだろうだと想像できます。あくまで想像に過ぎません。引数名も分かりやすくするべきです。
#include <iostream>
int sum(int* begin, int* end); // a -> begin, b -> end
int main() {
int a[] = {3, 5, 1};
std::cout << sum(a, a + 3) << std::endl;
}
名前の省略について考える
省略した名前はソースコードを短くしてくれるので、タイプする量が減るので早くソースコードを書き上げることができます。しかし、後から修正する時や他の人が読んだ時に、書いた時と同様の理解を短時間で出来るでしょうか?コメントを用いて省略した名前の説明を書くという方法もありますが、とにかく分かりやすくしておくことが必要です。
5. 命名規則の統一
プログラミングに関係する名前には命名規則というものが伴ってきます。以下に例を示します。
名前 | 読み方 | 書き方 |
---|---|---|
snake_case | スネークケース | 全て小文字。単語の区切りはアンダーバー |
CONSTANT_CASE | コンスタントケース, アッパースネークケース | 全て大文字。単語の区切りはアンダーバー |
camelCase | キャメルケース, ロワーキャメルケース | 先頭以外の単語の頭文字は大文字。それ以外は小文字 |
PascalCase | パスカルケース, アッパーキャメルケース | 単語の頭文字は大文字。それ以外は小文字 |
大文字や小文字・記号を用いて、名前にフォーマットを設けることで、その名前がどういう意味を持つのか推測できるようになります。
統一する
この命名規則はチームやプロジェクトで統一するべきところなので、これを採用すべきというわけではありませんが、一例を示します。
名前 | 命名規則 |
---|---|
関数 | camelCase |
変数 | snake_case |
クラス | PascalCase |
ファイル | snake_case |
定数 | CONSTANT_CASE |
より細かく決めても構いませんし、別の命名規則を採用することもあると思います。統一感のあるコードは欠けているコードと比べて読みやすいです。採用する命名規則を事前に決めてから開発しましょう。
6. 自然言語的
省略した関数名には注意が必要だということを確認しました。実際のところどう言った名前をつければいいのでしょうか。答えは簡単で、馴染み深い英単語を採用すればいいのです。また、関数名の先頭の英単語は動詞にすると分かりやすくなります。
合計時間を取得する関数
関数の処理について考えたとき、合計時間を戻り値とするものであったとします。関数名として適するものはなんでしょうか。まず、それぞれを英単語にしてみましょう。
日本語 | 英語 |
---|---|
合計 | total |
時間 | time |
取得する | get |
これを組み立ていきます。 動詞 + 目的語 という形で組み合わせれば問題ないです。 camelCase であれば getTotalTime になりますし、 snake_case であれば get_total_time になります。
偶数であるかを判定する関数
同様に英単語にしてみましょう。
日本語 | 英語 |
---|---|
偶数 | even number |
判定 | judgment |
これは judgmentEvenNumber となってしまいます。しかし、judgementとわざわざ書くのは面倒ですし、より適している単語があります。「偶数であるかを判定する」という部分を「偶数ですか?」と考えてみましょう。そうすると isEvenNumber という方が適していると考えられます。判定という処理はよく出ていく部分なので統一して is を採用してしまった方が分かりやすいです。
関数名を決める流れ
単語を英語に訳してみて、馴染み深いものを選ぶという手順で進めていきましょう。全く見たことがない英単語しか出てこない場合もあると思います。避けるのが一番良いですが出来ないこともあります。その場合はコメントとして翻訳元と先の対応を変えておくことをオススメします。関数名を決めるときに見たことがない英単語は、使う時にも分からないことが多いからです。
これもチームやプロジェクトで統一しておくと統一感が生まれたり、意味がわからない英単語が採用されるリスクを避けたりできます。
英単語 | 意味 | 制限 |
---|---|---|
get | 値を取得する | |
set | 値を変更する | |
update | 値を更新する | |
is | 判定する | 戻り値は bool 型 |
プログラミングでよく使う英単語のまとめ【随時更新】 も参考にしてみるといいかもしれません。
ソースコードは作品
プログラムは目的の処理をする為の手段であることは確かですが、同時にそれ自体が作品であり、美しさを追求すればより良く見えると思います。