はじめに
内容としては以下の電子書籍から零技法の内容を自分なりの解釈を含め、まとめたものになります。
「実践Javaプログラム: 火事場のJavaプログラム」井上 信幸 (著)
エンジニアがコードを記述する際に無意識に行っていることを言語化し、プログラム記述法として昇華させたものであると捉えております。
コードを記述する際にエンジニアによって考え方も多種多様であり、賛否のある内容かもしれませんが、そういう考えもあるかくらいに考えていただければと思います。
この記事で得られること
- 保守性と拡張性に優れたプログラムの記述方法
- 美しいプログラムと美しくないプログラムの判断方法
零技法とは
- 保守性と拡張性に優れたプログラムを記述するためのプログラム記述法
- プログラムを頭を使わずに機械的に記述することが可能
使用方法については後述
零技法を理解する前に、「プログラム」とは何か
零技法を理解する前に前提としてプログラムとは何かを説明します。
プログラムと聞いて思い浮かぶ物は多数あるかもしれませんが
プログラムは処理の 「手順書」 を指すものとして捉えます。
そしてプログラムには「美しい」プログラムと「美しくない」プログラムが存在すると言えます。
プログラムの例:レジ会計のプログラム
プログラムの説明をするにあたり以下に「レジ会計のプログラム」を例に説明します。
「レジ会計のプログラム」
- 商品受け取る。
- 商品をすべてスキャンし合計金額を算出する。
- 算出された金額を受領する。
- おつりを支払う。
- 商品を渡す。
上記はレジ会計を実行する 「手順書」 と言えます。
コーディングする際も上記のプログラムと同じように記述を行います。
美しくないプログラムとは?
上記に記載したレジ会計のプログラムは美しいプログラムと言えます。
その理由については美しくないプログラムと対比して確認してみましょう。
美しくないプログラム
- 商品受け取る
- 商品をすべてスキャンし合計金額を算出する。
- 商品の個数分バーコードをスキャンする。
- すべてスキャンし終えたら、合計金額を伝える。
- 算出された金額を受領する。
- 支払方法を確認する。
- 現金の場合は手渡しで受け取る。
- おつりがある場合は渡す。
- 商品を渡す。
- 袋が必要な場合は袋に入れて渡す。
感覚的に上記のプログラムは美しくないプログラムと感じていただけますでしょうか?
(美しくないプログラムのつもりで書いてみました)
なぜ美しくないのか?
「異なる抽象度のプログラムが混在しているため」 です。
例にあげた美しいレジ会計のプログラムは全て抽象度が同じであり具体的な処理記述のないプログラムなことに対し、
美しくないプログラムには「商品の個数分バーコードをスキャンする。」や「現金の場合は手渡しで受け取る。」のように具体的な処理記述があり、
異なる抽象度のプログラムが混在しています。
美しくないプログラムを改善
上記の美しくないプログラムを美しいプログラムに改善してみましょう。
抽象度が異なるプログラムが混在する場合は、そのプログラムを切り出し、抽象度が同じプログラムを複数作成します。
改善後のプログラム
- 商品受け取る
- 商品をすべてスキャンし合計金額を算出する。
- 商品の個数分バーコードをスキャンする。
- すべてスキャンし終えたら、合計金額を伝える。
- 算出された金額を受領する。
- 支払方法を確認する。
- 現金の場合は手渡しで受け取る。
- おつりを支払う。
- 商品を渡す。
- 袋が必要な場合は袋に入れて渡す。
段落ごとに抽象度がすべて同じになり、美しいプログラムになりました。
(色々ツッコミどころのあるプログラムなことには目をつぶってください)
設計書でよく見かける処理記述に近いものであることが分かると思います。
零技法
プログラムの説明をしたうえで以下に「零技法」の手順を説明します。
※以下の手順は何かしらのIDEを用いてコードを記述する手順を想定しています。
ぜひお手元のIDEを起動して実際に試していただければと思います。
零技法の手順
- 処理を日本語で記述
- 記述した内容がこれ以上抽象度を下げられない場合に実装
- 抽象度を下げられる場合は転記した日本語の通りにメソッドを定義
先ほどの改善後レジ打ちのプログラムを用いて上記の手順を実施してみます。
前提として以下とします。
- コードにはJavaを使用
- メソッド等の命名は適当です。適宜修正してください。
零技法の適用 excute()
それでは、以下のように例としてCashRegisterクラスのexcuteメソッドを作成し零技法を適用してみます。
public class CashRegister{
public void excute() {
}
}
手順1 処理を日本語で記述
プログラムの日本語を、そのままプログラム内に転記します。
public class CashRegister{
public void excute() {
// 商品受け取る
// 商品をすべてスキャンし合計金額を算出する。
// 算出された金額を受領する。
// おつりを支払う。
// 商品を渡す。
}
}
手順2 記述した内容がこれ以上抽象度を下げられない場合に実装
抽象度がすべて高いためスキップします。
手順3 抽象度を下げられる場合は転記した日本語の通りにメソッドを定義
public class CashRegister{
public void excute() {
// 商品受け取る
List<Goods> goodsList = receiveGoods();
// 商品をすべてスキャンし合計金額を算出する。
long totalAmount = scanGoodsList(goodsList);
// 算出された金額を受領する。
int change = receiveAmount(totalAmount);
// おつりを支払う。
giveChange(change);
// 商品を渡す。
giveGoods(goodsList);
}
}
これで 「レジ打ちの流れ」 となるプログラムの完成です。
この時点ではGoodsクラスと各メソッドが存在しないのでコンパイルエラーとなっているはずです。
IDEの機能を利用して、クラスとメソッドの定義をしましょう。
中身は空で問題ないです。
public class CashRegister{
public void excute() {
// 商品受け取る
List<Goods> goodsList = receiveGoods();
// 商品をすべてスキャンし合計金額を算出する。
long totalAmount = scanGoodsList(goodsList);
// 算出された金額を受領する。
int change = receiveAmount(totalAmount);
// おつりを支払う。
giveChange(change);
// 商品を渡す。
giveGoods(goodsList);
}
private long scanGoodsList(List<Goods> goodsList) {
return 0;
}
private List<Goods> receiveGoods() {
return null;
}
private int receiveAmount(long totalAmount) {
return 0;
}
private void giveChange(int change) {
}
private void giveGoods(List<Goods> goodsList) {
}
}
public class Goods {
}
これでコンパイルエラーも発生しなくなりました。
以降はメソッドそれぞれに零技法の手順1~手順3の繰り返しとなります。 同様にscanGoodsListメソッドにも零技法を適用してみます。零技法の適用 scanGoodsList
手順1 処理を日本語で記述
public class CashRegister{
private long scanGoodsList(List<Goods> goodsList) {
// 商品の個数分バーコードをスキャンする。
// すべてスキャンし終えたら、合計金額を伝える。
}
}
手順2 記述した内容がこれ以上抽象度を下げられない場合に実装
public class CashRegister{
private long scanGoodsList(List<Goods> goodsList) {
long totalAmount = 0;
// 商品の個数分バーコードをスキャンする。
for(Goods goods : goodsList){
//グッズごとにスキャンをする。
}
// 合計金額を返却。
return totalAmount;
}
}
- これ以上抽象度が下げられない、「商品の個数分バーコードをスキャンする。」の処理を記述しました。
- 「グッズごとにスキャンする。」を追加しました。
- これ以上抽象度が下げられない、「すべてスキャンし終えたら、合計金額を伝える。」処理を追加しました。またコメントの修正を行いました。
手順3 抽象度を下げられる場合は転記した日本語の通りにメソッドを定義
public class CashRegister{
private long scanGoodsList(List<Goods> goodsList) {
long totalAmount = 0;
// 商品の個数分バーコードをスキャンする。
for(Goods goods : goodsList){
//グッズごとにスキャンをする。
totalAmount += scanGoods(goods);
}
// 合計金額を返却。
return totalAmount;
}
private long scanGoods(Goods goods) {
return 0;
}
}
scanGoodsメソッドの追加を行いました。
後は新たに作成したメソッドも含め、すべてのメソッドに対し零技法の手順を繰り返すのみとなります。
以上が零技法の説明となります。
零技法を適用したプログラムは保守性や拡張性に優れたプログラムと言えます。
それを確かめるために上記のプログラムに仕様変更を入れてみて、それに耐えうるつくりになっているかを確認したいところですが、長くなってしまうので別の記事にできればと思います。
まとめ
プログラムとは
処理を行う 「手順書」 として考える。
そしてプログラムには「美しい」プログラムと「美しくない」プログラムが存在する。
- 美しいプログラム:処理ごとに抽象度がすべて同じであるプログラム。
- 美しくないプログラム:処理ごとに異なる抽象度のプログラムが混在しているプログラム。
※実装前に完璧に美しいプログラムを作ることはかなり難しいと思います。
実際、本記事を書きながらプログラムや実装に修正を入れながら記載していました。
(本来であれば設計書への手戻りになっていそうですね、、)
零技法
保守性と拡張性に優れたプログラムを記述するためのプログラム記述法であり、
零技法に則ってプログラムを記述すれば機械的に美しいプログラムを作ることができる。