SYSTEM NOTICE

Auto translation by AI. Be sure, accuracy, nuances and authorial intent may not be fully reflected.
見出し画像

[From the IT Frontline] So, that was enough—between a one-year renovation and a three-month renovation

“If 0%, it takes a year; if 1%, it takes three months.”

I read the news headline and chuckled a little.

It was an article about how a POS register manufacturer explained to a parliamentary working group that a proposal to set the consumption tax on groceries to 0% would take that long. To the public, it might seem like a strange story. It takes a year to change the tax rate from 8% to 0%, but only three months if it's 1%. Why is there such a difference just by changing one number?

But if you're an engineer in the same field, you probably thought this while reading the article—ah, well, that makes sense.

It's not surprise that comes first, but understanding. And there's another thing: a sense of déjà vu. I feel like I've heard this kind of story before. I'll write about where I heard it at the end.



The tax rate value is taken as a premise

First, I want to solve the simplest mystery. Why does 0% take longer than 1% or 3%?

To clarify, it is said that the zero consumption tax on groceries this time will not be an exemption, but rather a form where the tax rate is set to 0% while remaining taxable. For policy reasons, if it were made tax-exempt, businesses would not be able to deduct the consumption tax on their purchases. Therefore, as a system, a new situation is created: “taxable, but with a 0% tax rate.”

With that in mind, let's look inside a POS register. If you're just changing the tax rate to 1% or 3%, you can just rewrite 8 to 1 on the settings screen. Receipt printing, accounting integration, and aggregation processing all work within the existing framework. That's why it fits into three months.

But 0% is different.

In various parts of the system, the implicit premise that “the tax rate is a positive number” has been ingrained over many years. Tax amounts are calculated as “amount × tax rate,” and lines with a tax amount of 0 yen might not appear on the receipt, or the accounting system might reject a “tax amount of 0.” Invoice rounding might be written assuming a tax rate > 0. It might not be anticipated how to carry over an original transaction with a 0% tax rate in the event of a return or discount. The API validation of the linked system might not accept 0.

Setting the tax rate value to 0 is not just a matter of changing a number in the master data. It becomes a matter of reviewing all the premises ingrained in various parts of the system that “the tax rate is a positive number.”

It's not such a strange story, the people on the inside think.


Aggregation by tax rate or aggregation by tax category

Here, some engineers might not be convinced.

“No, no, it's strange to write code based on the tax rate in the first place. If you abstract it by tax category, it should work even if the tax rate value changes.”

That's correct. In fact, the system I'm building is like that. Products have tax categories attached, and the tax rate is derived from there. Aggregation processing also runs by tax category, not by tax rate. So, no matter what the tax rate is, as long as the category is the same, the aggregation framework doesn't change. Whether it's 10%, 8%, or 0%, it shouldn't stop significantly—or so it should be.

Just to be clear, I don't have the confidence to say it definitely won't stop. There might be parts of the processing that still assume a tax rate, or old logic that didn't anticipate a 0% value might be lurking in unexpected places. That's something you honestly won't know until you try. However, it probably won't reach the scale of what the major companies are saying will take a year.

Then why are many systems in the world written based on the tax rate rather than the tax category? I think this is also a natural question.

The answer, in a word, is that the principles and the simplified methods have been in agreement for a long timeだ。

In principle, consumption tax should be aggregated by tax category. From the beginning, the Consumption Tax Act has had categories for taxable, non-taxable, tax-exempt, and tax-free transactions. Tax returns are also prepared by aggregating these categories. That is the standard approach.

However, when applied to actual operations, even if we aggregated by tax rate, the answer was the same. In an era where there was only one tax rate, the 'total of taxable goods' and the 'total of goods at a 3% tax rate' were identical. No matter which way you aggregated, the resulting number was the same.

If the same number comes out, it is natural to choose the language closer to the actual work. Both customers and accountants talk more in terms of 'tax rates' than 'tax categories.' Requirement definition documents also state 'aggregate by tax rate.' That is how the business runs.

