Recent Posts (page 7 / 65)

by Leon Rosenshein

E_INSUFFICIENT_CONTEXT

As I mentioned the other day, I say “It depends” a lot. Then I go on to describe why it depends in that particular case. Yesterday I ran into a very short Mastodon Toot that is the underlying reason it depends.

“It depends” is human for E_INSUFFICIENT_CONTEXT_PROVIDED

The more I thought about it, the more true it became. It’s just that simple. At least the reason “it depends” is that simple. Doing something about it is a whole different thing. To answer the question, you still need the context. That leads to lots of follow-on questions and conversations so you can get that missing context. Once the context is provided the answer changes from “It depends.” to at least “Here’s a good way to start.”, if not “Here’s what’s worked for me in the past and what I recommend you do.”

And it doesn’t just lead to my stock “It Depends” answer. It also leads to my favorite question. “What are you really trying to do?”. That comes from the exact same place. Because that question is really about getting at the missing context. When someone presents you with an XY problem they’re not providing you with the context of the problem they’re really trying to solve, just the context of the blocker they’ve most recently run up against.

There are lots of ways to avoid providing insufficient context. Rubber Ducking is a great place to start. If your rubber duck can understand the problem you’ve probably provided enough context. Another good place to start is Jon Skeet’s Stack Overflow Question Checklist. Even if you’re not asking on Stack Overflow, that was a great checklist 10 years ago and it’s a great checklist now. Especially that last one. After you’ve gone through the checklist, re-read the question and make sure you’ve addressed everything on the checklist.

Providing sufficient context (asking your question well) helps everyone. It’s more than just being a good neighbor, although it is that as well. It reduces the number of back-and-forth cycles required to get enough shared context. Being mindful of others time makes it easier for the answerer. Which makes them more likely to have a good answer to your question. Which makes it more likely that you get an appropriate answer to your question. Which is what you really want anyway.

So next time you have a question, make sure the response isn’t E_INSUFFICIENT_CONTEXT_PROVIDED

by Leon Rosenshein

Don't Be Too DRY

DRY, or Don’t Repeat Yourself is a good rule of thumb. Like all other rules of thumb though, if you were to ask me if you should remove some specific bit of duplication, my answer would be “It depends”.

On the other hand, one of the Go Proverbs is, as Rob Pike tells us,

A little copying is better than a little dependency

So, when should you stay dry, and when should repeat yourself? It depends. It depends on whether or not you’re really repeating yourself or just have the same sequence of characters.

At one extreme you have something like this

numberOfSidesOfSquare := 4
numberOfSeasons := 4
defaultLegsOnDog := 4

There’s three things all set to the same value. Strict DRY might have you believe that you should define a constant called FOUR and then assign that to each of the variables, or write a function, setVariableTo4() that takes a variable and changes it’s value. But that doesn’t make sense. First, the integer 4 is already constant, so defining another constant is redundant. Second, while the action being taken is the same, semantically each of those lines does something different. There is no logical, domain relationship between the number of seasons, the number of sides of a square, and the default number of legs on a dog. The only thing they have in common is the value, 4.

At the other extreme you have bunch of classes that need to send out notifications. They all have functions with a signature something like


function sendNotification(smtpHost string, fromEmailAddress string, toEmailAddress string, subject string, body string) err {
}

Some have the arguments in a different order. Some don’t default smtpHost and subject if you pass in nil or an empty string, One doesn’t accept a from address at all. Inside the function some validate the inputs, others don’t. The error messages are different. They use different internal flow. If you look at the sequence of characters in the function bodies, they’re all different. No textual repeating at all. But logically, semantically, they do exactly the same thing. They validate the inputs. They transform the inputs into the right format and structures needed to talk to an SMTP host and send an email. The return an error if there’s some kind of problem sending the email. That’s something you should write once. For multiple reasons.

First, it makes future maintenance easier. If the default SMTP host changes you only need to change it in once place, and you can make sure no-one else even knows about it. If you want to change from email to Slack notification (or SMS or anything else) you can do that in one place. Or do them all. Again, only in one place. Second, from a Domain Driven Design standpoint, notification doesn’t belong in those classes. Yes, the classes need to do the notification, and they probably care who’s notified, but that’s it. They shouldn’t know or care how. It’s an external capability they should be told about.

