メイン言語が Java なので Java で特に気にしているところの個人的なまとめ
細かいところ重視なのでインターフェースや設計はこの記事では触れていない。
ポイント
- 変数のスコープは必要最低限であるか
- 変数名はスコープに応じて適切であるか
- 変数はイミュータブルであるか
変数のスコープは必要最低限であるか
スコープが広くなればなるほどその変数は役割が増えてしまう。
また、予期しないタイミングでの使用、変更といったバグを埋め込みやすくなる。
スコープは必要最低限にしたい。
ID(int)の配列から名前を問い合わせるfindメソッドがあるとする。
String name;
for (int id : identities) {
name = find(id);
// name を使った何かしらの処理
}
このコード例ではname変数をループ外で定義しているが、ループ内のみで name を使っている場合にループ内で定義するとスコープが狭くなる。
for (int id : identities) {
final String name = find(id);
// name を使った何かしらの処理
}
変数名はスコープに応じて適切であるか
前述のスコープとも影響するが変数名やメソッド名の命名はスコープに準じてより厳格にする必要がある。
逆にいえばスコープが狭ければ厳格である必要性がなくなることも意味する。
ループのカウンタである int i
などは極端に短いケースであるが、2-3行以内で使われるような変数は name
や age
など単純な変数名で良い。
一方で定数などはドメインに応じて厳格にする必要がある。
たとえば、 public static final String DEFAULT_USER_NAME_VALUE = "名無しさん";
のように。
USER
に種類がある場合にはその分定数が増え、変数名もより厳格にする必要があり、長くなってしまう。
ループの内外で何らかの名前を意味する変数を使いたい場合、name
と name2
のような名前ではなく userName
adminUserName
のようにする必要が出てくる。
イミュータブルであるか
変数はカウンタなど一部を除きイミュータブルにできる。
ミュータブルな変数の場合、予期しないタイミングで変更や名前空間の上書きなどが発生してしまう可能性がある。
IntelliJ などの IDE によっては変数作成時にローカル変数であっても final で定義されるものもある。
ただし、Map や List などは変数そのものは final であっても中身は書き換えられるため要注意。
Javaの場合、インスタンス変数として持っている private final List
を return しているメソッドなどは、deepcopy を返す、 UnmodifiableList
にしてから返すといった方法が望ましい。
private final List<String> nameList = new ArrayList<>();
public List<String> getNameList() {
return nameList;
}
これは以下のように置き換えることを検討したい。
private final List<String> nameList = new ArrayList<>();
public List<String> getNameList() {
return Collections.unmodifiableList(nameList);
}
プリミティブな定数で良いか
(プリミティブではないが)String や int の定数を用いるケースはそれなりに多いと思うが、列挙型やそれを表すクラスに置き換えることを検討したい。
たとえば、ファイルにデータを書き込む処理があるとして書き込みモードとして以下のような定数を用意したと考える。
/**
* 追記モード
*/
public static final String WRITE_MODE_APPEND = "append";
/**
* 上書きモード
*/
public static final String WRITE_MODE_OVERWRITE = "overwrite";
/**
* 新規作成モード(ファイルがあったらエラー)
*/
public static final String WRITE_MODE_NEW = "new";
このモードを使用した書き込みメソッドは次のようなシグニチャになるだろう。
public void write(String data, String mode)
第一引数と第二引数がどちらも String
であり引数名を確認しなければ判別しにくい。
また、mode
が String のために対応していない文字列の場合も考慮する必要が出てきてしまう。
たとえば列挙型やModeクラスにすることで第二引数に本来の data を渡してしまうようなミスが発生しなくなる。
enum WriteMode {
/**
* 追記モード
*/
APPEND,
/**
* 上書きモード
*/
OVERWRITE,
/**
* 新規作成モード
*/
NEW;
}
この場合、シグニチャは次のようになるだろう。
public void write(String data, WriteMode mode)
「まぁとりあえずこれで」の危険性
これ単独でみると当たり前のことを言っているが、検証しながらコーディングをするなどしていると「まぁとりあえずこれで」としてしまったコードが残っていたり、リファクタリング漏れによってこういったコードが入ってしまうことがあるので自戒を込めて注意したい。