Recent Posts (page 5 / 71)

by Leon Rosenshein

Code Virtues

I’ve talked about code smells before. Just the other day I talked about comments as a smell. What about the opposite? Are there code virtues?

Of course there are. Things that we want our code to have. In fact, the Pragmatic Programmers published a list back in 2011

Because English is a slippery language and, as Humpty Dumpty said, words can mean what we choose them to mean, each virtue is given as not only what it is, but what it isn’t. It gives you a continuum so you can tell what to avoid as well as what to move toward.

There are lots of details in the article, but touching briefly on each of the virtues:

Working: as opposed to incomplete

Straight from the Agile Manifesto, code needs to work. If it ain’t done, it isn’t adding value, and doesn’t have much virtue.

Unique: as opposed to duplicated

Is it DRY (Don’t Repeat Yourself)? Only have one way to do something. If you need to change something, have it in one place. This goes beyond constants and functions. Keep your domains clean and respect their boundaries.

Simple: as opposed to complicated

Be straightforward. Following on from Unique, have decisions made in one place. Have functions do one thing. Process collections instead of multiple individual items.

Clear: as opposed to puzzling

This one is a bit harder. I would have said Clear, as opposed to clever. Avoid neat tricks. Don’t surprise the reader with side effects. As John Scalzi once said, the failure mode of clever is a**hole.

Easy: as opposed to difficult

Don’t make things hard on yourself. For example, when you go from doing one thing to doing two, it makes sense to just add an if/else. When you add the third, that might still make sense. After that, some kind of table or data driven approach makes more sense. Another thing that makes things easy is automation. Once you figure out how to do something, automate it. It’s easier, and it’s harder to get it wrong.

Developed: as opposed to primitive

One of the big things we do when programming is deal with complexity. There’s some level of complexity you can’t get out of the system. What you can do is make sure the complexity is in the right place and covered with the appropriate abstractions. If your domains are set up correctly, you can use the domain and never need to deal with their internal complexity.

Brief: as opposed to chatty

Be succinct. For whatever succinct means in the language you’re using. Be idiomatic. Those conventions are shared for a reason. It makes your code easier to read and understand.

Now go forth and write virtuous code.

by Leon Rosenshein

Motivation

Motivation is a funny thing. If there’s a lot of motivation, it can make us do certain things. It can make us not do certain things. It can make us try to do everything. It can make us do nothing. It can make us focus solely on one thing, It can blind us.

On the other hand, if there’s no motivation, nothing happens.

Motivation is funny in another way. There are two kinds of motivation. There is internal and external motivation. Doing something for what you give yourself (internal motivation) and doing something for what someone else gives you (external motivation).

Doing something for what you give yourself just feels good. It’s interesting. You learn something. You enjoy it. You have Autonomy. It can lead to Mastery. And those are it’s Purpose. That’s serious motivation. That’s Drive

You can also get that from doing something for what others give you. You could have Autonomy if the other person sets the target, but you pick the path. You can have Mastery by learning something along the path you pick. And you can take on (or agree with) the other person’s purpose. You just have to pick the right person and the right target/purpose.

What extrinsic motivation can give you, that intrinsic motivation doesn’t necessarily have, is guardrails. Intrinsic motivation can (not always, but sometimes) push you towards local maximums. Without sufficient self-awareness, intrinsic motivation can lead to nerd sniping. You choose the most interesting path. You learn the most. You fulfill the specific purpose. But you missed the bigger goal you started working on.

Sure, you can (and should) keep track of the bigger goals and be aware of when you’re nerd-sniping yourself, but that’s hard. External motivation provides that awareness for you. Or, at least gives you an external point to check against. And because it’s external, there’s something you don’t get if you don’t meet the goal. It could be anything from a badge on a website to an atta-boy to a paycheck. Whatever it is, if you don’t meet the goal, you don’t get it.

That’s the key difference. That’s the guardrail. You don’t get something. With intrinsic motivation you can easily end up changing the goal/purpose so that you are getting your immediate needs met. You climb that hill right to the local maximum, and you’re stuck. No matter which way you go from there, it feels worse, because every direction is downhill.