And that was enough.


In 1989, the system at that time was close to the principles.

One thing I would like to add is that at the time of the introduction of the consumption tax in 1989, the design was actually closer to the principles.

I think those who know the systems of that time will agree, but many old systems had a simple binary value like a 'taxable flag' in the product master. 0 was non-taxable, 1 was taxable. For retailers handling stamps or gift certificates, it was unavoidable to implement a distinction for non-taxable items from the start. Since there was only one tax rate, it was like hardcoding 3% for taxable goods or having one system parameter. There was no need to have a tax rate for each product in the first place.

This design is not that far from the principles. It is a structure where you divide by tax category, and the tax rate is determined by that category. The framework is close to the system at Matsukiyo's place as well.

Even when the tax rate changed to 5% in 1997 and 8% in 2014, there was no need to change the design. You just had to rewrite the hardcoded value or the parameter value. The answers from the principles and the simplified method still matched.

The problem came next.


2019, what was asked on the front lines

In October 2019, a reduced tax rate was introduced. It was the first time multiple tax rates existed side by side.

At this time, in every workplace across the country, the same kind of conversation was probably being held. In meeting rooms. In design reviews. On Slack. In casual chats. — 'Should we add a flag, or rebuild by tax category?'.

Option A is to leave the existing 'taxable flag' as is and add a 'reduced tax rate target flag' next to it. Determine using two flags for each product. The tax rate value is held directly in the product or transaction. Low man-hours, low risk, fits within the scope of subsidies. The business runs.

Option B is to extend the existing tax category code to something like '0=non-taxable, 1=standard, 2=reduced' and rebuild it into a tax category-based structure. The tax rate is pulled from the master. Aggregation is also unified by tax category. High man-hours, risk involved, and subsidies might not be enough. However, it is faithful to the principles.

Neither is wrong. At the time, both are rational judgments.

My place chose B. Intentionally. Based on the principles of the Consumption Tax Act, it should originally be aggregated by tax category. Therefore, we build it faithfully to the principles. It wasn't that I had special foresight. Because it is a principle, we built it based on the principle, that's all.

But there were probably not a few workplaces that couldn't choose B.

This is not to say that the engineers at those workplaces were negligent. If the conditions are different, it is common for B to be excluded from realistic options.


The wall of the scope of impact

What I want to write here is about the procedure that one must always go through when considering implementation changes.

Examining the scope of impact.

Any decent engineer will always think about this the moment a proposal for an implementation change is made. Which code will change its behavior due to this change? Where are the referenced locations? Can it be caught with grep, or are there dynamic calls? Will it affect linked systems? Can data integrity be maintained? Are there tests? Is a rollback possible? One identifies these things and weighs the benefits against the risks.

When dealing with the reduced tax rate in 2019, engineers on the front lines must have followed this procedure without fail.

Identify the scope of impact for Option B. —There are 300 places that reference the tax classification code. Of those, 80 are dynamic calls. There are 12 linked batches, 40 types of forms, and 8 external API integrations. To fix, test, and deploy everything would take at least half a year. It won't make the subsidy deadline. In the first place, it would change the meaning of the existing code that handles 'taxable flag = 1', so the risk of regression would be spread throughout the entire system.

Then, what about Option A? —You only need to implement the parts that reference the new flag. You don't touch the existing code. It can be finished in two months. It's within the scope of the subsidy. The risk is localized around the new flag.

When presented with this comparison, which one do you choose? That's not a matter of the industry custom of 'don't touch working code.' It's a matter of a realistic decision as a project.

And choosing Option A here is a correct decision as a professional. It is actually more irresponsible to dive in and say, 'Alright, let's rebuild it based on principles' without looking at the scope of impact. I looked at it properly, judged it to be impossible, and chose a realistic plan. That is the very job of an engineer.

