Recent Posts (page 7 / 67)

by Leon Rosenshein

Green Fields And Platforms

I recently had the opportunity to work on a greenfield project. You know, the kind of project were you recognize a common problem a set of users have and you have an idea of how to solve the problem for them. It can be a great space to operate in. You have a green field to build your castle in. Sure, you’ve got the eternal constraints of time and resources, but other than that you’re free to innovate.

You get to choose your architecture. Need lots of extensibility governed by a common control? Maybe use a micro-kernel architecture. Have lots of different components that need to scale differently? Try microservices. You set the requirements and you decide on the architecture.

You get to examine the problem and solution space and figure out what the bounded contexts are. You pick the domains that drive the design. You pick the nouns (objects) and verbs (actions/API). You get to do it in a way that makes sense to you.

I was in that situation. We choose the language. We choose the architecture. We choose the domains. We built a proof of concept that showed us we were on the right path. Things were looking good.

Then we ran headlong into an observation Tolstoy made in Anna Karenina.

All happy families are alike; each unhappy family is unhappy in its own way.

We were trying to build a platform for others to build their solutions on. Like a good platform, one of the things we were doing was hiding complexity. We were trying to make it easier for our potential customers to focus on doing the things that added value and that their customers wanted. We wanted to make it so that they didn’t need to worry about the complexity and details of configuring and providing low latency connections between any two of a few hundred endpoints.

We spent some time talking to potential customers and it turns out that Tolstoy was right. At a high level all of our them were doing the same kinds of things. It was easy to see how a single platform could provide a foundation for all of them to build on and be happy. However, when we dug into the details of what they were unhappy about, it wasn’t as uniform.

Sure, they all ran into the same problems, and they all used most of the same tools to approach a solution. Unfortunately for us though, the solution to those common problems wasn’t the same across customers. And they weren’t different in the details and approach, they were also different in how those solutions integrated with the rest of their systems.

The domains had different shapes. The bounded contexts had similar names, but the boundaries were different. The set of verbs they used were generally synonyms of each other, but because the domains and contexts were different, they were incompatible. They were all unhappy with their bespoke solutions, but they were all unhappy in very specific, and different, ways.

Getting rid of that unhappiness still made sense as a solution, but the field wasn’t as green as we thought. Sure, we could have whatever architecture we wanted on the inside, but along the edges we found a whole new set of constraints. We had to (mostly) match existing code and APIs. Much more of a brownfield project than we originally thought.

As a platform we needed to make customers’ lives easier. And one of the big things we needed to do to make their lives easier was make sure the transition from their bespoke solutions to our framework was straightforward and painless. We realized that doing that had just become our biggest problem. In some ways it was a bigger problem than the original problem our platform was supposed to solve. Instead of just needing to hide the underlying complexity, we needed to hide the complexity of building a single API that could support the workflows and patterns of multiple different approaches to the problem.

To solve the problem, we kept reducing the scope of the initial version. Instead of a full-featured platform that could drop in, we focused on a very small subset of the problem space. Something we knew was important enough that all of our customers would want the change. Something we knew was separable enough that our customers could accept the change without having to do too much architectural surgery on their existing code. Something that would be the thin edge of the wedge and let us insert our platform in our customers’ codebase and then expand out from.

So next time someone tries to convince you that you’re going to be working on a greenfield project, remember that even the greenest of fields have edges, and the shape and context of those edges will turn what you think is a greenfield project into a brownfield project.

That doesn’t mean the idea is a bad one or that you should run away from it. On the contrary, it can be a great opportunity to learn a new domain, add lots of customer value, and have a chance to really stretch your design muscle. Just don’t forget the things on the edges, because that’s where the constraints and headaches come from.

by Leon Rosenshein

What Is Done?

The Definition of Done is an important part of the software development process. I’ve talked about being Done Done and Telling You What I Want before. I’ve also talked about the difference between Stories and Tasks. All of those things tell you about the importance of knowing what done means. There’s also some guidance in there on how to define done.

