<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
    <title>jeremymikkola.com</title>
    <link href="http://jeremymikkola.com/atom.xml" rel="self" />
    <link href="http://jeremymikkola.com" />
    <id>http://jeremymikkola.com/atom.xml</id>
    <author>
        <name>Jeremy Mikkola</name>
        
        <email>webmaster@jeremymikkola.com</email>
        
    </author>
    <updated>2026-03-04T00:00:00Z</updated>
    <entry>
    <title>AI Thoughts</title>
    <link href="http://jeremymikkola.com/posts/2026_03_04_ai_thoughts.html" />
    <id>http://jeremymikkola.com/posts/2026_03_04_ai_thoughts.html</id>
    <published>2026-03-04T00:00:00Z</published>
    <updated>2026-03-04T00:00:00Z</updated>
    <summary type="html"><![CDATA[<div class="info">
    Posted on March  4, 2026
    
</div>

<h1 id="bear-cases">Bear cases</h1>
<p>Every other podcast these days is talking up the wonders of AI and how it is just a matter of time before everything radically changes. Meanwhile, as soon as I peel my eyes and ears away from my phone and look outside the window, I see that really nothing has changed. The singularity, where and whenever it is, isn’t here yet.</p>
<p>As a fun intellectual exercise, I thought I’d think through some bear cases for AI companies.</p>
<p><strong>Model Stagnation</strong>. If models stop advancing past a certain point (or users perceive them to stop advancing), the competitive environment can shift to a race to the bottom. Those tens of billions of investment and the high operating expenses become quite a liability.</p>
<p><strong>Chip Shortage</strong>. This could take the form of supply chain issues (remember that term from the Covid years?) or simply from AI’s scale exceeding the world’s chip production capacity. Prices of chips are driven up to the point that additional investment has a negative expected ROI. Either training slows, or inference costs go up. This helps profits in the short term, but puts the companies in a risky position long term if someone else can still make chips.</p>
<p><strong>Model Supremacy</strong>. If one lab’s model starts outstripping all the other models, they’ll get the lion’s share of the revenue, and thus the investment, and be able to spend the most on training the next model, maintaining their lead. All the other labs’ revenue dries up.</p>
<p><strong>Investment Dries Up</strong>. AI companies will lose their luster at some point as surely as social media companies did. Investors will start having more interest in the return on their investment than in making more investments. When the music stops, not everyone will have a chair to sit in. Will each lab be able to make the transition to living off of what they’ve built so far?</p>
<p><strong>Cheap Models</strong>. Models that are cheap to run (free licensing costs, cheap hardware) have been getting good. A cheap model could plausibly get good enough that everyone stops paying $20 or $200 a month for their subscriptions. The SOTA models will of course be better, but when only the most intensive workloads need them, it becomes very hard to recoup the training cost.</p>
<p><strong>Legal</strong>. At this point it seems that the copyright ship has sailed and poses no risk to AI labs. Still, you could imagine laws limiting their power use or requiring them to pay well above market for their power. Or, laws governing the safety of models which are so severe that the models become useless. Real danger from the legal side seems unlikely, however Anthropic’s recent woes prove that this is still meaningful.</p>
<p>It’s interesting to think of which of these bear cases do and do not apply to Google, which has a large source of income outside of AI.</p>
<h1 id="expected-in-the-next-decade">Expected in the next decade</h1>
<p>Some of these will probably happen much sooner than a decade from now.</p>
<p><strong>A good robot model</strong>. It seems reasonable to expect that a model will be developed which you can plunk into some off the shelf humanoid-shaped robot hardware it will start being practically useful. They’ll get used in cases where the robot can either charge often or be tethered to a power source. Or maybe swapping batteries will be practical. Presumably the first uses will be cases where the cost of using humans (wages, danger, safety equipment) is high.</p>
<p><strong>Continual Learning</strong>. You can teach someone who has never played the guitar before how to make a C chord. You’ll have to give them very detailed instructions and corrections about what to do with each finger and how to fix buzzing strings. It might take them a minute or two to get it to sound good. Compare that to an experienced guitar player, who can play a C chord without even thinking: their brain is hundreds of times more efficient at that task because they’ve practiced it. AI models don’t get a chance to learn from their practice. They can keep knowledge in their context window the way the guitar student can keep your instructions in their head, but they never become faster or more efficient at a task over time. They don’t “compress” what they’ve experienced into updates to their weights.</p>
<p><strong>Bicycles for the mind</strong>. If computers are bicycles for the mind, why aren’t they bicycles for the artificial mind? Agents are starting to make really good use of tools that have been built for humans - particularly CLI tools - but we’ve seen fairly little (besides MCP servers) in the way of code for von neumann machines written specifically for making AI more powerful.</p>
<p><strong>New Chip Fabs</strong>. At some point it will start to make sense to start a new chip fab company to rival TSMC. Or, maybe one of the existing fab companies will step up their game and be competitive once again. It seems unlikely, but it is also possible they could be more vertically integrated and create their own lithography machines instead of relying on ASML.</p>
<p><strong>A trillion lines of code</strong>. Google has some billions of lines of code. That’s the unit we talk about code size in: no one cares how many assembly instructions that is because it’s not what we interact with. If we interact less directly with code, we’ll stop caring how much of it there is. It will also become easy to produce lots and lots of it. Some company may hit the trillion line mark. (Based on a quick Google search, the world probably has at least 10 trillion lines of code.) At that scale, how do you check for security issues? We might have to invent faster compilers and other tools to have any hope of even building that much code. When code becomes free, Amdahl’s law says that shipping software doesn’t become free because all the other costs start to dominate. We’ll have to invest more than ever in making good, fast developer tools.</p>
<p><strong>More efficient training</strong>. Someone pointed out that when you are working your way through a math textbook, you aren’t trying to predict the next word in the text. Most of your learning comes from doing the exercises. You can innately feel when you don’t know what step to take next in solving a problem and explicitly direct your reading in search of what you’re missing. If you get a problem wrong, you can step back over your own work and figure out what the problem was. You learn in a very different process from an LLM. That’s a good thing, because LLM training needs far more words than you’ll ever hear or read. The way training works today is fundamentally very inefficient. Models don’t compress reality all that much. They never feel an “aha” moment when they realize that many different observations can be explained with a new mental model. Some lab is sure to figure out how to fix this.</p>
<h1 id="hopes">Hopes</h1>
<p>There are a few things I hope AI will bring in the not-too-distant future:</p>
<p><strong>More cures</strong>. We might not tell AI “go fix migraines. make no mistakes.” but Google’s DeepMind has already proven that AI can be very useful in understanding biology (e.g. AlphaFold). AI should allow us to find more promising molecules much faster, and have a much higher success rate by sussing out problems in simulations.</p>
<p><strong>Build better businesses</strong>. If AI can listen in on every meeting and read every document (which it <em>basically</em> already can), it would have an amazing amount of insight into problems in businesses and be able to solve them. We forgot to tell the team in India about the priority change because they weren’t in that meeting? No worries, the AI noticed this and flagged the issue. Maybe it just shared that context with them without needing to be told. Someone is overloaded and burning out, but not doing a good job of communicating what’s on their plate because they are too stressed to think straight? AI could easily be trained to spot this and help. Decisions aren’t getting made? Well guess what, AI knows all the best organization practices and has read all the management books. Responsibilities shift to a new team? AI has all the history of where the skeletons are buried and which random script you have to run to fix things.</p>
<p><strong>Actually help humans</strong>. If algorithms can create echo chambers and make everyone mad at each other, surely AI could be used to help people understand each other (which requires intention, not mere information - which is why the internet by itself didn’t help here). If people want to hang out and make friends, AI could suggest times when people are available, things to do that everyone likes, and goad people out of the house.</p>
<h1 id="runaway">Runaway</h1>
<p>I don’t fundamentally believe the runaway intelligence (or the “city of geniuses”) case. Partially this is because the amount of electricity we can generate and the number of chips we can make is pretty limited. AI is bits that run on atoms, and atoms are a lot harder to scale than bits.</p>
<p>Models will certainly be useful in creating the next model (I’m sure labs already use one model to prepare training data for the next) the way that computers are a useful tool when designing the next generation of computer. Can an AI be trained that is better than any human at coming up with ways to make the next model better? Probably! (I wonder when it becomes the only logical choice for a lab to start devoting all their effort to building this.)</p>
<p>Will that create AI models that are smarter than humans? I mean, computers have always been smarter than humans in a lot of ways. A computer from 80 years ago can do way more math than I can. One from 40 years ago can remember far more information than I can. Algorithms have been able to design circuits, optimize routes, and predict weather better than any human for a while now. AI models make computers better than humans at many more things, and make them quite good at many more. It seems reasonable to expect that we’ll eventually make an AI that is better than any human at everything. The singularity appears imminent.</p>
<p>And maybe once you are smarter than humans you can work your way past all the bottlenecks: power, manufacturing, rare minerals, speed of light latency, and keep making more computational power and use it to build smarter models. I think this process doesn’t go on forever, for one of two reasons:</p>
<ol type="1">
<li><p>It plateaus. Human brains have some upper limit of intelligence, so it seems that silicon brains should as well. Show me something without an upper limit and I’ll show you a crypto scam.</p></li>
<li><p>It keeps getting more intelligent, but each iteration takes longer than the last. The advancements in computation power don’t keep up with the increased needs of each subsequent model thanks to the pesky limits of physics. The advancements get further and further apart until it is stalled for all practical purposes (or more realistically, until we decide to stop investing the world’s resources in letting it autoadvance).</p></li>
</ol>
]]></summary>
</entry>
<entry>
    <title>A UI might not make it easier</title>
    <link href="http://jeremymikkola.com/posts/2025_02_22_might_not_want_self_serve_ui.html" />
    <id>http://jeremymikkola.com/posts/2025_02_22_might_not_want_self_serve_ui.html</id>
    <published>2025-02-22T00:00:00Z</published>
    <updated>2025-02-22T00:00:00Z</updated>
    <summary type="html"><![CDATA[<div class="info">
    Posted on February 22, 2025
    
</div>

<p>Building self-serve access to internal systems is generally a good idea. It would be silly to do engineering work to make a change when pressing buttons would do. The fact that it’s generally (and not universally) a good idea means that there are cases where it isn’t a good idea. The idea of making a task easier by making it self-serve, no code, in a UI shouldn’t be applied when the unavoidable complexity and nuance of the task to be done exceeds what a UI can reasonably handle.</p>
<p>You probably have examples of a story like this: A company has a problem to solve. They build a system to solve that problem. It does a good job of solving that problem, though only for that variant of the problem. Time marches on and the system gets expanded to handle more variations of the problem. This expansion requires bespoke engineering work each time. Some systems grow to the point where the expansion becomes routine. The internals of that system (assuming sensible leadership in that engineering organization) evolve into general-purpose pieces that can be reused in the different variations of the problem. Handling new cases may be as simple as just plugging together the existing pieces of the system in a new configuration. As more and more variations are handled, the unique engineering work fades and configuration work takes over. At this point it’s reasonable to ask the question, why does this require engineering at all? Wouldn’t it be nice if the person who came up with the requirements could open some admin tool and punch in what they need? Let’s build a self-serve UI!</p>
<p>As stated above, building this UI often is a good idea. As a rule of thumb, the simpler the configuration, the better this idea is. When systems are more complex, it doesn’t make as much sense to have a self-serve layer in front of them. If you find yourself saying, “this is trivial, why do we need engineers involved?” then by all means create a self-serve UI. But if you are saying, “this is hard, let’s add a self-serve layer to make it easier,” you might find yourself running into some problems. Having self-serve access to the system doesn’t actually make the hard parts easier, but it does make the system more rigid, and it risks giving a false sense of security while actually making the system more dangerous.</p>
<p>Here are some reasons why you might <em>not</em> want to build a self-serve interface:</p>
<h2 id="it-may-not-solve-the-problem">It may not solve the problem</h2>
<p>The work to be done is configuration. Or at least, it is labeled as configuration work. But the people doing the work know that writing the config file is easy. The hard part is everything else leading up to that point. The actual engineering part of the work doesn’t go away just because there’s a self-serve way to handle the configuration portion.</p>
<p>A project where the end result is a new configuration might start with changing the underlying system to support that new configuration. It may have already been possible to specify that configuration, but it might not have actually worked (perhaps there is an option for languages, but it didn’t yet support right-to-left languages, or the phone number validation doesn’t work for that country). In other cases it may not even have been possible to specify the desired logic and it takes engineering work to create a way to describe what you want in the configuration. The required engineering work could be subtler than that: it might work fine for any one user (or even a thousand) but run into some kind of scaling or cost problem when shipped to production traffic.</p>
<p>Any time a system is asked to do a new thing that requires engineering work, regardless of whether that new thing is described as a feature or as a configuration. Even determining whether engineering work is needed requires the input from an engineer.</p>
<h2 id="practical-downsides">Practical downsides</h2>
<p>There are other practical downsides to structuring a config as data created by a UI and stored in a database instead of a config structured as a file checked in to git. You don’t (automatically) get revision history or deploy controls over the config.</p>
<p>Debugging can be harder as well. In a development environment you may not have a copy of the configuration as it exists in the production database. Accurately reproducing the bug now requires extra work to reproduce the configuration.</p>
<p>You can’t write tests for something that’s not there. Testing requires a copy of the logic being tested to live in the codebase. Often configuration written using DSLs will be treated like any other code and be subject to unit tests. This both tests that the configuration does what was expected and the system that is configured handles that config correctly. Without these tests, the new config is tested in production.</p>
<p>Building self-serve systems to handle complex cases is also hard work that takes a very real amount of engineering time. An accurate accounting of the real cost of building such a system (figuring out usecases, designing, building, iterating, documenting, teaching and learning) may reveal that it would cost far more to build than the work it could save - and it’s easy to overestimate how much work it will save. (See the previous point about solving the problem.)</p>
<h2 id="the-computer-cant-think-for-you">The computer can’t think for you</h2>
<p>When there are important decisions to be made surrounding the launch of a new variant of a thing, those decisions can’t be left up to the computer (at least not with today’s AI…). The computer hasn’t talked to customers, it doesn’t understand the goals of the company’s leaders, knows nothing of the company culture, isn’t qualified to understand today’s legal constraints, and doesn’t have experience with what has worked well or poorly in the past. It just doesn’t have the context to make decisions.</p>
<p>A self-serve UI can’t save you from novel engineering work, and it also can’t save you from thinking. You still have to understand goals, customers, priorities, communication channels, and everything else that goes into defining how this functionality will work and will be launched. Not only must this irreducible work be done, but someone also has to understand how to do it. They’ll have to reason it out from first principles or talk to people who have done it before. Computers can’t replace experience.</p>
<p>Experience gives an understanding of the implications of decisions. Experience can take you from thinking “we’ll refresh the data on all of the accounts” to “we need to start a conversation with the team that handles this contract to get our limits increased for this API that we call when refreshing that data.” Even if you have a 1-click way to start that data refresh process, someone still has to understand the implications of doing it.</p>
<h2 id="it-makes-the-system-too-rigid">It makes the system too rigid</h2>
<p>Projects don’t exist in a vacuum. They impact the things around them. When self-serve tooling is bolted on top of a system, it tends to change that system. The self-serve tooling depends on the system, but the system also tends to start depending on that tooling: you often end up being forced to use that tooling if you want to interact with the system. If this doesn’t happen when the tooling is built, it can happen (intentionally or accidentally) later as the two systems co-evolve. This is fine <em>if</em> you are always happy with using that UI. Sometimes you aren’t.</p>
<p>A UI can do the things that it was built to be able to do, and only those things. A UI can’t make the underlying system more general, but it can fail to expose the full breadth of that system’s generality. This means you can run into things that you want to do, and that the underlying system is able to do, but that you can’t do because the UI (that you now have to use) can’t handle it. This is a case where the UI intended to make the process easier is ironically making it harder.</p>
<p>This situation tends to be handled either by working around the self-serve tools (perhaps doing sketchy things like changing values in the database by hand) or by taking the time to update the UI to support this particular use case. Some systems see a constant stream of issues due to the workarounds. Others cause a pattern of delayed projects due to the time needed to stop and update the UI.</p>
<h2 id="a-false-sense-of-security">A false sense of security</h2>
<p>This isn’t so much a reason to avoid creating a self-serve UI as something to be aware of after creating it: the presence of a UI can make tasks look easier and safer than they actually are. Changes made to code are given a level of planning, thought, and scrutiny that presses of buttons are not.</p>
<p>When there’s a class of project that looks like it doesn’t require engineering effort - perhaps because there’s a nice shiny UI or perhaps because this kind of project sometimes really is possible without engineering effort - plans get made assuming the project will not involve any engineering effort. That might not be true in this particular case. The relevant engineers are finally consulted late in the project, and everyone is dismayed to find out that this actually will require engineering work, work from an engineering team has no spare capacity this quarter (“look at all the super important things we already had to cut!”) and even if they did, there’s no way it could be done in the 2 days before this thing launched. Some readers may have just felt their blood pressure increase.</p>
<p>To generalize that idea, it is dangerous to make a type of project appear too easy because that will set an expectation that it will actually be that easy. Take lessons from Star Trek’s Scotty.</p>
<p>There’s a point that is worth bringing up again: projects often require expertise and experience in that kind of project. For someone new, there can be a lot of unknown unknowns. Tooling that abstracts over details that are actually important can result in a project being undertaken by people who, through no fault of their own, are lacking critical knowledge to make that project successful. They may not even know all the things that they don’t know. The people who built this UI have this institutional knowledge, but since they aren’t involved in the project, that expertise doesn’t get transferred to the people doing the project. That institutional knowledge can also rot if they continue to not be involved in these projects. Simple interfaces give the incorrect impression that only a simple understanding of the project is needed. Planning and staffing decisions made based on that incorrect assumption.</p>
<p>Making a task self-serve doesn’t inherently make it safe. It certainly can, and this can be one of the strongest arguments for adding UIs. You can write any kind of logic you like to validate and safety-check, to require second-person approval, and anything else you can think of. For a lot of projects this really does make the operation quite safe! However, there are some projects that you can never make truly safe. No matter how nice of a UI you put on a flamethrower, it’s still a flamethrower. But that UI could make it <em>seem</em> safe, at least until someone’s car is on fire.</p>
<p>You can incrementally make UIs safer over time by adding more warnings and safeguards. However, know that those changes will be written in blood. You will have to accept making big mistakes with a UI before you’ve found all the ways it can be dangerous.</p>
<p>Self-serve UIs can even introduce safety problems of their own. This can happen when it is too easy to misclick something you can’t undo. Another common pattern is an operation having very unexpected side effects (think along the lines of, you update a minor setting only to fnd out that doing so resulted in it emailing thousands of users).</p>
<h2 id="conclusion">Conclusion</h2>
<p>Many if not most projects to give self-serve access to systems don’t run into these problems. If your goal is to minimize tedium, and there is genuinely very little decision-making, planning, expertise, engineering, or risk management required each time, then you are probably fine. By all means make the process easier.</p>
<p>But if you are trying to make hard work easier by adding a self-serve UI, consider whether you have a complete understanding of what was making that work hard in the first place. You might only be making it easier to accomplish the nominal task (“create a new config”) but not to do the actual work behind it.</p>
<p>When a project to build a self-serve UI is on the table, two things are clear: (1) there’s a problem to solve, and (2) there’s some engineering effort that can be thrown at the problem. When those are both the case, it may pay better dividends to approach the problem by getting a little bit deeper into the weeds. If there are sharp edges in the existing system that keep causing issues or deficiencies that keep having to be worked around, fix those (especially if everyone has given up on those problems being solved and considers them to be an intrinsic part of this kind of project). Make the system more general, make it easier to understand, write friction logs and fix that friction, fix the underlying data model - there’s an endless set of projects that might make future changes easier.</p>
<p>If you do build self-serve tools, make them as specific and orthogonal as possible. Let users recombine them in ways you had never expected.</p>
]]></summary>
</entry>
<entry>
    <title>What You Are Paying For</title>
    <link href="http://jeremymikkola.com/posts/2024_12_31_what_you_are_paying_for.html" />
    <id>http://jeremymikkola.com/posts/2024_12_31_what_you_are_paying_for.html</id>
    <published>2024-12-31T00:00:00Z</published>
    <updated>2024-12-31T00:00:00Z</updated>
    <summary type="html"><![CDATA[<div class="info">
    Posted on December 31, 2024
    
</div>

<p>When you go into your local burger shop and buy a burger, what are you paying for? One way to look at it is that you are paying for the food, for the convenience, for the experience - you are paying for what you get out of it.</p>
<p>You can also think about the things that are necessary to create the product you bought. There’s the beef and the buns. There’s also the labor that goes into making that burger. If you want to really pull apart the business you can list all sorts of things you are also paying for: the rent on the space the restaurant is in, their taxes, insurance, interest on loans, utilities, grease disposal, cleaning, advertising, repairs, accounting, shipping, spoilage, management, banking and payment services, toilet paper, and maybe a paper-thin slice of profit on top.</p>
<p>That mental model feels a lot more detailed. You could list out where every thousandth of a cent of that burger’s price ultimately goes. But that model misses a dimension: it misses why those elements cost what they do.</p>
<p>Take rent, for example. A big factor in rent costs is supply and demand. A big factor in the supply side is how much real estate the city will allow to be zoned commercially, and what restrictions they place on it. Laws intended to keep a city looking nice, like limiting the height of buildings, have a side effect of increasing rent. When you go to one of those hip locations to buy your burger, some of what you are paying for is the fact that the surrounding area is nice to be in. When you buy a burger you are buying the outcome of zoning decisions.</p>
<p>You are also paying for that location to continue to be a burger shop instead of turning into a Chase bank or Starbucks.</p>
<p>Let’s say that just down the street from the local burger shop is a franchise of the large national fast food chain of your choice. What are you paying for if you eat there, instead? Obviously you are paying for a slightly different mix of many of the same costs associated with the local place. The meat might be a bit cheaper but now there’s landscaping and power washing costs (this location has a drive-through). Since this location is a franchise of a chain, some slice of the pie also goes to that national chain. What does that buy?</p>
<p>It buys use of the brand name, it buys access to the logistics chain, and it buys advertising. It also buys whatever else that national chain decides to spend their remaining money on. In the corporate headquarters of every burger-selling chain out there, there will be someone whose job it is to handle expansion. Someone in some office building sits and makes decisions about whether a given site is good for a new location. When you buy a burger from your local instantiation of this burger empire, you are in effect buying more locations of that chain. Those new locations are likely somewhere hundreds or thousands of miles away and of little value to you.</p>
<p>When you pay for a SaaS service, part of what you are paying for is feature development. Whether you are paying for the feature development that already happened in the past, the feature development that is currently happening, or the feature development that will be done in the future… is left as an exercise for the reader. What it does not pay for is the development of features that are all aimed at you. Some of that development may go to make your life better: a thing runs a bit faster or has another bell and whistle. Some of that development is aimed more internally to the company: they make something cheaper for them to run, page them in the middle of the night less often, or have fewer security holes. But, like the national burger chain, some of it will be aimed at expansion. They will develop new features, spin up new teams, perhaps even create entire new product lines (or acquire other businesses) for the sake of expanding. They might start building the features that they need to have to win the big enterprise user, features that might be utterly useless to you. And yet part of what you are buying when you pay for that service is their ability to acquire those customers.</p>
<p>This entire line of thinking becomes practically useful when flipped on its head. Instead of thinking about the fact that buying a burger buys a nice neighborhood, one can follow the logic in the other direction and see that laws and policies that lead to nice neighborhoods will reduce the supply of commercial real estate, increasing the price of rent, and thus cause burgers to get more expensive. Expanding to enterprise users means giving your small users a bit less for their money. Every action has externalities, and every decision has costs. Things following the pattern of “you cannot have your cake and eat it too” (or “there’s no such thing as a free lunch”) are the rule not the exception.</p>
]]></summary>
</entry>
<entry>
    <title>What makes developers productive?</title>
    <link href="http://jeremymikkola.com/posts/developer_productivity.html" />
    <id>http://jeremymikkola.com/posts/developer_productivity.html</id>
    <published>2023-07-15T00:00:00Z</published>
    <updated>2023-07-15T00:00:00Z</updated>
    <summary type="html"><![CDATA[<div class="info">
    Posted on July 15, 2023
    
</div>

<p>Many ingredients go into developer productivity. Some are obvious and easy to measure (build times
are an easy example to point to). But I think we tend to miss other important factors, perhaps
because they are hard to directly measure. For example, no system collects a number that shows how
well each developer understands the system they work on. It’s impossible to directly measure the
things that happen inside a developer’s head. If you only measure the output (their productivity),
that data won’t tell you what is missing (e.g. documentation).</p>
<p>It might be hard to find a clear metric for some of the factors below, but it is still worthwhile to
ensure that you don’t have problems in these areas that hurt productivity. Some of these factors
overlap, but they are different enough mental models that it is useful to think about them
separately.</p>
<p><strong>Knowing what to build</strong>. Building the wrong thing very quickly isn’t especially
productive. It’s important to know what the customer needs, what other teams will accept (How many
indexes can a database table have? Would a feature share information we can’t legally share?), and
what has been tried before and didn’t work.</p>
<p>Sometimes you build things acknowledging that they might be the wrong thing to build, but you build
it anyway for the sake of what you’ll learn. You might learn from a prototype why an approach
doesn’t work or from an MVP what a customer actually needs. Still, you need to have a sense for what
ideas are worth trying.</p>
<p><strong>Doing fewer things</strong>. Being able to get a task done quickly is good, not having to do it at all is
better. A company’s processes can add “busywork” that you’d be more productive without. Sometimes
there are easy ways to tweak a process so that it delivers the same value with much less work.</p>
<p>There can also be a certain amount of effort needed to “keep the lights on” (KTLO). This is work
that constantly needs to be done (e.g. answering support tickets) but doesn’t move projects
forward. This work can look productive by many metrics (tickets closed, commits merged) without
leaving the company in a better place.</p>
<p><strong>Tooling that reacts quickly</strong>. Developers are constantly using tools: their editor highlights
code and autocompletes method names, git commits the code, a build system runs tests. Every
additional second of time these tools take has a high cost when multiplied by how often they
run. Besides the raw cost in minutes and hours, slow tools also break a developer’s focus. Having
your work stopped in its tracks by a slow tool is also frustrating, especially to someone who is
stressed about timelines.</p>
<p>See also <a href="https://jeremymikkola.com/posts/2021_04_01_the_problem_with_slow_tools.html">The problem with slow
tools</a>.</p>
<p><strong>Knowledge in the developer’s head</strong>. Here’s one that’s hard to measure!</p>
<p>All else being equal, a developer with more relevant knowledge will be more productive. They won’t
have to dig through code because they’ll already know how it works. They’ll know how to use the
tools and what pitfalls to avoid. They’ll ask the right questions. 10x developers exist, and they’re
the people who really know the codebase.</p>
<p>This means that a team shouldn’t own more things than they can collectively keep in their heads
(ideally with a bus number greater than 1). It also means it is advantageous to minimize how often
ownership changes hands - no one will ever know more about a thing than the person who created
it. Ideally people new to a system can work alongside those already familiar with it and learn from
them.</p>
<p>You also need clean boundaries between systems. A clean interface with simple semantics means you
can think about the properties of that interface instead of having to know about the entire system
behind it.</p>
<p>Documentation is a great way to transmit knowledge. It is especially important to have when
developers need to accomplish a specific task that they aren’t familiar with. A lack of
documentation can slow a developer down because they’ll have to research for themselves how to
accomplish the task, they’ll make mistakes and have to redo their work, and they’ll have to wait for
other teams to answer questions. This can easily turn a one hour task into a two day task. If 100
developers need to do this task, the cost of missing a page of documentation could be roughly the
yearly salary of a developer.</p>
<p>This is also an argument for increased specialization. It is unproductive to require every developer
to work on a broad range of things. An hour spent learning details of some security system or a
capacity planning process is an hour not spent understanding the system the engineer works on or the
domain in which they need to solve problems.</p>
<p><strong>Helpful infrastructure</strong>. The infrastructure should be an aid rather than a hindrance. This
means it needs to align reasonably closely to what needs to be done. Every piece of infrastructure is
designed with some use cases in mind, but the needs of a project might fall outside of those use
cases. It’s frustrating to hear the statements “you must use our infrastructure” and “you can’t do
that with our infrastructure.” You waste time either working around the infrastructure or sitting in
meetings where you attempt to convince the owners of the infrastructure to meet your needs.</p>
<p><strong>Low tech debt</strong>. The existing code will never be a perfect fit for what you are trying to do; the
original author didn’t have a crystal ball to know what kind of change you’d need to make. But some
pieces of code are much easier to change than others. The answer to “how do we do X” should never be
“we have to rearchitect this entire thing”. When there’s more tech debt, smaller changes to the
functionality require larger changes to the system. Lowering tech debt minimizes the surface area
that (a) you need to understand and (b) you need to change.</p>
<p>Projects to pay off tech debt need to be finished. Abandoning or deprioritizing them in the middle
can leave the system worse off than it started (see <a href="https://jeremymikkola.com/posts/2022_01_29_tech_debt_gets_worse_before_it_gets_beffer.html">Tech debt gets worse before it gets
better</a>).</p>
<p><strong>Low rates of failure</strong>. If a tool fails to run, a flaky build fails, a deploy fails, or your
change causes production errors and you have to revert, you’ve wasted time dealing with a
failure. Lowering the probability of these failures increases productivity.</p>
<p>Besides the engineer who experienced it, failures tend to also waste the time of teams that own the
failing system because they’ll be called upon to help diagnose and fix the failure.</p>
<p><strong>Productive practices are practical</strong>. The best way to learn how to solve a particular problem
might be to write a prototype. If the environment discourages prototyping, it could be discouraging
the most productive approach. If your monitoring tool is painful to use, developers will make fewer
dashboards, measure fewer things, and decisions will be less data-driven as a result. On the other
hand, if you make it easier to split a huge change into smaller code reviews, that can make the code
easier to review and safer to deploy.</p>
<p><strong>How much an engineer can focus</strong>. Engineers operate on a maker schedule. They need to be able to
focus. That focus can be stolen by meetings and interruptions. Interruptions include slow CLI
commands, slow tests, and encountering a task you don’t know how to accomplish and now need to
research.</p>
<p>Worrying about too many things in any given week also hurts the ability to focus. A looming deadline
or an unanswered question from a manager takes up mental RAM even when you are trying to focus on
something else.</p>
<p><strong>Finishing tasks</strong>. Building 50% of a thing isn’t 50% as productive, it’s 0% as
productive. There’s few things less productive than work that is thrown away.</p>
<p>There are cases where abandoning a project half way through is the right call. Fighting the Sunk
Cost Fallacy is sometimes the right thing to do. But there shouldn’t be a consistent pattern of
changing priorities before a project finishes. That can drop a team’s productivity to zero.</p>
<hr />
<p>You can’t necessarily build a dashboard to measure all of these different factors, but I think any
developer could tell you which of these is currently impacting their productivity. Fixing those
problems could dramatically increase how much gets done. Some of the problems can be shockingly easy
to fix: a few hours spent writing a page of documentation might save the company thousands of hours.
When you need to cut trees quickly, start by sharpening the saw.</p>
]]></summary>
</entry>
<entry>
    <title>Tech debt gets worse before it gets better</title>
    <link href="http://jeremymikkola.com/posts/2022_01_29_tech_debt_gets_worse_before_it_gets_beffer.html" />
    <id>http://jeremymikkola.com/posts/2022_01_29_tech_debt_gets_worse_before_it_gets_beffer.html</id>
    <published>2022-01-29T00:00:00Z</published>
    <updated>2022-01-29T00:00:00Z</updated>
    <summary type="html"><![CDATA[<div class="info">
    Posted on January 29, 2022
    
</div>

<p>Projects to pay down tech debt tend to start by making more tech debt. The level of tech debt gets
worse before it gets better. The new, cleaner code is just adding to the mess until it allows you to
finally delete the old code.</p>
<p>This has two implications. First, don’t wait too long to start paying off tech debt. You don’t want
to be in a situation where temporarily making it any worse is unbearable. Second, don’t stop
clean-up projects in the middle. If you do, you may be left worse off than you started.</p>
<h2 id="it-gets-worse-before-it-gets-better">It gets worse before it gets better</h2>
<p>Have you ever tried to reorganize the stuff on some really messy shelves? You probably found it hard
to put any item where it should go because another object was intruding on its home (and that
object’s place is also occupied, and so on). Before long, you’ve resorted to pulling everything off
of a shelf, moving it to a pile on the floor. This gives you a clear shelf to start filling with the
things that belong there. Now progress can begin, and you can put that one shelf in order.</p>
<p>As you started the work of organizing the shelf, things initially got worse, not better. What
started as a messy set of shelves became a messy set of shelves plus piles on the floor. It wasn’t
until near the end that things started to take a turn for the better. If you graph the amount of
mess over time, it might look like this:</p>
<svg width="700" height="350" viewbox="0 0 700 350" xmlns="http://www.w3.org/2000/svg">
<path fill="none" stroke="#888888" stroke-width="3px" stroke-linecap="round" d="M 20,310 L 680,310" />
<text x="80" y="330" fill="#333333">Time</text>
<path fill="none" stroke="#888888" stroke-width="3px" stroke-linecap="round" d="M 40,20 L 40,330" />
<text fill="#333333" transform="translate(20, 20) rotate(90)">Mess</text>
<path fill="none" stroke="#333333" stroke-width="2px" d="M 50,170 L 150,165 C 250,160 250,65 350,65 C 450,65 450,270 550,265 L 650,260" />
<text x="100" y="200" fill="#333333">Moving things to the floor</text>
<text x="350" y="290" fill="#333333">Putting things back on the shelf</text>
</svg>
<p>The very same thing happens when you take on a project to start paying down tech debt. As you start
to replace the old thing with the new thing, you initially start making the mess worse, not
better. It’s only as you get towards the end of the project that the amount of tech debt finally
goes below the original level.</p>
<svg width="700" height="350" viewbox="0 0 700 350" xmlns="http://www.w3.org/2000/svg">
<path fill="none" stroke="#888888" stroke-width="3px" stroke-linecap="round" d="M 20,310 L 680,310" />
<text x="80" y="330" fill="#333333">Time</text>
<path fill="none" stroke="#888888" stroke-width="3px" stroke-linecap="round" d="M 40,20 L 40,330" />
<text fill="#333333" transform="translate(20, 20) rotate(90)">Tech debt</text>
<path fill="none" stroke="#333333" stroke-width="2px" d="M 50,170 L 150,165 C 250,160 250,65 350,65 C 450,65 450,270 550,265 L 650,260" />
<text x="100" y="200" fill="#333333">Starting a cleanup project</text>
<text x="350" y="290" fill="#333333">Deleting the old code</text>
</svg>
<p>It is reasonable to ask: Why does this happen?</p>
<h2 id="rebuilding-a-car-while-driving-it">Rebuilding a car while driving it</h2>
<p>What about tech debt makes it get worse before it gets better?</p>
<p>It helps to start with thinking about why the tech debt is there in the first place. Systems tend to
accumulate tech debt as they are changed. With each tweak or new feature, the old structures become
slightly less correct. Hacks and work-arounds creep in. Like adding layers upon layers of paint,
each change introduces only a small amount of tech debt, but it adds up.</p>
<p>People don’t change systems frequently for no reason. You don’t go out in the garage and rearrange
the box of Christmas lights several times a week. You only make changes to things when you expect to
get some value out of those changes. Frequent changes imply that there’s a lot of value to be had
(otherwise it wouldn’t be worth the effort). There is a correlation between the rate at which a
system accumulates tech debt and the value that the system provides<a href="#fn1" class="footnote-ref" id="fnref1" role="doc-noteref"><sup>1</sup></a>.</p>
<p>That value depends on the system continuing to work. The fact that you need the system to keep
working means you can’t just delete it and write a new clean piece of code from scratch. The
business wouldn’t be too happy if the signup system was just gone for six months while you work on
writing a shiny new one. Other developers would suffer if you deleted code that’s needed to build
and test their systems. You have to keep the old thing working while you are busy making it cleaner.</p>
<p>The siren song of the ground-up rewrite calls to us, trying to make us forget the fact that it’s
much harder to replace an existing system than it is to create one from scratch<a href="#fn2" class="footnote-ref" id="fnref2" role="doc-noteref"><sup>2</sup></a>. In reality,
rewrites feel more like trying to rebuild a car while driving it down the road.</p>
<p>You have to keep supporting old APIs until every client has moved to the new APIs. Existing data has
to be migrated to new models. You have to research (and often replicate) the quirks and undocumented
features of the existing system. All these different issues have a complicated web of dependencies
between them, making it hard to even know where to start.</p>
<h2 id="without-a-map">Without a map</h2>
<p>Any time you are paying down non-trivial tech debt, you are probably restructuring the system to
make it a better fit for today’s needs. The good news is that you should get a system that’s cleaner
and easier to reason about. The bad news? The structure is different, so the components of the old
system - the classes, database tables, services, pipelines - won’t map 1:1 to components in the new
design.</p>
<p>Say you are redesigning a system involving products and prices. In the old system, there’s one giant
messy class with all the price calculation logic. In the new system, each product’s class is
responsible for price calculations for that one product. While you are working on writing those new
product classes, you’ll still have to use the old messy class for price calculations (at least when
working with the products that you haven’t migrated yet). You can only safely delete that old code
when every single product is ready to calculate prices for itself. In the meantime, you have a lot
of duplication between the new product classes and the old price calculation class. This duplication
happened because the components of the new system didn’t map 1:1 to the components of the old
system - you couldn’t just drop in a new price calculation class with the same interface as the old
one. Instead, you had to wait until all the different things it mapped to (all the different product
classes, in this case) were in place before you could finally delete it.</p>
<p>You may often find yourself in a situation where there’s basically no piece of the old system that
you can safely delete until nearly all of the new system is in place.</p>
<p>So, tech debt accumulates due to changes. Changes are made because the system has value. That value
still needs to be provided while the tech debt is being fixed. That makes life difficult and you
end up with duplicated systems.</p>
<h2 id="now-there-are-two-of-them">Now there are two of them</h2>
<p>That duplication of systems is why the amount of tech debt gets worse. You are literally adding the
tech debt in the new version of the system to the tech debt that already existed in the old
version. It’s like pulling the contents of a shelf out into a pile on the floor.</p>
<p>Many things become more difficult when you have these duplicated systems. For example, what if you
need to redact some data? Now you have to remove it from two tables. If there’s a security update to
be done, you probably have to do it twice. To find out why we incorrectly sent someone an email, you
have to look at how both the old and the new system might have sent that email. This additional tech
debt makes it harder to debug problems, harder to make changes, and harder to operate the system.</p>
<h2 id="fight-the-entropy">Fight the entropy</h2>
<p>What can you do about it? Don’t wait too long to start paying off technical debt, and once you’ve
started paying it off, don’t stop in the middle.</p>
<p>As tech debt accumulates it goes from beneath notice to annoying to intolerable and finally
show-stopping. If you wait until it becomes intolerable - or even close to intolerable - before you
start paying it off, I have some bad news for you. The process of paying it off will (temporarily!)
make it even worse. It might push things over the edge from annoying to intolerable or intolerable
to completely show-stopping. If you wait too long, you’ll find yourself between a rock and a hard
place. Either you start a project to pay it off (which makes it worse), or you leave it alone, and
it will keep getting worse by itself.</p>
<p>Tech debt becomes more difficult to pay off the worse it is. Projects for paying off a big piece of
tech debt take longer and incur more temporary debt than projects to pay off smaller amounts of tech
debt. It’s hard to make changes in systems with a lot of tech debt - and that applies to changes
intended to fix that tech debt just like any other changes.</p>
<p>You also really, really don’t want to stop in the middle of a project to pay off tech debt. You
don’t want to start with the “things get worse” part and never get to the “before they get better”
part!</p>
<svg width="700" height="350" viewbox="0 0 700 350" xmlns="http://www.w3.org/2000/svg">
<path fill="none" stroke="#888888" stroke-width="3px" stroke-linecap="round" d="M 20,310 L 680,310" />
<text x="80" y="330" fill="#333333">Time</text>
<path fill="none" stroke="#888888" stroke-width="3px" stroke-linecap="round" d="M 40,20 L 40,330" />
<text fill="#333333" transform="translate(20, 20) rotate(90)">Tech debt</text>
<path fill="none" stroke="#333333" stroke-width="2px" d="M 50,170 L 150,165 C 250,160 250,65 350,65 L 650,50" />
<text x="100" y="200" fill="#333333">Starting a cleanup project</text>
</svg>
<p>The importance of not stopping in the middle has a few implications. First, try to get the tech debt
fixed as quickly as possible so that new priorities don’t have a chance to override it. Second, if
you do end up stopping in the middle, remember that there’s now an extra large amount of value to be
hard from restarting the project! Restarting a stalled migration project can be better than starting
a new project since it will both take less time (part of the work has already been done) and will
have a bigger payoff (The [re]starting point includes the tech debt of the partial new system).</p>
<p>Now if you’ll excuse me, I see a bookcase that needs rearranging.</p>
<section id="footnotes" class="footnotes footnotes-end-of-document" role="doc-endnotes">
<hr />
<ol>
<li id="fn1"><p>There’s an interesting double effect here: increasing the rate at which a system is changed
both increases the amount of tech debt that accumulates <em>and</em> increases how often that tech debt is
encountered. A corollary is if people never need to touch a system, they probably won’t care about
how much tech debt it contains.<a href="#fnref1" class="footnote-back" role="doc-backlink">↩︎</a></p></li>
<li id="fn2"><p><a href="https://www.joelonsoftware.com/2000/04/06/things-you-should-never-do-part-i/">Joel on Software, <em>Things You Should Never
Do</em></a><a href="#fnref2" class="footnote-back" role="doc-backlink">↩︎</a></p></li>
</ol>
</section>
]]></summary>
</entry>
<entry>
    <title>A Few Notes on Problem Solving</title>
    <link href="http://jeremymikkola.com/posts/2022_01_01_a_few_notes_on_problem_solving.html" />
    <id>http://jeremymikkola.com/posts/2022_01_01_a_few_notes_on_problem_solving.html</id>
    <published>2022-01-01T00:00:00Z</published>
    <updated>2022-01-01T00:00:00Z</updated>
    <summary type="html"><![CDATA[<div class="info">
    Posted on January  1, 2022
    
</div>

<p>Someone asked <a href="https://old.reddit.com/r/learnprogramming/comments/ripkz7/can_an_experienced_developer_explain_to_us_mere/">a
question</a>
about how to get better at problem solving. It got me thinking: how does problem solving work? It
seemed like an interesting question to try to answer. I’m going to gear this post toward solving
technical problems, but some of this may apply more broadly.</p>
<h2 id="what-is-the-problem">What is the problem?</h2>
<p>The first step is to clarify what the problem actually is<a href="#fn1" class="footnote-ref" id="fnref1" role="doc-noteref"><sup>1</sup></a>. How will you know for sure if you
have solved the problem? Are you solving the right problem?</p>
<p>You might find some of these activities helpful in building your understanding of the problem.</p>
<ul>
<li>Ask questions. If you don’t know where to start, start with the very basic questions. “Why is it
important to do X?” or “What is a Z?”.</li>
<li>Write down or verbalize your current understanding. Forcing yourself to create concrete words is a
great way to uncover things that you don’t know. You can also share this definition of the problem
to see if others agree.</li>
<li>Look at the details of the requirements and the situation. Small details can sometimes have a big
impact on what the solution looks like.</li>
<li>Learn more about the area the problem is in. If you have a problem with a database operation,
you’ll need to understand the database and the code that calls it.</li>
</ul>
<p>The real problem is sometimes different from how the problem is initially presented. If someone
can’t log in, maybe the problem isn’t with their password. Their internet connection might be
down. Don’t take the description of a problem at face value. Focus on the symptoms, not the
diagnosis you are given. Do you own digging to uncover the core issue.</p>
<p>Clarifying what the problem is doesn’t only happen as the first step. As you learn more details,
find possibilities, and solve sub-problems, you may find that you have new questions to answer about
what the problem actually is.</p>
<h2 id="pattern-matching">Pattern matching</h2>
<p>It can help to see if there is a similar problem that you already know how to solve. Perhaps you can
even adapt that solution to this problem, or at least re-use some techniques. Knowing how to bake
bread isn’t the same as knowing how to bake brownies, but you can see how some practical knowledge
would carry over.</p>
<p>However, it’s possible to fall into a trap of getting too focused on the solution to that other,
similar problem. Reshaping that problem’s solution to fit your problem doesn’t always work. Trying
to force it to work can distract you from spending time working in the right direction. I see this
sometimes when I give technical interviews. Candidates can latch on to algorithms they’ve memorized
and try to force them to work for this problem, even though the algorithms solve a different
problem. This rarely leads to anything that works.</p>
<p>You can sometimes get mileage out of adapting ideas or ways of thinking from other problems that, on
the surface, seem very different but share some deeper patterns. The solution you draw inspiration
from might not even come from the same domain.</p>
<h2 id="breaking-a-problem-down">Breaking a problem down</h2>
<p>Solving little problems is easier than solving big problems. You can get multiple little problems
out of one big problem by breaking that problem down. If you can keep breaking a problem down into
smaller and smaller pieces, you might be able to make the pieces small enough that they are trivial
to solve.</p>
<p>Sometimes you can break pieces off of a problem to make it smaller. Can you forget about file
parsing for the moment and just focus on solving the problem once the data is in memory?</p>
<p>Other problems can be broken down into steps or stages. If you know that step A has to happen before
step B, you can solve the problem of how to make step A happen as a separate sub-problem from how to
make B happen. For example, if you are trying to fix a latency issue, problem A is finding where the
latency is coming from and problem B is finding a way to make that part faster.</p>
<p>There are many different ways to break down problems. Some techniques are more useful than others
for a given problem area. It helps to watch how experts in an area break down problems. Do they
break it down by areas of ownership? By layers in the system? By use-case?</p>
<h2 id="solving-a-smaller-problem">Solving a smaller problem</h2>
<p>Solving a smaller problem is related to breaking a problem down. Instead of splitting the problem
into separate pieces, you create a version of the problem that’s easier to solve.</p>
<p>Some examples:</p>
<ul>
<li>Instead of solving the problem for all users, how would you solve the problem for one user, by
hand?</li>
<li>Instead of solving the problem for arrays of any size, how would you solve the problem for an
array with a single element?</li>
<li>How would the function compute this if we didn’t care how long it took?</li>
<li>Can I figure out how the bug happens for just this one user?</li>
<li>Instead of changing the whole company’s culture, how do I get one person to do things differently
once?</li>
</ul>
<p>Solving the smaller problem won’t give you a solution to the whole problem - in fact, the solution
you come up with may not even be the right direction to be going - but it will get the wheels
turning and give you some new ideas.</p>
<p>You may find that while the original problem isn’t solved, the smaller problem has been solved
before. It may work to adapt those solutions and scale them up to solve the entire problem.</p>
<p>There can be an art to creating a toy version of a problem that is meaningfully easier to solve but
still gives useful insight into how to solve the whole problem. It seems to depend on creating a
good, simplified mental model of the problem<a href="#fn2" class="footnote-ref" id="fnref2" role="doc-noteref"><sup>2</sup></a>. Once you have a good mental model, you can start
building a smaller version of the problem that still conforms to that model.</p>
<p>If your problem is cleaning up the kitchen, one of your mental models might be “dishes need to go in
the dishwasher.” Using that mental model, you can create a smaller problem: putting a single dish
into the dishwasher.</p>
<p>Smaller versions of problems are also useful for mentally testing out solutions. If a solution
doesn’t work for the smaller version, it likely won’t work for the original problem either! It might
be hard to see how a group would react to a policy change, but it’s probably easy to figure out how
Alice and Bob will react. If they would feel insulted, it’s a good bet that others would as well.</p>
<h2 id="iterating">Iterating</h2>
<p>Theorizing all day without actually testing your theories will get you nowhere. You don’t expect to
scale a mountain in a single step, and you shouldn’t expect to solve a hard problem in a single
flash of brilliance. Both must be done one step at a time.</p>
<p>Iteration in problem-solving is a sequence of steps that alternate between movement and
learning/thinking. Each thing you learn informs your thinking about how you move next. Each thing
you try has the potential to teach you things you didn’t know.</p>
<p>When building something new, you can start with a small MVP. You build this based on your current
theory about how it should work. You then try it out to learn how it actually performs (can people
actually use the MVP?). This step gives you information to update your theory about how it should
work. From there, you can take another step forward and build the next piece or fix the next
problem. You then test that, build a little more, test again, etc. The feedback you get at each
iteration will show you things you wouldn’t have thought of had you attempted to design every detail
of the end state in a vacuum.</p>
<p>If you are solving a coding question in an interview, you might iterate by thinking of different
approaches to solve the problem, then thinking about how well that approach would work for the
problem. You might also iterate on different ways to break down the problem. Check your idea about
how to break down the problem to see if it yields sub-problems that are easier to work with. (Does
the problem get easier if I separate the problems of keeping track of which nodes to visit next from
the problem of keeping track of the nodes I’ve already visited?)</p>
<p>When debugging, iteration is a sequence of forming theories and testing them. Your theories might be
as simple as “it gets to this line in the code.” The best theories to test are the ones that have a
50/50 chance of being true. That way, each test carves away the largest possible amount of the
remaining possibility space (or, to put it in information theory terms, it gives the most bits of
information).</p>
<p>Since iteration is valuable, it behooves you to find ways to make iteration
cheaper/easier/faster. All else being equal, you’re more likely to find a solution if you can try 20
things instead of 2.</p>
<p>Don’t fall into the trap of blind iteration. Trying a bunch of things randomly is not a fast way to
solve problems! Each iteration cycle needs both thought and motion.</p>
<h2 id="understanding-more-deeply">Understanding more deeply</h2>
<p>Let’s say you have a car that won’t run properly. Maybe the power steering isn’t working. You know
the basics of how a car works, but you aren’t a mechanic. When you pop the hood, you can point out
the engine and the oil dipstick, but that’s about it. Do you think you’d be able to find what is
wrong with power steering? No, you’d need to know much much more about how that part of the car
works. You need to understand how the car works more deeply.</p>
<p>The same applies to any kind of problem solving. If there’s a system, you need to understand how
that system behaves. If there’s a component, you need to understand why that component is
there. Learn the gritty details. Don’t just learn that two components have a gasket between them,
also learn why gaskets need to be there and what they look like when they’ve failed.</p>
<p>Solving problems is all about making changes to get the outcome you want. This requires
understanding two things: what changes you can make, and what the consequences of those changes will
be. Sometimes it’s hard to know what changes can be made, other times it’s hard to know what the
results of making a change will be. Learn more about the system until you can confidently do both.</p>
<p>How do you understand things more deeply? Reading and experimenting are both good things to
try. Read documentation, code, blog posts, bug reports - read anything that improves your mental
model of the system. Experimenting can teach you all kinds of interesting things that you might
never learn by just reading. The voltage across two wires might be too low, or the function might
return an error when you don’t expect it. Call functions in a REPL or short script to see what they
do.</p>
<p>Writing down an explanation of your current understanding is a great technique. It gives you a
reference to use years later when you’ve forgotten all the details. More importantly, it will help
you get the problem solved by forcing your brain to process the details in a more careful
order. Seeing the words on a page can also help reveal questions you haven’t asked yet.</p>
<p>Collect facts that you’ve discovered to be true, even if you don’t know yet how they are useful.</p>
<h2 id="checking-assumptions">Checking assumptions</h2>
<p>Incorrect assumptions can be the difference between a problem that’s solvable and one that is
impossible. Checking your assumptions is especially important when debugging.</p>
<p>If you think a piece of code didn’t run, it’s important to confirm that it actually didn’t before
you go trying to find the reason it didn’t run. You won’t get anywhere trying to fix a problem that
isn’t there.</p>
<p>It’s really easy to not notice what assumptions you are making. When you feel like you are getting
stuck, take some time to step back from the problem and ask yourself what you’re assuming. Testing
those assumptions is never a waste of time. Either you prove that you were right (and now you know
more facts), or you realize your assumption was wrong and this saves you from continuing further
down the wrong path.</p>
<h2 id="finding-alternate-angles">Finding alternate angles</h2>
<p>This is almost the opposite of trying to understand the situation more deeply. When trying to solve
a difficult problem, it’s possible to get too focused on going deep into one detail. You can miss
the fact that there’s another way to solve the problem that doesn’t even involve the detail you are
staring at.</p>
<p>This is a good step to take when you already have the problem completely loaded into your head and
the direct approach to solving the problem hasn’t succeeded. Take a step back from the problem and
do something else for a while. Take a walk, write an email, whatever. Your mind will keep working on
it in the background. When you come back to it later, it might be much easier to see different ways
of looking at the problem.</p>
<p>“Rubber ducking” - the process of talking through a problem out loud, explaining it to someone even
if no one but a rubber duck is there listening - is a great way to find things you’ve missed,
avenues you haven’t tried yet, or assumptions you can question.</p>
<p>For example, you might ask yourself if there is a way to improve the system that leads to the
problem instead of fixing the problem itself. If water is coming into a boat, you can either work on
bailing it out (fixing the problem) or patch the hole that’s letting water in (fix the system
leading to the problem). When a system is causing a problem, sometimes no amount of effort attacking
the problem itself will do more than temporarily fix the issue. As long as the system is unchanged,
the problem will just come back. I recommend reading the book <em>Thinking in Systems: A Primer</em> to
learn more about this.</p>
<h2 id="experience">Experience</h2>
<p>Solving a new kind of problem for the first time is hard. Solving a second problem of that kind
might be just as hard. But over time, solving a kind of problem will become easier. When someone
solves a problem much more easily than you, it’s probably because they have solved many similar
problems before. Raw intelligence is a poor match for experience.</p>
<p>It’s really useful to watch how people with experience go about solving problems. What questions do
they ask? What do they look at? What do they try first? What do they filter out as irrelevant?</p>
<p>I’ve found it useful to ask myself what someone I know who is an expert would do if they were faced
with this problem.</p>
<h2 id="persistence">Persistence</h2>
<p>Sometimes, the best way to solve a problem is to just keep working on it. Even if you don’t see any
line of attack, continue to roll the problem over in your head. You might be surprised what new
insights you get if you just let the problem stew for a while.</p>
<p>If a problem was easy to solve, it would probably have been solved already. Don’t be discouraged
when a problem feels difficult to solve. That’s what problems are supposed to feel like! Agree with
yourself that you’ll keep investing units of effort (pages read, theories tried, minutes spent
writing) regardless of whether they feel like they are helping.</p>
<h2 id="go-solve-problems">Go solve problems</h2>
<p>Remember that solving problems requires a mix of thinking and taking action. Don’t get stuck doing
just one or the other. You have to both build and test your mental models. Writing things down will
improve your thinking. Persistence is important, but sometimes the right way to persist is to take a
step back and let your brain chew on it for a while.</p>
<p>I hope this is useful the next time you come across a problem to solve.</p>
<section id="footnotes" class="footnotes footnotes-end-of-document" role="doc-endnotes">
<hr />
<ol>
<li id="fn1"><p>Assuming you’re sure the problem actually needs to be solved. Sometimes it doesn’t!<a href="#fnref1" class="footnote-back" role="doc-backlink">↩︎</a></p></li>
<li id="fn2"><p>Look at the mental models here for inspiration: <a href="https://www.defmacro.org/2016/12/22/models.html">Mental
Models</a> and <a href="https://medium.com/@yegg/mental-models-i-find-repeatedly-useful-936f1cc405d">Mental Models I Find Repeatedly
Useful</a>.<a href="#fnref2" class="footnote-back" role="doc-backlink">↩︎</a></p></li>
</ol>
</section>
]]></summary>
</entry>
<entry>
    <title>Please don't argue with the weather</title>
    <link href="http://jeremymikkola.com/posts/2021_10_11_weather_and_estimates.html" />
    <id>http://jeremymikkola.com/posts/2021_10_11_weather_and_estimates.html</id>
    <published>2021-10-11T00:00:00Z</published>
    <updated>2021-10-11T00:00:00Z</updated>
    <summary type="html"><![CDATA[<div class="info">
    Posted on October 11, 2021
    
</div>

<p>Say you have a vacation planned next week. You are going south to a fun coastal town renown for its
beaches. You’re hoping to get outside, get some sun, and stick your toes in the water while drinking
something with too much sugar and alcohol<a href="#fn1" class="footnote-ref" id="fnref1" role="doc-noteref"><sup>1</sup></a>.</p>
<p>Hoping for good weather, you turn on the news. The meteorologist says it will be 50 F and
cloudy. This isn’t what you were hoping for! Sitting on the beach won’t feel right in drab weather
like that.</p>
<p>Think about how you respond to this situation: do you try to argue with the meteorologist? Do you
say, “how about we actually do 72 F and sunny?” No, that would be silly. A forecast isn’t a decision
the meteorologist is making. It’s merely a prediction about what will happen. Predicting a different
temperature will do nothing to how the air will feel.</p>
<p>No, if you want different weather next week, your only option is to go to a different place. You
obviously can’t argue with the weather.</p>
<p>Yet in software companies, we often try to do exactly that. When an engineer says that a project
will take 7 weeks, someone is sure to argue and ask for it to be done in 5. But changing the
estimate written down on paper doesn’t change how hard the project is to accomplish. It doesn’t
remove time waiting for other teams or mitigate project risks. It doesn’t make the code cleaner, the
tests run faster, or magically document undocumented libraries. It is mere arguing with the weather.</p>
<p>If you want a different estimate for the end of a project, the only option is to change where that
project is going. Choosing to build something smaller or different can (sometimes!) give you more
favorable estimates. It helps to know the whole weather map, not just the weather in that one
town.</p>
<p>But sometimes it is just plain cold everywhere. There’s no law of physics that says there must exist
a spot on the map with a temperature you like. If the system is too complex and too full of tech
debt, there might not be any meaningful options that can be accomplished in 5 weeks. Pushing back on
the person who tells you this won’t change the facts.</p>
<p>If you are especially wise, you may notice a pattern where the temperature is frequently not what
you want. You realize that you need to make a systematic change in how you are planning your
vacations. Perhaps your yearly beach vacation needs to move from January to August. In software,
this might look like giving a team time to pay down tech debt so that they can move faster later,
staffing a team they get blocked by, or involving the team sooner in the new product.</p>
<p>Please don’t argue with the weather. If you don’t like the temperature, change where you are going.</p>
<section id="footnotes" class="footnotes footnotes-end-of-document" role="doc-endnotes">
<hr />
<ol>
<li id="fn1"><p>I can’t recall speaking with anyone whose vacation plans actually look like this<a href="#fnref1" class="footnote-back" role="doc-backlink">↩︎</a></p></li>
</ol>
</section>
]]></summary>
</entry>
<entry>
    <title>Scary Logic</title>
    <link href="http://jeremymikkola.com/posts/scary_logic.html" />
    <id>http://jeremymikkola.com/posts/scary_logic.html</id>
    <published>2021-05-12T00:00:00Z</published>
    <updated>2021-05-12T00:00:00Z</updated>
    <summary type="html"><![CDATA[<div class="info">
    Posted on May 12, 2021
    
</div>

<p>Nightmares run on Scary Logic. In a nightmare, that moving shadow turns out to be a fast predatory
monster. That noise behind you? It’s something that is about to attack. You turn to run but your
legs won’t work. The fire is spreading but you can’t reach the water. In any situation, the scariest
possibility you consider is what ends up happening next. This is Scary Logic.</p>
<p>According to Scary Logic, anything that is scary is true.</p>
<p>When scared, you imagine scary things. Scary Logic tells you to treat those scary things as real and
true. These new terrible facts redouble your level of fear. You imagine scarier things still, and
they too become real. This cycle builds until the picture is plainly nonsense to an outside
observer.</p>
<p>That nonsense situation is so aligned to (and built out of) the mental state of the person imagining
it that it seems completely plausible to them. They don’t notice a disconnect from reality. In a
nightmare, you don’t stop and think that monsters don’t exist. You are not operating on normal
logic - you’re operating on Scary Logic.</p>
<p>Some (but likely not all<a href="#fn1" class="footnote-ref" id="fnref1" role="doc-noteref"><sup>1</sup></a>) conspiracy theories rely on Scary Logic.</p>
<p>Vaccines and diseases are scary; powerful people who do things you can’t control are
scary. According to normal logic, powerful people and diseases are unrelated. According to Scary
Logic, the powerful people made up the disease to control you (scary). Scary Logic, taking that as a
fact to now build upon, goes a step further and tells you that the vaccine is actually to implant
microchips to track you (even scarier).</p>
<p>Normal logic says that if the powerful wanted to track you, the cell phone you already carry would
do a much better job. But Scary Logic doesn’t see objections or weight evidence. Scary logic just
confirms the scary as true.</p>
<p>Someone making a claim that confirms a Scary Logic result will seem trustworthy. Those who disagree
are ill-informed, lying, or brainwashed, says Scary Logic. A single voice proclaiming that things
are Even Scarier sounds more knowledgeable and authoritative than ten thousand experienced people
with more measured takes on the matter.</p>
<p>Scary Logic may not provide a line of reasoning for how one knows the scary things are true, but
that’s no problem. When confronted with the need to explain the Scary Truth to someone else, people
automatically find reasoning that, if not convincing, is at least hard to quickly poke holes
in. Coming up with reasons for what we believe is such an automatic process that it’s hard to tell
which is the chicken and which is the egg.</p>
<p>There’s no shortage of scary things in the world: natural disasters, accidental disasters, diseases,
wars, totalitarian governments, terrorists, and more. While normal logic acknowledges that these are
scary and bad and leaves it at that, Scary Logic instead uses them as merely a starting point for
telling a grand story about how things are even worse. Some minor but poignant evil becomes the
cornerstone of a certainty that the world is becoming morally bankrupt.</p>
<p>One way to combat Scary Logic is to learn the underlying mechanics of how things work. When you
don’t know the properties of something, any scary idea will sound plausible. Knowing what is and is
not actually possible allows you to quickly reject the ideas from Scary Logic when they start to
disconnect from reality.</p>
<p>Learning more about what an mRNA vaccine does can help you evaluate statements like “it edits your
DNA<a href="#fn2" class="footnote-ref" id="fnref2" role="doc-noteref"><sup>2</sup></a>.” Airplanes are less scary when you know how they are tested and read the statistics about
their safety. The crazy nuts on the Other side of the political spectrum are less scary when you
realize that the news seeks out the most inflammatory figures they can find (not a representative
average), and when you think back to how many people like that you actually meet in real life. Hard
statistics are the enemy of knee-jerk fearful reactions.</p>
<p>As a parting word of caution: beware of media that uses Scary Logic to get viewership. It is
unfortunately a very effective technique.</p>
<section id="footnotes" class="footnotes footnotes-end-of-document" role="doc-endnotes">
<hr />
<ol>
<li id="fn1"><p>Though others seem to have similar self-confirming properties.<a href="#fnref1" class="footnote-back" role="doc-backlink">↩︎</a></p></li>
<li id="fn2"><p><a href="https://www.gavi.org/vaccineswork/will-mrna-vaccine-alter-my-dna">Will mRNA alter my DNA</a>,
and <a href="https://xkcd.com/2425/">relevant XKCD</a>.<a href="#fnref2" class="footnote-back" role="doc-backlink">↩︎</a></p></li>
</ol>
</section>
]]></summary>
</entry>
<entry>
    <title>The problem with slow tools</title>
    <link href="http://jeremymikkola.com/posts/2021_04_01_the_problem_with_slow_tools.html" />
    <id>http://jeremymikkola.com/posts/2021_04_01_the_problem_with_slow_tools.html</id>
    <published>2021-04-01T00:00:00Z</published>
    <updated>2021-04-01T00:00:00Z</updated>
    <summary type="html"><![CDATA[<div class="info">
    Posted on April  1, 2021
    
</div>

<p>Slow developer tools have costs beyond just the time they take. We should invest
in ensuring they are fast.</p>
<p>I’ve been bothered by some slow tools lately. Waiting for them to run has given
me time to think. How much time are they actually wasting? Let’s say I run a
build 15 times a day and have to wait an average of 1 minute for each build.</p>
<p>That doesn’t seem too bad, right? Well, if you do the math, 15 minutes a day
times 230 working days a year equals just shy of 60 hours - or 1.5 work weeks -
wasted per year. Multiply that by the size of the engineering organization, and
this starts to look pretty expensive<a href="#fn1" class="footnote-ref" id="fnref1" role="doc-noteref"><sup>1</sup></a>.</p>
<p>But here’s the thing: That might be significantly <em>under</em>-estimating how bad
this is.</p>
<p>You see, humans aren’t machines. You know it, I know it, but it can be easy to
miss what it means when you look at math like 15 * 230. Here’s one relevant
difference: machines can stop, wait for some process, and resume right away
afterwards as though nothing had happened. Starting and stopping doesn’t matter
to them<a href="#fn2" class="footnote-ref" id="fnref2" role="doc-noteref"><sup>2</sup></a>. I am not a machine<a href="#fn3" class="footnote-ref" id="fnref3" role="doc-noteref"><sup>3</sup></a> and do not freeze and stare into space while
waiting for tools. I check Slack, I check Hacker News, I get distracted.</p>
<p>This is a problem in a few ways:</p>
<ul>
<li>A quick delay can accidentally into 5 minutes of reading Slack. The latest
posts about Stonks are just too amusing.</li>
<li>I lose context on what I was doing. If I had achieved the elusive productive
state of focus (a ‘flow state’), that’s gone clear out the window chasing
after a squirrel. The productivity boost I get from having everything in my
head is gone.</li>
<li>I have to redirect my focus back to what I had been working on. Shifting focus
is a natural thing to do several times a day, but there’s still a cost each
time. It takes a little bit of my depletable reserves of energy and
willpower. After bringing my focus back to the work a few dozen times, it
starts to feel harder to do.</li>
</ul>
<p>A 1 minute pause doesn’t just cost me that minute of productivity, it has costs
that linger long after the pause is over. It’s like hitting a stoplight when
driving. After the light turns green, your car doesn’t immediately get back up
to the speed limit. It takes a while to accelerate. During that acceleration,
you’re moving slower and using more gas. Removing a 1 minute pause might mean I
get the task done 5 or even 15 minutes sooner.</p>
<p>And getting blocked by my tools is super frustrating. I’m trying to get
something done, but these random roadblocks keep halting me in my tracks. It’s
like being in stop and go traffic all day long. Getting out and walking might be
slower, but at least it would be less frustrating because I wouldn’t keep being
forced to stop. If an office can afford creature comforts like La Croix and
extremely tiny bags of snacks, maybe saving our sanity can also get room in the
budget?</p>
<p>There might be another, subtler cost of slow tooling. Remember the whole
‘developers are human’ bit? As it turns out, humans don’t follow fixed,
pre-programmed behavior. When faced with frustrations, we change our behavior in
response.</p>
<p>How might our behavior change in response to tests being slow to run? We’ll try
to avoid running them. We’ll probably write fewer of them, because we know that
writing them leads to running them, and running them leads to waiting, and
waiting leads to the dark side.</p>
<p>What about environments that are slow to build and slow to switch branches?
We’ll try to only do those things when we really have to. That small cleanup I
could write in 30 seconds? Nah, I don’t feel like wrestling with tooling for 5
minutes. That idea for a better approach I thought of? Eh, what I have is good
enough - leaving the code a bit messy is unpleasant, but dealing with that
tooling would be worse.</p>
<p>So to summarize, slow tooling causes productivity losses greater than you’d
expect based on the raw amount of time it takes because of the distraction it
causes. It changes developer behavior in ways you might not like. And it
frustrates my socks off.</p>
<p>If you are in charge of some developer tooling, please take the time to
understand how often during the day it gets used, and how long it takes to run
in real world cases. It’s all too easy to fall into the trap of thinking the
tool you are maintaining is fast because it’s fast on your machine. Your machine
probably has a clean state, without all the other tooling running at the same
time constantly fighting with each other to see which can make the bigger mess
of the build directory. Measure how long it takes for real users.</p>
<section id="footnotes" class="footnotes footnotes-end-of-document" role="doc-endnotes">
<hr />
<ol>
<li id="fn1"><p>Fun fact: a 1 hour weekly meeting costs each participant about 1 work week
per year.<a href="#fnref1" class="footnote-back" role="doc-backlink">↩︎</a></p></li>
<li id="fn2"><p>Come to think of it, some kinds of machines don’t like this,
either. Aircraft bodies, for example, take wear roughly proportional to how
many times they take off and reach altitude.<a href="#fnref2" class="footnote-back" role="doc-backlink">↩︎</a></p></li>
<li id="fn3"><p>Citation needed.<a href="#fnref3" class="footnote-back" role="doc-backlink">↩︎</a></p></li>
</ol>
</section>
]]></summary>
</entry>
<entry>
    <title>Writing Useful Comments in Code</title>
    <link href="http://jeremymikkola.com/posts/2021_03_21_useful_comments.html" />
    <id>http://jeremymikkola.com/posts/2021_03_21_useful_comments.html</id>
    <published>2021-03-21T00:00:00Z</published>
    <updated>2021-03-21T00:00:00Z</updated>
    <summary type="html"><![CDATA[<div class="info">
    Posted on March 21, 2021
    
</div>

<p>What makes for useful comments in code?</p>
<p>Good comments should be written with their audience in mind, and should convey
new and useful information. They should make the life easier for the person
reading them.</p>
<h2 id="audience">Audience</h2>
<p>Sometimes commenting code can be about as pleasant and useful as waiting in line
at the DMV. I’m thinking of the Javadoc-style comments, where you have to
document every argument, return value, and exception.</p>
<div class="sourceCode" id="cb1"><pre class="sourceCode java"><code class="sourceCode java"><span id="cb1-1"><a href="#cb1-1" aria-hidden="true" tabindex="-1"></a><span class="co">/**</span></span>
<span id="cb1-2"><a href="#cb1-2" aria-hidden="true" tabindex="-1"></a> <span class="co">*</span> Adds two values together</span>
<span id="cb1-3"><a href="#cb1-3" aria-hidden="true" tabindex="-1"></a><span class="co"> * </span><span class="an">@param</span><span class="co"> </span><span class="an">a</span><span class="co"> </span>the first value</span>
<span id="cb1-4"><a href="#cb1-4" aria-hidden="true" tabindex="-1"></a> <span class="co">*</span> <span class="an">@param</span><span class="co"> </span><span class="an">b</span><span class="co"> </span>the second value</span>
<span id="cb1-5"><a href="#cb1-5" aria-hidden="true" tabindex="-1"></a> <span class="co">*</span> <span class="an">@return</span> a and b added together</span>
<span id="cb1-6"><a href="#cb1-6" aria-hidden="true" tabindex="-1"></a> <span class="co">*/</span></span>
<span id="cb1-7"><a href="#cb1-7" aria-hidden="true" tabindex="-1"></a><span class="kw">public</span> <span class="dt">int</span> <span class="fu">add</span><span class="op">(</span><span class="dt">int</span> a<span class="op">,</span> <span class="dt">int</span> b<span class="op">);</span></span></code></pre></div>
<p>These comments often feel pointless because they are the wrong tool for the
job. They go into detail that’s probably unnecessary and tell you things you
probably already know. While that style can be useful for libraries used by
thousands of developers, we usually are writing code with a smaller
audience. Our comments will be read by just a few other teams, or perhaps just
our team, or even just ourselves. We don’t need to drive a semi truck to get the
groceries.</p>
<p>When you think about who the audience for the comments is, you might realize
that they’ll already know everything you were going to say in that comment. In
that case, it’s fine to skip writing that comment and direct your effort
somewhere else where it will be more useful.</p>
<p>You audience will appreciate it when you write comments that tell them new
things that they find useful. You can warning them about a common pitfall, give
context behind a decision, or leave hints to help them understand the code.</p>
<p>A comment should use the easiest to follow, least technical language it can
without being inaccurate. It’s not an academic paper; it’s an explanation to
your fellow developer.</p>
<p>Write comments for the sake of the person reading them, not for the sake of
writing comments.</p>
<h2 id="new-and-useful">New and useful</h2>
<p>Comments should tell the reader something that they don’t already know, but
would find useful to know. Fortunately, there’s a lot of different things you
can say that a reader will find useful.</p>
<p>It’s useful for comments to explain why. You can see for yourself what the code
<em>does</em>, but without comments you can only guess at why that’s the right thing to
do.</p>
<p>My personal favorite comments in code are ones that explain why something is
different from what you’d expect. Those comments answer a question you’ll have
about the code right as you have it, and show that the author was thinking ahead
(and didn’t just make a mistake). Any time something is surprising or prone to
misinterpretation, that’s a great opportunity for a useful comment.</p>
<p>Sometimes there’s a process people working on the code will need to
follow. Maybe you need to deploy one part of the change before merging another
part, or perhaps you should give customer support a heads-up before your change
goes out. If the process has to do with one particular area of the code, it’s
useful to write that process (or link to it) in a comment above that part of the
code.</p>
<p>Comments can act as a shortcut. They’ll convey something the readers technically
could have figured out themselves, but much less time and effort. A good comment
might replace a half hour of digging with 5 seconds of reading. Potential
shortcuts include:</p>
<ul>
<li>A link to where to find a design document.</li>
<li>A simple summary of what a complicated-looking function actually does
(e.g. “this skips any entries we can remove without impacting the result”).</li>
<li>A pointer to somewhere else in the code the reader should look at.</li>
<li>A description of how the classes fix together (e.g. “plans contain a single
step because steps can contain other steps in the composite pattern”).</li>
<li>A name for what is going on (e.g. “this is basically the visitor pattern”).</li>
</ul>
<p>I also like TODOs in code. Sometimes they can feel like a sign of a messy
codebase, but (when used in moderation) they give really useful context. TODOs
can tell the reader if this structure was intentional, or just a quick
hack. They can give context on what is temporary, or what you might want to do
differently when writing similar code.</p>
<p>Some TODO comments can be just noise. If they just say <code>TODO: Fix this</code>, that
doesn’t helper the reader know what needs to be fixed or how it should be
fixed. A comment like <code>TODO: Use the new database connection type</code> is far
better - it tells you what to do and gives some hints about the rest of the
codebase.</p>
<hr />
<p>Next time you are reading some unfamiliar code, pay attention to the
comments. Which do you find useful? What do you wish a comment would tell you?</p>
]]></summary>
</entry>

</feed>