Of course, those two are the extremes. For other cases you need to think more deeply about it. Are the things the same semantically/logically? Are they parts of multiple domains? Are they really parts of those domains or are they used by those domains? Should the functionality be extracted and put somewhere else entirely. Would not repeating yourself add some coupling you don’t want? If you have some coupled things, can you break the coupling by repeating yourself.

TL/DR; being DRY is good, but don’t be too dry. Think about the costs and benefits first.

by Leon Rosenshein

Lies, Damn Lies, And Statistics

When you’re running code in production, metrics are important. You need to know that things are working correctly. You need to know when things are starting to go in the wrong direction before any of your users or customers notice. Metrics can tell you that.

The problem with metrics at scale is that, in order to make sense of them, they’re often aggregates. Mean, median, mode, p90, and p95. All ways to turn lots of numbers into a single representative number. Unfortunately, aggregates like that are very lossy compression. The more variable the data, the less useful things like the average are.

Consider Van Gogh’s The Starry Night. According to John Rauser the average color in The Starry Night is #4C5C6D. That’s interesting, but it really doesn’t say much about the painting. Who wants to look at a colored rectangle?

There’s much more information in the painting, and I’d much rather look at it.

Van Gogh's Starry Night

Not only is there a lot of missing information, I don’t think that average color even exists in the painting.

Or consider something like a P99 latency. Not only are there many ways for the P99 value to be wrong, but even if it’s accurate you could still easily be missing the point. Statistics can be tricky things, especially percentiles. Sure, your P99 value means 99% of the values are lower, but it doesn’t tell you anything about why that last percent of items is higher, nor does it tell you how much higher.

Image you have 10,000 web requests be served by 100 servers. 9,900 complete in less that 30ms, so your P99 is 30ms. That’s a pretty good P99. That’s probably within your SLA, so according to the SLA you’re all set. What that P99 doesn’t tell you is that the other 100 take 5 seconds each. Somewhere in there you’ve got a significant problem. The other thing it doesn’t tell you is that all 100 of the slow requests were handled by the same server. Which means you’ve probably got a routing/hardware problem. Just looking at your P99 and you wouldn’t have known there was a problem, let alone that you it’s on the physical side, not the software side.

Which is another way of saying don’t just look at the aggregates, look at your raw data and see what else it’s telling you. It can tell you a lot about how things are really going, if you let it.

by Leon Rosenshein

Pass or Fail?

We say a test passes when the results are as expected and that it fails when one or more of them is not. When one or more asserts fails. That makes sense. When you take a test in school and get the right answers you pass. If you don’t, you fail.

When you took that test in school and got the right answers who passed? You, or the test? It’s been a long time, but the last time I took a test in school I got enough correct answers. When I got the results, they said I passed. They didn’t say anything about the test itself passing or failing.

So here’s another framing question for you. Why do we say we run tests on our software when as a student we take a test? Why do we say the tests pass or fail for software when we say the student passed or failed? I’ve got some thoughts on why.

I think it goes back to Taylorism, Legibility, and language. We tried to divide by function for efficiency. We tried to make sure we always had good, centralized metrics to tell us we were on track. We execute the tests, not the software to get those metrics.

Taylorism is all about assembly lines, decomposing work into separable tasks, and optimizing repetitive steps for efficiency. That doesn’t make sense for writing code, but it might make sense for testing code. After all, the tests are a separable step, and we repeat testing the code over and over again. So Taylorism leads us to considering the tests to be their own thing, completely separate from the code.

Legibility is the idea that the only way to make sure you do things efficiently is to centrally plan, record, and respond to deviations from the plan. To do that you need have the results of all of the phases. You break the coding into tasks and give them a state. You identify all of the tests and give them a state. Since the tests exist for a long time and, according to the plan, never change, the state of the test is the result of the last time the software was tested, pass, or fail.

And then there’s language. In this case, it’s the language of executables. Tests are code. You run code. Testing code, especially unit tests, but even integration and beta tests, are code you run. The code doesn’t run the tests, the tests run the code. The Sapir-Worf Hypothesis says that language influences thought. The language makes us say and think about running the tests to get results instead of testing the software and seeing if the software passes or fails the test(s).