What those posts don’t do though, is point out any of the potential hazards along the way. Like most other sets of directions, they provide guidance on what to do, but they don’t tell you much about how to recognize when you’re off course. How to recognize the situations where your definition of done is actually causing more problems than it solves.

The first, and arguably, the most important, is being too rigid. You have your acceptance criteria and damn it, you either meet them or you don’t. You said there would be 95% line coverage with unit tests, but there’s only 94.87% coverage. You failed. Or you said there would be a P95 response time of less than 100ms, but P95 is 110 ms. Sorry, you tried hard, but you failed. That’s pretty demotivating, and no-one wants that, so instead of admitting to failure, you ignore the definition, declare victory, and move on. Everyone feels good. But after a few such occurrences, the definition of done becomes unimportant. Since everyone knows you’re just going to move on anyway, why bother doing what you said?

Being too rigid in the definition, then ignoring whether you met the definition or not quickly leads to the definition of done being irrelevant. The trick is to make the definition of done comprehensive enough that you’re always adding value, but not so strict that 50+% of the time you end up missing it. Instead leave some room in the definition of done so that it’s almost always achievable.

Which leads right to the second problem. Failing to inspect and adapt. Whether you call your process Scrum, Agile, XP, Kanban, BDUF, or something else, if you’re not paying attention to what’s really happening, you’re not going to end up where you want to be. Not meeting a definition of done by the specified date is a problem, but a bigger problem is to just accept that it happened, and will continue to happen, and not try and make things better. Every failure to meet a date is not a problem, it’s an opportunity.

Acknowledge the miss, then look at your process and your definition of done and make changes. As I mentioned above, you can make the definition less rigid. Instead of saying 95% coverage, say that coverage will be > 95% or the coverage at the beginning, whichever is lower. Now, your goal is not to suddenly meet some arbitrary coverage number, but to keep getting better until you meet the threshold you’ve defined you need to maintain.

Along the same lines, and worth a whole post on it’s own in the future, is that using a standard definition of done is a problem. It might be too rigid. It might be too lax. If it’s standardized and shared, it’s almost certainly out of context. Even if it’s a “best practice”, the label of best practice comes from someone else, and might not (probably doesn’t?) apply in this specific case.

So by all means, have a definition of done. Make sure it fits your team and context. Define it so that it’s achievable. Achieve it whenever you can. When you can’t, fess up to the miss. And always, always, always, take the time to look back and learn how to make your definition of done better.

by Leon Rosenshein

High Quality Quality

Before today’s topic, a little housekeeping. After a little over 14 months, I’m back at Aurora. I’ve taken on a Tech Lead Manager role, responsible for measuring and helping to improve software quality in support of Aurora’s mission to deliver self-driving, safely, quickly, and broadly.

Of course, before you can measure (or improve) something, you need to define what it is. In this case it’s software quality. There are lots of ways to measure software quality. To me, the most important is suitability to the problem at hand. Whatever software you write needs to add value and solve the problem it intends to solve.1

If you never intend to touch the code again, nor will it ever be used in a situation you didn’t expect, nor with inputs you hadn’t considered, you might have quality software. For the rest of us, writing code that will need to be changed, either to handle new requirements or new operating conditions or new environments, solving the problem at hand is a necessary, but not sufficient condition for having quality software.

Then there’s the definition of solving the problem. Have you solved the problem if you only solve it in some specific cases? Maybe, but it depends on the failure mode. And what the expectations are for that failure mode. A basic calculator is pretty simple. If it adds positive numbers correctly and says “invalid input” for negative numbers that might be quality. If it gives you the wrong answer, it’s not quality software.

You might be saying to yourself, wait a minute, this isn’t about software quality, it’s about correctness and suitability, or External Software Quality (ESQ). That’s sort of true, but you can’t have quality software without it, and if we let ourselves forget that we’ve got a whole different problem.2