With extrinsic motivation you can’t do that. That local maximum becomes a bias in a direction, and you might go part of the way there. Eventually, though, the bias is overcome by the pull (or push, depending on how you view things) towards that external goal gets you back on track. It helps keep you from getting stuck on along the way.

There’s also another thing to keep in mind. While your capacity to do things is limited, your motivation doesn’t have the same limits. And intrinsic and intrinsic and extrinsic motivation come from two different suppliers. They’re not a Zero-Sum game. You don’t have to give up on one to get the other. More is better than less, and you want to balance them.

When they’re balanced, you not only meet the external goals and get those rewards, you also meet your internal goals, and get those rewards.

And that can help reduce or even avoid burnout. Which is good for all of us.

by Leon Rosenshein

Comments Are A Smell

I’ve talked about comments in the past. Comments are a code smell, right? Maybe, or maybe not. It depends. Like so many things in the software world, a recommendation phrased as a comparison has been turned into a binary flag.

Why that happens is understandable. Binary is easy. True/False. 1/0. So easy even a computer can understand it. It reduces cognitive load. On the other hand, comparisons and value statements are hard. There’s more cognitive load. So instead of Working software over comprehensive documentation, people say don’t bother to write documentation. And from comments often are used as a deodorant we get Code never lies, comments sometimes do and conventional wisdom saying comments are bad. It is known.

The thing is, Just like in Game Of Thrones, just because it is known, it isn’t necessarily true. Taking these value statements as binaries leads to the bigger problem of the things we know that just ain’t so.

Are comments a code smell? Yes. That just means they might be a bad thing. Are they a bad thing? They might be. It depends. Comments that just duplicate the code are a smell because the code and the comments don’t always change together. Which means you end up with stale comments. And those are a bad thing.

Comments that describe the code because the code is hard to understand are also a code smell. This is where the phrase comments are sweet smelling comes from. The comment is there because it smells good. It’s definitely a code smell. It makes the code easier to understand. It helps the next person who must look at the code.

But just like regular deodorant, it’s masking another smell. One that isn’t so good. The fact that the code in question can’t be easily understood is also a smell. It’s just been masked. So even though the comment is a good smell, it’s still an indication of a problem that ought to be addressed.

Other times, comments are there to explain why. They’re there to let future developers know some bit of arcane business knowledge or external factor that means the code needs to be this way. Or they might be there to explain what conditions might arise that would cause the code to be sub-optimal, or maybe even be invalidated. Or what needs to happen so that the code could be removed entirely. All of those are smells. Things that are right for now, but might need to be looked at again later.

So yes, comments are a code smell. It’s a sign that you need to look at the code and the comment and think about why they’re there and what should be done about it and when.

But, just like all the other code smells, it’s not an absolute. It doesn’t mean Thou shalt not write comments. It means that you need to think before you type.

by Leon Rosenshein

Diffusion Of Responsibility

I’ve talked about ownership before. I’ve even mentioned diffusion of responsibility a couple of times. But I haven’t really talked about it. It’s time to talk about it.

I’m a grandfather now. Have been for almost a year. It’s amazing watching my granddaughter grow and learn. It’s also mazing watching my daughter learn about being a parent, just like I did many years ago. Unsurprisingly, we have many of the same opinions on how to raise a child, but it’s about 30 years later, so we’ve all learned a few things and advice has changed. If nothing else, car seats have changed a lot. The car seats we used back in the day weighed a lot less than the baby. The infant one my granddaughter used weighed more than she did.

Some things haven’t changed though. You need to watch toddlers all the time. They’re curious, and they’re fast. One thing my daughter is doing that is a really good idea is that when they’re out and about with she’s out and about with her daughter and friends, especially near water, the baby has a dedicated watcher. Everyone is thinking about her, and will act if they see a problem, but someone has the responsibility. Because diffusion of responsibility is a thing. If everyone is watching, then no one is watching.