That’s how our frame gets us to talk about running tests and tests passing of failing. Its interesting, and mostly harmless, but its not completely harmless. It’s not harmless because the frame does more than just define the words we use. It also influences how we think of of our software and it’s state. We say the test failed. We don’t say the software failed. That frame makes us think of fixing the test to make it pass, not fix the software. Sure, we usually fix the software, but since the test is what’s failing it makes complete sense to change the test until it passes. The frame makes us think that might be the right thing to do. But it’s probably not.

So next time you find yourself talking about tests failing, consider reframing it and talk about the software failing the test. Because how you look at things influences what you do about it.

by Leon Rosenshein

Autonomy, Alignment, Purpose, and Urgency

I’ve talked about Autonomy, Alignment, Purpose, and Urgency before. What I haven’t done much of is compare and relate them. Particularly the Autonomy/Alignment and Purpose/Urgency pairs.

Autonomy is great. You get to decide what you (and/or your team) do. You decide what you think is important and you go do it. You expect the work to be impactful, and you get to have fun doing it. Ask for forgiveness instead of permission.

Alignment gets everyone moving in the same general direction. It’s vector math. To make forward progress the sum of the vectors needs to be positive (for whatever positive means for you). To use a buzzword, it’s synergy between teams/groups. Everyone moves forward more than any individual would/could.

Purpose is the ever important why. Not the business purpose, although that’s important, but the internal desire to do something that will have a big impact in an area that’s personally important. As Daniel Pink said in Drive, “All of us want to be part of something bigger than ourselves, something that matters.”

Urgency is about timeliness. When does something need to be done? When a building is on fire the urgent things are to make sure people are safe, make sure the fire doesn’t spread, and put the fire out, in that order. Only after all that is done should you work on preventing the next fire.

The question is, how do they interact?

How much autonomy should you have? How much alignment? When there is neither autonomy nor alignment you end up with Brownian motion. People wander in random directions and overall, not much changes. A lot of alignment and not much autonomy gives you robots. People blindly doing what they’re told. Hopefully it’s the right thing. If not, oh well. Do it anyway since those are the orders. Too much autonomy and no alignment ends up with everyone moving fast, but in random directions, so again, there’s very little overall progress. When there’s a good balance of alignment and autonomy that you get the most progress towards the goal.

Four quadrant graph. Low Autonomy/Low Alignment shows little motion in random directions. Low autonomy, high alignment shows conformance and everyone moving a little in the same direct. High autonomy, low alignment shows everyone move farther, but in wildly different and changing directions. High autonomy, high alignment shows everyone converging on the goal

Then there’s urgency and purpose. They pair a little differently. Urgency is short term. Too much urgency, or urgency for too long leads to burnout. In gaming, everything was urgent Crunch Time regularly led to people leaving as soon as something shipped. Purpose is long term. It’s sustainable. The more purpose you have the more energy you have to apply. And when you achieve your purpose, you don’t feel exhausted, you feel whole.

Urgency is extrinsic. A sense of urgency is imposed from the outside. It often exhibits as micromanagement, or some seemingly arbitrary timeline. It happens through power differentials. The manager/boss wants something done and makes the team work harder. The team doesn’t have a choice. Work harder or suffer the consequences. Purpose, on the other hand, is intrinsic. It’s personal. It’s empowering. It’s shareable. Others can see and understand your purpose and join you in it.

Where it gets really interesting is how Purpose and Urgency interact with Alignment and Autonomy.

Purpose can drive alignment. If everyone has the same purpose, then they’re much more likely to align on the same goal. Urgency, with its external definition of the goal has the same result. The difference comes from the time horizon. If you’re only caring about the next few hours, you’ll probably get better results from an externally defined goal. If you’re thinking about the long-term results for your customer/user, then shared purpose is going to serve you better.

Purpose also drives you towards autonomy. While everyone might have the same general purpose, it’s unlikely that everyone’s exact purpose is the same. This leads people to group themselves by similarity of purpose, and then want to do what they see as the most important thing. They want autonomy to make those decisions. Urgency, with its externally defined goal and micromanagement, will reduce autonomy.

