Sr. Content Developer at Microsoft, working remotely in PA, TechBash conference organizer, former Microsoft MVP, Husband, Dad and Geek.
157049 stories
·
33 followers

Shift-Left Static Code Analysis

1 Share

It’s a situation you’ve probably experienced before: late in your development process, a security vulnerability surfaces during final testing, weeks after the code was written. As a result, you find yourself context-switching from your current sprint, reconstructing decisions you made a month ago, and trying to coordinate fixes across multiple systems. 

How do you avoid these late changes of focus? For many teams, the answer is rethinking where quality-related activities sit within the development process, as part of a “Shift-Left” strategy. But what does this process mean for your team and your approach? Let’s take a closer look at shift left and whether it’s the right strategy for your development projects.

What is a shift-left approach?

Shift left is a philosophy that means you move important activities such as testing, quality assurance, and security checks earlier in the software development lifecycle (SDLC). In this timeline, the left side of the process typically represents early phases like requirements and design, while the right side represents later stages like testing and deployment.

It’s a term that’s becoming increasingly common in software development as project managers and leaders see how it transforms the way teams approach code quality.

An overview of the shift-left approach

The shift-left approach relies on automation, collaboration, and continuous feedback to maintain quality throughout development. Its key principles include:

  • Early testing and validation: You begin testing as soon as development starts. Developers run checks early and address issues before code reaches shared branches.
  • Automation: Continuous integration pipelines, automated test suites, and static code analysis provide fast, consistent feedback without an unmanageable impact on manual effort.
  • Developer ownership: Developers share responsibility for quality and security instead of relying solely on QA or security teams.
  • Continuous feedback: Every code change is reviewed and improved as it moves through the workflow, so feedback loops replace the traditional handoff model.
A traditional versus shift-left approach

Why is a shift-left strategy important?

In most traditional development workflows, testing and security reviews happen late in the cycle. But that means that when issues surface, fixing them requires significant rework, often at critical moments. This approach can introduce delays that affect your entire release schedule, along with increased costs, and several other problems.

Context switching costs

A design flaw that’s discovered during final testing can force you to revisit decisions made weeks earlier, which means having to switch your approach at short notice. Rebuilding that mental context slows your development and increases the chance of introducing new problems while you’re still trying to address the original issue.

Integration complexity

A vulnerability caught late in the process can affect several parts of the system and may require updates across multiple components and teams. With shift left, you can identify the same issue during development, where it’s usually contained within a small, localized change that’s quicker to fix.

Schedule impact

Late architectural problems leave you with a difficult choice. Do you delay the release or ship a design that has the potential to create long-term technical debt? 

When issues surface earlier in the cycle, your teams can adjust with far less disruption because changes are smaller, better understood, and easier to integrate while code is still being written, rather than when it’s ready for release.

Shift left in static code analysis

Static code analysis brings shift-left testing principles directly into everyday development. These tools check for bugs, vulnerabilities, and quality issues while you’re writing code, so you don’t have to wait for later testing stages.

Unlike runtime testing, static code analysis reads your source code without executing it. This allows checks to run during active development. If your developer writes a database query, for example, the tool can flag a possible SQL injection risk while the code is still fresh.

Static code analysis also catches issues that traditional shift-left testing might not trigger. It can identify unused variables, inconsistent naming, excessive complexity, and security risks that only appear under specific runtime conditions. Instead of relying on test cases, the analysis inspects your code’s structure and highlights patterns that could lead to defects.

How to implement shift-left code analysis

Implementation of shift-left code analysis typically happens through two primary integration points that work together to catch issues at different stages of your development process:

  • IDE integration: Your developer writes a function and the static code analysis tool flags a potential SQL injection risk or unused variable in real time. They see the same checks locally that will appear in the CI pipeline, which removes surprises during builds. Because the code is fresh and they’re already in the context, fixes take minutes rather than hours. This feedback loop accelerates learning and helps your developers internalize quality standards.
  • CI/CD pipeline integration: Each pull request triggers an automated static scan before code can be merged. Your developers see analysis results directly in their pull request, reducing the risk of insecure or buggy code entering the main branch. Quality gates can enforce standards automatically, and the pipeline fails if new code introduces critical security issues or exceeds complexity thresholds. This removes the need for manual review of routine quality checks and lets human reviewers focus on architecture and logic.

Benefits of “shifting left