Obviously, it’s not a binary thing. But if you know that everyone is watching, you allow yourself to get distracted. You don’t force yourself to pay attention when there’s something else pulling at you. Because you know others are watching, and they’ll notice if something happens and do something. Right? Probably, but not certainly. They might be thinking the same thing. Counting on someone else to be paying close attention.

The same thing applies to software design and development. Particularly to the non-functional requirements(NFRs). Everyone is responsible for them, in all parts of the software development lifecycle. When you’re developing a new feature or fixing a defect/bug/regression, you know that’s what you’re trying to do, so you focus on that. That does not mean, however, that you stop being responsible for the NFRs. You’re always responsible for them. And so is everyone else.

The thing is, software development is a socio-technical activity, and as we’ve seen, with us humans, the default is that if everyone is responsible, no one is. That means we need to be on guard for that bias, and work to ensure that it doesn’t let us get into bad habits.

There are some simple things you can do that can help. When you’re designing that new feature of fixing that defect, think about the future. Think about not just the specific problem, but the general class of problems. Can you make some potential issues impossible with your design? Like everything else, the answer to the question of “How much abstraction should I add here?” is “It Depends”. If you’ve been asked for a code review, either do it well and truly, or decline. You do your part by either doing the work or making it clear that someone else needs to do it. If you’re sending code out for review, review it yourself. Take the time to look at it with fresh eyes as someone else would look at it.

There are organization things you can do. Maintaining code quality is everyone’s responsibility, but you can have a team whose job is to make sure that people are doing that. That’s what my team at work does. We don’t add the quality. We don’t schedule the tasks to add quality. But we do look at things from a quality perspective. We let folks know where they’ve missed the mark, and we provide tools and training to help them do better.

Ownership and responsibility are important. Everyone can’t be responsible for everything, or nothing would get done. But that doesn’t mean that no one is responsible for some things. Some things are everyone’s responsibility.

It’s just that sometimes we need to be reminded that there are some things everyone is responsible for. And everyone needs to do. Those things are often called Norms. And that’s a discussion for another day.

by Leon Rosenshein

Code Coverage Is Not Enough

Tales from the real word today. Code coverage is important. It can give you confidence. It can help you move faster. It can alert you to problems before they get exposed to your users. But it’s no guarantee of correctness.

Bad Tests

The first problem is that 100% code coverage does not in any way guarantee that the tests you’re using to measure your coverage are valid tests. Writing good unit tests is hard. A unit test that calls a function then asserts true and returns gives the same coverage as a well written unit test. There’s enough to say about the writing of good tests to fill up at least 3 more posts, so I’ll leave that for another day.

Transitive Coverage

Unit tests almost always have some transitive dependencies. Unless you’re careful, you’ll end up counting the coverage of those transitive dependencies as part of your code coverage. Because you aren’t trying to test those dependencies, only how you act with them, you get very little signal on the correctness of them. But since you’re just looking at “was this line executed?”, you get coverage. You don’t know if that coverage comes from direct testing, where the coverage means something, or from transitive coverage where it means nothing. That erodes a bit of confidence.

Branch Coverage

Another problem you’re going to have is complex branching statements and short circuiting logic. It’s part of writing good tests, but if you’re and’ing 4 booleans together there are 16 ways you need to test it. Add in some or’s and groupings, and it’s very easy to test both sides of a branch, but not really validate that you’ve covered all the possible cases. Another bit of confidence gone.

Integration Coverage and Emergent Behavior

But even if your unit tests are good, and you have 100% coverage of the source lines, you don’t necessarily have coverage of all the possible combinations of those things. As your system gets bigger, and the components interact in different ways, you get more and more emergent behavior. And your unit tests don’t validate ANY of those interactions. So you lose another bit of confidence,

But, But, But

If code coverage doesn’t guarantee correctness, why should you care? Why do ISO-26262 and MISRA talk about it? Why should you bother to measure or do anything about code coverage?

