Recent Posts (page 24 / 65)

by Leon Rosenshein

Multiple Projects

Work in progress and I have an interesting relationship. I understand, both intellectual and viscerally, that regularly working on multiple things at once takes longer than doing the first things first. Motion is not the same as progress, and just because you’re busy doesn’t mean you’re adding value. On the other hand, sometimes there are external events you need to wait for. Doing nothing in those cases doesn’t help anyone. While not all motion is progress, lack of motion is lack of progress. Sometimes it is the right thing to do, but when?

The first thing to think about is to compare the time spent on a project to the time spent switching between projects, the context switch time. If you’re talking about writing a design document in the morning, responding to email right after lunch, and then debugging an outage in the afternoon you may or may not be switching projects. Over the day you’ve done at least 3 things, but you were probably working on the same overall project. On the other hand, while you’re doing that afternoon debugging session a co-worker comes over and asks why the class structure is the way it is, so you’re looking at one piece of code and talking about a second one. Then your phone rings and it’s a call from your SO. Now all of a sudden you’re doing 3 completely different things at once. I don’t know about you, but I can’t do three things as well as I can do any of them.

One of the reasons comes from system thinking. When you think about things as interrelated systems you recognize that there is some level of interconnection between things, even if you can’t see it on the surface. And for true mlti-tasking, that interconnectedness looks something like

Time to context switch

One estimate of a context switch is that 20% of a day’s time is lost to switching between projects. That means that if you’re working on 5 different projects (not tasks in a project) you might be losing 80% of your time to context switches. That’s a lot of time. To a first approximation, that’s about 1½ hours per switch. And the same amount of time to switch back.

That’s a lot of time, and I generally don’t have that much extra time in my day. So again, to a first approximation, unless you expect that external delay to take over 3 hours, you’re much better off finding something else in the same project to work on. Do a code review. Answer some related emails (leave the unrelated ones for later). Work on that design doc. There will still be a context switch, but it’s much smaller and will take much less time.

by Leon Rosenshein

Gift Giving

I’ve talked about the gift of feedback before. How important it is, and how useful it is. One thing I didn’t talk about then though is what kind of feedback you give when. Gifts should match the occasion. You wouldn’t give a housewarming gift for a 75th anniversary, and feedback is just the same.

Let’s say you want your house painted, so you look around at newly painted houses and find that the ones you like have the same painter. You call the painter and start to discuss things. The painter puts a swatch on the side of the house for you to look at. You look at the swatch, tell the painter you don’t like that the edges of the swatch aren’t crisp and they should do a better job of edges and finishing things and head to work. You come back at the end of the day and see that your house is almost fully painted. The only thing left is the door and the window frames. The edges are perfect. Just what you want. But not the color. So you call the painter and tell them it’s 2 shades too dark and they need to redo it.

That doesn't make a lot of sense, does it? The wrong feedback at the wrong time. It’s understandable though. We want orderliness and predictability. We want to close out issues. So instead of opening a discussion on the things that are unclear we focus on things we can close out. But we should fight that urge and give the right feedback at the right time.

That’s the 1/50/90%feedback idea. When things are just starting out, when there’s 1% of the project done and most of the work is in the future, feedback is about the big things. Is the direction or vision correct? Are the right upstream and downstream dependencies identified and accounted for? Does this fit into the bigger picture you’re going for? In the house painting example, when looking at the swatch on the side of the house, the feedback should be about the color. How it fits in with your overall vision for the house. How it works with the landscaping and the surrounding neighborhood. Not if the edges are crisp.

Some time later, when things are well underway, say 50% done, feedback is about how it’s going and how well things are matching with expectations and vision. Are there any new learnings/understandings from doing the work that far that might cause a change in the plan? If so, have they been raised, answered, and incorporated? Again, back to the house painting, when one or two walls have the base color applied, does the color still work with the surroundings? When you look at it from farther away, from a perspective you couldn’t see with just a swatch, does it make the statement you want? Now might be the time to think about the trim color for the eaves and windows.

Finally, when the project is 90+% done, look at the external details. The fit and finish. It’s (mostly) too late to change the big details, but what are the smaller things that you can only see now in the full context that might need some adjustment. Back at the house, the painting is almost finished. You’re doing a walk-around with the painter. Now is the time to talk about the edges between the walls and windows. Look for paint splatters. What they call in the trade the punch list. The things that need to happen before it’s truly done.

And that applies to software just as well as it does to house painting. When you see a proposal or vision doc, that’s not the time to say there needs to be an api for X and it needs to include parameter Y, or write the exact error text if an invalid parameter is provided. Instead you should make sure the domains are properly separated, the use case for the X api is covered and that as a principle, error messages are provided and that they include enough context for the user to understand how to fix it.

