Skip to content

AI Translated Document

  • This document was translated using an AI translation tool.
  • Due to the nature of AI translation, some sentences or terms may be interpreted differently from the original intent.
  • There may be mistranslations or inaccuracies, so please refer to the original text or consider additional review if accuracy is critical.
  • Your suggestion for a better translation would be highly appreciated.

Original Document in Korean

View Original Document (Korean)

Distrustful Coding vs Trustful Coding

HJ, Ph.D. / Software Architect
(js.seth.h@gmail.com)
Draft: October 2024 / Revised: December 2025

[Author’s Intent] This article challenges the belief that defensive validation guarantees stability. It’s not a matter of right or wrong, but of appropriateness—so the reasons why the results of each choice may differ from common assumptions are analyzed from the perspectives of determinism, quality, exceptions, and organizational cost.

Executive Summary

  • Each software component—such as functions, modules, and systems—has explicit or implicit contracts between the caller and the callee.
  • Depending on what expectations you have about whether these contracts will be upheld, they can be divided into two categories.
  • Distrust-based culture
    • Especially Java, and many modules in its ecosystem, are distrust-based.
    • Code does not trust external input, fellow developers, library users, or the system environment.
    • Type checks, input validation, and potential format conversions are performed at every stage.
  • Trust-based culture
    • Especially Python, JavaScript, and languages focused on rapid development.
    • Code trusts external input, fellow developers, library users, and the system environment.
    • Type checks, input validation, and potential format conversions are omitted as much as possible.
  • Generally, distrust-based approaches are seen as more stable, while trust-based approaches are considered more vulnerable to bugs and exceptions.
  • But is that really true? Results not rooted in language characteristics are not guaranteed by language choice. On the other hand, some results are clearly determined by language features.
  • Modern computers are deterministic systems, always guaranteeing the same result for the same context, input, and code.
  • Bugs are fundamentally a matter of code quality.
  • Exceptions are inherently unpredictable, and defensive coding cannot guarantee correct operation regardless of code quality.
  • In a situation of mutual distrust, code complexity increases, development speed drops, and readability suffers.
  • If trust is assumed, productivity and code readability improve, communication is more frequent, and collaboration is lighter.
  • Language/cultural characteristics affect how much a developer's individual ability can be leveraged.
  • Therefore, it is hard to separate this from development strategy and team composition.
  • The more elite and smaller the team, the more trust-based approaches are preferred; the more difficult and heavy the collaboration, the more distrust-based approaches are preferred.

Preface

Each component of software—such as functions, modules, and systems—operates according to predefined behaviors and possesses contracts for those behaviors. These contracts may be technically specified through function signatures, or may take the form of logical constraints.

To give a concrete example, functions or class methods have explicit or implicit contracts between the caller and the callee. This contract means that if the caller provides certain input values and context to the callee, the callee promises to perform the corresponding operation and guarantee the output. Explicit contracts are revealed through function signatures and types, while implicit contracts are expressed through documentation, conventions, or internal rules, such as the range of acceptable input values. The distinction between syntactic errors and logical errors is also closely related to which contract (explicit or implicit) has been violated.

Should we design on the premise that these contracts will be mutually upheld? Or should we design on the premise that they will not be upheld? Depending on this premise, the macro-level outcome changes significantly.

General Theory

Let's first summarize the well-known general theory.

Generally, distrust-based approaches in software development are considered advantageous for ensuring stability. This is because they do not trust external input, fellow developers, libraries, or the system environment, and actively apply validation and defensive programming at every stage. This approach can prepare for unpredictable situations and potential errors, and is considered effective for increasing system robustness and reliability. Therefore, this strategy is preferred in complex systems, large-scale collaborations, and projects with many external interfaces.

On the other hand, trust-based approaches trust both external and internal members and the system environment, and perform only minimal validation. As a result, code becomes simpler, development is faster, and collaboration is more efficient. However, trust-based coding is generally considered more vulnerable to potential bugs and exceptions. If unexpected input or situations occur in unvalidated areas, it is believed that the entire system can be affected. Due to high productivity, this strategy is often observed in small teams, rapid prototyping, and environments where internal conventions are well maintained.

But is that really so?

Deterministic Systems and the Nature of Software

Unless quantum computers become commonplace, computers are deterministic systems.
Determinism vs Nondeterminism, the P-NP problem—let's skip that, it's for graduate school.

