Write While True Episode 52: Using Feedback

Lou: Hey, Brian. I wanted to start this episode by doing a little bit of a follow up to episode 48, where we talked about starting a collaboration. One of the things we ended with was doing a simple collaboration by just getting feedback on something you’re writing. And I wanted to talk to you about your thoughts on what to do with feedback.

Brian: Yeah, let’s do it, for sure. And to begin, the assumptions here are that you’re working on a piece of writing that you intend to iterate on. You’re going to revise this writing, you’re going to improve it. It might be a book, newsletter, blog post, something you care enough about to spend time on after you first put it out into the world for feedback.

Transcript

Dev Stack, Part XI: Sandboxing

Late last year, I completely changed my dev stack to Python on Linux with some other things. I wrote a series about it at the time:

My choices were driven by the dangers of AI Coding Agents and Supply Chain attacks (more generally, just running untrusted code).

Getting all development off of my main machine was a big step. Choosing Linux for that machine was driven by cost per computing power for a desktop machine, and that I only need to run VSCode, a browser, and dev tools that are Linux first anyway.

I have been programming on the bare OS, but I was always going to want more isolation between projects and between the projects and the machine. I finally completed that step.

My choice was to use QEMU-KVM, an open-source VM solution. This blog about QEMU-KVM on Ubuntu was the most useful (and accurate) for me.

My general setup:

  1. The machine only has Ubuntu, Firefox, Tailscale (see networking), and my KVM setup described above.
  2. I built one VM to work on a new project (charting visualizations for Google Sheets), which only needs Ubuntu, VSCode, Git, and Firefox.
  3. This project is in Javascript, but I am building it with a dependency on D3 and nothing else. No NPM, not even jest. D3 is only loaded by the browser (not on the machine)
  4. For testing, I am building a minimal test harness in JS. It runs in the browser, so it will also be able to do DOM testing.
  5. There is no firewall yet, but I will probably do that soon. As a first step, just limiting the ports. I will document that if I go that way. It would be inside the VM.
  6. I allow some limited logged in browsing in my outside OS, mostly ChatGPT, but not Google. The main OS is for research. Nothing else can be installed on it (through any means, even trusted). The VM browsers are only for using my software (not the internet).

Other solutions I considered:

  1. Cloud based programming (like codespaces): This would definitely work for some projects I have, but I feel like I’d run up against limitations. Long-term, I think this will become the only sane way to program.
  2. Docker: I am not that comfortable with it, and it seems like running GUIs (like VSCode) is not trivial. It would be more efficient with sharing installed software, but wasting disk space is just not an issue.
  3. No Sandbox: Just putting all development on a dedicated computer is probably enough. I went the VM route mostly out of personal interest. Having done it, one big plus is snapshotting.

Write While True Episode 51: Phase Based Writing Goals

Brian: I’m building up my ability to do that by spending my mornings and my first sip of coffee, learning this framework. And at the same time, you got to watch out because learning a JavaScript framework is not writing per se. So that’s what I’ve been up to. And it’s felt productive and like it works toward my ultimate goal, my ultimate writing focused goal, but I haven’t been writing. So I’ve mixed feelings about that. 

And I guess I want to say, Lou, you went through the whole process of blogging for a long time and then taking on a book project or multiple book projects, writing the book, editing the books. Now you’re in the phase where you have to be marketing the book. I hope you’re marketing the book. Talk to me about writing goals in different phases of projects.

Transcript

Write While True Episode 50: Habit Troubleshooting

Brian: I’m Brian Hall. And today we’re talking again about habits. In the last episode, we both set out some intentions to change our writing habits and establish a daily practice. Let’s check in on that. Lou, how did it go for you?

Lou: Not great, Brian. I did the habit none times.

Brian: Okay, not great. Remind us what you were going to do and tell us what happened.

Transcript

Write While True Episode 49: Tiny Habits

Brian: And so just getting yourself in position and getting started in this way tends to lead to more powerful, robust output. I guess I’ve reached the point where just by starting consistently every morning, I was cranking out a blog post a day for quite a while. And I guess that’s the other thing I’ll say too, is habits can come and go. You can lose a habit and it’s probably not helpful to beat yourself up about that because you can also regain them. If you’re in the middle of a sprint, you might write every single morning and you might produce quite a bit. And then you might finish that book or reach the end of that series of blog posts. And maybe you abandon your habit. It’s there to be reclaimed, I guess.

