見出し画像

【ITの、現場から。】それで、十分だった——1年かかる改修と、3ヶ月で済む改修のあいだに

「0%なら1年、1%なら3ヶ月」

ニュースの見出しを読んで、少し笑ってしまった。

食料品の消費税を0%にする案について、POSレジのメーカーが国会の実務者会議でそう説明した、という記事だった。世間から見ると、おかしな話に見えるかもしれない。税率を8%から0%にするのに1年。でも1%にするなら3ヶ月。数字を1つ変えるだけで、どうしてそんなに差が出るのか。

でも、同業のエンジニアなら、この記事を読みながら、たぶんこう思った——ああ、まあ、そうだよね。

驚きじゃなく、納得が先に来る。そしてもう一つ、既視感も来る。この手の話、前にも聞いた気がする。どこで聞いたのかは、最後に書く。


税率という値が、前提になっている

まず、いちばん素朴な謎から解いておきたい。なぜ0%だけが、1%や3%より時間がかかるのか。

ひとつ確認しておくと、今回の食料品消費税ゼロは、食料品を非課税にするのではなく、課税のまま税率を0%にする、という形になると言われている。政策的な理由があって、非課税にしてしまうと事業者が仕入の消費税を控除できなくなるからだ。だから、制度としては「課税だけど税率0%」という、新しい状況が生まれる。

そのうえで、POSレジの中を覗いてみる。税率を1%や3%に変えるだけなら、設定画面で8を1に書き換えれば済む。レシート印字も、会計連携も、集計処理も、既存の枠組みの中で動く。だから3ヶ月で収まる。

でも0%は違う。

システムの各所には、「税率は正の数である」という暗黙の前提が、長年かけて染み込んでいる。税額は「金額 × 税率」で計算されて、税額0円の行はレシートに出ないかもしれないし、会計システムが「税額0」を弾くかもしれない。インボイスの端数処理は税率>0を前提に書かれているかもしれない。返品や値引きで、税率0の元取引をどう引き継ぐか、想定されていないかもしれない。連携先のAPIバリデーションが0を受け付けないかもしれない。

税率の値を0にする、というのは、単にマスタの数字を変える話じゃない。システム各所に染み込んだ「税率は正の数」という前提を、全部見直す話になる。

そんなにおかしな話じゃないでしょ、と中の人は思う。


税率ごとの集計か、税区分ごとの集計か

ここで、一部のエンジニアは腑に落ちないかもしれない。

「いやいや、そもそも税率を前提にコードを書くのがおかしいでしょ。税区分で抽象化しておけば、税率の値が変わっても動くはず」

その通りだ。実際、僕のところで作っているシステムは、そうなっている。商品には税区分がついていて、税率はそこから引く構造。集計処理も、税率ごとじゃなく、税区分ごとに走る。だから税率が何%だろうが、区分が同じなら、集計の枠組みは変わらない。10%でも8%でも0%でも、大きくは止まらない——はずだ。

念のため言っておくと、絶対に止まらないと言い切れる自信まではない。どこかの処理で税率を前提にしている箇所が残っているかもしれないし、0%という値を想定していなかった古い判定が、思わぬところに潜んでいるかもしれない。そこは素直に、やってみないとわからない。ただ、大手が1年かかると言っている規模の話には、たぶん、ならない。

じゃあ、世の多くのシステムは、なぜ税区分じゃなく税率を前提に書かれているのか。これも当然の疑問だと思う。

答えは、一言で言えば、原理原則と簡便法の答えが、長いあいだ一致していたからだ。

原理原則でいえば、消費税の集計は税区分ごとに行うべきだ。消費税法には最初から、課税、非課税、不課税、免税という区分がある。申告書も区分ごとに集計して出す。それが筋だ。

でも、現場の業務に落としたとき、税率ごとに集計しても、答えは同じだった。税率が1種類しかない時代、「課税区分の商品の合計」と「税率3%の商品の合計」は、完全に一致する。どっちで集計しても、出てくる数字は同じ。

同じ数字が出るなら、現場の言葉に近い方を選ぶのが自然だ。お客さんも会計士も、「課税区分」より「税率ごと」の言葉で会話する。要件定義書にも「税率ごとに集計」と書かれる。それで業務は回る。

それで、十分だった。


1989年、その時のシステムは原理原則に近かった

ひとつ補足しておきたいのは、1989年の消費税導入時点で、設計はむしろ原理原則に近かったということだ。