Adopting shift-left practices improves your development workflow by surfacing issues earlier and giving teams continuous feedback. Automated checks, early validation, and real-time analysis reduce costs, accelerate delivery, and raise code quality.

Speeding up feedback loops

With a shift-left approach, your developers see issues as they’re working on the code, rather than getting feedback days or weeks later. Immediate feedback speeds up learning because your developers can adjust their approach while the context is still fresh. Issues are flagged within minutes instead of only coming to light during the next test cycle.

Reducing technical debt

Checking code at an earlier stage means potential technical debt gets fixed before it spreads through the codebase. A function that is refactored during development represents a quick change. But if that same problem is only discovered months later, it may then connect to many parts of the system, turning a simple fix into a multi-day effort that requires broad testing and coordination.

Improving code quality

Through shift left, you know code that reaches testing has already passed multiple checks, in the IDE, in local analysis, and in the CI pipeline. That means quality becomes part of the regular workflow, rather than just becoming an end-of-cycle inspection. This layered process produces cleaner, more maintainable code with fewer defects.

better code quality from shift-left practices

Cost savings

Fixing issues early is far cheaper than addressing them late in the cycle. A vulnerability that’s resolved in the IDE takes minutes and requires very little coordination. However, if the same issue is found during security testing, it will mean context switching, investigation, regression testing, and possible schedule changes. The cost difference therefore grows rapidly as issues occur later in the lifecycle, so it’s beneficial to employ shift left and catch them earlier.

Challenges of a shift left approach

Shift-left practices deliver clear benefits for developers, but they also introduce some challenges your team should plan for. Understanding these helps you roll out the approach more smoothly and determine if it’s the right strategy for your own team.

Sometimes there are too many false positives

Analysis tools sometimes flag code that’s safe in your context. Too many of these warnings can create noise, and your developers might start ignoring alerts they wrongly assume to be false positives. Tuning the rules for your codebase, suppressing irrelevant checks, and focusing on high-confidence issues all help raise the signal-to-noise ratio. This takes some initial effort but pays off quickly.

Integration friction and issues with onboarding

Adding new tools to your existing process means you need to do configuration work, CI pipeline updates, quality gate setup, and documentation changes. Your developers will also need time to learn the system and adjust their workflow. A gradual rollout can therefore be the best way to integrate a shift-left strategy. Start with high-value checks and expand coverage once the team is comfortable working in this way.

Actually getting developers to adopt these methods

Developers who are used to handing code over to QA will need to change their habits to adopt a new approach. Shift left requires them to take ownership of quality, fix issues flagged by analysis tools, and view quality as part of development rather than a separate phase, all of which can take some time to integrate.

You can improve adoption by highlighting the benefits, giving training, and exploring quick wins. Early successes build momentum and can turn developers into advocates for your new ways of working.

Transform your workflow with shift left

Shift left transforms quality from a late-stage checkpoint into a continuous part of your workflow, enabling efficiency and savings. Earlier detection reduces costs, shortens feedback loops, and raises code quality. Static code analysis enables shift-left practices at scale by catching issues during development when context is fresh, fixes are simple, and changes are contained.

When you adopt shift-left practices, you catch vulnerabilities in the IDE instead of during penetration testing. You can also identify complexity problems during pull requests rather than during maintenance months later, as well as fixing bugs while the code is still familiar rather than after it ships to production.

The result is cleaner code, faster releases, and developers who spend more time on feature work instead of unplanned fixes and debt remediation. Shift left doesn’t remove the need for testing and quality assurance. It just makes those stages more effective by ensuring the code that reaches them has already been reviewed multiple times.

Tools like Qodana add static analysis directly to your workflow through IDE integration and CI/CD automation. Try Qodana and explore where early detection can have the biggest impact in your process, and how static analysis supports those improvements.

Try Qodana

Read the whole story
alvinashcraft
7 minutes ago
reply
Pennsylvania, USA
Share this story
Delete