Having said all that, what about code quality, or Internal Software Quality (ISQ)? When you start talking about ISQ, you’re talking about some of the ilities. Readability. Maintainability. Extendability. Scaleability. Securability. Optionality. Things that matter when you come back to the code weeks or months later because you’ve got a new requirement, or you found a problem.

At the heart of ISQ is the notion that while we might write code occasionally, we spend way more time reading and modifying code that’s already written. That’s where things like coding styles and linting come in. Things that make it easier to read the code because things look the same. Loop boundaries are clear. Variables are named clearly and not re-used for strange purposes. Things that make it easier to understand not just the exact meaning of a specific line, but how that line fits into the overall flow of the code. Reducing nesting and indentation. Things that make it easier to change code and be confident that you haven’t broken something. Increased code and branch coverage. Static analysis. Reducing cyclomatic complexity. Things thay keep the reader’s cognitive load down so that they can focus on the why, and not get caught up understanding the what.

Things like coverage, static analysis, and cyclomatic complexity are nice because they are quantifiable. You can look at today’s numbers and compare them to the numbers from last week or last month and see if you’re getting better or worse. They can be calculated for small parts of the code and changes to the numbers can be attributed to specific changes to the code, so you know what areas need work. You can use the numbers as gates to allow (or block) changes if you don’t like the way the numbers are impacted by the change. Doing that can help your ISQ. Just like ESQ, you need to measure and respond to what those numbers are telling you. But you also need to be careful to understand what you’re doing. You might be moving the numbers in the right direction, but there’s no guarantee that you’re improving your ISQ. If they’re getting worse, ISQ is probably going down, but the inverse isn’t always true.

Or put another way, the set of things you need to do to improve quality is also the set of things that make developers more productive. Things like DORA and SPACE. High ISQ leads to happy developers and happy developers leads to high ESQ.3 Unfortunately, if you think measuring code quality is hard, measuring developer sentiment and productivity is even harder.

There’s no silver bullet or magic potion. Instead, you have to implement all the individual measures, think about them, and make a change that you think will drive things in the right direction. Then you reevaluate and see if you’ve made things better. When something works, you do more of it. You look at incentives and goals, then you make sure the incentives align with the goals. You take many more much smaller steps, improving things one step at a time.

Or, you start instrumenting your code reviews and minimize the WTFs/Min.

Image of two doors to rooms were a code review is happening. One door has a caption reading 'Good Code'. The other has the caption 'Bad Code'. The good code door has though bubbles for 2 WTFs. The bad code door has 5.

WTFs/m


  1. It’s ok if the street finds its own use for your software. If it solves that problem, it has quality for that problem, even if it’s not the one you originally tried to solve. ↩︎

  2. It’s a problem for a different time, but for now, take a look at these search results ↩︎

  3. That’s also a topic for a different time ↩︎

by Leon Rosenshein

A Problem Vs. The Problem

If you’re looking for the All Models Are Wrong that was here by mistake, just follow this link.

I’ve talked about Root Cause Analysis (RCA) before. Otherwise known as “Why did this really happen?” It’s an important tool in deciding if you’re fixing a problem or alleviating a symptom. Both are very doable, and both can be appropriate. The question you need to ask yourself is “What impact do you want the solution you’re coming up with to have?”

The answer to that question is very contextual. If you’re doing a market analysis or trying to find product/market fit then you’re looking to solve a problem that your customers might not even know they have yet. At the other end of the spectrum, are outages and incident response. Something that used to work is suddenly not working. Users know exactly what their problem is (they can’t do the thing they’re trying to do), but have no ability to get it working again. You, on the other hand, have no idea what the user’s problems are, but you can see that your software/service isn’t responding correctly.