Transcript

26 for 26 March Update

At the beginning of the year I wrote down 26 fun mini-goals to try to do in 2026. I gave an update at the end of January and here’s another one with an update since then.

  1. I’ve been doing well with my vegan recipe books and I also found a great new recipe online for Sopa Locro de Papa that connects me to my Ecuadorian roots.
  2. I’ve donated all of the books I don’t want to keep (way more than 10), so that goal is done.
  3. I went to a new restaurant for valentine’s day in NYC.
  4. I appeared on the Thinking in Tech podcast where I talked about tech debt and AI.
  5. I released episode 48 of Write While True.
  6. I wrote one new Amazon Book Review for Forever Fit [ad] by Maxime Sigouin
  7. I started a new open-source project to package up some visualizations I have made in D3 and want to use in the Google Sheet I ship with my book, Swimming in Tech Debt.

If you want to see the sheet that I use to manage tech debt and get free emails on how to use it, sign up below:

Finding My First Open Source Contribution

I keep track of my GitHub open source contributions on this site’s GitHub page, but only back to 2013. According to GitHub, I opened my account in late 2010 to open a couple of issues on Yammer.net, which I was using to build an internal tool for Atalasoft that needed access to our Yammer data (Yammer was a precursor to Slack).

My first GitHub source contribution was to YUICompressor (a JavaScript compression tool) to output a Munge Map to aid debugging. I PR’d it in 2011. I needed this to help debug Atalasoft’s JS code in production.

But, that’s just GitHub. I’ve been posting code in other places before that. Here’s a multithreaded prime number sieve in clojure from 2008. Here’s a port of Apple’s CPPUnit to run on Windows from 2006. I found evidence that I published a JavaScript Code39 Bar Code Generator on my Atalasoft blog in 2008, which also has a Code39 web app based on it (which hosts the JS code). I have a lot of code snippets on StackOverflow, but only after 2008. My first post with code was in 2003 (comparing jUnit and NUnit).

I had a distinct memory of emailing an open source dev with a multi-threaded race condition fix for a C++ data structure that we used at Spheresoft. Looking at a list of our external libraries jarred my memory that it was WFC by Samuel R. Blackburn. I also found the WFC release notes in the Wayback Machine that mention my fix. He migrated WFC to GitHub much later, but I found a comment mentioning my fix. The actual diff predates the migration, but it’s the double-checked locking directly below the comment:

    // 1999-12-08
    // Many many thanks go to Lou Franco (lfranco@spheresoft.com)
    // for finding an bug here. In rare but recreatable situations,
    // m_AddIndex could be in an invalid state.

So, that’s 1999. Ironically, my oldest verified contribution is actually on GitHub, but predates its release by about eight years. Where’s my green square?

Before that, I have to go by memory because I can’t find the originals.

One thing that came to mind was back in college. I co-developed code for our computer center to draw plots on a Unix PC terminal (saving paper). Using that code, we also built a Unix PC driver for GNU Plot and sent it to them. I am pretty sure this was hosted on MIT’s Athena.

That would be in 1991 or so. I did some simple searches and didn’t find it, but supposedly there are FTP archives from that era, so I might try looking later.

Write While True Episode 48: Start a Collaboration

This is the beginning of season five, episode 48, and I’m going to tell you about something that we’re doing a little different now. This has always been a podcast where I, a software developer, talks to you, who I think are also software developers, about what I write and I’m trying to share with you tips and techniques for writing for people like us that want to write.

And in this season of Write While True, we’re going to start something new.

Write While True is now a collaboration between me and another software developer who writes. His name is Brian Hall. And throughout the entirety of this run of podcasts, I’ve talked a lot about the kinds of things I write, blogs, and I wrote a book, and this podcast.

Transcript

What Makes a Good First Vibe Coding Project

Code can be dangerous to run. It could have security issues. It could leak secrets. If you don’t know what you are doing yet, vibe coding is a good way encounter those problems fast.

Here are some aspects of a project that make it a good one to start learning how to vibe code. This won’t make them perfectly safe (no code that you don’t read could be). But, here’s where to start.

  1. It is a tool that only you will use
  2. It doesn’t need to deploy code to a server that is exposed to the public Internet
  3. It doesn’t need access to any services that require authentication
  4. It is meant to be a prototype
  5. It is run client-side only in a sandbox. For example: a 100% in-browser JavaScript or mobile app.