Agent orchestration is so two-years ago​​​​‌‍​‍​‍‌‍‌​‍‌‍‍‌‌‍‌‌‍‍‌‌‍‍​‍​‍​‍‍​‍​‍‌​‌‍​‌‌‍‍‌‍‍‌‌‌​‌‍‌​‍‍‌‍‍‌‌‍​‍​‍​‍​​‍​‍‌‍‍​‌​‍‌‍‌‌‌‍‌‍​‍​‍​‍‍​‍​‍‌‍‍​‌‌​‌‌​‌​​‌​​‍‍​‍​‍‌‍​‌‍‌‌​​‍‍‌​‌‌​‌‍​‌‌‍​‌‍‍‌‍‌‌‍‌‍‌‌‌​‍‌‍‌‍‌‍​‌‍‌‌​‍‍‌‍​‌‍​‍‌‍‍‌‌‍‍‌‌​‌‍‌‌‌‍‍‌‌​​‍‌‍‌‌‌‍‌​‌‍‍‌‌‌​​‍‌‍‌‌‍‌‍‌​‌‍‌‌​‌‌​​‌​‍‌‍‌‌‌​‌‍‌‌‌‍‍‌‌​‌‍​‌‌‌​‌‍‍‌‌‍‌‍‍​‍‌‍‍‌‌‍‌​​‌‌‍‌​​‌‌​​‍‌‍​‍​​​​​‌​‌‍‌‌​‍‌​‌​​​‍​​​​​‍‌​‌​‌‍‌‌​‌‌‍‌​​‍‌‌‍​‌​​‌​​​‌‍​‍​‍‌‌‍​‍‌‍‌‍‌‍​‍‌‍​‌​​‌​‌​​‌​​‍‌‍​​​‍‌‍‌‍‌‍​‍​‍‌‌​‌‍‌‌​​‌‍‌‌​‌‌‍​‍‌‍​‌‍‌‍‌‌‌​​‌‍‌​‌‌​​‍‌​​‌‍​‌‌‌​‌‍‍​​‌‌‌​‌‍‍‌‌‌​‌‍​‌‍‌‌​‌‍​‍‌‍​‌‌​‌‍‌‌‌‌‌‌‌​‍‌‍​​‌‌‍‍​‌‌​‌‌​‌​​‌​​‍‌‌​​‌​​‌​‍‌‌​​‍‌​‌‍​‍‌‌​​‍‌​‌‍‌‍​‌‍‌‌​​‍‍‌​‌‌​‌‍​‌‌‍​‌‍‍‌‍‌‌‍‌‍‌‌‌​‍‌‍‌‍‌‍​‌‍‌‌​‍‍‌‍​‌‍​‍‌‍‌‍‍‌‌‍‌​​‌‌‍‌​​‌‌​​‍‌‍​‍​​​​​‌​‌‍‌‌​‍‌​‌​​​‍​​​​​‍‌​‌​‌‍‌‌​‌‌‍‌​​‍‌‌‍​‌​​‌​​​‌‍​‍​‍‌‌‍​‍‌‍‌‍‌‍​‍‌‍​‌​​‌​‌​​‌​​‍‌‍​​​‍‌‍‌‍‌‍​‍​‍‌‍‌‌​‌‍‌‌​​‌‍‌‌​‌‌‍​‍‌‍​‌‍‌

