この記事の目次
はじめに
本記事では、Robert C. Martinの名著『Clean Code』の第2章「意味のある名前」に関して、自分が大切だと感じた箇所をまとめました。さらに、いくつかの具体的なコード例を追加して解説しています。本書の第2章にはさらに詳細な命名に関するベストプラクティスが含まれていますので、興味がある方はぜひお読みください。
この記事の目次
本記事では、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];
}
}
例えば、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
クラスを用意することです。
package org.example.person;
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
という大きな概念に属していることがはっきりします。このように、名前付けされたクラスや関数、名前空間などに適切に配置することで、変数の文脈をより明確にすることができます。
上手く名前をつけることで、コードを文章と段落のように見せることができます。命名は際限なく繰り返される行為であり、コードは読まれる時間が実際に書いた時間よりも圧倒的に長いことを考えると、一番重要な行為といっても過言ではありません。適切な命名は、コードの意図を明確にし、保守性と拡張性を向上させるための鍵です。
命名時には以下のポイントを意識して、日々のコーディングで実践しましょう。
カテゴリー: