コード品質とは?
コード品質とは何でしょうか。「品質」ですので、高い・低いで評価されるものになります。
では「品質の高いコード」とは何でしょう? 読みやすいコード? 落ちないコード? 高速なコード? 正直なところ、何をもって品質が高いとするかは、プロジェクトによって異なるところです。ですが、そのような品質を評価するためには、定義された方法でソフトウェアを定量的に分析し、その分析結果を何かしらの基準と照らし合わせる必要があります。技術的な観点から、ソフトウェア品質の評価としてよく使われる要素としては以下のものが挙げられます。
実行効率(Efficiency)
おそらく一番わかりやすいコード品質は実行効率でしょう。最近のコンピュータは速いので、そこまで気にする必要はないかもしれませんが、コードの実行速度は速いにこしたことはありません。
少ないメモリや遅いCPUでも高速に動くコードなら、最終的にできあがったソフトウェアを少ない費用で動かすことができます。Webサービスのフロントエンドのコードなら、きびきびした反応がユーザーの満足度を向上させるでしょう。
一方、実行結果が正しくとも、実行速度が極端に悪いコードでは、要件によっては使い物にならないこともあります。自分のコードがボトルネックとなってチームに迷惑をかけてしまうのは、なんとしても避けたいものです。とはいえコンピュータが動く仕組みは複雑なので、実行速度が速いか遅いか正しく判断するのは、それほど簡単ではありません。
例えばリスト1は実行効率が悪いコードの例です。
// データベースに接続
connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/myDatabase", "user", "password");
for (int i = 0; i < 100; i++) {
// 新しい PreparedStatement を作成
preparedStatement = connection.prepareStatement("SELECT * FROM users WHERE id = ?");
preparedStatement.setInt(1, i);
// クエリを実行
resultSet = preparedStatement.executeQuery();
// 結果の処理
while (resultSet.next()) {
System.out.println("User ID: " + resultSet.getInt("id"));
System.out.println("User name: " + resultSet.getString("name"));
}
}
forループの中のexecuteQueryでデータベースに問い合わせをしています。100回繰り返しのループなので、100回問い合わせます。データベースの知識がないと、これがどのくらい時間のかかる処理であるかピンとこないかと思いますが、たぶん怖い先輩にひどく怒られるくらい時間がかかります。
これに気づくためには、ただ動くプログラムを書けるようになるだけでなく、そのプログラムがどのように動いているのか背景の知識を地道に学んでいくしかありません。
上のコードをどう直したらよいかは少し難しい話題ですが、例えば、SQLを工夫して100回のデータベース問い合わせをまとめて、一度だけの問い合わせで必要なデータをすべて取れるようにします。つまり、100回のループ内のデータベースアクセスをループの外に出してしまうわけです。実際には、そのSQLの書き方自体にも色々と効率を上げるテクニックがあります。
最終的に、熟練の先輩エンジニアがレビューしたコードは、リスト1のような正しく動くだけのコードに比べ、はるかに品質の高いコードになっていることでしょう。
可読性(Readability)
読みやすいコード、つまり「可読性」はコード品質の基盤と言えます。しかし、なぜコードは読みやすい方が良いのでしょうか?
コードはコンピュータが実行するものです。読みやすいコードであればコンピュータも喜んで実行してくれるのでしょうか。そんなことはありません(繰り返しますが、今のところは)。
コードを読みやすくするのは、一緒に開発するチームメンバーのためです。企業における開発プロジェクトの多くはチームで行われます。他人にとってわかりやすいコードを書くことは開発を効率よく進める上で重要な要素です。
普段の生活でも、筆者の世代ではズボンと言っていたものが、パンツと言われて一瞬戸惑ったりしますが、プログラミングでも同じことです。プログラムの意図が他のメンバーに誤解なくスムースに伝わるように、ささいな書き方であってもチーム内で合わせておいた方が良いのです。
チーム内のメンバーには将来の自分も含まれます。時間がたってコードを見返したとき、そのコードを書いたときの自分が何を意図していたのか、可読性を高めて、きちんと伝わるようにしておくことは大切です。
読みやすいコードを書くためには、簡単なことから難しいものまで、色々なテクニックがありますが、ここではごく簡単なことだけ紹介します。
わかりやすく一貫性のある命名規則
変数、関数、クラスについて、そのコード要素の目的や動作を明確に示すような意味のある名前を選ぶようにします。意味のある名前は、追加のコメントの必要性を減らすことができます。「コメントを読まずとも、コードを読めばわかる」のが理想的です。英語が苦手だと辛いところですが、そういうときこそChatGPTを頼って適切な名前を提案してもらうのもいいと思います。
また、プロジェクト全体で名前の付け方の指針を共有し、一貫性を持たせましょう。命名規則の例としては以下のようなものがあります。
- キャメルケース(Camel Case):変数や関数名を、最初の単語を小文字、次の単語からは先頭を大文字で記述します。例:numberOfStudents、calculateTotal、isValidUser
- スネークケース(Snake Case):変数や関数名を、単語の間をアンダースコアで区切って記述します。例:number_of_students、calculate_total、is_valid_user
- ドメイン固有の用語の統一:プロジェクトのドメインに関連する特定の用語や略語を統一します。例:ユーザー名を表す用語を統一する(customer、user、clientなど)、商品名を表す用語を統一する(product、productName、description、titleなど)
命名規則の改善の例をリスト2に挙げます。
//読みにくい命名規則 a = 5; //改善された命名規則 numberOfStudents = 5;
フォーマットの統一
一貫したフォーマットは、コードの読みやすさを向上させます。インデントなどのコードの見た目の改善は、エディタや統合開発環境(IDE)を使っていれば自動になされることがほとんどですが、プロジェクトで共通したルールを適用するのがポイントです。
例えば、インデントとしてタブを使うか、スペースを使うか。スペースを使うとしたら何文字分か、など。ルールが混在してしまわないように気をつけましょう。
// インデントが適用されていないコード
if (condition) {
for (int i=0;i<10;i++){
if (anotherCondition) {
doSomething();
}
}
}
// 4文字スペースでインデントされたコード
if (condition) {
for (int i = 0; i < 10; i++) {
if (anotherCondition) {
doSomething();
}
}
}
保守性(Maintainablity)
保守というと、消耗部品を交換したり故障を修理しながら製品を長持ちさせたりすることのように聞こえます。ハードウェアならともかく、ソフトウェアは時とともに一部の部品が故障するようなことはありませんから、ソフトウェアの保守性とは何だろうと疑問に思うことでしょう。
ソフトウェアは一度完成したらそれで開発終了とは限らず、その後も継続して改良や修正されることがよくあります。例えば、納品後に機能追加や画面の修正を依頼されることや、製品リリース後しばらく経ってから不具合(バグ)が見つかって、それを修正することもあります。長く使い続けられるソフトウェアほど、そういった機会が増えてきます。それらをソフトウェアの保守と言います。
自動車のような長く使われる工業製品は、後から部品交換がしやすいように設計されています。車全体を分解しなくても、エンジン部品を交換できるように、簡単にボンネットを開けられるようになっています。
ソフトウェアも同様です。後からプログラムの一部を簡単に置き換えたり、修正できたりするように最初から作ってあれば、保守しやすいソフトウェアになります。どうすればそうなるかはプロジェクトの特性にも依存するので難しい話になるのですが、ここではごく簡単な例だけ示します。
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
System.out.println("Enter a number: ");
int n = scanner.nextInt();
int sum = 0;
for (int i = 1; i <= n; i++)
sum += i;
System.out.println("The sum of numbers from 1 to " + n + " is " + sum);
scanner.close();
}
}
このコードは、ユーザーに数値を入力させて、1からその数値までの数字の合計を計算して表示するものです。このコードの保守性を上げるなら、例えば次のようなことが考えられます。
- 長いmainメソッドだけのコードですが、一般に、長いメソッドはいくつかの短いメソッドに分解した方がよいとされます。その方が保守のとき、短いメソッド単位でコードを交換(書き換え)できるからです。一部の短いメソッドを交換するだけであれば、新しく交換するメソッドも簡単に書けますし、他のメソッドは正しく動き続けることが期待できます。
- 1からの合計をするコードですが、将来、保守の一環で2や他の数からの合計に修正したくなるかもしれません(例としてです)。そのとき、数字の1がコードのあちこちに散らばっていると、一部の1を2と書き換え忘れる修正もれが生まれやすくなります。
リスト5は、保守性を改善したコードです。
import java.util.Scanner;
public class Main {
private static final int START_NUMBER = 1;
public static void main(String[] args) {
int n = getInput();
int sum = calculateSum(n);
printResult(n, sum);
}
public static int getInput() {
Scanner scanner = new Scanner(System.in);
System.out.println(“Enter a number: “);
int n = scanner.nextInt();
scanner.close();
return n;
}
public static boolean validateInput(int n) {
return n > 0;
}
public static int calculateSum(int n) {
int sum = 0;
for (int i = START_NUMBER; i <= n; i++)
sum += i;
return sum;
}
public static void printResult(int n, int sum) {
System.out.println(“The sum of numbers from “ + START_NUMBER + “ to “ + n + “ is “ + sum);
}
}
長かったmain関数が短くなりました。2カ所の数字の1はSTART_NUMBERに変わりました。2からの合計に修正したくなったら、冒頭のSTART_NUMBERの宣言だけを修正するだけで良くなります。修正もれの心配もなくなります。
その他の品質観点
他にも、堅牢であること(Robustness)、拡張可能であること(Scalability)、安全であること(Security)など、ソフトウェア品質には様々な観点があります。