1 Share
Ryan welcomes Saahil Jain, CTO of You.com, to discuss why building agents with a 2024 mindset is a mistake as modern models improve at long-horizon tasks, why heavy orchestration layers can hurt model performance more than help it, and why the 2026 competitive edge actually comes from information retrieval and unique data paired with end-to-end evaluation.​​​​‌‍​‍​‍‌‍‌​‍‌‍‍‌‌‍‌‌‍‍‌‌‍‍​‍​‍​‍‍​‍​‍‌​‌‍​‌‌‍‍‌‍‍‌‌‌​‌‍‌​‍‍‌‍‍‌‌‍​‍​‍​‍​​‍​‍‌‍‍​‌​‍‌‍‌‌‌‍‌‍​‍​‍​‍‍​‍​‍‌‍‍​‌‌​‌‌​‌​​‌​​‍‍​‍​‍‌‍​‌‍‌‌​​‍‍‌​‌‌​‌‍​‌‌‍​‌‍‍‌‍‌‌‍‌‍‌‌‌​‍‌‍‌‍‌‍​‌‍‌‌​‍‍‌‍​‌‍​‍‌‍‍‌‌‍‍‌‌​‌‍‌‌‌‍‍‌‌​​‍‌‍‌‌‌‍‌​‌‍‍‌‌‌​​‍‌‍‌‌‍‌‍‌​‌‍‌‌​‌‌​​‌​‍‌‍‌‌‌​‌‍‌‌‌‍‍‌‌​‌‍​‌‌‌​‌‍‍‌‌‍‌‍‍​‍‌‍‍‌‌‍‌​​‌‌‍‌​​‌‌​​‍‌‍​‍​​​​​‌​‌‍‌‌​‍‌​‌​​​‍​​​​​‍‌​‌​‌‍‌‌​‌‌‍‌​​‍‌‌‍​‌​​‌​​​‌‍​‍​‍‌‌‍​‍‌‍‌‍‌‍​‍‌‍​‌​​‌​‌​​‌​​‍‌‍​​​‍‌‍‌‍‌‍​‍​‍‌‌​‌‍‌‌​​‌‍‌‌​‌‌‍​‍‌‍​‌‍‌‍‌‌‌​​‌‍‌​‌‌​​‍‌​​‌‍​‌‌‌​‌‍‍​​‌‌‍‌‌‌‍​‌‍​‌‍‌‌‌​‍‌​​‌‌​​‌‍​‍‌‍​‌‌​‌‍‌‌‌‌‌‌‌​‍‌‍​​‌‌‍‍​‌‌​‌‌​‌​​‌​​‍‌‌​​‌​​‌​‍‌‌​​‍‌​‌‍​‍‌‌​​‍‌​‌‍‌‍​‌‍‌‌​​‍‍‌​‌‌​‌‍​‌‌‍​‌‍‍‌‍‌‌‍‌‍‌‌‌​‍‌‍‌‍‌‍​‌‍‌‌​‍‍‌‍​‌‍​‍‌‍‌‍‍‌‌‍‌​​‌‌‍‌​​‌‌​​‍‌‍​‍​​​​​‌​‌‍‌‌​‍‌​‌​​​‍​​​​​‍‌​‌​‌‍‌‌​‌‌‍‌​​‍‌‌‍​‌​​‌​​​‌‍​‍​‍‌‌‍​‍‌‍‌‍‌‍​‍‌‍​‌​​‌​‌​​‌​​‍‌‍​​​‍‌‍‌‍‌‍​‍​‍‌‍‌‌​‌‍‌‌​​‌‍‌‌​‌‌‍​‍‌‍​‌‍‌‍‌‌‌​​‌‍‌​‌‌​​‍‌‍‌​​‌‍​‌‌‌​‌‍‍​​‌‌‍‌‌‌‍​‌‍​‌‍‌‌‌​‍‌​​‌‌​​‍‌‍‌​​‌‍‌‌‌​‍‌​‌​​‌‍‌‌‌‍​‌‌​‌‍‍‌‌‌‍‌‍‌‌​‌‌​​‌‌‌‌‍​‍‌‍​‌‍‍‌‌​‌‍‍​‌‍‌‌‌‍‌​​‍​‍‌‌
Read the whole story
alvinashcraft
7 minutes ago
reply
Pennsylvania, USA
Share this story
Delete

Event Sourcing: set-and-remove-based bi-temporal events

1 Share

We return to bi-temporal event sourcing in part twelve of my series on event sourcing. This time, we look at set-and-remove-based bi-temporal events – the second variant of bi-temporal events in our system. You’ll see why they are needed, and what additional problems this kind of event brings to the table.

In earlier posts in this series, I showed you bi-temporal events. We look at an example showing how organisational charts (forms and units) change over time. We call that kind of bi-temporal event stream the lifetime (or create-update-delete-based) kind because a thing is created, updated, and maybe deleted. The events represent the thing’s lifetime. We used two time axes (effective and application) to specify when the event takes effect (e.g., when the organisational chart takes effect) and when the data entered the system (the application). To project the organisational chart, we sorted the events by their effective timestamps to project the timeline. The application timestamp is only relevant when multiple events share the same effective timestamp, so we can, for example, override an update with a newer one.

lifetime timeline with create, update, and delete events

Set-and-remove-based bi-temporal events

We also have a different kind of timeline: set-and-remove-based.
Examples are:

  • The assignment (and removal) of a calendar to an organisational unit from a given date.
  • The rules to check when validating workdays (whether a user’s entered data results in a meaningful workday).
  • Stating that an employee starts or stops working on a specific project at a given time.
  • The settings to use when calculating project activities from the above start/stop information.

These events do not reflect the lifetime of a thing but rather a timeline that specifies which data is relevant at any given time. So, if we need to calculate activities based on start/stop information, we look up the settings for the relevant workday(s).