Games fit most of these.

I’ve been having fun with my nephew writing JavaScript games using PhaserJS. Agents seem to know this library well and we almost never need to look at the code. The games run in a sandbox (the browser) and don’t require any server-side code (that could be hacked).

Reducing Technical Debt: Top Five Coding Resources

When I wrote my book about technical debt, I decided to concentrate more on personal mindset, team dynamics, and organizational influence. I did this because I thought that the coding aspects of paying technical debt were well-covered, and that I could just refer to them.

Even with these resources, I had still been on teams that struggled with more foundational questions of whether code was debt at all, whether it was worth paying, how to explain it to our management, and how to prioritize and plan projects to do something about it. That’s what my book is about.

If you want to read some free chapters of Swimming in Tech Debt, sign up here:

Here’s my list of resources that are helpful when you need practical steps for addressing tech debt that you have identified and prioritized.

These resources cover Seams, Refactoring, Substitutions, Accretions, and Tidyings. A full toolkit of techniques to tackle debt.

#1: Working Effectively with Legacy Code [ad] by Michael Feathers. In this book, Feathers defines legacy code as code without tests. So it makes sense that the bulk of the book is the description of techniques for adding tests to code that wasn’t designed to be testable. Feathers introduces the concept of a seam, which is code you add to a function that makes it possible to control what that function does from an external source. The key point is that, in production, its behavior is completely unchanged.

Here’s a trivial example to illustrate a seam. It will seem like overkill, but the technique is useful in real-world code. Given a function like this:

def calculate_sales_tax(amount, zip_code):
  rate = db_lookup_sale_tax_for_zip(zip_code)
  return amount * rate

In a codebase without tests or database mocking, functions like this are risky to change. The goal is to get it under test before we refactor it, so let’s do it with a seam to learn the idea.

The first step is to change it to this:

def calculate_sales_tax(amount, zip_code):
  rate = 0.08
  if not TESTING:
    rate = db_lookup_sale_tax_for_zip(zip_code)
  return amount * rate

By adding the if not TESTING: we’re accomplishing two things:

  1. We have not changed the production code
  2. We have made the code testable

So, the next two steps are to add tests, and then to refactor (which is safer because the code is under test). To see the utility of seams, you have to imagine doing this systematically in the giant spaghetti functions you find in legacy codebases. Also, in the book, Feathers shows more sophisticated ways to do this than just an if statement.

The beginning of the book sets up the seam concept, and the bulk of the book is examples of how to do this with more realistic code. This is the best “How To” book for tackling technical debt in legacy projects that I have read, and its existence meant that, in my book, I could concentrate more on the questions of “When to” pay technical debt and “Who should” do it.

The point of a seam is to make refactoring safe. So, the next book on this list is the seminal work on refactoring.

#2: Refactoring: Improving the Design of Existing Code [ad] by Martin Fowler. The main contribution of Refactoring was to make the term (“refactoring”) ubiquitous. Unfortunately, even Fowler had to admit, the term is often misused. Whenever you go to daily update, and someone mentions refactoring, you could be sure that they are not doing what Fowler described.

To Fowler, a refactoring is a tiny act. His book catalogs about a hundred types. Examples include Renaming a Variable and Extracting a Variable. Each of these takes seconds. The more complex ones might take minutes. Hardly something you’d avoid or mention. Refactoring is something you are meant to do all of the time. Assuming, of course, that your code is under test.

You can freely refactor in code that is under test because the point of a refactoring is to improve the code without changing the behavior. It’s quick because the verification is automatic. Without that, safe refactoring would take longer because you need to write tests first.

So, enabling refactoring might take time. Instead of saying that you are refactoring, a more correct thing to note in your updates is that you are automating tests on code that you need to change, so that you don’t introduce regressions. In addition, you are reducing the chance of problems being found in review or QA. This is something that does take time, but also has value. If you label this activity “refactoring”, you risk being asked not to do it.

In my book, I talk about refactoring along with the more expansive idea of Substitution, which comes from Barbara Liskov and was formalized in her paper with Jeanette Wing.

