Even translations between human languages (which allows for ambiguity) can be messy. Imagine if the target language is for a system that will exactly do as told unless someone has qualified those actions as bad.
Even translations between human languages (which allows for ambiguity) can be messy. Imagine if the target language is for a system that will exactly do as told unless someone has qualified those actions as bad.
Also, as someone else said, consider the root causes of an issue, whether those are in code logic or business ops or some intersection between the two.
When I save twenty hours of a client's money and my own time, by telling them that a new software feature they want would be unnecessary if they changed the order of questions their employees ask on the phone, I've done my job well.
By the same token, if I'm bored and find weird stuff in the database indicating employees tried to perform the same action twice or something, that is something that can be solved with more backstops and/or a better UI.
Coding business logic is not a one-way street. Understanding the root causes and context of issues in the code itself is very hard and requires you to have a mental model of both domains. Going further and actually requesting changes to the business logic which would help clean up the code requires a flexible employer, but also an ability to think on a higher order than simply doing some CRUD tasks.
The fact that I wouldn't trust any LLM to touch any of my code in those real world cases makes me think that most people who are touting them are not, in fact, writing code at the same level or doing the same job I do. Or understand it very well.
So right now an LLM and the developer you describe here are two very different thing and an LLM will, by design, never replace you
> The fact that I wouldn't trust any LLM to touch any of my code in those real world cases makes me think that most people who are touting them are not, in fact, writing code at the same level or doing the same job I do. Or understand it very well.
I agree with this specifically for agentic LLM use. However, I've personally increased my code speed and quality with LLMs for sure using purely local models as a really fancy auto complete for 1 or 2 lines at a time.
The rest of your comment is good, bit the last paragraph to me reads like someone inexperienced with LLMs looking to find excuses to justify not being productive with them, when others clearly are. Sorry.
And to have boundless contextual awareness… dig a rabbit hole, but beware that you are in your own hole. At this point you can escape the hole but you have to be purposefully aware of what guardrails and ladders you give the agent to evoke action.
The better, more explicit guardrails you provide the more likely the agent is able to do what is expected and honor the scope and context you establish. If you tell it to use silverware to eat, be assured it doesn’t mean to use it appropriately or idiomatically and it will try eating soup with a fork.
Lastly don’t be afraid of commits and checkpoints, or to reject/rollback proposed changes and restate or reset the context. The agent might be the leading actor, but you are the director. When a scene doesn’t play out, try it again after clarification or changing camera perspective or lighting or lines, or cut/replace the scene entirely.
It’s only tedious once per codebase or task, then you find the less tedious recipe and you’re done.
You can even get others to do the tedious part at their layer of abstraction so that you don’t have to anymore. Same as compilers, cpu design, or any other pet of the stack lower than the one you’re using.
I like to explain my work as "do whatever is needed to do as little work as possible".
Being by improving logs, improving architecture, updating logs, pushing responsibilities around or rejecting some features.
More significantly though, OP seems right on to me. The basic functionality of LLMs is handy for a code writing assistant, but does not replace a software engineer, and is not ever likely too no matter how many janky accessories we bolt on. LLMs are fundamentally semantic pattern matching engines, and are only problem solvers in the context of problems that are either explicitly or implicitly defined and solved in their training data. They will always require supervision because there is fundamentally no difference between a useful LLM output and a “hallucination” except the utility rating that a human judge applies to the output.
LLMs are good at solving fully defined, fully solved problems. A lot of work falls into that category, but some does not.
Just to add, I think there are three things that LLMs don't address here, but maybe it's because they're not being asked the broader questions:
1. What are some reasonable out-of-band alternatives to coding the thing I'm being asked to code?
2. What kind of future modifications might the client want, and how can we ensure this mod will accommodate those without creating too many new constraints, but also without over-preparing for something that night not happen?
3. What is the client missing that we're also missing? This could be as simple as forgetting that under some circumstances, the same icon is being used in a UI to mean something else. Or that an error box might obscure the important thing that just triggered the error. Or that six years ago, we created a special user level called "-1" that is a reserved level for employees in training, and users on that level can't write to certain tables. And asking the question whether we want them to be able to train on the new feature, and if so, whether there are exceptions to that which would open the permissions on the DB but restrict some operations in the middleware.
"What are we missing" is 95% of my job, and unit tests are useless if you don't know all the potential valid or invalid inputs.
This sentiment of, a human will always be needed, there’s no replacement for human touch, the stakes are too high, is as old as time
You just said, quite literally, that people leveraging LLMs to code are not doing it at your level - that’s borders on hubris
The fact of the matter is that like most tools, you get out of AI what you put into it
I know a lot of engineers and this pride, this reluctance to accept the help is super common
The best engineers on the other hand are leveraging this just fine, just another tool for them that speeds things up
We’re living it. We see it every day. The business leaders cannot be convinced that this isn’t making less skilled developers more productive.
Those rules are also very fuzzy and only get defined more formally by the coding process.
But that's just plain wrong and a proper developer would be allowed to change that. If you're not authenticating properly, you get a 401. That means you can't prove you're who you say you are.
If you are past that, i.e. we know that you are who you say you are, then the proper return code is 403 for saying "You are not allowed to access what you're trying to access, given who you are".
Which funnily enough seems to be a very elusive concept to many humans as well, never mind an LLM.
I.e. that would be the appropriate thing to do if you're trying to prevent leakage of information i.e. enumeration of resources. But you should not return 401 for this still. A 404 is the appropriate response for pretending that "it's just not there" if you ask me. You can't return 404 when it's not there and a 403 when you have no access if enumeration is bad.
So for example, if you don't have access to say the settings of a repo you have access to, a 403 is OK. No use pretending with a 404, because we all know the settings are just a feature of Github.
However, pretending that a repo you don't have access to but exists isn't there with a 404 is appropriate because otherwise you could prove the existence of "superSecretRepo123" simply by guessing and getting a 403 instead of a 404.
It really boils down to what scenario you have in mind. Developers do interact with product managers and discussions do involve information flowing both ways. Even if a PM ultimately decides what the product should do, you as a developer have a say in the process and outcome.
Also, there are always technological constraints, and some times even practical constraints are critical. A PM might want to push this or that feature but if it's impossible to deliver on a specific deadline they have no alternative to compromise, and the compromise is determined by what developers call out.
Successful projects do this. Ideally, front loaded.
Unsuccessful projects attempt to reify the chaos.
There's plenty of that work, and it goes by many names ("enterprise", others).
But lots and lots and lots of programmers are concerned with using computers for computations: making things with the new hardware that you couldnt with the old hardware being an example. Embedded, cryptography, graphics, simulation, ML, drones and compilers and all kinds of stuff are much more about resources than business logic.
You can define up business logic to cover anything I guess, but at some point its no longer what you meant by that.
I went and got an MBA to try and get around this. It didn't work.
When the employer business isn't shipping software, engineers have no other option than actually learn the business as well.
They usually code for the happy path, and add edge cases as bugs are discovered in production. But after a while both happy path and edge cases blend into a ball of mud that you need the correct incantation to get running. And it's a logic maze that contradict every piece of documentation you can find (ticket, emails). Then it quickly become something that people don't dare to touch.
When a non-developer writes code with an LLM, their ability to write good code decreases. But at the same time, it goes up thanks to more "business context."
In a year or two, I imagine that a non-developer with a proper LLM may surpass a vanilla developer.
Agree strongly, and I think this is basically what the article is saying as well about keeping a mental model of requirements/code behavior. We kind of already knew this was the hard part. How many times have you heard that once you get past junior level, the hard part is not writing the code? And that It's knowing what code to write? This realization is practically a right of passage.
Which kind of begs the question for what the software engineering job looks like in the future. It definitely depends on how good the AI is. In the most simplistic case, AI can do all the coding right now and all you need is a task issue. And frankly probably a user written (or at least reviewed, but probably written) test. You could make the issue and test upfront and farm out the PR to an agent and manually approve when you see it passed the test case you wrote.
In that case you are basically PM and QA. You are not even forming the prompt, just detailing the requirements.
But as the tech improves can all tasks fit into that model? Not design/architecture tasks - or at least without a new task completion model than described above. The window will probably grow, but its hard to imagine that it will handle all pure coding tasks. Even for large tasks that theorhetically can fit into that model, you are going to have to do a lot of thinking and testing and prototyping to figure out the requirements and test cases. In theory you could apply the same task/test process but that seems like it would be too much structure and indirection to actually be helpful compared to knowing how to code.
I agree with the PM role, but with such low requirements that anyone can do it.
The good ones wear multiple hats and actually define the problem, learns sufficiently about a domain to interact with it or the experts on said domain and figures out what are the short Vs long term tradeoffs to focus on the value and not just the technical aspect.
An earlier effort at AI was based on rules and the C. Forgy RETE algorithm. Soooo, rules have been tried??
Rules engines were traditionally written in Prolog or Lisp during the AI wave they were cool.
Forgy was Charles Forgy.
For a "rules engine", there was also IBM's YES/L1.
But software architects (especially of various reusable frameworks) have to maintain the right set of abstractions and make sure the system is correct and fast, easy to debug, that developers fall into the pit of success etc.
Here are just a few major ones, each of which would be a chapter in a book I would write about software engineering:
ENVIRONMENTS & WORKFLOWS Environment Setup Set up a local IDE with a full clone of the app (frontend, backend, DB). Use .env or similar to manage config/secrets; never commit them. Debuggers and breakpoints are more scalable than console.log. Prefer conditional or version-controlled breakpoints in feature branches. Test & Deployment Environments Maintain at least 3 environments: Local (dev), Staging (integration test), Live (production). Make state cloning easy (e.g., DB snapshots or test fixtures). Use feature flags to isolate experimental code from production.
BUGS & REGRESSIONS Bug Hygiene Version control everything except secrets. Use linting and commit hooks to enforce code quality. A bug isn’t fixed unless it’s reliably reproducible. Encourage bug reporters to reset to clean state and provide clear steps. Fix in Context Keep branches showing the bug, even if it vanishes upstream. Always fix bugs in the original context to avoid masking root causes.
EFFICIENCY & SCALE Lazy & On-Demand Lazy-load data/assets unless profiling suggests otherwise. Use layered caching: session, view, DB level. Always bound cache size to avoid memory leaks. Pre-generate static pages where possible—static sites are high-efficiency caches. Avoid I/O Use local computation (e.g., HMAC-signed tokens) over DB hits. Encode routing/logic decisions into sessionId/clientId when feasible. Partitioning & Scaling Shard your data; that’s often the bottleneck. Centralize the source of truth; replicate locally. Use multimaster sync (vector clocks, CRDTs) only when essential. Aim for O(log N) operations; allow O(N) preprocessing if needed.
CODEBASE DESIGN Pragmatic Abstraction Use simple, obvious algorithms first—optimize when proven necessary. Producer-side optimization compounds through reuse. Apply the 80/20 rule: optimize for the common case, not the edge. Async & Modular Default to async for side-effectful functions, even if not awaited (in JS). Namespace modules to avoid globals. Autoload code paths on demand to reduce initial complexity. Hooks & Extensibility Use layered architecture: Transport → Controller → Model → Adapter. Add hookable events for observability and customization. Wrap external I/O with middleware/adapters to isolate failures.
SECURITY & INTEGRITY Input Validation & Escaping Validate all untrusted input at the boundary. Sanitize input and escape output to prevent XSS, SQLi, etc. Apply defense-in-depth: validate client-side, then re-validate server-side. Session & Token Security Use HMACs or signatures to validate tokens without needing DB access. Enable secure edge-based filtering (e.g., CDN rules based on token claims). Tamper Resistance Use content-addressable storage to detect object integrity. Append-only logs support auditability and sync.
INTERNATIONALIZATION & ACCESSIBILITY I18n & L10n Externalize all user-visible strings. Use structured translation systems with context-aware keys. Design for RTL (right-to-left) languages and varying plural forms. Accessibility (A11y) Use semantic HTML and ARIA roles where needed. Support keyboard navigation and screen readers. Ensure color contrast and readable fonts in UI design.
GENERAL ENGINEERING PRINCIPLES Idempotency & Replay Handlers should be idempotent where possible. Design for repeatable operations and safe retries. Append-only logs and hashes help with replay and audit. Developer Experience (DX) Provide trace logs, debug UIs, and metrics. Make it easy to fork, override, and simulate environments. Build composable, testable components.
ADDITIONAL TOPICS WORTH COVERING Logging & Observability Use structured logging (JSON, key-value) for easy analysis. Tag logs with request/session IDs. Separate logs by severity (debug/info/warn/error/fatal). Configuration Management Use environment variables for config, not hardcoded values. Support override layers (defaults → env vars → CLI → runtime). Ensure configuration is reloadable without restarting services if possible. Continuous Integration / Delivery Automate tests and checks before merging. Use canary releases and feature flags for safe rollouts. Keep pipelines fast to reduce friction.
You should probably go do that, rather than using the comment section of HN as a scratch pad of your stream of consciousness. That's not useful to anyone other than yourself.
Is this a copypasta you just have laying around?
If irony was a ton of bricks, you'd be dead
Not really. It goes off on a tangent, and frankly I stopped reading the wall of text because it adds nothing of value.
If you write a wall of text where the first pages are inane drivel, what do you think are the odds that the rest of that wall of text suddenly adds readable gems?
Sometimes a turd is just a turd, and you don't need to analyze all of it to know the best thing to do is to flush it.
It really isn't. There is no point to pretend it is, and even less of a point to expect anyone should waste their time with an unreadable and incoherent wall of text.
You decide how you waste your time, and so does everyone else.
1. Set up a local IDE with a full clone of the app (frontend, backend, DB).
Thus the app must be fully able to run on a small, local environment, which is true of open source apps but not always for for-profit companies
2. Use .env or similar to manage config/secrets; never commit them.
A lot of people don’t properly exclude secrets from version control, leading to catastrophic secret leaks. Also when everyone has their own copy, the developer secrets and credentials aren’t that important.
3. Debuggers and breakpoints are more scalable than console.log. Prefer conditional or version-controlled breakpoints in feature branches.
A lot of people don’t use debuggers and breakpoints, instead doing logging. Also they have no idea how to maintain DIFFERENT sets of breakpoints, which you can do by checking the project files into version control, and varying them by branches.
4. Test & Deployment Environments Maintain at least 3 environments: Local (dev), Staging (integration test), Live (production).
This is fairly standard advice, but it is best practice, so people can test in local and staging.
5. Make state cloning easy (e.g., DB snapshots or test fixtures).
This is not trivial. For example downloading a local copy of a test database, to test your local copy of Facebook with a production-style database. Make it fast, eg by rsync mysql innodb files.