High urgency, while it can be effective in the short term, has the overall effect of pushing us to the top left quadrant of the graph. Which might be ok in the short term, if you really know what the goal is and no-one else does. But for long term success, it’s much better to have shared purpose and allow matching autonomy that pushed you to the top right quadrant. Which is where you have the best overall outcome.

by Leon Rosenshein

The Immutable Past

Comments are important. I’ve written about them a bunch of times. Comments in docs. Comments in code. Comments in git commits and PRs. As an aside, git commit messages and Pull Request messages are often very different things with different intents.

All the old things still apply. Comment about why you did something, not what. Comment about what you decided to not do. Comment about when you might revisit the decision. The kind of comments that don’t go out of date as soon as someone else touches the code.

A quick heuristic that can help you know if you’re making a comment that won’t go out of date is to see if the comment makes sense if you prepend it with something like “On <today’s date> we wanted/needed/decided to/choose not to …”. That’s because the past is fairly immutable. The code (at the moment) says what was decided, and the comment says why.

The present, on the other hand, changes a lot. Our understanding of the domain will grow. The situation changes. What seemed correct then is wrong now. But the thoughts and reasons behind the decision didn’t change. They were valid when we made them, and that hasn’t changed, so those comments are still correct.

But now that we know more, whether by deeper understanding, the situation changing, a shifting goal, or something else entirely. So we want/need to change the code to match. And that’s a good thing. That’s being Agile. The situation changed, so we adapted.

We adapt the code, but we don’t need or want to change the comments. They’re recording a moment in time and that moment is still very real and very valid. We might add to the comments with our new understanding and why they old choice is no longer the appropriate one. But again, it’s documenting the why and the why not. Documenting the moment in time, right next to the code it’s relevant to.

What you don’t want to do is turn your comments into a changelog. After all, we have git (or whatever version control you’re using) to record the actual code changes.

by Leon Rosenshein

The Law Of Instruments

if all you have is a hammer, all you see are nails.

Kaplan, Maslow, et al.

That’s not exactly what Maslow and the others said, but that’s a pretty common paraphrase. And it’s true. If the only tool you have is a hammer, you tend to treat every problem as a nail and pound on it until the problem is gone. Not necessarily solved, but at least it’s gone. Because of that, you need to know you tools. When to use them, and when not to use them.

However, using the right tool for the right job is only part of the issue. You don’t just have a bias to use the tool at hand. A perhaps bigger problem is that you also have a bias around how you see the situation. Hat Tip to GeePaw Hill who helped me put this into perspective for myself. It’s not really about hammers or nails. It’s not even about tools and uses.

The deeper bias is about the frame you view things in/through. And that’s a very subtle bias. It’s subtle because it operates before perception and cognition. You see things in terms of the frame of reference you’re viewing them from. Back to the hammer and nail, it’s not that you see a screw and decide that the easiest way to make it not stick up is to apply percussive maintainance. It’s that you see a nail, then you think about how to fix the problem of a nail sticking up, and whack it with a hammer.

The situation you’re in, the frame makes you see things not in the way they are, but in a way that fits into your ability to fix. Then you think about it and apply a reasonable solution to the problem, as you see it. The key to countering the bias is to recognize that you might not be seeing what you’re seeing.

Unfortunately, this isn’t the world of Dungeons and Dragons, and you can’t attempt to disbelieve. At least not by just rolling some dice. Instead, you have to do what the roll of the dice simulates. You need to:

  1. Recognize there’s a chance you might be fooling yourself
  2. Examine the situation closely
  3. Decide if you’re seeing what you think you’re seeing
  4. Do something about it.

Of course, the hardest part is the first part. You probably don’t have the time or energy to assume everything you perceive if wrong. After all, the vast majority of it is correct, so it’s just slowing you down. What you can do, though, is identify some of your common responses that might come from misperceptions. Once again, with the hammer and nail. If you see something sticking up that shouldn’t be and you reach for your hammer, look again. If you’re doing rough framing of a wall, it very well might be a nail. If, on the other hand, there’s a lump under your carpet, it’s probably not a nail, so check first.

