クリーンコード【命名編】
はじめに
本記事では、Robert C. Martinの名著『Clean Code』の第2章「意味のある名前」に関して、自分用にまとめました。コードは追加したり差し替えたりしています。本書の第2章には詳細な命名に関するベストプラクティスが含まれていますので、興味がある方はぜひお読みください。
イントロダクション
命名は、変数、関数、引数、クラス、パッケージ、ファイル、ディレクトリなど、あらゆるものに対して繰り返し行う最も重要な作業の一つです。ここでは、命名に関するルールを見ていきます。
コードの意図を明確にする
以下のような関数を見てみましょう。
public static List<String> filter(List<String> list) {
List<String> list1 = new ArrayList<>();
for (String s : list) {
if (s.length() > 5) {
list1.add(s);
}
}
return list1;
}
このコードは意図が不明です。以下の様に関数や変数の名前をより正確な表現に変えるだけで、コード上で何を行なっているか理解しやすくなります。
public static List<String> filterByLength(List<String> items) {
List<String> filteredItems = new ArrayList<>();
for (String item: items) {
if (item.length() > MIN_LENGTH) {
filteredItems.add(item);
}
}
return filteredItems;
}
もう一つの例です。
public static void copyCharArray(char[] c1, char[] c2) {
for (int i = 0; i < c1.length; i++) {
c2[i] = c1[i];
}
}
こちらも、引数の名前を変更することで読みやすくなります。
public static void copyCharArray(char[] source, char[] destination) {
for (int i = 0; i < source.length; i++) {
destination[i] = source[i];
}
}
明確な区別をつける
Info、Data、Objectなど抽象的で曖昧なワードをクラス名に利用して区別するのは避けるべきです。例えば、Productというクラスがある場合、ProductInfoやProductDataという名前のクラスは何を表すのか不明です。getAccountとgetAccountObjectも違いが分かりません。明確に違いが分かる名前をつける必要があります。
検索可能にする
一文字の名前や数値定数は、IDEから簡単に検索できません。特に、複数の場所で使われる名前は、検索できるようにしておく必要があります。
インターフェース名は単純に
例えば、ファイル読み込みのためのインターフェースを作成する場合、IFileReaderとFileReaderでは、後者の単純な名前が良いです。読者にインターフェースと認識させるべきではなく、具象クラスではなくインターフェースに対してプログラミングすることが好まれることを考えても、インターフェース名は冗長にすべきではないです。
クラス名
クラス名、オブジェクト名にはSpider、Customer、Containerのような名詞を利用し、Manager、Processor、Data、Infoなどの抽象的なワードは利用すべきではありません。
メソッド名
メソッドには、crawl、save、buildDownloaderのような動詞を利用します。アクセサ(ゲッター)、ミューテータ(セッター)、プレディケート(条件をチェックしてブール値を返すメソッド)には、get、set、isを前置すべきです。
静的ファクトリメソッド
コンストラクタをオーバーロードする場合は、静的ファクトリメソッドを利用し、メソッド名に引数を表現するものを含めることで分かりやすくなります。
public class Date {
private int year;
private int month;
private int day;
private Date(int year, int month, int day) {
this.year = year;
this.month = month;
this.day = day;
}
public static Date fromYearMonthDay(int year, int month, int day) {
return new Date(year, month, day);
}
public static Date fromString(String dateString) {
String[] parts = dateString.split("-");
int year = Integer.parseInt(parts[0]);
int month = Integer.parseInt(parts[1]);
int day = Integer.parseInt(parts[2]);
return new Date(year, month, day);
}
}
命名の一貫性を保つ
例えば、get、fetch、retrieveのような名前を同じ用途で使用すると混乱します。一つの概念には一つの名前を統一し、必要であれば語彙集を用意するのが良いです。
技術的な用語の使用
コンピュータサイエンスの用語、アルゴリズムやデザインパターンの名前など技術的な名前を用いるのは問題なく、エンジニア同士で概念を伝えるのに便利です。例えば、jobQueueなどは一目で理解できます。
ドメイン用語の使用
共通認識であるドメイン領域の用語を使用することで、業務の専門家に意味を聞くことができます。
文脈を加える
以下のPersonクラスを見てください。
public class Person {
private String firstName;
private String lastName;
private String street;
private String houseNumber;
private String city;
private String state;
private String zipcode;
private String phoneNumber;
private String email;
public Person(String firstName, String lastName, String street, String houseNumber, String city, String state, String zipcode, String phoneNumber, String email) {
this.firstName = firstName;
this.lastName = lastName;
this.street = street;
this.houseNumber = houseNumber;
this.city = city;
this.state = state;
this.zipcode = zipcode;
this.phoneNumber = phoneNumber;
this.email = email;
}
public void printPersonDetails() {
System.out.println("Name: " + firstName + " " + lastName);
System.out.println("Address: " + houseNumber + " " + street + ", " + city + ", " + state + " " + zipcode);
System.out.println("Phone Number: " + phoneNumber);
System.out.println("Email: " + email);
}
}
このクラスには、Personのプロパティと、Personの住所の一部を表すプロパティが混ざっています。streetやhouseNumberなどは住所の一部です。ここで、もしstateフィールドのみがあるメソッドで利用されていたら、この変数は住所の一部かどうか判断できるでしょうか?printPersonDetailsメソッドを見て初めてstateは住所の一部であると分かります。addrStreet、addrStateのようにプレフィックスをつけることで文脈を明らかにすることができますが、より望ましいのはAddressクラスを用意することです。
class Address {
private String street;
private String houseNumber;
private String city;
private String state;
private String zipcode;
public Address(String street, String houseNumber, String city, String state, String zipcode) {
this.street = street;
this.houseNumber = houseNumber;
this.city = city;
this.state = state;
this.zipcode = zipcode;
}
@Override
public String toString() {
return houseNumber + " " + street + ", " + city + ", " + state + " " + zipcode;
}
}
public class Person {
private String firstName;
private String lastName;
private Address address;
private String phoneNumber;
private String email;
public Person(String firstName, String lastName, Address address, String phoneNumber, String email) {
this.firstName = firstName;
this.lastName = lastName;
this.address = address;
this.phoneNumber = phoneNumber;
this.email = email;
}
public void printPersonDetails() {
System.out.println("Name: " + firstName + " " + lastName);
System.out.println("Address: " + address.toString());
System.out.println("Phone Number: " + phoneNumber);
System.out.println("Email: " + email);
}
}
こちらの例では、streetやstateなどの変数がAddressという大きな概念に属していることがはっきりします。このように、名前付けされたクラスや関数、名前空間などに適切に配置することで、変数の文脈をより明確にすることができます。
結論
上手く名前をつけることで、コードを文章と段落のように見せることができます。命名は際限なく繰り返される行為であり、コードは読まれる時間が実際に書いた時間よりも圧倒的に長いことを考えると、一番重要な行為といっても過言ではありません。適切な命名は、コードの意図を明確にし、保守性と拡張性を向上させるための鍵です。
命名時には以下のポイントを意識して、日々のコーディングで実践しましょう。
- コードの意図を明確にする
名前をより正確な表現に変えることで、コードの意図が明確になります。 - 明確な区別をつける
意味が曖昧な名前は避け、違いが分かる名前をつけます。 - 検索可能にする
一文字の名前や数値定数は避け、検索可能な名前を使います。 - インターフェース名は単純にする
冗長なインターフェース名を避け、単純な名前を使います。 - クラス名は名詞にする
名詞を利用し、曖昧なワードは避けます。 - メソッド名は動詞にする
動詞を利用し、アクセサ、ミューテータ、プレディケートには適切なプレフィックスを付けます。コンストラクタをオーバーロードする場合は、静的ファクトリメソッドを利用し、メソッド名に引数を表現するものを含めます。 - 命名の一貫性を保つ
同じ用途で異なる名前を使うことを避けます。 - 技術的な用語を使用する
コンピュータサイエンスの用語やデザインパターンの名前を積極的に使います。 - ドメイン用語を使用する
共通認識のあるドメイン領域の用語を使用します。 - 文脈を加える
クラスや関数、名前空間に適切に配置することで、変数の文脈を明確にします。
Discussion