At an interim review, make sure the domain boundaries still make sense as things have evolved. Make sure use cases are covered, or at least not precluded. Think about new requirements that might have come up, or changed to up and downstream dependencies that might impact things. How has the overall environment changed and what are the effects of that. It’s still not the time to correct the grammar on the error messages, but it is the time to make sure the principles are being followed and the info is there.

Finally, as the project is nearing completion, feedback gets very specific. Now is the time to check the grammar in the error messages. Make sure that it’s solving the problem completely, not leaving off some edge cases. Feedback about things that might make it easier to live with over the long term. And of course, if you do see a big problem, say something. It’s better to fix things late in the dev cycle than to put something out that makes things worse.

So continue to give the gift of feedback. Just make sure the gift matches the occasion

by Leon Rosenshein

Things We Know (or think we know)

There are lots of things we know. Or at least assume to be true. But how many of them really are? Consider RFC 1925, which lays out a set of “truths” for the internet. And since it can be found on the Internet, it must be true.

Of course, sometimes the easiest way to define truth is to define what it isn’t. That’s where the Fallacies of distributed computing come in. We’re all developing distributed systems. We all “know” these things are fallacies But sometimes, when writing code, we forget. And we forget at our own peril.

Consider the first fallacy. The network is reliable. We all know that it’s not. Certainly not in the small. Any given packet of information could be lost, or delivered late, or be slightly garbled by the time it’s delivered. And yet we rely on the unreliable network, You’re reading this over a network. Builds require access to remote information. Our phones use a network. In the large we can (usually) rely on the network because at the small there are things like retries, checksums, sequence numbers, and time stamps. So we rely on the network because the alternative is to be physically touching the same hardware at the same time. And of course that’s really a network as well, and not 100% reliable. But often it’s close enough to being reliable in the large that we can just ignore those error cases.

Or more likely, we decide that adding sufficient defense in depth, whether it’s via hardware redundancy, redundant routes, caching, store and forward, or any of a dozen other work-arounds, costs more, not just in raw dollar terms, but also in terms of added complexity, loss of agility, and increased cognitive load, to not be worth handling. So where do you draw the line? Well, it depends. It depends on the cost of all those changes.

Or maybe you’re not worried about the network. Your problem is simpler. You just need to deal with people’s names. That’s simple, right? 3, maybe 4 strings. Easy to store and display. Or maybe not. Some folks have 5+ strings in their name. I only have two. The artist formerly known as Prince had mostly one, then for a while it was just a custom glyph. He broke so many of the things we know about names in just a few short years. Or more recently a couple couldn’t use the baby name they wanted because it didn’t follow the rules.

All of which is to say, examine your assumptions. They may be correct. They may not be. The cost of dealing with outliers might be more than you’re currently willing to pay. That’s a fine answer. Just make it knowingly, not by mistake/default.

And whatever decision you make, write down why so you understand why you did what you did when you did it.

by Leon Rosenshein

Eliza

And I don’t mean Eliza Doolittle. No, in this case I’m talking about one of, if not the first, natural language processing artificial intelligence agents. I’m talking about the Eliza that came out of MIT’s AI lab in the mid 60s.

Eliza and I are about the same age, and I first ran into it in my early teens. And like any typical teen, I spent a bunch of time getting Eliza wrapped up in knots and saying things that made no sense at all. But in fact, if you took it seriously, you could have what appeared to be a real conversation. Could it pass the turing test? No, but in a limited domain it could feel real.

There are a bunch of things to be learned from Eliza. Not the least of which is that much conversation is shallow, and doesn’t require much, if any, subject matter knowledge. The second is around systems thinking (more on that at a later date) and emergent behavior. Eliza used a very simple script, with some pattern matching and lookback, to generate its responses. Remember, this was almost 60 years ago, and there wasn’t any semantic understanding of the words. But still, some people thought they were talking to a real therapist and say it helped.

It turns out that recently someone found the original ELIZA code and the DOCTOR script it used to act like a therapist. That website has links to the code and lots of interesting info about how Eliza’s original author felt about how Eliza was received.

And if you want to try it out yourself and don’t have an IBM 7094 running MAD-SLIP, check out this javascript version. I’d be interested to hear how your conversations went.

by Leon Rosenshein

Proverbially Speaking

Go is a pretty minimalist language. When you get down to it, there’s not much there. There are only 25 keywords. Types come in 3 flavors, simple types, aggregate types, and reference types, and the magic interface. Simple types are numbers (int/uint/float with precisions), strings and booleans. Aggregates include arrays and structs, while reference types are slices, pointers, funcs, and a go-ism, channels. That’s it.

Any yet, you can, and people have, written significant system/back-end/control-plane tools and systems with it. Things like Kubernetes, Prometheus, Docker, and Terraform for example. We have too. BatchAPI is Go.