当時のシステムを知っている人なら頷いてくれると思うけれど、昔のシステムの多くは、商品マスタに「課税フラグ」のような単純な2値を持っていた。0が非課税、1が課税。切手や商品券を扱う小売なら、導入当初から非課税の区別は実装せざるを得なかった。税率は1種類だから、課税商品については3%をハードコードするか、システムパラメータとして1つ持つか、そんな感じ。商品ごとに税率を持つ必要は、そもそもなかった。

この設計は、原理原則にも、そんなに遠くない。税区分で分けて、税率は区分から決まる構造。まつきよのところのシステムとも、骨格は近い。

1997年に5%、2014年に8%と税率が変わっても、設計は変える必要がなかった。ハードコードされた値か、パラメータの値を書き換えれば終わり。原理原則と簡便法の答えは、まだ一致していた。

問題は、その次に来た。


2019年、現場で問われたこと

2019年10月、軽減税率が導入された。税率が複数並ぶ、初めての事態だった。

このとき、全国のあらゆる現場で、たぶん、同じような会話が交わされていた。打ち合わせ室で。設計レビューで。Slackで。立ち話で。——「フラグを足すか、税区分で作り直すか」

選択肢Aは、既存の「課税フラグ」はそのままにして、「軽減税率対象フラグ」を横に足す。商品ごとに2つのフラグで判定する。税率の値は、商品やトランザクションに直接持たせる。工数小、リスク小、補助金の範囲に収まる。業務は回る。

選択肢Bは、既存の税区分コードを「0=非課税、1=標準、2=軽減」のように拡張して、税区分ベースの構造に作り直す。税率はマスタから引く。集計も税区分ごとに統一する。工数大、リスクあり、補助金だけでは足りないこともある。ただし原理原則には忠実。

どちらも、間違いじゃない。その場では、どちらも合理的な判断だ。

僕のところはBを選んだ。意図的に。消費税法の原理原則に照らせば、本来は税区分で集計するべきだ。であれば、原理原則に忠実に作る。特別な先見性があったわけじゃない。原理原則だから、原理原則で作った、それだけだ。

でも、Bを選べなかった現場も、たぶん、少なくなかった。

これは、その現場のエンジニアが怠慢だった、という話じゃない。条件が違えば、Bが現実的な選択肢から外れることは、普通にある。


影響範囲という壁

ここで書いておきたいのが、実装変更を検討するときに必ず通る手順の話だ。

影響範囲の検討。

まともなエンジニアなら、実装変更の提案が出た瞬間、必ずこれを考える。この変更で、どのコードが動きを変えるか。参照している箇所はどこか。grepで引っかかるか、動的呼び出しはないか。連携先に影響は出るか。データ整合性は保たれるか。テストはあるか。ロールバックは可能か。これを洗い出して、メリットとリスクを天秤にかける。

2019年の軽減税率対応のとき、現場のエンジニアは、必ずこの手順を踏んだはずだ。

選択肢Bの影響範囲を洗い出す。——税区分コードを参照している箇所が三百。うち、動的呼び出しが八十。連携バッチが十二個、帳票が四十種類、外部API連携が八箇所。全部直してテストしてデプロイするのに、最低でも半年。補助金の期限に間に合わない。そもそも、既存の「課税フラグ=1」を扱うコードの意味を変えることになるから、デグレリスクが全体に分散する。

じゃあ、選択肢Aは。——新しいフラグの参照箇所だけを実装すればいい。既存コードは触らない。2ヶ月で収まる。補助金の範囲内。リスクは新しいフラグ周辺に局所化される。

この比較を突きつけられて、どちらを選ぶか——それは「動いてるコードはいじるな」という業界慣習の話じゃない。プロジェクトとしての現実的な判断の話だ。

そして、ここで選択肢Aを選ぶのは、プロフェッショナルとして正しい判断だ。影響範囲を見ずに「よし、原理原則で作り直そう」と突っ込むほうが、むしろ無責任だ。きちんと見て、無理だと判断して、現実的な案を選んだ。それはエンジニアの仕事そのものだ。

Bを選びたかったエンジニアは、たぶん、全国に大勢いた。提案した人もいただろう。影響範囲を洗い出して、あまりの広さに言葉を失った人もいただろう。発注側に説明して、予算が通らなくて引き下がった人もいただろう。その悔しさも、想像できる気がする。

彼らが愚かだったわけじゃない。影響範囲が、選択肢を決めたのだ。


補助金は、誰を助けたか

ここで、もうひとつの要素が絡んでくる。補助金の話。

2019年の軽減税率導入のときは、中小向けに軽減税率対策補助金が整備された。1事業者あたり上限200万円、レジ1台あたり20万円。これが、技術的負債を清算できる数少ない窓になる——はずだった。

でも、補助金の効き方は、規模によって真逆になる。

大手POSメーカーの内情を、僕は知らない。ただ、数千店舗や数万店舗を抱える会社にとって、200万円という補助金がどれくらいの意味を持つか、外から想像するに、たぶん、大きな意味を持てる規模じゃない。本改修の予算は桁がまったく違う世界だ。補助金は誤差に近くて、本予算との戦いは別途発生する。その戦いで、選択肢Bまで予算が届くかというと、相当厳しいんじゃないかと思う。

うちみたいな規模だと、話が逆になる。自社の改修規模が数十万から数百万。補助金が実費を上回ることすらある。その余裕で、「どうせ改修するなら、ついでに原理原則で作り直そう」という判断が通る。浮いた予算で、税区分ベースの設計まで手を伸ばせる。

つまり、僕が2019年にBを選べたのは、技術力があったからじゃない。規模が小さくて、補助金で余裕が出て、影響範囲が手の内に見えたから、だと思う。

大手は影響範囲が人間の認知を超える。洗い出すだけで数ヶ月。そして洗い出した結果が「無理」だったとき、Bは選択肢から外れる。Aしか残らない。同じ補助金制度が、うちには清算の方向に、大手には最小限対応の方向に働く。

それだけの話だ。誇れることじゃない。たまたま相性が良かった、というだけの話だ。

うちのシステムが2026年の今も大きくは止まらない見込みなのは、偉いからじゃない。小さいからだ。


Y2Kのときも、それで十分だった

ここまで読んで、「この話、どこかで聞いた気がする」と思った人がいるなら、鋭い。

2000年問題だ。

Y2Kのときも、僕はこの仕事をしていた。ちょうど90年代に開発されたシステムを担当することが多くて、そちらは西暦4桁で書かれていた。でも、少し前に書かれたCOBOLのソースを開くと、年は2桁が定番だった。「YY」という変数が、黙って1900年代を前提にして動いていた。

よく「メモリやディスクが貴重だったから2桁で省略した」と説明される。でも、現場で古いソースを見てきた感覚としては、その説明は、少し後付けに聞こえる。

COBOLで業務アプリを書き始めた頃、ソフトウェアは、そもそも動かすこと自体が大きな仕事だった。今みたいにフレームワークもライブラリもない、環境も貧弱な中で、業務をコードで再現する。動くシステムを作ること、それ自体がプロフェッショナルの仕事だった。

そのシステムは、当時のお客さんの業務を完璧に回していた。1970年代の在庫管理、80年代の受発注、90年代初頭の給与計算。ちゃんと動いた。ちゃんと仕事をした。年が2桁だったのは、最適化のためでも、怠慢のためでもなく、当時の業務には、2桁で十分だったからだ。1900年代の範囲で動いている限り、2桁で書いた日付と、4桁で書いた日付の答えは、完全に一致した。原理原則は4桁だけど、簡便法の2桁で同じ結果が出た。

30年後にそのコードがまだ動いている、なんて、書いた人は思っていなかったと思う。5年、長くて10年で、システムは新しくなるもの——当時の感覚は、たぶんそうだった。

でも、動いた。動いたから、使われた。使われ続けたから、止められなくなった。周りの連携が増えて、止めるコストが雪だるまになった。気づいたら、30年経っていた。

そして1999年の暮れ、全世界で一斉に、2桁の影を塗り直すことになった。

構造が、今回の話と、そっくりなのだ。

  • 原理原則があった(年4桁/税区分で集計)

  • でも、簡便法でも答えが一致した(1900年代の間は/税率が1種類の間は)

  • だから簡便法が選ばれた

  • 既存を触るリスクが大きすぎて、途中で書き直せなかった

  • 想定より長く使われた

  • 条件が変わって、原理原則と簡便法の答えがズレた

  • そのとき、全産業レベルの一斉対応が必要になった

Y2Kから、25年。業界は学んだ気になっていたと思う。「長く使われる前提で設計しよう」「技術的負債を早めに返そう」。研修資料にもそう書いてあった。

でも、別のレイヤーで、同じことが起きている。


短く書かれ、長く使われる

ここまで書いてきて、たぶん、記事で一番言いたかったことが見えてきた。

ソフトウェアは、短命なものとして書かれ、長命に使われる

これが、負債の正体だと思う。

書くときは、3年、5年、長くても10年くらいを想定している。そのあいだに業務が変わる、技術が古くなる、会社が変わる——どこかで必ずリプレースされる、と思っている。だから、今必要なものを、今書く。YAGNIが正しい。今ないニーズに備えて抽象化するのは、過剰だ。

しかも、原理原則と簡便法の答えが一致している期間は、簡便法を選ぶ合理性がさらに強くなる。同じ答えが出るなら、シンプルな方がいい。これは良いエンジニアリングの定義そのものだ。

でも、動いたコードは、簡単には止められない。動いているから、止める理由がない。止める理由がないから、使い続けられる。使い続けられるから、周りのシステムとの連携が増える。連携が増えるから、止められなくなる。止められないから、さらに長く生きる。

そうやって、5年のつもりだったコードが、30年動く。

書き手と使い手の時間軸が、構造的にずれている。そして、原理原則と簡便法の一致期間が、書き手の想定より長い。これは、エンジニアの未熟さの話じゃない。業界全体の、構造的な特性だと思う。

だから、たぶん、繰り返す。Y2Kもそうだった。消費税0%もそうだ。これからも、別のレイヤーで、別の名前で、同じ構造の問題が繰り返し顕在化する。IPv4、32ビットタイムスタンプ、国コード2文字、その他、今は「それで十分」と思われている何かが、たぶんいつか、同じように火を噴く。


それでも

じゃあ、僕らにできることは何もないのか、というと、そうでもないと思う。

未来を具体的に予測することは、人間には無理だ。30年先は見えない。80年代に、税率が0%になる日が来るなんて、誰も想像していなかった。今、書いているコードが30年後にどういう世界で動いているか、僕にも想像できない。

できることは、たぶん、ひとつだけある。

簡便法を選ぶときに、原理原則も一度見てから選ぶ、ということだ。

消費税法には最初から税区分があった。西暦には4桁ある。業務の原典を読めば、そこには時間で変わらない骨格が書かれている。具体的な税率や業務ルールは変わる。でも、区分とか、桁数とか、その業務を成立させている骨格は、技術トレンドよりずっとゆっくりしか動かない。

その原理原則を、無視するんじゃなく、見たうえで、簡便法を選ぶ。両方を見比べて、影響範囲、コスト、時間、リスクを検討して、その結果として簡便法が合理的なら、それでいい。それはプロフェッショナルな判断だ。

ダメなのは、原理原則を見ずに、簡便法だけで設計を決めてしまうことだ。見てしまえば、「今回は簡便法でいくけど、いつか書き直すべきだな」というコメントくらいは、コードのどこかに残せる。30年後、誰かがそのコメントを読んで、選択肢を思い出せるかもしれない。

僕のところのシステムが動くのも、たまたま規模が小さかったからで、たまたま補助金と相性が良かったからだ。偉いからじゃない。そしてたぶん、うちがこの先、大きくなるか、古くなるか、どちらかが起きた瞬間、同じ罠が待っている。今日正しく書いているつもりのコードも、25年後には「いじるな」と言われているかもしれない。そしてそう言う人は、たぶん正しい。触ると壊れるから。

逃げ道はない。ソフトウェアは、時間の中で必ず古くなる。古くなるほど、触れなくなる。

でも、今、書くときに、一瞬だけ考えておくことはできる。もし、このコードが30年後まで動いていたら、誰を困らせるだろうか——そう自問することだけ、忘れないようにする。大がかりな未来予測はいらない。一瞬、ほんの一瞬、考えるだけ。

それでも世界が大きく変わるわけじゃないけれど、30年後、このコードを触る誰かの1年を、ほんの少しだけ短くできるかもしれない。


Y2Kから、25年。また同じ種類のニュースが流れている。次の25年後、何が火を噴くのか、僕にはわからない。

ただ——このコードも、いつか誰かに「それで、十分だった」と言われるのだろう。言われないコードを書くことは、たぶんできない。ただ、言われたときに、少しだけ恨みが軽いものを残しておきたい。

それが、今の僕にできる、精一杯だ。

いいなと思ったら応援しよう!

コメント

コメントするには、 ログイン または 会員登録 をお願いします。
少人数のIT会社です。AIを本格活用しながら、少人数で大きな仕事ができる体制を作っています。 私の記事を見て怒らないでください そこに私はいません 眠ってなんかいません クララが書いてます
【ITの、現場から。】それで、十分だった——1年かかる改修と、3ヶ月で済む改修のあいだに|まつきよ
word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word

mmMwWLliI0fiflO&1
mmMwWLliI0fiflO&1
mmMwWLliI0fiflO&1
mmMwWLliI0fiflO&1
mmMwWLliI0fiflO&1
mmMwWLliI0fiflO&1
mmMwWLliI0fiflO&1