
はじめに
初めまして、今年度SEとしてNRIネットコムに入社した竹内です。 生物系の学科を卒業してITの世界に飛び込んでから、もうすぐ1年になります。
卒業研究ではJavaを使って生物の進化をシミュレーションするプログラムを作成し、ほとんど独学で「出てほしい結果が出るコード」を書けるようになりました。
しかし、入社後のJava研修では今まで聞いたことのなかった手法を次々に学び、「コードを書くのに本当にこんな面倒なルールが必要なの?」と思ったのを覚えています。
そんな私の考えを変えたのが「保守性」という言葉でした。
実際にJavaでコードを書き、レビューを受ける中で、その「面倒なルール」こそが保守性を担保するために必要であり、チーム開発に欠かせないものだと気付いたのです。
この記事では、私が大学時代に書いたコードの失敗を例に、チーム開発における保守性の重要性とそれを高めるための具体的なポイントを紹介します。
※Javaを題材としていますが、他言語にも応用可能な内容になっています。
保守性とは?
保守性とは、コードを「理解しやすく、修正しやすく、拡張しやすい」状態に保つことです。
チーム開発では1つのコードに対して複数人がレビュー・修正を行います。また、仕様変更やバグ修正は必ず発生するものです。
そのため、人に読まれる・修正されることを前提としてコードを書く必要があります。
保守性が低いコードは理解しにくく、修正に時間がかかり、バグが増え、開発コストが膨らむ原因になります。
これが、ただ動いて結果が出るコードを書けばよかった大学時代との大きな違いでした。
ここからは、私が研修で学んだ保守性を高める手法について紹介します。
保守性を高める手法
命名規則
変数名やメソッド名は、コードを読む人が「何を表しているか」を一目で理解できるようにすることが重要です。
また、命名を規則化することでコード全体の一貫性が保たれ、レビューや修正がしやすくなります。
命名のポイント
意味が伝わらない名前は避ける
命名スタイルを統一する(メソッド・メンバ変数はキャメルケース、等)
メソッド名は動詞で始め、動作が推測できるものにする
リスト型は末尾にListをつける
私の失敗
for 文のインデックスを i, j, k ...としていた
→どのインデックスが何のループを表すかわかりにくい
→入れ子構造を修正する際はインデックスを振りなおす必要があり、修正コストが大きい
for (int i = 0; i < chromosomes.length; i++) { for (int j = 0; j < genes.length; j++) { //処理 } }
解決策
インデックスを含め、全ての変数を意味のある名前にする
for (int chrIndex = 0; chrIndex < chromosomes.length; chrIndex++) { for (int geneIndex = 0; geneIndex < genes.length; geneIndex++) { //インデックスが何を指すか一目でわかる //処理 } }
メソッド切り出し、共通化
何度も出てくる処理はメソッド化することでコードが簡潔になり、理解しやすくなるだけでなく、複数回使いまわすことができます。
さらに、記載の重複が減るため、修正時は該当メソッドだけを変更すればよい状態になります。これにより、バグ発生のリスクを減らすことができます。
メソッド切り出しのポイント
メソッド内に処理が多いときは切り出してメソッド化する
1つのメソッドは1つの責務に絞る
似ている処理は引数を工夫して共通化する
私の失敗
メソッドを作らず、全ての処理をメインメソッドに書いていた
→あるロジックを変更したいとき、どこからどこまで修正すればよいかわからない
→同じような処理が複数回出てきて読みにくい
//似た処理が複数回出てくる for (int geneIndex = 0; geneIndex < genes.length; geneIndex++) { chromosome1[geneIndex]=0; } for (int geneIndex = 0; geneIndex < genes.length; geneIndex++) { chromosome2[geneIndex]=1; }
解決策
似ている処理が複数回出てくるときはメソッドとして切り出し、共通化する
//処理を使いまわす initializeChromosome(chromosome1, 0); initializeChromosome(chromosome2, 1); //共通化したメソッド public void initializeChromosome(int[] chromosome, int gene) { for (int geneIndex = 0; geneIndex < chromosome.length; geneIndex++) { chromosome[geneIndex] = gene; } }
定数化
数字はベタに書かず、必ず定数化して一目でわかるように意味をもたせます。
定数とは、一度設定すると変更できない値のことで、final修飾子を付与して表します。
数字を変更したい場合は、定数を変更するだけでコード全体を一括で修正することができます。
定数化のポイント
・数字はfinal修飾子を付与し、大文字スネークケースで定数化する
・ループ回数や配列の長さは定数化した数字を使って指定する
私の失敗
配列の長さやループ回数に直接数字を使っていた
→ 数字が何を意味しているのか一目でわからない
→ 数字を変更したいとき、他の箇所に同じ意味の数字があればすべて修正する必要がある
int[] chromosome = new int[1000];//1000が何を表す数字かわからない
解決策
ループ回数や配列の長さは定数化した数字を使って指定する
public static final int GENES_PER_CHROMOSOME = 1000; int[] chromosome = new int[GENES_PER_CHROMOSOME];//染色体(chromosome)は1000の遺伝子(gene)から成る配列だとわかる
列挙型:Enum
曜日や干支のようなあらかじめ決まった値の集合は、Enumで定義することで意味のある名前で管理することができ、間違った値を使うことを防ぐことができます。Enumの中で列挙された値はクラス初期化時に1つずつインスタンスが生成され、そのインスタンスを参照する形で共有されます。
Enumのポイント
public修飾子をつけ、1つのファイル内に分離すると他クラスから使いやすい
フィールドはfinal、コンストラクタはprivateにして、一度定義された値が変更されないようにする
getterを作成し、安全に情報にアクセスできるようにする
私の失敗
雌雄や遺伝子の種類を数字(int型)で管理していた
→どの数字が何を表すか一目でわからない
→文字で出力したい場合、数字を文字に変換する処理を別途実装する必要がある
int geneType = 0; //コメントがないと何を表すかわからない
解決策
値の集合はEnumで名前をつけて管理する
Gene geneType = Gene.X; // 一目で意味がわかる
public enum Gene { X(0, "X染色体"), Y(1, "Y染色体"), O(2, "なし"); private final int code; private final String label; private Gene(int code, String label) { this.code = code; this.label = label; } public String getLabel() { //文字で出力するgetter return label; } public int getCode() { //数字で出力するgetter return code; } }
おわりに
一見面倒なルールに感じるかもしれませんが、今回紹介した工夫を取り入れることで長期的に保守しやすいコードを書くことができます。
最後に余談ですが、この気づきを卒業研究の先生にお話しする機会がありました。
そのとき先生は、やっと気づいたか!と笑いながらも、
「最初はどんなコードでも動かせればいい。気づいて後から直せばいいんだから。」
と言ってくださいました。
この言葉で、動くだけのコードを目指していた時期も無駄ではなく、次のステップとして保守性を意識して取り入れ、成長するプロセスが大切だと感じました。
私と同じ新人SEやこれからSEを目指すという方、「なんとなく書けるけどもう一歩先に進みたい!」という方の参考になれば幸いです。