#3: A Behavioral Notion of Subtyping by Barbara Liskov and Jeanette Wing. This paper is about how to design classes in an object-oriented language so that they can be subtyped safely (and how to do that subtyping). It’s related to the ideas in design by contract and opposes the notion of allowing for breaking changes. Liskov substitution is about replacing components in systems where you cannot (or will not) update the rest of the codebase to accommodate them.

It does this through subtyping, but I have always interpreted it as applying to almost any change I make to a system, not just class inheritance. In Object-Oriented programming, subtyping is one way to add new functionality to a codebase, but in my experience, it’s not the main way. In my current work, I don’t use OO or subtyping much at all. But, I substitute code all of the time.

In the paper, the authors describe a way to subtype that is safe to deploy because it does not violate any assumption in the system. This way of thinking applies to any change you might make to code. If you can, it’s better to change the code in a way that doesn’t invalidate all of your other code. Or, if you must, you know that you aren’t “substituting” any more, and there’s a lot more work to do.

Like a refactoring, a substitution should not break tests, but unlike a refactoring, substitutions do not aim to preserve all behavior. In fact, the entire point of a substitution is to add functionality. The paper describes how to do it safely.

To sum it up, your new code will be substitutable for your old code if (in addition to any language syntax requirements): (1) it does not add required pre-conditions to the caller, (2) does not break promised post-conditions, (3) does not violate any invariants, and (4) does not mutate anything assumed to be immutable.

The paper is formal and might not the best entry-point. But, the Liskov Substitution wikipedia article describes it well, and if you want to read more about how I apply substitution to any code change, I cover that in Chapter 5 of my book.

Another great entry-point on this subject is the video Spec-ulation by Rich Hickey

#4: Spec-ulation (video) by Rich Hickey. In Spec-ulation, Rich Hickey talks about the concept of accretion and how to use it to grow software. Accretion is related to substitution and refactoring and is another way to approach changing code (to pay off tech debt, for example).

Rich makes two interesting (and related) points about accretion. (1) like Liskov, he defines safe actions such as “doing more” and “requiring less”, which map to pre- and post-conditions (2) additionally, he argues that Clojure’s syntax makes many more accretions (i.e. substitutions) non-breaking.

In her definition of substitution, Liskov states that all compile-time, syntax, or language level errors introduced by new code are (by definition) non-substitutable. The paper is about “behavior” breaking substitution that is syntactically correct, but will fail at runtime.

For example, in C, if you add a new parameter to a function, that function is not substitutable because all callers need to be changed. In languages like Clojure, adding a parameter is not a syntax error because there is a default and callers don’t need to change. Rich’s talk is about the many syntax affordances in Clojure that allow future accretion (and is a warning about using new tools to subvert them).

He argues that language features like strong type checking, which offer compile-time correctness, limit software growth through accretion because changes that would be non-breaking (and safe) cause syntax errors. This can work well if you control all of the code and can update it. But it causes problems in public code libraries. Clients cannot upgrade their dependencies to get bugfixes and new features if that means their build will be broken for code that was working fine.

The takeaway here is to look at your language’s features and see some of these affordances as a way to pay off tech debt safely. For example: one, often overlooked, feature is namespaces. Using them, you can keep old and new (replacement) code running side-by-side until you are sure it’s ok to remove the old one.

I learned so much about growing code from Kent Beck in eXtreme Programming: Explained, but a more recent book from Beck is more specific to technical debt.

#5 Tidy First?: A Personal Exercise in Empirical Software Design [ad] by Kent Beck. This book builds on Beck’s previous books and the ones inspired by them (like Refactoring). In it, he introduces the idea of a tidying, which like a refactoring, aims to improve code without changing behavior. It is also meant as something to be done all of the time and to be quick and safe (because of tests).

Tidyings, if possible, are even smaller than refactorings. They are more about making code more readable now rather than making it easier to change (which refactorings do). Tidyings are things you can do to help you be a more active reader of code. For example, getting the reading order right and using whitespace better to chunk the code. Kent describes it as a way to get back into the original spirit of refactoring (tiny, safe changes that don’t alter behavior).

With seams, refactorings, substitutions, accretions, and tidyings, you can do a lot to improve a codebase and make it easier to change. This reduction of resistance to change is what I mean when I say to pay tech debt, and these resources detail the main ways I do it.