set-remove-based timeline

Override or Insert?

When an event (with a later application) is added to the event stream with an earlier effective timestamp than an existing event, we have a problem. The following picture shows that we first add event A, then B, and finally C. Horizontally, we see the application time axis, so the events are in order of addition: A, B, C. Vertically, we see the effective time axis. And the order of the events, by their effective timestamps, is A, C, B.

When projecting the events into a timeline, we have two options. Either event C overrides event B, resulting in the right projection (override). Or we keep both events so that the value of C is only valid until the effective timestamp of event B, resulting in the left projection (insert).

For example, when a user specifies the rules that need to be run to validate a workday, they specify a set of rules and a date from which that set is effective (= operation 1). Now, what should happen when the user specifies a new set of rules for an earlier effective date (= operation 2)? Should the new set be used for dates after the effective date of operation 1 – operation 2 overrides operation 1 – or should the old rules be effective from the date that operation 1 specified?

Both scenarios can be valid. Therefore, we need to be able to choose the insert/override behaviour when we project events.

Summary

Set-and-remove-based timelines introduce the problem that events can be projected into a timeline in two ways. Either a later-added event with an earlier effective date is inserted or overrides later events.

There is also another problem: how to reverse the effect of an event in such timelines. That is the topic of the next post in this series.

Deep Dive

Let’s take a look at organisational calendars. A unit in an organisational chart can have a calendar in our application. Every member of the organisational unit automatically sees the unit’s events in their calendar. An easy way to define company- or department-wide holidays or parties.

A calendar can be added to an organisational unit per an effective date, and it can also be removed per an effective date:

Then, we define that the added event should be treated as a set event (Sets (effective, value)), and the removed event should be treated as a remove event (Removes effective) when projecting a timeline.

Finally, we need to specify the projection behaviour. First, we need to specify that we want to use a set-and-remove-based projection (ProjectionActionConfiguration = SetRemove). Second, we specify the override behaviour. In this example, we use OverrideBehavior.Override. So, when there are already set events, and we add an earlier (effective) event, the new event overrides all later (effective) events. This happens when a user decides that a calendar should be available earlier than already specified.

Read the whole story
alvinashcraft
8 minutes ago
reply
Pennsylvania, USA
Share this story
Delete

The role of ActivitySource in OpenTelemetry for .NET

1 Share

While doing some pair programming to integrate OpenTelemetry tracing to a .NET application, we had a discussion on how to use the ActivitySource. It looks simple. You new one up, give it a name, start an activity, done.

The discussion started when we added a second ActivitySource with the exact same name in a different class. This made us wonder: "Are we duplicating traces now? Is this a memory leak? Do we need a singleton?"

So we decided to dig deeper. This post is what we learned…


What ActivitySource actually is

ActivitySource is part of System.Diagnostics, not part of the OpenTelemetry NuGet packages. Microsoft built tracing primitives directly into the BCL, and OpenTelemetry's .NET SDK simply listens to them. This is why you can add distributed tracing to a library without taking a dependency on OpenTelemetry at all.

An ActivitySource is a factory for Activity objects, and an Activity is .NET's name for what OpenTelemetry calls a span.(don’t ask me why decided to use a different name for the same concept) Nothing gets recorded, exported, or even created in memory as a real object unless something is listening.

That "something" is the TracerProviderBuilder configuration in your OpenTelemetry setup.

public static class TelemetryConstants
{
    public const string ServiceName = "OrderProcessing.Api";
}

public class OrderService
{
    private static readonly ActivitySource ActivitySource = new(TelemetryConstants.ServiceName);

    public async Task ProcessOrderAsync(int orderId)
    {
        using var activity = ActivitySource.StartActivity("ProcessOrder");
        activity?.SetTag("order.id", orderId);

        // ... business logic

        activity?.SetStatus(ActivityStatusCode.Ok);
    }
}

And on the OpenTelemetry side:

builder.Services.AddOpenTelemetry()
    .WithTracing(tracing => tracing
        .AddSource(TelemetryConstants.ServiceName)
        .AddAspNetCoreInstrumentation()
        .AddOtlpExporter());

That AddSource(...) call is the piece that actually matters. If the name you pass there doesn't match the name of your ActivitySource, your activities are created, populated with tags, disposed... and silently dropped. No error, no warning. This is the number one reason people think "OpenTelemetry isn't working" when actually tracing is working exactly as configured — just not listening to the right name.