Modern computers are deterministic systems: the same input and code always produce the same result. Thus, all software behavior is exactly predetermined. When we talk about "unexpected behavior" or "unexpected bugs," it simply means that planners, developers, or operators did not plan, identify, or test that case in advance—it does not mean the software is not behaving as coded.

Software always behaves as specified, meaning that if you use it as specified, you get the specified result. Of course, most of the time it works as intended, but in some cases, code does not behave as intended—i.e., code that fails probabilistically. But even then, the CPU is operating exactly as instructed. This, too, is a human failure in expressing complex intent in code. Of course, humans are fallible, and from a business perspective, it is reasonable to plan for such failures.

However, when it comes to development strategy or technology stack selection, it is a mistake to see human failure as code or machine failure.

Code Quality

Code quality is often said to include not only meeting functional requirements, but also maintainability, readability, extensibility, and stability. But in this context, I mean something else.

Just as good intentions do not always lead to good results, code that expresses an intention does not always reflect that intention exactly. In such cases, 95–99% of the time it works as intended, but 1–5% of the time it does not. That is, errors occur at the boundary conditions of algorithms/logic. This means the problem was misunderstood, or a correct general solution was not found.

For example, for a quadratic equation, we know the quadratic formula as a general solution. But this is just an exemplary case; if mathematics had not advanced, say in the Middle Ages, we would have had only an approximate formula, and the results would have had errors. In fact, even today, in fluid dynamics, "solving the 3D Navier-Stokes equations" is one of the seven Millennium Prize Problems, and humanity can only compute approximations.

This difference in solution quality also applies to business logic.
For any problem a service must solve, do you know the exact "general solution formula"? Or do you only have an "approximate formula"?
If only approximation is possible, the more input (i.e., the more users), the greater the need for follow-up support due to approximation errors.

This is often overlooked, but it is an important element of code quality. Maintainability, readability, extensibility, and stability are only possibilities for reducing future costs, but "result error" is a meaningful factor because it definitively increases future (operational) costs.

A Precise Understanding of Exceptions

The word "exception" is used loosely in everyday life, but it needs to be precisely divided into two meanings.

The first is a rare case; the second is the unexpected.
From the perspective of deterministic systems, these two cannot be mixed. Rare cases are infrequent, but if you repeat the process infinitely, they will inevitably occur. But "unexpected" is not defined or handled in a deterministic worldview—it is an out-of-system element.

Modern computer systems are deterministic. But reality is so complex that, at least for humans, it is not a comprehensible deterministic system, and as humans use computer systems, unexpected situations are injected into software.

When we evaluate system stability, we are not talking about how well rare cases are handled, but about resilience to unexpected situations.
Rare cases are issues to be solved by reducing "error" as mentioned in code quality. In contrast, the correct standard for stability is not terminating in the face of out-of-system shocks, but maintaining as much service as possible.

Why Input Validation Is Not Enough for Expected Problems

It is realistically impossible to perfectly validate all inputs and situations. The more complex the system, the more complex the context.

Humans make more mistakes when things are complex.

Here, context means all factors affecting the result except the input. For example, even when a web server retrieves a post by its number, many factors affect the result: the length of the post in the DB, whether it mixes HTML, whether it is blinded, current authentication info, required permissions, daily usage limits, current usage, etc. All these factors can be summed up in one word: "context."

We can be sure that computer services behave as coded, but it is hard to be sure that all context is understood and can be checked in advance. Safety devices that only work within the developer's expected range cannot be the top priority. Nor is it appropriate to pursue perfection in this regard.

In deterministic systems, it is better to have a few means that work in all situations, rather than many means with gaps. Having many incomplete means is not very meaningful as a final safety device.

Language Characteristics

Have you ever seen "Hello World" implemented in different languages?

Programming languages each have different goals, and their syntax, structure, memory management, and sync/async control are designed accordingly. Thus, even for the same function, the length and structure differ by language. Each language ecosystem also has its own culture and conventions, which are closely related to the language's goals. As a result, each language has its own color, and multilingual users can feel these differences more clearly.

Each language promotes itself based on stability, flexibility, productivity, responsiveness, speed, etc. These claims are not always true, but language characteristics do affect development strategy.

For example, Python's strict indentation improves readability through consistency, and modern JavaScript's async-await is friendly to asynchronous operations. These characteristics are fixed once a language is chosen, and affect team culture, coding style, library behavior, and development strategy.