Because while code coverage doesn’t guarantee correctness, changes in code coverage very reliably tells you when something has changed. It helps guide you to where there is more work to be done. When you follow that guidance, you can improve your confidence in the correctness of your code. And that’s what makes code coverage valuable.

And one last thought on testing-based coverage. Don’t forget, testing can’t prove things are right. It just proves things aren’t wrong as your tests define wrong.

by Leon Rosenshein

The Finish Line Is Just Another Milestone

Speaking of remodeling updating your code, If you’re releasing a new version of your code, that means you’ve already released a version of your code. That means that at some time in the past, you finished your code. Or at least thought you did. But now, here you are releasing a new version. So I guess you weren’t done. Or to put it another way,

The finish line is just another milestone.

Yup. Unlike a race, the finish line with software almost never means you’re done.1 Especially with version 1, but really, for all versions. EVERY new project I’ve working on that made it to the first release, has had things that I knew could have been done better. Features I thought should have been added. Errors that needed better handling. From personal projects to school projects to AAA games to multi-year multi-million dollar defense projects released the initial version with known work I (and the team) wanted to do. Those things didn’t make the initial release, for very good reasons. Reasons like boredom and more pressing issues, homework due dates, hitting a release date for game sales, to contractual obligations. Time is fleeting, but there’s always an important date in there somewhere.

First, you work hard. Then you release. You take a break and regroup. You listen to what your users are saying. You combine that with what you know you didn’t do but thought you should. Then you do it all over again.

This time, the list of things you didn’t do is smaller, but it’s more targeted, because you know more. More about the problem space. More about the constraints you added the first time. More about what your users really want to do. You’re starting from a different place, but the process is pretty similar. You develop the code. You learn more along the way. You make mistakes and fix them. Then, you release again.

But again, you’re not done. There are still things you wanted to do. User requests you didn’t get to. Rough edges that you want to fix. And the cycle begins again.

And it doesn’t matter how long your cycle is. It could be a 2-week sprint. It could be 5-year plan. It doesn’t matter. Release is not the same as done.

Forest gump running with the caption ‘Might as well keep building’

That’s the real lesson here. Release is not a finish line. It’s just another milestone along the path. And like all those other milestones, there’s another one coming up. So be ready for it.


  1. While there is a tremendous amount of personal learning that can be done releasing version one, there’s also a whole different type of personal learning and growth that comes from releasing version 2. And the versions after that. If all you do is release version 1 of a project, then move on to a new green field and build another version 1, you’re missing out on a whole different class of things to learn. ↩︎

by Leon Rosenshein

Home Ownership and Software Updates

Seen on Bluesky

Homeownership is a lifelong commitment to discovering what it takes to modernize a legacy system and the lengths to which you will go and compromises you will make in doing so.

It’s often been said that software development is like home construction. You have a site, some requirements, a plan, you start to build, then you adjust as needed. There’s some truth to that, and from far enough away, it’s accurate.

However, when you look closer the analogy falls apart. First, we’ve been building homes a lot longer than we’ve been writing programs. The number of things that are known to home builders as tacit knowledge vastly outnumbers the tacit knowledge of software developers. Most developers have built a handful of programs, or the same program many times. Especially developers that work on their own or in a small company. On the other hand, your typical home building company has a much larger legacy behind them. They made those mistakes before. And the industry made some of them so long ago that the fix is documents. As building codes.

Which is the second big difference. Home building is a regulated industry. Even if you’re building everything yourself, there aren’t many places left where you don’t have to follow the same rules and regulations as every other builder, and, your work will be inspected by a third party when it’s done. There aren’t many software projects where the local government software inspector comes by and makes sure you don’t have any dangling pointers, missing null checks, or all the known security patches. That almost certainly happened to your house though. At multiple times during the build, the inspector came out and checked the plumbing, the electrical, the framing, the layout of the stairs, and even how many electric outlets you have.