The root of our discussion: it's fine to create it more than once

Here's the statement that started the discussion:

"There must be exactly one ActivitySource instance per name in the whole application, otherwise something breaks."

That turns out NOT to be true. You can create as many ActivitySource instances as you want with the same name, in as many classes, assemblies, or NuGet packages as you want. The OpenTelemetry SDK doesn't care about instance identity. It subscribes by name (and optionally version), not by object reference.

// File A
private static readonly ActivitySource Source = new("OrderProcessing.Api");

// File B, completely unrelated class
private static readonly ActivitySource Source = new("OrderProcessing.Api");

Both of these are picked up by the same AddSource("OrderProcessing.Api") call. Activities from either one land in the same trace pipeline, with the same resource attributes. No duplication, no double-exporting, no leak.

Why does this matter in practice?

  • Libraries don't need to expose their internal ActivitySource. A NuGet package can declare its own source with a well-known name, and consumers just call AddSource("ThatLibrary.Name") without ever touching the library's internals.
  • You don't need a shared static class passed around through DI just to get access to one ActivitySource instance. Each component can declare its own, using a shared naming convention (typically the assembly name or a constant).
  • Modular monoliths and Razor Class Libraries can each own their tracing setup without coordinating a single source of truth for the instance itself — only the name needs to be agreed on.

The one thing that does need to line up is the string. Typos are the actual failure mode here, not multiple instantiations.

A wrong-first-attempt

A pattern we noticed in the original codebase: someone tried to be "correct" by injecting ActivitySource through DI as a singleton, because that feels like the safe, testable thing to do.

// Works, but adds ceremony you don't need
services.AddSingleton(new ActivitySource("OrderProcessing.Api"));

public class OrderService(ActivitySource activitySource)
{
    public async Task ProcessOrderAsync(int orderId)
    {
        using var activity = activitySource.StartActivity("ProcessOrder");
        // ...
    }
}

There's nothing broken about this, but it's extra machinery for no real benefit, since ActivitySource instances with the same name are interchangeable anyway. The idiomatic pattern in .NET tracing code and what you'll see in Microsoft's own libraries, is a private static readonly ActivitySource field per class or per assembly, named after the component it belongs to:

internal static class Telemetry
{
    public static readonly ActivitySource Source = new("OrderProcessing.Api.Orders");
}

Simple, no DI registration required, and it works whether the class using it comes from your application, a class library, or a third-party package that adopted the same convention.

Naming and versioning

ActivitySource also accepts an optional version:

private static readonly ActivitySource ActivitySource = new("OrderProcessing.Api.Orders", "1.2.0");

The version travels with the resulting spans as metadata but doesn't affect whether the SDK listens to it — AddSource still matches on name. Use it the same way you'd use assembly versioning: informative for debugging which build produced a trace, not a mechanism for isolation.

A convention worth adopting: name your sources like namespaces (Company.Product.Component), matching how you'd structure logger categories with ILogger<T>. It keeps AddSource calls predictable and makes wildcard matching (AddSource("OrderProcessing.*") in some exporters) actually useful.

Wrapping up

ActivitySource is the entry point into .NET's native tracing story, and OpenTelemetry is just one (very common) listener on top of it. The name is the contract; get that right, register it with AddSource, and you can create as many instances of it as your codebase's structure naturally calls for. Don't let the fear of "duplicate instances" push you into DI ceremony you don't need.

More information

Read the whole story
alvinashcraft
9 minutes ago
reply
Pennsylvania, USA
Share this story
Delete

Blog about things you don't understand yet

1 Share

Every post I publish represents at least two things I’ve learned: the thing that prompted me to write the post, and the thing I learned in the course of writing it. If I don’t learn anything new while I’m writing, it’s not interesting enough to publish.

Typically I learn way more than two things. For instance, in my o3 geoguessr post, I started out with the idea that most AI prompts probably don’t work, and I ended up learning that newer OpenAI models have lost o3’s ability to geolocate. That’s interesting! In my most recent post on C2PA, I started out with the idea that C2PA requires near-universal adoption, but I learned a ton of things about PKI, managing private keys on local devices, how C2PA actually works, and so on. In my post on the Luddites, I started out with the idea that the Luddite movement was fundamentally decentralized, but ended up fascinated by Luddite culture (which was far more elitist, misogynist, and violent than the pop-Luddism books describe). I could do this for every single post on the blog.