Readability and Intuition

Readability is often thought of as simply "easy to read code." But readability is closely related to intuition, which can be explained by the theory of cognitive load. Cognitive load is a psychological concept that there is a limit to how much information a person can process at once. If code is complex or has too much exception handling/validation logic, developers must interpret a lot at once, increasing cognitive load. This leads to mistakes, misunderstandings, and maintenance difficulties.

In reality, high readability means the reader can intuitively understand a lot at once. Here, "intuition" is based on the developer's learned patterns, rules, and experience, and is greatly affected by individual interpretive ability, but also by how standard/traditional/common-sense the code and design are.

Communication depends greatly on both the sender's ability to compose messages and the receiver's literacy.

Considering cognitive load, code is more readable the more directly it is written toward its intended function. Thus, highly readable code uses variable and function names that match domain knowledge, and has little exception handling or validation. In terms of readability, even if you take a distrustful approach, it is better to write guard functions (specialized for validation, blocking logic execution) separately.

On the other hand, the "concise structure" often mentioned as affecting readability is very ambiguous. Structure can be measured quantitatively by module count, fan-in/fan-out, Big-O, loop nesting, etc., but the same numbers do not guarantee the same conciseness, and reducing these sizes often requires near-revolutionary change.

Therefore, for readability, regardless of trust/distrust, it is best to achieve it through variable and function names and usage that are simple and conform to standards/tradition/common sense/convention, from the perspective of cognitive load.

Productivity and Deterministic Systems

The IBM System/360, released in 1964, cost as much as several years or decades of a developer's salary.
Today, a developer's annual salary is more expensive than a server.

In the past, hardware was much more expensive than developer labor, so stability and efficiency were essential. But today, developer labor far outweighs hardware costs, and rapid development, iterative improvement, and agile response to market changes are more highly valued. Thus, productivity is becoming more important in software development every year.

One strategy for increasing productivity is to take advantage of the fact that computers are deterministic systems. In deterministic systems, the same context, input, and code always produce the same result. Therefore, as long as a particular area (function, class, module, layer) is not used in an unexpected way, it can be operated like a clean room, allowing you to focus on software functionality without unnecessary worry.

In other words, instead of distrust and layered validation at every level and module, you can gather validation, convention checks, and final exception barriers (crash barriers) at the outermost layer surrounding the clean room, pursuing both productivity and stability. This kind of distribution and concentration is a good example of aspect-oriented programming (AOP).

The Cost of Communication

The cost of communication is a very important factor in software development. Trust-based coding, in particular, assumes conventions, agreements, and implicit understanding among team members, and only when communication is smooth does it deliver high productivity and efficiency. Thus, when team members have different experiences or perspectives, misunderstandings and errors can occur. The resulting costs include not only bug fixes, but also redesign, schedule delays, and, most importantly, the collapse of trust.

So, does assuming distrust reduce communication costs at the expense of increased code complexity and slower development? Not really. In fact, the more defensive logic and validation in code, and the greater the distance between developers/departments, the more restrictions must be written in formal documents, and the more likely change requests will arise due to different perspectives between creators and users. Thus, assuming distrust does not simply reduce communication costs; it just changes their form and distribution. Interdependent modules must know enough about each other to work together.

Communication is unavoidable in any case, and optimizing its cost is important. Communication is ultimately the transfer of information, and optimizing that cost is easier when there is more implicit than explicit content. In a standards-based system, you can list only special cases and refer to strict standards (RFCxxxx, ISOxxxx) or de facto conventions for the rest, reducing communication costs. This shows that developer homogeneity—shared background knowledge, experience, design strategy, team operation strategy, and business vision—has a huge impact on communication costs.

Like how Map-Reduce moves computation (information processing) but not data.

However, building an educational system for this increases costs again. Generally, to cover even rough computer science knowledge, as shown by university curricula, it takes four years—excluding vacations and with some compression for personal projects, at least 2.5 years. But even 2.5 years is a bare minimum, and you cannot expect practical applicability or a high level of knowledge integration.

It is realistically impossible for a development organization to bear these costs directly, so reducing communication costs inevitably becomes a matter of hiring and screening.

Knowledge Integration - https://en.wikipedia.org/wiki/Knowledge_integration

