Pangram verdict · v3.3
We believe this text is mainly AI, with some human-written content.
AI likelihood · overall
AIArticle text · 1,496 words · 1 segments analyzed
There is a misunderstanding about software engineering that AI is making increasingly visible: we tend to confuse writing code with building software. There is some overlap in certain aspects, but they’re not the same thing. Writing code means translating an idea into instructions a computer can execute. Building software means deciding which instructions should exist in the first place, how they should interact, which constraints matter, what’s the cost of the decisions, which tradeoffs can be considered acceptable, and how the resulting system can evolve without collapsing under its own constraints and limitations. Let’s start from a basic premise: AI is an essential tool because it is remarkably good at the first problem. The second is where software engineering actually begins. The difficult part was never typing the code Consider a relatively ordinary engineering requirement. We need to process incoming events and update some data. And these are some of the first questions that arise during a technical discussion: Should we process them synchronously? Should we put them into a queue? Do we need exactly-once processing, or is at-least-once sufficient? Can the system tolerate eventual consistency? What happens when processing fails halfway through? Should we retry? How many times? What happens if the consumer is unavailable for three hours? Do events need to preserve ordering? How much traffic do we expect today? What about two years from now? What are the consequences if an event is processed twice? … and so on. These questions have very little to do with syntax. Programming language’s choice matters, because affects team’s fluency, team’s performance, system’s performance, safety, maintainability, tooling, and operational characteristics, but it does not answer the fundamental questions. The difficult part is choosing the architecture that represents the right set of compromises. And there is rarely a universally correct answer. 1 problem can have N completely different correct solutions This is particularly obvious when software exists inside a business. Imagine two companies asking their engineering teams to build what sounds like exactly the same feature. Their requirements may look identical on paper. But: Company A has 500 users while Company B has 20 million. Company A may have three engineers maintaining the system. Company B may have 200. One may require strong consistency because mistakes have serious financial consequences. The other may happily accept eventual consistency in exchange for availability and throughput. One company may need to ship in three weeks. Another may expect the system to remain operational for fifteen years. One may already have Kafka, Kubernetes, PostgreSQL, observability infrastructure, and engineers experienced with distributed systems. Another may have a single application server and a PostgreSQL database maintained by four developers. The technically impressive solution for one company could be an irresponsible solution for the other. This is why architecture cannot be reduced to asking: What is the best way to implement X? The correct question is usually closer to: Given these constraints, this team, this business, this infrastructure, this budget, these risks, and the expected evolution of the product, what is the most appropriate way to implement X, today? That is a radically different question. AI generates solutions. Engineers own tradeoffs. This distinction is becoming increasingly important because of how AI is being adopted by software organizations. AI is extraordinarily useful for software development. We use it as an accelerator: generating boilerplate, exploring APIs, proposing implementations, finding potential bugs, explaining unfamiliar code, producing tests, comparing approaches, or simply reducing the amount of mechanical work required to turn an idea into working code. But there is a dangerous tendency to extend this capability into something much broader: delegating engineering judgment itself You can: give an AI model a requirement and ask it to design the system, “no mistakes”: It will design one. ask it to choose a database: It will choose one. ask whether you should introduce a queue, microservices, caching, CQRS, event sourcing, Kubernetes, Redis, or another abstraction: It will give you an answer. The existence of an answer, however, does not mean the underlying engineering problem has been solved. The real problem is that the correct decision depends on context, often an enormous amount of context, that needs hours, days, or even weeks to analyze, understand, and evaluate, often involving multiple departments within the same organization, and frequently leaving gray areas that are difficult to formalize and may become a challenge when future changes are required. This is how real world works: some of that context exists in documentation. Much of it does not. It exists in conversations with customers. In the history of the product. In the skills of the engineering team. In operational incidents from three years ago. In budget constraints. In deadlines. In contractual obligations. In the weird behavior of a legacy system nobody wants to touch. In knowing that a customer is likely to request a particular feature six months from now. And sometimes it exists simply in experience: recognizing that a theoretically elegant architecture will become an operational nightmare for the team expected to maintain it. You cannot treat all of this as a small detail that will somehow be captured by adding another paragraph to an AI prompt. There is no architecture without tradeoffs Engineering is largely the discipline of deciding which problems you are willing to have. Even if you don’t work at FAANG scale, your projects may still need to process enough data that making the right engineering decisions becomes essential to maintaining adequate performance, without having to throw expensive hardware at systemic inefficiencies. Even “simple” choices come with factors that need to be considered: Normalize the database aggressively, and you may gain integrity while making certain queries more complicated. Denormalize it, and you may improve read performance while increasing synchronization complexity. Add caching, and you reduce latency while introducing invalidation problems. Introduce asynchronous processing and you decouple components while creating new failure modes. Split a monolith into microservices and you gain independent deployment boundaries while introducing distributed-system complexity. Optimize aggressively and you may gain performance while sacrificing readability. Abstract aggressively and you may reduce duplication while making the system harder to understand. Avoid abstraction and the opposite problem eventually appears. None of these can be evaluated independently. Every optimization spends complexity somewhere. The job of an engineer is therefore not simply to make the system work. It is to decide where complexity belongs. Algorithmic thinking matters more, not less This is why I think algorithmic thinking becomes more important in the age of AI, not less. And by algorithmic thinking I do not simply mean knowing textbook algorithms or being able to implement a red-black tree from memory. By algorithmic thinking, I mean defining a set of basic rules to follow and applying everyday. I’ve put together a ten-point set of principles: Decompose the problem. Identify invariants. Understand data flows. Choose appropriate data structures. Reason about time and space complexity. Identify contention. Understand failure modes. Recognize which operations are expensive and which ones actually matter. Separate essential complexity from accidental complexity. Understand what should happen when assumptions stop being true. I know, in the time of boosted productivity, this can sounds weird. AI can generate ten implementations in the time it previously took an engineer to write one. The challenge is to use AI as a tool to apply these principles through targeted, controlled iterations, while keeping a human in the loop. That makes choosing the right implementation ten times more important. This is what boosted productivity really means. Your programming language is a tool, not an identity The same applies to programming languages. There is value in choosing the right tool for the job. Languages have different strengths, ecosystems, performance characteristics, type systems, concurrency models, deployment models, and safety guarantees. Those differences matter, but engineering discussions sometimes give programming-language choice “overvaluation”. In many business systems, choosing between two reasonable languages will matter considerably less than choosing the wrong architecture, the wrong data model, or the wrong operational strategy. Pick a language appropriate for the scenario. If several languages are appropriate, there is significant value in choosing the one your team understands deeply. Don’t base your decisions on the assumption that LLMs perform better with one PL or another. Always put the team first. The 5-matters rules are: Fluency matters. Ecosystem maturity matters. Maintainability matters. Debugging matters. Deployment matters. This is why, today more than ever, with LLMs at our disposal, I encourage engineers not to be religiously loyal to a single PL, but to become as polyglot as possible, learning to use AI assistants as learning tools, not merely as code generators, always double checking official docs. The real optimization target is the lifetime of the system Another mistake is optimizing software exclusively for its current requirements. Modern software cannot be static, and it must be built to respond to following changes: Users Customers Traffic Business models Regulations Teams Infrastructures Every point has a weight that can change overtime. So the feature that appears insignificant today can become the core workflow two years later.