はじめに
HI Engineer Collegeでは、実務未経験のエンジニアの方や、エンジニアに興味がある方を募集したおります、まずはお気軽にお問い合わせください。
※ (株)G&T(【内々定まで最短2週間】ゼロからしっかりじっくり研修☆20代活躍中!)
※ HI Engineer Collegeに興味あがある方はこちら(プログラミングを学習したい方)
※ プログラミングでわからない事などを相談したい場合にはLINEオープンチャットにご参加ください(無料)
良いコードと悪いコードを学ぶ理由とは
1. ベストプラクティスの理解
良いコードを学ぶことで、効率的で読みやすく、保守しやすいプログラミング手法や設計原則(例:DRY原則、KISS原則)を身につけることができます。これにより、品質の高いソフトウェアを作成する能力が向上します。
2. 共通のミスを認識する
悪いコードの例を学ぶことで、よくある誤りやアンチパターン(例:スパゲッティコード、ハードコーディング)を理解し、それらを避ける方法を学べます。これにより、バグの発生を減らし、開発プロセスをスムーズに進められます。
3. コードの可読性と保守性の向上
良いコードは他の開発者にも理解しやすく、チームでの協力が容易になります。一方、悪いコードは後から修正や拡張が難しくなるため、避けるべき点を具体的に把握することが重要です。
4. 問題解決能力の向上
悪いコードを分析することで、問題点を見つけ出し、改善策を考えるスキルが養われます。これは、実際の開発現場で発生するさまざまな課題に対処する際に役立ちます。
5. 効率的な学習と成長
良いコードと悪いコードの比較学習は、効果的なフィードバックを提供し、自身のコーディングスタイルを客観的に評価する機会を与えます。これにより、継続的なスキルアップが可能となります。
6. チームワークとコミュニケーションの向上
統一されたコーディングスタイルを持つことで、チーム内でのコミュニケーションが円滑になり、コードレビューや共同作業が効率的に行えます。悪いコードの例を共有することで、全員が同じ基準を持つことができます。
それでは1つ1つ見ていきましょう!
タスク 1: 文字列の出力
Bad Code
// Bad Code Example: 文字列の出力
public class HelloWorld {
public static void main(String[] args) {
System.out.println("こんにちは世界");
}
}
-
説明:
一見問題ないように見えますが、このコードは特に悪い点はありません。ただし、あえて改善点を挙げるとすれば、クラス名やメソッド名がもう少し説明的であれば、将来的な拡張や理解がしやすくなります。 -
クリーンコードの観点:
- 命名の明確さの欠如: クラス名が一般的すぎて、他の用途にも使われやすい。
Good Code
/**
* シンプルな挨拶を表示するクラス
*/
public class GreetingPrinter {
public static void main(String[] args) {
printGreeting();
}
/**
* 挨拶メッセージをコンソールに出力します。
*/
public static void printGreeting() {
System.out.println("こんにちは、世界!");
}
}
-
説明:
クラス名をGreetingPrinter
とし、メソッドprintGreeting
を作成することで、コードの目的が明確になります。コメントを追加することで、コードの意図がより理解しやすくなっています。 -
クリーンコードの観点:
- 命名の明確さ: クラス名とメソッド名が具体的で、役割が明確。
-
単一責任の原則:
printGreeting
メソッドが一つの責任(挨拶の出力)に集中。 - コメントの活用: クラスとメソッドに説明的なコメントを追加。
タスク 2: 簡単な計算
Bad Code
// Bad Code Example: 簡単な計算
public class Calculator {
public static void main(String[] args) {
int a = 5;
int b = 3;
int c = a + b;
System.out.println(c);
}
}
-
説明:
基本的な計算を行っていますが、変数名がa
,b
,c
と意味が分かりづらく、何を計算しているのかが一目で分かりません。また、計算結果を出力する際の文脈がありません。 -
クリーンコードの観点:
-
意味のない変数名:
a
,b
,c
は具体的な意味を持たない。 - 可読性の低さ: 計算の目的が明確でない。
-
意味のない変数名:
Good Code
/**
* 簡単な加算を行うクラス
*/
public class SimpleCalculator {
public static void main(String[] args) {
int firstNumber = 5;
int secondNumber = 3;
int sum = addNumbers(firstNumber, secondNumber);
System.out.println("合計: " + sum);
}
/**
* 2つの整数を加算します。
*
* @param num1 最初の数
* @param num2 2番目の数
* @return num1 と num2 の合計
*/
public static int addNumbers(int num1, int num2) {
return num1 + num2;
}
}
-
説明:
変数名をfirstNumber
,secondNumber
,sum
とし、メソッドaddNumbers
を作成することで、コードの目的が明確になります。出力時に「合計: 」と表示することで、結果の意味が分かりやすくなっています。コメントを追加して、クラスとメソッドの役割を説明しています。 -
クリーンコードの観点:
-
意味のある変数名:
firstNumber
,secondNumber
,sum
は具体的な意味を持つ。 -
メソッドの抽出:
addNumbers
メソッドを作成し、再利用性と可読性を向上。 - コメントの追加: クラスとメソッドに説明的なコメントを追加。
-
意味のある変数名:
タスク 3: 条件分岐
Bad Code
// Bad Code Example: 条件分岐
public class AgeChecker {
public static void main(String[] args) {
int age = 17;
if(age >= 18){
System.out.println("成人です");
}
else{
System.out.println("未成年です");
}
}
}
-
説明:
基本的な条件分岐を行っていますが、変数名が短く、コメントがないため、コードの意図が一目で分かりづらいです。また、出力メッセージもハードコードされており、変更が難しいです。 -
クリーンコードの観点:
-
意味のない変数名:
age
は適切ですが、他の部分で改善の余地がある。 - ハードコードされたメッセージ: 出力メッセージが直接コードに書かれているため、変更が難しい。
-
意味のない変数名:
Good Code
/**
* 年齢に基づいて成人か未成年かを判定するクラス
*/
public class AgeChecker {
public static void main(String[] args) {
int userAge = 17;
checkAge(userAge);
}
/**
* 年齢をチェックして、成人か未成年かを表示します。
*
* @param age ユーザーの年齢
*/
public static void checkAge(int age) {
if (age >= 18) {
System.out.println("成人です");
} else {
System.out.println("未成年です");
}
}
}
-
説明:
変数名をuserAge
に変更し、メソッドcheckAge
を作成することで、コードの意図が明確になります。コメントを追加して、クラスとメソッドの役割を説明しています。これにより、将来的な拡張やメンテナンスが容易になります。 -
クリーンコードの観点:
-
意味のある変数名:
userAge
はユーザーの年齢を明確に表す。 -
メソッドの抽出:
checkAge
メソッドを作成し、責任を分担。 - コメントの追加: クラスとメソッドに説明的なコメントを追加。
-
意味のある変数名:
ここからは中級です、カリキュラムを終えてから勉強してみましょう。
タスク 1: 配列のソート
Bad Code
// Bad Code Example: 配列のソート
public class SortExample {
public static void main(String[] args) {
int[] nums = {5, 3, 8, 4, 2};
sortArray(nums);
for (int i = 0; i < nums.length; i++) {
System.out.println(nums[i]);
}
}
static void sortArray(int[] arr) {
for(int i=0;i<arr.length;i++) {
for(int j=i+1;j<arr.length;j++) {
if(arr[j]<arr[i]){
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
}
}
}
-
説明:
このコードでは、手動でバブルソートを実装していますが、ネストされたループにより時間計算量がO(n²)となり、効率が悪いです。また、変数名が短すぎて意味がわかりにくく、コメントもないためコードの意図が理解しづらいです。さらに、再利用性が低く、標準ライブラリを活用していない点も問題です。 -
クリーンコードの観点:
-
可読性の低さ: 意味のわかりにくい変数名 (
nums
,arr
) とコメントの欠如。 - 再利用性の欠如: 汎用的なソートメソッドの不使用。
- 効率の悪さ: 不必要に複雑なアルゴリズムの使用。
-
可読性の低さ: 意味のわかりにくい変数名 (
Good Code
import java.util.Arrays;
/**
* 配列のソートを行うクラス
*/
public class SortExample {
public static void main(String[] args) {
int[] numbers = {5, 3, 8, 4, 2};
sortArray(numbers);
for (int number : numbers) {
System.out.println(number);
}
}
/**
* 与えられた配列を昇順にソートします。
*
* @param array ソートする整数配列
*/
static void sortArray(int[] array) {
Arrays.sort(array);
}
}
-
説明:
このコードでは、Javaの標準ライブラリであるArrays.sort
を使用して配列をソートしています。これにより、コードが簡潔で読みやすくなり、効率的なソートが保証されます。また、変数名が意味を持ち、コメントが追加されているため、コードの意図が明確です。for-each
ループを使用して配列を出力することで、可読性が向上しています。 -
クリーンコードの観点:
-
可読性の向上: 意味のある変数名 (
numbers
,array
) と適切なコメントの追加。 - 再利用性の確保: 標準ライブラリの活用により、汎用的なソート機能を利用。
- 効率の良さ: 標準ライブラリの効率的なアルゴリズムを使用。
-
可読性の向上: 意味のある変数名 (
タスク 2: クラス設計とカプセル化
Bad Code
// Bad Code Example: クラス設計とカプセル化
public class Person {
public String name;
public int age;
public void display() {
System.out.println("名前: " + name + ", 年齢: " + age);
}
}
public class Main {
public static void main(String[] args) {
Person p = new Person();
p.name = "田中";
p.age = -10; // 不正な年齢
p.display();
}
}
-
説明:
Person
クラスのフィールドがpublic
に設定されており、外部から直接アクセスおよび変更が可能です。これにより、例えばage
に負の値を設定するなど、不正なデータが入力されるリスクがあります。カプセル化が欠如しており、データの整合性が保たれていません。 -
クリーンコードの観点:
-
カプセル化の欠如: フィールドが
public
で外部から直接アクセス可能。 - データの整合性の欠如: 不正な値の設定を防ぐ仕組みがない。
-
命名規則の不備: メソッド名が一般的すぎる (
display
)。
-
カプセル化の欠如: フィールドが
Good Code
/**
* 人を表すクラス
*/
public class Person {
private String name;
private int age;
/**
* コンストラクタで名前と年齢を設定します。
*
* @param name 人の名前
* @param age 人の年齢(0以上)
*/
public Person(String name, int age) {
this.name = name;
setAge(age);
}
/**
* 名前を取得します。
*
* @return 名前
*/
public String getName() {
return name;
}
/**
* 名前を設定します。
*
* @param name 設定する名前
*/
public void setName(String name) {
this.name = name;
}
/**
* 年齢を取得します。
*
* @return 年齢
*/
public int getAge() {
return age;
}
/**
* 年齢を設定します。年齢は0以上でなければなりません。
*
* @param age 設定する年齢
* @throws IllegalArgumentException 年齢が0未満の場合
*/
public void setAge(int age) {
if (age < 0) {
throw new IllegalArgumentException("年齢は0以上でなければなりません。");
}
this.age = age;
}
/**
* 人の情報を表示します。
*/
public void displayInfo() {
System.out.println("名前: " + name + ", 年齢: " + age);
}
}
public class Main {
public static void main(String[] args) {
try {
Person person = new Person("田中", 25);
person.displayInfo();
person.setAge(-5); // 例外がスローされます
} catch (IllegalArgumentException e) {
System.out.println("エラー: " + e.getMessage());
}
}
}
-
説明:
Person
クラスのフィールドがprivate
に設定され、ゲッターとセッターを通じてアクセスおよび変更が行われます。セッターで年齢が0以上であることを検証することで、データの整合性が保たれます。コンストラクタで初期値を設定し、適切な例外処理を行うことで、クラスの使用が安全かつ明確になります。メソッド名も具体的で意味がわかりやすくなっています。 -
クリーンコードの観点:
-
カプセル化の実装: フィールドを
private
にし、ゲッターとセッターを通じてアクセス。 - データの整合性の確保: セッターで入力値を検証し、不正な値を防止。
- 明確な命名規則: メソッド名が具体的で意図が明確。
- ドキュメンテーションの充実: クラスやメソッドにコメントを追加。
-
カプセル化の実装: フィールドを
タスク 3: 例外処理の実装
Bad Code
// Bad Code Example: 例外処理の実装
public class DivisionExample {
public static void main(String[] args) {
int a = 10;
int b = 0;
int result = a / b; // ここで例外が発生する
System.out.println("結果: " + result);
}
}
-
説明:
このコードでは、ゼロ除算が発生し、例外が未処理のままプログラムがクラッシュします。例外処理が欠如しているため、ユーザーにエラーメッセージが適切に伝わらず、プログラムの信頼性が低下します。 -
クリーンコードの観点:
- 例外処理の欠如: 発生しうる例外に対する対策がない。
- ユーザーへのフィードバック不足: エラーメッセージが適切に表示されない。
- プログラムの安定性の欠如: 例外発生時にプログラムが予期せず終了。
Good Code
public class DivisionExample {
public static void main(String[] args) {
int numerator = 10;
int denominator = 0;
try {
int result = divide(numerator, denominator);
System.out.println("結果: " + result);
} catch (ArithmeticException e) {
System.out.println("エラー: ゼロで割ることはできません。");
}
}
/**
* 2つの整数を割り算します。
*
* @param numerator 分子
* @param denominator 分母
* @return 割り算の結果
* @throws ArithmeticException 分母がゼロの場合
*/
public static int divide(int numerator, int denominator) throws ArithmeticException {
return numerator / denominator;
}
}
-
説明:
このコードでは、try-catch
ブロックを使用してArithmeticException
を適切に処理しています。ゼロ除算が発生した場合に、ユーザーに明確なエラーメッセージを表示し、プログラムが予期せず終了するのを防いでいます。また、メソッドに適切なコメントを追加することで、コードの意図が明確になっています。 -
クリーンコードの観点:
- 例外処理の実装: 発生しうる例外を適切にキャッチし、処理。
- ユーザーへの適切なフィードバック: エラーメッセージをわかりやすく表示。
- 明確なメソッド設計: メソッドにコメントを追加し、機能を明確化。
- プログラムの安定性の向上: 例外発生時もプログラムが正常に動作し続ける。
タスク 4: ユーザー入力の処理
Bad Code
// Bad Code Example: ユーザー入力の処理
import java.util.Scanner;
public class UserInputExample {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
System.out.println("数字を入力してください:");
String input = sc.nextLine();
int number = Integer.parseInt(input);
System.out.println("入力された数字は " + number + " です。");
}
}
-
説明:
このコードはユーザーからの入力を整数に変換していますが、入力が整数でない場合にNumberFormatException
が発生します。エラーハンドリングがなく、ユーザーが不正な入力をした場合にプログラムがクラッシュする可能性があります。また、Scanner
オブジェクトがクローズされていないため、リソースリークのリスクがあります。 -
クリーンコードの観点:
- エラーハンドリングの欠如: 入力が不正な場合の対策がない。
-
リソース管理の不備:
Scanner
がクローズされていない。 - ユーザーエクスペリエンスの低下: 不正な入力時にユーザーに再入力を促さない。
Good Code
import java.util.InputMismatchException;
import java.util.Scanner;
/**
* ユーザーからの整数入力を安全に処理するクラス
*/
public class UserInputExample {
public static void main(String[] args) {
Scanner scanner = null;
try {
scanner = new Scanner(System.in);
System.out.print("数字を入力してください: ");
int number = getValidInteger(scanner);
System.out.println("入力された数字は " + number + " です。");
} catch (Exception e) {
System.out.println("予期せぬエラーが発生しました: " + e.getMessage());
} finally {
if (scanner != null) {
scanner.close();
}
}
}
/**
* ユーザーから有効な整数を取得します。
*
* @param scanner Scannerオブジェクト
* @return ユーザーが入力した整数
*/
private static int getValidInteger(Scanner scanner) {
while (true) {
try {
return scanner.nextInt();
} catch (InputMismatchException e) {
System.out.print("無効な入力です。もう一度数字を入力してください: ");
scanner.next(); // 不正な入力をクリア
}
}
}
}
-
説明:
このコードでは、ユーザーからの入力が整数であることを検証し、不正な入力があった場合に再度入力を促します。try-catch
ブロックを使用して例外を適切に処理し、プログラムのクラッシュを防いでいます。また、finally
ブロックでScanner
をクローズすることで、リソースリークを防止しています。これにより、ユーザー入力の処理がより堅牢で信頼性の高いものになります。 -
クリーンコードの観点:
- エラーハンドリングの実装: 不正な入力時に例外をキャッチし、再入力を促す。
-
リソース管理の適切化:
Scanner
をfinally
ブロックでクローズ。 - ユーザーエクスペリエンスの向上: 不正な入力時に具体的な指示を提供。
- メソッドの責任分担: 入力取得を別メソッドに分割し、コードの再利用性と可読性を向上。
まとめ
良いコードと悪いコードを学ぶことは、単に技術的なスキルを向上させるだけでなく、チームでの協力や問題解決能力の向上にも繋がります。これにより、より高品質で信頼性の高いソフトウェアを開発するための基盤を築くことができます。継続的に学び、実践することで、優れたエンジニアとして成長していきましょう。