Another thing about Go is that the community has more than a little bit opinionated. That’s why when you look at Go code, even poorly written Go code, it looks like Go code. The biggest reason for that, of course, is gofmt. While you can write Go with your own style, there is one true style, and gofmt will quickly and easily apply it for you.

The other way it does that is through what’s colloquially known as idiomatic go. The kind of Go that matches the way other Go developers think. And a lot of that comes from the Go Proverbs. A few sentence fragments from a talk by Rob Pike, one of Go’s developers. And they capture not how the language works, but how you should use it. Ideas designed to help developers write code in a way that used the language the way they thought it should be used. There’s even a nifty little website and set of icons/images.

Some are very Go specific, like Cgo is not Go or interface{} says nothing, but others have broader reach, like A little copying is better than a little dependency or Documentation is for users. Of all of them, the one that speaks most to me is Clear is better than clever.

Go is not about writing as few lines of code as possible. It’s not about packing as much logic into a single line as you can. It’s not about implicit handling of errors somewhere at the top of the chain. It’s very much about doing something and responding to what happened, even (especially?) if it wasn’t what you wanted to happen. Because it’s not about just writing the code. It’s about maintaining it. Supporting it. Extending it. Basically keeping the cognitive load as low as possible when interpreting the code itself so you can focus on the user’s problem and not have to worry about understanding what the code is doing behind the scenes.

What do you like about the proverbs, and what other ones should there be?

by Leon Rosenshein

When You Have A Hammer

You haven’t mastered a tool until you understand when it should not be used.

Kelsey Hightower

I’ve talked about patterns and best practices before. They’re great. They give you a starting point. But they’re no substitute for thinking about your situation.

Consider the hammer, screwdriver, and pliers. In theory, there’s very little you can’t do with that set of tools. You use the hammer to put screws in. You use the pliers to take screws out. And you use the screwdriver to open paint cans, pry valve covers off, and, with the hammer, chisel out notches in wood. That’s mastery of your tools, right?

Not exactly. Sure it works, but screwdrivers are much better at putting in and taking out screws. using screwdrivers as prybars or chisels can work, but you end up with

screwdriver

And the less said about using pliers for every nut, bolt, and pipe you need to turn, the better.

The same can be said of software. Consider DRY. Don’t repeat yourself. Short functions. Generally good ideas. But even that can be taken to extremes. Consider this silliness

package main
import (
    "fmt"
)

func increment(x int) int {
    return x + 1
}

func is_less_than(x int, threshold int) bool {
    return x < threshold
}

func print_i_and_j(i int, j int) {
    fmt.Printf("I: %d, J: %d\n", i, j)
}

func main() {
    for i := 0; is_less_than(i, 10); i = increment(i) {
        for j := 0; is_less_than(j, 15); j = increment(j) {
            print_i_and_j(i, j)
        }
    }
}

It works, but it’s actually less clear. Sure, we can guess what those functions do, we can’t be sure. Especially if they’re in another package/library.

Or, a different example would be microservices. Sure, at planet scale lots of things need to scale at different rates/shapes, so when you get to that scale you need it. But using a microservice architecture for a simple static website doesn’t make a lot of sense.

So when you reach into your toolbox, make sure you grab the right tool, not the one on top :)

by Leon Rosenshein

Wat

Heading into the weekend, a little programming humor for you. All languages have their oddities, but some are a little odder than others. For instance, Javascript can lead to watman.

And if that’s not enough oddness, here are some more weird languages. Take a look, one of them might be just the thing for your next project, but probably not. 

by Leon Rosenshein

OOP at Scale

Quick, what’s the defining item of object oriented programming? Classes? Hierarchy? Inheritance? Enterprise Java Factories? 

According to Alan Kay, who came up with the term over 50 years ago, the 3 defining parts of OOP are:

  • Message passing
  • Encapsulation
  • Dynamic binding

Nothing about objects, classes, or inheritance. More about isolation and clear boundaries. You can (and should) do that with your code as well. Use function calls (message passing) instead of doing things yourself. Don’t set up some shared state and pass control off, explicitly send the required info and explicitly collect the answer.

Keep like things (data) and functionality (methods) together. The most common way to do that is with a class, but duck typing can suffice. It doesn’t need to be a class. It could just be a library. Everything about an employee could start with Employee_ and just be a loose method in there. Use data structures that collect all the relevant information together, not just a big heap of data.

Wait until as late as possible to decide exactly which method to call. Sure, you know what you want to do, and how to call it (which message to send), but don’t decide exactly which one until later. Dependency injection is a great example. If you need to durably store a blob of data just call the Store() method. Which store (file system, S3, database, testing, etc.) you determine at runtime because it’s encapsulated.

That’s Object Oriented Programming.

