ブログトップ

公開日:

更新日:

13 min read

エンジニアリング

シンプルなエンジニアリングが生む大きな価値

シンプルなエンジニアリングが生む大きな価値のイメージ

エンジニアリングは難しくありません。 システム開発を円滑に進めるための知恵とも言えます。 これをするかしないかで、システム開発や保守の効率が大きく変わります。 特に問題なのは「バグを誘発するような問題を防ぐ」です。 これまでに間違ったコメントや不適切なネーミングなどがバグを誘発することを事例として挙げてきました。 今回はDBのテーブル設計における事例を用いて、エンジニアリングの有用性を説明します。

思わぬ発見

CRMシステムの改修作業中、ロジックを精査している時に思わぬことに気がつきました。 消費税率の扱い方がテーブルによって異なっていたのです。

   -- デフォルト税率テーブル
CREATE TABLE define_taxrate (
    trate INT NOT NULL    -- 税率が整数(10%は10として格納)
);

-- 商品情報テーブル
CREATE TABLE productinfo (
    ptaxrate DOUBLE NOT NULL,    -- 税率が小数(10%は0.1として格納)
    /* その他のカラム */
);

-- 売上テーブル
CREATE TABLE salespro (
    trate INT NOT NULL,    -- 税率が整数(10%は10として格納)
    /* その他のカラム */
);

同じシステム内で、同じ「消費税率」という値なのに、あるテーブルでは整数(10%は10)として、 別のテーブルでは小数点(10%は0.1)として格納されていました。 文法的には正しく、テーブル設計としても一見問題ないように見えます。 しかし、これは明らかにバグを誘発する構造です。

おまけにカラム名まで違うので、コードを書く際に混乱する可能性が高いです。 いえ、混乱しました。イライラしました。だから、この記事を書いています(笑)

笑えないって…

シンプルな解決策

既存のシステムが稼働中であり、テーブル構造の変更は影響が大きすぎるため、別のアプローチを取ることにしました。 データアクセス用のクラスを新たに作成し、今回、大きく改修するページからは、 このクラスを通じてのみデータベースにアクセスするようにしたのです。

   /**
 * 税率管理クラス
 *
 * システム内での税率の取り扱いを統一するためのクラスです。

 * データベース内では税率の表現方法が以下のように異なりますが、
 * このクラスを通じて取得する場合は常に小数点形式(0.1)で統一されます。

 *
 * - define_taxrate.trate: INT型(10%は10として格納)
 * - productinfo.ptaxrate: DOUBLE型(10%は0.1として格納)
 * - salespro.trate: INT型(10%は10として格納)
 */
class TaxRateManager {
    private $db;

    public function __construct(Database $db) {
        $this->db = $db;
    }

    /**
     * 商品の税率を取得
     *
     * @param string $productKey 商品キー
     * @return float 税率(小数点形式:10%の場合は0.1)
     */
    public function getProductTaxRate(string $productKey): float {
        $result = $this->db->query(
            "SELECT ptaxrate FROM productinfo WHERE xKey = ?",
            [$productKey]
        );

        // すでに小数点形式で格納されているのでそのまま返す
        return $result->ptaxrate;
    }

    /**
     * デフォルト税率を取得
     *
     * @param string $key 税率キー
     * @return float 税率(小数点形式:10%の場合は0.1)
     */
    public function getDefaultTaxRate(string $key): float {
        $result = $this->db->query(
            "SELECT trate FROM define_taxrate WHERE xKey = ?",
            [$key]
        );

        // INT型(10)から小数点形式(0.1)に変換
        return (float)$result->trate / 100;
    }

    /**
     * 売上データの税率を取得
     *
     * @param string $salesKey 売上キー
     * @return float 税率(小数点形式:10%の場合は0.1)
     */
    public function getSalesTaxRate(string $salesKey): float {
        $result = $this->db->query(
            "SELECT trate FROM salespro WHERE xKey = ?",
            [$salesKey]
        );

        // INT型(10)から小数点形式(0.1)に変換
        return (float)$result->trate / 100;
    }
}

このクラスを使うことで、アプリケーション側では税率を常に小数点形式(0.1)として扱えるようになります:

   $taxManager = new TaxRateManager($db);

// 税率の取得(すべて小数点形式で統一)
$defaultTax = $taxManager->getDefaultTaxRate('STANDARD');  // 0.1
$productTax = $taxManager->getProductTaxRate('PRODUCT001');  // 0.1
$salesTax = $taxManager->getSalesTaxRate('SALE001');  // 0.1

// 税額計算(混乱の余地がない)
$price = 1000;
$taxAmount = $price * $productTax;  // 常に小数点形式(0.1)で計算