If you’re building a distributed system and there’s a latency problem your first response might be to use a cache. That might be the answer. But before you go build/install that cache, look at the system again. Maybe you don’t even need to do what’s taking a long time. Maybe the thing you’re looking for changes so rapidly that a cache is the wrong thing to do and you need to fix the response time. Or maybe the problem is your SLO is just too high.

So next time you find yourself swinging that metaphorical hammer, before you make contact, take another look and make sure you’re seeing what you think you’re seeing.

by Leon Rosenshein

Effective Software Engineers

What do you think of when you think of effective software engineers? Do you think of things like knowledge of frameworks, libraries, and technologies, an understanding of Big O notation and data structures, or maybe the ability to write code fast? I know a lot of people go in that direction when they think of effective engineers. While I agree that those things are important to being a good programmer, I think being a good engineer, software or otherwise, requires an entirely different set of skills.

As I said just last week, and 6 months ago, software engineering is a social activity. Not just in how you debug and ask questions, but how you work with those around you. Your peers. Your stakeholders. Your customers. Your downstream dependencies. The people who will be maintaining what you’re writing. Your future self.

Which leads me to this article on What Makes An Effective Software Engineer. It goes through a list of things to do (and not do).

10 traits of effective engineers: 1) Cares about the user. 2) Great problem solver. 3) Keeps things simple. 4) Communicates well. 5) Builds trust, autonomy, and social capital. 6) Understands team strategy. 7) Prioritizes well and executes independently. 8) Thinks long term. 9) Leaves projects better than found them. 10) Comfortable taking on new challenges.

Look at that list. None of them are specific to writing code, or even software development in general. Instead, they’re about the traits needed to work well with all of the people involved.

I’n not going to talk about all of them, but here’s a little something about the ones that really resonate with me.

It all starts with the customer. Caring about the customer. Knowing what problem you’re solving and why. It’s about adding value for the user, whoever that is.

Keeping things simple goes along with thinking about the long term. Don’t do things you don’t need yet. Make it simple and easy to change. It’s an iterative process and you learn what you need to do by doing it, so keep it simple, allowing for the long term things when you’re ready for them.

Leaves things better than you found them. The boy scout’s rule. Learn as you go. Make it simpler as you understand more about the domain. Refactor to make it easy to make the change (this can be hard), then make the change. Make things easier for the maintainer

And finally, you need to communicate well. You don’t ever want to surprise someone. New things are explained well. Every change, every decision, internally and externally visible, has not just a what, but a why. You won’t always have agreement, but you should have consensus, and you DO make sure everyone is on the same page.

Check out the article. 10 things you need to be an effective software engineer, but (almost) nothing at all about writing code. In fact, the only place there’s mention of coding and code practices is in the anti-patterns.

by Leon Rosenshein

Breaker Breaker Rubber Duck

When I think about rubber ducks I think of two things. I think about Ernie singing in his bathtub and I think about the old C. W. McCall song, Convoy. And they’re both relevant here. Ernie is talking to his rubber duck. talking through his thoughts and plans. In Convoy, anti-establishment as it was, Rubber Duck, the lead driver in the convoy, recognizes that driving, as solitary as you are in the cab of your truck traveling down the interstate, is something you do with others. You’re part of group and the group does better together.

Ernie from Sesame Street signing to his rubber duck
A line of trucks coming around a curve, with Rubber Duck in the lead

I’ve talked about Rubber Duck Debugging before. It’s the idea that a good way to start a debugging session is by explaining the situation to an inanimate object. You describe what the problem is, what you know, what you think, and the assumptions you’ve made. It gets you to be very explicit about those things and being explicit often uncovers what you missed. It might have been something you didn’t know and needed to find out. It might have been a misconception/bad assumption. It might have been a misunderstanding of what the requirements are. Or it might not help you figure out the solution, but it helped you clearly articulate the problem.

Which brings me to this Medium article. It says that rubber ducking is bad and you shouldn’t talk to that inanimate object. Instead, you should talk to a real person. That’s not a bad idea. After all, Software development is a social activity. You’re almost certainly working with others. If you’re the only person writing code, there’s others involved. Design. Sales. Marketing. Support. Even if you’re doing all that, there will be customers. Or at least users. What you’re writing and their feedback is a slow speed interactive discussion. Software development IS social.