There were probably many engineers across the country who wanted to choose B. Some likely proposed it. Some probably identified the scope of impact and were left speechless by how vast it was. Some likely explained it to the ordering party, couldn't get the budget approved, and had to back down. I feel like I can imagine that frustration, too.

It's not that they were foolish. The scope of impact determined the option.


Who did the subsidies help?

Here, another factor comes into play. The topic of subsidies.

When the reduced tax rate was introduced in 2019, a subsidy for reduced tax rate measures was established for small and medium-sized enterprises. The limit was 2 million yen per business operator and 200,000 yen per cash register. This was supposed to be one of the few windows to clear out technical debt.

However, the effectiveness of the subsidy is the exact opposite depending on the scale.

I don't know the inner workings of major POS manufacturers. But for a company with thousands or tens of thousands of stores, imagining from the outside how much meaning a 2 million yen subsidy has, it's probably not a scale where it can have much significance. The budget for a major renovation is in a completely different order of magnitude. The subsidy is close to an error margin, and the battle for the main budget occurs separately. I don't think it's very likely that the budget would reach Option B in that battle.

At a scale like ours, the story is the opposite. The scale of our own renovation is from several hundred thousand to several million. The subsidy can even exceed the actual costs. With that leeway, the decision to 'if we're going to renovate anyway, let's rebuild it based on principles while we're at it' gets approved. With the surplus budget, we can even reach out to design based on tax classifications.

In other words, the reason I was able to choose B in 2019 wasn't because I had technical prowess. I think it's because the scale was small, the subsidy provided leeway, and the scope of impact was within my grasp.

For large companies, the scope of impact exceeds human cognition. It takes months just to identify it. And when the result of that identification is that it's 'impossible,' B is removed from the options. Only A remains. The same subsidy system works in the direction of liquidation for us, and in the direction of minimal response for large companies.

That's all there is to it. It's nothing to be proud of. It just happened to be a good fit, that's all.

The reason our system is expected not to stop significantly even now in 2026 is not because we are great. It's because we are small.


That was enough during Y2K, too.

If you've read this far and thought, 'I feel like I've heard this story somewhere,' you're sharp.

It's the Year 2000 problem.

I was doing this job during Y2K as well. I often handled systems developed in the 90s, which were written with four-digit years. But when I opened COBOL source code written a bit earlier, two-digit years were the standard. A variable called 'YY' was silently operating under the assumption of the 1900s.

It's often explained that 'they used two digits because memory and disk space were precious.' But from my experience looking at old source code in the field, that explanation sounds a bit like an afterthought.

When I started writing business applications in COBOL, getting software to run at all was a major task. In an environment without the frameworks or libraries we have today, and with limited resources, we had to reproduce business processes in code. Building a system that worked was, in itself, the work of a professional.

Those systems perfectly handled the business of the customers at the time. Inventory management in the 70s, order processing in the 80s, payroll in the early 90s. They worked properly. They did their jobs properly. The fact that the year was two digits wasn't for optimization or out of negligence, but because two digits were enough for the business at the time. As long as it was operating within the 1900s, the answer from a date written in two digits and a date written in four digits matched perfectly. The principle was four digits, but the shortcut of two digits produced the same result.

I don't think the people who wrote that code ever imagined it would still be running 30 years later. Systems were meant to be replaced in five, or ten years at most—that was likely the mindset back then.

But it worked. Because it worked, it was used. Because it was used continuously, it couldn't be stopped. The surrounding integrations increased, and the cost of stopping it snowballed. Before I knew it, 30 years had passed.

And then, at the end of 1999, the whole world had to repaint the shadows of those two digits all at once.

The structure is exactly the same as the story this time.

  • There was a principle (4-digit year / aggregation by tax category)

  • But the shortcut also produced the same answer (while in the 1900s / while there was only one tax rate)

  • So the shortcut was chosen

  • The risk of touching the existing system was too high, so it couldn't be rewritten halfway through

  • It was used longer than expected

  • Conditions changed, and the answers from the principle and the shortcut diverged

  • At that point, a simultaneous response across the entire industry became necessary