Taking a position

I think the core reason this works is that every single one of my blog posts argues a point. I never publish a post that just gives some scattered thoughts on a topic, or a post that only says “yes, I agree with this other article”. If I write a draft that nobody sensible could disagree with, I scrap the draft. Making sure that everything I write is at least minimally controversial is a forcing function: it forces me to think about what the most interesting part of my position is, and it forces me to do enough research to defend it against the obvious criticisms.

This is contrary to a lot of advice I read about blogging, which encourages the aspiring blogger to treat their posts as a form of unstructured self-expression. If unstructured self-expression is what you want to do, that’s cool. The point of having a blog is that you get to write what you want. However, this advice isn’t as helpful as it sounds.

Before I was in tech, I was a philosophy grad student. But before that, I was a poet. One thing you learn when you try to write poetry is that it is way easier to write to a restrictive structure than it is to simply “write what you feel”. This should be obvious when you actually think about it. The task of a poet is to repeatedly choose the next word. Writing to a structure (typically rhyme or meter) narrows that choice to a small set of words, instead of the entire English language. It’s the same with blogging. Forcing yourself to write about specific, potentially-controversial points makes consistently writing easier, not harder.

Writing, thinking, and research

Writing is the best way to think clearly about a topic. It’s easy to believe you understand something when you’re just turning it over in your head. When you have to condense that down into words, you find out exactly how much you do or don’t understand. I am constantly having moments where I type something, stop myself, and think “wait, that can’t actually be right”, or “is that really true?”

By the time I write my way to the end of the post, I’m usually thinking so much more clearly about the topic that my conclusion paragraph is way better than my introduction. In fact, I’ve picked up the habit of going back and immediately rewriting the first paragraph as part of my first-draft process, because I know I’m going to end up doing it anyway.

I also change my mind a lot while I write. Here are a bunch of examples of posts where I began writing them with the opposite opinion to the one that eventually made it into the post. I think this is a good sign, and I hope I never stop doing it. You should be researching and thinking about every post you write, and that means you should frequently learn new things that change your mind.

Because of all this, I deliberately choose to write blog posts about things I don’t yet quite understand but would like to, like LLM steering, Stripe’s Tempo blockchain, C2PA and watermarking, space cooling, interaction models, LLM inference internals, and so on. This is great for me, because I learn a lot. Is it great for my readers?

Is blogging to learn irresponsible?

I sometimes worry that I should only be writing about areas I already know very well, like tech company dynamics or working in large codebases, rather than presenting myself as an authority on fields I’m actually still learning. Should I let historians of the Luddites write about Luddism, Web3 engineers write about blockchains, and so on? I think this is acceptable for three reasons.

First, it’s sometimes easier for a beginner to write an introduction to a field than for an expert. Experts routinely overestimate the knowledge of the general public, and have often internalized the reasons why their field is important so deeply that they struggle to express them. I think my explainer posts are valuable because I always spend the first chunk of the post talking about what the original problem is before I get into the technical solution.

Second, sometimes the public consensus on a topic is just plain wrong, to the point where even a little bit of research is enough to demonstrate why. Many of my posts I’m proudest of have been along these lines: arguing that the “500ml per prompt” water usage figure for LLMs was ludicrous, or that the popular Apple “Illusion of Thinking” paper was tracking persistence, not reasoning, that GPUs live longer than three years and the AI companies have large profit margins on inference, and so on.

Third, I try to make it clear on my blog who I am and what my credentials actually are. Even if it’s not explicitly described in the post, I have my real name and resume available on my /about page, so I don’t think a careful reader could be easily fooled into thinking I’m an expert on 19th-century England or space physics or LLM economics or anything like that.

Feedback

Even if nobody reads what you write, writing is still a good discipline for getting your thoughts in order. But another big reason why writing is a great learning tool is that you can get feedback.

I think it’s obvious why this is useful, but I do want to make two points about feedback. First, if you do make your posts public, you need to have a pretty thick skin. People on the internet often fall over themselves to come up with the most cutting criticism or the harshest dunk. This goes double if you take my previous advice and try to write posts that make a clear, controversial point about a subject you’re learning. If you’re the kind of person whose whole day is ruined when a stranger is cruel to them, you might want to keep your blogging private or only share it among friends.

