見出し画像

綺麗なコードとは何か?

仕事における開発でも、個人的な開発でも、コードを綺麗に保っておく必要に迫られます。

3日後の自分は他人。とても良くできたコードだと思っていたのに、たったの3日の間、そのコードに触らないでいただけで、まるで他人が書いたコードのように感じられるものです。

こうした経験はプログラマならば、誰しもきっと実感のあることだと思います。

では、綺麗なコードとはどういったコードを指すのでしょう?

綺麗なコードというのは、なかなか定義が難しいものです。そこには必ず主観が入ってしまうからです。

そこで消費税額を計算するロジックをいくつかのパターンで実装してみて、どういったコードが綺麗なコードなのかを探ってみましょう。

なお、答えは出しません。

自分がどういったコードを書く傾向があって、どうしたら何が変わるのかを考えてもらえたらと思います。

ハードコーディングする

プログラミング初心者に限らず、あまりイケてない職業プログラマでもやってしまいがちなハードコーディングするパターンを見てみましょう。

 #include  <stdio.h>

int main(void) {
    double price = 1000;
    double taxedPrice = price + price * 8 / 100;

    printf("%f", taxedPrice);

    return 0;
}

税率は長いスパンで変動します。つまり変数です。変数をハードコーディングすると言うことは、コーディング以前の問題、大きな設計ミスということになります。ハードコーディングした値というのは定数になるからです。

税率の値を8という整数で持つか、レーティングに変換して0.08という少数で持つか。これは設計に依存します。後者は割り算の演算コストが高かった時代に好んで使われていました。掛け算1回で済むからです。

マクロを使う

プログラミングスキルが上がり、少しでもCをかじったレベルにあるプログラマなら、マクロを使うことを思いつくかもしれません。

 #include  <stdio.h>
 #define  CONSUMPTION_TAX_RATE    8

int main(void) {
    double price = 1000;
    double taxedPrice = price + price * CONSUMPTION_TAX_RATE / 100;

    printf("%f", taxedPrice);

    return 0;
}

入門書では、マクロを使えば後から柔軟に値を書き換えることができる、という説明をしているものが多いようです。

しかし、マクロの定義値を変更すると、必ずリビルドしなければなりません。値そのものは柔軟に書き換えることができますが、リビルドを必要とするためプログラムを柔軟に運用することができなくなります。

関数にまとめる

もう少し経験を積んだプログラマになると、消費税率を取得する部分を関数にまとめると思います。

 #include  <stdio.h>

int GetConsumptionTaxRate(void) {
    // read tax rate from file or database
}

int main(void) {
    double price = 1000;
    double taxedPrice = price + price * GetConsumptionTaxRate() / 100;

    printf("%f", taxedPrice);

    return 0;
}

税率は常に変動する可能性を秘めていますから、外部から値を取り込むようにしてしまうというわけです。

関数へまとめておけば、読み出し先がファイルからデータベースへ変わっても、上位に当たるプログラムではロジック変更をせずに済みます。

関数には処理の再利用性だけではなく、インターフェースを保ってあれば中身を丸っきりすげ替えることができるというメリットがあります。

問題を隠蔽する

税率を整数で持つか、あるいはレーティングとして少数で持つかという設計上の問題がありました。

次のようにコードを書き換えると、その問題を関数内へ押し込めることができます。

 #include  <stdio.h>

int GetConsumptionTaxRate(void) {
    // read tax rate from file or database
}

double CalculateConsumptionTax(double price) {
    return price * GetConsumptionTaxRate() / 100;
}

int main(void) {
    double price = 1000;
    double taxedPrice = price + CalculateConsumptionTax(price);

    printf("%f", taxedPrice);

    return 0;
}

課税額を小数点何位で四捨五入したいなどといった要件が出てきても、CalculateConsumptionTax()を修正すれば済むようになります。仕様変更に強くなります。

日本では流通硬貨は円までしかありませんが、為替レートなどからもわかるように銭までを扱う計算が多用されます。

10円のうまい棒を1個ずつ買えば税金は掛かりませんが、税率8%なら2本まとめて買うと21円になり、1円の税額を取られます。

これは外税の話ですが、内税の場合はどうなるでしょうか?

そういう問題を含めて仕様変更しやすくなります。

やりすぎは禁物

関数化もやりすぎは禁物です。

ある金額に対する税額を求めるのではなく、税込みの金額を返してしまうと、関数が高機能になりすぎてしまいます。

たとえば、レシートへ税額を印字したいなどといった場合に対応できなくなります。

 #include  <stdio.h>

int GetConsumptionTaxRate(void) {
    // read tax rate from file or database
}

double CalculateConsumptionTax(double price) {
    return price * GetConsumptionTaxRate() / 100;
}
double CalculateConsumptionTaxedPrice(double price) {
    return price + CalculateConsumptionTax(price);
}

int main(void) {
    double price = 1000;
    double taxedPrice = CalculateConsumptionTaxedPrice(price);

    printf("%f", taxedPrice);

    return 0;
}

合計金額の税金計算にも向きません。内税、外税などが混ざるので、単一の金額に対する計算ではもはや補いきれないからです。

課税方式をまとめる

文中にも出てきた内税、外税のほか、軽減税率も消費税額の計算をする際には省くことはできません。また、税率の切替日の考慮も必要です。

税率の切替日は、たとえば、予約販売を行うなどして、予約時点の価格を保証する必要がある場合に利用されます。

内税なのか外税なのか?
軽減税率の対象になるのか?
購入時点の税率はいくつか?

綺麗なコードを書くためには、予めこれらの情報をまとめておく必要があり、どのような値を使ってインターフェースするのかを検討します。

しかし、これまでの技術者はこれらの対応を継ぎ足し継ぎ足しでやってきました。

ゼロから仕様をまとめてコーディングした場合と、継ぎ足しながら(しかも書き直しさせてもらえない状態で)コーディングした場合で何が異なるでしょうか?

是非、このサンプルコードを改良して取り組んでみてもらえたらと思います。

この記事が気に入ったらサポートをしてみませんか?