In the first, arguably easier, case, you have a couple of luxuries. No one is upset with you (yet). They have limited, or no, expectations. You have the time to think deeply about the problem and figure out what the root cause is and come up with a solution that solves that root cause. Then, with the root cause addressed, you can meet the user’s specific needs and add value for them.

When you’re dealing with an outage on the other hand, you don’t have the luxury of time. Something is broken and your users are suffering. You might just be slowing them down a little, but you could also be costing them time and money. At Uber, if the rider/driver matcher wasn’t working you had both problems. The riders were unable to get where they needed to be on time, and the drivers weren’t able to earn money. That puts more than a little urgency on solving the problem. That urgency drives you away from doing RCA and solving the underlying problem, and instead solving the proximate cause. However, you still need to solve the root problem.

Which leads right back to a problem vs the problem. Sometimes you need to solve a problem, the one right in front of you. Door A or door B? It’s highly unlikely you’ll be in the same situation again, so there’s no deeper, root cause to find and fix.

Consider the case where you’re at some kind of social event and you’ve walked up to buffet and they have lots of choices you like. Take a little of everything, or a lot of one thing. Make a choice. If you’re still hungry after, make a choice again. There’s no need to investigate why you’re at the buffet, how the selection of dishes was determined, or figure out a way to change those things.

On the other hand, if you’re at the event and you’re not hungry, the situation is different. Even if you have some kind of restricted diet, you’re not hungry, so there’s no need to find something to eat. You can just grab a drink and think about what you cold do if you were counting on the buffet having something you could eat. There are lots of ways to handle dietary restrictions at buffets, from carefully picking foods to talking to the provider to eating before or bringing your own food with you. There’s no pressure so take the time to figure out what you want to do next time you end up at a social event that might have a buffet.

On the gripping hand, you’re at the event, you’ve been on the road for 12 hours, so you’re more than a little hungry, and you’ve got those dietary restrictions. Now you need to solve both problems. You’re hungry, and clearly you hadn’t fully thought through the situation. What can you do to get something to eat now (the proximate problem) and how can you make sure you don’t find yourself dealing with the same problem again (the root cause)? To make matters worse, you’re hangry because you haven’t eaten in 12 hours. In that case, fall back to the incident response pattern. Stop the damage and grab a drink. Mitigate by finding something to eat. Identify and implement the long-term fix by keeping some iron rations in the car so don’t go 12 hours without eating again.

So next time you find yourself solving a problem, make sure you’re solving the right problem at the right time.

by Leon Rosenshein

All Models Are Wrong, But Some Are Useful

As I’ve mentioned before, I’m a mechanical/aerospace engineer by training. My first job out of school was doing simulations to do trade off analysis on different aircraft configurations. Some were studies around mission effectiveness in different combat environments, and others were aerodynamic simulations to see what effect changing flight control software would have. In all of those cases we were using code to represent the various real-world systems and their interactions to predict what the results would be in the field. And we needed to do lots of trials.

This was 30 years ago, and the computers were slower, so we made lots of simplifying assumptions so we could get our results faster. First, computers are discrete, and the real world is generally, analog. We made time slices small and used all sorts of integration methods and feedback loops to make it seem like things were analog. Second, all that code was an approximation of reality. For the aerodynamics we took 3 and 4 dimensional tables (angle of attack, speed, altitude, and control surface positions) of flight data and interpolated between the data points. Which meant we left out a lot of things, like how all of those things (and others) were changing. For avionics and other systems, we used statistical data. How far away radar could detect objects of a given size. How effective missiles and countermeasures were. Again all defined in nice, neat, discrete tables.

In other words, a model of the systems. We knew they weren’t exactly correct, but we felt they were correct enough draw conclusions from the results. Which leads us right to

All models are wrong, but some are useful

        – George Box

Box was correct when he hinted at it in 1976, and made that exact statement in 1978. The models are wrong, but they’re useful in getting results faster, cheaper, and safer than you could by running those 1000’s of trials.