Those differences aside, one way that homes and software are similar is updates. At some point, potentially long after the original builder and owner are no longer involved, the current owner is going to reach out to a new builder and say “Can you change X because I want to do Y”. And the builder is going to say yes. Then figure out how to do it. Or at least try to, And find that there’s a pipe right where the new owner wants a doorway. Or the walls just aren’t strong enough to support a second floor. Or someone secretly added another set of outlets to an existing circuit, and plugging in one more phone charge blows the main breaker for the entire house.

So the builder figures out how to make things work. They convince the new owner that want the door four feet to the left, or they add a parallel electric system to the house, or the build a second story on stilts so it’s really a new house carefully place just above the current house, so the stairways line up, but in fact they’re completely separate.1 Then it happens a few months later, and not only does the builder have to deal with the original build, but they also must deal with the compromises and issues that came from the first change. When you do a home remodel, you not only have to design in the things you want, you have to design in the choices of the original builder (and any change between now and then).

With software it’s kind of the same thing. Especially legacy software. Decisions were made. For probably good reasons at the time. To deal with a known requirement. As with Chesterton’s Fence, unless you know exactly why something was done and that it’s no longer needed, you shouldn’t change it. So when you have to add a new feature to a piece of software, the first thing you need to do is make sure you haven’t broken any of your current users (Hyrum’s Law).

That means each time you need to change something, whether it’s a building or software, unless you work very hard, and do things that are not directly related to the thing you’re trying to enable, you make it harder to make the next change.

So THINK before you make that change. Think about what you’re trying to do. Think about how you can do it without impacting other users. And especially, think about future developers and what they’ll need to do when they have to make yet another change.

Uber's famous hanging staircase

  1. Back in Uber’s original headquarters there was a desire to have a stairway between the 4th and 5th floors in the middle of the common area, but the building owners didn’t allow it. In true Uber fashion, the interior design team was not deterred. Instead of giving up, they hung a spiral staircase from the deck between the 4th and 5th floors and had it go down to almost (but not quite) touch the deck of the fourth floor. Technically it met the rules. The 4th and 5th floors were not connected. It was just “Hanging Art”. Yet somehow, people could easily walk between the common areas of the two floors. But I pity the next owner of that space who just wants their floors to be floors. ↩︎

by Leon Rosenshein

Verification First Development

From the “Listen to Hillel” file, an article on Verification First Development. I really like the idea of Test Driven Development, but I have to be honest. I don’t always do it. Sometimes I practice Test After Development. Meaning I write my tests to verify that the code I wrote does what I wanted it to do. But after reading Hillel’s article, I realized that even though I sometimes write my tests after, I almost always know how I’m going to verify it. I have a positive test case in place, and I often have a large handful of known error cases. Having a name to put on things makes me feel good.

Another good thing about verification first is that it’s a great definition of done. And knowing when you’re done is critical. You’ve added some kind of value when you’re done. You get to stop what you’re doing when you’re done. You get to build up some momentum and celebrate when you’re done. You get to move on to the next thing when you’re done. These are all good things. And it’s part of the Purpose that Pink talked about in Drive. It helps motivate us.

And thinking about verification first development of course makes me think of my current job. At Aurora, our mission is to deliver the benefits self-driving technology, safely, quickly, and broadly. To do that, Aurora has a safety case. That safety case, which was built long before the code was finished, defines done. And not just in big broad terms, but also in detail. With specific requirements. To be done, we need to close the safety case. That means that a lot of work goes into building and executing the plan to verify that things are safe. So while different teams have their own flavors of development style, it’s all guided by a verification first mindset.

So, depending on what I’m developing, I might be using BDD, DDD, TDD, Test After Development, or sometimes I’m just experimenting to learn about a space and to figure out what I really want to do. Regardless of how I’m doing it though, there’s always at least a plan for how I verify the results are what I want.

The question now is, as that other Hillel would say,

If not now, then when?

If not now, then when?
by Leon Rosenshein

Safety Nets and Guardrails

Safety nets and guardrails sound like the same thing, but they’re not. They are very similar though. They both help prevent bad things from happening. Where they differ is in how and when they operate.