Knowledge integration is the process of combining multiple knowledge models or perspectives into a single common model.
If information integration is simply merging data structures, knowledge integration focuses on synthesizing different viewpoints and interpretations for deeper understanding.
It includes considering how new information interacts with existing knowledge, and how to modify or extend existing knowledge.
This process resolves conflicts between knowledge, fills gaps, and improves learning and problem-solving ability.

The Workforce Pyramid

Labor costs account for almost all of the budget in software development.

Every field has a workforce pyramid. The criteria may differ, but let's assume grades S, A, B, C, D.
The more outstanding the talent, the more expensive and rare, and it is realistically impossible to hire as many as you want.
On the other hand, the less skilled are more plentiful and easier to hire, but it is harder to get high individual performance.

Experienced and outstanding talent has broad background knowledge, diverse experience, well-integrated knowledge and application skills, and, above all, accurate situational judgment. This allows them to balance many trade-offs appropriately. Therefore, it is reasonable to let them execute the best under as much freedom as possible.

On the other hand, those with less experience and only memorized knowledge, not understanding, should follow given rules, even if somewhat tight, to ensure consistent quality. That is, rather than openness or optimal solutions, it is better to operate for mass production of adequate quality through an industrial engineering approach, maintaining overall productivity.

Not all screws need to meet the same quality standards—spaceship screws and toy car screws are different.

This trade-off in workforce supply is a real constraint that limits the available choices in development strategy. Thus, you have to choose within those limits, and that choice must be linked to team composition, product architecture, development strategy, operating rules, and cost issues. Ultimately, you need to coordinate everything, from hiring and required time, to the software production process and maintenance (the product side), and investment, maintenance costs, and business profitability (the business side).

Conclusion

The choice between trust and distrust (or closely related language/cultural codes) in software development is not a simple matter, but a complex decision that must consider project characteristics, team capabilities, organizational structure, and real-world constraints.

However, the results of that decision are often quite different from conventional impressions.

Whatever you choose, you must strive to maximize the strengths and overcome the weaknesses of each approach. In many cases, there are no detailed guidelines for maximizing strengths, separate problems are mistaken for the same, or there is no consideration for overcoming the weaknesses of a strategy/culture, or closely linked issues are not reviewed.

The optimal choice can change depending on the situation, so any choice is fine.

However, you must understand the inseparable causal relationships that come with that choice, and strive for optimal operation. This is not something for a specific position, but a role required of everyone in the organization—lead, middle manager, or junior developer—so everyone should know our direction.

Development Culture / Culture Fit Test

If you want to talk about fit, start with the standard.

Many companies claim to have their own development culture, or sometimes require a culture fit test during hiring.
But very few have actually organized what their culture aims for, or what they expect from members, into practical standards or guidelines like a 'standard code of conduct.'

The idea of distinguishing Fitted/Not Fitted without even defining the direction is very sloppy.
Honestly, it's often just a "gut feeling" about whether you can work together, or traditional HR interviews with some marketing spin.

In My Case...

Below are requirements I give to new developers. The "Hard" ones may take years, but the rest are just habits.
Also, whatever strategy you take, these are needed for developer communication.

  1. No pronouns—words like "this," "here," "that," "there," "like this" are sources of misunderstanding.
  2. If you use an abbreviation, write out the full term at least once.
  3. For qualitative judgments like "risky" or "good," always provide quantitative facts as evidence.
  4. Minimize modifiers—remove as many as possible (e.g., "very many" → "many").
  5. Always state "context, purpose, expectations, current situation" when asking questions.
  6. When a choice is needed, state "premise, options, expected results, estimation steps and basis."
  7. Since common sense differs by domain, do not omit decisions with the word "common sense."
  8. (Hard) Simplify based on expertise—use metaphors/terms/concepts to convey the core accurately.
  9. (Hard) Assimilate background knowledge—it's best if you can understand and use the other person's background knowledge.

Note: The above is for "thinking people" who actively share information to achieve the best results together.
If "information hiding" is important, the guidelines should change (e.g., meetings between competitors).

See Also

Author

HJ, Ph.D. / Software Architect
(js.seth.h@gmail.com)
https://js-seth-h.github.io/website/Biography/

Over 20 years in software development, focusing on design reasoning and system structure - as foundations for long-term productivity and structural clarity.

Researched semantic web and meta-browser architecture in graduate studies,
with an emphasis on structural separation between data and presentation.

Ph.D. in Software, Korea University
M.S. in Computer Science Education, Korea University
B.S. in Computer Science Education, Korea University