Box wasn’t the first one to talk about it though. Going back to the 1930’s, Alfred Korzybski talked about how maps represented something, and could be very useful, but they’re not the same thing.

A map is not the territory it represents, but, if correct, it has a similar structure to the territory, which accounts for its usefulness.

        – Alfred Korzybski

Besides being a representation of a single point in time, in the past, maps don’t have all the details. Depending on how closely the intended use of a map is with how you’re using it those details could be crucial. If you’re out hiking in the bush, even the most detailed road map won’t help you know elevation. Conversely, a topographic map is great for hiking in the mountains, but not very good if you need to know which freeway to take between two cities. And that doesn’t even consider that the map might just be wrong.

A lot of software development is based on models and mental maps of the various domains. From Machine Learned models (Uber’s ETA or pricing models, ChatGPT and the various LLMs, etc) to expert based heuristics (financial fraud detection algorithms, alerting on operational metrics, etc), to something as seemingly simple as the state machine for bug tracking, we use maps and models to help us understand the dependencies and interactions between systems. The better the model (dependencies), the more accurate the predicted results (interactions), and the better we can use them to drive system behavior in the direction we want. Get it wrong and we end up with the cobra effect and things get worse.

Another way we use maps is data schemas. Schemas are maps of the structure of how the data is expected to fit together. We use the schema to store the data. We use the schema to drive how we process the data. We use the schema to define how we accept input and provide output. The closer the map (schema) is to the territory (the actual data and its structure) the more useful the map is. If the schema doesn’t match the structure, then you find people working around the system instead of with the system. Using and changing things becomes even harder than it would have been.

With all of that said, maps and models are useful and important. They reduce cognitive load. They easy communication. They let us get results without having to simulation every air molecule flowing over a wing in a continuous stream. You just need to remember that the model might be useful, but it’s wrong in one or more ways, and that the structure of the map is helpful, but you can’t really travel by map.

by Leon Rosenshein

Mea Culpa, Mea Maxima Culpa

I goofed. One of the first things to do when you make a mistake is to acknowledge you were wrong. Back in September of 2021 I wrote a piece called Measurement where I listed a bunch of management quotes and said that it’s important to use the whole quote, not take part of the quote out of context. I believed that when I said it then, and I still feel that way now. However, I’ve recently been made aware that many of the quotes I’ve attributed to Peter Drucker weren’t actually said by him. I did a little digging, and if you can’t believe the Drucker Institute on what he didn’t say, who can you believe?

In this case, the quote was

What gets measured gets managed - even when it’s pointless to measure and manage it, and even if it harms the purpose of the organization to do so.

That was actually said by a someone commenting on a 1956 paper that criticized the first part of the statement. Which was the point of my post back in 2021, so I can feel good that I got that part right at least.

It turns out that what Drucker actually said was

Unless we determine what shall be measured and what the yardstick of measurement in an area will be, the area itself will not be seen.

That’s something different. Yes, it’s related to measurement, but has nothing to do with how people will respond to knowing that something is being measured. It has nothing to do with whether or not it’s something you should be measuring. What it’s saying is that you need to pick not only what you’re going to measure, but how you’re going to measure it. If you get either one of those wrong, you’re not going to see the reality of the situation you’re looking at.

If you’re not measuring what you think you’re measuring, what are you measuring? How are those measurements going to influence what people do? People work to control what is being measured. That’s a very common thing for people to do. However, if you’re measuring the wrong thing, you’re very likely to get a result that doesn’t move you towards the goal you said you were trying to achieve.

