AI 资讯
The Best Engineering Teams Use AI and Junior Developers Differently
Over the past year, I've watched a lot of engineering teams go through the same adoption pattern with AI tools. They start using GitHub Copilot or Claude. Productivity goes up. And then someone in a meeting asks the question: "Do we still need as many junior developers?" I think that question reveals exactly the wrong mental model. The teams getting the most value from AI tools aren't the ones who figured out what AI can automate. They're the ones who figured out what AI should automate, and then designed their workflows around that distinction. That sounds like a small difference. It isn't. Most of the debate around AI and junior developers focuses on the wrong question: can AI do what juniors do? In a previous article, I explored why that question leads teams in the wrong direction. In another, I looked at what happens when organizations quietly remove the work juniors need to grow. This article is about what the best teams actually do instead. They don't pick AI over junior developers. They redesign how work flows. The AI and Junior Developers Debate Is Asking the Wrong Question The argument goes like this: AI can generate code, write tests, and produce documentation. Junior developers also generate code, write tests, and produce documentation. Therefore, AI can replace junior developers. This looks logical at the task level. But it misses something important. Junior developers aren't primarily valuable for their output. They're valuable for what they become while producing that output. Every bug they debug, every test they write, every pull request they review is quietly building something that doesn't appear in any sprint metric. You can automate a task. You can't automate the learning that comes from doing it. That's where the replacement narrative breaks down. What AI Is Actually Good At After using AI coding tools seriously for a while, certain patterns become clear. AI is fast and reliable for repetitive, well-defined work: boilerplate, standard implementat
开发者
LISKOV SUBSTITUTION PRINCIPLE
A parent class must be able to be substituted by its child classes without breaking the application. In practice, this helps to organize the idea of inheritance, as it prevents us from extending a parent class only to later remove an already implemented method or do a “throw new Error(‘Not implemented’)”. Making us much more careful during planning. THE BIGGEST SYMPTOM OF ERROR Unfortunately, it is a symptom that appears late, but it is exactly when we are going to make a new implementation. You realize you violated Liskov when you are going to build a class or subclass and need to purposely throw an error in the implementation of a method. Exactly because that method shouldn't be there, but it is. A BAD EXAMPLE For example, in a delivery system. In this case, the “Delivery” class should be the parent/base for the other implementations. But the ‘MotoboyDelivery’ class breaks this. Code Example: // BAD: The subclass breaks the parent class contract. class Delivery { public calculateShipping (): number { return 15.0 ; } public getTrackingCode (): string { return " TRK123456789 " ; } } class MotoboyDelivery extends Delivery { public calculateShipping (): number { return 8.0 ; } // ERROR! There is no tracking code. public getTrackingCode (): string { throw new Error ( " Motoboys do not have a tracking code. " ); } } THE SOLUTION For those who do not yet know the 'Liskov Substitution Principle', it might seem that fitting in a sequence of 'if's is the solution. But in reality, the ideal path is to rethink how this abstraction is built. A good guiding principle is to think that a child class must always be able to take the place of the parent, without breaking the application. A GOOD EXAMPLE Still in the delivery system. ‘Delivery’ now has ‘TrackableDelivery’ in the middle of the way. With this, each “leaf”/edge of the application inherits what makes the most sense and nothing is broken. Code Example: interface Delivery { calculateShipping (): number ; } interface Trackab
AI 资讯
The day I asked three LLM agents to rewrite legacy Java for me — and what actually happened
1. The question that started everything Three weeks into my internship, my supervisor sat down across from me and asked, very casually: "OK your NLP pipeline extracts intentions and rules from legacy Java. Nice. And then what? " I looked at him. I looked at my laptop. I looked back at him. The whole project — Pulsar Modernizer — was supposed to eventually turn legacy Java into modern Spring Boot code. My part was the "understand the old code" part. F1 = 0.857 on the annotated corpus, a shiny React UI, everything humming in Docker. But the "and then?" was doing a lot of work in that sentence. That evening I wrote in my notes: "Nobody has actually tried the generation part. Everyone assumes it'll be easy because LLMs. That is very obviously wrong." So I decided to try. 2. Why "just prompt an LLM to rewrite it" doesn't work The naive move — feed the old code and the extracted rules to an LLM and say "please modernize this" — has three problems and I hit all of them in the first hour: The model hallucinates. It happily invents helper classes that don't exist and calls methods with the wrong signature. You have no criterion for stopping. The model tells you "it's done ". OK. Is it? By what test? You have no criterion for equivalence. Even if it compiles, how do you know the new code actually does what the old one did? I needed something more constrained than "prompt it and pray". 3. The setup — a chain, not a monolith I ended up building three specialized agents in sequence: IntentCard + RuleCards │ ▼ [APIDesigner] ──► JSON contract (class, methods, DTOs, throws) │ ├───────────────┐ ▼ ▼ [CodeGenerator] [TestGenerator] │ │ ▼ ▼ .java *Test.java │ │ └────► verifier (mvn test) The key insight: each rule extracted from the legacy code should become a test that the generated code has to pass. This flips the whole thing. I don't trust the LLM. I trust javac and JUnit. I did all of this on a local model — Qwen 2.5 Coder 3B via Ollama. No cloud APIs, no data leaving my Mac. On a
AI 资讯
PRINCÍPIO DA SUBSTITUIÇÃO DE LISKOV
Uma classe mãe deve ser capaz de ser substituída pelas suas classes filhas sem que a aplicação quebre. Isso na prática ajuda a organizar a ideia de herança, já que nos faz evitar estender uma classe mãe, apenas para depois remover um método já implementado ou fazer um “throw new Error(‘Not implemented’)”. Fazendo com que tenhamos mais cuidado no planejamento. O MAIOR SINTOMA DE ERRO Infelizmente é um sintoma que aparece de forma tardia, mas é justamente quando vamos fazer uma nova implementação. Você percebe que feriu o Liskov quando você vai construir uma classe ou subclasse e precisa lançar um erro proposital na implementação de um método. Justamente porque aquele método não deveria estar ali, mas está. UM EXEMPLO RUIM Por exemplo em um sistema de entregas. Nesse caso a classe “Delivery” deveria ser a mãe/base para as demais implementações. Mas a classe ‘MotoboyDelivery’ quebra isso. Exemplo de Código: // RUIM: A subclasse quebra o contrato da classe mãe. class Delivery { public calculateShipping (): number { return 15.0 ; } public getTrackingCode (): string { return " TRK123456789 " ; } } class MotoboyDelivery extends Delivery { public calculateShipping (): number { return 8.0 ; } // ERRO! Não tem código de rastreio. public getTrackingCode (): string { throw new Error ( " Motoboys não possuem código. " ); } } A SOLUÇÃO Para quem ainda não conhece o 'Liskov Substitution Principle', pode parecer que encaixar uma sequência de ifs é a solução. Mas na verdade o caminho ideal é repensar como essa abstração é construída. Um bom norte é pensar que uma classe filha sempre deve ser capaz de substituir o lugar da mãe, sem quebrar a aplicação. UM EXEMPLO BOM Ainda no sistema de entregas. ‘Delivery’ agora tem no meio do caminho ‘TrackableDelivery’. Com isso, cada “folha”/ponta da aplicação herda quem faz mais sentido e nada é quebrado. Exemplo de Código: interface Delivery { calculateShipping (): number ; } interface TrackableDelivery extends Delivery { getTrackingCode (): st
AI 资讯
HTTP Caching Explained: max-age, ETag and Why Your Users Still See Last Week's CSS
📺 Prefer to watch? 90-second YouTube Short · 💬 Telegram Originally published on software-engineer-blog.com . You fixed the CSS. You deployed. You opened the site and checked it yourself — perfect. Then a customer sends a screenshot of last week's layout. Nothing is broken. No deploy failed, no CDN is lying to you, no file is corrupt. The browser is doing exactly what you told it to do, several days ago, in a header you probably never wrote by hand. This is the part of web performance that gets skipped, because caching looks like a setting rather than a contract. It is a contract. And like any contract, the interesting part is not what it gives you — it is what you can no longer do once you have signed it. Throughout this post I will use one running example: PlantPal , a small plant shop. One stylesheet ( app.css ), one logo ( logo.png ), one API endpoint ( /api/products ). The floor: a page load is not one thing Before caching means anything, you have to see what it is acting on. Loading PlantPal's homepage is not a request. It is roughly 40 separate requests — the HTML, the stylesheet, a few fonts, the logo, a dozen product images, the JavaScript bundle, the product API. Each one is a full round trip: DNS is probably warm, but you still pay connection setup, the request, the server's think time, and the bytes coming back. The numbers for a first visit: ~40 requests 1.2 MB transferred 2.1 s to a usable page Which gives us the only sentence in this post that you actually need to remember: The fastest request is the one the browser never sends. Not a faster server. Not a closer edge node. Not a smaller file. No request at all. Everything below is a way of getting closer to that. max-age: buying silence The blunt instrument is Cache-Control : HTTP / 1.1 200 OK Content-Type : text/css Cache-Control : max-age=31536000 31536000 is one year in seconds. You are telling every browser that receives this response: keep this copy and use it for a year without asking me again. O
AI 资讯
Beyond Writing Code: The Core Mindset of a Modern Software Engineer
Many beginner developers believe software engineering is all about mastering programming languages, framework syntaxes, and clearing error logs. In reality, writing code is only a fraction of the actual job. The true core of software engineering lies in analyzing complex domain problems, evaluating deep trade-offs, and designing robust systems that stand the test of time. Let's explore what it genuinely takes to transition from a coder to a modern software engineer with the right engineering mindset. 1. Writing Code vs. Solving Problems Anyone with a healthy brain can learn syntax and write functional scripts after a few tutorials. However, the real engineering challenge begins long before you touch your IDE. Understanding the Domain: Breaking down business logic and user requirements. Evaluating Alternatives: Assessing whether a feature needs a complex custom hook or a simple native state. Long-term Value: Building solutions that won't break when requirements shift tomorrow. 2. The Importance of Maintainability Code is read much more often than it is written. When you are working on large-scale applications, you are never coding alone—even if you are solo for now, your future self is essentially a stranger six months down the line. Crafting clean, self-documenting code with meaningful, intention-revealing names. Enforcing single-responsibility functions to keep modules decoupled. Using predictable patterns so teammates can navigate and scale the application without getting buried in technical debt. 3. Pragmatic System Design and Trade-offs There is no silver bullet in software engineering. Every architectural decision—whether choosing a database, state management library, or caching strategy—comes with heavy trade-offs. Performance vs. Development Speed: Knowing when to optimize early and when to ship MVP code. Scalability vs. Complexity: Avoiding over-engineering simple features just because a shiny new tool exists. Balancing Constraints: A great engineer evaluate
AI 资讯
Designing Reliable APIs for Production Applications: Lessons From Building Real-World Digital Products
Designing Reliable APIs for Production Applications: Lessons From Building Real-World Digital Products APIs are often described as the “bridge” between different parts of an application, but building a production-ready API involves much more than sending data from a frontend to a backend. Through my experience building full-stack applications, I've learned that a good API needs to be designed around reliability, security, maintainability and the actual needs of its users. Here are some of the principles I now consider when designing APIs: Design around resources, not screens An API shouldn't simply mirror the frontend interface. It should expose meaningful resources and operations that can evolve independently from the UI. Validate everything at the API boundary Data coming from a client should never be trusted automatically. Request validation, type checking and clear error responses help prevent invalid data from propagating through the system. Authentication is only the beginning An authenticated user should not automatically have access to every resource. APIs need appropriate authorisation and access-control rules for sensitive operations. Design predictable errors A useful API doesn't just return “something went wrong.” Clients need consistent status codes and structured error responses so that applications can respond appropriately. Think about idempotency This becomes particularly important when an API handles operations such as payments, orders or other actions that shouldn't accidentally happen twice because of a network retry. Don't expose unnecessary data APIs should return what the client needs rather than exposing entire database records. This reduces unnecessary data transfer and can also reduce the risk of accidentally exposing sensitive information. Logging and observability matter An API can appear perfect during development and still fail in production. Good logging and monitoring make it possible to understand what happened when requests fail, la
AI 资讯
Moving from AI-Assisted Engineering to AI-Agentic Software Engineering
Moving from AI-Assisted Engineering to AI-Agentic Software Engineering The rise of AI coding assistants has transformed how developers write software. Tools like GitHub Copilot, ChatGPT, Claude, and Gemini have significantly improved developer productivity by helping generate code, explain concepts, and automate repetitive tasks. However, the industry is now entering the next evolution: AI-Agentic Software Engineering . Instead of AI simply assisting developers, AI agents can now take ownership of entire software engineering tasks—from requirement analysis and architecture design to implementation, testing, documentation, and code reviews. The challenge is no longer whether to use AI, but how to integrate AI agents into a structured Software Development Lifecycle (SDLC). This requires moving away from vibe coding toward specification-driven development , where AI agents operate using well-defined requirements, standards, and engineering principles. Today, I'd like to discuss two of the most popular frameworks enabling this transition. 1. Spec Kit Spec Kit is a specification-driven framework designed for Human + AI collaborative software development . The philosophy is simple: define the specification before generating the code . Rather than asking an AI to build an application from a vague prompt, Spec Kit encourages teams to create structured specifications, architectural decisions, and engineering principles that guide AI throughout the development lifecycle. Some key benefits include: Structured and repeatable software development Better requirement traceability Consistent architecture decisions Reduced AI hallucinations Lower development costs through predictable AI interactions Support for selecting the most appropriate LLM based on project requirements Integration of quality engineering practices from the beginning of the SDLC Spec Kit is particularly valuable for engineering teams that want to adopt AI without sacrificing software quality or maintainability.
开发者
My First Engineering Job Is Teaching Me Something I Didn't Expect
Well, first real job and I still haven't gotten used to waking up for a 6:30 AM shift... I've been...
AI 资讯
Your Database Is Making 4 Promises. Here's What ACID Means.
Introduction Your program keeps opening transactions. A signup writes a new user row. A checkout debits one account and credits another. A form submission updates three related tables at once. You wrap it all in BEGIN and COMMIT and move on, trusting that the database will handle whatever happens in between. Most of the time it does. But what is it actually promising you when it handles that? And what does it have to do behind the scenes to keep that promise? Say a user transfers ₹1,000 from Account A to Account B. The application runs two updates: subtract 1,000 from A, add 1,000 to B. Now say the server crashes right after the first update runs but before the second one does. Account A: -₹1,000 Account B: +₹0 That money didn't move. It vanished. No error message fixes that, and no user accepts "the server restarted" as an explanation for their missing balance. This is the exact problem a set of guarantees called ACID was built to solve. Most developers can recite the acronym, Atomicity, Consistency, Isolation, Durability, without being able to explain what any of the four words actually promise, or what the database has to do internally to keep those promises. This article tries to fix that. -- 1. What Is a Transaction? Before ACID makes sense, you need to understand what a transaction actually is. A transaction is a group of one or more database operations treated as a single logical unit of work. Either the whole group succeeds, or none of it does. The bank transfer above is a textbook transaction: two updates that only make sense together. In SQL, a transaction usually looks like this: BEGIN ; UPDATE accounts SET balance = balance - 1000 WHERE id = 1 ; UPDATE accounts SET balance = balance + 1000 WHERE id = 2 ; COMMIT ; BEGIN tells the database "everything from here on is one unit." COMMIT tells it "we're done, make it permanent." If something goes wrong in between, a constraint violation, a crash, the application deciding to cancel, the database can issue a RO
AI 资讯
What are you working on? #01
What are you working on? I hear these words in my day-to-day. And sometimes, when I hear them, there’s this little brain freeze that happens because my brain is probably trying to put into words the amount of things that have wandered through my head in the last 24 hours. 😂 So I thought, okay, let me try something. I want to take some of those wandering thoughts, explorations, things I'm trying out and things I'm learning, and put them into writing. This is going to be a series where I come and talk about what I'm working on — software engineering, product, work, people, faith, relationships, rest, and whatever else happens to be taking up space in my head at the moment. So, what am I working on? I recently started writing backend code, and there’s a bit of a backstory to that. I built this frontend commerce store years ago where people can come and shop for furniture. At the time, I used a backend-as-a-service to handle the backend side of the application. Now, I’m coming back to that same system and writing the backend myself with NestJS. I wanted to go beyond just consuming a backend and actually understand what is happening behind the scenes. The learning process is a bit stretching at the moment because I’m getting familiar with a lot of new concepts. Tiring and frustrating? Yes. But the feeling when I finally understand the reason behind something is always refreshing. That has been really rewarding lately. I'm also in the middle of launching a mobile application at my workplace, going through system design classes, figuring out how to get the best out of my engineers (AI sub-agents, by the way 😅), and occasionally imagining that dream job where you get to build products that serve millions of people and work with really brilliant minds. Also, I discovered the productivity rush that comes with using large monitors. 😂 Then there's learning how to rest while also trying to close out all the open loops in my head. Building reading habits. Figuring out what to pri
AI 资讯
'We'll fix it later' is a loan. Here's the interest rate
Every time someone on your team says "we'll clean it up later," they're taking out a loan. The problem is that almost nobody checks the interest rate — until it bankrupts an entire sprint. Technical debt is the most-used and least-understood metaphor in software. Used well, the metaphor is genuinely powerful, because debt is exactly the right mental model — including the part everyone forgets: interest. Debt isn't the same as bad code First, a correction. Technical debt isn't just messy or bad code. It's a deliberate or accidental trade: you took a shortcut — skipped the abstraction, hardcoded the value, deferred the test — to move faster now, in exchange for a cost later. Sometimes that's a smart, conscious decision. Shipping today to validate an idea, knowing you'll refactor if it works, is often the right call. The debt isn't the problem; unmanaged, invisible debt is. The interest is the point Here's what the metaphor gets exactly right and most teams ignore. Debt accrues interest . Every feature you build on top of a shortcut is a little harder to build. Every bug in the messy area takes a little longer to fix. The shortcut doesn't cost you once — it taxes every future change that touches it, and that tax compounds. This is why teams mysteriously slow down over time. It rarely feels like a wall; it feels like everything gradually getting harder, estimates creeping up, small changes turning into week-long ordeals. That's compounding interest on debt nobody tracked. I've watched a system's velocity get quietly reclaimed by exactly this, and paying it down deliberately is part of how I approach building things properly . Good debt, bad debt The framework that makes this actionable: Deliberate, prudent debt: "We know the right design, but we're shipping the simple version to hit the deadline, and we'll fix it." Fine — it's a conscious, tracked trade. Accidental, reckless debt: "What's a design pattern?" — debt taken on through inexperience, invisibly, with no plan t
AI 资讯
We Will Get You Through It!
There is a comedy sketch from Bob & Tom that starts with a hilariously impossible promise: overnight delivery by train, from New York to Los Angeles. At one point, someone asks if they can really get a 2,000-pound package across the country overnight by rail. The answer is delivered with absolute confidence: “Norfolk and Waypal, overnight. Absolutely. Positively.” The name is doing some careful work. It lets you hear the phrase that nobody has actually said out loud. No way, pal. When I end up leading a project with six weeks left and something that feels like four months of work to do, I start the internal kickoff by telling the team to go watch that sketch. No other explanation. Just go watch it, then come back. Then I tell them: “Absolutely, positively, we will get you through it. There's Norfolk and Waypal, we are gonna to do it.” That does not mean we are going to do the thing exactly as it was originally promised. It means we are going to get through it. Absolutely. Positively. There is a difference. Laugh at the impossible first I think newer developers especially need permission to laugh at impossible requirements. An 800-pound gorilla from New York to Los Angeles overnight by train is impossible in a way that is easy to laugh at. A project that needs a full cloud environment, API work, a mobile application in the app stores, production deployment, security approvals, and a dozen other things in six weeks? That can feel less funny when it is sitting in your sprint board. But it may be just as impossible if we take the requirements literally. The first danger on a crunch project is shame. A junior developer can look at an impossible deadline and wonder if they are missing something. Maybe everyone else understands how this gets done. Maybe it is a talent problem. Maybe if they just worked harder, they could turn six weeks into twelve. Nope. Sometimes the work is just Norfolk and Waypal . Humor does not solve the problem. It lowers the temperature enough that
AI 资讯
Your `if` statements are a database nobody can query
Somewhere in your codebase there is a line that looks like this: if ( user . plan === ' enterprise ' || user . tenantId === ' acme-corp ' ) { // ... } Nobody remembers who wrote the second half of that condition. It has been there for two years. It is almost certainly still load-bearing. Here is the thing I want to convince you of: that line isn't code. It's data, and it's stored in the worst possible place. Every conditional that encodes a business decision is really a row. It has a condition, an outcome, and a bunch of implicit context about when it applies. You have hundreds of these rows. They're spread across a dozen services, written in four different styles, and there is no way to list them. You have a database. You just can't query it. Five things a database gives you that your code doesn't Once you look at it this way, the problems stop feeling like sloppiness and start feeling structural. There's no schema. One service decides a customer is premium by checking plan === 'premium' . Another checks subscription.tier > 2 . A third checks a flag that was set during a migration in 2023. All three are "the same rule" until the day they aren't, and there's nothing in the system that would notice the drift. There's no way to query it. Try to answer a simple question: what rules are live in production right now? You can't. Someone has to read the source. And grep won't save you, because the interesting conditions are compound, spread across guard clauses, and half of them are expressed as an early return rather than an if . There are no migrations. Changing a rate limit from 100 to 200 requires a pull request, a review, a CI run, and a deploy window. You're pushing a code change through the full pipeline to change a number. It's a schema migration with none of the tooling that makes schema migrations tolerable. There's no audit log. Git tells you who edited the line. It doesn't tell you who decided the rule, when it was supposed to expire, or whether the customer it
开发者
The Fix Was Committed. The Old Value Kept Running.
Originally published on hexisteme notes . I deleted three ambient API keys from my shell profile. Then I ran the standard clean-room check — spawn a shell with no inherited environment at all, env -i HOME="$HOME" /bin/zsh -lc 'echo "${VARNAME:-unset}"' , and read unset back for all three. That command doesn't lie: a shell started with an empty environment can only see what the current profile puts there, so if it reports the variable missing, the profile is clean. I closed the loop, reconnected my tools, and moved on. Minutes later I reconnected a review tool I run for cross-vendor sanity checks, and it came back healthy — with eight providers registered, one of them authenticated with a key I had just deleted. Not a cached credential from an old response. A live, working authentication, using a value that no longer existed anywhere on disk. The fix was committed. The old value kept running. Two different questions that sound like one "Did I fix the config?" and "Is the fix in effect?" collapse into a single question in your head, because in the common case they're the same event: you edit a file, the next thing that reads the file gets the new value, done. env -i answers the first question perfectly. It says nothing about the second, because it doesn't test any process that already exists — it only tests a brand-new one, freshly spawned, that has no choice but to read the current profile because it has no environment of its own yet. Every process that was already running before you made the edit is a different story. It read the profile once, at its own startup, copied whatever it found into its own memory, and has not looked at the file since. From that point forward it is not a reader of your shell profile — it is a cache of it. And caches don't invalidate themselves. Finding the actual culprit The process holding the stale value here was the editor I was working in — the same long-lived process that hosts my coding sessions and manages tool connections through M
AI 资讯
My Job Hasn't Changed. My Day Has.
Times are changing, my role is changing, my focus is changing, my impact is changing. But in essence – I'm still doing the same. I still build products that drive impact. Only my day-to-day looks completely different. The shift is happening, sooner or later, if you want it or not. Whether or not you can cope, is all up to you. In the past, I was neck-deep in code. That was what the majority of my time consumed. I liked it, building things, building products. These days, that's all done by an endless amount of AI agents. I barely touched any code in the past half year – if not even longer. My focus moved from building products to building my own process The work that used to go into a feature now goes into the process that produces the feature. Instead of losing the first hour of my day to Slack and email, I built a small stack of scheduled agents that hand me a briefing before I even open my laptop ( already wrote about that one ). Instead of reading every pull request line by line, I set up a review loop where agents do the first pass and I stay on the hook for whatever they flag. None of it started as a plan. Each piece started as one specific annoyance I got tired of and fixed. That's the actual mechanism: improve one small thing, it saves you time, you reinvest that time into the next small improvement. Compounding, not a grand strategy. The question I try to ask myself daily is simple: how can I do my job a bit better today than I did it yesterday? Not more. Not faster. Better. I also don't run ten parallel AI workflows across different projects at the same time because someone told me that's what a serious AI-software engineer does now. If I have multiple projects going on, I only focus on one project at a time. That's the amount of mental space I have right now, and I've stopped treating that as a shortcoming. My impact shifted from writing code to making my team better The time that used to go into implementation didn't disappear, it moved upstream. I now sp
AI 资讯
Before You Merge AI-Generated Code, Ask These 12 Questions
I've merged plenty of AI-generated code that was genuinely fine. I've also caught myself almost merging code that looked fine and wasn't, because it read like something a competent person wrote and my brain filled in the rest. Over the last year I've settled into a rough set of questions I run through before approving anything I didn't write line by line myself, generated or not. Here they are, in the order I actually ask them. 1. What problem is this code actually solving? It's easy to review whether code works and skip whether it solves the right thing. AI tends to answer the literal prompt, not the intent behind it. def get_active_users (): return db . query ( " SELECT * FROM users WHERE active = true " ) If "active" was supposed to mean "logged in within 30 days" and not a boolean flag that's rarely updated, this passes every test and still solves the wrong problem. Reviewer tip: Read the original ticket or request before reading the diff. Check the code against the intent, not just the literal ask. 2. Do I actually understand the implementation? Not "does it look reasonable," actually understand it, line by line, well enough to explain it to someone else. Reviewer tip: Try to explain the function out loud in one sentence per major step. If you get stuck anywhere, that's the part you haven't actually reviewed yet, just skimmed. 3. What assumptions is it making? Every implementation bakes in assumptions about the shape of the data, the order things happen in, or what "normal" looks like. function getLatestOrder ( orders ) { return orders [ orders . length - 1 ]; } This assumes orders is sorted chronologically and never empty. Neither assumption is stated anywhere. Reviewer tip: Ask "what does this assume about its inputs that isn't checked anywhere?" Write the answer down, literally, in the PR comment if it matters. 4. What happens with bad input? Bad input isn't an edge case, it's a certainty over a long enough timeline. def parse_age ( value ): return int ( val
AI 资讯
loveyourclanker.org
I created an open web resource for Software Engineers. https://loveyourclanker.org/ It highlights different patterns we can consciously choose use when interacting with our AI Coding tools (a.k.a 'Agents'... a.k.a 'Clankers') to stay in control, maintain quality and sensibly increase efficiency. I was prompted to do this (no pun intended) by observing some pretty alarming signals coming from this community. Token leaderboards, engineers being encouraged to use tools to "stay current" or "keep up" or "not be redundant", engineers quitting tools entirely to stay sane, engineers leaving social gatherings to get back to their agents, engineers setting up whole systems that automate away human engineers and then calling that "agentic engineering". I'm hoping that if we normalise and share how we use the tools, and show that there are different ways where you maintain more control and agency (... pun?) that it might promote a better If you find it helpful, share. If you disagree or want to contribute, raise a PR or ping me. It's all open and NFP.
AI 资讯
Design Notes for a Deterministic C++ Simulation Framework
“Same inputs, same result” sounds like a simple requirement. In a multithreaded simulation, it is an architectural constraint that touches data layout, scheduling, physics, randomness, floating-point behavior, serialization, and debugging. Determinism is valuable for replays, lockstep networking, regression tests, and reproducing hard failures. It does not happen automatically. Define the determinism boundary Start by stating what must match. Do two runs on the same executable and machine need identical results? Across different compilers? Across CPU architectures? Across operating systems? Those are increasingly difficult guarantees. A framework should document the supported boundary rather than using “deterministic” as a universal adjective. Control time Do not feed variable wall-clock deltas directly into a deterministic simulation. Use a fixed simulation step and decide how the renderer catches up or interpolates. Record inputs by simulation tick. If the system pauses or falls behind, handle that condition explicitly instead of silently changing the rules. Make randomness replayable Every pseudorandom decision needs a known generator, seed, and consumption order. A global generator shared by many systems is fragile because adding one random call in an unrelated feature shifts the sequence everywhere. Prefer scoped streams or deterministic derivation by system, entity, and tick where appropriate. Record seeds in test and replay artifacts. Schedule parallel work deliberately Multithreading introduces nondeterministic execution order. If two jobs write shared state, results may depend on timing even when data races are technically avoided. A robust job graph should make read and write sets visible, separate independent phases, and define deterministic merge or reduction rules. Avoid relying on thread completion order. Parallelize work whose outputs can be combined predictably. Keep entity iteration stable Entity-component systems often use dense arrays and swap-rem
AI 资讯
What Permit Files Can Teach Us About Reliable Workflow Software
Paperwork-heavy workflows rarely fail because a database cannot store another PDF. They fail because the system loses the relationship between the document, the real-world object, the decision it supports, and the stage of work it represents. Permits provide a useful example. A complete project record is not one uploaded form. It is an evidence chain that changes over time. A recent Local Service Ledger guide to Pasco County septic-repair records organizes the file into eight stages: property, existing system, site, pump-out, water and sewer, application, permit, and closeout. The guide's most important software lesson is that a receipt or contractor proposal alone does not establish the complete chain from reported problem to final recorded status. That distinction generalizes well beyond permits. 1. Give every workflow a stable subject Every document should attach to a stable entity: a property, customer, asset, case, project, or account. Do not rely on a filename or free-form address as the only identifier. Normalize enough data to prevent obvious duplication, preserve the source value, and retain a stable internal ID. For a property workflow, several records may contain slightly different owner names or address formatting. The system should help a reviewer determine whether they refer to the same site without silently overwriting those differences. 2. Separate observations, proposals, and decisions These are different kinds of facts: an owner reports a symptom; a contractor proposes a scope; an authority authorizes specific work; an inspector records a result; a final status closes the file. Collapsing them into one “project description” field destroys provenance. Model the actor, date, source, and status of each statement. The interface can display the current operational summary while preserving the earlier language that explains how the record evolved. 3. Make state transitions explicit A reliable workflow should not infer completion because a document exists