Second, even if your blogging is private, you can get feedback from LLMs. Like humans, LLMs will often give junk feedback. In my experience, OpenAI models will always tell me to moderate my claims or add caveats and hedges until I’m not saying anything at all. Sometimes their criticism will be straight-up wrong. But — particularly about technical topics — LLMs are great at pointing out areas you’ve genuinely misunderstood, and they’re far kinder than the average Lobsters or Hacker News commenter.

Conclusion

I’m pleased and grateful that people enjoy reading my posts, but even when nobody did, I still got a lot of value out of blogging. I write as a method of thinking more clearly, as an excuse to do research on topics I want to learn about, and as a way of getting feedback.

If you’d like to try it yourself, I suggest watching for these two things. First, you should be changing your mind a lot as you write. If not, you probably aren’t doing enough research. Second, your first draft’s conclusion should be much tighter and more expressive than its introduction. If not, you probably haven’t learned anything from the writing process, which means the draft can be scrapped.

I strongly recommend this practice to anyone with an interest in writing. You will see the benefits even if you don’t publish any of your writing on the internet, particularly now that you can get good technical feedback by pasting your post into a LLM1.


  1. For what it’s worth, I’ve fiddled with careful “review prompts” and it’s basically as good to just write “review, please:” and paste your article.

Read the whole story
alvinashcraft
9 minutes ago
reply
Pennsylvania, USA
Share this story
Delete

Daily Reading List – July 6, 2026 (#818)

1 Share

I enjoyed the time off, and am also happy to be back into routines. The tech world didn’t take much of a break, and I’m working through a reading backlog.

[blog] Most rewrites serve the engineer, not the business. Spicy headline, reasonable take. And using AI doesn’t help you understand the “why” of the decisions embedded within the existing system.

[blog] Where does Antigravity look for Agent Skills? We’ve made this confusing and I look forward to the point when agent skills can be easily discovered and stashed for later use. Mete does a good job here steering us to to the right places.

[article] 5 Traits That Set the Best Leaders Apart. Terrific advice. Some of these traits are easier to adopt than others.

[blog] How to Count Gemini Tokens Locally. As we all become more token conscious, it’s not a bad idea to understand the ins-and-outs of counting tokens.

[article] New Alibaba AI framework skips loading every tool, cutting agent token use 99%. We’re going to need to see more of this in all the AI frameworks. Instead of upfront loading of every tool (and all that context), we should have a lazy loading process.

[blog] What’s New in A2A: v1.0, a Python DX Glow-Up, and a Fresh New Look. A2A has staying power. You’re seeing it supported across programming languages and commercial products. This was a pretty major 1.0 release.

[blog] The State of AI in the SDLC: A Roadmap for Scaling. I buy it. As you chase your first customer, you’re just focused on the code (and use case). Then you have to think about process, and after that, organization.

[article] 10 moments that defined AI’s turbulent first half of 2026. Nobody would have guessed all of these back at the start of the year.

[blog] Of Skills and Loops with AI Assistance. If you invest up front in encoding your knowledge into skills and crafting agent loops, you’ll see some fairly dramatic speedups.

[blog] Building Gin: Simple Over Easy. Sometimes the throwaway thing you built along the way turns into the main thing. Great story about the start of Go’s most popular web framework.

[blog] Tom’s opinionated guide to skill building 101. Excellent. Skills don’t just serve software developers. They can make other disciplines, like technical writing, significantly better.

[article] The twilight of the chatbots. Looping agents with good direction don’t require a lot of human intervention. That’s different than early mainstream AI days where we engaged with chatbot interfaces.

[blog] Anomaly detection using dynamic thresholds and two-year-long alerts in Cloud Monitoring. Sometimes static thresholds or short lookback windows don’t give you the right signals. We’ve now made it easier to create alert policies over wide sets of historical metrics.

[article] The Pulse: a new trend, smart model routing. Yes, take advantage of tools that can pick the right model for the task. This can save you a ton of money.

[article] The website of the future may assemble itself for every visitor. I suspect that this generative UI pattern is going to show up in a LOT of web and mobile experiences in short order.

Want to get this update sent to you every day? Subscribe to my RSS feed or subscribe via email below:



Read the whole story
alvinashcraft
10 minutes ago
reply
Pennsylvania, USA
Share this story
Delete
Next Page of Stories