Consider measuring productivity. What does productivity mean? Sure, you could measure lines of code written, but that’s activity, not productivity. Measure lines written and you’ll get more lines written. Not necessarily good lines. Not efficient lines either. And there’s very little incentive to remove things that are bad. Probably not the result you want to achieve. You could measure tasks done, but that leads to lots of little tasks. Now taking many more smaller steps, and re-evaluating along the way, is a good thing, artificially dividing things up into tiny tasks and then just blindly doing them doesn’t get you a better result. Just because a task is done, was any value created or shared? And what does done mean anyway? A better way to measure productivity would be to somehow compare value delivered to users in a given time period. Of course, that’s easy to for me to say, but hard for you to determine. It’s hard because it depends on the context that you and your users are operating in. Get it wrong and you don’t get any visibility into the area you’re trying to measure.

Which is what Peter Drucker did say.

by Leon Rosenshein

Permission, Approval, and Feedback

There are endless debates online about the relative merits of code reviews/pull requests vs Ensemble/Mob/Pair programming. I’m not going to talk about any of that. You can read about it at one of more of those links.

I’m going to talk about the world that many (most?) developers live in. Requirements are identified. Designs are done. Work is discovered. The work is turned into tasks. Tasks are prioritized. Tasks are selected by / assigned to an individual. The individual does the work in semi-isolation. There is a code review/pull request process to follow before the code is merged into the main branch and then deployed. There are lots of variants on that, such as requiring design docs, automated tests, code coverage checks, required reviewers, sprint planning, time boxing, and such, but that’s the basic flow.

At each of those stages, there is some kind of gate to get out of the current state and into the next one. That’s where the differences between permission, approval, and feedback become very important. At many places you are required to have someone sign off on the work done. There are tools and processes in place that ensure you meet the requirement. That’s permission. To follow the official process, you need someone else to put a stamp on your work and say it’s OK. Sometimes there is even a defined person or set of people who are allowed to say it’s ok. They also have the power to say no and then you can’t move forward.

The next level is approval. Approval is someone saying, “I can’t really stop you, but I think that’s OK.” Approval is nice, but it’s not required, and it doesn’t really mean anything.

Then there’s feedback. Feedback is really interesting. It can range from silence to “LGTM” (Looks good to me), to “This is a great approach and simplifies a complicated problem.”, to “This is the exact wrong approach, and will make things harder in the future.”, to a more generic “Here’s another approach that might make things easier.”, or “Have you thought about how this change will impact X? I think there’s some interaction there.” The thing about feedback is that while it’s a gift, you don’t have to take it. If you’ve got the required permission, then you can ignore the feedback and move on.

That’s the rub. The most useful information is often in the feedback. The part you’re most likely to ignore. Especially when the feedback comes from an unexpected place. If someone you didn’t expect to care, like a customer, user, or some team that thinks about non-functional requirements (like security, UX, QA, PM, Finance, etc.) takes the time to give you feedback, that’s probably the most useful information you’re going to get.

Those apparently unrelated folks have a very different viewpoint than you do. They approach the problem from a different place and are focused on their area. If someone from Finance says that they’re concerned about how this change is going to impact the bottom line, or the security team is concerned that there’s an uncovered vulnerability in the design, then there’s a problem. There’s at least a problem with the work you’ve done in that it doesn’t explicitly state that you’ve thought that issue through and have a plan/mitigation in place. More likely, they’re pointing out a blind spot in your thinking and it’s something you need to go think through and make sure it’s addressed.

In a perfect world, the folks that are giving you permission are paying attention to the feedback, and it’s good to have another set of eyes to make sure the feedback is addressed, but it’s not their job. It’s your job, as the person doing the work, to take the feedback, internalize it, and respond to it.

by Leon Rosenshein

Any fool can write code the computer understands.

Any fool can write code that a computer can understand. Good programmers write code that humans can understand.

    – Martin Fowler

I’ve talked about that quote, and coding for the maintainer before. I’ve mentioned Martin Fowler even more often. However, almost all of those have been more than a year ago, and it’s an evergreen topic.

Over the last 40+ years I’ve written code in lots of languages. Various flavors of Ada, assembly, Bash, Basic, C/C++, Cobol, C#, Forth, Fortran, Java, JavaScript, F#, Pascal, Perl, PHP, PL/1, and Python, to name a few. In all of those languages you can write code that, if you don’t touch it for a few months, you look at it and wonder what idiot wrote it.