It has been 25 years since Y2K. I think the industry felt it had learned its lesson. 'Let's design with the assumption that it will be used for a long time,' and 'Let's pay off technical debt early.' That was even written in training materials.

But the same thing is happening at a different layer.


Written briefly, used for a long time

Having written this far, I think I can see what I most wanted to say in this article.

Software is written as something short-lived, but used for a long life.

I think this is the true nature of technical debt.

When writing, we assume a lifespan of three, five, or at most ten years. We assume that during that time, business processes will change, technology will become obsolete, and the company will change—that it will inevitably be replaced at some point. Therefore, we write what is needed now, right now. YAGNI is correct. Abstracting in preparation for needs that don't exist yet is excessive.

Moreover, during periods when the answers from fundamental principles and shortcuts align, the rationality of choosing the shortcut becomes even stronger. If the same answer is reached, simpler is better. This is the very definition of good engineering.

However, code that is running cannot be easily stopped. Because it is running, there is no reason to stop it. Because there is no reason to stop it, it continues to be used. Because it continues to be used, its integration with surrounding systems increases. Because integration increases, it becomes impossible to stop. Because it cannot be stopped, it lives even longer.

That is how code intended for five years ends up running for thirty.

The time horizons of the writer and the user are structurally misaligned. And the period during which fundamental principles and shortcuts align is longer than the writer expects. This is not a matter of engineer immaturity. I believe it is a structural characteristic of the entire industry.

Therefore, it will likely repeat. Y2K was like this. A 0% consumption tax rate is the same. From here on, problems of the same structure will repeatedly manifest at different layers and under different names. IPv4, 32-bit timestamps, two-letter country codes, and other things currently thought to be "enough" will likely blow up in the same way someday.


Even so

So, if you ask if there is nothing we can do, I don't think that's the case.

It is impossible for humans to predict the future in concrete terms. We cannot see thirty years ahead. In the 80s, no one imagined a day would come when the tax rate would be 0%. I cannot imagine what kind of world the code I am writing now will be running in thirty years from now.

There is, perhaps, only one thing we can do.

When choosing a shortcut, look at the fundamental principles first before deciding.

The consumption tax law had tax categories from the beginning. The Gregorian calendar has four digits. If you read the original source of the business, it contains a framework that does not change with time. Specific tax rates and business rules change. But categories, number of digits, and the framework that makes the business possible move much more slowly than technology trends.

Instead of ignoring those fundamental principles, look at them before choosing a shortcut. Compare both, consider the scope of impact, cost, time, and risk, and if the shortcut is rational as a result, that is fine. That is a professional judgment.

What is wrong is deciding on a design based solely on shortcuts without looking at the fundamental principles. If you look at them, you can at least leave a comment somewhere in the code saying, "We are going with a shortcut this time, but it should be rewritten someday." Thirty years from now, someone might read that comment and remember the alternatives.

The reason my system works is just because it happened to be small in scale and happened to be compatible with subsidies. It's not because I'm great. And perhaps, the moment our system either grows or becomes obsolete in the future, the same trap is waiting. The code I think I am writing correctly today might be labeled "do not touch" in twenty-five years. And the person saying that will likely be right. Because if you touch it, it will break.

There is no escape. Software inevitably becomes obsolete over time. The older it gets, the harder it becomes to touch.

But, when I write now, I can take just a split second to think. I will make sure not to forget to ask myself: if this code were still running 30 years from now, who would it cause trouble for? I don't need a grand prediction of the future. Just for a moment, just for a fleeting moment, I will think about it.

Even if that doesn't change the world in a big way, it might just make the year of whoever touches this code 30 years from now a little bit shorter.


It has been 25 years since Y2K. The same kind of news is circulating again. I don't know what will blow up in the next 25 years.

However—someday, someone will likely say of this code, too, 'And that was enough.' I probably can't write code that won't be said about. I just want to leave behind something that will be a little less resented when it is said.

That is the best I can do right now.

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