John Ousterhout, professor of computer science at Stanford University, joined SE Radio host Jeff Doolittle for a conversation about his book, A Philosophy of Software Design (Yaknyam Press). They discuss the history and ongoing challenges of software system design, especially the nature of complexity and the difficulties in handling it. The conversation also explores various design concepts from the book, including modularity, layering, abstraction, information hiding, maintainability, and readability.
From the Show
- John Ousterhout’s Profile | Stanford Profiles
- A Philosophy of Software Design (Yaknyam Press)
- On the Criteria to Be Used in Decomposing Systems Into Modules by David L. Parnas
- Software Fundamentals: Collected Papers by David L. Parnas
- Managing Technical Debt: Reducing Friction in Software Development (SEI Series in Software Engineering)
- Philosophy of technology in engineering education
- Teaching philosophy to engineering students
- Philosophy matters in engineering studies
- Special session – Teaching philosophy in engineering courses
- Decoding Software Design
- Design Strategy and Software Design Effectiveness
- Teaching a Software Design Methodology
- The Fog of Software Design
- Teaching software design with open source software
- Episode 481: Ipek Ozkaya on Managing Technical Debt
- Episode 387: Abhinav Asthana on Designing and Testing APIs
- SE-Radio Episode 333: Marian Petre and André van der Hoek on Software Design
- SE-Radio Episode 331: Kevin Goldsmith on Architecture and Organizational Design
- Episode 51: Design By Contract
Transcript brought to you by IEEE Software magazine.
This transcript was automatically generated. To suggest improvements in the text, please contact [email protected] and include the episode number and URL.
Jeff Doolittle 00:00:16 Welcome to Software Engineering Radio. I’m your host, Jeff Doolitle. I’m excited to invite John Ousterhout as our guest on the show today for a conversation about his book, a philosophy of software design, John Ousterhout is a professor of computer science at Stanford university. He created the TCL scripting language and the TK platform independent widget toolkit. He also led the research group that designed the experimental Sprite operating system and the first log structured file system, and is also the co-creator of the raft consensus algorithm. John’s book, A Philosophy of Software Design, provides insights for managing complexity in software systems based on his extensive industry and academic experience. Welcome to the show, John.
John Ousterhout 00:00:59 Hi, glad to be here. Thank you for inviting me.
Jeff Doolittle 00:01:01 So in the book there’s 15 design principles, which we may not get to all of them and we’re not going to go through them linearly, but these each come out through various discussions about complexity and software system decomposition. But before we dig deeply into the principles themselves, I want to start by asking you, we’re talking about design styles. So, is there just one good design style or are there many, and how do you kind of distinguish those?
John Ousterhout 00:01:25 It’s a really interesting question. When I started writing the book I wondered that myself, and one of the reasons for writing the book was to plant a flag out there and see how many people disagreed with me. I was curious to see if people would come to me and say, show me “no, I do things a totally different way,” and could actually convince me that, in fact, their way was also good. Because it seemed possible. You know, there are other areas where different design styles all work well; they may be totally different, but each works in its own way. And so it seems possible that could be true for software. So I have an open mind about this, but what’s interesting is that as the book’s been out there a few years and I get feedback on it, so far I’m not hearing anything that would suggest that, for example, the principles in the book are situational or personal and that there are alternate universes that are also valid. And so, my current hypothesis — my working hypothesis — is that in fact there are these absolute principles. But I’d be delighted to hear if anybody else thinks they have a different universe that also works well. I haven’t seen one so far.
Jeff Doolittle 00:02:24 Well, and just that mindset right there, I want to highlight as, you know, someone who does design that it’s more important that you put your ideas out there to be invalidated because you really can’t ever prove anything. You can only invalidate a hypothesis. So I love that was your attitude with this book too. You may say things that sound axiomatic, but you’re really putting out a theory and asking people and inviting critical feedback and conversation, which is really the only way the discovery of human knowledge works anyway. So in the software development life cycle, when do you design?
John Ousterhout 00:02:53 Oh boy, that’s, that may be the most fundamental question in all of software design. Well, as you know, there are many, many approaches to this. In the extreme, you do all your design up front. This has sometimes been caricatured by calling it the waterfall model, although that’s a bit of an exaggeration, but in the most extreme case, you do all design before any implementation. And then after that, the design is fixed. Well, we know that approach doesn’t work very well because one of the problems with software is these systems are so complicated that no human can visualize all of the consequences of a design decision. You simply cannot design a computer system up front — a system with any size — and get it right. There will be mistakes. And so you have to be prepared to fix those. If you’re not going to fix them, then you’re going to pay tremendous costs in terms of complexity and bugs and so on.
John Ousterhout 00:03:38 So you have to be prepared to do some redesign after the fact. Then there’s the other extreme. So people have recognized it that we should do design in more of an iterative fashion, do a little bit of design, a little bit of coding, and then some redesign, a little bit more coding, and that can get taken to the extreme where you essentially do no design at all. You just start coding and you fix bugs as a sort of design by debugging. That would be maybe an extreme caricature of the agile model. It sometimes feels like it’s becoming so extreme that there’s no design at all and that’s wrong also. So the truth is somewhere in between. I can’t give you a precise formula for exactly when, but if you do a bit of design up to the point where you really can’t visualize what’s going to happen anymore.
John Ousterhout 00:04:20 And then you have to build and see the consequences. And then you may have to go and design. Then you add on some more parts and so on. So I think design is a continuous thing that happens throughout a life, the lifecycle project. It never ends. You do some at the beginning. It’s always going on as subsystem become more mature. Typically you spend less and less time redesigning those. You’re not going to rebuild every subsystem every year, but recognize the fact that you may someday discover that even a very old system that you thought was perfect, that had everything right. Actually now no longer is serving the needs of the system. And you have to go back and redesign it.
Jeff Doolittle 00:04:57 Are there some real-world examples that you can pull from, that kind of demonstrate this process of design or maybe things that have happened historically that sort of reflect this, revisiting of design assumptions previously and then tackling them in a different way over time or refining designs as we go.
John Ousterhout 00:05:13 Great question. I can answer a slightly different question, which my students often ask me, which is how many times does it take you to get a design right?
Jeff Doolittle 00:05:21 Okay.
John Ousterhout 00:05:21 It’s not quite the same question. So my experience is when I design something, it typically takes about three tries before I get the design, right? I do design, first design, and then I start implementing it and it typically falls apart very quickly on implementation. I go back into a major redesign and then the second design looks pretty good, but even that one needs additional fine tuning over time. And so the third iteration is fine tuning. And then once you have that then systems, I think then those classes or modules tend to stand the test of time pretty well. But now your question was that there’s something where you have a module that really worked well.
Jeff Doolittle 00:05:57 I don’t even necessarily mean software by the way, right? Like, maybe real world or examples of how iterations and designs have changed and had to be revisited over time.
John Ousterhout 00:06:08 Well, I think the classic cause of that is technology change. When the underlying technologies for how we build something change often that will change what designs are appropriate. And so, for example, in cars, we’re seeing this with the advent of electrical vehicles, that’s changing all sorts of other aspects of the design of cars, like the structure of the car changes now, because the main structural element is this battery that lives in this very flat heavy thing at the bottom of the car that has fundamental impact on the design of the car. Or another example is the rise of large screen displays. And now we’re seeing the instrument clusters in cars changing fundamentally because there’s this large display that is, is replacing a lot of other stuff. And of course in computers, you know, we’ve seen design change with, with radical new technologies. The advent of the personal computer caused a whole bunch of new design issues to come along and the arrival of networks and the web again, changed a whole bunch of design issues. So technology, I think has a very big impact on design.
Jeff Doolittle 00:07:09 Yeah. And you mentioned cars, you know, if you think about the last hundred and what’s it been 140 years, maybe since the first bespoke automobiles were created and the technology certainly has changed from horses and buggies or horseless carriages to what we have now. And I think definitely software is, is experienced that as well. You know, now with distributed Cloud technologies, that’s just a whole another rethinking of how things are designed in order to tackle the challenges of complexity on complexity. Distributed systems in the Cloud seem to introduce. So speaking of complexity, there’s a few principles in the book that specifically relate to complexity. So in your experience, you know, you’ve said a few things like, for example, we need to recognize the complexity is incremental and you have to sweat the small stuff. And you mentioned another principle of pulling complexity downward. So first maybe speak a little bit about the nature of complexity and how that affect software systems. And then let’s explore these design principles in a little more detail.
John Ousterhout 00:08:05 Yes. So first let me first make clear about what I think is the uber principle. You know, the one principle to rule them all, is complexity. That to me is what design is all about. The fundamental weíre trying to build systems, that limit their complexity. Because the reason for that is that, the only thing that limits, what we can build in software is complexity. Really that’s the fundamental limits, our ability to understand the systems, the computer systems will allow us to build software systems that are far too large for us to understand. Memory sizes are large enough, processes are fast enough. We can build systems that could have tremendous functionality. If only we could understand them well enough to make these systems work. So everything is about complexity. So by the way, all of the principles in the book are all about managing complexities complexity. And I would also say that if you ever get to a point where it seems like one of these principles, I put forward conflicts with complexity, with managing complexity, go with managing complexity.
John Ousterhout 00:09:03 Then the principle is a bad principle for that situation. I just want to say before we start, that’s the overall thing. So everything else relates to that in some way. Then the second thing, I think the thing that’s important to realize about complexity is that it is incremental. That is it isn’t that you make one fundamental mistake that causes your systems complexity to grow without doubt it’s, it’s lots of little things and often things that you think this isn’t that big of a deal. I’m not going to sweat this issue. It’s only a little thing. Yeah, I know it’s a kludge, but it’s not really big. This won’t matter. And of course, no one of them matters that’s true. But the problem is that you’re doing dozens of them a week and each of the hundred programmers on your project is doing dozens of them a week and together they add up. And so what that means is that once complexity arises also, it’s extremely difficult to get rid of it because there’s no single fix there. Isn’t one thing you can go back and change that will rid of all that complexity, that’s accumulated over the years. Youíre going to change hundreds or thousands of things, and most organizations don’t have the courage and level of commitment to go back and make major changes like that so then you just end up living with it forever.
Jeff Doolittle 00:10:13 Well, and you mentioned before the human propensity to go for the short term, and I imagine that has a significant impact here as well. So you say complexity is incremental, you have to sweat the small stuff. So how much sweating is appropriate and how do you avoid say analysis paralysis or, I don’t know. I just imagine people saying there’s, they’re concerned that all progress will halt. If we stop to worry about the incremental addition of complexity. How do you fend that off or deal with that?
John Ousterhout 00:10:41 First? I’m sure people make those arguments. I’m sure a lot of people say to their bosses, well, do you want me to go back and clean up this code? Or do you want me to meet my deadline for this Friday? And almost all bosses will say, okay, I guess we have the deadline for this Friday. The question I would ask is how much can you afford? Think of it like an investment. That you’re going to spend a little bit more time today to improve the design, to keep complexity from creeping in, and then in return, you’re going to save time later. It’s like this investment is returning interest in the future. What I would argue is how much I, how much can you afford to invest? Could you afford to let yours slip 5 or 10 percent? Every schedules going to 5 or 10% slower than, but we’re going to get a much better design. And then the question is will that maybe that will actually gain you back more than 5 or 10%.
John Ousterhout 00:11:29 Maybe with that better design, you’ll actually run you’ll code twice as fast in the future. And so it has more than paid for itself. Now the challenge with this argument is no one’s ever been able to quantify how much you get back from the good design. And so, I believe it’s actually significant, far more than the cost, the extra cost of trying to make your design better. And I think many people believe that, but no one’s been able to do experiments that can prove that maybe that’s also another run of one of the reasons why people put off doing the design, because I can, I can measure the 5% slip in my current deadline. I can’t measure the 50% or hundred percent faster coding that we get in the future.
Jeff Doolittle 00:12:09 Yeah. And this is where I start to think about characteristics like quality, because from my perspective, a quality problem is when you’re having to worry about something that you shouldn’t had to worry about. So you mentioned cars before, right? What’s a quality problem in a car? Well, there’s something that is now your concern as a driver that should not be your concern. But what’s interesting too, is there’s scheduled maintenance for a car. And so putting that off for too long is going to lead, not to a quality problem because of the manufacturer, but it’s going to lead to a quality problem because of your negligence. And I wonder if you think a similar thing applies to software where this, if we’re negligent, maybe we can’t immediately measure the effects of that, but downstream, we can measure it in terms of pain.
John Ousterhout 00:12:51 I still fear it’s hard to measure it, but I agree with the notion of scheduled maintenance. I understand there are practical reality. Sometimes some things just have to get done and get done fast, you know, a critical bug that has your customers offline. They’re not going to be very comfortable with this argument that, well, it’s going to take us a couple of extra weeks because we want to make sure our design is good for our projects two years from now. So I recognize that I understand people have to work under real world constraints, but then I would say, try and find sometimes some budget where later on, people can come back and clean things up after you hit the deadline. Maybe the next week is used to clean up some of the problems that you knew had introduced at the last minute or some fraction of your team. Five of 10% their job is do code clean-ups rather than writing new code. It’s not an all or nothing. You don’t have to stop the world and argue, you don’t have to do heroics to have great design. It’s just in the same way that complexity builds up piece by piece. You can do good design piece by piece, lots of little steps you take along the way to make the design a little bit better. You don’t have to fix everything all at once.
Jeff Doolittle 00:14:00 So that’s the incremental factor. Meaning complexity is incremental, but sounds like you’re saying we can also incrementally address it as we go. So another principle regarding complexity, you mentioned pulling complexity downward. Can you explain a little bit more about what that means and how people apply that principle?
John Ousterhout 00:14:16 Yes, actually I originally had a different name for that. I called it the martyr principle.
John Ousterhout 00:14:24 People tell me that was a little bit too inflammatory maybe thatís why I took it out. But I still like it, the basic idea, Iím not referring to religious jihad when I say martyr. Iím thinking of a definition where a martyr is someone who takes suffering on themselves so that other people can be happier and live a better life. And I think of that’s our job as software designers that we take these big gnarly problems and try and find solutions to them that are incredibly simple and easy for other people to use. And actually, honestly, I don’t think of it as suffering. It’s actually what makes software fun is solving those hard problems, but this idea that pull the hard problems downward as opposed to the other philosophy is, well as a programmer, I’m just going to solve all the stuff that’s easy. And then I’ll just punch upwards all the other issues. A classic example is just throwing tons of exceptions for every possible, slightly strange condition, rather than just figuring out how to handle those conditions. So you don’t have to throw an exception. And so, and this gets back to managing complexity again. So the idea is that we want to somehow find ways of hiding complexity. So if I can build a module that solves really hard, gnarly problems, maybe it has to have some complexity internally, but it provides this really simple, clean interface for everybody else in the system to use. Then that’s reducing the overall complexity of the system. Cause only a small number of people will be affected by the complexity inside the module.
Jeff Doolittle 00:15:53 Yeah, that sounds very similar to what one of my mentors calls technical empathy.
John Ousterhout 00:15:58 I can guess what the meaning of that is. I like the idea. Yes.
Jeff Doolittle 00:16:01 Yes. Which personally I call the Homer Simpson principle where there’s this wonderful, and you can find a gift of it online somewhere or not a gift, but a short YouTube video of Homer Simpson with a bottle of vodka in one hand and a bottle of mayonnaise’s in the other. And Marge says, I don’t think that’s such a good idea. And he says, oh, that’s a problem for future Homer, but I don’t envy that guy. And he proceeds to consume the mayonnaise and vodka. And so the irony is, you know, you mentioned carrying the suffering, which of course in this case can be fun. Carrying the complexity yourself, right? Embracing the complexity yourself on behalf of others. So they don’t have to experience it ironically, a lot of times when you don’t do that, you’re not having technical empathy for your future self, because you’re going to come back and say, oh, I wrote this and then you end up carrying the pain anyway.
John Ousterhout 00:16:47 Actually another great example of that is configuration parameters. Rather to figure out how to solve a problem, just export 12 dials to the user say, and then, and not only are you punting the problem, but you can say, oh, I’m actually doing you a favor, because I’m giving you the ability to control all of this. So you’re going to be able to produce a really great solution for yourself. But oftentimes I think the reason people export the parameters is because they don’t actually have any idea how to set them themselves. And they’re somehow hoping that the user will somehow have more knowledge than they do, and be able to figure out the right way to set them. But more often than not, in fact, the user has even less knowledge to set those than the designer did.
Jeff Doolittle 00:17:24 Oh yeah. And 12 parameters, you know, 12 factorial is somewhere in the tens of billions. So good luck figuring it out, you know. Even with seven there’s, 5,040 possible combinations and permutations of those. So yeah. As soon as you export, you know, seven configuration parameters to your end user, you’ve just made their life incredibly challenging and complex.
John Ousterhout 00:17:42 That’s an example of pushing complexity, upwards.
Jeff Doolittle 00:17:45 Hmm. That’s perfect.
John Ousterhout 00:17:45 Me solve the problem? I force my users to solve it.
Jeff Doolittle 00:17:48 Yeah. And you also mentioned in there exceptions and just throwing exceptions everywhere, which relates to another one of the design principles, which is defining errors and special cases out of existence. So what are some examples of how you’ve applied this or seen this principal applied?
John Ousterhout 00:18:02 So first I need to make a disclaimer on this one. This is a principle that can be applied sometimes. But I have noticed, as I see people using it, they often misapply it. So let me first talk about how you kind of apply it, then we can talk about how it was misapplied. Some great examples, one of them was the unset command in the Tickle script language. So Tickle has a command Unset that creates to a variable. When I wrote Tickle, I thought no one in their right mind would ever delete a variable that doesn’t exist. That’s got to be an error. And so I threw an exception whenever somebody deletes a variable that doesn’t exist. Well, it turns out people do this all the time. Like the classic examples, you’re the middle of doing some work. You decide to abort, you want to clean up and delete the variables, but you may not know, remember, you may not know exactly which variables have been created or not. So you just go through and try and delete them all. And so what’s ended up happening is that if you look at Tickle code, virtually every unset command in Tickle is actually encapsulated inside a catch command that will catch the exception and throw it away. So what I should have done was simply redefine the meaning of the unset command, change it, instead of deleting a variable. It’s the new definition, is make a variable not exist. And if you think about the definition that way, then if the variable already doesn’t exist, you’re done, there’s no problem, itís perfectly natural. Thereís no error. So that just defines the error out of existence. An even better example I think is, deleting a file.
John Ousterhout 00:19:30 So what do you do if somebody wants to delete a file when the fileís open? Well, Windows took a really bad approach to this. They said you canít do that. And so if you use the Windowís system, you’ve probably been a situation where you tried to delete a file or a program tried to delete a file and you get an error saying, sorry, can’t delete file, files in use. And so what do you do? Then you go around, you try and close all the programs that maybe have that file open. I’ve been at times I couldn’t figure out which program had the file open. So I just had to reboot, hard to delete the file. And then it turn out it was a demon who had the file open and the demon got restarted. So Unix took a beautiful approach to this, itís really a lovely piece of design. Which is they said, Well itís not problem. You can delete a file when itís open, what weíll do is we’ll remove the directory entry. The file is completely gone as far as the rest of the world is concerned. Weíll actually keep the file around as long as someone has it open. And then when the last process closes the file, then weíll delete it. That’s a perfect solution to the problem. Now people complain about Windows. There has been changes made over the years. And I don’t remember exactly where Windows stands today, but at one point they had modified it
John Ousterhout 00:20:43 So that in fact, you could set a flag saying, it’s okay to delete this file while it’s open. And then Windows would do that, but it kept the directory entry around. And so you couldn’t create a new file until the file had finally been closed. And once the file was closed, the file would go away. The directory entry would go away. So a lot of programs like make which, you know, remove a file and then try and recreate. They wouldn’t work. They still wouldn’t work if the file was open. So they just kept defining errors, creating new errors, that cause problems for people. Whereas Unix had this beautiful solution of just eliminating all possible error conditions.
Jeff Doolittle 00:21:17 Well, and that is right back to pulling complexity downward because what do exceptions do they bubble upward? So by allowing them to bubble up, you’re violating that previous principle that we discussed.
John Ousterhout 00:21:27 Now I need to do a disclaimer so that people donít make a lot of mistake. I mentioned this principle to students of my class, so Iím actually at the point now where I may even stop this mentioning to students, because for some reason, no matter how much I disclaim this, they seem to think that they can simply define all errors out of existence. And in the first project for my class, inevitably, it’s a project building a network server where there are tons of exceptions that can happen. Servers crash, network connections fail. There will be projects that do not throw a single exception or even check for errors. And I’ll say, what’s going on here? And they’ll say, oh, we just defined those all out of existence. No, you just ignored them. That’s different. So, I do want to say errors happen, you know, most of the time you have to actually deal with them in some way, but sometimes if you think about it, you can actually define them away. So think of this as a spice, know that you use in very small quantities in some places, but if you use it too much, end up with something that tastes pretty bad.
Jeff Doolittle 00:22:35 Yeah. And I remember one of the, you know, early mistakes that a lot of programmers make when they first get started is empty catch blocks. And when you see those littered throughout the code, that is not what you mean when you’re saying systems. You’re not saying swallow and ignore, define, I don’t think this is one of the design principles, but it triggers in my thinking as well. That if there is an exceptional condition, you do want to let it fail fast. In other words, you want to find out and you, you want things to stop functioning, like bring it down. If there’s an exception and then figure out how to keep it from coming down in the first place, instead of just pretending nothing went wrong.
John Ousterhout 00:23:13 Well, this gets in another important thing. One of the most, I think one of the most important ideas in doing design, which I think is true in any design environment, software or anything else is you have to decide what’s important and what’s not important. And if you can’t decide, if you think everything is important, or if you think nothing’s important, you’re going to have a bad design. Good designs pick a few things that they decide are really important. And they emphasize those. You bring those out, you don’t hide them. You probably present them up to users. And so when software designs, the same thing. If an exception really matters, you probably do need to do something. You probably do need to pass it back to user. You probably want to highlight it, make it really clear if this thing happen. And then other things that are less important than those are the things you try and hide or encapsulate inside a module so that nobody else has to see them. The thing I tell my students over and over again is what’s important. What’s the most important thing here? Pick that out and focus your design around that.
Jeff Doolittle 00:24:05 Yeah. That, and as you mentioned previously, what can I do to handle this exceptional condition right here, instead of passing it further on, especially in a case where, like you mentioned, even in your design of Tickle where the exception really shouldn’t be happening. Because if the outcome is item potent, meaning performing the same action twice returns in the same outcome, then why is that an exceptional condition?
John Ousterhout 00:24:26 Right. Why should it be yep.
Jeff Doolittle 00:24:27 And then why should you pass that up? Because you’re just giving people useless information that they can’t do anything about.
John Ousterhout 00:24:32 Yes. I made something important that was not really important. That was my error.
Jeff Doolittle 00:24:37 Yes, yes. Yeah. And now I think that’s a big risk when we’re designing systems that we can fall into that trap. So it’s a good thing to watch out for. Maybe that’s and by the way, don’t make unimportant things important
John Ousterhout 00:24:48 And vice versa. So one of the mistakes people make in abstraction is they hide things that are important. But don’t expose things that are really important. And then the module becomes really hard to use because you can’t get at the stuff you need. You donít have the controls you need, youíre not aware of the things you need. So again, itís all about, itís a two-day street. Where either you emphasize whatís important, donít hide that. And then hide whatís unimportant. And by the way ideally, the best designs have the fewest number of things that are important, if you can do that. But it’s like, Einstein’s old saying about everything should be as simple as possible, but no simpler. Again, you can’t just pretend something’s unimportant when it really is, you have to figure out what really is important.
Jeff Doolittle 00:25:30 That’s right. And that takes creativity and effort, it doesn’t just magically come to you out of thin air.
John Ousterhout 00:25:35 Yeah. And insider experience too, in terms of knowing how people are going to use your system.
Jeff Doolittle 00:25:40 Yeah, I think that’s important too. Insider experience, as it pertains to design is going to be important. When you’re first getting started, you’re going to have more challenges, but the longer you do this, I imagine I’m assuming this is your experience as well, it does become somewhat easier to design things as you go when they’re similar to things you’ve experienced before.
John Ousterhout 00:25:57 It does. One of the things I tell my students, I tell them, if you’re not very experienced, figuring out what’s important is really hard. You donít have the knowledge to know. And so then what do you do? And so what I tell people is make a guess, don’t just ignore the question, think about it, make your best guess and commit to that. It’s like form hypothesis. And then test that hypothesis, you know, as you build the system, see was I right or was I wrong? And that act of committing, make a commitment. This is what I believe, so far and then testing it and then learning from it. That’s how you learn. But if you don’t ever actually make that mental commitment, I think try and figure it out, make your best guess, and then test that. Then I think it’s hard to learn.
Jeff Doolittle 00:26:45 Right. And what you’re saying there, I think is more than just test your implementation. It’s test your design.
John Ousterhout 00:26:51 Absolutely. Yeah.
Jeff Doolittle 00:26:52 Which makes a lot of sense.
John Ousterhout 00:26:54 Another related thing I tell my students in testing your design is, your code will speak to you if only you will listen. And this gets one of the things in the book that I think is most useful for beginners is red flags. That things you can see that will tell you that you’re probably on the wrong track in terms of designing, maybe to revisit something, but becoming aware of those so that you can get feedback from your systems themselves, they would use what you can observe about a system in order to learn what’s good and bad. And also in order to improve your design skills.
Jeff Doolittle 00:27:26 Absolutely. And there’s a great list of some of those red flags at the back of your book, as a reference for people. You’ve mentioned a couple times the word modules, and maybe it would be helpful before we dig in a little bit more into modules and layers, what are those words mean when you use them? To kind of help frame the upcoming sections here.
John Ousterhout 00:27:48 I think of a module as something that encapsulate a particular set of related functions. And I define modules really in terms of this complexity thing again. I think of a module is a vehicle for reducing overall system complexity. And the goal of a module, which I think is the same as the goal of abstraction, is to provide a simple way to think about something that’s actually complicated. That’s the idea, the notion that, that you have a very simple interface to something with a lot of functionality. In the book I use the word Deep to describe modules like that, thinking I use the analog of a rectangle where the area of the rectangle is the functionality of a module and the length of its upper edge is the complexity of the interface. And so the ideal modules those would have very interfaces so it’s a very tall skinny rectangle. Small interface and a lot of functionality. Shallow modules are those, that have a lot of interface and not much functionality. And the reasonís that’s bad is because of thatís interfaceís complexity. That the interface is the complexity that a module imposes on the rest of the system. And so we’d like to minimize that. So because lots of people will have to be aware of that interface. Not so many people will have to be aware of any internal complexity of the module.
Jeff Doolittle 00:29:12 Yeah, I saw this early in my career, and I still see it a lot, but not on systems I’m working on because I don’t do it anymore. But in the early days, what you could call forms over data applications, where it was, Here’s just a bunch of data entry screens, and then you can run reports. And when you do that, where does all the complexity reside and where does all the tacit knowledge live? Well, it lives in the end users. So then you have these highly trained end users that when they leave the company, everybody gets terrified because there went everything and all the knowledge. And, and now it seems that what we’ve done is we’ve said, well, let’s at least move that complexity into the application, but it ends up in front of the applications, which are now just having all that complexity inside them.
Jeff Doolittle 00:29:50 And they’re trying to orchestrate complex interactions with a bunch of different systems, and that’s not really solving the problem either. So I imagine when you say module, you don’t mean either of those two things, you mean, get it even further down, further away, right? In other words, like you don’t want the dashboard of your car, controlling your engine timing, but it seems to me, that’s the state of a lot of web applications where the front end is controlling the system in ways that really the system should be owning that complexity on behalf of the front end or the end user.
John Ousterhout 00:30:19 I think that sounds right. You’d like to separate the functions out so you don’t have one place that has a whole lot of knowledge because thatís going to be a whole lot of complexity in that one place. Now itís a little hard in application. A lot of stuff comes together at the top layout, the gooey layer. So that layer may have to have at least some knowledge of lots of other parts of the system, because it’s combining all those together to present to the user. So it’s a little harder, it’s a little harder to get modularity or sort of deep classes when you’re talking about the user at a face layout. And I think that’s just part of that is just structural because of the nature of the, of what it does. But youíd like to have as little of the system thatís possible to have that layout.
Jeff Doolittle 00:31:01 So modules, you mentioned, they’re basically taking complexity and they’re reducing the experience of that complexity for the consumer of that module in a sense.
John Ousterhout 00:31:12 Highly, right.
Jeff Doolittle 00:31:13 Right, right. Which goes back to the parnos paper as well, which weíll link in the show notes. And so then, talk about layers and how those relate them to modules.
John Ousterhout 00:31:22 I tend to think of layers as methods that call methods, that call methods. Or classes that depend on classes that depend on classes. And so that creates potentially a layered system. Although personally, when I code, I don’t really think about layers that much. I don’t think about a system as having discreet layers because the systems tend to be so complicated that that diagram would be very complex where, you know, sometimes layer a depends on layer B. And sometimes it may also depend on layer C at the same time, while B depends on C, that graph of usage to me has always felt very complex. And, I’m not sure I really have to understand that so much. If you’ve really got modularity that is these classes encapsulate well, I think I would argue that that that’s a more important way of thinking about systems than in terms of the layers.
Jeff Doolittle 00:32:15 Well, it sounds like too, when you’re saying layers there, there’s, there’s a relationship to dependencies there. If a method has to call another method on another class or another interface, there’s a dependency relationship there.
John Ousterhout 00:32:26 Yeah. Yeah. I definitely, I would agree with those are important. It’s just, it’s very hard, I think, to think systemically about all the dependencies. There’s no way you could look at a complex system and in your mind visualize all the dependencies between classes.
Jeff Doolittle 00:32:40 Right. Or necessarily have all dependencies have a certain classification of a certain layer, which kinda classic end tier architecture tried to do. But maybe in if I’m understanding you correctly, maybe that’s pretending we’re dealing with complexity, but we’re maybe, actually not?
John Ousterhout 00:32:55 Yeah, just that systems, big systems really don’t decompose naturally into perfect layers. Occasionally it works, you know, the TCP protocol is layered on top of the IP network protocol, which is layered on top of some underlying ethernet transport system. So there, the layering works pretty well and you can think about three distinct layers. But in general, I don’t think large software systems tend to break down cleanly into a perfect layer diagram.
Jeff Doolittle 00:33:21 Yeah. And I think part of the reason you just mentioned, you know, TCP, I think HTTP is another example of what I’ve read recently. You can call the narrow waste and that’s another design approach to things is if everything boils down to byte streams or text, there’s a narrow waist there. And from my experience, it seems that layering can really work really well in that kind of context, but not every system that we’re building necessarily has that narrow of a waist and maybe layering doesn’t quite apply as well in those type of situations.
John Ousterhout 00:33:50 I would HTTP is a great example of a deep module. Pretty simple interface. The basic protocolís very simple, relatively easy to implement, and yet it has allowed tremendous interconnectivity in the web and in the internet. So many different systems have been to communicate with each other effectively. Itís a really great example. Hiding a lot of complexity, making tremendous functionality possible with a pretty simple interface.
Jeff Doolittle 00:34:16 Yes. And I would say it’s also a classic example of just how much incidental complexity we can add on top of something that isn’t itself necessarily complex.
John Ousterhout 00:34:25 Maybe the corollary here is that people will always find ways of, of making systems more complicated than you would like.
Jeff Doolittle 00:34:31 Oh, that is absolutely true. Yes. Especially when there’s deadlines. Okay. So I think we have a better understanding of modules and layers then. So maybe talk a little bit more about what it means that modules should be deep. Like you mentioned a second ago about, you know, there’s sort of narrow and there’s a simple interface, so explore that a little bit more for us. So listeners can start thinking about how they can design modules that tend to be deep rather than shallow.
John Ousterhout 00:34:57 OK. So there’s two ways you can think about a module. One is in terms of what functionality it provides and one is in terms of the interface. But let’s start with the interface because I think that’s the key thing. The interface is everything that anyone needs to know in order to use the module. And to be clear, that’s not just the signatures of the methods. Yes, those are part of the interface, but there’s lots more, you know, side effects or expectations or dependencies. You must invoke this method before you invoke that method. Any piece of information that a user has to know in order to use the module that’s part of its interface. And so when you’re thinking about the complexity of interface, it’s important to think about all that. Functionality is harder to define. That’s just what it does. Maybe it’s the right way to think about a system with a lot of functionality, maybe it’s that it can be used in many, many different situations to perform different tasks. Maybe that’s the right way to think about it. I don’t have as good a definition. Maybe you have thoughts about how would you define the functionality of a module? You know, what makes one module more functional than another? Well,
Jeff Doolittle 00:35:55 I think my, my first thought is it relates somewhat back to what you said before about I call the technical empathy. But when you were referring before to the, the martyr principle, right, pulling complexity downward, the more complexity you can contain in a module through a simpler interface, I think would tend to add towards that richness and that depth. So, you know, for example, the power outlet is a wonderful example of an amazing abstraction. And, and I spend a lot of time thinking about it because it’s a great way. I think too, to help us think about how to simplify our software systems. I can plug any and all appliances into that simple power outlet. If I go to another country, I just need an adapter and I can still plug into it. And where’s the power coming from behind it? Well, I don’t know.
Jeff Doolittle 00:36:30 I know the options perhaps, but do I know exactly where this electron came from? I don’t. Right. And there’s a ton of complexity, then that’s encapsulated in that very simple interface. So for me, that, that’s how I kind of view as a deep module would be one that gives me a very simple interface by shielding me from a ton of complexity. Then I may want to think about and know about, right? For example, if I’m environmentally conscious, I might care about where my powers coming from, but when I go to plug in my vacuum, I’m probably not asking myself that question at the moment.
John Ousterhout 00:36:58 Yeah. Another way of thinking about it is really good modules, they just do the right thing. They donít have to be told, they just do the right thing. Here’s an example. I could tell you, I know for a fact, what is the world’s deepest interface. And what it is, is a garbage collector. Because when you add a garbage collector to a system, it actually reduces the interface. It has a negative interface because you no longer have a free method you have to call. Before you introduce the garbage collector you have to call free, now you donít. There is no interface with garbage collector. It just sneaks around behind the scenes and figures out what memory’s not being used and returns it to the pool so you can allocate from it. So that’s an example of just do the right thing. I don’t care how you do it. Just figure out when I’m done with memory and put it back in the free pool.
Jeff Doolittle 00:37:40 That’s a great point. So in that case, the interface is effectively zero from the standpoint of the end user, although, you call GC suppress finalized when you’re disposing, but that’s a whole another conversation for another day, but yes, and you’re right. That it does hide a lot of complexity from you in that sense. You know, I think as well of, you know, SQL databases that give you a well supposed to be a simple human readable language, but the complexity of what it does under the covers of query planning and you know, which indexes to use and these sort of things in trying to reduce table scanning, that’s a lot complexity thatís shielded behind. What is a much simpler language in comparison to what is actually happening under the covers.
John Ousterhout 00:38:21 Oh yeah SQL is a beautiful example of a very deep interface. Another one, one of my favorites is a spreadsheet. What an amazingly simple interface. We just have a two dimensional grid in which people could enter numbers or formulas. You could describe it in like that in three sentence. And now of course, people have added lots of bells and whistles over the years, but the basic idea is so simple and yet it’s so incredibly powerful. The number of things people can use spreadsheets for, it’s just astounding.
Jeff Doolittle 00:38:44 It is. And Microsoft Excel now has a function called Lambda. And so therefore spreadsheets are now Turing complete. But interestingly there with great power comes great responsibility. And I’m sure you’ve seen as I have some of the nastiest spreadsheets you could possibly imagine. And that’s, probably because design wasn’t really a thought. It was just, implement, implement, implement.
John Ousterhout 00:39:07 I don’t believe there is any way to prevent people from producing complicated systems. And sometimes or for that matter, to prevent people from introducing bugs, and sometimes systems go out of the way to try and prevent people from doing bad things. In my experience as often as not, those system also prevent people from doing good things. And so I think we should design to make it as easy as possible to do the right thing and then not worry too much if people abuse it, because that’s just going to happen and we can’t stop them.
Jeff Doolittle 00:39:38 I mean, you hope that with some code reviews, which from what we’re talking to it, you know, suggest to me that your code reviews should also be design reviews, that those could there’d be mechanisms to try to check this, but you can’t be paranoid and try to prevent any and all bugs in your system. Right?
John Ousterhout 00:39:54 Absolutely.
Jeff Doolittle 00:39:55 Yeah. So speak a little bit more to that. You know, I mentioned code review is a time not just for reviewing the code and the implementation, but also the design. So how do you encourage students or how have you experienced that before, where you try to introduce a design review as well in the code review process?
John Ousterhout 00:40:09 Well, to me, I just don’t separate those. When I review people’s code. If they ask me to review their code, they’re getting design feedback as well. Now you know, there may be times in a project where they just aren’t in a position to take that design feedback and act on it. But when I review, I’m going to provide it anyway, then I would argue people should anyway, just so that people are aware about it. And even if you can’t fix it today, you can put it on your to-do list that maybe when you get a little cleanup time after the next deadline, we can go back and get it. So I just, I feel like code reviews ought to be holistic things that look at, we want to find all of the possible ways of improving this software. We shouldn’t limit it to just certain kinds of improvements.
Jeff Doolittle 00:40:46 Yeah. I think that’s a great way of looking at it. And, and also recognizing that as you become more familiar with the design and you improve it over time, the design limits, the cognitive burden because now you can have a sense of knowing, well, where am I in the system? Where does this code live within the system? Right. And if you find code, that’s touching too many places in the system that sounds to me like a design smell or, or what you call red flag.
John Ousterhout 00:41:09 Like maybe that’ll be a red flag.
Jeff Doolittle 00:41:11 Yeah. I have to touch five modules in order to get this new functionality.
John Ousterhout 00:41:15 Sometimes you have to do it and that’s the best you can do, but it’s definitely a red flag. That’s the kind of thing where if I saw that, I would say, suppose, suppose I made the rule, we simply can’t do this. I simply will not do this. What would happen? Would I have to simply shut the system down? Or could I find some other way that gets around this problem? And what’s interesting is once if you see a red flag and you say, suppose I must eliminate this red flag. You almost always can.
Jeff Doolittle 00:41:39 Hmm. Yeah. And that’s one of those things too, where you mentioned, sometimes you have to touch five modules. The problem is when the sometimes becomes, well, this is just how we do it now because nobody stopped. And did the design thinking to say, why are we having to touch five modules every time we need to make a change like this?
John Ousterhout 00:41:53 Yeah. I’m not really good with the, the argument. Well, this is how we do it. So I realized that may be a necessity in some environments,
Jeff Doolittle 00:42:02 And I don’t even, and I don’t even necessarily mean as an argument, just more as a reality. Meaning people become, there’s a sense where people’s pain tolerance increases with familiarity. And so if you’re touching the same five modules over and over again, to make a certain kind of change without a design review or design thinking, I think people can just think even if they donít state it, ìthis is how we do itî, it just becomes how they do it. As opposed to saying, can we simplify the design by putting all that complexity together in a module so that we’re not having to touch five modules every time?
John Ousterhout 00:42:33 Yeah. I’m more of a rip the band aid off kind of person, but I donít want to constantly expose these things and get people thinking about them. But then again, I recognize, well, if you’re building a commercial product, there are certain constraints you have to work on. Itís dangerous to let those become too ingrained in you to the point where you, you no longer realize the costs that they’re incurring.
Jeff Doolittle 00:42:53 Yeah, that’s right. And that’s where I think, again, those having those red flags at the ready to be able to say, are we, are we having, are we experiencing red flag here? What can we do about it? And then comparing that to the pros and cons. Because there’s always tradeoffs and maybe you’re not going to fix it today, but you know, you’re going to have to fix it soon. And then you start thinking, well how can we do that incrementally and improve bit by bit instead of just accumulating the same mess over and over again. So let’s talk now a little bit about, we’ve talked about interfaces to modules and modules themselves and what they do, but someday we actually have to implement something. So one of the design principles is that working code isn’t enough. Now this sounds like a challenge to me. And I know you like putting challenges out there and making theories. So when I hear working code, I think of certain books like, you know, maybe Clean Code or certain aspects of the, you know, the agile methodologies that say what we care about is working code, but you say it’s not enough. So, speak to that a little bit and how maybe that disagrees with what the broader prevailing wisdom might say.
John Ousterhout 00:43:49 Well, who could object to code that works first of all. So how could I not be satisfied? That’s unreasonable.
Jeff Doolittle 00:43:56 Okay. So you’re upstream here.
John Ousterhout 00:43:59 So what I would say is actually yes, working code is the ultimate goal, but it’s not just working code today. It’s working code tomorrow and next year and year after that. What project can you point to and say, this project has already invested more than half of the total effort that ever be invested in this project. Be hard to point to anyone most of your investment in softwares, in the future for any project. And so the most important thing I would argue is to make that future development go fast, as opposed to you don’t want to make tradeoffs for today that make your future development go more slowly. And so that’s the key idea, that’s what I call I, I call the, the working code approach, the tactical approach, where we just focus on fixing the next deadline. And if you add a few extra bits of complexity in order to do that, you argue well that’s okay because we have to finish faster. And I contrast that to the strategic approach, where the goal is to produce the best design so that in the future, we can also develop as fast as possible. And of course other people use the word technical debt, which is a great way of characterizing this. You’re basically borrowing from the future when you code tactically, you’re saving little time today, but you’re going to pay it back with interest in the future. And so that’s why I argue for you should be thinking a little bit ahead. You need to be thinking about what will allow us to develop fast, not just today, but next year also.
Jeff Doolittle 00:45:15 Yeah. I just had an episode a few months ago with Ipek Ozkaya and she co-wrote a book she’s from the IEEE and we’ll put a link in the show notes. Her book is called Managing Technical Debt. And you mentioned before the idea of investing in design and similar concept now too, is view this as an investment and there’s debt and the debt will have interest and you will need to pay that interest at some point. And so that concept relates very much to the concept in that book. So speaking of, of technical debt and the, and the ways we tackle those things, you mentioned a second ago, the difference between being strategic and being tactical. And I’d like to explore that a little bit more because in the book you coin one of my favorite phrases now, which is, is hard to avoid using too often, which is the idea of a tactical tornado. So maybe explain for our listeners what a tactical tornado is, and then how good design can help prevent the tactical tornado syndrome.
John Ousterhout 00:46:04 Every organization has at least one tactical tornado. I’ve worked with them. I bet you’ve worked with them. When I ask for a show of hands. When I give talks about how many of you have worked with tactical tornadoes, virtually everybody raises their hands. Actually, then I ask how many of you think you might be a technical tornado? How many people will raise their hand? A tactical tornado is, is the ultimate tactical programmer. Do whatever it takes to make progress today, no matter how much damage it causes in the system. Often you see this, this is a person that will get a project, 80% of the way working, and then abandon it and work on the next project. The first chunk, make tremendous progress and leave it to other people to clean up all the mess at the end or the person that will, you know, when there’s a bug that needs to get fixed overnight.
John Ousterhout 00:46:46 Oh, they’ll fix it. But they’ll introduce two more bugs that other people have to come along later on. And what’s ironic about them is often managers consider these people heroes. Oh yeah. If I need something done in a hurry, I can just go to so and so and they’ll get it done. And then everybody else has to come along and clean up after them. And sometimes to those people, I’m not getting any work done because I’m cleaning up so and so’s problems. And so every organization has them. I just, I think what you need is management that doesn’t support those people. And recognizes again that these people are doing damage and not just fixing the bug, but also think about all the other damage they do. And I assume you’ve worked with tactical tornadoes over your career.
Jeff Doolittle 00:47:22 Well, I think there’s another category, which is recovering tactical tornadoes that you, you didn’t mention.
John Ousterhout 00:47:27 Meaning can you intervention with them?
Jeff Doolittle 00:47:29 Well meaning if you go back far enough in my career, there was a time where that moniker probably would’ve applied to me, but that’s going way back. But I think that’s another category is, you know, there’s individuals who are, most people are trying to do the right thing, but maybe the incentives are not set up properly or the system, you know, the general system around them is maybe not oriented to help them fall into the pit of success, right? Or the tendency to do the right thing. So I imagine for a lot of people who are doing that, it’s not necessarily that they’re nefarious or they just want to pass off all their, all their work to somebody. There may be some, but I think for a lot of people, it’s just the recognition of we’ve mentioned technical empathy before and things like this is, am I leaving bad things in my wake for the people behind me? And so I think you mentioned one is management support, but then I think also just a cultural ethos of, we try to build things that make other people’s lives easier and not just do things that make me look good or, or make it easy for me.
John Ousterhout 00:48:22 Yes, I think education is a big part of that. You need to recognize what happens and talk to the people and explain the problems with their approach. And hopefully you can convert them. I had a humorous experience in a recent startup. I was involved in where a new engineer came on board. We had a very strong culture of unit testing at the company. And so our software had pretty much hundred percent code coverage unit test. This engineer came in, apparently wasn’t used to having unit tests and he came and said, wow, this is fantastic. I can make changes so quickly. And I just run the unit test and everything works. These unit are fantastic. And then after a week or two, and the person had pushed a bunch of commits, I went back and said, you haven’t added any unit tests for the code you wrote and said, Oh, I need to write unit tests? And somehow was not able to make the tie in between the benefit he received from unit tests and the importance of actually writing them. So we had a talk and he started doing unit tests and everything was fine after that, but it had just never occurred to him that he should also have to write unit tests.
Jeff Doolittle 00:49:25 Oh, that’s hilarious. Well, then my other favorite is when people talk about refactoring, and they don’t have test coverage. And I say, well, refactoring is changing the implementation without changing the external behavior. And the even worse one is when they’re changing the unit tests constantly. When they change the implementation, it’s going just think about that for a minute. If somebody, you know, who was testing your automobile, did that, would you really trust that car? You’d probably be terrified. Yeah, it’s funny how those things sneak in, but that that’s a great point too, right? That that often people are teachable. Maybe they just don’t know, they don’t know better. And then having that team culture that says, this is how we do things and then helping introduce people to it can definitely help. Another design principle regarding implementation. And I think some explanation here will be helpful. The increments of software development should be abstractions, not features. Now we talked a second ago about how certain managers might really like those tactical tornadoes. And I imagine they might hear this and say, hold on a minute, you’re telling me the increments, which I imagine you mean the deliveries of software development should be abstractions, not features. And they’re going to cry out where are my features?
John Ousterhout 00:50:34 Well, OK. So like all design principles, this one doesn’t apply everywhere. And of course there are places where features matter. I listed this principle mostly in reaction to test driven design, where in which you don’t really do any design, you write a set of tests for the functionality you want, and then which all of which break initially. And then the software development process consists of simply going through making those tests pass one after another, until eventually have all the features you want. And the problem with this is that there’s never really a good point to design. And so you tend to just kind of throw things together. This tends really bad designs. And so what I would argue is as much as possible when you’re adding onto your system, try and do that by creating new abstractions. When you go and do it, build the whole abstraction, don’t just build the one tiny piece of the app abstraction that you need right now. Think about, think about what the real abstraction would be. Now that said, of course, there’s the top level in your system where you’re building features. Yeah. Yeah. So that’s, that system is going to be all about, add that part of the, going to be all about adding features, but most of your system, hopefully these underlying modules that get used.
Jeff Doolittle 00:51:37 Sure. Although I guess it depends on how you define feature, but from my standpoint, it’s, it’s sort of like, there is no spoon in the matrix. There is no features. Features are emergent properties of a composition of well-designed components. And that’s just how the world works. So nobody nobody’s actually building features, but good, you know, good luck explaining this to managers, eyes clays over, they say, but I want my features. That’s well, youíll get your features. But I guess I, you know, for me, I’d push this principle a little bit further and say, it’s maybe closer to axiomatic from my perspective that it absolutely should be abstractions and not features. But again, that’s also dependent on how you define feature, of course.
John Ousterhout 00:52:14 This is a way of thinking about, I think when you’re doing agile design, again, as you, what are the units that you’re adding onto your system? And that’s why I would say this should mostly be abstractions.
Jeff Doolittle 00:52:22 Yeah. So you talked about test driven design and there’s TDD, which could mean test driven development or test-driven design. So maybe talk about that a little bit more, because that sounds like that could be controversial for some listeners.
John Ousterhout 00:52:33 Yeah actually, sorry. I misspoke. I meant test driven development.
Jeff Doolittle 00:52:36 Oh, okay. So you did mean the same thing. And so the implication there is that we have these tests and then we build our software that could lead to a bad design is what you’re stating.
John Ousterhout 00:52:44 Yes. I think it’s highly likely to lead to a bad design, so I’m not a fan of TDD. Okay. I think it’s better to again, build a whole abstraction. And then I think actually better to write the tests afterwards, to when I write tests, I tend to do white box testing. That is, I look at the code I’m testing and I write tests to test that code that way I can make sure for example, that, that every loop has been tested and every condition, every if statement has been tested and so on.
Jeff Doolittle 00:53:09 So how do you avoid coupling your test to the implementation in that kind of an environment?
John Ousterhout 00:53:13 Well, there’s some risk of that, but then I mostly argue, is that a problem or is that a feature? And so the, the risk of that is that when you make change in implementation, you may have to make significant changes to your tests. And so that’s not, that’s not a bad thing, except that it’s extra work. I don’t see any, the only problem with that is it just takes longer to do it. As long as you’re not doing that a lot, as long as you’re not having to massive refactoring your tests all the time, then I’m okay with that. But you know, this is an area which I may just, other people might disagree with me on this one.
Jeff Doolittle 00:53:45 Yeah. And this, isn’t the show where I push your ideas against mine, but that might be a fun conversation to have maybe another context. But you did mention though that you encouraged starting with the abstraction and then writing your test against that. And so that does sound like, that could lend also towards more, you know, opaque testing as opposed to, you know, testing the implementation directly.
John Ousterhout 00:54:07 Yeah. Again, when I write test, I don’t actually test the abstraction. I tend to test the implementation. That’s actually the way I tend to do it. And just because I feel like I can test more thoroughly if I don’t look at the implementation at all, I think it’s more likely that they’re going to be things that Iím not going to notice to test. By the way I will say the failure of my approach to testing, is very good at catching errors by commission. Itís not so good at testing errors of omission. That is if you failed to implement something, then you’re not going to test for it. And you won’t notice that. And so if there’s something you should be doing that your code doesn’t do at all this style of testing will not get that. Maybe if you test it from the abstraction, maybe you would think about that and maybe you’d write a test that would catch that
Jeff Doolittle 00:54:52 Well, and this is where I’ll join your camp on TDD. In the sense of, I think that’s one of the that’s one of the struggles of TDD is I don’t think it works once a system gets beyond a certain amount of simplicity because you just cannot conceive of enough tests to actually have the full functionality emerge. It’s impossible. There’s, there’s diminishing returns on the amount of time. You can spend defining those tests and you will never have enough tests to have a full complex system emerge from that. And, and as you pointed out, it can also lead to poor design. So listeners can definitely have fun interacting with you on your Google groups channel after the show about TDD. Keep is civil people.
John Ousterhout 00:55:28 There is actually one place where I agree TDD is a good idea. That’s when fixing bugs. Before you fix a bug, you add a unit test that triggers the bug. Make sure the unit test fails, then fix the bug and make sure the unit test passes, because otherwise you run the risk that you having to actually fix the bug.
Jeff Doolittle 00:55:44 100%. I’d also say, and I think you’ll agree. That’s another element of a good design is that you can do what you just described. And if you can’t do what you just described, you should be asking yourself how to improve the design so that you can.
John Ousterhout 00:55:56 Yeah. That says something is not testable somehow. Yeah,
Jeff Doolittle 00:55:59 Exactly. So testability is another hallmark. And specifically what you just said, because I agree if you can write a failing test that exposes the air condition first, then you have confidence when that test passes that you solve that problem. And of course, if your other tests still pass, you know, you haven’t accidentally broken something else. At least that was tested previously. You still, you still could have broken something else, but it wasn’t something that you were testing previously. So it does increase your confidence, which is, which is good. Comments should describe things that are not obvious from the code. I have a feeling this principle might also be slightly controversial.
John Ousterhout 00:56:32 This principle is controversial in that there seems to a fairly large group of people who think that comments are not needed, or even compliments are a bad idea. For example, Robert Martin in his book, Clean Code, which is, I think one of the most popular books on software design, it’s certainly way farther up the Amazon list of most of bestselling books than my book is, for example. He says, and I believe the direct quote is ìEvery comment is a failureî. And the implication is that if you had to write a comment, it means you failed to make everything clear from your code. Well, I disagree with this point. I think that fundamentally it is not possible to describe in code all the things that people need to know in order to understand that code. You simply cannot do that. And that’s the purpose of comments.
John Ousterhout 00:57:23 So for example, in an interface, there are certain things you cannot describe in comments. If one method must be called before the other one, there’s no way in, in any modern programming language where you can describe that in the code itself. And there’s just many other examples. If you look at any piece of code, there are things that are important that people need know that simply canít be describe in the code. So if you want to have that abstraction, you really want to hide complexity, you have to have comments to do that. The alternative is you have to read the code of the module in order to understand it. That’s not, if you have to read the code, then you’re exposed to all of that internal complexity. You haven’t hidden any complexity. So I’m a very strong advocate of comments. Now I recognize that people sometimes don’t write good comments. And you know, the flip side of this is that the other mistake you can make is writing a comment that simply duplicates what’s in the code. With all in the comment ìAdd 1 to variable I followed by the statement I = I + 1î.
John Ousterhout 00:58:36 Those comments are useless, because theyíre simply repeating whatís in the code. Another example, I bet youíve seen this when you read the documentation. And you read the, for example, the Java docs for a method or the doc documentation, and there will be a method called Handle page fault. And what will the comment at the top say? Handle a page fault. So what has that comment added that wasn’t already obvious from the code? The word ìaî there’s no useful information there. So this is a double edged sword. It’s really important to think about what is not obvious from the code and document that, at the same time, don’t waste your time writing comments that simply repeat what you get from the code. So when you’re documenting a method, use different words from the variable name, don’t use the same words.
Jeff Doolittle 00:59:16 Or worse, the comments don’t match what the implementation actually does, which I think is part of the reason that Robert Martin might speak against that. But the ability to make bad comments is not a reason to have no comments.
John Ousterhout 00:59:28 Thatís right and there’s a risk that comments can become stale. That’s one of the four excuses people use for not writing comments. They say theyíll become stale anyway so why bother? But in my experience, it’s not that difficult to keep comments mostly up to date. There will occasionally be errors, but almost all the comments will still be accurate.
Jeff Doolittle 00:59:45 Yeah. And if people are using the software and are using the documentation to help them know how to use the software, then that can also be a way to keep them up to date if they’re not reflecting reality any longer.
John Ousterhout 00:59:56 Right. And the other thing is to think about where you put your comments, which is you want the comments as close as possible to the code that they’re describing so that if you change the code, you’re likely to see the comment and change it also.
Jeff Doolittle 01:00:07 Right. Which I would argue is true for all documentation, meaning the closer your documentation lives to the abstractions and implementations, the better, and the more likely it’ll be kept up to date. So one last principle that I want to talk about before we wrap up, ìSoftware should be designed for ease of reading, not ease of writing.î I think this definitely relates to some things we said previously, but talk a little bit more about what does that mean? Ease of reading versus ease of writing and how does that play out in software systems in your experience?
John Ousterhout 01:00:34 Well, there are various shortcuts you could often use that, make code a little bit easier to write, but make it harder to read? Two classic examples, pet peeves of mine about C++. The first one is the keyword auto, which you can use to say, ìI’m not going to tell you what type of variable this is. You, Madam Compiler, please figure it out on your own and just use the right type.î It’s super convenient and easy to use. But now when somebody reads the code, they have no way of, they have to go through themselves, basically repeat the compilers to try to figure out what type of thing this is. Another one is standard pair, is pair abstraction with the first and the second. Super easy if you need to return two values from a method, just return a pair. But the problem now is that everybody’s referring to the element of this result as result.first and result.second. And who knows what those actually are in fact? So the code was a little bit easier to write, you didnít have to spend the time to define a custom structure to return these things, but itís much harder to read. Not putting comments is another example. It makes it faster to write the code, but harder to read. And there’s, there’s a variety of other things. So if you just keep that in mind and ask yourself, ìAm I making this code as easy as possible to read?î Even if it takes you more time as writer, the thing is that code will be read a lot more times than it was written. And so it pays for itself.
Jeff Doolittle 01:01:51 The code will be read a lot more often than it’s written. And also the maintenance life cycle of the code will vastly exceed the development life cycle of the code.
John Ousterhout 01:01:59 You know, one of the problems, I think people forget, people forget that they forget. When they’re writing the code, they don’t think about the fact that even if I come back to this in three months, I’m not going to remember why I did this.
Jeff Doolittle 01:02:08 Yeah. That’s right. That’s why it’s so important sometimes to do a, get blame on code and then recognize that you are the one who did it. Right? That’s just, it’s a very important experience for everyone, ìWho wrote this terrible code?î Get blame, okay, I’m going to be quiet now. Yeah, that’s right. That’s right. Very important experience. John, is there anything else that you want to cover that maybe we’ve missed or any closing thoughts?
John Ousterhout 01:02:28 No, I think you’ve covered just about everything. This has been a really fun conversation.
Jeff Doolittle 01:02:31 I agree. And I definitely encourage listeners to get your book. And my understanding too, is there’s a Google group that they can join if they want to continue the conversation with you from here.
John Ousterhout 01:02:40 That is correct. I think it’s called [email protected]
Jeff Doolittle 01:02:44 Great. And we’ll definitely put a link to that in the show notes as well. If listeners want to find you on Twitter, is it JohnOusterhout@JohnOusterhout?
John Ousterhout 01:02:51 Uh, yes. I believe that’s right. They can always just Google me too. And that’ll probably get them started on finding. But I’m on Twitter. Yep. And I’m happy to take email. As I said at the beginning, I don’t claim to have all the answers. I’m still learning myself. The actual teaching of the course has actually changed my opinions about software design in a few ways. And so I’m eager to continue learning. So if there are things you see in the book that you think are wrong headed, I’d love to hear why you think that. Or if you have other design ideas that you think are really important that I haven’t mentioned, I’d love to hear those as well. And if you think there’s a parallel universe, getting back to our very leading-off question about whether design is absolute or relative, if you think there’s an alternative universe of design, that is totally disjointed from what I talk about and yet a really good world. I’d love to hear about that as well.
Jeff Doolittle 01:03:35 Awesome. Awesome. I love that perspective. I love your temperament and your desire to just learn. The ability to be a lifelong learner is a critical skill, I think, in our industry. So thanks for just demonstrating that for us in the way you approach these things.
John Ousterhout 01:03:49 Well, thanks for the conversation. I’ve enjoyed it.
Jeff Doolittle 01:03:51 All right. Well everyone, thanks so much for joining John and me today on Software Engineering Radio. This is Jeff Doolitle, thanks for listening.
[End of Audio]