メンテナンス性が高いコードの特徴
- 機能拡張がしやすい
- バグ修正がしやすい
- 仕様変更に対応しやすい
- 可読性が高い(読みやすい)
メンテナンス性が高いコードの特徴としてぱっと思いつくのは上記の4つぐらいでしょうか。
仕様変更はそもそも発生しないに越したことはないが、仕様変更が全くないシステム開発なんてほぼほぼあり得ないので、ある前提でプログラムを作っておく方が良いでしょう。
プログラミングを学習し初めのころは、文法を覚え、使用している言語の特性を知り、主要なライブラリの使い方を学んだりするかと思います。
プログラムの書き方とデバッグ力が身につけば、とりあえず動くものが作れるレベルにはなるでしょう。
でも、とりあえず動くものが作れたとしてもコードがきれいじゃなければまだ一人前とは言えません。
一人前になるための次のステップアップとして、メンテナンス性の高いコードを書くためのスキルが必要です。
この記事ではメンテナンス性の高いコードを書けるようになるための心得として、個人的に普段のコーディング時に意識していることをプログラマー初心者向けに紹介します。
心得1. コードを書いたら負け
まず、プログラミングはコードを書いた時点で負けです。
最近流行りのノーコードやローコードのツールを使ってコードを書かないプログラミングをしろという意味ではなく、あくまでもプログラミング言語を使ってコーディングする前提の話において、です。
プログラミングはコード書かないと始まらないのに、何を言っているんだと思う人もいるかもしれません。
実際、プログラミング言語を使ってプログラムを作るなら、コードを書かないと何も始まらない。
だけど、メンテナンス性の高いコードを書きたいのなら、コードを書いてはいけないという考えを常に意識しておくことは結構重要です。
バグが存在しないプログラムとは
ここで1つ質問。バグが存在しないプログラムとはどんなプログラムか?という問いに対して、みなさんは何と答えますか。
この質問の答えは、コードが書かれていないプログラムです。
コードが書かれていないということはそもそも動かないのでは?と思うかもしれません。
その通り。コードが書かれていなければプログラムは動きません。
けど、コードが書かれていないプログラムは動かない代わりに、バグも一切存在しない。
何が言いたいかというと、プログラムはそもそもコードを書かなければ、バグは存在しないということ。
そして、プログラムは人間がコードを書いた時点が確実にバグが混在してしまうということ。
プログラムの規模が大きくなり、コードの量が増えれば増えるほど、バグの数も増えます。
プログラムのバグを減らしたいのであれば、そもそもコードを極力書かないことが重要な要素となります。
コードを書くことのデメリット
コードをたくさん書くことのデメリットはバグが増えることだけではありません。他にも
- テストすべき項目が増える
- 修正の影響範囲が予測しにくくなる
- コードを読むのに時間がかかる
といったデメリットがあります。
機能豊富で便利なプログラムを作ろうと思うと、それだけ多くのコードを書かなければいけないのは事実ですが、コードを多く書くことは基本的にはデメリットしかないと認識しておきましょう。
プログラムを作るときにコード量は少ないに越したことはないです。
同じ機能を備えたプログラムが2つあったとして、コード量に差があるとするなら、少ないコードで実現されているプログラムの方が優れたプログラムと言えます。
ということで、プログラムを作るとき、コードは極力書いてはいけないという意識を持っておくことは重要。
「とりあえず動くプログラム」ができた後は、コードを見直してより少ないコードにできないかをチェックしてみましょう。
心得2. コピペ禁止
二つ目の心得は、コピペ禁止。なんですが、コーディングするときにコピペを全く使うなということではありません。
PCを使って作業するときにコピペほど便利な機能はないでしょう。
プログラミングを行うときにもそれは同じ。コピペは非常に便利で、効率的に作業するうえでは基本的に使いこなした方が良いです。
しかし、プログラミングにおいては、コピペを積極的に活用した方が良い場面と、コピペをあまりしない方が良い場面があることを知っておいてください。
具体的には
- Webサイトからのコピペ → OK
- 他プロジェクトのソースコードからのコピペ → OK
- 同じプロジェクト内のソースコードからのコピペ → NG
となります。まあ、この辺りは明確に基準があるわけではなく、ケースバイケースだとは思うんだけど、概ね上記の基準でコーディングするのが良いと個人的には思ってます。
コピペがNGとは結局どういう意味かというと、コピペして似たようなコード作るぐらいなら共通化して使いまわせということです。
関数にするのかクラスにするのかモジュールにするのか、どう共通化するのがベストなのかは、言語や処理の内容、使われる頻度や規模によるけれど、とりあえずコピペで似たようなコードをたくさん量産するな、ってことです。
コピペしてコードを書くと、コピー元のコードがバグっていた時に修正個所が増えて非常に面倒なことになります。
仕様変更があったり、機能追加があって修正が入る場合も同様。
プログラムというのは一旦作り終えた後にメンテナンスされる期間の方が圧倒的に長いです。
学習用で作るプログラムなら何の問題もありませんが、仕事でプログラムを作るときは後からからどんどん修正が入っていく前提で考えなが作っておくべきです。
そして、修正がある場合、修正個所はできるだけ少ないにこしたことはないのは明らか。
その方が時間がかからないし、テストの工数も少なくて済む。
長い目で見て楽をしたいのであれば、コピペして似たようなコードを量産してはいけない。
プログラムを書いている中で、自分が書いたコードをコピペして使いまわしたくなったら、共通化できないかどうかを検討しよう。
心得3. テストをめんどくさがる
プログラムの品質を上げるためには、コーディングを終えた後にテスト(検証)をすることが必須です。
個人で使用するプログラムならさておき、ユーザーに使ってもらうプログラムを開発するときにテストの作業を省略することはまずあり得ない。
とはいえ、テストというのはやっていてあまり楽しいものでもない。(個人的な意見ですが。)
テストにあまり長い時間は使いたくないのが開発者としての本音。しかし、品質を上げるためにはテストがおざなりになるのも当然よくない。
じゃあどうすればいいのか。
そもそも、テストするパターンが少なくて済むようなプログラムを作ればよいというのが結論。
そうすれば、テストの工数を削減することが可能です。
じゃあ、具体的にどうすればテストのパターンが多くならないプログラムを作れるのか。
以下のような関数を考えてみましょう。このtestFuncという関数をテストする場合、何パターンのテストすれば十分と言えるでしょうか。
<?php
function testFunc()
{
if (条件式A) {
処理A
} else {
処理B
}
}
条件式Aの複雑さにもよるとは思いますが、基本的には条件式Aを満たす場合と満たさない場合の2パターンで、処理Aと処理Bがそれぞれ期待した結果になるかを確かめれば一般的には十分と言えそうです。
つまり、テストパターンは最低2パターン。
続いて、こちらの場合はどうでしょうか。
<?php
function testFunc()
if () {
if() {
処理A
} else {
処理B
}
} else {
if() {
処理C
} else {
処理D
}
}
}
こちらもifの条件式によってテストパターンは変わってきそうだが、処理A~Dまで4つの処理パターンがあることから、最低でも4つのパターンを確認する必要はありそうですね。
じゃあこれは?
<?php
function testFunc()
if () {
if() {
if() {
if() {
if() {
if() {
…
こんなコードが実際に存在しているのかどうかわからないけれど、条件分岐がここまで複雑になると、テストパターンを漏れなく洗い出すのは非常に高難度になってきます。
結局、何が言いたいのかというと、条件分岐が増えれば増えるだけテストすべきパターンが増えることをまずは知っておいてほしいです。
つまり、テストで楽をしたいのであれば、できるだけ条件分岐を書かないことが肝となります。
しかし、実際問題、条件分岐を書かずに複雑なプログラムが作れるかというと、答えは当然NOです。
複雑なことをしたければ当然、分岐によって処理を分けなければいけない場合が必ず出てきます。
ただ、実は工夫次第でコード上から分岐をある程度減らすことは可能です。
コード上から条件分岐を減らすにはどうすればよいのか。具体的な方法を3つほど紹介します。
条件分岐を減らす方法1. ロジックの見直し
これはテクニックではなく、ほぼ気持ちの問題。
プログラムを作るとき、最初はとりあえず動くものを目指してコーディングをしていくことが人がほとんどだと思います。
コードを書かない方が良いという話でも触れましたが、コードの品質を上げたければ一度動くようになったコードを見直すことが重要です。
無駄なコードがないかどうかの確認と合わせて、条件分岐を少しでも減らせないか、条件式と処理の流れを見直してみましょう。
条件分岐を減らす方法2. SQLの活用
仕事で開発するプログラムの場合、十中八九DBMSを使用することになるかと思います。
DBを使用する場合に、DBから取得したデータをもとに条件を分岐して処理を分ける場合もあることでしょう。
今回はRDBを使用する前提で、例えば、以下のようなテーブルを考えます。
usersテーブル
id | name | age | gender | admin_flag |
---|---|---|---|---|
1 | Alice | 23 | 1 | 0 |
2 | Bob | 27 | 2 | 1 |
3 | Chris | 33 | 2 | 0 |
ここで、usersテーブルから取得したレコードをもとに以下のような情報を表示するプログラムを考えます。
名前:Bob 年齢:20代、性別:男性
プログラムで条件分岐しながら書くとしたら概ね以下のような感じでしょうか。
$sql = "select * from users where id = :id";
// DBアクセスに必要な処理を定義
$user = ... // SQLの実行結果が$userに格納されるものとする
$dispAge = "";
$dispGenger = "";
if($user->age >= 10 && $user < 20) {
$dispAge = "10代";
} else if($user->age >= 20 && $user < 30) {
$dispAge = "20代";
} else if ($user->age >= 30 && $user < 40) {
$dispAge = "30代";
} else {
$dispAge = "その他";
}
if ($user->gender === 1) {
$dispGenger = "女性";
} else if ($user->gender === 2) {
$dispGenger = "男性";
} else {
$dispGenger = "その他";
}
echo "名前:{$user->name} 年齢:{$dispAge} 性別:{$dispGenger}";
年代については10~30代までしか考慮していませんが、それでも条件分岐が多く、すべての条件をテストするのは面倒そうです。
そこで、今度はSQLで分岐ができるCASE式を活用することにします。CASE式を使うと、プログラムは概ね以下のようになります。
$sql = "select name
, case
when age >= 10 and age age < 20 then '10代'
when age >= 20 and age age < 30 then '20代'
when age >= 30 and age age < 40 then '30代'
else 'その他'
end dispAge
, case
when gender = 1 then '女性'
when gender = 2 then '男性'
else 'その他'
end dispGender
from users where id = :id";
// DBアクセスに必要な処理を定義
$user = ... // SQLの実行結果が$userに格納されるものとする
echo "名前:{$user->name} 年齢:{$user->dispAge} 性別:{$user->dispGenger}";
SQLが長くなった代わりに、プログラム言語側での条件分岐がなくなりました。
SQL条件分岐が移動しただけで、分岐そのものはなくなってないので結局変わらないのでは?と思う人もいるかもしれません。
しかし、SQLで分岐を定義する利点は、テストの容易さにあります。
SQLは、DBに接続できるツールさえあれば、プログラムを起動することなくSQL単体で実行結果を確認することができます。
通常、作成しようとしたプログラムをテストしようとする場合、プログラムを実行して画面から確認したり、あるいはユニットテスト用のプログラムを書いてテストを行ったります。
プログラム言語側に条件分岐を書いた場合、どうしても画面を起動するか、テスト用のプログラムの実行が必要ですが、SQLはそれ単体で正しく動作することを保証すれば、後はプログラム言語にそのまま埋め込めば良いだけなので、トータルのテスト工数は減らすことが期待できます。
今回はCASE式を使ってプログラム側の分岐をなくしましたが、他にも関数を駆使したりすることでプログラム上の分岐を減らせることはかなりあるので、DBから取得したデータを加工してしようする場合はSQLを有効に使おう。
条件分岐を減らす方法3. DBMSの機能を活用
先に説明したSQLを使った分岐は非常に便利ですが、SQL文の文字列自体が長くなってしまうのは可読性を下げるデメリットがあります。
また、似たようなSQLを他の場所での共通で使いたい場合もあります。
SQLの作成自体を関数化して共通化するのも一つの手ですが、DBMS側の機能でうまく隠蔽するのも手です。
例えば、DBMSでVIEWを定義します。
create view v_users as
select name, name, age,
, case
when age >= 10 and age age < 20 then '10代'
when age >= 20 and age age < 30 then '20代'
when age >= 30 and age age < 40 then '30代'
else 'その他'
end dispAge
, case
when gender = 1 then '女性'
when gender = 2 then '男性'
else 'その他'
end dispGender
from users
すると、先のプログラムは以下のように修正できます。
$sql = "select * from v_users where id = :id";
// DBアクセスに必要な処理を定義
$user = ... // SQLの実行結果が$userに格納されるものとする
echo "名前:{$user->name} 年齢:{$user->dispAge} 性別:{$user->dispGenger}";
VIEWを活用することでプログラムがシンプルになりました。
こうなった場合、VIEWがそもそも正しく動作するのかというテストは別で必要になりますが、プログラムからは条件分岐が消えているので、プログラム単体でのテストはシンプルになりそうな気がしますね。
DMBSの機能を活用するためには、そこに特化した知識を持つ人が必要で、かつソースコードをどう管理するかを決めていないと混乱を招く恐れがあるので、注意が必要です。
ですが、プログラムとうまく機能を切り分けて有効活用することで、プログラムのソースコードをシンプルにしつつ、プログラム単体のテストの工数削減につなげることの可能です。
条件分岐を減らす方法4. デザインパターンの活用
JavaやC#など、厳密なオブジェクト指向の言語を学んだことがある人は、デザインパターンについて聞いたことがある、あるいは学んだことがある人も多いかもしれません。
最近の言語はスクリプト言語でもオブジェクト指向の機能を持った言語が多いので、Javaなどを知らなくてもデザインパターンを学習した人も多いかもしれません。
知らない人のためにをざっくり説明すると、デザインパターンはよく使われる設計パターンをカタログ化したものです。
その中でも有名なのはGoFの23パターン。これはオブジェクト指向言語における設計パターンがまとめられたものになります。
詳細の解説は省きますが、その中の例えば「Template Methodパターン」や「Strategyパターン」などを使用すると、メソッドのオーバーライドを用いることで、見かけ上コードから条件分岐をなくすことができます。
具体的なコードで見てみます。
まずは条件分岐で処理を分ける例。
<?php
function calc()
{
if(条件1) {
処理A
} else if(条件2) {
処理B
} else if (条件3) {
処理C
}
}
続いてStrategyパターンを使って処理をクラス毎に処理を分ける例。
<?php
interface Sample
{
public function calc();
}
class SampleA implements Sample
{
public function calc()
{
処理A
}
}
class SampleB implements Sample
{
public function calc()
{
処理B
}
}
class SampleC implements Sample
{
public function calc()
{
処理C
}
}
デザインパターンを使うとクラスの数が増えるので、トータルのコード量という意味では増えてしまう部分はありますが、コード上から条件分岐はなくなっているのがわかるかと思います。
まあ、実際に処理を実行するためにはクラスのインスタンスを作成しなければいけないので、その時に分岐が発生してしまうこともあります。
ですが、そこはFactoryパターンという別のデザインパターンを使うことでインスタンス作成の処理を処理の実装から切り離して隠蔽することもできますし、その中でリフレクションという技術を使えば完全に分岐をなくしてしまうことだって可能です。
勘のいいひとは、デザインパターンで処理をクラスごとに分けたとしても、テストすべきパターンは減らないのでは?と思う人もいるかもしれません。
確かに、今回の例では、条件分岐を使おうがデザインパターンを使おうが、処理の内容は処理Aから処理Cまでの3パターンなので、結局最低でも3パターンはテストが必要であることに違いありません。
ただ、デザインパターンを使った方が、処理の影響範囲が限定的になります。
クラス単位で処理が独立しているので、修正した場合の影響範囲がクラス内に閉じており、機能を追加する場合もクラス追加で対応できます。
そうすると、テストをする範囲も常にクラス単位に限定されるので、長期的にはテストの工程削減が期待できます。
条件分岐を使って処理を1つの関数にまとめてしまった場合、1つの関数内に全ての処理が定義されているので、修正した際に他の処理に影響が及んでいないかを常に意識しなければいけないため、品質担保のハードルが上がってしまいます。
条件分岐を減らす方法5. メタプログラミングの活用
メタプログラミングは、そのまま訳すとプログラミングをプログラミングするみたいな意味合いになります。
実行したいプログラムをそのままコーディングするのではなく、実行したタイミングやパラメータによって、実行されるプログラムそのものが動的に変化していくような、そんなプログラミング手法のことを言います。
言葉だけでは分かりにくいと思うので、例で説明。例えば以下のような処理を考えます。
<?php
function funcA() { echo "A"; }
function funcB() { echo "B"; }
function run($param)
{
if($param === 'A') {
funcA();
} else if($param === 'B') {
funcB();
}
}
run('A'); // funcAを実行
run('B'); // funcBを実行
特に処理内容には意味のないプログラムになりますが、runメソッドでは引数で受け取った文字列によって条件を分岐して、実行する処理を分けています。
続いて、以下の処理を見てみます。
<?php
function funcA() { echo "A"; }
function funcB() { echo "B"; }
eval('funcA();'); // funcAを実行
eval('funcB();'); // funcBを実行
PHPにはevalという、引数で受け取った文字列をそのまま処理として実行できる関数があります。
このように、関数に渡したパラメータによってそもそも実行される処理が変化するようなプログラミング手法を、メタプログラミングと呼びます。
この関数を利用することにより、条件分岐をせずとも処理を呼び分けることができるようになります。
ただし、eval関数はこうした性質からセキュリティ上のリスクもあるので、安易に使って良いものではないです。
ですが、言語仕様の中にこうした技術もあることは知っておくとよいでしょう。
先にも少し触れましたが、リフレクションという技術を使うことで、コードの中に直接クラス名や関数名を書かずにインスタンスを作成したり、関数の呼び出したりもできます。
こうした技術は、条件分岐を減らしたり、プログラムの汎用性を上げることができるメリットがあります。
しかし、その反面で可読性の低下や実行速度の低下というデメリットもあるので、使う場面はよく考える必要があります。
とはいえ、デザインパターンやメタプログラミングという技術はフレームワークやライブラリの中で多用されている技術なので、使いこなせるようになっていると、フレームワークやライブラリに依存する問題解決の場でも役に立つことがあります。
あまり知識がないという方はステップアップとして学ぶといいかもしれません。
条件分岐を減らす方法6. 関数をうまく活用する
こちらについては別記事を書いたので、良ければ参考に。
条件分岐を減らす方法7. UIを見直す
WebアプリでもデスクトップアプなどのGUIを全体としたアプリでは、ボタン押下など、画面上の何かしらの操作をきっかけに処理が行われることが多いかと思います。
1つの処理の中で分岐が多く複雑な処理になっている場合、画面上の1つの操作における責任が重くなっている可能性があります。
画面上のUIを見直し、操作をうまく切り分けることで、処理も切り分けができて必然的に分岐を減らすことができます。
コードの中で分岐が多くなっている場合は、画面上の操作を分けて、1つ1つの処理をシンプルにできないかを検討してみてみるのも良いでしょう。
本を読もう
コードの書き方に関する本も多数あるので、いくつか紹介します。
1. リーダブルコード
非常に有名な本ですね。個人的には、「名前を分かりやすくしよう」「コメントをいい感じに書こう」ぐらいしか印象に残ってないですが。具体例も豊富で分かりやすいので、その2つを詳しく知りたい!という方にはお勧めです。
2. 達人プログラマー
これもかなり有名な本。リーダブルコードに比べると少し難易度は上がるけど、良いプログラミングをするためのエッセンスが詰め込まれているので、プログラマーとして食っていくなら一読しておきたい本。
3. プリンシプル オブ プログラミング
この本はコードは全く書かれていません。リーダブルコードや達人プログラマーなど、コーディングに関する様々な本から考え方のエッセンスを文章として分かりやすくまとめ上げてくれている本です。表紙に「1年目までに身につけたい一生役立つ101の原理原則」とあるように、早い段階で身に着けておいた方がよいであろう考え方が詰め込まれているので、「とりあえず動く」ものが作れるようになったうえでのステップアップ本としておすすめ。
4. 達人に学ぶSQL徹底指南書
プログラムで楽をするためにSQLを徹底先に使いこなしたいという人におすすめです。
まとめ
最後のまとめ。メンテナンス性の高いコードを書くためには
- コードを書いたら負けという意識を持とう
- 同じプロジェクト内でのコードはコピペせず極力共通化する意識を持とう
- テストを怠ってはいけないが、テスト工程をめんどくさがり、テスト工程を減らす努力をしよう
- 「とりあえず動く」ものができた後は、コードを見直し、無駄なコードや不要な条件分岐がないかチェックしよう
- SQLとDBMSの機能を使い倒そう
- デザインパターン、メタプログラミングを学んでみよう
- ステップアップできそうな本を読もう