With most of them though, if you take your time, think things through, and decompose things, its relatively easy to write code that at least the author can come back to later and understand what’s going on.

The one language that gave me the most grief though, was VBA in Excel. And the place that it gave me the most grief was in the Master Block Tracking (MBT) spreadsheet, back on the Global Ortho project. Imagine a spreadsheet with ~30 tables, the largest of which was about ~1500 rows and 75 columns. All to produce a single table that showed what work was in progress, what work was blocked, and what work had been recently finished. It worked, but it was a nightmare.

The reason it was a nightmare was not because it grew organically. It wasn’t because the requirements were unreasonable, or unclear. It wasn’t because the language didn’t support what I was trying to do. The reason it was a nightmare was because of the sunk cost fallacy and code that humans couldn’t read.

First, and most importantly, we used the wrong tool. We should have dropped Excel and moved to a website. That would have the problems of tiering, access control, change management, and making sure everyone had the latest version of things. But we didn’t re-evaluate the goals and work backwards. We just adapted the current system to work.

Second, the original, manually updated MBT had a set of auxiliary tables that the production team would update with the current state of the various processing pipelines. Then the MBT would do all the calculations in a combination Visual Basic for Applications (VBA) and calculated cells. And we didn’t change that. Instead of stepping back and redesigning things into a properly tiered architecture with a data layer, a business logic layer, and a display layer, everything other than data storage got mashed into spreadsheet.

Even if we wanted to use Excel as the display engine so that there could be offline analysis, we should have changed the design. We had calculated cells that lookups on top of lookups and then tried to calculate state. Instead of building intermediate tables based on the business logic and loading them into the spreadsheet, every cell did the logic needed and calculated the intermediate values locally. When there are 15 (yes, 15) closing parentheses at the end of the equation for a calculated cell, your code is essentially unreadable. And we had lots of different cells that had equations like that.

Every time we added a state, or changed the definition of a state, or had to handle a new edge case as we re-processed data, I would spend hours reacquainting myself with what I had done and understanding how things worked before I could start making the changes needed. For the last 9 months, I was the only one who could figure out what was going on and how to change things. Let me tell you, being indispensable is NOT a place you want to be. You might be needed for that thing, but you never get to do anything else.

Fred from Scooby Doo unmasking the villian and seeing himself

Providing a way for our stakeholders to understand, at a glance, how things were progressing in the project, what they could expect to see released soon, and where the bottlenecks were is something that was essential to finishing the project and I’m still very proud of. The code behind that tool, on the other hand, is something I’m not very proud of. Because while the computer could understand what I had written, no one else, even me, could. That’s never a good situation to be in. I spent too many hours maintaining the system. Hours that should have been spent making things better instead of maintaining the status quo.

Now, when I run into a situation like that, I take the time to make things better. To make sure I’m using the right tool for the job. To make things more readable. To make updates easier. So that when I have to go back into something I don’t end up wasting a lot of time figuring out how and why I did something. Smaller functions. Stored intermediate values. Making the implicit explicit. So us poor humans can understand what’s going on.

by Leon Rosenshein

Enter The Conversation

“Always enter the conversation already taking place in the customer’s mind.” ~Robert Collier

Interesting quote. It comes from the sales and marketing world. Basically, it says that your message will be heard better/clearer if what you’re saying fits into what your customer is already thinking than if it’s disconnected.

That makes sense. Sure, sometimes a cold call really does pique your interest and get you to follow up. The more common response to a cold call, however, is to just say “Thanks. I’ll think about it.”, then promptly forget about the entire encounter. Which, as a marketer, is the exact opposite of the desired response.