Safety nets help after something bad has happened. It’s what you do when things go wrong. Your traditional safety net catches something that has fallen. It could be a person off a roof, or a trapeze artist that missed a catch. It could also be a process that helps recover from a negative event. Like insurance (be it life, health, auto, home, or unemployment). It doesn’t mean the bad thing can’t happen, or that there will be no consequences, but it minimizes the negative impact/damage caused by the bad thing happening.

Safety net at the edge of a building

Or in the software world it could be a top-level error handling routine or executing a series of SQL statements inside a transaction so you can safely roll things back if there’s an error. Put another way, it’s using both a belt and suspenders even when your pants fit. Normally, the pants stay in place by themselves, but if they don’t for some reason, you’ve got the belt to hold your pants up. And if the belt snaps, there’s still the set of suspenders to hold them up. In terms of ilities, it’s resilience. 1

Guardrails, on the other hand, help prevent something bad from happening. Like the guardrails along the highway. They work to keep cars on the road and heading in the right general direction. It doesn’t mean you can’t force your way off the road, or that everything will still be perfect if you end up needing the guardrail, but things will be much better with the guardrail, and you’ll probably still get to your destination. It’s Poka Yoke, which I’ve talked about before. And just like you can have multiple levels of safety nets, you can have multiple guardrails. Like the rumble strips on a highway that tell you you’re drifting, before the guardrail pushes you back on track, both of them help you do the right thing.

Guardrail along a road

In software, guardrails come in multiple flavors. It’s using types instead of primitive obsession. Sure, you could use a string to store a value that is one of a limited set, but it can also store many invalid strings. If you instead use an ENUM that only supports the limited set, the user simply can’t set the value to something invalid. Another guardrail is using a builder to initialize something so that it either works or tells you immediately that it can’t be initialized instead of leaving you with something that won’t work. There are lots of other guardrails you can add to your software.

And remember, while safety nets and guardrails have the same basic goal, keep something terrible from happening, they are, in fact, orthogonal ideas. Which means you can (and should) use them both. Use guardrails that make it easy to use your API/functions the right way and hard to use them incorrectly. But recognize that it can still happen. So also include safety nets, so that when something is wrong, it gets handled the best way possible.


  1. Yes, I know resilience doesn’t end in ility, but it’s almost always in a list of -ilities ↩︎

by Leon Rosenshein

With Apologies to Uncle Bob

From the design by counter-example file, this might be the most practical definition of “unclean” code I’ve ever seen.

In the kitchen, the stuff I left on the counter is fine, I know why it’s there. Everything my family leaves on the counter is mess.

In our own software, we don’t trip over the rough edges, we can fix those later. For everyone else, our software is rough.

Jessica Joy Kerr

In your own kitchen, you don’t see your own clutter. It’s not bad, but there’s not a lot of available workspace. Similarly, that’s why everyone else’s code is not clean, but your code is. At least in your eyes.

A cluttered kitchen. It's not bad, but there's not a lot of available workspace

Clean code is good goal. And there are lots of heuristics and rules of thumb to help you get write clean code. You should always be thinking about them. Not blindly following them, but thinking about them. And you need to be aware of your biases and blind spots.

And one of the biggest, the one that makes identifying your own unclean code, is the same one that makes it hard to accurately and effectively edit your own writing. It’s a problem of context. When you’re writing, whether it’s code, a novel, an email, or a text message, you have an immense amount of context. When you go back to review/edit that text, unless it’s been a long time, you still have all that context. And even if it has been a long time, that context will come back pretty quickly. That means you don’t see the missing or doubled words. You don’t see the misspellings. You don’t notice that your functions are long, that you have complicated conditionals, or that function and variable names no longer match what they actually do.

Unfortunately, unless you’re the copy editor for someone, everyone else has much less context. They don’t know what you know and they see those things immediately. It makes it hard for them to understand your code. Just like it makes it hard for you to understand their code.

Or to work in someone else’s kitchen.

So the next time you’re reviewing your own code prior to getting someone else to review it, make sure you’re looking at not just with your own context, but also the context of someone who hasn’t seen it before.