If development is social, is it bad to talk to others on your team? No. Of course not. You should talk to the other people involved. You should talk to them frequently. About what you’re doing and why. What you need help with and what you can help others with. You should talk about the whys and the why nots.

What you shouldn’t do is substitute talking to others for thinking for yourself. Don’t bring all your problems to the team before you try to solve them. That’s what you’re there for. To solve problems. So try to do it.

That doesn’t mean you rubber duck until you solve the problem. It doesn’t mean you beat your head against the wall until one of them breaks. It doesn’t mean you have to solve every problem on your own or have all the answers right away, every time. It doesn’t mean you don’t need to put in any effort.

It means you need to try to solve the problem. It means you need to understand the problem. It means you have an obligation to bring a good question to the team when you do. And that often means rubber ducking.

How much rubber ducking is enough? Like many things development, it depends. But you always need to put in enough effort to ask a good question.

by Leon Rosenshein

Forward Compatability

Before I get into today’s topic, a little flashback. Since December 29th, we’ve had 4 days with measurable snow. The first one, over 2 days was a wet, heavy (for Colorado) foot of snow, the other two were more typical front range snows, 2 inches of light powder. But it never got that warm and sunny, so it’s been building up. It’s also been melting and refreezing. Which reminded me of this post abut YAGNI. You ain’t gonna need it. Until you do, so be prepared.

Interestingly enough, this kind of relates to today’s topic as well. Backward compatibility is pretty easy to understand, and relatively easy to do. Essentially it means using a new format for your input, while retaining a way to recognize that your input is in some older format, then handling it correctly. The reason it’s easy is that you’re in control of all the variables You get to define the new format and how to handle it. You get to define how you’ll distinguish between the two formats. You get to define how to convert the old format into the new one. And the old format can’t change. Easy Peazy.

Forward compatibility, on the other hand, means defining your output in such a way that it works with code that hasn’t been written yet. That’s just a little bit harder. It’s not just that you’re not in control of the variables, it’s that the variable hasn’t been created yet. How can you be compatible with something that doesn’t even exist yet?

In the strictest sense, you can’t. There’s no way to be forward compatible in all cases. What forward compatibility really means is that you make some design choices up front that don’t constrain you (much). Instead, they put some very light requirements on the future. As long as the future follow those simple constraints then they’re backward compatible.

Consider HTML. The design of HTML and what browsers due with tags they don’t understand makes it forward compatible. It’s not going to be exactly what had hoped for, but older browsers can parse and display newer HTML documents, even if they include things like the <article> or <section> tags.

Communication protocols are often like that. Since you don’t know what you don’t know at the beginning you make room for learning and growing. If you’ve done any Windows system level programming, you’re probably familiar with APIs that take a structure that includes the size of the structure. That’s there for forward compatibility. It lets the receiver of the structure know what version it’s getting. While it might have been possible to determine it by inspection, adding the size of the structure makes it clear.

Another common way of making things forward compatible is by nesting things. Sure, you could have an API that takes 5 parameters, but if you add a sixth one then you need to not just rebuild everything, but in many languages, you need to change the code to add that 6th parameter, even if you’re going to go with the default value. You can make that forward compatible by passing that info as a unit (think C structure). If the definition of the structure changes, you might need to recompile the code, but you don’t need to change anything. That’s what the folks building the Windows API did, and it certainly worked for them.

It’s also important when you have a distributed system and need to transfer data from one computer to another over a wire. In many cases you have no influence, let alone control, over the computer that is sending the data. You need to come up with something that is extensible and forward compatible. Enter Protocol Buffers. Designed from the ground up to be both forward compatible and extensible. Follow the guidelines and you can add lots of extensions without impacting existing users. Send a newer version of data to something built with the old definition of the schema and it will happily ignore data it doesn’t understand.

That doesn’t mean you can ignore the issue. You still have to make sure that you don’t make breaking changes. You still have to make sure that ignoring the new data makes sense and gives reasonable answers. But you can do all that in the new code, where you have control and the ability to change.

You just have to think ahead and make sure you can accept those changes. Just like you need to plow a little bit wider than you need now, because something is going to change in the future.