It’s a good point, and it explains why targeted marketing is a thing and why the online advertising market is so big. Advertising your sneakers to someone who appears to be looking for sneakers has a much higher likelihood of turning into a sale than advertising your sneakers to someone who’s sad and is looking for a funny cat video to cheer them up.

What has that got to do with software though? Of course, it applies to marketing software. But it applies to UI design. And documentation. And API design.

Why are people using your API? They’re (probably) not using your API because they like you and using it is fun. They’re using it to get a job done. That’s where they are.

That means that your API is a conversation with your customer and that the goal of the conversation is to get the job done. Your API should be written in a way that makes it easier to get the job done. It should be consistent. It should speak your customer’s language. It should hide complexity, not create it.

The key is having the conversation at the right level of abstraction. Let’s say you’re writing an API that a website can use to display and manage a user’s travel schedule. The API you build will be somewhere between a single function that lets the user make a single SQL call and a single function that returns the HTML to be rendered. You could do either one of those, but the former is really the API you’re going to call when you write a new API, and the latter is what the website is supposed to do.

Instead, think of it from the user’s point of view. What do they know, and what do they want to do? They know the end user they’re dealing with. They know if they want to display the existing trips, show the detail for a specific trip, add a trip, or possibly change a trip. That’s the information that tells you what your API should do. It also gives you good info into what you should call the functions, what the arguments are, and what the return values should be. That’s the conversation you’re having with your customer. In their terms, in their language.

Just don’t forget Hyrum’s Law 😄

by Leon Rosenshein

Let Me Tell You What I Want

I’ve talked about user stories and ISBAT before. The idea that a user story should be focused on something the user wants to accomplish. The acceptance criteria (AC) should show how the result adds value to the user. While the AC’s are about the definition of done, they’re not about the steps along the way.

Very often however, user stories, especially on backlogs and on teams that work as groups of individuals, turn into tasks. One of the biggest reasons for that is that while ISBAT makes it clear what the end state is, it doesn’t really define the goal. Consider this user story.

As a customer, I should be able to check out.

The user is defined. The end state is defined. What more could you want in a user story? Consider this slightly extended user story.

As a customer, I should be able to check out, so that I get the product I picked.

The beginning of the story is exactly the same. The only change is adding the so that part at the end. It might not even change how you end up implementing the story. And if it doesn’t change anything, why write it down? It’s just syntactic noise with no added value.

However, there is added value. It’s slightly hidden, but there’s actually a lot of value there. The first version of the story is really just a task for the implementer. The second version is a task, with but with costs and benefits for the user stated. The benefit is that the user gets the product they picked. The cost is that they have to check out. When you recognize that you’ve but a cap on how much the “cost” to the customer can be. It has to be less than the value they receive. If the cost is less than the value, then you have a happy (and hopefully repeat) customer. If it’s higher you might have a one-time customer because of their sunk cost (they’ve already picked the product and done whatever else, so it’s worth it at that point), but you’re not going to have a repeat customer. From the customer’s standpoint there’s no value proposition.

Your goal, when coming up with user stories, is to ensure the story highlights the user’s value proposition, without constraining the folks doing the implementation. It lets the implementors working backwards from the user’s value proposition and maximize it.

If instead, you use this version of the user story,

As a customer, I should be able to get the product I picked.

you empower the folks implementing the solution to come up with a one-click solution. If you already know the user’s payment and shipping information you can have two buttons on the web page. One has the traditional checkout flow. Verify quantities, payment info, shipping info, and whatever else you need. And you can have a second one that says something like “Instant Purchase”, which takes those defaults, processes the order, and responds with a order ID and a tracking number.

That adds a lot of value to the user. It gives them the choice to pay the price in time and effort to go through the standard flow, or to save that time and effort. To have way to make the purchase with time and energy cost. It also gives the user more control over the process. Which adds more value.

Which is why you need to be care to not only make your user stories actually be user stories, not tasks, but also they need to be focused on adding value and improving the user’s cost/benefit ratio.