Now consider microservices. While there are lots of transports, by definition, all communication between services is via a message. It has to be because the thing you're talking to is potentially running on a computer miles away. You’ve got to send a message.

And since that service is somewhere else you can’t just reach inside it for some info or to change something. You have to send a message. It doesn’t get much more encapsulated than that.

As for dynamic binding, there’s DNS, which can change between one message and another. And most microservice systems have another level of load balancer behind that DNS entry, so the actual service you’re talking to is decided until after you make the request, and milliseconds before the message is handled. It doesn’t get much more dynamic than that.

And that’s OOP at Scale

by Leon Rosenshein

Empirical Software Engineering

What is software engineering? What is important to software engineering? What do we know about software engineering? How do we know what we know about software engineering?

Perhaps more importantly, what do we know we don’t know about software engineering? According to Hillel Wayne

Empirical Software Engineering is the study of what actually works in programming. Instead of trusting our instincts we collect data, run studies, and peer-review our results.

Seems reasonable. But what does it mean? Are shorter functions better or worse? Is there a language that is better to use than some other language? Is a microservice architecture better than an event driven one? For that matter, what does better mean?

Like many things, the answer is, it depends. It depends on context. It depends on the problem space. It depends on the constraints you have to deal with. There’s a great scene in Apollo 13 where they need to “make this, fit into the hole for this, using nothing but that”. Those are constraints. Doesn’t matter what the perfect answer might be. That’s the best answer, right now.

And in Hillel’s talk, he talks about the definition of better a lot.  And not just what better means, but how do you know if you’re right, and if the cause you’re positing is really the cause? And qualitative vs. quantitative research. And the validity of the finding itself.

I’ll let you all watch the talk yourself for the details, but here’s a few of the biggest takeaways.

The best metric for the number of bugs in a code base is the number of lines of code. Pretty obvious. But also not very helpful. It’s not very actionable.

Org structure has a big impact. The higher number of different people/teams/groups adding changing code in a module, the higher the number of bugs. But it’s a balance. Silos and gatekeepers may have a small positive impact on the number of bugs, but they have a large impact on velocity.

Finally, the things that are most impactful to development are Stress and Sleep. Missing a little bit of sleep on one day makes you less productive. Missing a few hours a night is like missing a whole night’s sleep. And even worse, along with that reduction in productivity and effectiveness, comes an inability to notice that reduction. Which means, not only do you make mistakes, you can’t tell you’re making mistakes. And you can’t do anything about them.

So whatever else you do, make sure you get enough sleep.

And on a mostly unrelated note, take a look at the slide deck that goes with the talk. That’s a really interesting way to do a talk. Very minimalist. So you focus on the presenter. Which is often a good thing. But if you consider a presentation as two channels of information (spoken and written), it basically leaves the second channel unused. But that’s a topic for another day.

by Leon Rosenshein

Rice and Garlic

Here’s another one from GeePaw Hill, all about rice and garlic. Actually, it’s not about rice and garlic. It’s about generic advice.

As GeePaw tells it, he knows a chef who has a standard answer to the context-less question, “How can I make my cooking better?”. The blind answer is “That’s too much rice, and not enough garlic.” And you know, that’s pretty good advice when you don’t know what’s being cooked. Of course, there are edge cases. You can’t put too much rice in a bowl of rice as a side dish, and contrary to what they say in Gilroy, some things don’t need more garlic.

So what’s the software development equivalent? GeePaw says you should take more, smaller, steps. Again, without much context, that’s pretty good advice. Rather than try to get from 0 to 100 in a single step, try 0 to 1. Then 1 to 2. Continue that approach and you’ll still get to 100. Assuming along the way you haven’t realized that what you really want is 75 so you stopped there. Or more likely, you’ve found that the goal isn’t 100 anymore, it’s actually a close neighbor of 100, but off in a different dimension. Which you couldn’t have known without working towards the goal incrementally.

And not just that. That kind of advice scales as well. It doesn’t matter if you’re going to the moon, building a website, or writing a command line calculator. Make a step in the right direction. Test fire an engine. Get your website to deploy and log that it’s listening on a port. Get the CLI calculator to return an error about invalid input. And you can break those down further into actionable tasks. Figure out what each step is supposed to do. Then come up with a way to tell it it’s doing it, and every time you take a step use that mechanism to see if you’re getting closer, holding your ground, or falling backward.

There’s a name for that kind of development. Test Driven Development (TDD). It has all kinds of benefits. Add in some customer value/feedback and now it’s not just TDD, it’s Agile TDD. Which is great.

Because you get to work with your customer to not only show your progress, but they get value along the way, and you get better understanding of their problem(s) and can focus on that, instead of just trying to get to 100 one fell swoop, because 3 months ago you thought that was the exact target to hit.