さらに、クラスには詳細なPHPDocコメントを付け、なぜこのような実装になっているのかを明確に説明しています。 また、簡単な設計書もMarkdown形式で作成し、システムの構造や問題点、その対策についても記録を残しました。

ラッパーパターンという解決策

今回採用した解決方法は、一般的な「ラッパーパターン」の良い例といえます。 ラッパーパターンとは、問題のあるコードや一貫性のないインターフェースを、 新しい統一されたインターフェースで「包む」(ラップする)という設計手法です。

この方法には以下のような利点があります:

  1. 既存のコードに影響を与えることなく、新しい形式のインターフェースを提供できる
  2. 問題のある実装を隠蔽し、安全な使用方法を強制できる
  3. 将来的な変更が発生しても、ラッパー内部の修正で対応が可能

今回のケースでは、TaxRateManagerクラスが「ラッパー」として機能しています。 データベースの不統一な税率の表現方法を隠蔽し、常に一貫した形式(小数点形式)でデータを提供することで、 アプリケーション側のコードをシンプルかつ安全に保っています。

このように、ラッパーパターンは特に既存システムの改修において非常に有用なエンジニアリング手法の一つです。 システムを大きく変更することなく、問題のある部分を安全に扱えるようにできます。

エンジニアリングの効果

このような小さな工夫が、システム開発に大きな効果をもたらします。 税率の扱いが統一されることで、計算ミスや型変換の誤りによるバグを防ぐことができます。 また、明確なコメントと文書により、開発者の混乱を防ぎ、保守作業の効率を上げることができます。

これは決して複雑な技術を使った解決策ではありません。 シンプルな「カプセル化」の考え方を使い、問題のある構造を隠蔽し、安全な使い方を提供しているだけです。

なぜテーブルによって税率のカラムの型が違うのでしょうか?その理由が、このシステムの特徴を見ていくと推察できます。

まず、このシステムのコードには非常に特徴的な傾向があります。 それは、コードの共通化をせずに、コピペで済ませている箇所が非常に多いということです。 おそらく、税率に関しても同じことが起きたのでしょう。 既存のテーブルのカラムの型を確認するのが面倒だったので、その時々で適当な型を使ってしまったのではないでしょうか。

実は、このシステムを作った人には興味深い特徴がありました。 シェルが使えず、SQLもコマンドラインでは実行できないそうです。 phpMyAdminで操作していると話していました。 コマンドラインで ‘describe table名;’ と入力するだけで簡単に確認できることが、 phpMyAdminを立ち上げて画面遷移しないと分からない。 そういう手間を面倒くさがったのかもしれません。

もうひとつ、このシステムのコードにはある傾向が感じられました。 それは、目先の楽を優先し、共通化や再利用性といった将来的なことを考えない、 非常に刹那的(※)なプログラミングだということです。 「今が良ければいい。」というやつですね。 別の言い方をすれば、「動けばいい。」という考え方です。

※刹那的:その場限りで、先のことを考えない様子。 「刹那」は「ごく短い時間」を意味し、将来への配慮や長期的な視点が欠けている状態を表現しています。

実は、プログラムには作った人の性格が如実に表れるんです。

まとめ

今回はロジックを精査してる時点で気づいたからよかったのですが、 まさか消費税率がテーブルによって型が異なるなんて、想像していませんでした。 むしろ、なぜ型を統一しなかったのか不思議なくらいです。 エンジニアリングを無視した設計や実装は、このような「まさか」を引き起こします。

このような事例は、開発現場で日常的に遭遇する可能性があります。 大切なのは、バグを誘発しやすい構造に気づき、シンプルな方法で解決することです。 必ずしも完璧な設計や実装である必要はありません。 現実的な制約の中で、できる改善を着実に行っていくことが重要です。

今回の場合「消費税率は小数点で扱う」というルールを定め、 どこか(設計書やコメント)に明確に記載しておけば済んだ話です。

エンジニアリングは特別なスキルではありません。 日々の開発の中で、「この構造は分かりにくいかもしれない」「ここは間違いやすそうだ」 という気づきを大切にし、シンプルな改善を重ねていくことです。 それが結果として、プロジェクトの品質と効率を大きく向上させることにつながります。

目先の面倒くささを回避しようとして、後からトラブルにハマる。 ゴミを投げ捨てようとしたものの、ゴミ箱から外れて、結局拾ってそれを捨てるのは非効率です。 拾ったごみをまた投げ捨ててゴミ箱から外れて、それを再び繰り返すのは、もはや不毛です。

急がば回れ、です。